JavaScript · 13325 bytes Raw Blame History
1 // Building mesh factories for dougk
2 // Creates 3D meshes for placeable buildings
3
4 import * as THREE from 'three'
5
6 // Create a wooden dock
7 export function createDock(gradientMap) {
8 const group = new THREE.Group()
9
10 const woodMaterial = new THREE.MeshToonMaterial({
11 color: 0x8b6914,
12 gradientMap
13 })
14
15 const darkWoodMaterial = new THREE.MeshToonMaterial({
16 color: 0x5c4a1a,
17 gradientMap
18 })
19
20 // Main platform
21 const platformGeom = new THREE.BoxGeometry(1.2, 0.08, 0.6)
22 const platform = new THREE.Mesh(platformGeom, woodMaterial)
23 platform.position.y = 0.04
24 group.add(platform)
25
26 // Planks (detail lines)
27 for (let i = 0; i < 5; i++) {
28 const plankGeom = new THREE.BoxGeometry(0.02, 0.09, 0.58)
29 const plank = new THREE.Mesh(plankGeom, darkWoodMaterial)
30 plank.position.set(-0.5 + i * 0.25, 0.045, 0)
31 group.add(plank)
32 }
33
34 // Support posts
35 const postGeom = new THREE.CylinderGeometry(0.04, 0.05, 0.4, 6)
36 const postPositions = [
37 { x: -0.5, z: 0.25 },
38 { x: -0.5, z: -0.25 },
39 { x: 0.5, z: 0.25 },
40 { x: 0.5, z: -0.25 }
41 ]
42
43 for (const pos of postPositions) {
44 const post = new THREE.Mesh(postGeom, darkWoodMaterial)
45 post.position.set(pos.x, -0.15, pos.z)
46 group.add(post)
47 }
48
49 return group
50 }
51
52 // Create a fishing hut
53 export function createFishingHut(gradientMap) {
54 const group = new THREE.Group()
55
56 const woodMaterial = new THREE.MeshToonMaterial({
57 color: 0x9b7653,
58 gradientMap
59 })
60
61 const roofMaterial = new THREE.MeshToonMaterial({
62 color: 0x654321,
63 gradientMap
64 })
65
66 const windowMaterial = new THREE.MeshBasicMaterial({
67 color: 0x87ceeb,
68 transparent: true,
69 opacity: 0.6
70 })
71
72 // Base/floor
73 const baseGeom = new THREE.BoxGeometry(0.9, 0.06, 0.7)
74 const base = new THREE.Mesh(baseGeom, woodMaterial)
75 base.position.y = 0.03
76 group.add(base)
77
78 // Walls
79 const wallGeom = new THREE.BoxGeometry(0.85, 0.5, 0.65)
80 const walls = new THREE.Mesh(wallGeom, woodMaterial)
81 walls.position.y = 0.31
82 group.add(walls)
83
84 // Roof
85 const roofGeom = new THREE.ConeGeometry(0.55, 0.35, 4)
86 const roof = new THREE.Mesh(roofGeom, roofMaterial)
87 roof.position.y = 0.73
88 roof.rotation.y = Math.PI / 4
89 group.add(roof)
90
91 // Window
92 const windowGeom = new THREE.PlaneGeometry(0.15, 0.15)
93 const window1 = new THREE.Mesh(windowGeom, windowMaterial)
94 window1.position.set(0.43, 0.35, 0)
95 window1.rotation.y = Math.PI / 2
96 group.add(window1)
97
98 // Door frame
99 const doorGeom = new THREE.BoxGeometry(0.02, 0.35, 0.2)
100 const door = new THREE.Mesh(doorGeom, roofMaterial)
101 door.position.set(0.43, 0.24, 0)
102 group.add(door)
103
104 return group
105 }
106
107 // Create a mini lighthouse
108 export function createLighthouse(gradientMap) {
109 const group = new THREE.Group()
110
111 const whiteMaterial = new THREE.MeshToonMaterial({
112 color: 0xf5f5f5,
113 gradientMap
114 })
115
116 const redMaterial = new THREE.MeshToonMaterial({
117 color: 0xcc3333,
118 gradientMap
119 })
120
121 const glassMaterial = new THREE.MeshBasicMaterial({
122 color: 0xffffaa,
123 transparent: true,
124 opacity: 0.8
125 })
126
127 // Base
128 const baseGeom = new THREE.CylinderGeometry(0.25, 0.3, 0.15, 8)
129 const base = new THREE.Mesh(baseGeom, whiteMaterial)
130 base.position.y = 0.075
131 group.add(base)
132
133 // Tower - alternating stripes
134 const stripeHeight = 0.2
135 for (let i = 0; i < 4; i++) {
136 const stripeGeom = new THREE.CylinderGeometry(
137 0.18 - i * 0.02,
138 0.2 - i * 0.02,
139 stripeHeight,
140 8
141 )
142 const stripe = new THREE.Mesh(stripeGeom, i % 2 === 0 ? whiteMaterial : redMaterial)
143 stripe.position.y = 0.25 + i * stripeHeight
144 group.add(stripe)
145 }
146
147 // Lamp housing
148 const housingGeom = new THREE.CylinderGeometry(0.12, 0.1, 0.15, 8)
149 const housing = new THREE.Mesh(housingGeom, redMaterial)
150 housing.position.y = 1.02
151 group.add(housing)
152
153 // Glass/light
154 const glassGeom = new THREE.SphereGeometry(0.08, 8, 6)
155 const glass = new THREE.Mesh(glassGeom, glassMaterial)
156 glass.position.y = 1.0
157 group.add(glass)
158
159 // Light beam (animated)
160 const beamGroup = new THREE.Group()
161 beamGroup.position.y = 1.0
162
163 const beamMaterial = new THREE.MeshBasicMaterial({
164 color: 0xffffaa,
165 transparent: true,
166 opacity: 0.3,
167 side: THREE.DoubleSide
168 })
169
170 // Create a cone-shaped beam - tip at lamp, wide end extending outward
171 const beamGeom = new THREE.ConeGeometry(0.8, 2.5, 8, 1, true)
172 beamGeom.rotateX(-Math.PI / 2) // Tip toward -Z, base toward +Z
173 beamGeom.translate(0, 0, 1.25) // Move so tip is at origin (lamp), base extends outward
174 const beam = new THREE.Mesh(beamGeom, beamMaterial)
175 beamGroup.add(beam)
176
177 // Mark for animation
178 beamGroup.userData.isLightBeam = true
179 group.add(beamGroup)
180
181 // Roof cap
182 const capGeom = new THREE.ConeGeometry(0.14, 0.12, 8)
183 const cap = new THREE.Mesh(capGeom, redMaterial)
184 cap.position.y = 1.15
185 group.add(cap)
186
187 // Mark group as lighthouse for animation
188 group.userData.isLighthouse = true
189
190 return group
191 }
192
193 // Create reed cluster
194 export function createReeds(gradientMap) {
195 const group = new THREE.Group()
196
197 const reedMaterial = new THREE.MeshToonMaterial({
198 color: 0x4a7c3f,
199 gradientMap
200 })
201
202 const tipMaterial = new THREE.MeshToonMaterial({
203 color: 0x8b7355,
204 gradientMap
205 })
206
207 // Create 5-7 reeds
208 const reedCount = 5 + Math.floor(Math.random() * 3)
209 const reeds = []
210
211 for (let i = 0; i < reedCount; i++) {
212 const height = 0.4 + Math.random() * 0.3
213 const angle = (i / reedCount) * Math.PI * 2 + Math.random() * 0.5
214 const dist = Math.random() * 0.15
215
216 // Reed group (stalk + tip together for swaying)
217 const reedGroup = new THREE.Group()
218 reedGroup.position.set(
219 Math.cos(angle) * dist,
220 0,
221 Math.sin(angle) * dist
222 )
223
224 // Reed stalk
225 const stalkGeom = new THREE.CylinderGeometry(0.015, 0.02, height, 4)
226 const stalk = new THREE.Mesh(stalkGeom, reedMaterial)
227 stalk.position.y = height / 2 - 0.1
228 reedGroup.add(stalk)
229
230 // Cattail tip
231 const tipGeom = new THREE.CylinderGeometry(0.03, 0.025, 0.1, 6)
232 const tip = new THREE.Mesh(tipGeom, tipMaterial)
233 tip.position.y = height - 0.05
234 reedGroup.add(tip)
235
236 // Mark for animation with random phase
237 reedGroup.userData.isReed = true
238 reedGroup.userData.phase = Math.random() * Math.PI * 2
239 reedGroup.userData.baseRotX = (Math.random() - 0.5) * 0.2
240 reedGroup.userData.baseRotZ = (Math.random() - 0.5) * 0.2
241
242 reeds.push(reedGroup)
243 group.add(reedGroup)
244 }
245
246 // Mark group as reeds cluster for animation
247 group.userData.isReeds = true
248 group.userData.reedChildren = reeds
249
250 return group
251 }
252
253 // Create fence segment
254 export function createFence(gradientMap) {
255 const group = new THREE.Group()
256
257 const woodMaterial = new THREE.MeshToonMaterial({
258 color: 0xa0826d,
259 gradientMap
260 })
261
262 // Two posts
263 const postGeom = new THREE.BoxGeometry(0.06, 0.4, 0.06)
264
265 const post1 = new THREE.Mesh(postGeom, woodMaterial)
266 post1.position.set(-0.25, 0.15, 0)
267 group.add(post1)
268
269 const post2 = new THREE.Mesh(postGeom, woodMaterial)
270 post2.position.set(0.25, 0.15, 0)
271 group.add(post2)
272
273 // Pointed tops
274 const pointGeom = new THREE.ConeGeometry(0.04, 0.08, 4)
275
276 const point1 = new THREE.Mesh(pointGeom, woodMaterial)
277 point1.position.set(-0.25, 0.39, 0)
278 group.add(point1)
279
280 const point2 = new THREE.Mesh(pointGeom, woodMaterial)
281 point2.position.set(0.25, 0.39, 0)
282 group.add(point2)
283
284 // Cross beams
285 const beamGeom = new THREE.BoxGeometry(0.5, 0.04, 0.03)
286
287 const beam1 = new THREE.Mesh(beamGeom, woodMaterial)
288 beam1.position.set(0, 0.25, 0)
289 group.add(beam1)
290
291 const beam2 = new THREE.Mesh(beamGeom, woodMaterial)
292 beam2.position.set(0, 0.1, 0)
293 group.add(beam2)
294
295 return group
296 }
297
298 // Create a giant onion house
299 export function createOnionHouse(gradientMap) {
300 const group = new THREE.Group()
301
302 // Onion colors - layered purples and whites
303 const outerSkinMaterial = new THREE.MeshToonMaterial({
304 color: 0x8b668b, // Dusty purple outer skin
305 gradientMap
306 })
307
308 const innerSkinMaterial = new THREE.MeshToonMaterial({
309 color: 0xdda0dd, // Lighter purple inner layer peeking through
310 gradientMap
311 })
312
313 const rootMaterial = new THREE.MeshToonMaterial({
314 color: 0xd2b48c, // Tan roots
315 gradientMap
316 })
317
318 const doorMaterial = new THREE.MeshToonMaterial({
319 color: 0x4a3728, // Dark wood door
320 gradientMap
321 })
322
323 const chimneyMaterial = new THREE.MeshToonMaterial({
324 color: 0x8b7355, // Stone chimney
325 gradientMap
326 })
327
328 // Main onion body - bulbous bottom
329 const bulbGeom = new THREE.SphereGeometry(0.5, 10, 8)
330 bulbGeom.scale(1, 0.85, 1)
331 const bulb = new THREE.Mesh(bulbGeom, outerSkinMaterial)
332 bulb.position.y = 0.4
333 group.add(bulb)
334
335 // Onion top/neck tapering up
336 const neckGeom = new THREE.CylinderGeometry(0.15, 0.35, 0.4, 8)
337 const neck = new THREE.Mesh(neckGeom, outerSkinMaterial)
338 neck.position.y = 0.95
339 group.add(neck)
340
341 // Dried top sprout/tip
342 const tipGeom = new THREE.ConeGeometry(0.08, 0.25, 6)
343 const tip = new THREE.Mesh(tipGeom, rootMaterial)
344 tip.position.y = 1.27
345 tip.rotation.z = 0.15 // Slight lean for whimsy
346 group.add(tip)
347
348 // Peeling skin detail (decorative flaps)
349 const peelGeom = new THREE.PlaneGeometry(0.2, 0.35)
350 const peel1 = new THREE.Mesh(peelGeom, innerSkinMaterial)
351 peel1.position.set(0.45, 0.5, 0.15)
352 peel1.rotation.y = -0.4
353 peel1.rotation.z = 0.3
354 group.add(peel1)
355
356 const peel2 = new THREE.Mesh(peelGeom, innerSkinMaterial)
357 peel2.position.set(-0.35, 0.6, 0.3)
358 peel2.rotation.y = 0.6
359 peel2.rotation.z = -0.2
360 group.add(peel2)
361
362 // Root tendrils at the bottom
363 for (let i = 0; i < 5; i++) {
364 const angle = (i / 5) * Math.PI * 2 + Math.random() * 0.3
365 const rootGeom = new THREE.CylinderGeometry(0.02, 0.01, 0.15 + Math.random() * 0.1, 4)
366 const root = new THREE.Mesh(rootGeom, rootMaterial)
367 root.position.set(
368 Math.cos(angle) * 0.15,
369 -0.02,
370 Math.sin(angle) * 0.15
371 )
372 root.rotation.x = (Math.random() - 0.5) * 0.4
373 root.rotation.z = (Math.random() - 0.5) * 0.4
374 group.add(root)
375 }
376
377 // Door - cute rounded top
378 const doorGroup = new THREE.Group()
379
380 // Door frame (arch)
381 const doorFrameGeom = new THREE.BoxGeometry(0.22, 0.35, 0.05)
382 const doorFrame = new THREE.Mesh(doorFrameGeom, doorMaterial)
383 doorFrame.position.y = 0.175
384 doorGroup.add(doorFrame)
385
386 // Door arch top
387 const archGeom = new THREE.SphereGeometry(0.11, 8, 4, 0, Math.PI * 2, 0, Math.PI / 2)
388 const arch = new THREE.Mesh(archGeom, doorMaterial)
389 arch.position.y = 0.35
390 arch.rotation.x = Math.PI
391 doorGroup.add(arch)
392
393 // Door knob
394 const knobGeom = new THREE.SphereGeometry(0.02, 6, 4)
395 const knobMaterial = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
396 const knob = new THREE.Mesh(knobGeom, knobMaterial)
397 knob.position.set(0.07, 0.2, 0.03)
398 doorGroup.add(knob)
399
400 doorGroup.position.set(0.48, 0.02, 0)
401 doorGroup.rotation.y = Math.PI / 2
402 group.add(doorGroup)
403
404 // Chimney - whimsically placed on the side/top
405 const chimneyGroup = new THREE.Group()
406
407 const chimneyBaseGeom = new THREE.CylinderGeometry(0.06, 0.07, 0.3, 6)
408 const chimneyBase = new THREE.Mesh(chimneyBaseGeom, chimneyMaterial)
409 chimneyBase.position.y = 0.15
410 chimneyGroup.add(chimneyBase)
411
412 // Chimney cap
413 const capGeom = new THREE.CylinderGeometry(0.08, 0.06, 0.04, 6)
414 const cap = new THREE.Mesh(capGeom, chimneyMaterial)
415 cap.position.y = 0.32
416 chimneyGroup.add(cap)
417
418 // Position chimney at a jaunty angle on the onion
419 chimneyGroup.position.set(-0.25, 0.75, 0.2)
420 chimneyGroup.rotation.z = 0.3 // Tilted for whimsy
421 chimneyGroup.rotation.x = -0.15
422 group.add(chimneyGroup)
423
424 // Little window
425 const windowGeom = new THREE.CircleGeometry(0.08, 8)
426 const windowMaterial = new THREE.MeshBasicMaterial({
427 color: 0xffffcc,
428 transparent: true,
429 opacity: 0.7
430 })
431 const windowMesh = new THREE.Mesh(windowGeom, windowMaterial)
432 windowMesh.position.set(0.1, 0.55, 0.49)
433 group.add(windowMesh)
434
435 // Window frame
436 const windowFrameGeom = new THREE.TorusGeometry(0.08, 0.012, 4, 12)
437 const windowFrame = new THREE.Mesh(windowFrameGeom, doorMaterial)
438 windowFrame.position.set(0.1, 0.55, 0.485)
439 group.add(windowFrame)
440
441 return group
442 }
443
444 // Factory function to create building by type
445 export function createBuilding(type, gradientMap) {
446 switch (type) {
447 case 'dock_wooden':
448 return createDock(gradientMap)
449 case 'fishing_hut':
450 return createFishingHut(gradientMap)
451 case 'lighthouse':
452 return createLighthouse(gradientMap)
453 case 'reeds':
454 return createReeds(gradientMap)
455 case 'fence':
456 return createFence(gradientMap)
457 case 'onion_house':
458 return createOnionHouse(gradientMap)
459 default:
460 console.warn('Unknown building type:', type)
461 return new THREE.Group()
462 }
463 }
464
465 // Create ghost (preview) version of a building
466 export function createGhostBuilding(type, gradientMap, isValid) {
467 const building = createBuilding(type, gradientMap)
468
469 // Make all materials transparent and tinted
470 const color = isValid ? 0x44ff44 : 0xff4444
471 const opacity = 0.5
472
473 building.traverse((child) => {
474 if (child.isMesh) {
475 child.material = new THREE.MeshBasicMaterial({
476 color,
477 transparent: true,
478 opacity
479 })
480 }
481 })
482
483 return building
484 }