JavaScript · 5855 bytes Raw Blame History
1 // Pond environment - 3D water, shore, and fence with Wind Waker styling
2 import * as THREE from 'three'
3
4 export function createPond(scene, gradientMap) {
5 const group = new THREE.Group()
6 const radius = 4
7
8 // Colors
9 const waterColor = 0x46a0be // Vibrant teal
10 const waterDeep = 0x2d7a94 // Darker teal
11 const shoreColor = 0x78b456 // Bright grass green
12 const sandColor = 0xc8b080 // Sandy edge
13 const fenceColor = 0xb4823c // Warm wood
14
15 // Materials
16 const waterMaterial = new THREE.MeshToonMaterial({
17 color: waterColor,
18 gradientMap: gradientMap,
19 transparent: true,
20 opacity: 0.9
21 })
22
23 const shoreMaterial = new THREE.MeshToonMaterial({
24 color: shoreColor,
25 gradientMap: gradientMap
26 })
27
28 const sandMaterial = new THREE.MeshToonMaterial({
29 color: sandColor,
30 gradientMap: gradientMap
31 })
32
33 const fenceMaterial = new THREE.MeshToonMaterial({
34 color: fenceColor,
35 gradientMap: gradientMap
36 })
37
38 // Ground plane (grass)
39 const groundGeom = new THREE.CircleGeometry(radius + 2.5, 32)
40 groundGeom.rotateX(-Math.PI / 2)
41 const ground = new THREE.Mesh(groundGeom, shoreMaterial)
42 ground.position.y = -0.05
43 group.add(ground)
44
45 // Sandy shore ring
46 const sandGeom = new THREE.RingGeometry(radius - 0.2, radius + 0.5, 32)
47 sandGeom.rotateX(-Math.PI / 2)
48 const sand = new THREE.Mesh(sandGeom, sandMaterial)
49 sand.position.y = -0.02
50 group.add(sand)
51
52 // Water surface
53 const waterGeom = new THREE.CircleGeometry(radius, 32)
54 waterGeom.rotateX(-Math.PI / 2)
55 const water = new THREE.Mesh(waterGeom, waterMaterial)
56 water.position.y = 0
57 group.add(water)
58
59 // Water depth visual (darker center)
60 const deepGeom = new THREE.CircleGeometry(radius * 0.6, 24)
61 deepGeom.rotateX(-Math.PI / 2)
62 const deepMaterial = new THREE.MeshToonMaterial({
63 color: waterDeep,
64 gradientMap: gradientMap,
65 transparent: true,
66 opacity: 0.5
67 })
68 const deep = new THREE.Mesh(deepGeom, deepMaterial)
69 deep.position.y = -0.01
70 group.add(deep)
71
72 // Water highlight (light reflection)
73 const highlightGeom = new THREE.CircleGeometry(radius * 0.3, 16)
74 highlightGeom.rotateX(-Math.PI / 2)
75 const highlightMaterial = new THREE.MeshBasicMaterial({
76 color: 0x88d4e8,
77 transparent: true,
78 opacity: 0.4
79 })
80 const highlight = new THREE.Mesh(highlightGeom, highlightMaterial)
81 highlight.position.set(-radius * 0.35, 0.02, -radius * 0.35)
82 group.add(highlight)
83
84 // Grass tufts around the pond
85 const grassTuftGeom = new THREE.ConeGeometry(0.15, 0.3, 4)
86 const grassMaterial = new THREE.MeshToonMaterial({
87 color: 0x4a8530,
88 gradientMap: gradientMap
89 })
90
91 for (let i = 0; i < 30; i++) {
92 const angle = Math.random() * Math.PI * 2
93 const dist = radius + 0.8 + Math.random() * 1.5
94
95 const tuft = new THREE.Mesh(grassTuftGeom, grassMaterial)
96 tuft.position.set(
97 Math.cos(angle) * dist,
98 0.1,
99 Math.sin(angle) * dist
100 )
101 tuft.rotation.x = (Math.random() - 0.5) * 0.3
102 tuft.rotation.z = (Math.random() - 0.5) * 0.3
103 tuft.scale.setScalar(0.5 + Math.random() * 0.5)
104 group.add(tuft)
105 }
106
107 // Rickety fence
108 const fenceGroup = new THREE.Group()
109 const fenceX = radius + 1
110 const postCount = 5
111 const postSpacing = 0.8
112
113 for (let i = 0; i < postCount; i++) {
114 const wobble = Math.sin(i * 1.5) * 0.1
115
116 // Fence post
117 const postGeom = new THREE.BoxGeometry(0.12, 0.8, 0.12)
118 const post = new THREE.Mesh(postGeom, fenceMaterial)
119 post.position.set(
120 fenceX + wobble,
121 0.35,
122 -1.5 + i * postSpacing
123 )
124 post.rotation.x = wobble * 0.3
125 post.rotation.z = wobble * 0.5
126 fenceGroup.add(post)
127
128 // Post cap
129 const capGeom = new THREE.BoxGeometry(0.16, 0.06, 0.16)
130 const cap = new THREE.Mesh(capGeom, fenceMaterial)
131 cap.position.set(
132 fenceX + wobble,
133 0.78,
134 -1.5 + i * postSpacing
135 )
136 cap.rotation.x = wobble * 0.3
137 cap.rotation.z = wobble * 0.5
138 fenceGroup.add(cap)
139 }
140
141 // Horizontal rails
142 const railGeom = new THREE.BoxGeometry(0.08, 0.08, postSpacing * (postCount - 1) + 0.3)
143
144 const topRail = new THREE.Mesh(railGeom, fenceMaterial)
145 topRail.position.set(fenceX + 0.05, 0.6, -1.5 + (postCount - 1) * postSpacing / 2)
146 topRail.rotation.y = 0.02
147 fenceGroup.add(topRail)
148
149 const bottomRail = new THREE.Mesh(railGeom, fenceMaterial)
150 bottomRail.position.set(fenceX - 0.03, 0.25, -1.5 + (postCount - 1) * postSpacing / 2)
151 bottomRail.rotation.y = -0.03
152 fenceGroup.add(bottomRail)
153
154 group.add(fenceGroup)
155
156 // Ripple system
157 const ripples = []
158 const rippleGeom = new THREE.RingGeometry(0.1, 0.15, 16)
159 rippleGeom.rotateX(-Math.PI / 2)
160
161 function addRipple(x, z) {
162 const rippleMaterial = new THREE.MeshBasicMaterial({
163 color: 0xffffff,
164 transparent: true,
165 opacity: 0.6,
166 side: THREE.DoubleSide
167 })
168 const ripple = new THREE.Mesh(rippleGeom.clone(), rippleMaterial)
169 ripple.position.set(x, 0.02, z)
170
171 group.add(ripple)
172 ripples.push({
173 mesh: ripple,
174 age: 0,
175 maxAge: 1.5
176 })
177 }
178
179 function update(delta, elapsed) {
180 // Animate water highlight
181 highlight.position.x = -radius * 0.35 + Math.sin(elapsed * 0.5) * 0.2
182 highlight.position.z = -radius * 0.35 + Math.cos(elapsed * 0.5) * 0.2
183
184 // Update ripples
185 for (let i = ripples.length - 1; i >= 0; i--) {
186 const ripple = ripples[i]
187 ripple.age += delta
188
189 const progress = ripple.age / ripple.maxAge
190 ripple.mesh.scale.setScalar(1 + progress * 3)
191 ripple.mesh.material.opacity = 0.6 * (1 - progress)
192
193 if (ripple.age >= ripple.maxAge) {
194 group.remove(ripple.mesh)
195 ripple.mesh.geometry.dispose()
196 ripple.mesh.material.dispose()
197 ripples.splice(i, 1)
198 }
199 }
200 }
201
202 scene.add(group)
203
204 return {
205 group,
206 water,
207 radius,
208 addRipple,
209 update
210 }
211 }