App 2.49.1: the Caterpillar looks more like the one in the book

It arches over the middle like the cover's: overlapping chunks of
mottled green paper, a yellow fringe, olive feet, and a red-orange head
with a yellow face and purple antennae. Still an original drawing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-11-07 10:18:15 +01:00
co-authored by Claude Sonnet 5.5
parent 3bb0d5eb71
commit 16b11405c8
6 changed files with 84 additions and 44 deletions
@@ -17,9 +17,10 @@ import java.util.Locale;
* 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.
*
* In the middle the caterpillar: its red head carries the speed, and its ten green segments are the
* power, lit from the head backwards while drawing, blue while regenerating; it wiggles along while
* the car moves. On the left the sun is the battery, filling with the charge, and turning into a
* In the middle the caterpillar, arched like the one on the book's cover (overlapping chunks of
* mottled green tissue paper, a yellow fringe along the back, a red-orange head with a yellow face):
* the face carries the speed, and its ten segments are the power, lit from the head backwards while
* drawing, blue while regenerating; its back humps up and down while the car moves. 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
* 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
@@ -442,54 +443,90 @@ final class CaterpillarView extends ClusterCanvasView {
// ---------- The caterpillar: speed and power ----------
/** Tissue-paper greens for a segment: base colours, a light patch and a teal patch, by state. */
private static final int[][] DRAWING = { { 0xFF4CC15C, 0xFF38A94A, 0xFF66CC6A }, { 0xFF9BE08A, 0xFF2FA89A } };
private static final int[][] REGEN = { { 0xFF2A8FC9, 0xFF1F78B4, 0xFF4AA8DA }, { 0xFF9BDCFF, 0xFF3AC8D0 } };
private static final int[][] OFF = { { 0xFF2B7A40, 0xFF256B38, 0xFF328C4B }, { 0xFF3A9060, 0xFF2A8078 } };
/**
* The caterpillar arches over the middle like the one on the book's cover: overlapping chunks of
* mottled tissue paper in greens and teals, a fringe of yellow hair along its back, olive feet,
* and a red-orange head with a yellow face, green eyes and purple antennae. Power lights the
* segments (bright green, or blue when regenerating, dull when dark); the speed is on its face.
*/
private void drawCaterpillar(Canvas c, int w, float u, float pulse) {
float hx = w / 2f + 250f * u, hy = 400f * u, hr = 76f * u;
float hx = w / 2f + 250f * u, hy = 410f * u, hr = 76f * u;
int lit = Tally.ofPower(v.powerKw, 150f, 50f, SEGMENTS);
int colour = regen() ? ICE : GREEN;
// The segments from the tail towards the head, the legs behind them.
int[][] palette = regen() ? REGEN : DRAWING;
// The arch: highest in the middle, and it humps up and down while the car moves.
float arch = 92f * u + (float) Math.sin(time * 3.5f) * 16f * u;
float[] xs = new float[SEGMENTS], ys = new float[SEGMENTS];
for (int i = 0; i < SEGMENTS; i++) {
xs[i] = hx - 92f * u - i * 48f * u;
ys[i] = hy + 24f * u - arch * (float) Math.sin(Math.PI * (i + 0.7) / (SEGMENTS + 0.4));
}
// From the tail towards the head, each overlapping the one before.
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;
float rad = 32f * u * (1f + (on ? pulse * (i % 2 == 0 ? 0.18f : 0.12f) : 0f));
line.setColor(HEAD);
line.setStrokeWidth(4f * u);
float foot = (i % 2 == 0 ? 6f : -6f) * u;
c.drawLine(x, y + rad - 4f * u, x + foot, y + rad + 14f * u, line);
fill.setColor(on ? colour : 0xFF3B7A4A);
c.drawCircle(x, y, rad, fill);
line.setColor(on ? GREEN_DARK : 0xFF2A5A36);
line.setStrokeWidth(3f * u);
c.drawCircle(x, y, rad, line);
if (on) {
fill.setColor(0x44FFFFFF);
c.drawCircle(x - rad * 0.3f, y - rad * 0.35f, rad * 0.28f, fill);
int[][] pal = on ? palette : OFF;
float x = xs[i], y = ys[i];
int prev = Math.max(0, i - 1), next = Math.min(SEGMENTS - 1, i + 1);
float angle = (float) Math.toDegrees(Math.atan2(ys[prev] - ys[next], xs[prev] - xs[next]));
float grow = 1f + (on ? pulse * (i % 2 == 0 ? 0.16f : 0.1f) : 0f);
c.save();
c.rotate(angle, x, y);
c.scale(grow, grow, x, y);
// The foot, under every other segment.
if (i % 2 == 0) {
line.setColor(0xFF8A7B2E);
line.setStrokeWidth(8f * u);
c.drawLine(x, y + 46f * u, x + 3f * u, y + 60f * u, line);
}
// The chunk of paper with two patches of other tissue on it.
r.set(x - 37f * u, y - 48f * u, x + 37f * u, y + 48f * u);
fill.setColor(pal[0][i % 3]);
c.drawOval(r, fill);
fill.setColor(AccentColor.withAlpha(pal[1][0], 0x80));
r.set(x - 28f * u, y - (i % 2 == 0 ? 38f : 10f) * u, x + 4f * u, y + (i % 2 == 0 ? -4f : 36f) * u);
c.drawOval(r, fill);
fill.setColor(AccentColor.withAlpha(pal[1][1], 0x70));
r.set(x - 2f * u, y - (i % 2 == 0 ? 8f : 40f) * u, x + 30f * u, y + (i % 2 == 0 ? 38f : 0f) * u);
c.drawOval(r, fill);
// The fringe of hair along the back.
line.setColor(on ? 0xFFD9C84A : 0x80D9C84A);
line.setStrokeWidth(2f * u);
for (int k = -2; k <= 2; k++) {
c.drawLine(x + k * 12f * u, y - (46f - Math.abs(k) * 4f) * u, x + k * 15f * u, y - (57f - Math.abs(k) * 4f) * u, line);
}
c.restore();
}
// The head, with antennae and eyes, and the speed.
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);
// The head: purple antennae, a red-orange head with a yellow face, green eyes, the speed.
line.setColor(0xFF7B68C8);
line.setStrokeWidth(8f * u);
c.drawLine(hx - 24f * u, hy - hr + 10f * u, hx - 46f * u, hy - hr - 40f * u, line);
c.drawLine(hx + 24f * u, hy - hr + 10f * u, hx + 46f * u, hy - hr - 40f * u, line);
float hs = 1f + pulse * 0.04f;
fill.setColor(0xFFE8452B);
c.drawCircle(hx, hy, hr * hs, fill);
r.set(hx - 52f * u, hy - 64f * u, hx + 52f * u, hy + 62f * u);
fill.setColor(0xFFFFC21F);
c.drawOval(r, fill);
for (int side = -1; side <= 1; side += 2) {
fill.setColor(0xFFFFFFFF);
c.drawCircle(hx + side * 24f * u, hy - 34f * u, 14f * u, fill);
r.set(hx + side * 26f * u - 11f * u, hy - 50f * u, hx + side * 26f * u + 11f * u, hy - 20f * u);
fill.setColor(0xFF3F9A3A);
c.drawOval(r, fill);
line.setColor(INK);
line.setStrokeWidth(2f * u);
c.drawOval(r, line);
fill.setColor(INK);
c.drawCircle(hx + side * 24f * u + 3f * u, hy - 33f * u, 6f * u, fill);
c.drawCircle(hx + side * 26f * u, hy - 38f * u, 3.5f * u, fill);
}
label(c, fmt(v.speedKmh, "%.0f", "—"), hx, hy + 38f * u, 58f * u, 0xFFFFFFFF, serif, Paint.Align.CENTER);
label(c, "KM/H", hx, hy + 58f * u, 14f * u, 0xCCFFE9EA, face, Paint.Align.CENTER);
label(c, fmt(v.speedKmh, "%.0f", "—"), hx, hy + 30f * u, 46f * u, INK, serif, Paint.Align.CENTER);
label(c, "KM/H", hx, hy + 48f * u, 13f * u, 0xFF7A3A10, face, Paint.Align.CENTER);
boolean regen = regen();
String power = v.powerKw == null ? "— kW" : String.format(Locale.US, regen ? "+%.1f kW" : "%.1f kW",
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 - 92f * u - 4.5f * 48f * u, 556f * u, 34f * u, regen ? ICE : BONE, face, Paint.Align.CENTER, u);
}
// ---------- What plays: a butterfly or a ladybird tows it on tissue paper ----------