Compare commits
7
Commits
car-2.49.0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
957638208d | ||
|
|
142f016174 | ||
|
|
3cad9499fb | ||
|
|
60a31574a3 | ||
|
|
3a9b6e17c8 | ||
|
|
16b11405c8 | ||
|
|
3bb0d5eb71 |
@@ -140,6 +140,13 @@ 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-11-07 — App 2.51.0 published (`car-2.51.0`).
|
||||||
|
- 2026-11-07 — App 2.51.0: **the Caterpillar after the book's newer pictures** (owner sent a 3D picture of it). Plump round segments shaded like balls of wound paper (radial light from the upper left) with brush strokes in light green and teal, bigger towards the head, a fringe of yellow and orange bristles along the back, stubby brown feet; a big round red head with teal eyes rimmed in yellow and two lilac ears, the speed under the eyes. Crawl, readings and colours (green lit, blue regenerating, dull dark) unchanged. Original drawing, not the artwork. Tested: compiles, emulator screenshots with sample values (`screenshots/themes/caterpillar*.png`). Not tried on the car.
|
||||||
|
- 2026-11-07 — App 2.50.0 published (`car-2.50.0`).
|
||||||
|
- 2026-11-07 — App 2.50.0: **the Caterpillar crawls** (owner: "make it so the caterpillar appears to be going when we drive? Using normal caterpillar movement?"). While the car moves it crawls as a caterpillar does: a hump runs from the tail to the head, the body bunches and stretches a little, feet that are lifted swing forward and the others stay planted on a branch whose knots and leaves slide backwards with the speed; the crawl is quicker the faster the car goes (at most 1.2 crawls a second). It does not change its place on screen, so the readings stay put. Parked, it eases back into its high arch (it keeps drawing for that moment only). Two screenshots redone. Tested: unit tests, lint, emulator with sample values (frames 0.25 s apart show the hump travelling and the branch sliding). Not tried on the car.
|
||||||
|
- 2026-11-07 — App 2.49.1 published (`car-2.49.1`).
|
||||||
|
- 2026-11-07 — App 2.49.1: **the Caterpillar looks more like the one in the book** (owner: "Make it more like the original caterpillar", with the German edition's cover as the reference). It arches over the middle like the cover's: overlapping chunks of mottled green and teal paper, a yellow fringe along the back, olive feet, and a red-orange head with a yellow face, green eyes and purple antennae; the speed is on the face. Power still lights the segments (blue when regenerating) and the back humps while driving. Still my own drawing of it, not the book's artwork. Two screenshots redone. Tested: unit tests, lint, emulator with sample values. Not tried on the car.
|
||||||
|
- 2026-11-07 — App 2.49.0 published (`car-2.49.0`).
|
||||||
- 2026-11-07 — App 2.49.0: **the Caterpillar style** (owner: "a theme based on The Very Hungry Caterpillar"; design agreed first, defaults taken: dark night page, named "Caterpillar" not the book's title, fruit as range). `CaterpillarView`, an original cut-paper collage, not the book's illustrations: the caterpillar's red head is the speed and its ten segments the power (blue while regenerating, wiggling while driving); the sun is the battery (a grey moon below 20 %); a leaf holds trip, usage, climate and odometer; a clock in a paper circle; fruit in the story's order (1 apple, 2 pears, 3 plums, 4 strawberries, 5 oranges) is the range, one per 50 km, used ones with a bite out; P R N D are an egg, the caterpillar, a cocoon and a butterfly; hills are the last two minutes of power. The music light (now also for this style) bounces the sun's rays and the segments on the kick drum. What plays: a butterfly and, on the next pass, a ladybird tow a strip of tissue paper (colour fixed by the song); both in `SongCarriers`. Own palette (green). Two screenshots in THEMES.md. Tested: unit tests, lint, emulator with sample values and with the throwaway fake song. Not tried on the car.
|
- 2026-11-07 — App 2.49.0: **the Caterpillar style** (owner: "a theme based on The Very Hungry Caterpillar"; design agreed first, defaults taken: dark night page, named "Caterpillar" not the book's title, fruit as range). `CaterpillarView`, an original cut-paper collage, not the book's illustrations: the caterpillar's red head is the speed and its ten segments the power (blue while regenerating, wiggling while driving); the sun is the battery (a grey moon below 20 %); a leaf holds trip, usage, climate and odometer; a clock in a paper circle; fruit in the story's order (1 apple, 2 pears, 3 plums, 4 strawberries, 5 oranges) is the range, one per 50 km, used ones with a bite out; P R N D are an egg, the caterpillar, a cocoon and a butterfly; hills are the last two minutes of power. The music light (now also for this style) bounces the sun's rays and the segments on the kick drum. What plays: a butterfly and, on the next pass, a ladybird tow a strip of tissue paper (colour fixed by the song); both in `SongCarriers`. Own palette (green). Two screenshots in THEMES.md. Tested: unit tests, lint, emulator with sample values and with the throwaway fake song. Not tried on the car.
|
||||||
- 2026-11-07 — App 2.48.0 published (`car-2.48.0`).
|
- 2026-11-07 — App 2.48.0 published (`car-2.48.0`).
|
||||||
- 2026-11-07 — App 2.48.0: **the Christmas style** (owner: "let's also do a christmas based theme", with music and something that pulsates; design agreed first). `ChristmasView`: a tree with a big red bauble (speed), fairy lights arching over it (power: gold left to right while drawing, ice blue and right to left while regenerating), a glass mug of cocoa filling with the charge (cold blue and "BATTERY LOW" below 20 %), gift tags (trip, usage, climate, odometer), a wreath clock, a signboard (range) with a candy cane per 50 km, P R N D as baubles, and a snow drift of the last two minutes of power; snow falls and the lights twinkle while driving. Own palette (red); always in the drop-down; English only. The music light is now **"Light follows the music"** for Halloween and Christmas (same `PREF_MUSIC_LIGHT`, off by default): the tree's lights and the arch flare on the kick drum. What plays: Santa's sleigh with a reindeer and, on the next pass, an angel with a trumpet tow a strip of wrapping paper (colour fixed by the song); both in `SongCarriers`. Two screenshots in THEMES.md. Tested: unit tests, lint, emulator with sample values (panel and full screen, and with no data), with the throwaway fake song (both carriers), and the music-tap thread runs only with the switch on. Not tried on the emulator: the pulse itself with real sound (the code path is Halloween's `MusicTap`). Not tried on the car.
|
- 2026-11-07 — App 2.48.0: **the Christmas style** (owner: "let's also do a christmas based theme", with music and something that pulsates; design agreed first). `ChristmasView`: a tree with a big red bauble (speed), fairy lights arching over it (power: gold left to right while drawing, ice blue and right to left while regenerating), a glass mug of cocoa filling with the charge (cold blue and "BATTERY LOW" below 20 %), gift tags (trip, usage, climate, odometer), a wreath clock, a signboard (range) with a candy cane per 50 km, P R N D as baubles, and a snow drift of the last two minutes of power; snow falls and the lights twinkle while driving. Own palette (red); always in the drop-down; English only. The music light is now **"Light follows the music"** for Halloween and Christmas (same `PREF_MUSIC_LIGHT`, off by default): the tree's lights and the arch flare on the kick drum. What plays: Santa's sleigh with a reindeer and, on the next pass, an angel with a trumpet tow a strip of wrapping paper (colour fixed by the song); both in `SongCarriers`. Two screenshots in THEMES.md. Tested: unit tests, lint, emulator with sample values (panel and full screen, and with no data), with the throwaway fake song (both carriers), and the music-tap thread runs only with the switch on. Not tried on the emulator: the pulse itself with real sound (the code path is Halloween's `MusicTap`). Not tried on the car.
|
||||||
|
|||||||
@@ -307,9 +307,14 @@ trumpet; the paper's colour is fixed by the song. The app turns red with it.
|
|||||||
## Caterpillar
|
## Caterpillar
|
||||||
|
|
||||||
After a picture book about a very hungry caterpillar, drawn anew in cut-paper shapes and bright
|
After a picture book about a very hungry caterpillar, drawn anew in cut-paper shapes and bright
|
||||||
tissue colours on the book's night page. The caterpillar crosses the middle: its red head carries
|
tissue colours on the book's night page. The caterpillar arches over the middle, after the one on
|
||||||
the speed, and its ten green segments are the power, lit from the head backwards while drawing and
|
the newer pictures of the book: plump, round segments like balls of wound paper with brush strokes
|
||||||
blue while regenerating; it wiggles along while you drive. On the left the sun is the battery,
|
in light green and teal, a fringe of yellow and orange bristles along the back and stubby brown
|
||||||
|
feet, and a big round red head with teal eyes rimmed in yellow and two lilac ears. The face
|
||||||
|
carries the speed, and its ten segments are the power, lit from the head backwards while drawing and
|
||||||
|
blue while regenerating. While you drive it crawls the way a caterpillar does: a hump runs from its
|
||||||
|
tail to its head, the feet it lifts swing forward and the others stay planted, and the branch it
|
||||||
|
walks on slides by, faster the faster the car goes. Parked, it settles into its high arch. On the left the sun is the battery,
|
||||||
filling with the charge; below 20 % it turns into a grey moon and "BATTERY LOW" blinks. A big leaf
|
filling with the charge; below 20 % it turns into a grey moon and "BATTERY LOW" blinks. A big leaf
|
||||||
above it holds trip, usage, climate and odometer on paper labels. On the right a clock in a paper
|
above it holds trip, usage, climate and odometer on paper labels. On the right a clock in a paper
|
||||||
circle, and the story's fruit, one apple, two pears, three plums, four strawberries and five
|
circle, and the story's fruit, one apple, two pears, three plums, four strawberries and five
|
||||||
|
|||||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
|||||||
applicationId "com.stinnesbeck.nilsmg4"
|
applicationId "com.stinnesbeck.nilsmg4"
|
||||||
minSdk 27
|
minSdk 27
|
||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 166
|
versionCode 169
|
||||||
versionName "2.49.0"
|
versionName "2.51.0"
|
||||||
|
|
||||||
// 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.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.LinearGradient;
|
import android.graphics.LinearGradient;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
|
import android.graphics.RadialGradient;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.graphics.Shader;
|
import android.graphics.Shader;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
@@ -17,9 +18,11 @@ import java.util.Locale;
|
|||||||
* The Caterpillar Overview style, after the picture book about a very hungry caterpillar (drawn
|
* The Caterpillar Overview style, after the picture book about a very hungry caterpillar (drawn
|
||||||
* anew, not copied from it): cut-paper shapes in bright tissue colours on the book's night page.
|
* anew, not copied from it): cut-paper shapes in bright tissue colours on the book's night page.
|
||||||
*
|
*
|
||||||
* In the middle the caterpillar: its red head carries the speed, and its ten green segments are the
|
* In the middle the caterpillar, arched like the one on the book's cover (overlapping chunks of
|
||||||
* power, lit from the head backwards while drawing, blue while regenerating; it wiggles along while
|
* mottled green tissue paper, a yellow fringe along the back, a red-orange head with a yellow face):
|
||||||
* the car moves. On the left the sun is the battery, filling with the charge, and turning into a
|
* the face carries the speed, and its ten segments are the power, lit from the head backwards while
|
||||||
|
* drawing, blue while regenerating; while the car moves it crawls the way a caterpillar does: a hump runs from tail to head, its feet
|
||||||
|
* lifting and planting, on a branch that slides by. On the left the sun is the battery, filling with the charge, and turning into a
|
||||||
* grey moon with "BATTERY LOW" blinking below 20 %; above it a big leaf holds trip, usage, climate
|
* grey moon with "BATTERY LOW" blinking below 20 %; above it a big leaf holds trip, usage, climate
|
||||||
* and odometer on paper labels. On the right the clock in a paper circle, and the
|
* and odometer on paper labels. On the right the clock in a paper circle, and the
|
||||||
* story's fruit: one apple, two pears, three plums, four strawberries and five oranges, a fruit
|
* story's fruit: one apple, two pears, three plums, four strawberries and five oranges, a fruit
|
||||||
@@ -58,6 +61,8 @@ final class CaterpillarView extends ClusterCanvasView {
|
|||||||
private Bitmap sky;
|
private Bitmap sky;
|
||||||
private int skyW, skyH;
|
private int skyW, skyH;
|
||||||
private float time;
|
private float time;
|
||||||
|
/** Where the crawl is (whole numbers are one full crawl), how far the ground has gone by, and how much it crawls (0 to 1). */
|
||||||
|
private float phase, travel, crawl;
|
||||||
private long lastFrame;
|
private long lastFrame;
|
||||||
private MusicTap music;
|
private MusicTap music;
|
||||||
|
|
||||||
@@ -86,13 +91,18 @@ final class CaterpillarView extends ClusterCanvasView {
|
|||||||
if (w <= 0 || h <= 0) return;
|
if (w <= 0 || h <= 0) return;
|
||||||
float u = Math.min(h / 640f, w / 1100f);
|
float u = Math.min(h / 640f, w / 1100f);
|
||||||
boolean moving = v.speedKmh != null && v.speedKmh > 0.5f;
|
boolean moving = v.speedKmh != null && v.speedKmh > 0.5f;
|
||||||
if (moving) {
|
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
time += 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 = moving || crawl > 0.01f ? now : 0;
|
||||||
} else {
|
if (moving) {
|
||||||
lastFrame = 0;
|
float kmh = Math.min(v.speedKmh, 130f);
|
||||||
|
time += dt;
|
||||||
|
// One crawl takes a second or two; faster the faster the car goes, and the ground slides by.
|
||||||
|
phase = (phase + dt * Math.min(1.2f, 0.4f + kmh / 90f)) % 1000f;
|
||||||
|
travel = (travel + dt * kmh * 1.1f * u) % 100000f;
|
||||||
}
|
}
|
||||||
|
// It settles into its resting arch when the car stops, and sets off again when it moves.
|
||||||
|
crawl += ((moving ? 1f : 0f) - crawl) * Math.min(1f, dt * 4f);
|
||||||
if (sky == null || skyW != w || skyH != h) buildSky(w, h, u);
|
if (sky == null || skyW != w || skyH != h) buildSky(w, h, u);
|
||||||
c.drawBitmap(sky, 0, 0, image);
|
c.drawBitmap(sky, 0, 0, image);
|
||||||
boolean song = drawSong(c, w, u);
|
boolean song = drawSong(c, w, u);
|
||||||
@@ -103,8 +113,9 @@ final class CaterpillarView extends ClusterCanvasView {
|
|||||||
drawClock(c, w, u);
|
drawClock(c, w, u);
|
||||||
drawFruit(c, w, u);
|
drawFruit(c, w, u);
|
||||||
drawGears(c, w, u);
|
drawGears(c, w, u);
|
||||||
|
drawBranch(c, w, u);
|
||||||
drawCaterpillar(c, w, u, pulse);
|
drawCaterpillar(c, w, u, pulse);
|
||||||
keepAnimating(moving || pulse > 0f || song, 33);
|
keepAnimating(moving || crawl > 0.01f || pulse > 0f || song, 33);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void label(Canvas c, String s, float x, float y, float size, int colour, Typeface tf, Paint.Align a) {
|
private void label(Canvas c, String s, float x, float y, float size, int colour, Typeface tf, Paint.Align a) {
|
||||||
@@ -440,56 +451,181 @@ final class CaterpillarView extends ClusterCanvasView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- The branch it crawls on ----------
|
||||||
|
|
||||||
|
/** A branch under the caterpillar; its knots and leaves slide backwards as the car goes. */
|
||||||
|
private void drawBranch(Canvas c, int w, float u) {
|
||||||
|
float hx = w / 2f + 250f * u, y = 410f * u + 87f * u;
|
||||||
|
float l = hx - 640f * u, rr = hx + 110f * u, span = rr - l;
|
||||||
|
line.setColor(0xFF6B4A2B);
|
||||||
|
line.setStrokeWidth(20f * u);
|
||||||
|
c.drawLine(l, y, rr, y, line);
|
||||||
|
line.setColor(0x44FFD59A);
|
||||||
|
line.setStrokeWidth(4f * u);
|
||||||
|
c.drawLine(l + 6f * u, y - 6f * u, rr - 6f * u, y - 6f * u, line);
|
||||||
|
for (int k = 0; k < 8; k++) {
|
||||||
|
float x = l + (((k * span / 8f) - travel) % span + span) % span;
|
||||||
|
fill.setColor(0xFF4A3018);
|
||||||
|
r.set(x - 9f * u, y - 5f * u, x + 9f * u, y + 6f * u);
|
||||||
|
c.drawOval(r, fill);
|
||||||
|
if (k % 2 == 0) {
|
||||||
|
fill.setColor(0xFF3FA34D);
|
||||||
|
r.set(x - 4f * u, y + 6f * u, x + 20f * u, y + 18f * u);
|
||||||
|
c.drawOval(r, fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- The caterpillar: speed and power ----------
|
// ---------- The caterpillar: speed and power ----------
|
||||||
|
|
||||||
|
/** Paper for a segment: three base colours, a light and a teal for the strokes; by state. */
|
||||||
|
private static final int[][] DRAWING = { { 0xFF5FBF52, 0xFF2E9E6A, 0xFF8ACD4A }, { 0xFFC9F06A, 0xFF1F8A7A } };
|
||||||
|
private static final int[][] REGEN = { { 0xFF2A8FC9, 0xFF1F78B4, 0xFF4AA8DA }, { 0xFF9BDCFF, 0xFF3AC8D0 } };
|
||||||
|
private static final int[][] OFF = { { 0xFF2B7A40, 0xFF256B38, 0xFF328C4B }, { 0xFF3A9060, 0xFF2A8078 } };
|
||||||
|
private static final float SEG_R = 47f;
|
||||||
|
|
||||||
|
/** [state][base colour]: a round, lit-from-the-upper-left shading for one segment, in its own units. */
|
||||||
|
private final Paint[][] shades = new Paint[3][3];
|
||||||
|
private Paint headShade;
|
||||||
|
private float shadesFor;
|
||||||
|
|
||||||
|
private static int mix(int a, int b, float t) {
|
||||||
|
int ar = (a >> 16) & 0xFF, ag = (a >> 8) & 0xFF, ab = a & 0xFF;
|
||||||
|
int br = (b >> 16) & 0xFF, bg = (b >> 8) & 0xFF, bb = b & 0xFF;
|
||||||
|
return 0xFF000000 | (ar + (int) ((br - ar) * t)) << 16 | (ag + (int) ((bg - ag) * t)) << 8 | (ab + (int) ((bb - ab) * t));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildShades(float u) {
|
||||||
|
int[][][] states = { DRAWING, REGEN, OFF };
|
||||||
|
float r = SEG_R * u;
|
||||||
|
for (int st = 0; st < 3; st++) {
|
||||||
|
for (int k = 0; k < 3; k++) {
|
||||||
|
int base = states[st][0][k];
|
||||||
|
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
p.setShader(new RadialGradient(-0.3f * r, -0.35f * r, 1.5f * r,
|
||||||
|
new int[] { mix(base, 0xFFFFFFFF, 0.3f), base, mix(base, 0xFF000000, 0.18f) },
|
||||||
|
new float[] { 0f, 0.55f, 1f }, Shader.TileMode.CLAMP));
|
||||||
|
shades[st][k] = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headShade = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
headShade.setShader(new RadialGradient(-25f * u, -30f * u, 130f * u,
|
||||||
|
new int[] { 0xFFFF6A45, 0xFFE8452B, 0xFFA82414 }, new float[] { 0f, 0.55f, 1f }, Shader.TileMode.CLAMP));
|
||||||
|
shadesFor = u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The caterpillar, after the picture book's newer pictures: plump round segments like balls of
|
||||||
|
* wound paper, each with brush strokes in light green and teal, bigger towards the head, a fringe
|
||||||
|
* of yellow and orange bristles along the back, stubby brown feet, and a red apple-shaped head with
|
||||||
|
* big teal eyes and two lilac ears. At rest it arches high in the middle. Power lights the segments
|
||||||
|
* (bright green, or blue when regenerating, dull when dark); the speed is on the head under the
|
||||||
|
* eyes. Crawling, a real caterpillar pulls its rear forward and humps the body up, and the hump
|
||||||
|
* runs from the tail to the head while the feet it lifts swing forward and the others stay planted;
|
||||||
|
* the body also bunches and stretches as it goes.
|
||||||
|
*/
|
||||||
private void drawCaterpillar(Canvas c, int w, float u, float pulse) {
|
private void drawCaterpillar(Canvas c, int w, float u, float pulse) {
|
||||||
float hx = w / 2f + 250f * u, hy = 400f * u, hr = 76f * u;
|
if (shadesFor != u) buildShades(u);
|
||||||
|
float hx = w / 2f + 250f * u, hy = 410f * u;
|
||||||
int lit = Tally.ofPower(v.powerKw, 150f, 50f, SEGMENTS);
|
int lit = Tally.ofPower(v.powerKw, 150f, 50f, SEGMENTS);
|
||||||
int colour = regen() ? ICE : GREEN;
|
int state = lit == 0 ? 2 : regen() ? 1 : 0;
|
||||||
// The segments from the tail towards the head, the legs behind them.
|
float arch = (80f * (1f - crawl) + 26f * crawl) * u;
|
||||||
|
float cycle = phase % 1f;
|
||||||
|
float stretch = 1f + 0.08f * crawl * (float) Math.sin(cycle * 2f * Math.PI);
|
||||||
|
float wave = 1.25f - cycle * 1.6f;
|
||||||
|
float[] xs = new float[SEGMENTS], ys = new float[SEGMENTS], lift = new float[SEGMENTS];
|
||||||
|
for (int i = 0; i < SEGMENTS; i++) {
|
||||||
|
float along = (SEGMENTS - 1 - i) / (SEGMENTS - 1f);
|
||||||
|
float d = Math.max(0f, 1f - Math.abs(along - wave) / 0.4f);
|
||||||
|
lift[i] = crawl * d * d * (3f - 2f * d);
|
||||||
|
xs[i] = hx - 104f * u - i * 46f * u * stretch;
|
||||||
|
ys[i] = hy + 22f * u - arch * (float) Math.sin(Math.PI * (i + 0.7) / (SEGMENTS + 0.4)) - lift[i] * 46f * u;
|
||||||
|
}
|
||||||
|
float r = SEG_R * u;
|
||||||
|
// From the tail towards the head, each overlapping the one before.
|
||||||
for (int i = SEGMENTS - 1; i >= 0; i--) {
|
for (int i = SEGMENTS - 1; i >= 0; i--) {
|
||||||
float x = hx - 100f * u - i * 54f * u;
|
|
||||||
float y = hy + 10f * u + (float) Math.sin(time * 3.5f - i * 0.75f) * 12f * u;
|
|
||||||
boolean on = i < lit;
|
boolean on = i < lit;
|
||||||
float rad = 32f * u * (1f + (on ? pulse * (i % 2 == 0 ? 0.18f : 0.12f) : 0f));
|
int[][] pal = on ? (regen() ? REGEN : DRAWING) : OFF;
|
||||||
line.setColor(HEAD);
|
Paint shade = shades[on ? (regen() ? 1 : 0) : 2][i % 3];
|
||||||
line.setStrokeWidth(4f * u);
|
int prev = Math.max(0, i - 1), next = Math.min(SEGMENTS - 1, i + 1);
|
||||||
float foot = (i % 2 == 0 ? 6f : -6f) * u;
|
float angle = (float) Math.toDegrees(Math.atan2(ys[prev] - ys[next], xs[prev] - xs[next]));
|
||||||
c.drawLine(x, y + rad - 4f * u, x + foot, y + rad + 14f * u, line);
|
// Bigger towards the head, and swelling a little with the music.
|
||||||
fill.setColor(on ? colour : 0xFF3B7A4A);
|
float grow = (0.82f + 0.2f * (1f - i / (SEGMENTS - 1f))) * (1f + (on ? pulse * (i % 2 == 0 ? 0.14f : 0.09f) : 0f));
|
||||||
c.drawCircle(x, y, rad, fill);
|
c.save();
|
||||||
line.setColor(on ? GREEN_DARK : 0xFF2A5A36);
|
c.translate(xs[i], ys[i]);
|
||||||
|
c.rotate(angle);
|
||||||
|
c.scale(grow, grow);
|
||||||
|
// A stubby brown foot under every other one: a lifted foot swings forward and up.
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
float up = lift[i] * 12f * u, fwd = lift[i] * 12f * u;
|
||||||
|
this.r.set(-0.2f * r + fwd, 0.85f * r - up, 0.4f * r + fwd, 1.4f * r - up);
|
||||||
|
fill.setColor(0xFF9A6A3A);
|
||||||
|
c.drawOval(this.r, fill);
|
||||||
|
}
|
||||||
|
this.r.set(-r, -1.05f * r, r, 1.05f * r);
|
||||||
|
c.drawOval(this.r, shade);
|
||||||
|
// Brush strokes down the ball, light and teal in turn.
|
||||||
line.setStrokeWidth(3f * u);
|
line.setStrokeWidth(3f * u);
|
||||||
c.drawCircle(x, y, rad, line);
|
for (int k = -2; k <= 2; k++) {
|
||||||
if (on) {
|
float x0 = k * 0.32f * r;
|
||||||
fill.setColor(0x44FFFFFF);
|
line.setColor(AccentColor.withAlpha(pal[1][(k + 2) % 2], 0x70));
|
||||||
c.drawCircle(x - rad * 0.3f, y - rad * 0.35f, rad * 0.28f, fill);
|
path.reset();
|
||||||
|
path.moveTo(x0 * 0.7f, -0.8f * r);
|
||||||
|
path.quadTo(x0 * 1.5f, 0f, x0 * 0.7f, 0.8f * r);
|
||||||
|
c.drawPath(path, line);
|
||||||
}
|
}
|
||||||
|
// The fringe along the back, in yellow and orange.
|
||||||
|
line.setStrokeWidth(3f * u);
|
||||||
|
for (int k = -3; k <= 3; k++) {
|
||||||
|
line.setColor(on ? (k % 2 == 0 ? 0xFFFFC21F : 0xFFFF8A1F) : 0x80FFC21F);
|
||||||
|
c.drawLine(k * 0.28f * r, (-0.98f + Math.abs(k) * 0.05f) * r, k * 0.34f * r, (-1.22f + Math.abs(k) * 0.06f) * r, line);
|
||||||
}
|
}
|
||||||
// The head, with antennae and eyes, and the speed.
|
c.restore();
|
||||||
line.setColor(HEAD);
|
|
||||||
line.setStrokeWidth(4f * u);
|
|
||||||
c.drawLine(hx - 22f * u, hy - hr + 8f * u, hx - 40f * u, hy - hr - 30f * u, line);
|
|
||||||
c.drawLine(hx + 22f * u, hy - hr + 8f * u, hx + 40f * u, hy - hr - 30f * u, line);
|
|
||||||
fill.setColor(0xFFFFA726);
|
|
||||||
c.drawCircle(hx - 40f * u, hy - hr - 32f * u, 7f * u, fill);
|
|
||||||
c.drawCircle(hx + 40f * u, hy - hr - 32f * u, 7f * u, fill);
|
|
||||||
fill.setColor(HEAD);
|
|
||||||
c.drawCircle(hx, hy, hr * (1f + pulse * 0.04f), fill);
|
|
||||||
fill.setColor(0x33FFFFFF);
|
|
||||||
c.drawCircle(hx - hr * 0.35f, hy - hr * 0.4f, hr * 0.2f, fill);
|
|
||||||
for (int side = -1; side <= 1; side += 2) {
|
|
||||||
fill.setColor(0xFFFFFFFF);
|
|
||||||
c.drawCircle(hx + side * 24f * u, hy - 34f * u, 14f * u, fill);
|
|
||||||
fill.setColor(INK);
|
|
||||||
c.drawCircle(hx + side * 24f * u + 3f * u, hy - 33f * u, 6f * u, fill);
|
|
||||||
}
|
}
|
||||||
label(c, fmt(v.speedKmh, "%.0f", "—"), hx, hy + 38f * u, 58f * u, 0xFFFFFFFF, serif, Paint.Align.CENTER);
|
drawHead(c, hx, hy, u, pulse);
|
||||||
label(c, "KM/H", hx, hy + 58f * u, 14f * u, 0xCCFFE9EA, face, Paint.Align.CENTER);
|
|
||||||
boolean regen = regen();
|
boolean regen = regen();
|
||||||
String power = v.powerKw == null ? "— kW" : String.format(Locale.US, regen ? "+%.1f kW" : "%.1f kW",
|
String power = v.powerKw == null ? "— kW" : String.format(Locale.US, regen ? "+%.1f kW" : "%.1f kW",
|
||||||
Math.abs(v.powerKw));
|
Math.abs(v.powerKw));
|
||||||
outlined(c, power, hx - 100f * u - 4.5f * 54f * u, 556f * u, 34f * u, regen ? ICE : BONE, face, Paint.Align.CENTER, u);
|
outlined(c, power, hx - 104f * u - 4.5f * 46f * u, 556f * u, 34f * u, regen ? ICE : BONE, face, Paint.Align.CENTER, u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A red apple-shaped head, big teal eyes with yellow rims and two lilac ears, the speed under the eyes. */
|
||||||
|
private void drawHead(Canvas c, float hx, float hy, float u, float pulse) {
|
||||||
|
float hs = 1f + pulse * 0.04f;
|
||||||
|
c.save();
|
||||||
|
c.translate(hx, hy);
|
||||||
|
c.scale(hs, hs);
|
||||||
|
// The ears, stubby and rounded, leaning out.
|
||||||
|
for (int side = -1; side <= 1; side += 2) {
|
||||||
|
c.save();
|
||||||
|
c.rotate(side * 12f, side * 36f * u, -64f * u);
|
||||||
|
r.set(side * 36f * u - 15f * u, -122f * u, side * 36f * u + 15f * u, -58f * u);
|
||||||
|
fill.setColor(0xFFB59AE0);
|
||||||
|
c.drawOval(r, fill);
|
||||||
|
r.set(side * 36f * u - 15f * u, -122f * u, side * 36f * u - 2f * u, -58f * u);
|
||||||
|
fill.setColor(0x40FFFFFF);
|
||||||
|
c.drawOval(r, fill);
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
r.set(-88f * u, -76f * u, 88f * u, 82f * u);
|
||||||
|
c.drawOval(r, headShade);
|
||||||
|
fill.setColor(0x55000000);
|
||||||
|
r.set(-12f * u, -78f * u, 12f * u, -68f * u);
|
||||||
|
c.drawOval(r, fill);
|
||||||
|
for (int side = -1; side <= 1; side += 2) {
|
||||||
|
float ex = side * 34f * u;
|
||||||
|
r.set(ex - 19f * u, -50f * u, ex + 19f * u, 6f * u);
|
||||||
|
fill.setColor(0xFF2FA89A);
|
||||||
|
c.drawOval(r, fill);
|
||||||
|
line.setColor(0xFFFFD84A);
|
||||||
|
line.setStrokeWidth(4f * u);
|
||||||
|
c.drawOval(r, line);
|
||||||
|
fill.setColor(0x99FFFFFF);
|
||||||
|
c.drawCircle(ex - 6f * u, -34f * u, 4.5f * u, fill);
|
||||||
|
}
|
||||||
|
outlined(c, fmt(v.speedKmh, "%.0f", "—"), 0f, 50f * u, 40f * u, 0xFFFFF3D0, serif, Paint.Align.CENTER, u);
|
||||||
|
label(c, "KM/H", 0f, 70f * u, 12f * u, 0xFFFFE0B0, face, Paint.Align.CENTER);
|
||||||
|
c.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- What plays: a butterfly or a ladybird tows it on tissue paper ----------
|
// ---------- What plays: a butterfly or a ladybird tows it on tissue paper ----------
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 313 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 217 KiB After Width: | Height: | Size: 282 KiB |
Reference in New Issue
Block a user