App 2.39.2: Grid with map seen from higher up
The map gets its own camera, tilted 45 degrees down from about 300 m up, so roads keep their shape some 400 m ahead instead of collapsing into the horizon. In map mode the floor starts at half height, the speed is a little smaller, the floor's grid is fixed to the ground and turns with the roads, and the power disc has a dark backing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -138,6 +138,14 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does *
|
|||||||
confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces
|
confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces
|
||||||
upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator`
|
upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator`
|
||||||
(Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
|
(Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
|
||||||
|
- 2026-10-27 — App 2.39.2: **Grid with map seen from higher up** (owner: the map looked like a
|
||||||
|
fish-eye; wanted it more top-down). The map has its own pinhole camera now: 45° down, 300 m
|
||||||
|
up, the car on its axis just above the read-outs, the top of the floor about 420 m ahead
|
||||||
|
(scale there ~60 % of the car's, before ~1/20 at 900 m); the floor starts at half height in
|
||||||
|
map mode and the speed is 170 instead of 210 units; the floor's grid becomes a ground grid
|
||||||
|
(a line every 50 m, fixed to the ground, turning with the roads); roads from 250 m ahead
|
||||||
|
fainter; the power disc gets a dark backing over the map. Emulator with the Berlin tiles,
|
||||||
|
both layouts. Not tried on the car.
|
||||||
- 2026-10-26 — Server 2.8.0 image pushed and pinned in `k8s/server.yaml` by digest
|
- 2026-10-26 — Server 2.8.0 image pushed and pinned in `k8s/server.yaml` by digest
|
||||||
`sha256:8b8d94e0562833d196162866b7fdc6910d5dd219f6eb84572d4d5c91b74f7ff1` (checked with skopeo);
|
`sha256:8b8d94e0562833d196162866b7fdc6910d5dd219f6eb84572d4d5c91b74f7ff1` (checked with skopeo);
|
||||||
app 2.39.1 published (`car-2.39.1`). Not applied yet: the owner applies it.
|
app 2.39.1 published (`car-2.39.1`). Not applied yet: the owner applies it.
|
||||||
|
|||||||
@@ -165,9 +165,11 @@ films' other side.
|
|||||||
|
|
||||||
## Grid with map
|
## Grid with map
|
||||||
|
|
||||||
The Grid, with the roads round the car from OpenStreetMap (through OpenFreeMap) laid on its floor as light lanes, in
|
The Grid, with the roads round the car from OpenStreetMap (through OpenFreeMap) laid on its floor as light lanes, seen
|
||||||
the floor's perspective and turned with the direction you drive: motorways glow brightest, then
|
from a camera high above and behind the car, tilted down, so the streets keep their shape some
|
||||||
main roads, then the smaller streets, fading towards the horizon. The car is the chevron on the
|
400 m ahead, and turned with the direction you drive: motorways glow brightest, then main
|
||||||
|
roads, then the smaller streets, the far ones fainter. A ground grid every 50 m turns and slides
|
||||||
|
with them. The car is the chevron on the
|
||||||
floor, and between GPS fixes it runs on with the speed. The roads come through the car's own
|
floor, and between GPS fixes it runs on with the speed. The roads come through the car's own
|
||||||
server, which fetches only the map tiles you drive through and keeps them (see the README); the car also keeps
|
server, which fetches only the map tiles you drive through and keeps them (see the README); the car also keeps
|
||||||
the last few hundred tiles itself. Without GPS, the server or the roads, the floor says so and
|
the last few hundred tiles itself. Without GPS, the server or the roads, the floor says so and
|
||||||
|
|||||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
|||||||
applicationId "com.stinnesbeck.nilsmg4"
|
applicationId "com.stinnesbeck.nilsmg4"
|
||||||
minSdk 27
|
minSdk 27
|
||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 143
|
versionCode 144
|
||||||
versionName "2.39.1"
|
versionName "2.39.2"
|
||||||
|
|
||||||
// Nothing about the server ships in the APK: its URL and ingest token are entered in
|
// Nothing about the server ships in the APK: its URL and ingest token are entered in
|
||||||
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
|
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ import android.os.SystemClock;
|
|||||||
* the trail; the floor stands still when the speed is unread.
|
* the trail; the floor stands still when the speed is unread.
|
||||||
*
|
*
|
||||||
* With a map ("Grid with map", {@link MapRoads}): the roads round the car from OpenStreetMap lie
|
* With a map ("Grid with map", {@link MapRoads}): the roads round the car from OpenStreetMap lie
|
||||||
* on the floor in its perspective, heading up, the bigger ones glowing brighter, with the car
|
* on the floor in perspective, heading up, the bigger ones glowing brighter, with the car
|
||||||
* as a light marker a little way up the floor; between GPS fixes the car's position runs on
|
* as a light marker a little way up the floor; between GPS fixes the car's position runs on
|
||||||
* with the speed. The floor's tiles are then 40 m, so they move with the roads, and the trail
|
* with the speed. The map has its own camera, higher and steeper than the plain
|
||||||
* makes way for the map. Without a fix, a server or the roads, the plain floor with a line
|
* floor's, so far roads shrink less; the floor starts higher, the speed is a little smaller, its
|
||||||
|
* grid is fixed to the ground (a line every 50 m) and turns with the roads, and the trail makes
|
||||||
|
* way for the map. Without a fix, a server or the roads, the plain floor with a line
|
||||||
* saying why.
|
* saying why.
|
||||||
*
|
*
|
||||||
* Two palettes: the original cyan, or the chosen accent colour ({@link #setAccentPalette}). A
|
* Two palettes: the original cyan, or the chosen accent colour ({@link #setAccentPalette}). A
|
||||||
@@ -40,10 +42,13 @@ final class GridView extends ClusterCanvasView {
|
|||||||
private static final int WALL = 20, DISC = 60;
|
private static final int WALL = 20, DISC = 60;
|
||||||
private static final float TILE_M = 4f;
|
private static final float TILE_M = 4f;
|
||||||
/**
|
/**
|
||||||
* The map: metres per depth step of the floor, the car's depth (just above the read-outs),
|
* The map's camera: tilted 45° down from about 300 m up, the car on its axis, the top of the
|
||||||
* the nearest depth drawn, how far ahead, and the depth from which roads are drawn fainter.
|
* floor some 420 m ahead; nothing nearer to the lens than NEAR_M is drawn, nothing beyond
|
||||||
|
* MAP_RANGE_M looked at, and roads from FADE_M ahead are fainter. The ground grid under the
|
||||||
|
* roads has a line every GRID_M.
|
||||||
*/
|
*/
|
||||||
private static final float MAP_UNIT_M = 40f, CAR_DEPTH = 1.6f, NEAR = 0.6f, FAR_M = 900f, FADE_DEPTH = 5f;
|
private static final float PITCH = (float) Math.toRadians(45), CAM_H = 300f, AHEAD_M = 420f,
|
||||||
|
NEAR_M = 40f, MAP_RANGE_M = 1300f, FADE_M = 250f, GRID_M = 50f;
|
||||||
|
|
||||||
private final Paint line = new Paint(Paint.ANTI_ALIAS_FLAG);
|
private final Paint line = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
private final Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG);
|
private final Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
@@ -58,13 +63,16 @@ final class GridView extends ClusterCanvasView {
|
|||||||
private final int accent, accentLight;
|
private final int accent, accentLight;
|
||||||
/** Null for the plain Grid. */
|
/** Null for the plain Grid. */
|
||||||
private final MapRoads map;
|
private final MapRoads map;
|
||||||
/** Per road class, near and far (from {@link #FADE_DEPTH}). */
|
/** Per road class, near and far (from {@link #FADE_M} ahead). */
|
||||||
private final Path[] roads = new Path[RoadTile.CLASSES * 2];
|
private final Path[] roads = new Path[RoadTile.CLASSES * 2];
|
||||||
private float heading = Float.NaN;
|
private float heading = Float.NaN;
|
||||||
/** The pen while a road is added: the last point, and whether it continues from there. */
|
/** The pen while a road is added: the last point, and whether it continues from there. */
|
||||||
private float penX, penY;
|
private float penX, penY;
|
||||||
private boolean penDown;
|
private boolean penDown;
|
||||||
private Path penPath;
|
private Path penPath;
|
||||||
|
private final Path gridPath = new Path();
|
||||||
|
/** The camera of this frame: focal length and centre in pixels. */
|
||||||
|
private float camF, camCx, camCy;
|
||||||
|
|
||||||
private int main, light, dim, alert;
|
private int main, light, dim, alert;
|
||||||
/** The floor's position within one tile (0..1) and the disc's inner ring angle. */
|
/** The floor's position within one tile (0..1) and the disc's inner ring angle. */
|
||||||
@@ -107,16 +115,18 @@ final class GridView extends ClusterCanvasView {
|
|||||||
advance();
|
advance();
|
||||||
c.drawColor(0xFF000000);
|
c.drawColor(0xFF000000);
|
||||||
|
|
||||||
float horizon = h * 0.55f;
|
// With a map the floor starts higher, to give the roads room.
|
||||||
|
boolean mapShown = map != null && map.fix() != null && !map.tiles().isEmpty();
|
||||||
|
float horizon = h * (mapShown ? 0.5f : 0.55f);
|
||||||
drawSky(c, w, horizon);
|
drawSky(c, w, horizon);
|
||||||
drawFloor(c, w, h, horizon, u);
|
if (mapShown) drawMap(c, w, h, horizon, u);
|
||||||
boolean mapShown = map != null && drawMap(c, w, h, horizon, u);
|
else drawFloor(c, w, h, horizon, u);
|
||||||
if (map != null && !mapShown) drawMapState(c, w, h, horizon, u);
|
if (map != null && !mapShown) drawMapState(c, w, h, horizon, u);
|
||||||
drawGears(c, w, u);
|
drawGears(c, w, u);
|
||||||
drawClock(c, w, u);
|
drawClock(c, w, u);
|
||||||
drawSpeed(c, w, horizon, u);
|
drawSpeed(c, w, horizon, mapShown ? 170f : 210f, u);
|
||||||
drawWall(c, horizon, u);
|
drawWall(c, horizon, u);
|
||||||
drawDisc(c, w, u);
|
drawDisc(c, w, mapShown, u);
|
||||||
if (!mapShown) drawTrail(c, w, h, u);
|
if (!mapShown) drawTrail(c, w, h, u);
|
||||||
drawReadouts(c, w, h, u);
|
drawReadouts(c, w, h, u);
|
||||||
if (mapShown) drawAttribution(c, w, h, u);
|
if (mapShown) drawAttribution(c, w, h, u);
|
||||||
@@ -132,8 +142,7 @@ final class GridView extends ClusterCanvasView {
|
|||||||
float dt = lastFrame == 0 ? 0f : Math.min(0.2f, (now - lastFrame) / 1000f);
|
float dt = lastFrame == 0 ? 0f : Math.min(0.2f, (now - lastFrame) / 1000f);
|
||||||
lastFrame = now;
|
lastFrame = now;
|
||||||
if (v.speedKmh != null && v.speedKmh > 0.5f) {
|
if (v.speedKmh != null && v.speedKmh > 0.5f) {
|
||||||
float tile = map != null ? MAP_UNIT_M : TILE_M;
|
floorPhase = (floorPhase + v.speedKmh / 3.6f * dt / TILE_M) % 1f;
|
||||||
floorPhase = (floorPhase + v.speedKmh / 3.6f * dt / tile) % 1f;
|
|
||||||
}
|
}
|
||||||
if (v.powerKw != null) discTurn = (discTurn + v.powerKw * 3f * dt) % 360f;
|
if (v.powerKw != null) discTurn = (discTurn + v.powerKw * 3f * dt) % 360f;
|
||||||
}
|
}
|
||||||
@@ -172,11 +181,11 @@ final class GridView extends ClusterCanvasView {
|
|||||||
|
|
||||||
// ---------- Map ----------
|
// ---------- Map ----------
|
||||||
|
|
||||||
/** The roads round the car on the floor; false when there is nothing to draw. */
|
/** The roads round the car on the floor, seen by the map's camera, with a ground grid. */
|
||||||
private boolean drawMap(Canvas c, float w, float h, float horizon, float u) {
|
private void drawMap(Canvas c, float w, float h, float horizon, float u) {
|
||||||
MapRoads.Fix fix = map.fix();
|
MapRoads.Fix fix = map.fix();
|
||||||
java.util.Collection<RoadTile> tiles = map.tiles();
|
java.util.Collection<RoadTile> tiles = map.tiles();
|
||||||
if (fix == null || tiles.isEmpty()) return false;
|
if (fix == null) return;
|
||||||
// Heading up: ease round the shorter way; north up until the car has moved.
|
// Heading up: ease round the shorter way; north up until the car has moved.
|
||||||
float target = Float.isNaN(fix.bearing) ? (Float.isNaN(heading) ? 0f : heading) : fix.bearing;
|
float target = Float.isNaN(fix.bearing) ? (Float.isNaN(heading) ? 0f : heading) : fix.bearing;
|
||||||
if (Float.isNaN(heading)) heading = target;
|
if (Float.isNaN(heading)) heading = target;
|
||||||
@@ -192,13 +201,33 @@ final class GridView extends ClusterCanvasView {
|
|||||||
double carX = MapGeometry.worldX(fix.lon) + ran * Math.sin(run) / mpu;
|
double carX = MapGeometry.worldX(fix.lon) + ran * Math.sin(run) / mpu;
|
||||||
double carY = MapGeometry.worldY(fix.lat) - ran * Math.cos(run) / mpu;
|
double carY = MapGeometry.worldY(fix.lat) - ran * Math.cos(run) / mpu;
|
||||||
|
|
||||||
|
// The camera: the car where the chevron stands, the top of the floor AHEAD_M beyond it.
|
||||||
|
float carScreenY = h - 96f * u;
|
||||||
|
float z0 = CAM_H / (float) Math.tan(PITCH);
|
||||||
|
camF = (carScreenY - horizon) / (up(z0 + AHEAD_M) - up(z0));
|
||||||
|
camCy = carScreenY + camF * up(z0);
|
||||||
|
camCx = w / 2f;
|
||||||
|
|
||||||
float sin = (float) Math.sin(Math.toRadians(heading)), cos = (float) Math.cos(Math.toRadians(heading));
|
float sin = (float) Math.sin(Math.toRadians(heading)), cos = (float) Math.cos(Math.toRadians(heading));
|
||||||
float lane = w * 0.09f, depth = h - horizon, vx = w / 2f, m = (float) mpu;
|
float m = (float) mpu;
|
||||||
double range = FAR_M / mpu;
|
double range = MAP_RANGE_M / mpu;
|
||||||
|
|
||||||
|
// The ground grid, fixed to the ground: it turns and slides with the roads.
|
||||||
|
gridPath.reset();
|
||||||
|
penPath = null;
|
||||||
|
double carEast = carX * mpu, carNorth = -carY * mpu;
|
||||||
|
float offE = (float) (carEast % GRID_M), offN = (float) (carNorth % GRID_M);
|
||||||
|
int lines = (int) (MAP_RANGE_M / GRID_M);
|
||||||
|
for (int k = -lines; k <= lines; k++) {
|
||||||
|
float e = k * GRID_M - offE, n = k * GRID_M - offN;
|
||||||
|
gridLine(e, -MAP_RANGE_M, e, MAP_RANGE_M, sin, cos);
|
||||||
|
gridLine(-MAP_RANGE_M, n, MAP_RANGE_M, n, sin, cos);
|
||||||
|
}
|
||||||
|
|
||||||
for (Path p : roads) p.reset();
|
for (Path p : roads) p.reset();
|
||||||
for (RoadTile tile : tiles) {
|
for (RoadTile tile : tiles) {
|
||||||
double ox = (double) tile.x * MapGeometry.EXTENT - carX, oy = (double) tile.y * MapGeometry.EXTENT - carY;
|
double ox = (double) tile.x * MapGeometry.EXTENT - carX, oy = (double) tile.y * MapGeometry.EXTENT - carY;
|
||||||
if (ox > range || oy > range || ox + 2 * MapGeometry.EXTENT < -range || oy + 2 * MapGeometry.EXTENT < -range) {
|
if (ox > range || oy > range || ox + MapGeometry.EXTENT < -range || oy + MapGeometry.EXTENT < -range) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
float fx = (float) ox, fy = (float) oy;
|
float fx = (float) ox, fy = (float) oy;
|
||||||
@@ -212,7 +241,7 @@ final class GridView extends ClusterCanvasView {
|
|||||||
for (int k = 0; k < pts.length; k += 2) {
|
for (int k = 0; k < pts.length; k += 2) {
|
||||||
float east = (fx + pts[k]) * m, north = -(fy + pts[k + 1]) * m;
|
float east = (fx + pts[k]) * m, north = -(fy + pts[k + 1]) * m;
|
||||||
float fwd = east * sin + north * cos, side = east * cos - north * sin;
|
float fwd = east * sin + north * cos, side = east * cos - north * sin;
|
||||||
if (k > 0) segment(cls, pa, pf, side, fwd, vx, horizon, lane, depth);
|
if (k > 0) segment(roads[cls * 2 + (Math.min(pf, fwd) > FADE_M ? 1 : 0)], pa, pf, side, fwd);
|
||||||
pa = side;
|
pa = side;
|
||||||
pf = fwd;
|
pf = fwd;
|
||||||
}
|
}
|
||||||
@@ -222,67 +251,86 @@ final class GridView extends ClusterCanvasView {
|
|||||||
c.clipRect(0, horizon, w, h);
|
c.clipRect(0, horizon, w, h);
|
||||||
line.setStrokeJoin(Paint.Join.ROUND);
|
line.setStrokeJoin(Paint.Join.ROUND);
|
||||||
line.setStrokeCap(Paint.Cap.ROUND);
|
line.setStrokeCap(Paint.Cap.ROUND);
|
||||||
// Far roads thin and faint, so the horizon stays a line rather than a tangle.
|
line.setColor(AccentColor.withAlpha(main, 0x2A));
|
||||||
|
line.setStrokeWidth(1f * u);
|
||||||
|
c.drawPath(gridPath, line);
|
||||||
|
// Far roads thinner and fainter.
|
||||||
for (int cls = RoadTile.CLASSES - 1; cls >= 0; cls--) {
|
for (int cls = RoadTile.CLASSES - 1; cls >= 0; cls--) {
|
||||||
line.setColor(AccentColor.withAlpha(cls == 0 ? light : main, cls >= 3 ? 0x30 : 0x50));
|
line.setColor(AccentColor.withAlpha(cls == 0 ? light : main, cls >= 3 ? 0x50 : 0x80));
|
||||||
line.setStrokeWidth((cls == 0 ? 1.6f : 1f) * u);
|
line.setStrokeWidth((cls == 0 ? 2f : cls == 3 ? 1f : 1.4f) * u);
|
||||||
c.drawPath(roads[cls * 2 + 1], line);
|
c.drawPath(roads[cls * 2 + 1], line);
|
||||||
}
|
}
|
||||||
line.setColor(AccentColor.withAlpha(main, 0x80));
|
line.setColor(AccentColor.withAlpha(main, 0x90));
|
||||||
line.setStrokeWidth(1.4f * u);
|
line.setStrokeWidth(1.5f * u);
|
||||||
c.drawPath(roads[3 * 2], line);
|
c.drawPath(roads[3 * 2], line);
|
||||||
line.setColor(AccentColor.withAlpha(main, 0xC0));
|
line.setColor(AccentColor.withAlpha(main, 0xD0));
|
||||||
line.setStrokeWidth(2f * u);
|
line.setStrokeWidth(2.2f * u);
|
||||||
c.drawPath(roads[2 * 2], line);
|
c.drawPath(roads[2 * 2], line);
|
||||||
glow(c, roads[2], main, 2.4f * u);
|
glow(c, roads[2], main, 2.6f * u);
|
||||||
glow(c, roads[0], light, 3.2f * u);
|
glow(c, roads[0], light, 3.4f * u);
|
||||||
line.setStrokeJoin(Paint.Join.MITER);
|
line.setStrokeJoin(Paint.Join.MITER);
|
||||||
line.setStrokeCap(Paint.Cap.SQUARE);
|
line.setStrokeCap(Paint.Cap.SQUARE);
|
||||||
c.restore();
|
c.restore();
|
||||||
|
glowLine(c, 0, horizon, w, horizon, main, 1.5f * u);
|
||||||
|
|
||||||
// The car: a light-cycle chevron pointing ahead.
|
// The car: a light-cycle chevron pointing ahead.
|
||||||
float cy = horizon + depth / CAR_DEPTH;
|
|
||||||
path.reset();
|
path.reset();
|
||||||
path.moveTo(vx, cy - 16f * u);
|
path.moveTo(camCx, carScreenY - 16f * u);
|
||||||
path.lineTo(vx + 11f * u, cy + 10f * u);
|
path.lineTo(camCx + 11f * u, carScreenY + 10f * u);
|
||||||
path.lineTo(vx, cy + 4f * u);
|
path.lineTo(camCx, carScreenY + 4f * u);
|
||||||
path.lineTo(vx - 11f * u, cy + 10f * u);
|
path.lineTo(camCx - 11f * u, carScreenY + 10f * u);
|
||||||
path.close();
|
path.close();
|
||||||
fill.setColor(light);
|
fill.setColor(light);
|
||||||
c.drawPath(path, fill);
|
c.drawPath(path, fill);
|
||||||
glow(c, path, light, 1.5f * u);
|
glow(c, path, light, 1.5f * u);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** One stretch of road from (a0, f0) to (a1, f1) in metres (side, ahead), cut at the near edge. */
|
/** The camera's height of a ground point z metres ahead of the spot below it, over its depth. */
|
||||||
private void segment(int cls, float a0, float f0, float a1, float f1, float vx, float horizon, float lane,
|
private static float up(float z) {
|
||||||
float depth) {
|
float cos = (float) Math.cos(PITCH), sin = (float) Math.sin(PITCH);
|
||||||
float d0 = CAR_DEPTH + f0 / MAP_UNIT_M, d1 = CAR_DEPTH + f1 / MAP_UNIT_M;
|
return (z * sin - CAM_H * cos) / (z * cos + CAM_H * sin);
|
||||||
Path p = roads[cls * 2 + (Math.min(d0, d1) > FADE_DEPTH ? 1 : 0)];
|
}
|
||||||
if ((d0 < NEAR && d1 < NEAR) || (f0 > FAR_M && f1 > FAR_M)) {
|
|
||||||
|
/** A grid line between two ground points given east and north of the car, in metres. */
|
||||||
|
private void gridLine(float e0, float n0, float e1, float n1, float sin, float cos) {
|
||||||
|
penDown = false;
|
||||||
|
segment(gridPath, e0 * cos - n0 * sin, e0 * sin + n0 * cos, e1 * cos - n1 * sin, e1 * sin + n1 * cos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One stretch of ground from (side, ahead) a0, f0 to a1, f1 in metres from the car, through
|
||||||
|
* the camera, cut where it comes nearer to the lens than NEAR_M.
|
||||||
|
*/
|
||||||
|
private void segment(Path p, float a0, float f0, float a1, float f1) {
|
||||||
|
float cos = (float) Math.cos(PITCH), sin = (float) Math.sin(PITCH), z0 = CAM_H / (float) Math.tan(PITCH);
|
||||||
|
float za = z0 + f0, zb = z0 + f1;
|
||||||
|
float d0 = za * cos + CAM_H * sin, d1 = zb * cos + CAM_H * sin;
|
||||||
|
if (d0 < NEAR_M && d1 < NEAR_M) {
|
||||||
penDown = false;
|
penDown = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean cut = false;
|
boolean cut = false;
|
||||||
if (d0 < NEAR || d1 < NEAR) {
|
if (d0 < NEAR_M || d1 < NEAR_M) {
|
||||||
float t = (NEAR - d0) / (d1 - d0);
|
float t = (NEAR_M - d0) / (d1 - d0);
|
||||||
float a = a0 + (a1 - a0) * t, d = NEAR;
|
float a = a0 + (a1 - a0) * t, z = za + (zb - za) * t;
|
||||||
if (d0 < NEAR) {
|
if (d0 < NEAR_M) {
|
||||||
a0 = a;
|
a0 = a;
|
||||||
d0 = d;
|
za = z;
|
||||||
|
d0 = NEAR_M;
|
||||||
cut = true;
|
cut = true;
|
||||||
} else {
|
} else {
|
||||||
a1 = a;
|
a1 = a;
|
||||||
d1 = d;
|
zb = z;
|
||||||
|
d1 = NEAR_M;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float x0 = vx + a0 / MAP_UNIT_M * lane / d0, y0 = horizon + depth / d0;
|
float x0 = camCx + camF * a0 / d0, y0 = camCy - camF * (za * sin - CAM_H * cos) / d0;
|
||||||
float x1 = vx + a1 / MAP_UNIT_M * lane / d1, y1 = horizon + depth / d1;
|
float x1 = camCx + camF * a1 / d1, y1 = camCy - camF * (zb * sin - CAM_H * cos) / d1;
|
||||||
if (!penDown || cut || p != penPath || x0 != penX || y0 != penY) p.moveTo(x0, y0);
|
if (!penDown || cut || p != penPath || x0 != penX || y0 != penY) p.moveTo(x0, y0);
|
||||||
p.lineTo(x1, y1);
|
p.lineTo(x1, y1);
|
||||||
penPath = p;
|
|
||||||
penX = x1;
|
penX = x1;
|
||||||
penY = y1;
|
penY = y1;
|
||||||
|
penPath = p;
|
||||||
penDown = true;
|
penDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,9 +391,9 @@ final class GridView extends ClusterCanvasView {
|
|||||||
|
|
||||||
// ---------- Speed ----------
|
// ---------- Speed ----------
|
||||||
|
|
||||||
private void drawSpeed(Canvas c, float w, float horizon, float u) {
|
private void drawSpeed(Canvas c, float w, float horizon, float size, float u) {
|
||||||
text.setTypeface(thin);
|
text.setTypeface(thin);
|
||||||
text.setTextSize(210f * u);
|
text.setTextSize(size * u);
|
||||||
text.setTextAlign(Paint.Align.CENTER);
|
text.setTextAlign(Paint.Align.CENTER);
|
||||||
glowText(c, fmt(v.speedKmh, "%.0f", "—"), w / 2f, horizon - 62f * u, light, u);
|
glowText(c, fmt(v.speedKmh, "%.0f", "—"), w / 2f, horizon - 62f * u, light, u);
|
||||||
caption(c, "KM/H", w / 2f, horizon - 22f * u, u, Paint.Align.CENTER);
|
caption(c, "KM/H", w / 2f, horizon - 22f * u, u, Paint.Align.CENTER);
|
||||||
@@ -393,8 +441,13 @@ final class GridView extends ClusterCanvasView {
|
|||||||
// ---------- Power disc ----------
|
// ---------- Power disc ----------
|
||||||
|
|
||||||
/** Zero at the top, −50 kW a quarter turn anticlockwise, 150 kW three quarters clockwise. */
|
/** Zero at the top, −50 kW a quarter turn anticlockwise, 150 kW three quarters clockwise. */
|
||||||
private void drawDisc(Canvas c, float w, float u) {
|
private void drawDisc(Canvas c, float w, boolean overMap, float u) {
|
||||||
float cx = w - 170f * u, cy = 238f * u, outer = 132f * u, ring = 22f * u;
|
float cx = w - 170f * u, cy = 238f * u, outer = 132f * u, ring = 22f * u;
|
||||||
|
if (overMap) {
|
||||||
|
// The map runs under the disc: a dark backing keeps it readable.
|
||||||
|
fill.setColor(0xD0000000);
|
||||||
|
c.drawCircle(cx, cy, outer + 16f * u, fill);
|
||||||
|
}
|
||||||
line.setColor(dim);
|
line.setColor(dim);
|
||||||
line.setStrokeWidth(1.5f * u);
|
line.setStrokeWidth(1.5f * u);
|
||||||
c.drawCircle(cx, cy, outer + 10f * u, line);
|
c.drawCircle(cx, cy, outer + 10f * u, line);
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 83 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 81 KiB |
Reference in New Issue
Block a user