App 2.50.0: the Caterpillar crawls while you drive
A hump runs from the tail to the head, lifted feet swing forward while the others stay planted, and the branch it walks on slides by with the speed. Parked, it settles into its high arch. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
+2
-2
@@ -34,8 +34,8 @@ android {
|
||||
applicationId "com.stinnesbeck.nilsmg4"
|
||||
minSdk 27
|
||||
targetSdk 36
|
||||
versionCode 167
|
||||
versionName "2.49.1"
|
||||
versionCode 168
|
||||
versionName "2.50.0"
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -20,7 +20,8 @@ import java.util.Locale;
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
@@ -59,6 +60,8 @@ final class CaterpillarView extends ClusterCanvasView {
|
||||
private Bitmap sky;
|
||||
private int skyW, skyH;
|
||||
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 MusicTap music;
|
||||
|
||||
@@ -87,13 +90,18 @@ final class CaterpillarView extends ClusterCanvasView {
|
||||
if (w <= 0 || h <= 0) return;
|
||||
float u = Math.min(h / 640f, w / 1100f);
|
||||
boolean moving = v.speedKmh != null && v.speedKmh > 0.5f;
|
||||
long now = SystemClock.uptimeMillis();
|
||||
float dt = lastFrame == 0 ? 0f : Math.min(0.2f, (now - lastFrame) / 1000f);
|
||||
lastFrame = moving || crawl > 0.01f ? now : 0;
|
||||
if (moving) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
time += lastFrame == 0 ? 0f : Math.min(0.2f, (now - lastFrame) / 1000f);
|
||||
lastFrame = now;
|
||||
} else {
|
||||
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);
|
||||
c.drawBitmap(sky, 0, 0, image);
|
||||
boolean song = drawSong(c, w, u);
|
||||
@@ -104,8 +112,9 @@ final class CaterpillarView extends ClusterCanvasView {
|
||||
drawClock(c, w, u);
|
||||
drawFruit(c, w, u);
|
||||
drawGears(c, w, u);
|
||||
drawBranch(c, w, u);
|
||||
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) {
|
||||
@@ -441,6 +450,31 @@ 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 ----------
|
||||
|
||||
/** Tissue-paper greens for a segment: base colours, a light patch and a teal patch, by state. */
|
||||
@@ -458,12 +492,20 @@ final class CaterpillarView extends ClusterCanvasView {
|
||||
float hx = w / 2f + 250f * u, hy = 410f * u, hr = 76f * u;
|
||||
int lit = Tally.ofPower(v.powerKw, 150f, 50f, SEGMENTS);
|
||||
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];
|
||||
// At rest it arches high in the middle. 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.
|
||||
float arch = (92f * (1f - crawl) + 30f * 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++) {
|
||||
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));
|
||||
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 - 92f * u - i * 48f * u * stretch;
|
||||
ys[i] = hy + 24f * u - arch * (float) Math.sin(Math.PI * (i + 0.7) / (SEGMENTS + 0.4)) - lift[i] * 46f * u;
|
||||
}
|
||||
// From the tail towards the head, each overlapping the one before.
|
||||
for (int i = SEGMENTS - 1; i >= 0; i--) {
|
||||
@@ -480,7 +522,9 @@ final class CaterpillarView extends ClusterCanvasView {
|
||||
if (i % 2 == 0) {
|
||||
line.setColor(0xFF8A7B2E);
|
||||
line.setStrokeWidth(8f * u);
|
||||
c.drawLine(x, y + 46f * u, x + 3f * u, y + 60f * u, line);
|
||||
// A lifted foot swings forward and up; a planted one is on the branch.
|
||||
float up = lift[i] * 12f * u;
|
||||
c.drawLine(x, y + 46f * u, x + (3f + lift[i] * 12f) * u, y + 60f * u - up, 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);
|
||||
|
||||
Reference in New Issue
Block a user