1/* Celexly home: a small living world at the foot of every band. 2 Each one is a 3D diorama built on the engine in home-3d.js, and the same 3 figure, WuYuXiang, walks through all of them, so scrolling the page reads 4 as one person moving through the year rather than a set of unrelated 5 backdrops. Scroll position drives where he is in each scene: he walks in 6 from the left as a band arrives, stops to watch while it holds the screen, 7 and walks out to the right as the next season takes over. */ 8(function () { 9 "use strict"; 10 11 var E = window.CLX3D; 12 if (!E) return; 13 var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; 14 15 var C = E.hex2rgb; 16 function shade(c, k) { return [c[0] * k, c[1] * k, c[2] * k]; } 17 18 /* The last click, in world coordinates, decaying. Only one scene builds at 19 a time, so a single shared slot is enough and every scene can read it. */ 20 var ACT = { pulse: 0, x: 0 }; 21 22 /* deterministic scatter so a resize never reshuffles a scene */ 23 function rnd(i, k) { 24 var x = Math.sin(i * 127.1 + k * 311.7) * 43758.5453; 25 return x - Math.floor(x); 26 } 27 28 /* ---------------------------------------------------------------- parts */ 29 30 /* The floor of the world: a long slab lying in the XZ plane. */ 31 function ground(m, P, x0, x1, z0, z1) { 32 var w = (x1 - x0) / 2, d = (z1 - z0) / 2, cx = (x0 + x1) / 2, cz = (z0 + z1) / 2; 33 E.extrude(m, E.roundRect(w, d, 14, 2), -9, 9, P.soilSide, P.soil, 34 function (x, y, z) { return [cx + x, 9 + z, cz + y]; }, { bias: -400 }); 35 /* the surface, laid just proud of the earth under it */ 36 E.extrude(m, E.roundRect(w - 2, d - 2, 13, 2), -11, -9.4, P.turf, P.turf, 37 function (x, y, z) { return [cx + x, 9 + z, cz + y]; }, { bias: -396 }); 38 39 /* a ring going out from wherever it was last touched */ 40 if (ACT.pulse > 0.02) { 41 var rr = (1 - ACT.pulse) * 190 + 8; 42 var prev = null; 43 for (var q = 0; q <= 26; q++) { 44 var a = (q / 26) * Math.PI * 2;
45 var pt = [ACT.x + Math.cos(a) * rr, -2, 18 + Math.sin(a) * rr * 0.42]; 46 if (prev) m.line(prev, pt, P.ring || [255, 255, 255], 2, ACT.pulse * 0.5); 47 prev = pt; 48 } 49 /* and a few things kicked up out of the ground */ 50 for (q = 0; q < 8; q++) { 51 var kf = 1 - ACT.pulse; 52 var ka = (q / 8) * Math.PI * 2; 53 E.box(m, ACT.x + Math.cos(ka) * kf * 70, 54 -6 - Math.sin(ACT.pulse * Math.PI) * 34, 55 18 + Math.sin(ka) * kf * 30, 56 2, 2, 2, P.turf, null, 60, 0.8); 57 } 58 } 59 } 60 61 /* A tapering trunk, stacked so it narrows as it rises. */ 62 function trunk(m, x, z, h, w, colA, colB, lean) { 63 var seg = 5, i; 64 for (i = 0; i < seg; i++) { 65 var f = i / seg, f2 = (i + 1) / seg; 66 var ww = w * (1 - f * 0.45); 67 E.box(m, x + (lean || 0) * f * h, -h * f - h / (seg * 2), z, 68 ww, h / (seg * 2) + 0.6, ww, i % 2 ? colA : colB, null, 0, ww * 0.3); 69 } 70 } 71 72 /* A canopy of overlapping blobs, breathing on the wind. */ 73 function canopy(m, x, y, z, r, cols, t, seed) { 74 var n = 5, i; 75 for (i = 0; i < n; i++) { 76 var a = (i / n) * Math.PI * 2 + seed; 77 var rr = r * (0.5 + rnd(seed + i, 1) * 0.36); 78 var sway = Math.sin(t * 0.9 + seed + i) * 2.2; 79 E.gem(m, rr, 80 1, 81 x + Math.cos(a) * r * 0.62 + sway, 82 y + (rnd(seed + i, 2) - 0.5) * r * 0.5, 83 z + Math.sin(a) * r * 0.45, 84 cols[i % cols.length], null); 85 } 86 E.gem(m, r * 0.72, 1, x, y, z, cols[0], null); 87 } 88 89 function blobTree(m, x, z, h, r, cols, bark, t, seed) { 90 trunk(m, x, z, h, 5.5, bark[0], bark[1], 0); 91 canopy(m, x, -h - r * 0.5, z, r, cols, t, seed); 92 } 93 94 /* A conifer: stacked tiers, snow optional. */ 95 function pineTree(m, x, z, h, col, dark, snow) { 96 trunk(m, x, z, h * 0.34, 3.6, bark0, bark1, 0); 97 var tiers = 4, i; 98 for (i = 0; i < tiers; i++) { 99 var f = i / tiers; 100 var y = -h * 0.3 - f * h * 0.62; 101 var rr = 20 * (1 - f * 0.62); 102 E.extrude(m, E.disc(rr, 9), -5, 5, i % 2 ? col : dark, col, 103 function (px, py, pz) { return [x + px, y + pz, z + py]; }); 104 if (snow) { 105 E.extrude(m, E.disc(rr * 0.72, 9), -6.6, -5.2, [320, 326, 334], [330, 334, 340], 106 function (px, py, pz) { return [x + px, y + pz, z + py]; }, { bias: 30 }); 107 } 108 } 109 } 110 var bark0 = [122, 86, 62], bark1 = [96, 66, 46]; 111 112 /* A low dome. A sphere squashed by eye reads as a rock; stacked discs 113 read as something settled on the ground. */ 114 function dome(m, x, z, r, h, col, top) { 115 var tiers = 8, i; 116 for (i = 0; i < tiers; i++) { 117 var f = i / tiers, f2 = (i + 1) / tiers; 118 var rr = r * Math.sqrt(Math.max(0.05, 1 - f * f)); 119 E.extrude(m, E.disc(rr, 14), -f2 * h, -f * h, col, top || col, 120 function (px, py, pz) { return [x + px, pz, z + py]; }, { bias: -22 + i }); 121 } 122 } 123 124 /* Falling matter: petals, leaves or snow, each drifting on its own curl. */ 125 function fall(m, t, cols, n, size, speed, x0, x1, spin) { 126 for (var i = 0; i < n; i++) { 127 var ph = rnd(i, 7); 128 var y = -((t * speed + ph * 200) % 200); 129 var x = x0 + rnd(i, 8) * (x1 - x0) + Math.sin(t * 0.8 + ph * 9) * 16; 130 var z = -70 + rnd(i, 9) * 140; 131 var a = t * spin + ph * 7; 132 var ca = Math.cos(a), sa = Math.sin(a); 133 var s = size * (0.7 + rnd(i, 10) * 0.6); 134 var c = cols[i % cols.length]; 135 m.face([ 136 [x - s * ca, y - s * 0.5, z - s * sa], 137 [x + s * ca, y - s * 0.3, z + s * sa], 138 [x + s * ca, y + s * 0.5, z + s * sa], 139 [x - s * ca, y + s * 0.3, z - s * sa] 140 ], c, { bias: 120 }); 141 } 142 } 143 144 /* ------------------------------------------------------------- the figure 145 WuYuXiang. One skeleton, standing or seated, with a looping action per 146 world: watching the blossom, raking leaves, sipping something hot. He 147 stays put; the season is what changes around him. */ 148 function person(m, x, z, t, P, act, face) { 149 var seated = act === "sip" || act === "warm" || act === "tea" || act === "work"; 150 var crouch = act === "feed"; 151 var breathe = Math.sin(t * 1.5) * 0.9; 152 var base = -breathe; 153 154 /* Everything is built facing the viewer and then turned, so walking can
155 point him along his travel instead of sliding him sideways while he 156 stares out of the screen. */ 157 var _cf = Math.cos(face || 0), _sf = Math.sin(face || 0); 158 function part(dx, dy, dz, hx, hy, hz, col, bias, r) { 159 E.box(m, x + dx * _cf - dz * _sf, base - dy, z + dz * _cf + dx * _sf, 160 hx, hy, hz, col, null, 161 bias === undefined ? 20 : bias, r === undefined ? Math.min(hx, hy) * 0.3 : r); 162 } 163 function head(dy, tilt, lean) { 164 part(0, dy - 8, 0, 3, 2.5, 3, P.skin, 24, 0.9); 165 part(0, dy, lean, 8, 7.4, 7, P.skin, 26, 2.4); 166 part(0, dy + 6, lean + 0.6, 8.6, 3.3, 7.6, P.hair, 38, 2); 167 part(0, dy + 1.5, -6.6 + lean, 8.2, 2.9, 1.2, P.hair, 42, 1.4); 168 part(-3.2, dy - 0.5 + tilt, -7.4 + lean, 1.3, 1.7, 0.6, P.eye, 56, 0.4); 169 part(3.2, dy - 0.5 + tilt, -7.4 + lean, 1.3, 1.7, 0.6, P.eye, 56, 0.4); 170 if (P.scarf) { 171 part(0, dy - 5, 0, 8.4, 2.6, 7.4, P.scarf, 44, 1.6); 172 part(5, dy - 11, -6, 2, 5, 1.2, P.scarf, 46, 0.8); 173 } 174 m.anchor("head", [x, base - dy - 14, z]); 175 } 176 /* A two piece limb hung off a fixed shoulder. `sw` swings the upper arm 177 forward from straight down, `el` bends the forearm on from there, both 178 in radians, so the elbow always meets and the hand lands where the two 179 angles say it should. Returns the hand, for anything being held. */ 180 function arm(side, sw, el, lift) { 181 var sx = side * 10.5, sy = 30, U = 7, F = 6.5; 182 var ex = sx, ey = sy - (lift || 0) - Math.cos(sw) * U, ez = -Math.sin(sw) * U; 183 var a2 = sw + el; 184 var hx2 = ex, hy2 = ey - Math.cos(a2) * F, hz2 = ez - Math.sin(a2) * F; 185 /* upper arm, at the midpoint of shoulder to elbow */ 186 part(sx, (sy + ey) / 2 - (lift || 0) * 0.5, ez / 2, 2.3, U / 2 + 0.7, 2.3, P.skin, 24, 0.9); 187 /* forearm, at the midpoint of elbow to hand */ 188 part(ex, (ey + hy2) / 2, (ez + hz2) / 2, 2.1, F / 2 + 0.6, 2.1, P.skin, 26, 0.9); 189 return [hx2, hy2, hz2]; 190 } 191 192 if (crouch) { 193 /* down on his heels at the water */ 194 var dip = Math.max(0, Math.sin(t * 0.9)); 195 part(-4.5, 4, -3, 3.3, 4, 5, P.trouser, 20, 1); 196 part(4.5, 4, -3, 3.3, 4, 5, P.trouser, 20, 1); 197 part(0, 14, 0, 8.2, 7, 5.4, P.coat, 24, 2); 198 part(0, 12, -5.4, 5.8, 5, 0.9, P.coatIn, 40, 1.6); 199 part(-10, 13, -2, 2.3, 5, 2.3, P.skin, 26, 0.9); 200 part(10, 12 - dip * 6, -6 - dip * 5, 2.3, 5.5, 2.3, P.skin, 26, 0.9); 201 head(26, 1.2, 1.6); 202 return; 203 } 204 205 if (seated) { 206 part(-4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1); 207 part(4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1); 208 part(-4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1); 209 part(4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1); 210 part(0, 18, 0, 8.5, 8, 5.5, P.coat, 24, 2); 211 part(0, 16, -5.4, 6, 5.6, 0.9, P.coatIn, 40, 1.6); 212 213 if (act === "warm") { 214 /* both palms held out to the fire, turning slowly */ 215 var wv = Math.sin(t * 1.2) * 0.16; 216 part(-8, 20 + wv * 8, -10, 2.3, 4.4, 2.3, P.skin, 26, 0.9); 217 part(8, 20 - wv * 8, -10, 2.3, 4.4, 2.3, P.skin, 26, 0.9); 218 part(-8, 20 + wv * 8, -15, 2, 1.6, 2.6, P.skin, 28, 0.8); 219 part(8, 20 - wv * 8, -15, 2, 1.6, 2.6, P.skin, 28, 0.8); 220 head(30, 0, 0.8); 221 return; 222 } 223 if (act === "work") { 224 /* tapping at something on the bench in front of him */ 225 var tap = Math.sin(t * 6) * 1.2 + Math.sin(t * 9.3) * 0.6; 226 part(-7, 19, -10, 2.3, 4.2, 2.3, P.skin, 26, 0.9); 227 part(7, 19 + tap, -10, 2.3, 4.2, 2.3, P.skin, 26, 0.9); 228 part(-7, 16, -14, 2, 1.4, 2.4, P.skin, 28, 0.8); 229 part(7, 16 + tap, -14, 2, 1.4, 2.4, P.skin, 28, 0.8); 230 head(30, 1.4, 1.2); 231 return; 232 } 233 /* sip and tea: the cup comes up to his face, then back down */ 234 var cyc = (t * 0.28) % 1; 235 var lift = cyc < 0.34 ? cyc / 0.34 : cyc < 0.52 ? 1 : Math.max(0, 1 - (cyc - 0.52) / 0.3); 236 var cupY = 20 + lift * 9, cupZ = -8.5 + lift * 2.4; 237 part(-9, 18 + lift * 3, -6 - lift * 1.5, 2.3, 4.6, 2.3, P.skin, 26, 0.9); 238 part(9, 18 + lift * 3, -6 - lift * 1.5, 2.3, 4.6, 2.3, P.skin, 26, 0.9); 239 part(0, cupY, cupZ, 3.2, 3, 3, P.mug, 44, 1); 240 part(3.6, cupY, cupZ, 1, 1.4, 0.8, P.mug, 44, 0.4); 241 for (var q = 0; q < 5; q++) { 242 var sp = ((t * 0.7 + q * 0.2) % 1); 243 part(Math.sin(sp * 7 + q) * 3.4, cupY + 4 + sp * 16, cupZ,
244 1 - sp * 0.5, 1 - sp * 0.5, 1 - sp * 0.5, P.steam, 48, 0.5); 245 } 246 head(30 + lift * 1.2, lift * 1.6, 0); 247 return; 248 } 249 250 /* --- standing --- */ 251 part(-4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1); 252 part(4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1); 253 part(-4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1); 254 part(4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1); 255 part(0, 25, 0, 8.5, 8.5, 5.5, P.coat, 22, 2); 256 part(0, 23, -5.4, 6, 6, 0.9, P.coatIn, 38, 1.6); 257 258 if (act === "rake") { 259 /* A stroke, not a wave. He reaches the head out, drags it back toward 260 his feet, then resets, and the leaves come with it. */ 261 var cyc = (t * 0.55) % 1; 262 var pull = cyc < 0.55 263 ? 1 - Math.pow(1 - cyc / 0.55, 2) /* the drag, quick then easing */ 264 : 1 - (cyc - 0.55) / 0.45; /* reaching back out */ 265 var far = 34 - pull * 24; /* how far out the head is */ 266 var lean = pull * 0.22; 267 268 arm(-1, 0.5 + pull * 0.5, 0.5 - pull * 0.3, 0); 269 arm(1, 0.95 - pull * 0.5, 0.45 - pull * 0.2, 0); 270 /* the shaft runs from his hands down to the head on the ground */ 271 var hxp = x + 10, hy0 = base - 26, hz0 = -8; 272 var hz1 = -far, hy1 = base - 2; 273 for (var sg = 0; sg < 6; sg++) { 274 var f2 = sg / 5; 275 E.box(m, hxp, hy0 + (hy1 - hy0) * f2, z + hz0 + (hz1 - hz0) * f2, 276 1.2, 2.4, 1.2, P.tool, null, 24, 0.5); 277 } 278 E.box(m, hxp, base - 3, z + hz1, 9, 1.2, 1.4, P.tool, null, 24, 0.5); 279 for (var ti = -3; ti <= 3; ti++) { 280 E.box(m, hxp + ti * 2.8, base - 1.4, z + hz1, 0.8, 2.4, 0.8, P.tool, null, 24, 0.3); 281 } 282 /* leaves gathered in front of the tines, piling as the stroke lands */ 283 for (var lf = 0; lf < 10; lf++) { 284 var lo = (lf % 5) - 2; 285 E.box(m, hxp + lo * 3.4 + (lf > 4 ? 1.6 : 0), 286 base - 1.2 - (lf > 4 ? 2.2 : 0) - pull * 1.6, 287 z + hz1 - 5 - (lf > 4 ? 2 : 0), 288 2.6, 1, 2, lf % 2 ? [214, 96, 48] : [232, 150, 62], null, 22, 0.8); 289 } 290 head(38, 0.8, 0.4 + lean * 4); 291 return; 292 } 293 if (act === "light") { 294 /* reaching up to a lantern, checking the flame */ 295 var rr = 0.5 + 0.5 * Math.sin(t * 0.8); 296 arm(-1, 0.35, 0.35, 0); 297 arm(1, -2.5 * rr - 0.2, 0.5 - rr * 0.3, 0); 298 head(38, -0.8 * rr, 1.4 * rr); 299 return; 300 } 301 if (act === "walk") { 302 /* getting from one place to the next, legs and arms opposed */ 303 var step = Math.sin(t * 5.4); 304 part(-4.5 + step * 2, 5, step * 3.4, 3.3, 5, 3.3, P.boot, 18, 1); 305 part(4.5 - step * 2, 5, -step * 3.4, 3.3, 5, 3.3, P.boot, 18, 1); 306 part(-4.5 + step * 1.4, 13, step * 2.2, 3.2, 4.6, 3.2, P.trouser, 18, 1); 307 part(4.5 - step * 1.4, 13, -step * 2.2, 3.2, 4.6, 3.2, P.trouser, 18, 1); 308 part(0, 25, 0, 8.5, 8.5, 5.5, P.coat, 22, 2); 309 part(0, 23, -5.4, 6, 6, 0.9, P.coatIn, 38, 1.6); 310 arm(-1, 0.3 - step * 0.5, 0.4, 0); 311 arm(1, 0.3 + step * 0.5, 0.4, 0); 312 head(38, 0, 0.2); 313 return; 314 } 315 if (act === "stretch") { 316 /* reaching for the pack as he comes up */ 317 /* sitting up out of sleep, both arms reaching over his head */ 318 var up3 = Math.min(1, Math.max(0, Math.sin(t * 0.9) * 0.5 + 0.5)); 319 part(-4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1); 320 part(4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1); 321 part(-4.5, 9, -12, 3.2, 3.4, 3.2, P.trouser, 22, 1); 322 part(4.5, 9, -12, 3.2, 3.4, 3.2, P.trouser, 22, 1); 323 part(0, 18, 0, 8.5, 8, 5.5, P.coat, 24, 2); 324 part(0, 16, -5.4, 6, 5.6, 0.9, P.coatIn, 40, 1.6); 325 arm(-1, -2.6 * up3 - 0.2, 0.2, 6); 326 arm(1, -2.6 * up3 - 0.2, 0.2, 6); 327 head(30, -up3 * 1.4, -up3 * 2); 328 return; 329 } 330 if (act === "feedfire") { 331 /* leaning in to lay a log on, then sitting back off the heat */ 332 var lay2 = Math.max(0, Math.sin(t * 1.6)); 333 part(-4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1); 334 part(4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1); 335 part(-4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1); 336 part(4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1); 337 part(0, 18 - lay2 * 2, -lay2 * 4, 8.5, 8, 5.5, P.coat, 24, 2); 338 part(0, 16 - lay2 * 2, -5.4 - lay2 * 4, 6, 5.6, 0.9, P.coatIn, 40, 1.6); 339 part(-7, 17 - lay2 * 6, -11 - lay2 * 8, 2.3, 4.4, 2.3, P.skin, 26, 0.9); 340 part(7, 17 - lay2 * 6, -11 - lay2 * 8, 2.3, 4.4, 2.3, P.skin, 26, 0.9); 341 /* the log going on */ 342 part(0, 15 - lay2 * 7, -16 - lay2 * 10, 5, 1.8, 1.8, [122, 86, 62], 30, 0.8); 343 head(30 - lay2 * 2, 1.2, 1.2 + lay2 * 2); 344 return; 345 } 346 if (act === "roll") { 347 /* both hands on the pin, pushing it out and drawing it back */ 348 var rl = Math.sin(t * 2.6); 349 part(-4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1); 350 part(4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1); 351 part(-4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1); 352 part(4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1); 353 part(0, 24, 1.5 + rl * 1.5, 8.5, 8.5, 5.5, P.coat, 22, 2); 354 part(0, 22, -4 + rl * 1.5, 6, 6, 0.9, P.coatIn, 38, 1.6); 355 /* arms out over the board, moving with the pin */ 356 part(-8, 22, -10 - rl * 5, 2.3, 5.4, 2.3, P.skin, 26, 0.9); 357 part(8, 22, -10 - rl * 5, 2.3, 5.4, 2.3, P.skin, 26, 0.9); 358 /* the pin, and its handles */ 359 part(0, 18, -14 - rl * 6, 7, 1.8, 1.8, P.tool, 30, 0.9); 360 part(-8.5, 18, -14 - rl * 6, 1.6, 1.1, 1.1, P.tool, 30, 0.5); 361 part(8.5, 18, -14 - rl * 6, 1.6, 1.1, 1.1, P.tool, 30, 0.5); 362 head(38, 1.4, 1.2); 363 return; 364 } 365 if (act === "peel") { 366 /* the long peel held out level, in or out of the oven */ 367 var pv = Math.sin(t * 1.5) * 0.12; 368 part(-4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1); 369 part(4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1); 370 part(-4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1); 371 part(4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1); 372 part(0, 25, 0, 8.5, 8.5, 5.5, P.coat, 22, 2); 373 part(0, 23, -5.4, 6, 6, 0.9, P.coatIn, 38, 1.6); 374 arm(-1, -1.5 + pv, 1.5, 0); 375 arm(1, -1.5 - pv, 1.5, 0); 376 /* the shaft, running out level, and the blade on the end of it */ 377 for (var pq = 0; pq < 7; pq++) { 378 part(2, 27 + pv * 6, -12 - pq * 6, 1, 1, 3.4, P.tool, 30, 0.4); 379 } 380 part(2, 27 + pv * 6, -56, 6, 0.8, 6, P.tool, 30, 0.6); 381 if (P.onPeel) { 382 part(2, 25 + pv * 6, -56, 4, 2.2, 3.2, P.loaf || [206, 156, 96], 34, 1.6); 383 } 384 head(38, 0.8, 1); 385 return; 386 } 387 if (act === "fish") { 388 /* Sat on the bank with the rod well out over the water. Nothing is 389 hauled up: the float just sits there and rings go out from it when 390 something touches the line. */ 391 var idle = Math.sin(t * 0.7) * 0.5; 392 var nudge = Math.max(0, Math.sin(t * 0.5)); 393 part(-4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1); 394 part(4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1); 395 part(-4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1); 396 part(4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1); 397 part(0, 18, 0, 8.5, 8, 5.5, P.coat, 24, 2); 398 part(0, 16, -5.4, 6, 5.6, 0.9, P.coatIn, 40, 1.6); 399 part(0, 26, 6, 5, 6, 2.4, P.rag || [72, 74, 66], 22, 1); 400 part(-8, 19, -8, 2.3, 4.4, 2.3, P.skin, 26, 0.9); 401 part(8, 19, -9, 2.3, 4.4, 2.3, P.skin, 26, 0.9); 402 /* The rod runs out to his right, because that is where the water is: 403 he sits on the left bank and the pond opens away from him. Running it 404 toward the viewer put the float down on the grass. */ 405 for (var rq = 0; rq < 14; rq++) { 406 var rf = rq / 14; 407 part(8 + rf * 74, 22 + rf * 8 + idle * rf, -6 - rf * 10, 408 1.8, 0.8, 0.8, P.tool || [138, 100, 70], 30, 0.3); 409 } 410 /* the line, straight down from the tip to a float on the surface */ 411 var fx = 84, fz = -16; 412 for (var lq = 0; lq < 9; lq++) { 413 part(fx, 28 - lq * 3, fz, 0.28, 1.6, 0.28, [222, 226, 232], 28, 0.15); 414 } 415 part(fx, 4, fz, 1.5, 1.5, 1.5, [214, 74, 62], 30, 0.6); 416 part(fx, 6.6, fz, 1.1, 1.1, 1.1, [246, 246, 250], 30, 0.4); 417 /* rings going out from it, two of them, always running */ 418 for (var rr3 = 0; rr3 < 2; rr3++) { 419 var rp3 = ((t * 0.45 + rr3 * 0.5) % 1); 420 var rad3 = 3 + rp3 * 26; 421 var prev3 = null; 422 for (var aq = 0; aq <= 20; aq++) { 423 var aa3 = (aq / 20) * Math.PI * 2;
424 var pt3 = [x + fx + Math.cos(aa3) * rad3, base - 3.2, 425 z + fz + Math.sin(aa3) * rad3 * 0.45]; 426 if (prev3) m.line(prev3, pt3, [206, 232, 242], 1.5, (1 - rp3) * 0.75); 427 prev3 = pt3; 428 } 429 } 430 head(38, 0, 1.4 + nudge * 0.3); 431 return; 432 } 433 if (act === "plant") { 434 /* crouched, both hands down, settling something into the ground */ 435 var pd = 0.5 + 0.5 * Math.sin(t * 2.4); 436 part(-4.5, 4, -3, 3.3, 4, 5, P.trouser, 20, 1); 437 part(4.5, 4, -3, 3.3, 4, 5, P.trouser, 20, 1); 438 part(0, 14, 0, 8.2, 7, 5.4, P.coat, 24, 2); 439 part(0, 12, -5.4, 5.8, 5, 0.9, P.coatIn, 40, 1.6); 440 part(-7, 11 - pd * 3, -9, 2.3, 5, 2.3, P.skin, 26, 0.9); 441 part(7, 11 - pd * 3, -9, 2.3, 5, 2.3, P.skin, 26, 0.9); 442 head(26, 1.4, 1.8); 443 return; 444 } 445 if (act === "eat") { 446 /* the apple comes up to his mouth and goes back down, a bite at a time */ 447 var bite = (t * 0.8) % 1; 448 var up2 = bite < 0.45 ? bite / 0.45 : Math.max(0, 1 - (bite - 0.45) / 0.35); 449 part(-4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1); 450 part(4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1); 451 part(-4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1); 452 part(4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1); 453 part(0, 25, 0, 8.5, 8.5, 5.5, P.coat, 22, 2); 454 part(0, 23, -5.4, 6, 6, 0.9, P.coatIn, 38, 1.6); 455 arm(-1, 0.3, 0.35, 0); 456 arm(1, -0.6 - up2 * 1.1, 0.8 - up2 * 0.35, 0); 457 /* the apple itself, in his hand */ 458 part(9, 30 + up2 * 8, -7 - up2 * 2, 2.2 - up2 * 0.4, 2.2 - up2 * 0.4, 2.2, 459 [206, 62, 54], 60, 1); 460 part(9, 32.6 + up2 * 8, -7 - up2 * 2, 0.5, 1, 0.5, [96, 128, 70], 60, 0.3); 461 head(38, up2 * 0.8, 0.6 + up2 * 0.8); 462 return; 463 } 464 if (act === "code") { 465 /* sat at the bench, hands on the keys, head down at the screen */ 466 var tap2 = Math.sin(t * 7) * 0.09 + Math.sin(t * 11.3) * 0.05; 467 part(-4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1); 468 part(4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1); 469 part(-4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1); 470 part(4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1); 471 part(0, 18, 0, 8.5, 8, 5.5, P.coat, 24, 2); 472 part(0, 16, -5.4, 6, 5.6, 0.9, P.coatIn, 40, 1.6); 473 part(-7, 19 + tap2 * 8, -11, 2.2, 4.2, 2.2, P.skin, 26, 0.9); 474 part(7, 19 - tap2 * 8, -11, 2.2, 4.2, 2.2, P.skin, 26, 0.9); 475 part(-7, 16 + tap2 * 8, -15, 2, 1.4, 2.4, P.skin, 28, 0.8); 476 part(7, 16 - tap2 * 8, -15, 2, 1.4, 2.4, P.skin, 28, 0.8); 477 head(30, 1.6, 1.4); 478 return; 479 } 480 if (act === "sleep" || act === "stretch") { 481 /* the pack is off and flat on the ground beside him either way */ 482 /* flat on the grass, lid up, straps loose */ 483 part(-15, 2.2, 10, 6, 2.2, 4.4, P.rag || [72, 74, 66], 16, 1); 484 part(-15, 4.6, 10, 4.4, 0.5, 3, P.coatIn, 18, 0.7); 485 part(-19, 1.2, 10, 1.2, 1.2, 3.4, P.rag || [72, 74, 66], 18, 0.5); 486 part(-11, 1.2, 10, 1.2, 1.2, 3.4, P.rag || [72, 74, 66], 18, 0.5); 487 } 488 if (act === "sleep") { 489 /* flat out on his back, chest rising, one arm behind his head */ 490 var br = Math.sin(t * 0.9) * 0.5; 491 /* A part lying on the ground has its centre one half height up, and no 492 more. The extra four units here were holding the whole figure off the 493 grass, which is what looked like it was floating. */ 494 function lay(dz, dy, hx, hy, hz, col, bias, r) { 495 E.box(m, x + dy, base - hy, z + dz, hx, hy, hz, col, null, 496 bias === undefined ? 20 : bias, r === undefined ? 1 : r); 497 } 498 /* Low and long. Every part is about half its old height so the figure 499 lies down rather than standing up in section, and it runs along the 500 ground instead of stacking up off it. */ 501 lay(20, -4, 3, 2.2, 4, P.boot, 18, 1); 502 lay(20, 4, 3, 2.2, 4, P.boot, 18, 1); 503 lay(10, -4, 2.8, 2.4, 6, P.trouser, 18, 1); 504 lay(10, 4, 2.8, 2.4, 6, P.trouser, 18, 1); 505 lay(-3, 0, 5.5, 2.8 + br, 9, P.coat, 22, 2); 506 lay(-3, 0, 4, 3 + br, 1, P.coatIn, 30, 1.4); 507 lay(1, -8, 2, 1.8, 5, P.skin, 22, 0.8); 508 lay(1, 8, 2, 1.8, 5, P.skin, 22, 0.8); 509 lay(-14, 0, 4.4, 3.2, 4.4, P.skin, 24, 1.8); 510 lay(-18, 0, 4.8, 3.6, 3, P.hair, 30, 1.6); 511 lay(-16, -2.2, 0.8, 0.9, 1.2, P.eye, 40, 0.35); 512 lay(-16, 2.2, 0.8, 0.9, 1.2, P.eye, 40, 0.35); 513 /* the odd Z drifting off him */ 514 for (var zq = 0; zq < 3; zq++) { 515 var zf = ((t * 0.4 + zq * 0.33) % 1); 516 E.box(m, x + 6, base - 12 - zf * 26, z - 16, 1.6 - zf, 1.6 - zf, 0.6, 517 P.eye, null, 50, 0.4); 518 } 519 m.anchor("head", [x, base - 16, z - 14]); 520 return; 521 } 522 if (act === "photo") { 523 /* down on the flowers with a camera up at his face, holding still for 524 the shot then lowering it to look at what he got */ 525 var pc = (t * 0.34) % 1; 526 var upf = pc < 0.62 ? Math.min(1, pc / 0.16) : Math.max(0, 1 - (pc - 0.62) / 0.18); 527 var snap = pc > 0.44 && pc < 0.47; 528 arm(-1, -1.5 * upf - 0.2, 0.9 - upf * 0.5, 0); 529 arm(1, -1.5 * upf - 0.2, 0.9 - upf * 0.5, 0); 530 /* the camera, at his eye when it is up and at his chest when it is not */ 531 var cy2 = base - (22 + upf * 15), cz2 = -9 - upf * 3; 532 E.box(m, x, cy2, z + cz2, 5, 3.6, 3, P.cam || [58, 60, 70], null, 40, 1.2); 533 E.box(m, x + 1.6, cy2 - 4.4, z + cz2, 1.6, 1, 1.6, P.cam || [58, 60, 70], null, 42, 0.5); 534 E.box(m, x, cy2, z + cz2 - 3.4, 2.2, 2.2, 1.2, [120, 160, 190], null, 44, 1); 535 if (snap) { 536 E.box(m, x, cy2, z + cz2 - 6, 5, 5, 3, [330, 330, 330], null, 60, 2); 537 } 538 head(38, upf * 1.4, 1.6 + upf * 1.6); 539 return; 540 } 541 if (act === "gaze") { 542 /* one hand up shading his eyes, scanning slowly */ 543 var pan = Math.sin(t * 0.4) * 1.6; 544 arm(-1, 0.3, 0.4, 0); 545 arm(1, -1.9, 1.5, 0); 546 head(38, 0.4, 1 + pan * 0.3); 547 return; 548 } 549 /* watch: head tipped back, and every so often a hand out for a petal */ 550 var reach = Math.max(0, Math.sin(t * 0.42)); 551 arm(-1, 0.3, 0.35, 0); 552 arm(1, -0.4 - reach * 1.5, 0.6 - reach * 0.45, 0); 553 head(38, -1.2, 2.4); 554 } 555 556 /* What is under the pond. The tank is built with no front wall, so the 557 ground above reads as a bank you are looking in behind: koi working the 558 water, weeds leaning on the current, bubbles going up, light coming 559 down through the surface. */ 560 function underwater(m, t, p) { 561 var i, k; 562 var WY0 = 18, WY1 = 300; /* waterline, running off the frame */ 563 var back = [46, 92, 116], side = [38, 78, 100], bed = [128, 132, 112]; 564 565 E.box(m, 0, (WY0 + WY1) / 2, 74, 300, (WY1 - WY0) / 2, 2, back, null, -300, 0); 566 E.box(m, -298, (WY0 + WY1) / 2, 0, 2, (WY1 - WY0) / 2, 74, side, null, -298, 0); 567 E.box(m, 298, (WY0 + WY1) / 2, 0, 2, (WY1 - WY0) / 2, 74, side, null, -298, 0); 568 569 570 /* the surface seen from below, and the light coming through it */ 571 E.box(m, 0, WY0, 0, 300, 1.4, 74, [120, 186, 210], null, -305, 0); 572 for (i = 0; i < 9; i++) { 573 var lx = -260 + i * 66 + Math.sin(t * 0.5 + i) * 12; 574 m.line([lx, WY0 + 2, -30 + (i % 3) * 30], [lx + 26, 190, 20 + (i % 2) * 30], 575 [176, 224, 236], 2.4, 0.12 + 0.06 * Math.sin(t * 1.3 + i)); 576 } 577 578 /* weeds, leaning together on the current */ 579 for (i = 0; i < 16; i++) { 580 var wx = -280 + rnd(i, 51) * 560, wz = -50 + rnd(i, 52) * 100; 581 var wh = 18 + rnd(i, 53) * 30; 582 var wbase = 100; 583 var lean = Math.sin(t * 0.7 + i) * 7; 584 for (k = 0; k < 5; k++) { 585 var f = k / 5; 586 E.box(m, wx + lean * f * f, wbase - 6 - f * wh - wh / 10, wz, 587 2.2 - f * 0.8, wh / 10, 2.2 - f * 0.8, 588 k % 2 ? [64, 138, 96] : [48, 116, 78], null, -60, 0.6); 589 } 590 } 591 /* stones, set on a shelf partway down rather than on a floor */ 592 for (i = 0; i < 7; i++) { 593 var sr = 8 + rnd(i, 56) * 10; 594 E.box(m, -250 + rnd(i, 54) * 500, 96 - sr * 0.5, -30 + rnd(i, 55) * 60, 595 sr, sr * 0.5, sr * 0.7, [104, 108, 104], null, -70, sr * 0.4); 596 } 597 598 /* koi working the tank, each on its own circuit */ 599 for (i = 0; i < 7; i++) { 600 var a = t * (0.16 + i * 0.035) + i * 1.4; 601 var rr = 60 + i * 26; 602 var kx = Math.cos(a) * rr; 603 var kz = Math.sin(a) * rr * 0.36; 604 /* they break toward whatever just hit the water */ 605 if (ACT.pulse > 0.02) { 606 var dart = ACT.pulse * 0.5; 607 kx += (ACT.x - kx) * dart; 608 kz += (18 - kz) * dart * 0.5; 609 } 610 var ky = WY0 + 18 + i * 9 + Math.sin(t * 0.8 + i) * 6; 611 var col = i % 3 === 0 ? [252, 246, 244] : i % 3 === 1 ? [246, 130, 92] : [236, 176, 90]; 612 var tail = Math.sin(t * 4 + i) * 4; 613 /* body, then a tail that beats behind it */ 614 E.box(m, kx, ky, kz, 9, 3.4, 4.4, col, null, -40, 1.6); 615 E.box(m, kx - Math.cos(a) * 11, ky, kz - Math.sin(a) * 4 + tail, 4, 2.4, 3, col, null, -41, 1.2); 616 E.box(m, kx - Math.cos(a) * 15, ky, kz - Math.sin(a) * 6 + tail * 1.6, 617 2.6, 3.4, 1.6, col, null, -42, 0.8); 618 /* a dark eye so it reads as a fish and not a lozenge */
619 E.box(m, kx + Math.cos(a) * 7, ky - 1, kz + Math.sin(a) * 2.4 - 3.4, 620 1.4, 1.4, 0.8, [32, 34, 40], null, -38, 0.4); 621 } 622 623 /* bubbles going up in two threads */ 624 for (i = 0; i < 14; i++) { 625 var bf = ((t * 0.34 + rnd(i, 58)) % 1); 626 var bx = i < 7 ? -120 : 130; 627 E.box(m, bx + Math.sin(bf * 8 + i) * 9, 100 - bf * 78, (i % 7) * 8 - 24, 628 1 + bf * 1.6, 1 + bf * 1.6, 1 + bf * 1.6, [186, 226, 238], null, -30, 0.8); 629 } 630 } 631 632 /* The stream that ties the closing band to the footer. Same channel, same 633 depth, same bank, drawn in both scenes so the water runs straight across 634 the join between the two canvases. */ 635 function stream(m, t, warm, flow) { 636 var i; 637 var wcol = warm ? [92, 132, 156] : [52, 70, 110]; 638 var lip = warm ? [126, 96, 74] : [44, 52, 82]; 639 /* the channel, cut back into the bank */ 640 E.box(m, 0, 5, 48, 430, 5, 22, lip, null, -200, 0); 641 E.box(m, 0, 3, 48, 426, 1.6, 19, wcol, null, -196, 0); 642 /* the surface catching light, moving downstream */ 643 for (i = 0; i < 30; i++) { 644 var sx = -420 + ((i * 39 + t * (34 + (flow || 0) * 90)) % 840); 645 var sz = 40 + (i % 4) * 9; 646 E.box(m, sx, 1.6, sz, 7 + (i % 3) * 4, 0.5, 1.1, 647 warm ? [206, 196, 170] : [128, 152, 206], null, -190, 0.4); 648 } 649 /* stepping stones across it, in both scenes at the same places */ 650 var ST = [-176, -104, 12, 96, 208]; 651 for (i = 0; i < ST.length; i++) { 652 E.box(m, ST[i], 1, 48 + (i % 2 ? -6 : 6), 9, 3.4, 7, 653 warm ? [150, 142, 130] : [86, 92, 116], null, -170, 2); 654 } 655 } 656 657 /* What is out beyond the platform. Only things that belong in a sky: banks 658 of cloud sitting further back than the near weather, and motes on the 659 wind between them. Nothing hangs there that would not hang there. */ 660 function surroundings(m, t, P, kind) { 661 var i, k; 662 var lit = P.cloudLit || [255, 255, 255]; 663 var shd = P.cloudShd || [214, 222, 236]; 664 /* a far bank, bigger and slower than the near cloud, well back */ 665 for (i = 0; i < 5; i++) { 666 var drift = ((t * (2.2 + (i % 3)) + rnd(i, 91) * 1600) % 1700) - 850; 667 var cy = -190 - rnd(i, 92) * 130; 668 var cz = 620 + rnd(i, 93) * 320; 669 var csc = 1.4 + rnd(i, 94) * 1.3; 670 var body = [ 671 [0, 0, 0, 26, 7, 12], [-19, 1, -2, 13, 6, 10], [20, 1, 2, 14, 6, 10], 672 [-6, -8, 0, 15, 7, 10], [11, -9, -1, 11, 6, 9], [2, -16, 0, 8, 5, 7] 673 ]; 674 for (k = 0; k < body.length; k++) { 675 var bd = body[k]; 676 E.box(m, drift + bd[0] * csc, cy + bd[1] * csc, cz + bd[2] * csc, 677 bd[3] * csc, bd[4] * csc, bd[5] * csc, 678 k < 3 ? shd : lit, null, -270 + k, bd[3] * csc * 0.22); 679 } 680 } 681 /* motes carried on the wind between the banks */ 682 for (i = 0; i < 20; i++) { 683 var mf = ((t * 0.06 + rnd(i, 61)) % 1); 684 E.box(m, -460 + mf * 940, -90 - rnd(i, 62) * 170 + Math.sin(t * 0.5 + i) * 12, 685 340 + rnd(i, 63) * 300, 2.4, 2.4, 2.4, 686 P.mote || [255, 255, 255], null, -250, 1); 687 } 688 } 689 690 /* The sky over each world, built out of the same blocks as the ground under 691 it: chunky clouds drifting on their own beats, birds beating across, and 692 whatever that season's cloud is carrying, falling out of the bottom of it. 693 Everything is placed well back so it reads as sky rather than clutter. */ 694 function skyworld(m, t, P, cfg) { 695 var i, k; 696 var CL = cfg.clouds || 0; 697 698 /* --- clouds. Each is a little heap of boxes so it reads as pixel art in 699 three dimensions rather than a flat sprite. --- */ 700 var cloudAt = []; 701 for (i = 0; i < CL; i++) { 702 var drift = ((t * (5 + (i % 3) * 2.5) + rnd(i, 71) * 1100) % 1180) - 590; 703 var cy = -118 - rnd(i, 72) * 110; 704 var cz = 210 + rnd(i, 73) * 250; 705 var csc = 0.7 + rnd(i, 74) * 0.9; 706 cloudAt.push([drift, cy, cz, csc]); 707 var lit = P.cloudLit || [255, 255, 255]; 708 var shd = P.cloudShd || [214, 222, 236]; 709 /* a heap: a wide base course, a narrower one on top, a cap */ 710 var body = [ 711 [0, 0, 0, 26, 7, 12], [-19, 1, -2, 13, 6, 10], [20, 1, 2, 14, 6, 10], 712 [-6, -8, 0, 15, 7, 10], [11, -9, -1, 11, 6, 9], [2, -16, 0, 8, 5, 7] 713 ]; 714 for (k = 0; k < body.length; k++) { 715 var bd = body[k]; 716 E.box(m, drift + bd[0] * csc, cy + bd[1] * csc, cz + bd[2] * csc, 717 bd[3] * csc, bd[4] * csc, bd[5] * csc, 718 k < 3 ? shd : lit, null, -240 + k, bd[3] * csc * 0.22); 719 } 720 } 721 722 /* --- what the cloud is carrying, falling out from under it --- */ 723 if (cfg.drop && cloudAt.length) { 724 var N = cfg.dropN || 30; 725 for (i = 0; i < N; i++) { 726 var src = cloudAt[i % cloudAt.length]; 727 var ph = rnd(i, 75); 728 var f = ((t * (cfg.dropSpeed || 0.22) + ph) % 1); 729 var fx = src[0] + (rnd(i, 76) - 0.5) * 90; 730 var fy = src[1] + 14 + f * (Math.abs(src[1]) + 40); 731 var fz = src[2] - 40 + rnd(i, 77) * 80; 732 if (cfg.drop === "rain") { 733 /* a thread rather than a block, angled with the fall */ 734 m.line([fx, fy, fz], [fx - 3, fy + 15, fz], P.dropCol || [196, 226, 240], 1.3, 0.5); 735 } else if (cfg.drop === "snow") { 736 var sw = Math.sin(t * 1.4 + ph * 9) * 16; 737 E.box(m, fx + sw, fy, fz, 1.8, 1.8, 1.8, [320, 326, 336], null, -60, 0.7); 738 } else { 739 /* petals and leaves ride the wind: a long sideways drift and a turn */ 740 var wind = Math.sin(t * 0.7 + ph * 7) * 46 + f * 70; 741 var sp = t * 2.4 + ph * 9;
742 var cs2 = Math.cos(sp), sn2 = Math.sin(sp); 743 var col = (P.dropCols || [[255, 176, 203]])[i % (P.dropCols || [[255, 176, 203]]).length]; 744 E.box(m, fx + wind, fy, fz + sn2 * 6, 745 3.4 * Math.abs(cs2) + 0.8, 1, 2.2, col, null, -60, 0.6); 746 } 747 } 748 } 749 750 /* --- birds, two skeins working across --- */ 751 for (i = 0; i < (cfg.birds || 0); i++) { 752 var lane = i % 2; 753 var bp = ((t * (0.05 + lane * 0.018) + i * 0.09) % 1.25) - 0.12; 754 var bx = -560 + bp * 1120; 755 var by = -220 - lane * 60 + Math.sin(t * 0.5 + i) * 14; 756 var bz = 300 + lane * 120 + (i % 3) * 30; 757 var beat = Math.sin(t * 8 + i * 1.3); 758 var bc = P.birdCol || [72, 66, 74]; 759 E.box(m, bx, by, bz, 3.4, 1.6, 2.2, bc, null, -230, 0.8); /* body */ 760 E.box(m, bx + 4.4, by - 0.4, bz, 1.6, 1.2, 1.4, bc, null, -230, 0.5); /* head */ 761 E.box(m, bx - 4.6, by - 0.6, bz, 2.4, 1, 1.6, bc, null, -230, 0.5); /* tail */ 762 /* wings, up and down out of phase with each other */ 763 E.box(m, bx, by - beat * 3.2, bz - 5.5, 2, 0.9, 4.4, bc, null, -229, 0.5); 764 E.box(m, bx, by + beat * 3.2, bz + 5.5, 2, 0.9, 4.4, bc, null, -229, 0.5); 765 } 766 } 767 768 /* Things carried down the stream. A new one sets off every couple of 769 seconds, so there is always a line of them in the water, and each one 770 runs the closing band and then the footer as a single journey. 771 772 The identity is taken from the journey, not from the clock, which is what 773 was wrong before: the slot was derived from a shorter period than the 774 journey itself, so an item changed into a different one halfway down and 775 the two bands never showed the same thing. */ 776 var TRIP = 15, SPACING = 2; 777 778 function drifter(m, leg) { 779 /* Every item is numbered by when it set off, so each launch is a different 780 piece and keeps that piece the whole way down. Keying it to the journey 781 index instead gave a run of identical objects, because everything in 782 flight at once shared the same index. */ 783 var jHi = Math.floor(CLOCK / SPACING); 784 var jLo = Math.max(0, Math.ceil((CLOCK - TRIP) / SPACING)); 785 for (var k = jLo; k <= jHi; k++) { 786 var age = CLOCK - k * SPACING; 787 if (age < 0 || age >= TRIP) continue; 788 var n = k; /* the launch number */ 789 var f = age / TRIP; /* 0 at the top, 1 at the end */ 790 /* first half of the journey is the closing band, second half the foot */ 791 if (leg === 0 ? f >= 0.5 : f < 0.5) continue; 792 var g = leg === 0 ? f * 2 : (f - 0.5) * 2; 793 var item = FLOTSAM[n % FLOTSAM.length]; 794 795 var x = -470 + g * 940; 796 var y = -8 + Math.sin(CLOCK * 1.6 + k) * 1.4; 797 var z = 48; 798 var spin = Math.sin(CLOCK * 0.7 + k) * 0.2; 799 var cs = Math.cos(spin), sn = Math.sin(spin); 800 var S = 1.5; /* everything reads larger now */ 801 802 (function (x, y, z, cs, sn, item, k) { 803 function b(dx, dy, dz, hx, hy, hz, col, bias, r) { 804 dx *= S; dy *= S; dz *= S; 805 E.box(m, x + dx * cs - dy * sn, y + dx * sn + dy * cs, z + dz, 806 hx * S, hy * S, hz * S, col, null, 807 bias === undefined ? 44 : bias, (r === undefined ? 0.7 : r) * S); 808 } 809 item.draw(b, CLOCK + k); 810 for (var i = 0; i < 4; i++) { 811 var wf = i / 4; 812 E.box(m, x - 16 - i * 10, -3.4, z + Math.sin(CLOCK * 2 + i + k) * 2.4, 813 6 - wf * 3.4, 0.5, 1.6 + wf, [206, 226, 236], null, 30, 0.5); 814 } 815 })(x, y, z, cs, sn, item, k); 816 } 817 } 818 819 var GOLD = [226, 188, 92], DGOLD = [150, 112, 40], WOOD = [122, 84, 52]; 820 var IRON = [128, 136, 148], DIRON = [86, 92, 104], WHITE = [246, 246, 250]; 821 var BLACK = [40, 38, 46], RED = [206, 62, 54], GREEN = [86, 168, 82]; 822 var FLOTSAM = [ 823 { id: "chest", draw: function (b) { /* the original */ 824 b(0, 0, 0, 11, 5.5, 8, WOOD, 44, 1.2); 825 b(0, -6, 0, 11.4, 2.6, 8.4, [96, 64, 40], 46, 2.4); 826 b(-6.5, -2, 0, 1.4, 8, 8.6, IRON, 48, 0.5); 827 b(6.5, -2, 0, 1.4, 8, 8.6, IRON, 48, 0.5); 828 b(0, -3.4, -8.6, 2.4, 2.6, 0.8, GOLD, 50, 0.6); 829 } }, 830 { id: "mushroom", draw: function (b) { /* Mario */ 831 b(0, 2, 0, 4.4, 4, 4, [244, 240, 226], 44, 1.4); 832 b(0, -4, 0, 8, 4.4, 7, RED, 46, 3); 833 b(-4, -5.4, -3, 2.2, 1.6, 1.6, WHITE, 48, 1); 834 b(4, -5.4, -3, 2.2, 1.6, 1.6, WHITE, 48, 1); 835 b(0, -8, 1, 2.6, 1.6, 2.6, WHITE, 48, 1); 836 } }, 837 { id: "sword", draw: function (b) { /* Zelda */ 838 b(0, -14, 0, 1.8, 12, 0.9, [214, 220, 232], 46, 0.4); 839 b(0, -1, 0, 6.5, 1.4, 1.4, GOLD, 48, 0.5); 840 b(0, 3, 0, 1.4, 3.4, 1.4, [96, 76, 120], 48, 0.5); 841 b(0, 7, 0, 2.4, 1.6, 2.4, GOLD, 48, 0.8); 842 } }, 843 { id: "pokeball", draw: function (b) { /* Pokemon */ 844 b(0, -2.5, 0, 6, 3, 6, RED, 46, 3); 845 b(0, 2.5, 0, 6, 3, 6, WHITE, 46, 3); 846 b(0, 0, 0, 6.4, 0.9, 6.4, BLACK, 48, 1); 847 b(0, 0, -6.2, 2, 2, 0.8, WHITE, 50, 0.9); 848 } }, 849 { id: "diamond", draw: function (b, t) { /* Minecraft */ 850 var g = 0.6 + 0.4 * Math.sin(t * 2.4); 851 b(0, 0, 0, 4.4, 3, 3, [120 + 90 * g, 230, 240], 46, 1.2); 852 b(0, -4, 0, 2.6, 2, 2, [150 + 80 * g, 240, 248], 48, 0.8); 853 b(0, 4, 0, 2.6, 2, 2, [110 + 70 * g, 214, 232], 48, 0.8); 854 } }, 855 { id: "cube", draw: function (b) { /* Portal */ 856 b(0, 0, 0, 6, 6, 6, [186, 190, 196], 46, 1); 857 b(0, 0, -6.2, 3, 3, 0.6, [244, 148, 176], 48, 1.4); 858 b(0, -6.2, 0, 3, 0.6, 3, [244, 148, 176], 48, 1.4); 859 b(6.2, 0, 0, 0.6, 3, 3, [244, 148, 176], 48, 1.4); 860 } }, 861 { id: "duck", draw: function (b) { /* rubber duck */ 862 b(0, 0, 0, 6, 4, 4.4, [246, 208, 78], 46, 2.4); 863 b(4, -5, 0, 3, 3, 3, [246, 208, 78], 48, 1.4); 864 b(7.5, -5, 0, 1.8, 1.2, 1.2, [232, 130, 48], 50, 0.5); 865 b(5.4, -6.4, -2.6, 0.7, 0.8, 0.5, BLACK, 52, 0.3); 866 } }, 867 { id: "crown", draw: function (b) { /* roguelike staple */ 868 b(0, 0, 0, 7, 1.8, 5, GOLD, 46, 0.6); 869 b(-5, -3.4, 0, 1.4, 2.4, 1.4, GOLD, 48, 0.5); 870 b(0, -4.6, 0, 1.4, 3.4, 1.4, GOLD, 48, 0.5); 871 b(5, -3.4, 0, 1.4, 2.4, 1.4, GOLD, 48, 0.5); 872 b(0, -7.6, 0, 1.6, 1.4, 1.6, RED, 50, 0.7); 873 } }, 874 { id: "herb", draw: function (b) { /* Resident Evil */ 875 b(0, 0, 0, 3.4, 4, 3, [196, 62, 58], 46, 1.4); 876 b(-3.4, -3, 0, 2.6, 1, 1.8, [86, 158, 82], 48, 0.9); 877 b(3.4, -4, 0, 2.6, 1, 1.8, [86, 158, 82], 48, 0.9); 878 b(0, -6, 0, 1, 2.4, 1, [70, 128, 66], 48, 0.4); 879 } }, 880 { id: "strawhat", draw: function (b) { /* One Piece */ 881 b(0, 0, 0, 9, 1.2, 8, [232, 200, 130], 46, 3); 882 b(0, -3, 0, 4.6, 3, 4.4, [232, 200, 130], 48, 1.6); 883 b(0, -3, 0, 4.9, 1, 4.7, RED, 50, 1.6); 884 } }, 885 { id: "notebook", draw: function (b) { /* Death Note */ 886 b(0, 0, 0, 6, 1.2, 8, BLACK, 46, 0.5); 887 b(0, -1.4, 0, 5.4, 0.6, 7.4, [58, 54, 62], 48, 0.5); 888 b(0, -2.1, 2, 3, 0.4, 2, [214, 210, 216], 50, 0.3); 889 } }, 890 { id: "dragonball", draw: function (b, t) { /* Dragon Ball */ 891 var g = 0.6 + 0.4 * Math.sin(t * 2); 892 b(0, 0, 0, 5, 5, 5, [246, 176 + 40 * g, 70], 46, 2.4); 893 var pts = [[-2, -2], [2, -2], [0, 0], [-2, 2], [2, 2]]; 894 for (var i = 0; i < 4; i++) { 895 b(pts[i][0], pts[i][1], -5.2, 0.8, 0.8, 0.5, [200, 58, 48], 50, 0.4); 896 } 897 } }, 898 { id: "blades", draw: function (b) { /* Attack on Titan */ 899 b(-2, 0, 0, 1.2, 9, 0.8, [206, 214, 226], 46, 0.3); 900 b(2.4, 1, 0, 1.2, 9, 0.8, [206, 214, 226], 46, 0.3); 901 b(-2, 8, 0, 1.8, 2.4, 1.6, [70, 66, 74], 48, 0.6); 902 b(2.4, 9, 0, 1.8, 2.4, 1.6, [70, 66, 74], 48, 0.6); 903 } }, 904 { id: "ramen", draw: function (b) { /* Naruto */ 905 b(0, 0, 0, 6.5, 3.4, 6, [232, 234, 238], 46, 2); 906 b(0, -3.4, 0, 5.6, 0.8, 5, [226, 190, 120], 48, 1.8); 907 b(-1.6, -4.4, -1.6, 1.6, 0.8, 1.6, WHITE, 50, 0.8); 908 b(1.6, -4.4, 1, 1.6, 0.8, 1.6, [236, 140, 130], 50, 0.8); 909 b(3, -6, 3, 0.5, 3, 0.5, [196, 164, 110], 50, 0.3); 910 } },
911 { id: "soulgem", draw: function (b, t) { /* Madoka */ 912 var g = 0.55 + 0.45 * Math.sin(t * 2.6); 913 b(0, 0, 0, 3.4, 4.4, 3.4, [150 + 60 * g, 90, 200], 46, 1.6); 914 b(0, 4.4, 0, 4.4, 1.4, 4.4, GOLD, 48, 1.2); 915 b(0, -4.6, 0, 1.6, 1.6, 1.6, [220, 190, 250], 50, 0.7); 916 } }, 917 { id: "umbrella", draw: function (b) { /* Ghibli */ 918 b(0, -4, 0, 9, 1.4, 8, [72, 96, 120], 46, 3.4); 919 b(0, -6.4, 0, 4.4, 1.6, 4, [72, 96, 120], 48, 2); 920 b(0, 2, 0, 0.9, 7, 0.9, WOOD, 46, 0.4); 921 b(-2, 8, 0, 2.4, 0.9, 0.9, WOOD, 48, 0.4); 922 } }, 923 { id: "ring", draw: function (b, t) { /* LOTR */ 924 var g = 0.5 + 0.5 * Math.sin(t * 1.8); 925 var n = 12; 926 for (var i = 0; i < n; i++) { 927 var a2 = (i / n) * Math.PI * 2; 928 b(Math.cos(a2) * 5, Math.sin(a2) * 5, 0, 1.2, 1.2, 1.4, 929 [226 + 20 * g, 188 + 20 * g, 92], 46, 0.5); 930 } 931 } }, 932 { id: "snitch", draw: function (b, t) { /* Harry Potter */ 933 var w = Math.sin(t * 9) * 2.6; 934 b(0, 0, 0, 3.4, 3.4, 3.4, GOLD, 46, 1.6); 935 b(-5, w, 0, 3.4, 0.7, 2.4, [244, 240, 220], 48, 1); 936 b(5, -w, 0, 3.4, 0.7, 2.4, [244, 240, 220], 48, 1); 937 } }, 938 { id: "saber", draw: function (b, t) { /* Star Wars */ 939 b(0, 0, 0, 1.8, 7, 1.8, IRON, 46, 0.5); 940 b(0, -3, 0, 2.1, 1, 2.1, DIRON, 48, 0.5); 941 b(0, 1, 0, 2.1, 1, 2.1, DIRON, 48, 0.5); 942 b(0, 5.4, 0, 1.4, 1.4, 1.4, RED, 50, 0.6); 943 /* a hand's width of blade, flickering */ 944 var l = 3 + Math.sin(t * 6) * 0.5; 945 b(0, -8 - l, 0, 1, l, 1, [255, 120, 100], 52, 0.4); 946 } }, 947 { id: "excalibur", draw: function (b) { /* Arthurian */ 948 b(0, 4, 0, 8, 4, 7, [128, 124, 120], 44, 2.4); 949 b(0, -10, 0, 1.6, 10, 0.8, [220, 226, 236], 46, 0.4);
950 b(0, -1, 0, 5.4, 1.2, 1.2, DGOLD, 48, 0.5); 951 b(0, 2.4, 0, 1.6, 2.4, 1.6, DGOLD, 48, 0.6); 952 } }, 953 { id: "wizhat", draw: function (b) { /* the old wizard */ 954 b(0, 0, 0, 8, 1.2, 7, [66, 78, 140], 46, 3); 955 b(0, -3.5, 0, 4.4, 3, 4, [66, 78, 140], 48, 1.6); 956 b(1, -7.5, 0, 2.4, 3, 2.2, [58, 68, 126], 48, 1); 957 b(0, -1.6, -6.4, 1.2, 1.2, 0.6, GOLD, 50, 0.5); 958 } }, 959 { id: "bottle", draw: function (b) { /* a message in it */ 960 b(0, 0, 0, 3, 5.5, 3, [176, 208, 190], 46, 1.4); 961 b(0, -6, 0, 1.4, 2, 1.4, [176, 208, 190], 48, 0.6); 962 b(0, -8, 0, 1.6, 1, 1.6, WOOD, 50, 0.6); 963 b(0, 1, 0, 2, 3, 1.4, [236, 226, 196], 50, 0.6); 964 } }, 965 { id: "sonicring", draw: function (b, t) { /* Sonic */ 966 var n = 14; 967 for (var i = 0; i < n; i++) { 968 var a2 = (i / n) * Math.PI * 2; 969 b(Math.cos(a2) * 5.5, Math.sin(a2) * 5.5, 0, 1.3, 1.3, 1.3, 970 [244, 206, 72], 46, 0.5); 971 } 972 } }, 973 { id: "pacman", draw: function (b, t) { /* Pac-Man */ 974 var open = 0.5 + 0.5 * Math.abs(Math.sin(t * 3)); 975 var n = 16; 976 for (var i = 0; i < n; i++) { 977 var a2 = (i / n) * Math.PI * 2; 978 /* leave the mouth out, opening and closing */ 979 if (Math.abs(((a2 + Math.PI) % (Math.PI * 2)) - Math.PI) > Math.PI - open * 0.8) continue; 980 for (var rr = 2; rr <= 6; rr += 2) { 981 b(Math.cos(a2) * rr, Math.sin(a2) * rr, 0, 1.4, 1.4, 1.6, 982 [248, 220, 68], 46, 0.5); 983 } 984 } 985 b(0, 0, 0, 1.6, 1.6, 1.6, [248, 220, 68], 46, 0.5); 986 } }, 987 { id: "invader", draw: function (b) { /* Space Invaders */ 988 var rows = ["0010100", "0111110", "1101011", "1111111", "0101010", "1000001"]; 989 for (var r = 0; r < rows.length; r++) { 990 for (var c = 0; c < rows[r].length; c++) { 991 if (rows[r][c] !== "1") continue; 992 b((c - 3) * 1.9, (r - 2.5) * 1.9, 0, 0.95, 0.95, 1.1, [126, 226, 118], 46, 0.2); 993 } 994 } 995 } }, 996 { id: "tetris", draw: function (b) { /* Tetris */ 997 var cells = [[0, 0], [1, 0], [2, 0], [1, 1]]; /* the T piece */ 998 for (var i = 0; i < cells.length; i++) { 999 b((cells[i][0] - 1) * 4, (cells[i][1] - 0.5) * 4, 0, 2, 2, 2, 1000 [156, 96, 214], 46, 0.5); 1001 b((cells[i][0] - 1) * 4, (cells[i][1] - 0.5) * 4, -2.2, 1.2, 1.2, 0.5, 1002 [196, 150, 240], 48, 0.4); 1003 } 1004 } }, 1005 { id: "slime", draw: function (b, t) { /* Dragon Quest */ 1006 var w = 1 + Math.sin(t * 2.4) * 0.06; 1007 b(0, 2, 0, 6.5 * w, 3.4, 6 * w, [78, 128, 226], 46, 3); 1008 b(0, -2, 0, 4.4 * w, 2.6, 4 * w, [78, 128, 226], 46, 2.4); 1009 b(0, -5.4, 0, 1.4, 1.6, 1.4, [78, 128, 226], 46, 0.6); 1010 b(-1.8, 0, -4.4, 0.9, 1.1, 0.6, [250, 250, 255], 50, 0.4); 1011 b(1.8, 0, -4.4, 0.9, 1.1, 0.6, [250, 250, 255], 50, 0.4); 1012 b(0, 3, -4, 2.4, 0.7, 0.6, [30, 40, 90], 50, 0.3); 1013 } }, 1014 { id: "materia", draw: function (b, t) { /* Final Fantasy */ 1015 var g = 0.55 + 0.45 * Math.sin(t * 2.2); 1016 b(0, 0, 0, 4.4, 4.4, 4.4, [90 + 40 * g, 220, 140], 46, 2.2); 1017 b(0, -1.4, -4, 1.4, 1.4, 0.8, [230, 255, 240], 50, 0.7); 1018 } }, 1019 { id: "puzzle", draw: function (b) { /* Yu-Gi-Oh */ 1020 for (var r = 0; r < 5; r++) { 1021 var w = 5.5 - r * 1.1; 1022 b(0, -4 + r * 2.2, 0, w, 1.1, w * 0.5, [230, 190, 70], 46, 0.5); 1023 } 1024 b(0, -6.4, 0, 1.2, 1.6, 1.2, [230, 190, 70], 48, 0.5); 1025 b(0, 0, -2.6, 1.8, 1.8, 0.6, [70, 60, 40], 50, 0.8); 1026 } }, 1027 { id: "hammer", draw: function (b) { /* Mjolnir */ 1028 b(0, -2, 0, 5, 4, 4.4, [150, 158, 170], 46, 1); 1029 b(-5.4, -2, 0, 0.9, 4.4, 4.8, [110, 118, 130], 48, 0.6); 1030 b(5.4, -2, 0, 0.9, 4.4, 4.8, [110, 118, 130], 48, 0.6); 1031 b(0, 5, 0, 1.2, 7, 1.2, [122, 84, 52], 46, 0.5); 1032 b(0, 11.6, 0, 1, 1.6, 1, [206, 200, 190], 48, 0.5); 1033 } } 1034 ]; 1035 1036 /* The margins. Content sits down the middle of every band, so the far left 1037 and right of each world are the only parts never covered, and they were
1038 the emptiest. Each world gets a pair of standing pieces out there, keyed 1039 to what that world is. */ 1040 function margins(m, t, P, kind) { 1041 var i; 1042 var L = -352, R = 352; /* out past the platform edges */ 1043 1044 function post(x, h, col, capCol) { 1045 /* a weathered marker post, leaning a little */ 1046 var lean = Math.sin(t * 0.4 + x) * 1.6; 1047 for (i = 0; i < 5; i++) { 1048 var f = i / 5; 1049 E.box(m, x + lean * f, -f * h - h / 10, 24, 3.2 - f * 0.6, h / 10, 3.2 - f * 0.6, 1050 i % 2 ? col : capCol, null, 6, 1); 1051 } 1052 E.box(m, x + lean, -h - 4, 24, 7, 4, 2, capCol, null, 8, 1.2); 1053 } 1054 function boulder(x, r, col, top) { 1055 dome(m, x, 34, r, r * 0.8, col, top); 1056 dome(m, x + r * 1.3, 44, r * 0.55, r * 0.45, col, top); 1057 } 1058 1059 if (kind === "winter") { 1060 /* drifts banked up, and a fence line going out of frame */ 1061 dome(m, L, 30, 40, 22, [304, 312, 324], [330, 336, 344]); 1062 dome(m, R, 34, 34, 19, [298, 306, 320], [326, 332, 342]); 1063 for (i = 0; i < 3; i++) { 1064 post(L + 40 + i * 26, 22, [116, 92, 70], [140, 112, 86]); 1065 post(R - 40 - i * 26, 22, [116, 92, 70], [140, 112, 86]); 1066 } 1067 } else if (kind === "jungle") { 1068 boulder(L, 30, [58, 92, 60], [86, 128, 84]); 1069 boulder(R - 40, 26, [58, 92, 60], [86, 128, 84]); 1070 for (i = 0; i < 4; i++) { 1071 E.box(m, L + 30 + i * 16, -8 - (i % 2) * 6, 40, 3, 8, 3, [64, 116, 58], null, 8, 1); 1072 E.box(m, R - 30 - i * 16, -8 - (i % 2) * 6, 40, 3, 8, 3, [64, 116, 58], null, 8, 1); 1073 } 1074 } else if (kind === "autumn" || kind === "craft" || kind === "dusk" || kind === "night") { 1075 /* dry stone, and a pair of markers down the verge */ 1076 boulder(L, 26, [128, 118, 104], [156, 146, 130]); 1077 boulder(R - 30, 22, [128, 118, 104], [156, 146, 130]); 1078 post(L + 54, 26, [116, 92, 70], [140, 112, 86]); 1079 post(R - 54, 26, [116, 92, 70], [140, 112, 86]); 1080 } else { 1081 /* the green bands: bushes and a marker either side */ 1082 var g1 = P.far ? P.far[0] : [110, 172, 104]; 1083 var g2 = P.far ? P.far[1] : [140, 196, 132]; 1084 dome(m, L, 34, 30, 22, g1, g2); 1085 dome(m, L + 44, 46, 18, 13, g1, g2); 1086 dome(m, R - 10, 36, 26, 19, g1, g2); 1087 post(L + 78, 24, [122, 96, 70], [146, 116, 88]); 1088 post(R - 60, 24, [122, 96, 70], [146, 116, 88]); 1089 } 1090 /* a low bank running out to the edge of frame on both sides, so the 1091 ground does not simply stop where the platform does */ 1092 E.box(m, L - 30, 6, 30, 90, 7, 60, P.soilSide, null, -320, 0); 1093 E.box(m, L - 30, -0.4, 30, 88, 1.6, 58, P.turf, null, -318, 0); 1094 E.box(m, R + 30, 6, 30, 90, 7, 60, P.soilSide, null, -320, 0); 1095 E.box(m, R + 30, -0.4, 30, 88, 1.6, 58, P.turf, null, -318, 0); 1096 } 1097 1098 /* A squirrel working an acorn. It always has one in its paws and is always 1099 at it, and the head hangs off the same rocking value as the body rather 1100 than one of its own, so the two can never come apart. */ 1101 function squirrel(m, t, x, z) { 1102 var FUR = [148, 96, 58], FUR2 = [122, 76, 44], BELLY = [214, 186, 156]; 1103 var NUT = [140, 100, 58], CAP = [96, 66, 38], EYE = [30, 26, 28]; 1104 var rock = Math.sin(t * 1.1) * 0.6; 1105 var nib = Math.sin(t * 13) * 0.5; 1106 var eat = ((t * 0.14) % 1); 1107 var i; 1108 1109 var eaten = Math.floor((t / 7.2) % 5); 1110 for (i = 0; i < 5; i++) { 1111 if (i < eaten) continue; 1112 var ax = x + 17 + (i % 3) * 5, az = z + 3 + Math.floor(i / 3) * 5; 1113 var ay = -2.4 - (i % 2) * 2; 1114 E.box(m, ax, ay, az, 2.2, 2, 1.8, NUT, null, 14, 0.9); 1115 E.box(m, ax, ay - 2.4, az, 2, 0.9, 1.6, CAP, null, 15, 0.6); 1116 } 1117 1118 function b(dx, dy, dz, hx, hy, hz, col, bias, r) { 1119 E.box(m, x + dx + rock * (dy / 22), -dy, z + dz, hx, hy, hz, col, null, 1120 bias === undefined ? 22 : bias, r === undefined ? 0.8 : r); 1121 } 1122 b(-2.6, 3, 2, 2.4, 3, 3.4, FUR2, 20, 1.2); 1123 b(2.6, 3, 2, 2.4, 3, 3.4, FUR2, 20, 1.2); 1124 b(0, 9, 0, 3.6, 5, 3.2, FUR, 24, 1.4); 1125 b(0, 8.6, -3, 2.6, 3.4, 0.9, BELLY, 30, 1.2); 1126 b(-1.8, 12.4 + nib * 0.4, -4, 1.1, 1.4, 1.4, FUR2, 32, 0.5); 1127 b(1.8, 12.4 + nib * 0.4, -4, 1.1, 1.4, 1.4, FUR2, 32, 0.5); 1128 var nsc = 1 - eat * 0.7; 1129 b(0, 13 + nib * 0.4, -5.2, 1.7 * nsc, 1.6 * nsc, 1.4 * nsc, NUT, 34, 0.6); 1130 b(0, 14.6 + nib * 0.4, -5.2, 1.5 * nsc, 0.7 * nsc, 1.2 * nsc, CAP, 35, 0.4); 1131 b(0, 15, -1.4, 1.8, 2, 1.8, FUR, 34, 0.7); /* the neck */ 1132 var hy = 17.6 + nib; 1133 b(0, hy, -3, 2.6, 2.4, 2.6, FUR, 36, 1.2); 1134 b(0, hy - 0.6, -5.4, 1.4, 1.2, 1.2, BELLY, 38, 0.6); 1135 b(-1.9, hy + 2.2, -2.6, 1, 1.4, 0.8, FUR2, 38, 0.5); 1136 b(1.9, hy + 2.2, -2.6, 1, 1.4, 0.8, FUR2, 38, 0.5); 1137 b(-1.4, hy + 0.4, -5, 0.7, 0.8, 0.5, EYE, 40, 0.3); 1138 b(1.4, hy + 0.4, -5, 0.7, 0.8, 0.5, EYE, 40, 0.3); 1139 for (i = 0; i < 7; i++) { 1140 var f = i / 6; 1141 var flick = Math.sin(t * 3 + f * 2.2) * (1.4 + f * 2.6); 1142 b(flick * 0.4, 5 + f * 13, 4 + Math.sin(f * 2.1) * 5, 1143 2 + f * 1.4, 1.8, 1.6 + f * 1.2, i % 2 ? FUR : FUR2, 18, 1.2); 1144 } 1145 } 1146 1147 /* ---------------------------------------------------------------- props */ 1148 1149 function cabin(m, x, z, P, t) { 1150 /* walls */ 1151 E.box(m, x, -26, z, 34, 26, 26, P.wall, P.wallCap, 0, 2); 1152 /* log courses, so the walls read as timber */ 1153 for (var i = 0; i < 5; i++) { 1154 E.box(m, x, -8 - i * 10, z - 26.6, 34, 1.1, 0.6, P.wallLine, null, 26, 0.4); 1155 } 1156 /* gabled roof, stepped in courses the way a voxel roof would be laid */ 1157 var courses = 6; 1158 for (var s2 = 0; s2 < courses; s2++) { 1159 var f2 = s2 / courses; 1160 E.box(m, x, -54 - s2 * 6, z, 40 * (1 - f2 * 0.82) + 2, 3.2, 34 - s2 * 0.6, 1161 s2 % 2 ? P.roof : P.roofDark, null, 20 + s2, 1); 1162 } 1163 /* eaves overhanging the walls */ 1164 E.box(m, x, -52, z, 42, 2.2, 35, P.roofDark, null, 19, 1); 1165 /* the window, lit from inside, with a warm pool on the snow */ 1166 var glow = 0.72 + Math.sin(t * 2.2) * 0.14 + Math.sin(t * 5.1) * 0.05; 1167 E.box(m, x - 11, -30, z - 28.4, 8.6, 7.6, 0.5, P.frame, null, 38, 1); 1168 E.box(m, x - 11, -30, z - 29, 7.4, 6.4, 0.5, 1169 [255, 214 + 34 * glow, 132 + 60 * glow], null, 42, 0.8); 1170 /* glazing bars */ 1171 E.box(m, x - 11, -30, z - 29.4, 0.5, 6.4, 0.3, P.frame, null, 46, 0.2); 1172 E.box(m, x - 11, -30, z - 29.4, 7.4, 0.5, 0.3, P.frame, null, 46, 0.2); 1173 /* the light it spills onto the snow outside */ 1174 E.extrude(m, E.disc(24, 12), -0.7, 0.1, [255, 232, 178], [255, 236, 190], 1175 function (px, py, pz) { return [x - 11 + px, pz, z - 40 + py]; }, { bias: -370 }); 1176 /* door */ 1177 E.box(m, x + 13, -16, z - 27, 7, 16, 1, P.door, null, 40, 1.4); 1178 E.box(m, x + 16, -16, z - 28.2, 1, 1, 0.5, P.frame, null, 44, 0.4); 1179 /* chimney and smoke, rising and spreading as it goes */ 1180 E.box(m, x + 22, -74, z + 6, 5, 12, 5, P.stone, null, 30, 1); 1181 for (var k = 0; k < 8; k++) { 1182 var f = ((t * 0.22 + k * 0.125) % 1); 1183 var r = 1.6 + f * 4; 1184 /* it pales toward the sky as it thins out */ 1185 var sm = 300 + f * 20; 1186 E.gem(m, r, 1, x + 22 + Math.sin(f * 5 + k) * 10, -86 - f * 52, z + 6 + Math.
1186cos(f * 4) * 5, 1187 [sm, sm + 2, sm + 6], null); 1188 } 1189 } 1190 1191 function campfire(m, x, z, t) { 1192 var i; 1193 /* a ring of stones */ 1194 for (i = 0; i < 8; i++) { 1195 var a = (i / 8) * Math.PI * 2; 1196 E.box(m, x + Math.cos(a) * 15, -3, z + Math.sin(a) * 15, 3.4, 3, 3.4, 1197 [150, 148, 152], null, 20, 1.2); 1198 } 1199 /* crossed logs */ 1200 E.box(m, x, -6, z, 13, 2.6, 2.6, [122, 86, 62], null, 22, 1); 1201 E.box(m, x, -6, z, 2.6, 2.6, 13, [98, 68, 48], null, 22, 1); 1202 /* flame: three tapering tiers that flicker out of step */ 1203 for (i = 0; i < 4; i++) { 1204 var f = i / 4; 1205 var fl = 1 + Math.sin(t * 9 + i * 1.7) * 0.2 + Math.sin(t * 15 + i) * 0.1 1206 + ACT.pulse * 0.9; 1207 var col = i === 0 ? [255, 216, 120] : i === 1 ? [255, 168, 58] : i === 2 ? [242, 110, 40] : [206, 66, 30]; 1208 E.gem(m, (9 - f * 6) * fl, 1, x + Math.sin(t * 7 + i) * 1.4, -10 - i * 7 * fl, z, 1209 col, null); 1210 } 1211 /* embers lifting off it */ 1212 for (i = 0; i < 8; i++) { 1213 var e = ((t * 0.8 + i * 0.125) % 1); 1214 E.box(m, x + Math.sin(e * 9 + i) * 12, -22 - e * 52, z + Math.cos(e * 6 + i) * 8, 1215 0.9, 0.9, 0.9, [255, 180, 90], null, 60, 0.4); 1216 } 1217 /* the light it throws on the ground */ 1218 var lit = 0.5 + Math.sin(t * 2.6) * 0.1; 1219 E.extrude(m, E.disc(40, 14), -0.6, 0.2, [255, 200, 130], [255, 206, 140], 1220 function (px, py, pz) { return [x + px, pz, z + py]; }, { bias: -380 }); 1221 return lit; 1222 } 1223 1224 function lantern(m, x, z, t, i, lit) { 1225 if (lit === undefined) lit = 1; 1226 var glow = (0.75 + Math.sin(t * 2 + i) * 0.2) * lit; 1227 E.box(m, x, -6, z, 2, 6, 2, [90, 76, 68], null, 20, 0.6); 1228 /* every channel scales with the glow, so an unlit lantern goes dark 1229 rather than going red */ 1230 E.box(m, x, -18, z, 6, 7, 6, 1231 [44 + 211 * glow, 38 + 176 * glow, 34 + 116 * glow], null, 24, 1.6); 1232 E.box(m, x, -26, z, 7.5, 1.6, 7.5, [70, 58, 52], null, 26, 0.8); 1233 } 1234 1235 function bench(m, x, z, col, colB) { 1236 E.box(m, x, -9, z, 22, 1.8, 7, col, null, 20, 0.8); 1237 E.box(m, x, -16, z + 6.4, 22, 6, 1.6, col, null, 22, 0.8); 1238 E.box(m, x - 17, -4, z, 1.6, 5, 6, colB, null, 20, 0.5); 1239 E.box(m, x + 17, -4, z, 1.6, 5, 6, colB, null, 20, 0.5); 1240 } 1241 1242 function grassTuft(m, x, z, t, cols, h, seed) { 1243 for (var i = 0; i < 4; i++) { 1244 var sway = Math.sin(t * 1.6 + seed + i) * 2; 1245 var hh = h * (0.55 + rnd(seed + i, 3) * 0.6); 1246 E.box(m, x + (i - 1.5) * 2.4 + sway * 0.4, -hh / 2, z + (rnd(seed + i, 4) - 0.5) * 5, 1247 1, hh / 2, 1, cols[i % cols.length], null, 20, 0.3); 1248 } 1249 } 1250 1251 /* ---------------------------------------------------------------- worlds */ 1252 var PAL = { 1253 person: { 1254 skin: [246, 214, 186], hair: [58, 42, 50], eye: [40, 30, 38], 1255 coat: [92, 122, 168], coatIn: [130, 158, 198], trouser: [64, 72, 96], 1256 boot: [58, 48, 56], mug: [300, 300, 308], steam: [330, 332, 338], scarf: null, 1257 tool: [138, 100, 70] 1258 } 1259 }; 1260 function personPal(over) { 1261 var o = {}, k; 1262 for (k in PAL.person) if (PAL.person.hasOwnProperty(k)) o[k] = PAL.person[k]; 1263 for (k in over) if (over.hasOwnProperty(k)) o[k] = over[k]; 1264 return o; 1265 } 1266 1267 var WORLDS = { 1268 /* ---- cherry blossom: he stops under the trees and watches them fall ---- */ 1269 sakura: function (m, t, p, seed) { 1270 /* Asleep under the trees most of the time, up for a stretch, back down. */ 1271 var qk = (t % 17) / 17; 1272 ACTOR.x = -14; ACTOR.z = 46; 1273 ACTOR.act = qk < 0.62 ? "sleep" : qk < 0.86 ? "stretch" : "sleep"; 1274 var P = { soil: [150, 116, 96], soilSide: [122, 92, 74], turf: [150, 196, 132] }; 1275 P.far = [[255, 176, 203], [255, 200, 220]]; P.mote = [255, 196, 216]; 1276 P.cloudLit=[255,245,250];P.cloudShd=[240,214,228];P.dropCols=[[255,176,203],[255,206,224],[246,150,184]];P.birdCol=[92,72,86]; 1277 skyworld(m, t, P, { clouds: 0, birds: 8, drop: "petal", dropN: 34, dropSpeed: 0.18 }); 1278 surroundings(m, t, P, "leaf"); 1279 ground(m, P, -300, 300, -70, 70); 1280 margins(m, t, P, "spring"); 1281 var pink = [[255, 176, 203], [255, 200, 220], [246, 150, 184]]; 1282 blobTree(m, -190, -30, 74, 34, pink, [[122, 86, 62], [96, 66, 46]], t, 1); 1283 blobTree(m, -70, 20, 58, 26, pink, [[122, 86, 62], [96, 66, 46]], t, 4); 1284 blobTree(m, 120, -36, 84, 38, pink, [[122, 86, 62], [96, 66, 46]], t, 7); 1285 blobTree(m, 240, 10, 52, 24, pink, [[122, 86, 62], [96, 66, 46]], t, 11); 1286 bench(m, 34, 44, [154, 112, 82], [120, 84, 60]); 1287 lantern(m, -128, 40, t, 0); 1288 lantern(m, 186, 36, t, 2); 1289 for (var i = 0; i < 7; i++) { 1290 grassTuft(m, -260 + i * 84, 52, t, [[126, 186, 106], [104, 166, 88]], 8, i); 1291 } 1292 fall(m, t, pink, 44, 3.4, 22, -300, 300, 1.4); 1293 return P; 1294 }, 1295 1296 /* ---- high summer: long light, tall grass, fireflies coming out ---- */ 1297 summer: function (m, t, p, seed) { 1298 /* Working the meadow flower by flower: walk across, kneel to it, shoot, 1299 move on to the next. */ 1300 var SPOTS = [-40, 96, 214]; 1301 var CYs = 16, qs = (t % CYs) / CYs; 1302 var legS = Math.min(SPOTS.length - 1, Math.floor(qs * SPOTS.length)); 1303 var wS = qs * SPOTS.length - legS; 1304 var prevS = SPOTS[(legS + SPOTS.length - 1) % SPOTS.length]; 1305 if (wS < 0.35) { 1306 ACTOR.act = "walk";
1307 ACTOR.x = prevS + (SPOTS[legS] - prevS) * (wS / 0.35); 1308 ACTOR.z = 42; 1309 ACTOR.face = SPOTS[legS] >= prevS ? -Math.PI / 2 : Math.PI / 2; 1310 } else { 1311 ACTOR.act = "photo"; ACTOR.x = SPOTS[legS]; ACTOR.z = 42; 1312 } 1313 var P = { soil: [140, 118, 82], soilSide: [112, 94, 64], turf: [122, 186, 106] }; 1314 P.far = [[92, 168, 96], [122, 194, 118]]; P.mote = [255, 246, 180]; 1315 P.cloudLit=[255,255,255];P.cloudShd=[214,228,240];P.birdCol=[66,74,68]; 1316 skyworld(m, t, P, { clouds: 9, birds: 9 }); 1317 surroundings(m, t, P, "leaf"); 1318 ground(m, P, -300, 300, -70, 70); 1319 margins(m, t, P, "spring"); 1320 var green = [[92, 168, 96], [122, 194, 118], [70, 142, 78]]; 1321 blobTree(m, -170, -26, 92, 42, green, [[110, 80, 56], [86, 60, 42]], t, 2); 1322 blobTree(m, 150, -34, 78, 36, green, [[110, 80, 56], [86, 60, 42]], t, 6); 1323 blobTree(m, 258, 14, 54, 24, green, [[110, 80, 56], [86, 60, 42]], t, 9); 1324 for (var i = 0; i < 14; i++) { 1325 grassTuft(m, -280 + i * 42, 30 + (i % 3) * 16, t, 1326 [[142, 202, 120], [110, 178, 96], [90, 156, 80]], 13, i); 1327 } 1328 /* bushes and wildflowers through the middle of the meadow */ 1329 dome(m, -40, 30, 20, 16, [78, 148, 84], [110, 182, 112]); 1330 dome(m, 6, 44, 14, 11, [86, 158, 90], [118, 190, 118]); 1331 dome(m, 62, 26, 17, 13, [72, 140, 78], [104, 176, 106]); 1332 for (i = 0; i < 18; i++) { 1333 var fx = -270 + rnd(i, 24) * 540, fz = 14 + rnd(i, 25) * 46; 1334 var fh = 9 + rnd(i, 26) * 6; 1335 var fw = Math.sin(t * 1.5 + i) * 1.4; 1336 E.box(m, fx + fw, -fh / 2, fz, 0.7, fh / 2, 0.7, [96, 162, 88], null, 20, 0.3); 1337 E.box(m, fx + fw * 1.6, -fh - 1.4, fz, 2, 2, 2, 1338 [[255, 232, 120], [255, 250, 250], [255, 176, 200]][i % 3], null, 26, 0.9); 1339 } 1340 /* fireflies, blinking out of phase */ 1341 for (i = 0; i < 16; i++) { 1342 var f = rnd(i, 12); 1343 var on = 0.35 + 0.65 * Math.abs(Math.sin(t * 1.6 + f * 8)); 1344 E.box(m, -260 + f * 520 + Math.sin(t * 0.7 + i) * 22, 1345 -30 - rnd(i, 13) * 50 + Math.sin(t * 1.1 + i * 2) * 9, 1346 -60 + rnd(i, 14) * 120, 1347 1.5 * on, 1.5 * on, 1.5 * on, [255, 246, 150], null, 90, 0.5); 1348 } 1349 return P; 1350 }, 1351 1352 /* ---- autumn: the maples let go and he walks through the drift ---- */ 1353 autumn: function (m, t, p, seed) { 1354 var P = { soil: [138, 104, 74], soilSide: [110, 82, 58], turf: [186, 150, 100] }; 1355 P.far = [[214, 96, 48], [232, 150, 62]]; P.mote = [240, 190, 130]; 1356 P.cloudLit=[255,246,232];P.cloudShd=[228,208,186];P.dropCols=[[214,96,48],[232,150,62],[188,68,44]];P.birdCol=[86,64,50]; 1357 skyworld(m, t, P, { clouds: 8, birds: 7, drop: "leaf", dropN: 40, dropSpeed: 0.2 }); 1358 surroundings(m, t, P, "leaf"); 1359 ground(m, P, -300, 300, -70, 70); 1360 margins(m, t, P, "autumn"); 1361 var fire = [[214, 96, 48], [232, 150, 62], [188, 68, 44]]; 1362 blobTree(m, -200, -30, 80, 36, fire, [[104, 74, 52], [80, 56, 40]], t, 3); 1363 blobTree(m, -50, 24, 56, 25, [[232, 168, 72], [214, 128, 54]], [[104, 74, 52], [80, 56, 40]], t, 5); 1364 blobTree(m, 140, -34, 88, 40, fire, [[104, 74, 52], [80, 56, 40]], t, 8); 1365 /* drifts of fallen leaves banked against the ground */ 1366 for (var i = 0; i < 26; i++) { 1367 var lx = -280 + rnd(i, 15) * 560, lz = 8 + rnd(i, 16) * 54; 1368 E.box(m, lx, -1.6, lz, 3.4, 0.8, 2.6, 1369 fire[i % fire.length], null, 10, 1); 1370 } 1371 squirrel(m, t, -6, 56); 1372 E.gem(m, 16, 1, 210, -10, 34, [206, 118, 54], null); 1373 E.gem(m, 12, 1, 230, -8, 40, [226, 152, 66], null); 1374 fall(m, t, fire, 40, 4, 26, -300, 300, 2.1); 1375 return P; 1376 }, 1377 1378 /* ---- winter: the cabin, the fire, and a mug of hot chocolate ---- */ 1379 winter: function (m, t, p, seed) { 1380 /* A turn at the fire: lay a log on, warm through, then drink. */ 1381 var qw = (t % 13) / 13; 1382 ACTOR.x = 226; ACTOR.z = 26; 1383 ACTOR.act = qw < 0.22 ? "feedfire" : qw < 0.58 ? "warm" : "sip"; 1384 var P = { 1385 soil: [236, 244, 255], soilSide: [198, 212, 234], turf: [300, 308, 320], 1386 wall: [126, 92, 66], wallCap: [146, 108, 78], wallLine: [98, 70, 50], 1387 roof: [268, 280, 296], roofDark: [214, 228, 248], 1388 frame: [86, 62, 46], door: [96, 68, 48], stone: [140, 138, 144] 1389 }; 1390 P.mote = [300, 306, 318];
1391 P.cloudLit=[248,252,255];P.cloudShd=[206,218,236];P.birdCol=[70,80,98]; 1392 skyworld(m, t, P, { clouds: 12, birds: 2, drop: "snow", dropN: 74, dropSpeed: 0.16 }); 1393 surroundings(m, t, P, "pine"); 1394 ground(m, P, -300, 300, -70, 70); 1395 margins(m, t, P, "winter"); 1396 cabin(m, -150, -18, P, t); 1397 pineTree(m, -258, -34, 96, [46, 112, 74], [32, 86, 56], true); 1398 pineTree(m, 236, -30, 88, [46, 112, 74], [32, 86, 56], true); 1399 pineTree(m, 300, 6, 70, [46, 112, 74], [32, 86, 56], true); 1400 campfire(m, 182, 20, t); 1401 /* the log he sits on */ 1402 E.box(m, 226, -7, 26, 22, 6, 6, [122, 86, 62], null, 18, 2.4); 1403 /* drifts banked against the ground */ 1404 dome(m, -46, 46, 26, 13, [304, 312, 324], [330, 336, 344]); 1405 dome(m, 272, 44, 20, 10, [298, 306, 320], [326, 332, 342]); 1406 dome(m, -6, 52, 14, 7, [304, 312, 324], [330, 336, 344]); 1407 fall(m, t, [[330, 334, 340], [310, 318, 332]], 60, 2.4, 16, -300, 300, 0.8); 1408 return P; 1409 }, 1410 1411 /* ---- rainforest: rain coming through the canopy ---- */ 1412 jungle: function (m, t, p, seed) { 1413 /* The orchard cycle. He sets a sapling, it runs up through its stages, 1414 comes into fruit, drops one, and he eats it. Then it starts again. */ 1415 var CY = 13; /* seconds for the whole thing */ 1416 var q = (t % CY) / CY; 1417 var TX = 44, TZ = 14; /* clear of the standing trees */ 1418 var grow, apples = 0, dropF = -1; 1419 if (q < 0.1) { grow = 0.04; ACTOR.act = "plant"; ACTOR.x = TX - 40; ACTOR.z = 34; } 1420 else if (q < 0.58) { 1421 grow = Math.pow((q - 0.1) / 0.48, 0.72); 1422 ACTOR.act = "watch"; ACTOR.x = 248; ACTOR.z = 38; 1423 } else if (q < 0.74) { 1424 grow = 1; apples = (q - 0.58) / 0.16; 1425 ACTOR.act = "watch"; ACTOR.x = 248; ACTOR.z = 38; 1426 } else if (q < 0.82) { 1427 grow = 1; apples = 1; dropF = (q - 0.74) / 0.08; 1428 ACTOR.act = "watch"; ACTOR.x = TX + 34; ACTOR.z = 40; 1429 } else { 1430 grow = 1; apples = 1 - (q - 0.82) / 0.18 * 0.3; 1431 ACTOR.act = "eat"; ACTOR.x = TX + 34; ACTOR.z = 40; 1432 } 1433 var P = { soil: [96, 74, 52], soilSide: [74, 56, 40], turf: [78, 138, 74] }; 1434 P.far = [[62, 138, 68], [92, 172, 92]]; P.mote = [196, 226, 200]; 1435 P.cloudLit=[228,238,226];P.cloudShd=[186,204,188];P.dropCol=[196,226,240];P.birdCol=[60,78,58]; 1436 skyworld(m, t, P, { clouds: 12, birds: 4, drop: "rain", dropN: 68, dropSpeed: 0.9 }); 1437 surroundings(m, t, P, "leaf"); 1438 ground(m, P, -300, 300, -70, 70); 1439 margins(m, t, P, "jungle"); 1440 var leaf = [[62, 138, 68], [92, 172, 92], [44, 108, 54]]; 1441 blobTree(m, -210, -34, 108, 44, leaf, [[92, 68, 48], [70, 50, 36]], t, 2); 1442 blobTree(m, -60, -14, 84, 34, leaf, [[92, 68, 48], [70, 50, 36]], t, 5); 1443 blobTree(m, 160, -38, 118, 48, leaf, [[92, 68, 48], [70, 50, 36]], t, 9); 1444 for (var i = 0; i < 12; i++) { 1445 grassTuft(m, -280 + i * 50, 34 + (i % 2) * 14, t, 1446 [[96, 176, 92], [70, 142, 76]], 16, i + 3); 1447 } 1448 1449 /* the tree he set, at whatever stage it has reached */ 1450 (function () { 1451 var th = 12 + grow * 96; 1452 var cr = 6 + grow * 40; 1453 /* the mound it was planted in */ 1454 E.box(m, TX, -3, TZ, 9, 3, 8, [96, 74, 52], null, 12, 2); 1455 if (grow < 0.12) { 1456 /* a shoot with two seed leaves */ 1457 E.box(m, TX, -6, TZ, 1, 4, 1, [96, 158, 84], null, 20, 0.4); 1458 E.box(m, TX - 3, -9, TZ, 3, 0.9, 2, [122, 186, 96], null, 22, 0.8); 1459 E.box(m, TX + 3, -9, TZ, 3, 0.9, 2, [122, 186, 96], null, 22, 0.8); 1460 return; 1461 } 1462 trunk(m, TX, TZ, th, 2 + grow * 5, [110, 80, 56], [86, 60, 42], 0); 1463 /* boughs, once there is a trunk worth having them on */ 1464 if (grow > 0.45) { 1465 for (var bq = 0; bq < 3; bq++) { 1466 var ba = (bq / 3) * Math.PI * 2 + 0.6; 1467 E.box(m, TX + Math.cos(ba) * cr * 0.4, -th * 0.72, TZ + Math.sin(ba) * cr * 0.3, 1468 cr * 0.34, 1.6, 2, [98, 70, 48], null, 14, 0.8); 1469 } 1470 } 1471 canopy(m, TX, -th - cr * 0.45, TZ, cr, 1472 [[62, 138, 68], [92, 172, 92], [44, 108, 54]], t, 4); 1473 /* fruit, hung under the canopy once it is in season */ 1474 if (apples > 0.02) { 1475 for (var aq = 0; aq < 7; aq++) { 1476 if (aq / 7 > apples) break; 1477 var aa = (aq / 7) * Math.PI * 2 + 0.4;
1478 var ax2 = TX + Math.cos(aa) * cr * 0.62; 1479 var az2 = TZ + Math.sin(aa) * cr * 0.42; 1480 var ay2 = -th - cr * 0.1 + Math.sin(aq * 2.1) * 6; 1481 if (aq === 0 && dropF >= 0) { 1482 /* the one that comes down, and rolls a little when it lands */ 1483 var fy = ay2 + dropF * dropF * (Math.abs(ay2) - 4); 1484 var roll = dropF > 0.92 ? (dropF - 0.92) * 220 : 0; 1485 E.box(m, ax2 + roll, Math.min(-3, fy), az2, 2.4, 2.4, 2.4, 1486 [206, 62, 54], null, 40, 1.1); 1487 continue; 1488 } 1489 E.box(m, ax2, ay2, az2, 2.4, 2.4, 2.4, [206, 62, 54], null, 30, 1.1); 1490 E.box(m, ax2, ay2 - 3, az2, 0.5, 1.2, 0.5, [96, 128, 70], null, 30, 0.3); 1491 } 1492 } 1493 })(); 1494 /* undergrowth banked between the trunks */ 1495 dome(m, -140, 34, 24, 18, [52, 118, 60], [84, 158, 86]); 1496 dome(m, -14, 46, 18, 13, [58, 128, 66], [92, 168, 94]); 1497 dome(m, 92, 30, 26, 20, [48, 112, 56], [80, 152, 82]); 1498 dome(m, 240, 40, 20, 15, [54, 122, 62], [88, 162, 90]); 1499 /* vines let down out of the canopy, swinging a little */ 1500 var VN = [[-210, -34, 92], [-60, -14, 70], [160, -38, 100], [120, 10, 60]]; 1501 for (i = 0; i < VN.length; i++) { 1502 var vx = VN[i][0] + (i % 2 ? 26 : -26), vz = VN[i][1] + 12, vh = VN[i][2]; 1503 for (var k2 = 0; k2 < 9; k2++) { 1504 var kf = k2 / 9; 1505 var swing = Math.sin(t * 0.9 + i + kf * 2) * 5 * kf; 1506 E.box(m, vx + swing, -vh + kf * vh * 0.8, vz, 0.9, vh * 0.045, 0.9, 1507 [64, 116, 58], null, 20, 0.3); 1508 if (k2 % 2 === 0) { 1509 E.box(m, vx + swing + (k2 % 4 ? 3 : -3), -vh + kf * vh * 0.8, vz, 1510 3, 1, 2, [92, 172, 92], null, 22, 0.8); 1511 } 1512 } 1513 } 1514 /* rain, drawn as fast falling threads */ 1515 for (i = 0; i < 60; i++) { 1516 var f = rnd(i, 17); 1517 var ry = -((t * 150 + f * 220) % 220); 1518 var rx = -300 + rnd(i, 18) * 600; 1519 var rz = -70 + rnd(i, 19) * 140; 1520 m.line([rx, ry, rz], [rx - 3, ry + 16, rz], [206, 232, 240], 1.1, 0.4); 1521 } 1522 return P; 1523 }, 1524 1525 /* ---- a koi pond: he crouches at the edge and feeds them ---- */ 1526 pond: function (m, t, p, seed) { 1527 var P = { soil: [148, 118, 96], soilSide: [120, 94, 76], turf: [136, 190, 122] }; 1528 underwater(m, t, p); 1529 P.far = [[168, 206, 150], [200, 224, 180]]; P.mote = [214, 236, 244]; 1530 P.cloudLit=[255,250,252];P.cloudShd=[226,232,240];P.dropCols=[[255,186,210],[255,214,228]];P.birdCol=[86,80,90]; 1531 skyworld(m, t, P, { clouds: 3, birds: 8, drop: "petal", dropN: 18, dropSpeed: 0.15 }); 1532 surroundings(m, t, P, "leaf"); 1533 /* Banks at either end only. The middle of the plot is open water the 1534 whole way across, rather than a channel cut through a lawn. */ 1535 ground(m, P, -300, -190, -70, 70); 1536 ground(m, P, 190, 300, -70, 70); 1537 /* a lip along the back so the water reads as held rather than floating */ 1538 E.box(m, 0, 4, 62, 196, 5, 9, P.soilSide, null, -220, 0); 1539 E.box(m, 0, -1.6, 62, 196, 1.6, 9, P.turf, null, -218, 0); 1540 /* the surface, and the light moving across it */ 1541 E.box(m, 0, 6, 0, 194, 1.4, 66, [126, 190, 214], null, -210, 0); 1542 for (var wq = 0; wq < 26; wq++) { 1543 var wx2 = -186 + ((wq * 21 + t * 26) % 372); 1544 E.box(m, wx2, 4.6, -50 + (wq % 6) * 20, 8 + (wq % 3) * 5, 0.5, 1.4, 1545 [176, 224, 236], null, -206, 0.5); 1546 } 1547 /* stones set along both banks */ 1548 for (var i = 0; i < 12; i++) { 1549 var bx2 = i < 6 ? -190 + 8 : 190 - 8; 1550 E.box(m, bx2, -3, -46 + (i % 6) * 19, 7, 4, 6, [162, 158, 154], null, 14, 2); 1551 } 1552 /* koi, turning under the surface */ 1553 for (i = 0; i < 5; i++) { 1554 var ka = t * (0.3 + i * 0.07) + i * 1.6; 1555 var kr = 52 + i * 22; 1556 var kx = Math.cos(ka) * kr, kz = 6 + Math.sin(ka) * kr * 0.34; 1557 var col = i % 2 ? [246, 130, 92] : [252, 246, 244]; 1558 E.box(m, kx, -2, kz, 6.5, 1.4, 3, col, null, -370, 1.2); 1559 E.box(m, kx - Math.cos(ka) * 7, -2, kz - Math.sin(ka) * 3.5, 2.6, 1.1, 2.2, 1560 col, null, -371, 0.9); 1561 } 1562 /* ripples where he is dropping food */ 1563 for (i = 0; i < 3; i++) { 1564 var rp = ((t * 0.5 + i * 0.333) % 1); 1565 var rrr = 6 + rp * 26; 1566 var prev = null; 1567 for (var k = 0; k <= 20; k++) { 1568 var ang = (k / 20) * Math.PI * 2;
1569 var pt = [-46 + Math.cos(ang) * rrr, -1.2, 16 + Math.sin(ang) * rrr * 0.5]; 1570 if (prev) m.line(prev, pt, [214, 240, 250], 1.1, 0.5 * (1 - rp)); 1571 prev = pt; 1572 } 1573 } 1574 blobTree(m, -220, -34, 72, 32, [[255, 176, 203], [255, 200, 220]], 1575 [[122, 86, 62], [96, 66, 46]], t, 3); 1576 blobTree(m, 214, -30, 64, 28, [[132, 190, 120], [162, 210, 148]], 1577 [[110, 80, 56], [86, 60, 42]], t, 8); 1578 for (i = 0; i < 8; i++) grassTuft(m, -270 + i * 76, 58, t, [[122, 186, 106], [98, 162, 90]], 9, i); 1579 lantern(m, 150, 52, t, 1); 1580 return P; 1581 }, 1582 1583 /* ---- the workshop: a bench under a tree, where the work gets made ---- */ 1584 workshop: function (m, t, p, seed) { 1585 var P = { soil: [146, 116, 92], soilSide: [118, 92, 72], turf: [150, 190, 138] }; 1586 P.far = [[132, 190, 120], [162, 210, 148]]; P.mote = [226, 238, 224]; 1587 P.cloudLit=[255,252,250];P.cloudShd=[228,228,236];P.birdCol=[80,76,84]; 1588 skyworld(m, t, P, { clouds: 8, birds: 6 }); 1589 surroundings(m, t, P, "leaf"); 1590 ground(m, P, -300, 300, -70, 70); 1591 margins(m, t, P, "spring"); 1592 /* the bench, and a stool he sits on */ 1593 E.box(m, 0, -22, -4, 46, 2.4, 18, [156, 116, 82], null, 20, 1); 1594 E.box(m, -40, -11, -4, 2.6, 11, 2.6, [124, 90, 64], null, 18, 0.8); 1595 E.box(m, 40, -11, -4, 2.6, 11, 2.6, [124, 90, 64], null, 18, 0.8); 1596 E.box(m, 18, -8, 22, 9, 8, 9, [132, 96, 68], null, 18, 1.4); 1597 /* a screen on the bench, its content flickering as it redraws */ 1598 var lit = 0.7 + Math.sin(t * 3.1) * 0.12 + Math.sin(t * 7.7) * 0.06; 1599 E.box(m, -18, -34, -8, 13, 9.5, 1.4, [46, 40, 54], null, 26, 1); 1600 E.box(m, -18, -34, -9.4, 11.4, 8, 0.5, [90 * lit + 40, 150 * lit + 40, 210 * lit + 30], null, 30, 0.6); 1601 for (var r = 0; r < 4; r++) { 1602 var lw = 3 + ((Math.floor(t * 3) + r * 3) % 5) * 1.5; 1603 E.box(m, -18 - 4.5 + lw / 2, -38 + r * 2.4, -10, lw / 2, 0.4, 0.3, 1604 [220, 240, 255], null, 34, 0.2); 1605 } 1606 /* parts laid out along the bench */ 1607 E.box(m, 4, -26, -6, 4, 2, 4, [214, 96, 124], null, 26, 1); 1608 E.box(m, 13, -25.6, -2, 3, 1.6, 3, [124, 168, 214], null, 26, 0.8); 1609 E.box(m, 32, -26, -4, 5, 2, 5, [232, 176, 92], null, 26, 1.2); 1610 /* a lamp on a stalk, throwing a pool on the bench */ 1611 E.box(m, -38, -30, 2, 1, 8, 1, [96, 88, 96], null, 22, 0.4); 1612 E.box(m, -38, -39, 2, 4, 3, 4, [255, 226, 150], null, 26, 1.4); 1613 blobTree(m, -180, -34, 82, 36, [[132, 190, 120], [162, 210, 148], [110, 172, 104]], 1614 [[110, 80, 56], [86, 60, 42]], t, 5); 1615 blobTree(m, 208, -30, 66, 30, [[255, 190, 210], [255, 210, 226]], 1616 [[122, 86, 62], [96, 66, 46]], t, 2); 1617 for (var i = 0; i < 8; i++) grassTuft(m, -280 + i * 78, 54, t, [[130, 190, 112], [104, 166, 94]], 9, i); 1618 return P; 1619 }, 1620 1621 /* ---- a bamboo grove, where he takes tea ---- */ 1622 bamboo: function (m, t, p, seed) { 1623 var P = { soil: [140, 122, 88], soilSide: [112, 96, 68], turf: [168, 196, 128] }; 1624 P.far = [[150, 190, 100], [176, 208, 128]]; P.mote = [232, 240, 210]; 1625 P.cloudLit=[252,254,246];P.cloudShd=[224,232,214];P.dropCols=[[150,190,100],[176,208,128]];P.birdCol=[74,84,66]; 1626 skyworld(m, t, P, { clouds: 7, birds: 5, drop: "leaf", dropN: 16, dropSpeed: 0.16 }); 1627 surroundings(m, t, P, "leaf"); 1628 ground(m, P, -300, 300, -70, 70); 1629 margins(m, t, P, "spring"); 1630 /* culms, each swaying on its own beat, with joints up the stem */ 1631 for (var i = 0; i < 16; i++) { 1632 var bx = -290 + rnd(i, 31) * 580; 1633 var bz = -60 + rnd(i, 32) * 100; 1634 var bh = 90 + rnd(i, 33) * 70; 1635 var lean = Math.sin(t * 0.6 + i) * 5; 1636 var segs = 7; 1637 for (var k = 0; k < segs; k++) { 1638 var f = k / segs; 1639 E.box(m, bx + lean * f * f, -f * bh - bh / (segs * 2), bz, 1640 2.6 - f * 0.7, bh / (segs * 2), 2.6 - f * 0.7, 1641 k % 2 ? [136, 176, 90] : [116, 158, 76], null, 10, 0.8); 1642 E.box(m, bx + lean * f * f, -f * bh - bh / segs, bz, 1643 3 - f * 0.7, 0.5, 3 - f * 0.7, [92, 132, 62], null, 12, 0.4); 1644 } 1645 /* a few leaves off the top */ 1646 for (k = 0; k < 4; k++) { 1647 var la = k * 1.6 + i;
1648 E.box(m, bx + lean + Math.cos(la) * 9, -bh - 4 - k * 3, bz + Math.sin(la) * 6, 1649 6, 0.8, 1.6, [150, 190, 100], null, 14, 0.6); 1650 } 1651 } 1652 /* a mat and a low table for the tea */ 1653 E.box(m, 0, -1.4, 26, 26, 1.4, 16, [206, 186, 142], null, -20, 1); 1654 E.box(m, 22, -7, 22, 8, 5, 7, [148, 110, 78], null, 18, 1.2); 1655 E.box(m, 22, -13, 22, 3, 1.6, 3, [244, 244, 240], null, 26, 1); 1656 for (i = 0; i < 4; i++) { 1657 var sp2 = ((t * 0.6 + i * 0.25) % 1); 1658 E.box(m, 22 + Math.sin(sp2 * 6 + i) * 2.6, -16 - sp2 * 14, 22, 1659 0.9 - sp2 * 0.4, 0.9 - sp2 * 0.4, 0.9 - sp2 * 0.4, [330, 332, 338], null, 30, 0.4); 1660 } 1661 var stone = [[-150, 40], [-90, 52], [120, 46], [190, 38]]; 1662 for (i = 0; i < stone.length; i++) { 1663 E.box(m, stone[i][0], -3, stone[i][1], 8, 3, 6, [166, 160, 152], null, 12, 2); 1664 } 1665 return P; 1666 }, 1667 1668 /* ---- the same water as the band above, carrying on down. No banks 1669 across the front and no platform edge, so the two read as one 1670 body rather than two worlds stacked. ---- */ 1671 openwater: function (m, t, p, seed) { 1672 var P = { soil: [148, 118, 96], soilSide: [120, 94, 76], turf: [136, 190, 122], 1673 cloudLit: [255, 250, 252], cloudShd: [226, 232, 240], 1674 dropCols: [[255, 186, 210], [255, 214, 228]], birdCol: [86, 80, 90] }; 1675 skyworld(m, t, P, { clouds: 2, birds: 6, drop: "petal", dropN: 16, dropSpeed: 0.15 }); 1676 var i; 1677 1678 /* the water body, running off both sides of the frame */ 1679 E.box(m, 0, 96, 0, 430, 104, 96, [46, 92, 116], null, -300, 0); 1680 /* No surface here. This band sits under the one the pond above already
1681 has, and drawing a second waterline is exactly what made the two read 1682 as separate bodies rather than one. */ 1683 /* light coming down through it */ 1684 for (i = 0; i < 12; i++) { 1685 var lx = -400 + i * 70 + Math.sin(t * 0.5 + i) * 14; 1686 m.line([lx, 10, -40 + (i % 3) * 34], [lx + 26, 108, 10 + (i % 2) * 30], 1687 [176, 224, 236], 2.4, 0.1 + 0.05 * Math.sin(t * 1.3 + i)); 1688 } 1689 /* weed on the bed */ 1690 for (i = 0; i < 20; i++) { 1691 var vx = -410 + rnd(i, 81) * 820, vz = -60 + rnd(i, 82) * 120; 1692 var vh = 16 + rnd(i, 83) * 26; 1693 var lean = Math.sin(t * 0.7 + i) * 6; 1694 for (var k = 0; k < 5; k++) { 1695 var f = k / 5; 1696 E.box(m, vx + lean * f * f, 118 - f * vh - vh / 10, vz, 1697 2 - f * 0.7, vh / 10, 2 - f * 0.7, 1698 k % 2 ? [64, 138, 96] : [48, 116, 78], null, -60, 0.6); 1699 } 1700 } 1701 for (i = 0; i < 9; i++) { 1702 var sr = 8 + rnd(i, 84) * 11; 1703 E.box(m, -390 + rnd(i, 85) * 780, 116 - sr * 0.5, -50 + rnd(i, 86) * 100, 1704 sr, sr * 0.5, sr * 0.7, [104, 108, 104], null, -70, sr * 0.4); 1705 } 1706 /* the koi, working the whole width */ 1707 for (i = 0; i < 9; i++) { 1708 var a = t * (0.15 + i * 0.03) + i * 1.1; 1709 var rr = 70 + i * 34; 1710 var kx = Math.cos(a) * rr; 1711 var kz = Math.sin(a) * rr * 0.3; 1712 var ky = 34 + i * 8 + Math.sin(t * 0.8 + i) * 7; 1713 var col = i % 3 === 0 ? [252, 246, 244] : i % 3 === 1 ? [246, 130, 92] : [236, 176, 90]; 1714 var tail = Math.sin(t * 4 + i) * 4; 1715 E.box(m, kx, ky, kz, 9, 3.4, 4.4, col, null, -40, 1.6); 1716 E.box(m, kx - Math.cos(a) * 11, ky, kz - Math.sin(a) * 4 + tail, 4, 2.4, 3, col, null, -41, 1.2); 1717 E.box(m, kx - Math.cos(a) * 15, ky, kz - Math.sin(a) * 6 + tail * 1.6, 1718 2.6, 3.4, 1.6, col, null, -42, 0.8); 1719 E.box(m, kx + Math.cos(a) * 7, ky - 1, kz + Math.sin(a) * 2.4 - 3.4, 1720 1.4, 1.4, 0.8, [32, 34, 40], null, -38, 0.4); 1721 } 1722 /* bubbles */ 1723 for (i = 0; i < 16; i++) { 1724 var bf = ((t * 0.3 + rnd(i, 87)) % 1); 1725 E.box(m, -300 + (i % 8) * 84 + Math.sin(bf * 8 + i) * 9, 1726 116 - bf * 100, -30 + (i % 5) * 16, 1727 1 + bf * 1.6, 1 + bf * 1.6, 1 + bf * 1.6, [186, 226, 238], null, -30, 0.8); 1728 } 1729 /* shafts coming down from the surface overhead */ 1730 for (i = 0; i < 12; i++) { 1731 var sx2 = -400 + i * 70 + Math.sin(t * 0.4 + i) * 12; 1732 m.line([sx2, -30, -40 + (i % 3) * 34], [sx2 + 30, 190, 10 + (i % 2) * 30], 1733 [176, 224, 236], 2.6, 0.09); 1734 } 1735 return P; 1736 }, 1737 1738 /* ---- the craft floor: a wheel throwing a pot, a kiln firing, and the 1739 shelf it all ends up on. For the band about how the work is made. */ 1740 craft: function (m, t, p, seed) { 1741 /* One loaf, all the way through: rolled out on the board, shaped, 1742 carried in on the peel, baked while the oven works, drawn back out 1743 and set on the shelf. Then the next one. */ 1744 var P = { soil: [138, 112, 92], soilSide: [112, 90, 72], turf: [176, 158, 132], 1745 cloudLit: [255, 250, 248], cloudShd: [230, 220, 222], mote: [246, 226, 236], 1746 tool: [150, 122, 92], loaf: [206, 156, 96] }; 1747 skyworld(m, t, P, { clouds: 7, birds: 5 }); 1748 surroundings(m, t, P, "leaf"); 1749 ground(m, P, -300, 300, -70, 70); 1750 margins(m, t, P, "craft"); 1751 var i; 1752 1753 var CY = 15; 1754 var q = (t % CY) / CY; 1755 var BX = -170, OX = 150; /* the board, and the oven */ 1756 var doughR = 0, onBoard = 0, onPeel = 0, inOven = 0, shelfExtra = 0; 1757 1758 if (q < 0.26) { /* rolling it out */ 1759 ACTOR.act = "roll"; ACTOR.x = BX; ACTOR.z = 42; 1760 doughR = 0.4 + (q / 0.26) * 0.6; onBoard = 1; 1761 } else if (q < 0.36) { /* shaped, and taken up */ 1762 ACTOR.act = "roll"; ACTOR.x = BX; ACTOR.z = 42; 1763 doughR = 1; onBoard = 1; 1764 } else if (q < 0.46) { /* carried in on the peel */ 1765 ACTOR.act = "peel"; ACTOR.x = OX - 66; ACTOR.z = 40; onPeel = 1; 1766 } else if (q < 0.74) { /* baking */ 1767 ACTOR.act = "warm"; ACTOR.x = OX - 58; ACTOR.z = 44; inOven = 1; 1768 } else if (q < 0.86) { /* drawn back out */ 1769 ACTOR.act = "peel"; ACTOR.x = OX - 66; ACTOR.z = 40; onPeel = 1; 1770 } else { /* set on the shelf */ 1771 ACTOR.act = "roll"; ACTOR.x = BX; ACTOR.z = 42; shelfExtra = 1; 1772 } 1773 P.onPeel = onPeel; 1774 1775 /* --- the board he works on --- */ 1776 E.box(m, BX, -22, 30, 34, 2.4, 16, [156, 122, 88], null, 18, 1); 1777 E.box(m, BX - 28, -11, 30, 2.6, 11, 2.6, [124, 94, 64], null, 16, 0.8); 1778 E.box(m, BX + 28, -11, 30, 2.6, 11, 2.6, [124, 94, 64], null, 16, 0.8); 1779 /* flour, a jar and a bowl at the back of it */ 1780 E.box(m, BX - 20, -26, 36, 5, 2, 5, [236, 230, 216], null, 22, 1.4); 1781 E.box(m, BX + 18, -28, 36, 4, 4, 4, [186, 196, 196], null, 22, 1.4); 1782 if (onBoard) { 1783 /* the dough, spreading as it is worked */ 1784 E.box(m, BX, -25.4, 26, 5 + doughR * 6, 1.6 - doughR * 0.5, 4 + doughR * 3, 1785 [232, 214, 178], null, 26, 1.4); 1786 } 1787 1788 /* --- the oven --- */ 1789 E.box(m, OX, -34, 20, 34, 34, 30, [128, 108, 96], null, 0, 3); 1790 E.box(m, OX, -72, 20, 26, 6, 24, [104, 86, 76], null, 4, 2); 1791 E.box(m, OX, -86, 20, 7, 9, 7, [104, 86, 76], null, 6, 2); 1792 /* the mouth, brighter while there is something in it */ 1793 var burn = (inOven ? 0.85 : 0.5) + Math.sin(t * 2.8) * 0.16 + Math.sin(t * 6.1) * 0.08; 1794 E.box(m, OX, -30, -11, 12, 10, 1.4, [92, 70, 62], null, 28, 1.4); 1795 E.box(m, OX, -30, -12, 10, 8, 0.8, 1796 [255, 150 + 90 * burn, 60 + 50 * burn], { emit: 1, glow: 14 * burn }, 30, 1.2); 1797 if (inOven) { 1798 /* the loaf sitting in the mouth, darkening as it bakes */ 1799 var bake = (q - 0.46) / 0.28; 1800 E.box(m, OX, -28, -8, 4, 2.2 + bake * 0.8, 3.2, 1801 [232 - bake * 30, 200 - bake * 46, 150 - bake * 54], null, 34, 1.6); 1802 } 1803 for (i = 0; i < 6; i++) { /* heat off the stack */ 1804 var hf = ((t * 0.5 + i * 0.166) % 1); 1805 E.gem(m, 2 + hf * 5, 1, OX + Math.sin(hf * 6 + i) * 9, -96 - hf * 54, 20, 1806 [236 + hf * 14, 226 + hf * 16, 220 + hf * 20], null); 1807 } 1808 /* firewood stacked against it */ 1809 for (i = 0; i < 6; i++) { 1810 E.box(m, OX + 30, -4 - (i % 3) * 6, 44 + Math.floor(i / 3) * 8, 1811 9, 3, 3, i % 2 ? [122, 92, 62] : [104, 78, 52], null, 14, 1.4); 1812 } 1813 1814 /* --- the shelf, filling up through the day --- */ 1815 E.box(m, -20, -54, 52, 58, 2, 12, [140, 114, 90], null, 10, 1); 1816 E.box(m, -20, -30, 52, 58, 2, 12, [140, 114, 90], null, 10, 1); 1817 var loaves = 4 + shelfExtra; 1818 for (i = 0; i < loaves; i++) { 1819 var sx = -62 + i * 22; 1820 E.box(m, sx, -60, 52, 7, 4, 5, [212 - (i % 3) * 14, 168 - (i % 3) * 12, 108], 1821 null, 14, 2.4); 1822 if (i < 3) { 1823 E.box(m, sx, -36, 52, 6.5, 3.6, 4.6, [206 - (i % 2) * 12, 160, 104], 1824 null, 14, 2.2); 1825 } 1826 } 1827 /* a basket of them at the foot */ 1828 E.box(m, 58, -6, 44, 12, 6, 9, [158, 126, 82], null, 12, 2); 1829 E.box(m, 58, -13, 44, 9, 3, 6, [212, 170, 112], null, 16, 2.4); 1830 1831 for (i = 0; i < 9; i++) { 1832 grassTuft(m, -270 + i * 66, 60, t, [[150, 136, 112], [128, 116, 96]], 7, i); 1833 } 1834 return P; 1835 }, 1836 1837 /* ---- dusk: lanterns lit along the path, the day closing ---- */ 1838 dusk: function (m, t, p, seed) { 1839 var P = { soil: [128, 96, 78], soilSide: [102, 76, 60], turf: [166, 132, 104] }; 1840 P.wide = 1; 1841 P.far = [[214, 138, 96], [232, 172, 118]]; P.mote = [255, 206, 150]; 1842 P.cloudLit=[255,232,206];P.cloudShd=[226,186,158];P.birdCol=[92,66,58]; 1843 skyworld(m, t, P, { clouds: 8, birds: 8 }); 1844 surroundings(m, t, P, "leaf"); 1845 ground(m, P, -430, 430, -70, 70); 1846 margins(m, t, P, "dusk"); 1847 stream(m, t, true, p); 1848 drifter(m, 0); 1849 var warm = [[214, 138, 96], [232, 172, 118], [188, 110, 78]]; 1850 blobTree(m, -200, -30, 72, 32, warm, [[92, 66, 48], [70, 50, 36]], t, 4); 1851 blobTree(m, 190, -32, 82, 36, warm, [[92, 66, 48], [70, 50, 36]], t, 7); 1852 /* Six lanterns, lit one at a time. He walks to the next, reaches up to 1853 it, it comes on, and he moves along. Once the path is lit they go 1854 out together and he starts at the near end again. */ 1855 var LN = 6, CYd = 19; 1856 var qd = (t % CYd) / CYd; 1857 var slot = Math.min(LN - 1, Math.floor(qd * (LN + 0.6))); 1858 var within = qd * (LN + 0.6) - slot; 1859 var lx0 = -230 + slot * 92;
1860 if (within < 0.55) { 1861 /* on his way, between the last one and this */ 1862 var from = -230 + Math.max(0, slot - 1) * 92; 1863 ACTOR.act = "walk"; 1864 ACTOR.x = from + (lx0 - from) * (within / 0.55) - 22; 1865 ACTOR.z = 56; 1866 ACTOR.face = lx0 >= from ? -Math.PI / 2 : Math.PI / 2; 1867 } else { 1868 ACTOR.act = "light"; ACTOR.x = lx0 - 22; ACTOR.z = 52; 1869 } 1870 for (var i = 0; i < LN; i++) { 1871 /* lit once he has been to it, and only while the path is still filling */ 1872 var on = qd < 0.94 && (i < slot || (i === slot && within > 0.7)); 1873 lantern(m, -230 + i * 92, 44, t, i, on ? 1 : 0.12); 1874 } 1875 for (i = 0; i < 8; i++) { 1876 grassTuft(m, -250 + i * 72, 20, t, [[150, 122, 88], [128, 102, 72]], 8, i); 1877 } 1878 return P; 1879 }, 1880 1881 /* ---- night: the year closes out under the stars ---- */ 1882 night: function (m, t, p, seed) { 1883 /* the dusk ground after dark, not a different ground */ 1884 var P = { soil: [92, 70, 58], soilSide: [72, 54, 44], turf: [116, 94, 76] }; 1885 P.mote = [214, 226, 255]; 1886 P.cloudLit=[188,196,224];P.cloudShd=[142,150,182];P.birdCol=[52,56,78]; 1887 skyworld(m, t, P, { clouds: 7, birds: 2 }); 1888 surroundings(m, t, P, "pine"); 1889 ground(m, P, -430, 430, -70, 70); 1890 margins(m, t, P, "night"); 1891 stream(m, t, false, p); 1892 drifter(m, 1); 1893 pineTree(m, -230, -30, 88, [34, 66, 74], [24, 48, 56], false); 1894 pineTree(m, 220, -28, 78, [34, 66, 74], [24, 48, 56], false); 1895 lantern(m, -60, 36, t, 1); 1896 lantern(m, 90, 40, t, 3); 1897 campfire(m, -290, 22, t); 1898 E.box(m, -250, -7, 30, 20, 6, 6, [92, 70, 56], null, 18, 2.4); 1899 for (var i = 0; i < 22; i++) { 1900 var tw = 0.4 + 0.6 * Math.abs(Math.sin(t * 1.2 + i)); 1901 E.box(m, -280 + rnd(i, 21) * 560, -110 - rnd(i, 22) * 60, -60 + rnd(i, 23) * 120, 1902 1.4 * tw, 1.4 * tw, 1.4 * tw, [226, 236, 255], null, 90, 0.5); 1903 } 1904 return P; 1905 } 1906 }; 1907 1908 /* who he is in each world, and what he is doing there. He stays in one 1909 place and repeats the action; nothing here is driven by the scroll. */ 1910 /* A world can hand back an act and a place to stand, so a scene and the 1911 figure in it run one sequence together rather than two loops side by 1912 side. Cleared before every build. */ 1913 var ACTOR = { act: null, x: 0, z: 0, face: 0 }; 1914 1915 /* Absolute seconds, the same number in every world on any given frame. Each 1916 mount's own `t` starts at its first painted frame, so two bands can never 1917 agree on it; anything that has to be handed from one band to the next is 1918 timed off this instead. */ 1919 var CLOCK = 0; 1920 1921 /* --- shared frame budget ------------------------------------------------ 1922 Eleven worlds, the sky and the product models all draw on the same thread, 1923 so no one of them can tell on its own whether the page is keeping up. They 1924 report into this instead: it holds a rolling average of how long a world
1925 frame costs and hands back a quality level everything scales to. On a 1926 machine that is coping nothing changes; on one that is not, resolution and 1927 rate come down together and the page stays smooth instead of stuttering. */ 1928 /* Wide screens make each full-width canvas much more expensive even when a 1929 single world frame looks cheap in isolation. Start desktop at the same 1930 30fps / near-1x quality tier the adaptive budget would eventually choose, 1931 and do not immediately climb back to 60fps before combined page cost is 1932 visible. Mobile keeps the original full-quality behaviour. */ 1933 var desktopBudget = window.matchMedia && window.matchMedia("(min-width: 900px)").matches; 1934 var BUDGET = { 1935 avg: 6, 1936 level: desktopBudget ? 0.82 : 1, 1937 maxLevel: desktopBudget ? 0.82 : 1, 1938 checked: 0 1939 }; 1940 function budgetReport(ms) { 1941 BUDGET.avg += (ms - BUDGET.avg) * 0.08; 1942 BUDGET.checked++; 1943 if (BUDGET.checked < 20) return; /* let it settle first */ 1944 /* a world frame over about 9ms means the page cannot hold 60 with this 1945 many of them; over 16 it cannot hold 30 either */ 1946 if (BUDGET.avg > 15 && BUDGET.level > 0.55) BUDGET.level -= 0.05; 1947 else if (BUDGET.avg > 9 && BUDGET.level > 0.75) BUDGET.level -= 0.02; 1948 else if (BUDGET.avg < 5 && BUDGET.level < BUDGET.maxLevel) { 1949 BUDGET.level = Math.min(BUDGET.maxLevel, BUDGET.level + 0.01); 1950 } 1951 } 1952 window.CLX_BUDGET = BUDGET; 1953 1954 /* One observer gates every world. Previously all twelve mounts called 1955 getBoundingClientRect on every animation frame, even several screens away, 1956 which made desktop scrolling fight needless layout reads. */ 1957 var worldObserver = null; 1958 if ("IntersectionObserver" in window) { 1959 worldObserver = new IntersectionObserver(function (entries) { 1960 for (var oi = 0; oi < entries.length; oi++) { 1961 entries[oi].target._clxWorldActive = entries[oi].isIntersecting; 1962 } 1963 }, { rootMargin: "120px 0px" }); 1964 } 1965 1966 /* worlds nobody stands in */ 1967 var UNMANNED = { openwater: 1 }; 1968 1969 var CAST = { 1970 sakura: { pal: {}, act: "sleep", x: -14, z: 46 }, 1971 pond: { pal: { coat: [110, 148, 190], coatIn: [148, 182, 216], tool: [138, 100, 70] }, act: "fish", x: -196, z: 46 }, 1972 workshop: { pal: { coat: [96, 110, 148], coatIn: [132, 148, 186] }, act: "code", x: 14, z: 24 }, 1973 bamboo: { pal: { coat: [126, 152, 96], coatIn: [162, 186, 126] }, act: "tea", x: -206, z: 34 }, 1974 summer: { pal: { coat: [236, 238, 244], coatIn: [214, 220, 232], cam: [58, 60, 70] }, act: "photo", x: 214, z: 42 }, 1975 autumn: { pal: { coat: [176, 96, 62], coatIn: [206, 132, 88], scarf: [214, 96, 48], 1976 tool: [138, 100, 70] }, act: "rake", x: 24, z: 34 }, 1977 winter: { pal: { coat: [78, 96, 142], coatIn: [110, 130, 178], scarf: [214, 96, 48] }, 1978 act: "sip", x: 226, z: 26 }, 1979 jungle: { pal: { coat: [86, 132, 106], coatIn: [116, 164, 134] }, act: "watch", x: 248, z: 38 }, 1980 craft: { pal: { coat: [242, 238, 230], coatIn: [214, 206, 192], tool: [150, 122, 92] }, act: "roll", x: -170, z: 42 }, 1981 dusk: { pal: { coat: [154, 96, 82], coatIn: [188, 128, 108] }, act: "light", x: -262, z: 40 }, 1982 night: { pal: { coat: [62, 74, 116], coatIn: [92, 106, 152], scarf: [214, 96, 48] }, 1983 act: "warm", x: -250, z: 30 } 1984 }; 1985 1986 /* how far down the frame each world sits, so scenes with depth below the 1987 ground line still fit */ 1988 /* The open water band sits high in its frame on purpose: the surface has to 1989 reach the top edge, or the empty sky above it shows as a stripe of page 1990 between this water and the pond it is supposed to be continuing. */ 1991 var OY = { pond: 0.02, openwater: -0.46, dusk: 0.26, night: 0.26 }; 1992 1993 /* --------------------------------------------------------------- mounting */ 1994 function mount(host, kind, idx) { 1995 var build = WORLDS[kind]; 1996 if (!build) return; 1997 var cast = CAST[kind] || CAST.sakura; 1998 1999 var wrap = document.createElement("div"); 2000 wrap.className = "world"; 2001 wrap.setAttribute("aria-hidden", "true"); 2002 wrap._clxWorldActive = !worldObserver; 2003 if (worldObserver) worldObserver.observe(wrap); 2004 var cv = document.createElement("canvas"); 2005 wrap.appendChild(cv); 2006 host.insertBefore(wrap, host.firstChild); 2007 var ctx = cv.getContext("2d"); 2008 var W = 0, H = 0, dpr = 1; 2009 2010 function size(r) { 2011 r = r || wrap.getBoundingClientRect(); 2012 if (!r.width || !r.height) return false;
2013 if (Math.round(r.width) === W && Math.round(r.height) === H && 2014 Math.abs(dpr - Math.min(window.devicePixelRatio || 1, 1.5) * BUDGET.level) < 0.06) return true; 2015 dpr = Math.min(window.devicePixelRatio || 1, 1.5) * BUDGET.level; 2016 W = Math.round(r.width); H = Math.round(r.height); 2017 cv.width = Math.round(W * dpr); cv.height = Math.round(H * dpr); 2018 cv.style.width = W + "px"; cv.style.height = H + "px"; 2019 ctx.setTransform(dpr, 0, 0, dpr, 0, 0); 2020 return true; 2021 } 2022 2023 function nameTag(pt, alpha) { 2024 if (!pt) return; 2025 var label = "WuYuXiang"; 2026 ctx.save(); 2027 ctx.globalAlpha = alpha; 2028 ctx.font = '600 12px "Share Tech Mono", ui-monospace, monospace'; 2029 var tw = ctx.measureText(label).width; 2030 var pw = tw + 26, ph = 20; 2031 var bx = pt[0] - pw / 2, by = pt[1] - ph - 12; 2032 /* the leader down to his head */ 2033 ctx.strokeStyle = "rgba(30,26,34,0.35)"; 2034 ctx.lineWidth = 1; 2035 ctx.beginPath(); ctx.moveTo(pt[0], by + ph); ctx.lineTo(pt[0], pt[1] - 2); ctx.stroke(); 2036 /* plate */ 2037 ctx.fillStyle = "rgba(255,255,255,0.9)"; 2038 ctx.strokeStyle = "rgba(30,26,34,0.22)"; 2039 if (ctx.roundRect) { ctx.beginPath(); ctx.roundRect(bx, by, pw, ph, 6); } 2040 else { ctx.beginPath(); ctx.rect(bx, by, pw, ph); } 2041 ctx.fill(); ctx.stroke(); 2042 /* a live dot, so the tag reads as a marker rather than a caption */ 2043 ctx.fillStyle = "#e75a7c"; 2044 ctx.beginPath(); ctx.arc(bx + 9, by + ph / 2, 2.6, 0, Math.PI * 2); ctx.fill(); 2045 ctx.fillStyle = "#2b1f29"; 2046 ctx.textBaseline = "middle"; 2047 ctx.fillText(label, bx + 16, by + ph / 2 + 0.5); 2048 ctx.restore(); 2049 } 2050 2051 /* a click anywhere on the strip lands in the scene */ 2052 var hit = { pulse: 0, x: 0 }; 2053 wrap.style.pointerEvents = "auto"; 2054 wrap.style.cursor = "pointer"; 2055 wrap.addEventListener("click", function (ev) { 2056 var r2 = wrap.getBoundingClientRect(); 2057 if (!r2.width) return; 2058 /* screen x across the strip maps onto the scene's own width */ 2059 hit.x = ((ev.clientX - r2.left) / r2.width - 0.5) * 620; 2060 hit.pulse = 1; 2061 }); 2062 2063 var t0 = null; 2064 function frame(now) { 2065 var r = wrap.getBoundingClientRect(); 2066 var vh = window.innerHeight || 800; 2067 if (r.bottom < -60 || r.top > vh + 60) return; 2068 if (!size(r)) return; 2069 if (t0 === null) t0 = now; 2070 var t = (now - t0) / 1000; 2071 CLOCK = now / 1000; 2072 2073 /* p: how far this band has travelled up the screen, 0 arriving, 1 gone */ 2074 var p = 1 - Math.max(0, Math.min(1, (r.top + r.height * 0.5) / (vh * 0.9))); 2075 var travel = (p - 0.5) * 2; /* how far through the band we are */ 2076 2077 hit.pulse = Math.max(0, hit.pulse - 0.012); 2078 ACT.pulse = hit.pulse; ACT.x = hit.x; 2079 2080 ctx.clearRect(0, 0, W, H); 2081 ACTOR.act = null; ACTOR.face = 0; 2082 var m = new E.Model(); 2083 build(m, t, p, idx); 2084 2085 if (!UNMANNED[kind]) { 2086 person(m, ACTOR.act ? ACTOR.x : cast.x, ACTOR.act ? ACTOR.z : cast.z, 2087 t, personPal(cast.pal), ACTOR.act || cast.act, 2088 ACTOR.act ? ACTOR.face : 0); 2089 } 2090 2091 /* Fixed camera. Turning it with the scroll meant two worlds meeting at a 2092 band boundary sat at different angles and scales, so their ground 2093 lines could never line up. Scroll drives what happens inside a scene 2094 instead, through p, not where it is viewed from. */ 2095 var yaw = Math.sin(t * 0.11) * 0.09; 2096 var pitch = 0.2; 2097 var scale = Math.max(0.34, W / 780); 2098 E.render(ctx, m, W, H, yaw, pitch, scale, 2099 H * (OY[kind] === undefined ? 0.3 : OY[kind])); 2100 /* the name plate is gone: it followed him everywhere and pulled the 2101 eye off whatever he was actually doing */ 2102 } 2103 2104 if (reduce) { size(); frame(0); return; } 2105 /* These are background scenery, so they run at half rate. It halves the 2106 cost of rebuilding and depth-sorting a few hundred faces per world and 2107 is not noticeable on a diorama that only drifts. */ 2108 var last = -1e9; 2109 (function loop(now) { 2110 requestAnimationFrame(loop); 2111 now = now || 0; 2112 if (document.hidden) return; /* nothing to see */ 2113 if (!wrap._clxWorldActive) return; /* off-screen worlds stay idle */ 2114 /* Sixty while the page is coping, thirty when it is starting to 2115 struggle, twenty when it plainly is not. The budget below already 2116 measures what a world frame costs and was written against 60 all 2117 along; the rate was simply never allowed up there. 15 rather than 2118 16.67 so a frame arriving a shade early is not thrown away, which 2119 would halve the rate rather than trim it. */ 2120 if (now - last < (BUDGET.level > 0.85 ? 15 : BUDGET.level > 0.7 ? 24 : 50)) return; 2121 last = now; 2122 var t1 = (window.performance && performance.now) ? performance.now() : 0; 2123 frame(now); 2124 if (t1) budgetReport(performance.now() - t1); 2125 })(); 2126 } 2127 2128 function boot() { 2129 /* every band on the home page gets a world, including the ones that 2130 were still flat colour: the strip, the closing call and the footer */ 2131 /* every band gets its own scene, none of them repeated */ 2132 var picks = [ 2133 [".hero", "pond"], 2134 [".strip", "openwater"], 2135 ["#products", "workshop"], 2136 ["#stack", "bamboo"], 2137 ["#about", "craft"], 2138 ["#extensions", "sakura"], 2139 ["#apps", "summer"], 2140 ["#websites", "autumn"], 2141 ["#ai", "winter"], 2142 ["#subproducts", "jungle"], 2143 [".cta", "dusk"], 2144 [".footer", "night"] 2145 ]; 2146 var n = 0; 2147 for (var i = 0; i < picks.length; i++) { 2148 var el = document.querySelector(picks[i][0]); 2149 if (!el) continue; 2150 if (getComputedStyle(el).position === "static") el.style.position = "relative"; 2151 mount(el, picks[i][1], n++); 2152 } 2153 } 2154 2155 /* the flight builds on the same scenes, so it needs to reach them */ 2156 window.CLX_WORLDS = { 2157 worlds: WORLDS, cast: CAST, person: person, pal: personPal, 2158 actor: ACTOR, unmanned: UNMANNED, 2159 setClock: function (v) { CLOCK = v; } 2160 }; 2161 2162 if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot); 2163 else boot(); 2164})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.