← back to Motion Table - Solid Rotationcompare with ground truth →
model output
Claude Sonnet 4.6
Motion Table - Solid Rotation
A 0.45T 0.14
1<!DOCTYPE html>
2<html>
3<head>
4<style>
5 * { margin: 0; padding: 0; box-sizing: border-box; }
6 body { background: #4a4a4a; display: flex; justify-content: center; align-items: center; width: 100vw; height: 100vh; overflow: hidden; }
7 canvas { display: block; }
8</style>
9</head>
10<body>
11<canvas id="c"></canvas>
12<script>
13const canvas = document.getElementById('c');
14const ctx = canvas.getContext('2d');
15canvas.width = 1024;
16canvas.height = 768;
17
18const cx = 512, cy = 384;
19const R = 130;
20
21// Spirograph: epitrochoid/hypotrochoid
22// Looking at the frames: starts as a single circle, then inner circle rolls inside
23// creating a hypotrochoid pattern that cycles through various petal/loop configurations
24
25// The animation shows a spirograph being drawn progressively
26// It looks like a hypotrochoid with slowly changing parameters
27
28let t = 0;
29const totalDuration = 20000; // 20 seconds for full cycle
30let startTime = null;
31
32// Store drawn path points
33let points = [];
34let phase = 0;
35
36// Parameters that slowly change to create the morphing effect
37// Hypotrochoid: x = (R-r)*cos(t) + d*cos((R-r)/r * t)
38// y = (R-r)*sin(t) - d*sin((R-r)/r * t)
39
40// From the frames, it looks like multiple overlapping ellipses/circles
41// that create spirograph patterns. The ratio changes over time.
42
43const outerR = R;
44
45function draw(timestamp) {
46 if (!startTime) startTime = timestamp;
47 const elapsed = (timestamp - startTime) % totalDuration;
48 const progress = elapsed / totalDuration; // 0 to 1
49
50 ctx.clearRect(0, 0, canvas.width, canvas.height);
51 ctx.fillStyle = '#4a4a4a';
52 ctx.fillRect(0, 0, canvas.width, canvas.height);
53
54 // Draw the spirograph path up to current progress
55 // The pattern: ratio r/R slowly changes from ~1 (circle) through various fractions
56 // creating different petal counts
57
58 ctx.strokeStyle = 'rgba(255,255,255,0.85)';
59 ctx.lineWidth = 1;
60 ctx.lineCap = 'round';
61 ctx.lineJoin = 'round';
62
63 // Draw multiple overlapping hypotrochoids with slightly different phases
64 // to create the "hand-drawn" sketchy look
65
66 const numTrails = 8;
67
68 for (let trail = 0; trail < numTrails; trail++) {
69 const trailOffset = (trail / numTrails) * 0.15;
70 const trailProgress = Math.max(0, progress - trailOffset * 0.3);
71
72 if (trailProgress <= 0) continue;
73
74 const alpha = trail === 0 ? 0.9 : 0.3 - trail * 0.02;
75 ctx.strokeStyle = `rgba(255,255,255,${Math.max(0.05, alpha)})`;
76
77 drawSpirograph(trailProgress, trail * 0.08);
78 }
79
80 requestAnimationFrame(draw);
81}
82
83function drawSpirograph(progress, phaseOffset) {
84 // The ratio changes over time to create morphing patterns
85 // r/R ratio: starts near 1 (circle), morphs through various values
86
87 const totalAngle = progress * Math.PI * 20; // total angle swept
88
89 // Slowly varying inner radius ratio
90 const rRatio = 0.5 + 0.45 * Math.sin(progress * Math.PI * 2 + phaseOffset);
91 const r = outerR * Math.abs(rRatio);
92 const d = outerR * 0.85;
93
94 const steps = Math.floor(totalAngle / (Math.PI * 2) * 360);
95 if (steps < 2) return;
96
97 ctx.beginPath();
98
99 for (let i = 0; i <= steps; i++) {
100 const angle = (i / steps) * totalAngle + phaseOffset;
101
102 // Hypotrochoid
103 const Rr = outerR - r;
104 const x = cx + Rr * Math.cos(angle) + d * Math.cos((Rr / Math.max(r, 1)) * angle);
105 const y = cy + Rr * Math.sin(angle) - d * Math.sin((Rr / Math.max(r, 1)) * angle);
106
107 if (i === 0) ctx.moveTo(x, y);
108 else ctx.lineTo(x, y);
109 }
110
111 ctx.stroke();
112}
113
114// Better approach: draw a spirograph that looks like the frames
115// The frames show: single circle -> overlapping ellipses -> star/petal patterns -> back to circle
116// This is a Lissajous/spirograph with slowly changing frequency ratio
117
118function drawBetter(timestamp) {
119 if (!startTime) startTime = timestamp;
120 const elapsed = (timestamp - startTime) % totalDuration;
121 const progress = elapsed / totalDuration;
122
123 ctx.clearRect(0, 0, canvas.width, canvas.height);
124 ctx.fillStyle = '#4a4a4a';
125 ctx.fillRect(0, 0, canvas.width, canvas.height);
126
127 // Draw multiple slightly offset spirographs for sketchy look
128 const layers = [
129 { alpha: 0.85, offset: 0, rOffset: 0 },
130 { alpha: 0.35, offset: 0.03, rOffset: 2 },
131 { alpha: 0.25, offset: -0.02, rOffset: -1.5 },
132 { alpha: 0.2, offset: 0.05, rOffset: 3 },
133 { alpha: 0.15, offset: -0.04, rOffset: -2.5 },
134 ];
135
136 for (const layer of layers) {
137 ctx.strokeStyle = `rgba(255,255,255,${layer.alpha})`;
138 ctx.lineWidth = 1;
139 ctx.beginPath();
140
141 const totalAngle = progress * Math.PI * 24;
142 const steps = Math.max(2, Math.floor(totalAngle * 60));
143
144 // Slowly changing ratio creates the morphing spirograph
145 // ratio oscillates to create different petal counts
146 const baseRatio = 1 - progress * 0.15; // slowly decreasing
147
148 for (let i = 0; i <= steps; i++) {
149 const t = (i / steps) * totalAngle;
150
151 // Use a ratio that creates interesting patterns
152 // The ratio between outer and inner determines petal count
153 const ratio = 7/8 + layer.rOffset * 0.005;
154 const r = outerR * ratio;
155 const Rr = outerR - r;
156 const d = outerR * 0.92;
157
158 const x = cx + Rr * Math.cos(t + layer.offset) + d * Math.cos((Rr/r) * t + layer.offset);
159 const y = cy + Rr * Math.sin(t + layer.offset) - d * Math.sin((Rr/r) * t + layer.offset);
160
161 if (i === 0) ctx.moveTo(x, y);
162 else ctx.lineTo(x, y);
163 }
164 ctx.stroke();
165 }
166
167 requestAnimationFrame(drawBetter);
168}
169
170requestAnimationFrame(drawBetter);
171</script>
172</body>
173</html>