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) {
59
   chest.position.set(0.25, 0.32, 0)
59
   chest.position.set(0.25, 0.32, 0)
60
   group.add(chest)
60
   group.add(chest)
61
 
61
 
62
-  // Scraggly feather tufts on body
62
+  // Subtle feather tufts on body (toned down)
63
   const tuftMaterial = bodyMaterial
63
   const tuftMaterial = bodyMaterial
64
   const featherPositions = [
64
   const featherPositions = [
65
-    { x: -0.4, y: 0.45, z: 0.15, rx: 0.3, rz: 0.5 },
65
+    { x: -0.38, y: 0.44, z: 0.1, rx: 0.2, rz: 0.3 },
66
-    { x: -0.45, y: 0.4, z: -0.12, rx: -0.2, rz: 0.4 },
66
+    { x: -0.38, y: 0.44, z: -0.1, rx: -0.2, rz: 0.3 },
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 },
70
   ]
67
   ]
71
 
68
 
72
   for (const f of featherPositions) {
69
   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)
74
     const feather = new THREE.Mesh(featherGeom, tuftMaterial)
71
     const feather = new THREE.Mesh(featherGeom, tuftMaterial)
75
     feather.position.set(f.x, f.y, f.z)
72
     feather.position.set(f.x, f.y, f.z)
76
     feather.rotation.x = f.rx
73
     feather.rotation.x = f.rx
@@ -78,18 +75,16 @@ export function createDoug(scene, gradientMap) {
78
     group.add(feather)
75
     group.add(feather)
79
   }
76
   }
80
 
77
 
81
-  // Tail feathers - more prominent and scraggly
78
+  // Tail feathers - subtle curl up
82
   const tailGroup = new THREE.Group()
79
   const tailGroup = new THREE.Group()
83
   const tailFeathers = [
80
   const tailFeathers = [
84
-    { x: -0.58, y: 0.38, z: 0, rx: 1.8, rz: 0, scale: 1.2 },
81
+    { x: -0.55, y: 0.36, z: 0, rx: 1.6, rz: 0, scale: 1.0 },
85
-    { x: -0.62, y: 0.42, z: 0.1, rx: 1.6, rz: 0.3, scale: 1.0 },
82
+    { x: -0.52, y: 0.40, z: 0.08, rx: 1.4, rz: 0.2, scale: 0.7 },
86
-    { x: -0.62, y: 0.42, z: -0.1, rx: 1.6, rz: -0.3, scale: 1.0 },
83
+    { x: -0.52, y: 0.40, z: -0.08, rx: 1.4, rz: -0.2, scale: 0.7 },
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 },
89
   ]
84
   ]
90
 
85
 
91
   for (const t of tailFeathers) {
86
   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)
93
     const tail = new THREE.Mesh(tailGeom, bodyMaterial)
88
     const tail = new THREE.Mesh(tailGeom, bodyMaterial)
94
     tail.position.set(t.x, t.y, t.z)
89
     tail.position.set(t.x, t.y, t.z)
95
     tail.rotation.x = t.rx
90
     tail.rotation.x = t.rx
@@ -148,27 +143,20 @@ export function createDoug(scene, gradientMap) {
148
   rightCheek.scale.set(0.8, 0.7, 0.6)
143
   rightCheek.scale.set(0.8, 0.7, 0.6)
149
   group.add(rightCheek)
144
   group.add(rightCheek)
150
 
145
 
151
-  // Head tuft - messier, multiple feathers
146
+  // Head tuft - simple pair of feathers
152
-  const tuftGeom = new THREE.ConeGeometry(0.04, 0.14, 4)
147
+  const tuftGeom = new THREE.ConeGeometry(0.035, 0.12, 4)
153
   const tuft1 = new THREE.Mesh(tuftGeom, bodyMaterial)
148
   const tuft1 = new THREE.Mesh(tuftGeom, bodyMaterial)
154
-  tuft1.position.set(0.35, 0.95, 0)
149
+  tuft1.position.set(0.34, 0.93, 0.03)
155
-  tuft1.rotation.z = -0.4
150
+  tuft1.rotation.z = -0.3
151
+  tuft1.rotation.x = 0.2
156
   group.add(tuft1)
152
   group.add(tuft1)
157
 
153
 
158
   const tuft2 = new THREE.Mesh(tuftGeom, bodyMaterial)
154
   const tuft2 = new THREE.Mesh(tuftGeom, bodyMaterial)
159
-  tuft2.position.set(0.32, 0.92, 0.06)
155
+  tuft2.position.set(0.34, 0.93, -0.03)
160
-  tuft2.rotation.z = -0.2
156
+  tuft2.rotation.z = -0.3
161
-  tuft2.rotation.x = 0.3
157
+  tuft2.rotation.x = -0.2
162
-  tuft2.scale.setScalar(0.8)
163
   group.add(tuft2)
158
   group.add(tuft2)
164
 
159
 
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
-
172
   // Beak - flatter, more duck-like
160
   // Beak - flatter, more duck-like
173
   const beakGeom = new THREE.ConeGeometry(0.09, 0.32, 6)
161
   const beakGeom = new THREE.ConeGeometry(0.09, 0.32, 6)
174
   beakGeom.scale(1, 1, 0.6) // Flatten it
162
   beakGeom.scale(1, 1, 0.6) // Flatten it
@@ -228,7 +216,9 @@ export function createDoug(scene, gradientMap) {
228
     idleTimer: 0,
216
     idleTimer: 0,
229
     nextIdleMove: 3 + Math.random() * 4,
217
     nextIdleMove: 3 + Math.random() * 4,
230
     wobble: 0,
218
     wobble: 0,
231
-    headBob: 0
219
+    headBob: 0,
220
+    rippleTimer: 0,
221
+    isMoving: false
232
   }
222
   }
233
 
223
 
234
   // Movement speeds
224
   // Movement speeds
@@ -323,18 +313,35 @@ export function createDoug(scene, gradientMap) {
323
 
313
 
324
         // Wobble animation while moving
314
         // Wobble animation while moving
325
         state.wobble += delta * 8
315
         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
326
       }
330
       }
327
     } else {
331
     } else {
332
+      state.isMoving = false
328
       // Arrived
333
       // Arrived
329
       if (state.mode === 'swimming') {
334
       if (state.mode === 'swimming') {
330
         state.mode = 'idle'
335
         state.mode = 'idle'
331
-        // Eat nearby bread
336
+        // Eat nearby bread - create splash ripple!
332
         for (const bread of breadBits) {
337
         for (const bread of breadBits) {
333
           if (!bread.eaten) {
338
           if (!bread.eaten) {
334
             const bx = bread.position.x - state.position.x
339
             const bx = bread.position.x - state.position.x
335
             const bz = bread.position.z - state.position.z
340
             const bz = bread.position.z - state.position.z
336
             if (Math.sqrt(bx * bx + bz * bz) < 0.4) {
341
             if (Math.sqrt(bx * bx + bz * bz) < 0.4) {
337
               bread.eaten = true
342
               bread.eaten = true
343
+              // Eating splash ripple
344
+              pond.addRipple(state.position.x, state.position.z)
338
             }
345
             }
339
           }
346
           }
340
         }
347
         }
@@ -356,7 +363,17 @@ export function createDoug(scene, gradientMap) {
356
     group.position.z = state.position.z
363
     group.position.z = state.position.z
357
 
364
 
358
     // Bobbing on water
365
     // 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
+    }
360
 
377
 
361
     // Rotation (face direction of movement)
378
     // Rotation (face direction of movement)
362
     // Duck model faces +X locally, so offset by -PI/2 to align with movement
379
     // 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) {
49
   sand.position.y = -0.02
49
   sand.position.y = -0.02
50
   group.add(sand)
50
   group.add(sand)
51
 
51
 
52
-  // Water surface
52
+  // Water surface - higher resolution for wave animation
53
-  const waterGeom = new THREE.CircleGeometry(radius, 32)
53
+  const waterGeom = new THREE.CircleGeometry(radius, 48, 8)
54
   waterGeom.rotateX(-Math.PI / 2)
54
   waterGeom.rotateX(-Math.PI / 2)
55
   const water = new THREE.Mesh(waterGeom, waterMaterial)
55
   const water = new THREE.Mesh(waterGeom, waterMaterial)
56
   water.position.y = 0
56
   water.position.y = 0
57
   group.add(water)
57
   group.add(water)
58
 
58
 
59
-  // Water depth visual (darker center)
59
+  // Store original water vertex positions for wave animation
60
-  const deepGeom = new THREE.CircleGeometry(radius * 0.6, 24)
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)
61
   deepGeom.rotateX(-Math.PI / 2)
64
   deepGeom.rotateX(-Math.PI / 2)
62
   const deepMaterial = new THREE.MeshToonMaterial({
65
   const deepMaterial = new THREE.MeshToonMaterial({
63
     color: waterDeep,
66
     color: waterDeep,
64
     gradientMap: gradientMap,
67
     gradientMap: gradientMap,
65
     transparent: true,
68
     transparent: true,
66
-    opacity: 0.5
69
+    opacity: 0.6
67
   })
70
   })
68
   const deep = new THREE.Mesh(deepGeom, deepMaterial)
71
   const deep = new THREE.Mesh(deepGeom, deepMaterial)
69
-  deep.position.y = -0.01
72
+  deep.position.y = -0.02
70
   group.add(deep)
73
   group.add(deep)
71
 
74
 
72
-  // Water highlight (light reflection)
75
+  // Secondary highlight shimmer
73
-  const highlightGeom = new THREE.CircleGeometry(radius * 0.3, 16)
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)
74
   highlightGeom.rotateX(-Math.PI / 2)
89
   highlightGeom.rotateX(-Math.PI / 2)
75
   const highlightMaterial = new THREE.MeshBasicMaterial({
90
   const highlightMaterial = new THREE.MeshBasicMaterial({
76
-    color: 0x88d4e8,
91
+    color: 0xa8e8f8,
77
     transparent: true,
92
     transparent: true,
78
-    opacity: 0.4
93
+    opacity: 0.5
79
   })
94
   })
80
   const highlight = new THREE.Mesh(highlightGeom, highlightMaterial)
95
   const highlight = new THREE.Mesh(highlightGeom, highlightMaterial)
81
   highlight.position.set(-radius * 0.35, 0.02, -radius * 0.35)
96
   highlight.position.set(-radius * 0.35, 0.02, -radius * 0.35)
82
   group.add(highlight)
97
   group.add(highlight)
83
 
98
 
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
+
84
   // Grass tufts around the pond
124
   // Grass tufts around the pond
85
   const grassTuftGeom = new THREE.ConeGeometry(0.15, 0.3, 4)
125
   const grassTuftGeom = new THREE.ConeGeometry(0.15, 0.3, 4)
86
   const grassMaterial = new THREE.MeshToonMaterial({
126
   const grassMaterial = new THREE.MeshToonMaterial({
@@ -396,9 +436,39 @@ export function createPond(scene, gradientMap) {
396
   let smokeSpawnTimer = 0
436
   let smokeSpawnTimer = 0
397
 
437
 
398
   function update(delta, elapsed) {
438
   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
+
399
     // Animate water highlight
455
     // Animate water highlight
400
-    highlight.position.x = -radius * 0.35 + Math.sin(elapsed * 0.5) * 0.2
456
+    highlight.position.x = -radius * 0.35 + Math.sin(elapsed * 0.5) * 0.3
401
-    highlight.position.z = -radius * 0.35 + Math.cos(elapsed * 0.5) * 0.2
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
+    }
402
 
472
 
403
     // Update ripples
473
     // Update ripples
404
     for (let i = ripples.length - 1; i >= 0; i--) {
474
     for (let i = ripples.length - 1; i >= 0; i--) {