zeroed-some/dougk / b334448

Browse files

tone down duck ruffles, add water anim improvements

Authored by espadonne
SHA
b334448425f1a630b536b9303b8a0a67faee655d
Parents
ea0aa57
Tree
866720f

2 changed files

StatusFile+-
M src/renderers/three/duck.js 49 32
M src/renderers/three/pond.js 82 12
src/renderers/three/duck.jsmodified
@@ -59,18 +59,15 @@ export function createDoug(scene, gradientMap) {
5959
   chest.position.set(0.25, 0.32, 0)
6060
   group.add(chest)
6161
 
62
-  // Scraggly feather tufts on body
62
+  // Subtle feather tufts on body (toned down)
6363
   const tuftMaterial = bodyMaterial
6464
   const featherPositions = [
65
-    { x: -0.4, y: 0.45, z: 0.15, rx: 0.3, rz: 0.5 },
66
-    { x: -0.45, y: 0.4, z: -0.12, rx: -0.2, rz: 0.4 },
67
-    { x: -0.3, y: 0.48, z: 0, rx: 0, rz: 0.3 },
68
-    { x: 0, y: 0.5, z: 0.25, rx: 0.4, rz: -0.2 },
69
-    { x: 0, y: 0.5, z: -0.25, rx: -0.4, rz: -0.2 },
65
+    { x: -0.38, y: 0.44, z: 0.1, rx: 0.2, rz: 0.3 },
66
+    { x: -0.38, y: 0.44, z: -0.1, rx: -0.2, rz: 0.3 },
7067
   ]
7168
 
7269
   for (const f of featherPositions) {
73
-    const featherGeom = new THREE.ConeGeometry(0.06, 0.18, 4)
70
+    const featherGeom = new THREE.ConeGeometry(0.04, 0.12, 4)
7471
     const feather = new THREE.Mesh(featherGeom, tuftMaterial)
7572
     feather.position.set(f.x, f.y, f.z)
7673
     feather.rotation.x = f.rx
@@ -78,18 +75,16 @@ export function createDoug(scene, gradientMap) {
7875
     group.add(feather)
7976
   }
8077
 
81
-  // Tail feathers - more prominent and scraggly
78
+  // Tail feathers - subtle curl up
8279
   const tailGroup = new THREE.Group()
8380
   const tailFeathers = [
84
-    { x: -0.58, y: 0.38, z: 0, rx: 1.8, rz: 0, scale: 1.2 },
85
-    { x: -0.62, y: 0.42, z: 0.1, rx: 1.6, rz: 0.3, scale: 1.0 },
86
-    { x: -0.62, y: 0.42, z: -0.1, rx: 1.6, rz: -0.3, scale: 1.0 },
87
-    { x: -0.55, y: 0.48, z: 0.05, rx: 1.4, rz: 0.15, scale: 0.8 },
88
-    { x: -0.55, y: 0.48, z: -0.05, rx: 1.4, rz: -0.15, scale: 0.8 },
81
+    { x: -0.55, y: 0.36, z: 0, rx: 1.6, rz: 0, scale: 1.0 },
82
+    { x: -0.52, y: 0.40, z: 0.08, rx: 1.4, rz: 0.2, scale: 0.7 },
83
+    { x: -0.52, y: 0.40, z: -0.08, rx: 1.4, rz: -0.2, scale: 0.7 },
8984
   ]
9085
 
9186
   for (const t of tailFeathers) {
92
-    const tailGeom = new THREE.ConeGeometry(0.05, 0.25, 4)
87
+    const tailGeom = new THREE.ConeGeometry(0.04, 0.18, 4)
9388
     const tail = new THREE.Mesh(tailGeom, bodyMaterial)
9489
     tail.position.set(t.x, t.y, t.z)
9590
     tail.rotation.x = t.rx
@@ -148,27 +143,20 @@ export function createDoug(scene, gradientMap) {
148143
   rightCheek.scale.set(0.8, 0.7, 0.6)
149144
   group.add(rightCheek)
150145
 
151
-  // Head tuft - messier, multiple feathers
152
-  const tuftGeom = new THREE.ConeGeometry(0.04, 0.14, 4)
146
+  // Head tuft - simple pair of feathers
147
+  const tuftGeom = new THREE.ConeGeometry(0.035, 0.12, 4)
153148
   const tuft1 = new THREE.Mesh(tuftGeom, bodyMaterial)
154
-  tuft1.position.set(0.35, 0.95, 0)
155
-  tuft1.rotation.z = -0.4
149
+  tuft1.position.set(0.34, 0.93, 0.03)
150
+  tuft1.rotation.z = -0.3
151
+  tuft1.rotation.x = 0.2
156152
   group.add(tuft1)
157153
 
158154
   const tuft2 = new THREE.Mesh(tuftGeom, bodyMaterial)
159
-  tuft2.position.set(0.32, 0.92, 0.06)
160
-  tuft2.rotation.z = -0.2
161
-  tuft2.rotation.x = 0.3
162
-  tuft2.scale.setScalar(0.8)
155
+  tuft2.position.set(0.34, 0.93, -0.03)
156
+  tuft2.rotation.z = -0.3
157
+  tuft2.rotation.x = -0.2
163158
   group.add(tuft2)
164159
 
165
-  const tuft3 = new THREE.Mesh(tuftGeom, bodyMaterial)
166
-  tuft3.position.set(0.32, 0.92, -0.06)
167
-  tuft3.rotation.z = -0.2
168
-  tuft3.rotation.x = -0.3
169
-  tuft3.scale.setScalar(0.7)
170
-  group.add(tuft3)
171
-
172160
   // Beak - flatter, more duck-like
173161
   const beakGeom = new THREE.ConeGeometry(0.09, 0.32, 6)
174162
   beakGeom.scale(1, 1, 0.6) // Flatten it
@@ -228,7 +216,9 @@ export function createDoug(scene, gradientMap) {
228216
     idleTimer: 0,
229217
     nextIdleMove: 3 + Math.random() * 4,
230218
     wobble: 0,
231
-    headBob: 0
219
+    headBob: 0,
220
+    rippleTimer: 0,
221
+    isMoving: false
232222
   }
233223
 
234224
   // Movement speeds
@@ -323,18 +313,35 @@ export function createDoug(scene, gradientMap) {
323313
 
324314
         // Wobble animation while moving
325315
         state.wobble += delta * 8
316
+        state.isMoving = true
317
+
318
+        // Create swimming ripples
319
+        state.rippleTimer += delta
320
+        const rippleInterval = state.mode === 'swimming' ? 0.25 : 0.5
321
+        if (state.rippleTimer >= rippleInterval) {
322
+          state.rippleTimer = 0
323
+          // Ripple slightly behind the duck
324
+          const rippleX = state.position.x - Math.sin(state.rotation) * 0.3
325
+          const rippleZ = state.position.z - Math.cos(state.rotation) * 0.3
326
+          pond.addRipple(rippleX, rippleZ)
327
+        }
328
+      } else {
329
+        state.isMoving = false
326330
       }
327331
     } else {
332
+      state.isMoving = false
328333
       // Arrived
329334
       if (state.mode === 'swimming') {
330335
         state.mode = 'idle'
331
-        // Eat nearby bread
336
+        // Eat nearby bread - create splash ripple!
332337
         for (const bread of breadBits) {
333338
           if (!bread.eaten) {
334339
             const bx = bread.position.x - state.position.x
335340
             const bz = bread.position.z - state.position.z
336341
             if (Math.sqrt(bx * bx + bz * bz) < 0.4) {
337342
               bread.eaten = true
343
+              // Eating splash ripple
344
+              pond.addRipple(state.position.x, state.position.z)
338345
             }
339346
           }
340347
         }
@@ -356,7 +363,17 @@ export function createDoug(scene, gradientMap) {
356363
     group.position.z = state.position.z
357364
 
358365
     // Bobbing on water
359
-    group.position.y = Math.sin(elapsed * 2) * 0.03
366
+    const bobAmount = Math.sin(elapsed * 2)
367
+    group.position.y = bobAmount * 0.03
368
+
369
+    // Occasional idle bob ripples (when bobbing down)
370
+    if (!state.isMoving && bobAmount < -0.9 && state.rippleTimer > 1.5) {
371
+      state.rippleTimer = 0
372
+      pond.addRipple(state.position.x, state.position.z)
373
+    }
374
+    if (!state.isMoving) {
375
+      state.rippleTimer += delta
376
+    }
360377
 
361378
     // Rotation (face direction of movement)
362379
     // Duck model faces +X locally, so offset by -PI/2 to align with movement
src/renderers/three/pond.jsmodified
@@ -49,38 +49,78 @@ export function createPond(scene, gradientMap) {
4949
   sand.position.y = -0.02
5050
   group.add(sand)
5151
 
52
-  // Water surface
53
-  const waterGeom = new THREE.CircleGeometry(radius, 32)
52
+  // Water surface - higher resolution for wave animation
53
+  const waterGeom = new THREE.CircleGeometry(radius, 48, 8)
5454
   waterGeom.rotateX(-Math.PI / 2)
5555
   const water = new THREE.Mesh(waterGeom, waterMaterial)
5656
   water.position.y = 0
5757
   group.add(water)
5858
 
59
-  // Water depth visual (darker center)
60
-  const deepGeom = new THREE.CircleGeometry(radius * 0.6, 24)
59
+  // Store original water vertex positions for wave animation
60
+  const waterPositions = waterGeom.attributes.position.array.slice()
61
+
62
+  // Water depth visual (darker center with gradient)
63
+  const deepGeom = new THREE.CircleGeometry(radius * 0.7, 32)
6164
   deepGeom.rotateX(-Math.PI / 2)
6265
   const deepMaterial = new THREE.MeshToonMaterial({
6366
     color: waterDeep,
6467
     gradientMap: gradientMap,
6568
     transparent: true,
66
-    opacity: 0.5
69
+    opacity: 0.6
6770
   })
6871
   const deep = new THREE.Mesh(deepGeom, deepMaterial)
69
-  deep.position.y = -0.01
72
+  deep.position.y = -0.02
7073
   group.add(deep)
7174
 
72
-  // Water highlight (light reflection)
73
-  const highlightGeom = new THREE.CircleGeometry(radius * 0.3, 16)
75
+  // Secondary highlight shimmer
76
+  const shimmerGeom = new THREE.CircleGeometry(radius * 0.5, 24)
77
+  shimmerGeom.rotateX(-Math.PI / 2)
78
+  const shimmerMaterial = new THREE.MeshBasicMaterial({
79
+    color: 0x6bc4d8,
80
+    transparent: true,
81
+    opacity: 0.25
82
+  })
83
+  const shimmer = new THREE.Mesh(shimmerGeom, shimmerMaterial)
84
+  shimmer.position.set(radius * 0.15, 0.01, radius * 0.15)
85
+  group.add(shimmer)
86
+
87
+  // Main highlight (sun reflection)
88
+  const highlightGeom = new THREE.CircleGeometry(radius * 0.25, 16)
7489
   highlightGeom.rotateX(-Math.PI / 2)
7590
   const highlightMaterial = new THREE.MeshBasicMaterial({
76
-    color: 0x88d4e8,
91
+    color: 0xa8e8f8,
7792
     transparent: true,
78
-    opacity: 0.4
93
+    opacity: 0.5
7994
   })
8095
   const highlight = new THREE.Mesh(highlightGeom, highlightMaterial)
8196
   highlight.position.set(-radius * 0.35, 0.02, -radius * 0.35)
8297
   group.add(highlight)
8398
 
99
+  // Small sparkle highlights
100
+  const sparkles = []
101
+  const sparkleMaterial = new THREE.MeshBasicMaterial({
102
+    color: 0xffffff,
103
+    transparent: true,
104
+    opacity: 0.7
105
+  })
106
+  for (let i = 0; i < 6; i++) {
107
+    const sparkleGeom = new THREE.CircleGeometry(0.08 + Math.random() * 0.06, 6)
108
+    sparkleGeom.rotateX(-Math.PI / 2)
109
+    const sparkle = new THREE.Mesh(sparkleGeom, sparkleMaterial.clone())
110
+    const angle = Math.random() * Math.PI * 2
111
+    const dist = Math.random() * radius * 0.8
112
+    sparkle.position.set(
113
+      Math.cos(angle) * dist,
114
+      0.03,
115
+      Math.sin(angle) * dist
116
+    )
117
+    sparkle.userData.baseX = sparkle.position.x
118
+    sparkle.userData.baseZ = sparkle.position.z
119
+    sparkle.userData.phase = Math.random() * Math.PI * 2
120
+    group.add(sparkle)
121
+    sparkles.push(sparkle)
122
+  }
123
+
84124
   // Grass tufts around the pond
85125
   const grassTuftGeom = new THREE.ConeGeometry(0.15, 0.3, 4)
86126
   const grassMaterial = new THREE.MeshToonMaterial({
@@ -396,9 +436,39 @@ export function createPond(scene, gradientMap) {
396436
   let smokeSpawnTimer = 0
397437
 
398438
   function update(delta, elapsed) {
439
+    // Animate water surface waves
440
+    const positions = waterGeom.attributes.position.array
441
+    for (let i = 0; i < positions.length; i += 3) {
442
+      const x = waterPositions[i]
443
+      const z = waterPositions[i + 2]
444
+      const dist = Math.sqrt(x * x + z * z)
445
+
446
+      // Gentle concentric waves from center
447
+      const wave1 = Math.sin(dist * 1.5 - elapsed * 2) * 0.03
448
+      // Cross-wave pattern
449
+      const wave2 = Math.sin(x * 0.8 + elapsed * 1.5) * Math.cos(z * 0.8 + elapsed * 1.2) * 0.02
450
+
451
+      positions[i + 1] = waterPositions[i + 1] + wave1 + wave2
452
+    }
453
+    waterGeom.attributes.position.needsUpdate = true
454
+
399455
     // Animate water highlight
400
-    highlight.position.x = -radius * 0.35 + Math.sin(elapsed * 0.5) * 0.2
401
-    highlight.position.z = -radius * 0.35 + Math.cos(elapsed * 0.5) * 0.2
456
+    highlight.position.x = -radius * 0.35 + Math.sin(elapsed * 0.5) * 0.3
457
+    highlight.position.z = -radius * 0.35 + Math.cos(elapsed * 0.5) * 0.3
458
+    highlight.material.opacity = 0.4 + Math.sin(elapsed * 2) * 0.1
459
+
460
+    // Animate shimmer
461
+    shimmer.position.x = radius * 0.15 + Math.cos(elapsed * 0.4) * 0.2
462
+    shimmer.position.z = radius * 0.15 + Math.sin(elapsed * 0.3) * 0.2
463
+    shimmer.material.opacity = 0.2 + Math.sin(elapsed * 1.5 + 1) * 0.1
464
+
465
+    // Animate sparkles - twinkle effect
466
+    for (const sparkle of sparkles) {
467
+      const twinkle = Math.sin(elapsed * 4 + sparkle.userData.phase)
468
+      sparkle.material.opacity = twinkle > 0.3 ? 0.8 : 0
469
+      sparkle.position.x = sparkle.userData.baseX + Math.sin(elapsed * 0.5 + sparkle.userData.phase) * 0.1
470
+      sparkle.position.z = sparkle.userData.baseZ + Math.cos(elapsed * 0.5 + sparkle.userData.phase) * 0.1
471
+    }
402472
 
403473
     // Update ripples
404474
     for (let i = ripples.length - 1; i >= 0; i--) {