zeroed-some/dougk / 806496f

Browse files

add ollie

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
806496ff6c861919111450aa9d457d8cd8367545
Parents
4d9e005
Tree
66575d9

2 changed files

StatusFile+-
M src/renderers/three/index.js 6 2
A src/renderers/three/octopus.js 348 0
src/renderers/three/index.jsmodified
@@ -9,10 +9,11 @@ import { createPond } from './pond.js'
99
 import { BreadManager } from './bread.js'
1010
 import { createDonny } from './narwhal.js'
1111
 import { createKoiSchool } from './koi.js'
12
+import { createOllie } from './octopus.js'
1213
 import { unlockAudio } from './sounds.js'
1314
 
1415
 let scene, camera, renderer, composer, outlinePass
15
-let doug, pond, breadManager, donny, koiSchool
16
+let doug, pond, breadManager, donny, koiSchool, ollie
1617
 let clock
1718
 let animationId = null
1819
 
@@ -93,6 +94,7 @@ export function start(container) {
9394
   breadManager = new BreadManager(scene, toonGradient)
9495
   donny = createDonny(scene, toonGradient)
9596
   koiSchool = createKoiSchool(scene, toonGradient, pond.radius)
97
+  ollie = createOllie(scene, toonGradient)
9698
 
9799
   // Post-processing
98100
   composer = new EffectComposer(renderer)
@@ -108,7 +110,7 @@ export function start(container) {
108110
   outlinePass.edgeThickness = 1.5
109111
   outlinePass.visibleEdgeColor.set(0x191410)
110112
   outlinePass.hiddenEdgeColor.set(0x191410)
111
-  outlinePass.selectedObjects = [doug.group, pond.group, donny.group, koiSchool.group]
113
+  outlinePass.selectedObjects = [doug.group, pond.group, donny.group, koiSchool.group, ollie.group]
112114
   composer.addPass(outlinePass)
113115
   composer.addPass(new OutputPass())
114116
 
@@ -169,6 +171,7 @@ function animate() {
169171
   pond.update(delta, elapsed)
170172
   donny.update(delta, elapsed, pond, doug)
171173
   koiSchool.update(delta, elapsed)
174
+  ollie.update(delta, elapsed, pond, doug)
172175
 
173176
   composer.render()
174177
 }
@@ -198,6 +201,7 @@ export function stop() {
198201
   pond = null
199202
   donny = null
200203
   koiSchool = null
204
+  ollie = null
201205
   breadManager = null
202206
 }
203207
 
src/renderers/three/octopus.jsadded
@@ -0,0 +1,348 @@
1
+// Ollie the Octopus - curious inspector of the pond
2
+import * as THREE from 'three'
3
+
4
+export function createOllie(scene, gradientMap) {
5
+  const group = new THREE.Group()
6
+
7
+  // Color palette - purple theme
8
+  const bodyColor = 0x7b4b94 // Deep purple
9
+  const bellyColor = 0xb89bc9 // Lighter lavender
10
+  const suckerColor = 0xd4a5c9 // Pink-ish
11
+  const glassRimColor = 0xd4af37 // Gold
12
+
13
+  // Materials
14
+  const bodyMaterial = new THREE.MeshToonMaterial({
15
+    color: bodyColor,
16
+    gradientMap: gradientMap
17
+  })
18
+
19
+  const bellyMaterial = new THREE.MeshToonMaterial({
20
+    color: bellyColor,
21
+    gradientMap: gradientMap
22
+  })
23
+
24
+  const suckerMaterial = new THREE.MeshToonMaterial({
25
+    color: suckerColor,
26
+    gradientMap: gradientMap
27
+  })
28
+
29
+  const glassRimMaterial = new THREE.MeshToonMaterial({
30
+    color: glassRimColor,
31
+    gradientMap: gradientMap
32
+  })
33
+
34
+  const glassMaterial = new THREE.MeshBasicMaterial({
35
+    color: 0x88ccff,
36
+    transparent: true,
37
+    opacity: 0.3
38
+  })
39
+
40
+  // Head/Mantle - bulbous dome
41
+  const mantleGeom = new THREE.SphereGeometry(0.5, 10, 8)
42
+  mantleGeom.scale(1.2, 1.4, 1.0)
43
+  const mantle = new THREE.Mesh(mantleGeom, bodyMaterial)
44
+  mantle.position.y = 0.3
45
+  group.add(mantle)
46
+
47
+  // Lower mantle/body connector
48
+  const lowerMantleGeom = new THREE.SphereGeometry(0.4, 8, 6)
49
+  lowerMantleGeom.scale(1.1, 0.8, 1.0)
50
+  const lowerMantle = new THREE.Mesh(lowerMantleGeom, bellyMaterial)
51
+  lowerMantle.position.y = -0.1
52
+  group.add(lowerMantle)
53
+
54
+  // Eyes - big and expressive Wind Waker style
55
+  const eyeGeom = new THREE.SphereGeometry(0.12, 8, 6)
56
+  const eyeWhiteMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff })
57
+  const pupilMaterial = new THREE.MeshBasicMaterial({ color: 0x1a1a1a })
58
+
59
+  // Left eye
60
+  const leftEyeWhite = new THREE.Mesh(eyeGeom, eyeWhiteMaterial)
61
+  leftEyeWhite.position.set(0.25, 0.35, 0.35)
62
+  leftEyeWhite.scale.set(1, 1.2, 0.8)
63
+  group.add(leftEyeWhite)
64
+
65
+  const leftPupilGeom = new THREE.SphereGeometry(0.06, 6, 4)
66
+  const leftPupil = new THREE.Mesh(leftPupilGeom, pupilMaterial)
67
+  leftPupil.position.set(0.32, 0.35, 0.4)
68
+  group.add(leftPupil)
69
+
70
+  // Right eye
71
+  const rightEyeWhite = new THREE.Mesh(eyeGeom, eyeWhiteMaterial)
72
+  rightEyeWhite.position.set(0.25, 0.35, -0.35)
73
+  rightEyeWhite.scale.set(1, 1.2, 0.8)
74
+  group.add(rightEyeWhite)
75
+
76
+  const rightPupil = new THREE.Mesh(leftPupilGeom, pupilMaterial)
77
+  rightPupil.position.set(0.32, 0.35, -0.4)
78
+  group.add(rightPupil)
79
+
80
+  // Eye shines
81
+  const shineGeom = new THREE.SphereGeometry(0.03, 6, 4)
82
+  const shineMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff })
83
+
84
+  const leftShine = new THREE.Mesh(shineGeom, shineMaterial)
85
+  leftShine.position.set(0.35, 0.4, 0.38)
86
+  group.add(leftShine)
87
+
88
+  const rightShine = new THREE.Mesh(shineGeom, shineMaterial)
89
+  rightShine.position.set(0.35, 0.4, -0.38)
90
+  group.add(rightShine)
91
+
92
+  // Create 8 tentacles
93
+  const tentacles = []
94
+  const tentacleGroup = new THREE.Group()
95
+  tentacleGroup.position.y = -0.3
96
+
97
+  for (let i = 0; i < 8; i++) {
98
+    const angle = (i / 8) * Math.PI * 2
99
+    const tentacle = createTentacle(bodyMaterial, suckerMaterial, gradientMap)
100
+    tentacle.position.x = Math.cos(angle) * 0.35
101
+    tentacle.position.z = Math.sin(angle) * 0.35
102
+    tentacle.rotation.y = -angle + Math.PI / 2
103
+    // Splay outward slightly
104
+    tentacle.rotation.z = 0.3
105
+    tentacles.push(tentacle)
106
+    tentacleGroup.add(tentacle)
107
+  }
108
+
109
+  group.add(tentacleGroup)
110
+
111
+  // Magnifying glass - attached to front-right tentacle (index 1)
112
+  const magGlassGroup = new THREE.Group()
113
+
114
+  // Handle
115
+  const handleGeom = new THREE.CylinderGeometry(0.02, 0.025, 0.3, 6)
116
+  const handle = new THREE.Mesh(handleGeom, glassRimMaterial)
117
+  handle.rotation.z = Math.PI / 2
118
+  handle.position.x = -0.15
119
+  magGlassGroup.add(handle)
120
+
121
+  // Rim
122
+  const rimGeom = new THREE.TorusGeometry(0.15, 0.02, 8, 16)
123
+  const rim = new THREE.Mesh(rimGeom, glassRimMaterial)
124
+  magGlassGroup.add(rim)
125
+
126
+  // Glass lens
127
+  const lensGeom = new THREE.CircleGeometry(0.14, 16)
128
+  const lens = new THREE.Mesh(lensGeom, glassMaterial)
129
+  lens.position.z = 0.01
130
+  magGlassGroup.add(lens)
131
+
132
+  // Position magnifying glass at end of front-right tentacle
133
+  magGlassGroup.position.set(0.9, -0.5, -0.3)
134
+  magGlassGroup.rotation.y = -0.5
135
+  group.add(magGlassGroup)
136
+
137
+  // Ollie starts hidden below the water
138
+  group.position.y = -3
139
+  group.visible = false
140
+
141
+  scene.add(group)
142
+
143
+  // State
144
+  const state = {
145
+    mode: 'waiting',
146
+    timer: 45 + Math.random() * 30, // First appearance in 45-75 seconds (after narwhal)
147
+    emergeX: 0,
148
+    emergeZ: 0,
149
+    surfaceTime: 0
150
+  }
151
+
152
+  function createTentacle(bodyMat, suckerMat, gradient) {
153
+    const tentacleObj = new THREE.Group()
154
+
155
+    // 3 segments, getting smaller
156
+    const segments = [
157
+      { radius: 0.08, length: 0.35 },
158
+      { radius: 0.06, length: 0.3 },
159
+      { radius: 0.04, length: 0.25 }
160
+    ]
161
+
162
+    let yOffset = 0
163
+    segments.forEach((seg, idx) => {
164
+      const segGeom = new THREE.CylinderGeometry(seg.radius * 0.7, seg.radius, seg.length, 6)
165
+      const segMesh = new THREE.Mesh(segGeom, bodyMat)
166
+      segMesh.position.y = yOffset - seg.length / 2
167
+      tentacleObj.add(segMesh)
168
+
169
+      // Add suckers on underside (only first two segments)
170
+      if (idx < 2) {
171
+        for (let s = 0; s < 2; s++) {
172
+          const suckerGeom = new THREE.SphereGeometry(0.015, 4, 4)
173
+          const sucker = new THREE.Mesh(suckerGeom, suckerMat)
174
+          sucker.position.set(-seg.radius * 0.8, yOffset - seg.length * 0.3 - s * 0.12, 0)
175
+          sucker.scale.set(1, 0.5, 1)
176
+          tentacleObj.add(sucker)
177
+        }
178
+      }
179
+
180
+      yOffset -= seg.length
181
+    })
182
+
183
+    // Curly tip
184
+    const tipGeom = new THREE.SphereGeometry(0.03, 6, 4)
185
+    tipGeom.scale(1, 1.5, 1)
186
+    const tip = new THREE.Mesh(tipGeom, bodyMat)
187
+    tip.position.y = yOffset - 0.03
188
+    tentacleObj.add(tip)
189
+
190
+    return tentacleObj
191
+  }
192
+
193
+  function startRumble(pond) {
194
+    state.mode = 'rumbling'
195
+    state.timer = 0
196
+
197
+    // Pick random spot in outer zone of pond (70-90% radius)
198
+    const angle = Math.random() * Math.PI * 2
199
+    const dist = Math.random() * pond.radius * 0.2 + pond.radius * 0.7
200
+    state.emergeX = Math.cos(angle) * dist
201
+    state.emergeZ = Math.sin(angle) * dist
202
+
203
+    group.position.x = state.emergeX
204
+    group.position.z = state.emergeZ
205
+    group.rotation.y = angle + Math.PI / 2
206
+  }
207
+
208
+  // Helper to smoothly interpolate angles
209
+  function lerpAngle(from, to, t) {
210
+    let diff = to - from
211
+    while (diff > Math.PI) diff -= Math.PI * 2
212
+    while (diff < -Math.PI) diff += Math.PI * 2
213
+    return from + diff * t
214
+  }
215
+
216
+  function update(delta, elapsed, pond, doug) {
217
+    state.timer += delta
218
+
219
+    // Calculate angle to face Doug
220
+    let angleToDoug = 0
221
+    if (doug) {
222
+      const dougPos = doug.getPosition()
223
+      const dx = dougPos.x - group.position.x
224
+      const dz = dougPos.z - group.position.z
225
+      angleToDoug = Math.atan2(dx, dz)
226
+    }
227
+
228
+    // Animate tentacles (always, when visible)
229
+    if (group.visible) {
230
+      tentacles.forEach((t, i) => {
231
+        const phase = i * (Math.PI / 4)
232
+        // Wave motion
233
+        t.rotation.x = 0.3 + Math.sin(elapsed * 2 + phase) * 0.25
234
+        t.rotation.z = 0.3 + Math.cos(elapsed * 1.5 + phase) * 0.15
235
+      })
236
+
237
+      // Magnifying glass sway
238
+      magGlassGroup.rotation.z = Math.sin(elapsed * 3) * 0.15
239
+      magGlassGroup.rotation.x = Math.sin(elapsed * 2.5) * 0.1
240
+    }
241
+
242
+    switch (state.mode) {
243
+      case 'waiting':
244
+        if (state.timer >= 75) {
245
+          startRumble(pond)
246
+        }
247
+        break
248
+
249
+      case 'rumbling':
250
+        // Create rumble ripples
251
+        if (state.timer < 2) {
252
+          if (Math.random() < delta * 6) {
253
+            const rx = state.emergeX + (Math.random() - 0.5) * 1.0
254
+            const rz = state.emergeZ + (Math.random() - 0.5) * 1.0
255
+            pond.addRipple(rx, rz)
256
+          }
257
+        } else {
258
+          state.mode = 'emerging'
259
+          state.timer = 0
260
+          group.visible = true
261
+          group.position.y = -2
262
+          group.rotation.y = angleToDoug
263
+        }
264
+        break
265
+
266
+      case 'emerging':
267
+        const emergeProgress = Math.min(state.timer / 1.8, 1)
268
+        const easeOut = 1 - Math.pow(1 - emergeProgress, 3)
269
+        group.position.y = -2 + easeOut * 2.2
270
+
271
+        // Slowly turn toward Doug - curious inspection
272
+        group.rotation.y = lerpAngle(group.rotation.y, angleToDoug, delta * 0.6)
273
+
274
+        // Gentle wobble during emerge
275
+        group.rotation.x = Math.sin(state.timer * 3) * 0.05
276
+        group.rotation.z = Math.cos(state.timer * 2.5) * 0.04
277
+
278
+        if (emergeProgress >= 1) {
279
+          state.mode = 'surfaced'
280
+          state.timer = 0
281
+          state.surfaceTime = 5 + Math.random() * 3 // Stay 5-8 seconds
282
+        }
283
+        break
284
+
285
+      case 'surfaced':
286
+        // Bob gently
287
+        group.position.y = 0.2 + Math.sin(elapsed * 2) * 0.05
288
+
289
+        // Track Doug with magnifying glass - curious inspection!
290
+        group.rotation.y = lerpAngle(group.rotation.y, angleToDoug, delta * 0.4)
291
+
292
+        // Gentle rocking
293
+        group.rotation.x = Math.sin(elapsed * 1.2) * 0.03
294
+        group.rotation.z = Math.cos(elapsed * 1.0) * 0.02
295
+
296
+        // Extra curious magnifying glass wobble when pointed at Doug
297
+        magGlassGroup.rotation.y = -0.5 + Math.sin(elapsed * 4) * 0.1
298
+
299
+        // Occasional ripples
300
+        if (Math.random() < delta * 0.4) {
301
+          pond.addRipple(
302
+            group.position.x + (Math.random() - 0.5) * 0.6,
303
+            group.position.z + (Math.random() - 0.5) * 0.6
304
+          )
305
+        }
306
+
307
+        if (state.timer >= state.surfaceTime) {
308
+          state.mode = 'submerging'
309
+          state.timer = 0
310
+        }
311
+        break
312
+
313
+      case 'submerging':
314
+        const submergeProgress = Math.min(state.timer / 1.5, 1)
315
+        const easeIn = Math.pow(submergeProgress, 2)
316
+        group.position.y = 0.2 - easeIn * 2.5
317
+
318
+        // Tentacles curl inward as submerging
319
+        tentacles.forEach((t, i) => {
320
+          const phase = i * (Math.PI / 4)
321
+          t.rotation.x = 0.3 + easeIn * 0.5 + Math.sin(elapsed * 2 + phase) * 0.15
322
+        })
323
+
324
+        // Add ripples as submerging
325
+        if (Math.random() < delta * 5) {
326
+          pond.addRipple(
327
+            group.position.x + (Math.random() - 0.5) * 0.8,
328
+            group.position.z + (Math.random() - 0.5) * 0.8
329
+          )
330
+        }
331
+
332
+        if (submergeProgress >= 1) {
333
+          state.mode = 'waiting'
334
+          state.timer = 0
335
+          group.visible = false
336
+          group.position.y = -3
337
+          group.rotation.x = 0
338
+          group.rotation.z = 0
339
+        }
340
+        break
341
+    }
342
+  }
343
+
344
+  return {
345
+    group,
346
+    update
347
+  }
348
+}