Add turn types to offline routing
This commit is contained in:
parent
fb1815c01d
commit
794c3106cf
15 changed files with 152 additions and 90 deletions
|
@ -172,7 +172,7 @@ public class BinaryRoutePlanner {
|
|||
* Calculate route between start.segmentEnd and end.segmentStart (using A* algorithm)
|
||||
* return list of segments
|
||||
*/
|
||||
public List<RouteSegmentResult> searchRoute(final RoutingContext ctx, RouteSegment start, RouteSegment end) throws IOException {
|
||||
public List<RouteSegmentResult> searchRoute(final RoutingContext ctx, RouteSegment start, RouteSegment end, boolean leftSideNavigation) throws IOException {
|
||||
// measure time
|
||||
ctx.timeToLoad = 0;
|
||||
ctx.visitedSegments = 0;
|
||||
|
@ -281,7 +281,7 @@ public class BinaryRoutePlanner {
|
|||
printDebugMemoryInformation(ctx, graphDirectSegments, graphReverseSegments, visitedDirectSegments, visitedOppositeSegments);
|
||||
|
||||
// 4. Route is found : collect all segments and prepare result
|
||||
return prepareResult(ctx, start, end);
|
||||
return prepareResult(ctx, start, end, leftSideNavigation);
|
||||
|
||||
}
|
||||
|
||||
|
@ -806,7 +806,7 @@ public class BinaryRoutePlanner {
|
|||
/**
|
||||
* Helper method to prepare final result
|
||||
*/
|
||||
private List<RouteSegmentResult> prepareResult(RoutingContext ctx, RouteSegment start, RouteSegment end) {
|
||||
private List<RouteSegmentResult> prepareResult(RoutingContext ctx, RouteSegment start, RouteSegment end, boolean leftside) {
|
||||
List<RouteSegmentResult> result = new ArrayList<RouteSegmentResult>();
|
||||
|
||||
RouteSegment segment = ctx.finalReverseRoute;
|
||||
|
@ -861,22 +861,26 @@ public class BinaryRoutePlanner {
|
|||
completeTime += distOnRoadToPass;
|
||||
completeDistance += distance;
|
||||
}
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
result.get(i).setDescription(getDescription(result, i));
|
||||
}
|
||||
// update distance description
|
||||
int toUpdate = 0;
|
||||
for (int i = 1; i < result.size(); i++) {
|
||||
if (result.get(i).getDescription().length() != 0) {
|
||||
float dist = 0;
|
||||
for (int j = toUpdate; j < i; j++) {
|
||||
dist += result.get(j).getDistance();
|
||||
int toUpdate = -1;
|
||||
float dist = 0;
|
||||
for (int i = 0; i <= result.size(); i++) {
|
||||
TurnType t = null;
|
||||
if (i < result.size()) {
|
||||
t = getTurnInfo(result, i, leftside);
|
||||
result.get(i).setTurnType(t);
|
||||
}
|
||||
if (t != null || i == result.size()) {
|
||||
if (toUpdate >= 0) {
|
||||
result.get(toUpdate).setDescription(
|
||||
result.get(toUpdate).getTurnType().toString() + String.format(" and go %.2f meters", dist));
|
||||
}
|
||||
result.get(toUpdate).setDescription(result.get(toUpdate).getDescription() + String.format(" %.2f meters", dist));
|
||||
toUpdate = i;
|
||||
dist = 0;
|
||||
}
|
||||
if ( i < result.size()) {
|
||||
dist += result.get(i).getDistance();
|
||||
}
|
||||
}
|
||||
|
||||
if (PRINT_TO_CONSOLE_ROUTE_INFORMATION_TO_TEST) {
|
||||
println("ROUTE : ");
|
||||
double startLat = MapUtils.get31LatitudeY(start.road.getPoint31YTile(start.segmentStart));
|
||||
|
@ -903,20 +907,22 @@ public class BinaryRoutePlanner {
|
|||
}
|
||||
|
||||
|
||||
private String getDescription(List<RouteSegmentResult> result, int i) {
|
||||
private TurnType getTurnInfo(List<RouteSegmentResult> result, int i, boolean leftSide) {
|
||||
if (i == 0) {
|
||||
return "Go ahead ";
|
||||
return TurnType.valueOf(TurnType.C, false);
|
||||
}
|
||||
RouteSegmentResult prev = result.get(i - 1) ;
|
||||
if(prev.getObject().roundabout()) {
|
||||
return "";
|
||||
return null;
|
||||
}
|
||||
RouteSegmentResult rr = result.get(i);
|
||||
if (rr.getObject().roundabout()) {
|
||||
int exit = 1;
|
||||
RouteSegmentResult last = rr;
|
||||
for (int j = i; j < result.size(); j++) {
|
||||
RouteSegmentResult rnext = result.get(j);
|
||||
if (rnext.getObject().roundabout()) {
|
||||
last = rnext;
|
||||
boolean plus = rnext.getStartPointIndex() < rnext.getEndPointIndex();
|
||||
int k = rnext.getStartPointIndex();
|
||||
if (j == i) {
|
||||
|
@ -933,31 +939,34 @@ public class BinaryRoutePlanner {
|
|||
}
|
||||
}
|
||||
// combine all roundabouts
|
||||
return "Round (exit " + exit + ") and go ";
|
||||
TurnType t = TurnType.valueOf("EXIT"+exit, leftSide);
|
||||
t.setTurnAngle((float) MapUtils.degreesDiff(last.getBearingBegin(), prev.getBearingEnd()));
|
||||
return t;
|
||||
}
|
||||
String description = "";
|
||||
TurnType t = null;
|
||||
if (prev != null) {
|
||||
// add description about turn
|
||||
double mpi = MapUtils.degreesDiff(prev.getBearingEnd(), rr.getBearingBegin());
|
||||
|
||||
if (mpi >= 50) {
|
||||
if (mpi < 60) {
|
||||
description = "Turn slightly left and go";
|
||||
t = TurnType.valueOf(TurnType.TSLL, leftSide);
|
||||
} else if (mpi < 120) {
|
||||
description = "Turn left and go";
|
||||
t = TurnType.valueOf(TurnType.TL, leftSide);
|
||||
} else if (mpi < 135) {
|
||||
description = "Turn sharply left and go";
|
||||
t = TurnType.valueOf(TurnType.TSHL, leftSide);
|
||||
} else {
|
||||
description = "Make uturn and go";
|
||||
t = TurnType.valueOf(TurnType.TU, leftSide);
|
||||
}
|
||||
} else if (mpi < -50) {
|
||||
if (mpi > -60) {
|
||||
description = "Turn slightly right and go";
|
||||
t = TurnType.valueOf(TurnType.TSLR, leftSide);
|
||||
} else if (mpi > -120) {
|
||||
description = "Turn right and go";
|
||||
t = TurnType.valueOf(TurnType.TR, leftSide);
|
||||
} else if (mpi > -135) {
|
||||
description = "Turn right left and go";
|
||||
t = TurnType.valueOf(TurnType.TSHR, leftSide);
|
||||
} else {
|
||||
description = "Make uturn and go";
|
||||
t = TurnType.valueOf(TurnType.TU, leftSide);
|
||||
}
|
||||
} else {
|
||||
// keep left/right
|
||||
|
@ -967,21 +976,24 @@ public class BinaryRoutePlanner {
|
|||
if(attachedRoutes != null){
|
||||
for(RouteSegmentResult rs : attachedRoutes){
|
||||
double ex = MapUtils.degreesDiff(rs.getBearingBegin(), rr.getBearingBegin());
|
||||
if(ex < 40 && ex >= 0) {
|
||||
if(ex < 30 && ex >= 0) {
|
||||
kl = true;
|
||||
} else if(ex > -40 && ex <= 0) {
|
||||
} else if(ex > -30 && ex <= 0) {
|
||||
kr = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kl) {
|
||||
description = "Keep left and go";
|
||||
t = TurnType.valueOf(TurnType.KL, leftSide);
|
||||
} else if(kr){
|
||||
description = "Keep right and go";
|
||||
t = TurnType.valueOf(TurnType.KR, leftSide);
|
||||
}
|
||||
}
|
||||
if(t != null) {
|
||||
t.setTurnAngle((float) -mpi);
|
||||
}
|
||||
}
|
||||
return description;
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ public class RouteSegmentResult {
|
|||
private float speed;
|
||||
private float distance;
|
||||
private String description = "";
|
||||
// this make not possible to make turns in between segment result for now
|
||||
private TurnType turnType;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public RouteSegmentResult(RouteDataObject object, int startPointIndex, int endPointIndex) {
|
||||
|
@ -45,6 +47,13 @@ public class RouteSegmentResult {
|
|||
return list;
|
||||
}
|
||||
|
||||
public TurnType getTurnType() {
|
||||
return turnType;
|
||||
}
|
||||
|
||||
public void setTurnType(TurnType turnType) {
|
||||
this.turnType = turnType;
|
||||
}
|
||||
|
||||
public RouteDataObject getObject() {
|
||||
return object;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package net.osmand.plus.routing;
|
||||
package net.osmand.router;
|
||||
|
||||
public class TurnType {
|
||||
public static final String C = "C"; // continue (go straight) //$NON-NLS-1$
|
||||
|
@ -75,4 +75,34 @@ public class TurnType {
|
|||
public boolean isRoundAbout() {
|
||||
return value.equals("EXIT"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if(isRoundAbout()){
|
||||
return "Take " + getExitOut() + " exit";
|
||||
} else if(value.equals(C)) {
|
||||
return "Go ahead";
|
||||
} else if(value.equals(TSLL)) {
|
||||
return "Turn slightly left";
|
||||
} else if(value.equals(TL)) {
|
||||
return "Turn left";
|
||||
} else if(value.equals(TSHL)) {
|
||||
return "Turn sharply left";
|
||||
} else if(value.equals(TSLR)) {
|
||||
return "Turn slightly right";
|
||||
} else if(value.equals(TR)) {
|
||||
return "Turn right";
|
||||
} else if(value.equals(TSHR)) {
|
||||
return "Turn sharply right";
|
||||
} else if(value.equals(TU)) {
|
||||
return "Make uturn";
|
||||
} else if(value.equals(TRU)) {
|
||||
return "Make uturn";
|
||||
} else if(value.equals(KL)) {
|
||||
return "Keep left";
|
||||
} else if(value.equals(KR)) {
|
||||
return "Keep right";
|
||||
}
|
||||
return super.toString();
|
||||
}
|
||||
}
|
|
@ -100,7 +100,7 @@ public class RouterTestsSuite {
|
|||
throw new IllegalArgumentException("End segment is not found for test : " + testDescription);
|
||||
}
|
||||
|
||||
List<RouteSegmentResult> route = planner.searchRoute(ctx, startSegment, endSegment);
|
||||
List<RouteSegmentResult> route = planner.searchRoute(ctx, startSegment, endSegment, false);
|
||||
|
||||
|
||||
NodeList segments = testCase.getElementsByTagName("segment");
|
||||
|
|
|
@ -630,7 +630,7 @@ public class MapRouterLayer implements MapPanelLayer {
|
|||
|
||||
});
|
||||
|
||||
List<RouteSegmentResult> searchRoute = router.searchRoute(ctx, st, e);
|
||||
List<RouteSegmentResult> searchRoute = router.searchRoute(ctx, st, e, false);
|
||||
if (animateRoutingCalculation) {
|
||||
playPauseButton.setVisible(false);
|
||||
nextTurn.setText("FINISH");
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
1. All your modified/created strings are in the top of the file (to make easier find what's translated).
|
||||
PLEASE: Have a look at http://code.google.com/p/osmand/wiki/UIConsistency, it may really improve your and our work :-) Thx - Hardy
|
||||
-->
|
||||
<string name="route_roundabout">Roundabout : take %1$d exit</string>
|
||||
<string name="route_kl">Keep left</string>
|
||||
<string name="route_kr">Keep right</string>
|
||||
<string name="rendering_attr_noPolygons_description">Make all areal land features on map transparent</string>
|
||||
<string name="rendering_attr_noPolygons_name">No polygons</string>
|
||||
<string name="rendering_attr_appMode_name">Rendering mode</string>
|
||||
|
|
|
@ -14,13 +14,11 @@ import net.osmand.Algoritms;
|
|||
import net.osmand.GPXUtilities.WptPt;
|
||||
import net.osmand.IProgress;
|
||||
import net.osmand.access.AccessibleToast;
|
||||
import net.osmand.plus.OsmandPlugin;
|
||||
import net.osmand.plus.OsmandSettings;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.ResourceManager;
|
||||
import net.osmand.plus.activities.LocalIndexHelper.LocalIndexInfo;
|
||||
import net.osmand.plus.activities.LocalIndexHelper.LocalIndexType;
|
||||
import net.osmand.plus.development.OsmandDevelopmentPlugin;
|
||||
import net.osmand.plus.osmedit.OpenstreetmapRemoteUtil;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
|
@ -46,15 +44,15 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
public class LocalIndexesActivity extends OsmandExpandableListActivity {
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package net.osmand.plus.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import net.osmand.plus.activities.search.SearchActivity;
|
||||
import android.app.Activity;
|
||||
|
||||
public class OsmandIntents {
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import net.osmand.osm.MapUtils;
|
|||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.routing.RouteProvider.GPXRouteParams;
|
||||
import net.osmand.router.RouteSegmentResult;
|
||||
import net.osmand.router.TurnType;
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
|
||||
|
@ -44,12 +45,43 @@ public class RouteCalculationResult {
|
|||
|
||||
}
|
||||
|
||||
public String toString(TurnType type, Context ctx) {
|
||||
if(type.isRoundAbout()){
|
||||
return ctx.getString(R.string.route_roundabout, type.getExitOut());
|
||||
} else if(type.getValue().equals(TurnType.C)) {
|
||||
return ctx.getString(R.string.route_head);
|
||||
} else if(type.getValue().equals(TurnType.TSLL)) {
|
||||
return ctx.getString(R.string.route_tsll);
|
||||
} else if(type.getValue().equals(TurnType.TL)) {
|
||||
return ctx.getString(R.string.route_tl);
|
||||
} else if(type.getValue().equals(TurnType.TSHL)) {
|
||||
return ctx.getString(R.string.route_tshl);
|
||||
} else if(type.getValue().equals(TurnType.TSLR)) {
|
||||
return ctx.getString(R.string.route_tslr);
|
||||
} else if(type.getValue().equals(TurnType.TR)) {
|
||||
return ctx.getString(R.string.route_tr);
|
||||
} else if(type.getValue().equals(TurnType.TSHR)) {
|
||||
return ctx.getString(R.string.route_tshr);
|
||||
} else if(type.getValue().equals(TurnType.TU)) {
|
||||
return ctx.getString(R.string.route_tu);
|
||||
} else if(type.getValue().equals(TurnType.TRU)) {
|
||||
return ctx.getString(R.string.route_tu);
|
||||
} else if(type.getValue().equals(TurnType.KL)) {
|
||||
return ctx.getString(R.string.route_kl);
|
||||
} else if(type.getValue().equals(TurnType.KR)) {
|
||||
return ctx.getString(R.string.route_kr);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public RouteCalculationResult(List<RouteSegmentResult> list, Location start, LatLon end,
|
||||
Context ctx, boolean leftSide) {
|
||||
this.directions = new ArrayList<RouteDirectionInfo>();
|
||||
this.errorMessage = null;
|
||||
this.locations = new ArrayList<Location>();
|
||||
|
||||
float prevDirectionTime = 0;
|
||||
float prevDirectionDistance = 0;
|
||||
for (int routeInd = 0; routeInd < list.size(); routeInd++) {
|
||||
RouteSegmentResult s = list.get(routeInd);
|
||||
boolean plus = s.getStartPointIndex() < s.getEndPointIndex();
|
||||
|
@ -74,55 +106,29 @@ public class RouteCalculationResult {
|
|||
i--;
|
||||
}
|
||||
}
|
||||
TurnType turn;
|
||||
String description;
|
||||
if (routeInd == 0) {
|
||||
turn = TurnType.valueOf(TurnType.C, leftSide);
|
||||
description = ctx.getString(R.string.route_head);
|
||||
} else {
|
||||
RouteSegmentResult prev = list.get(routeInd - 1);
|
||||
double mpi = MapUtils.degreesDiff(prev.getBearingEnd(), s.getBearingBegin());
|
||||
TurnType turn = s.getTurnType();
|
||||
|
||||
turn = TurnType.valueOf(TurnType.C, leftSide);
|
||||
description = ctx.getString(R.string.route_head);
|
||||
if(mpi >= 50) {
|
||||
if(mpi < 60) {
|
||||
turn = TurnType.valueOf(TurnType.TSLL, leftSide);
|
||||
description = ctx.getString(R.string.route_tsll);
|
||||
} else if(mpi < 120) {
|
||||
turn = TurnType.valueOf(TurnType.TL, leftSide);
|
||||
description = ctx.getString(R.string.route_tl);
|
||||
} else if(mpi < 135) {
|
||||
turn = TurnType.valueOf(TurnType.TSHL, leftSide);
|
||||
description = ctx.getString(R.string.route_tshl);
|
||||
} else {
|
||||
turn = TurnType.valueOf(TurnType.TU, leftSide);
|
||||
description = ctx.getString(R.string.route_tu);
|
||||
}
|
||||
} else if (mpi < - 50){
|
||||
if(mpi > - 60) {
|
||||
turn = TurnType.valueOf(TurnType.TSLR, leftSide);
|
||||
description = ctx.getString(R.string.route_tslr);
|
||||
} else if(mpi > - 120) {
|
||||
turn = TurnType.valueOf(TurnType.TR, leftSide);
|
||||
description = ctx.getString(R.string.route_tr);
|
||||
} else if(mpi > - 135) {
|
||||
turn = TurnType.valueOf(TurnType.TSHR, leftSide);
|
||||
description = ctx.getString(R.string.route_tshr);
|
||||
} else {
|
||||
turn = TurnType.valueOf(TurnType.TU, leftSide);
|
||||
description = ctx.getString(R.string.route_tu);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(routeInd == 0 || !turn.getValue().equals(TurnType.C)) {
|
||||
// TODO correlate speed (weight sum) when next turn type is C
|
||||
if(turn != null) {
|
||||
RouteDirectionInfo info = new RouteDirectionInfo(s.getSegmentSpeed(), turn);
|
||||
info.setDescriptionRoute(description + " " +
|
||||
OsmAndFormatter.getFormattedDistance(s.getDistance(), ctx));
|
||||
String description = toString(turn, ctx);
|
||||
info.setDescriptionRoute(description);
|
||||
info.routePointOffset = prevLocationSize;
|
||||
if(directions.size() > 0 && prevDirectionTime > 0 && prevDirectionDistance > 0) {
|
||||
RouteDirectionInfo prev = directions.get(directions.size() - 1);
|
||||
prev.setAverageSpeed(prevDirectionDistance / prevDirectionTime);
|
||||
prev.setDescriptionRoute(prev.getDescriptionRoute() + " " + OsmAndFormatter.getFormattedDistance(prevDirectionDistance, ctx));
|
||||
prevDirectionDistance = 0;
|
||||
prevDirectionTime = 0;
|
||||
}
|
||||
directions.add(info);
|
||||
}
|
||||
prevDirectionDistance += s.getDistance();
|
||||
prevDirectionTime += s.getSegmentTime();
|
||||
}
|
||||
if(directions.size() > 0 && prevDirectionTime > 0 && prevDirectionDistance > 0) {
|
||||
RouteDirectionInfo prev = directions.get(directions.size() - 1);
|
||||
prev.setAverageSpeed(prevDirectionDistance / prevDirectionTime);
|
||||
prev.setDescriptionRoute(prev.getDescriptionRoute() + " " + OsmAndFormatter.getFormattedDistance(prevDirectionDistance, ctx));
|
||||
}
|
||||
introduceFirstPoint(start);
|
||||
updateListDistanceTime();
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package net.osmand.plus.routing;
|
||||
|
||||
import net.osmand.router.TurnType;
|
||||
|
||||
public class RouteDirectionInfo {
|
||||
// location when you should action (turn or go ahead)
|
||||
public int routePointOffset;
|
||||
|
|
|
@ -39,6 +39,7 @@ import net.osmand.router.GeneralRouter.GeneralRouterProfile;
|
|||
import net.osmand.router.RouteSegmentResult;
|
||||
import net.osmand.router.RoutingConfiguration;
|
||||
import net.osmand.router.RoutingContext;
|
||||
import net.osmand.router.TurnType;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
@ -505,7 +506,7 @@ public class RouteProvider {
|
|||
return new RouteCalculationResult("End point is far from allowed road.");
|
||||
}
|
||||
try {
|
||||
List<RouteSegmentResult> result = router.searchRoute(ctx, st, en);
|
||||
List<RouteSegmentResult> result = router.searchRoute(ctx, st, en, leftSide);
|
||||
return new RouteCalculationResult(result, start, end, app, leftSide);
|
||||
} catch (OutOfMemoryError e) {
|
||||
return new RouteCalculationResult("Not enough process memory");
|
||||
|
|
|
@ -4,6 +4,7 @@ import net.osmand.plus.activities.ApplicationMode;
|
|||
import net.osmand.plus.voice.AbstractPrologCommandPlayer;
|
||||
import net.osmand.plus.voice.CommandBuilder;
|
||||
import net.osmand.plus.voice.CommandPlayer;
|
||||
import net.osmand.router.TurnType;
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import net.osmand.plus.R;
|
|||
import net.osmand.plus.activities.MapActivity;
|
||||
import net.osmand.plus.routing.RouteDirectionInfo;
|
||||
import net.osmand.plus.routing.RoutingHelper;
|
||||
import net.osmand.plus.routing.TurnType;
|
||||
import net.osmand.router.TurnType;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
|
|
|
@ -2,7 +2,7 @@ package net.osmand.plus.views;
|
|||
|
||||
import net.osmand.OsmAndFormatter;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.routing.TurnType;
|
||||
import net.osmand.router.TurnType;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package net.osmand.plus.views;
|
||||
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.routing.TurnType;
|
||||
import net.osmand.router.TurnType;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
|
|
Loading…
Reference in a new issue