Skip to content

Commit 7d5719b

Browse files
committed
Add yard models and fence layout
1 parent a5a9810 commit 7d5719b

3 files changed

Lines changed: 138 additions & 5 deletions

File tree

public/assets/yard/Fence.glb

16.4 KB
Binary file not shown.

public/assets/yard/House.glb

233 KB
Binary file not shown.

src/game/world/OwnerGoal.ts

Lines changed: 138 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,18 @@ export class OwnerGoal {
3232
private colliders: any[] = []
3333
private ownerRoot: Group | null = null
3434
private ownerMixer: AnimationMixer | null = null
35+
private ownerBaseOffsetY = 0
36+
private yardFallback: Group | null = null
37+
private houseRoot: Group | null = null
38+
private fenceRoot: Group | null = null
3539

3640
constructor(
3741
private readonly terrain: Terrain,
3842
private readonly physics: Physics,
3943
) {
40-
this.build()
44+
this.buildFallbackYard()
4145
void this.loadOwnerModel()
46+
void this.loadYardModels()
4247
this.reset()
4348
}
4449

@@ -75,7 +80,16 @@ export class OwnerGoal {
7580

7681
if (this.ownerRoot) {
7782
this.ownerRoot.position.copy(this.humanOffset)
78-
this.ownerRoot.position.y = this.ownerPosition.y - this.group.position.y
83+
this.ownerRoot.position.y = this.ownerPosition.y - this.group.position.y + this.ownerBaseOffsetY
84+
}
85+
86+
if (this.houseRoot) {
87+
this.houseRoot.position.copy(this.houseOffset)
88+
}
89+
90+
if (this.fenceRoot) {
91+
this.fenceRoot.position.set(0, 0, 0)
92+
this.updateFenceToTerrain()
7993
}
8094

8195
placed = true
@@ -93,7 +107,7 @@ export class OwnerGoal {
93107
return this.group.position.clone()
94108
}
95109

96-
private build() {
110+
private buildFallbackYard() {
97111
const fenceMat = new MeshStandardMaterial({ color: 0x7c5a34, roughness: 0.9 })
98112
const fencePostGeo = new BoxGeometry(0.3, 1.3, 0.3)
99113
const fenceRailGeo = new BoxGeometry(this.yardSize, 0.22, 0.18)
@@ -171,7 +185,8 @@ export class OwnerGoal {
171185
porch.position.set(this.houseOffset.x, 0.35, this.houseOffset.z + 3.2)
172186
porch.receiveShadow = true
173187

174-
this.group.add(
188+
this.yardFallback = new Group()
189+
this.yardFallback.add(
175190
fenceGroup,
176191
houseBase,
177192
roof,
@@ -182,6 +197,8 @@ export class OwnerGoal {
182197
porch,
183198
)
184199

200+
this.group.add(this.yardFallback)
201+
185202
this.colliderDefs.push(
186203
{
187204
offset: new Vector3(this.houseOffset.x, 1.7, this.houseOffset.z),
@@ -247,8 +264,10 @@ export class OwnerGoal {
247264
}
248265

249266
const boxAfter = new Box3().setFromObject(root)
250-
root.position.y += -boxAfter.min.y
267+
const baseOffset = -boxAfter.min.y
268+
root.position.y += baseOffset
251269

270+
this.ownerBaseOffsetY = baseOffset
252271
this.ownerRoot = root
253272
this.ownerMixer = gltf.animations.length ? new AnimationMixer(root) : null
254273
if (this.ownerMixer && gltf.animations[0]) {
@@ -261,6 +280,120 @@ export class OwnerGoal {
261280
}
262281
}
263282

283+
private async loadYardModels() {
284+
try {
285+
const loader = new GLTFLoader()
286+
const [houseGltf, fenceGltf] = await Promise.all([
287+
loader.loadAsync(`${import.meta.env.BASE_URL}assets/yard/House.glb`),
288+
loader.loadAsync(`${import.meta.env.BASE_URL}assets/yard/Fence.glb`),
289+
])
290+
291+
const house = houseGltf.scene
292+
const fence = fenceGltf.scene
293+
294+
this.prepareYardModel(house)
295+
this.prepareYardModel(fence)
296+
297+
const houseBounds = this.groundModel(house)
298+
const fenceBounds = new Box3().setFromObject(fence)
299+
300+
const houseScale = this.scaleModelToWidth(houseBounds, 6.2)
301+
house.scale.setScalar(houseScale)
302+
this.groundModel(house)
303+
304+
const fenceGroup = this.buildFencePerimeter(fence, fenceBounds)
305+
306+
house.position.copy(this.houseOffset)
307+
308+
this.houseRoot = house
309+
this.fenceRoot = fenceGroup
310+
311+
if (this.yardFallback) {
312+
this.yardFallback.visible = false
313+
}
314+
315+
this.group.add(fenceGroup, house)
316+
this.updateFenceToTerrain()
317+
this.rebuildColliders()
318+
} catch (error) {
319+
console.warn('Yard model load failed', error)
320+
}
321+
}
322+
323+
private prepareYardModel(root: Group) {
324+
root.traverse((obj) => {
325+
const mesh = obj as any
326+
if (mesh.isMesh) {
327+
mesh.castShadow = true
328+
mesh.receiveShadow = true
329+
}
330+
})
331+
}
332+
333+
private groundModel(root: Group) {
334+
const bounds = new Box3().setFromObject(root)
335+
root.position.y += -bounds.min.y
336+
return bounds
337+
}
338+
339+
private scaleModelToWidth(bounds: Box3, targetWidth: number) {
340+
const size = bounds.getSize(new Vector3())
341+
const width = Math.max(size.x, size.z)
342+
if (width > 0.0001) {
343+
const scale = targetWidth / width
344+
return scale
345+
}
346+
return 1
347+
}
348+
349+
private buildFencePerimeter(fenceRoot: Group, bounds: Box3) {
350+
const fenceGroup = new Group()
351+
const fenceScale = this.scaleModelToWidth(bounds, this.yardSize)
352+
353+
const openingFraction = 0.28
354+
const gapStart = this.yardSize * 0.5 - this.yardSize * openingFraction
355+
356+
const createFenceSection = (position: Vector3, yaw: number) => {
357+
const section = fenceRoot.clone(true)
358+
section.scale.setScalar(fenceScale)
359+
section.position.copy(position)
360+
section.rotation.y = yaw
361+
this.prepareYardModel(section)
362+
this.groundModel(section)
363+
fenceGroup.add(section)
364+
}
365+
366+
// North side (leave opening on the east end).
367+
if (gapStart > -this.yardSize * 0.5) {
368+
createFenceSection(new Vector3((gapStart - this.yardSize * 0.5) * 0.5, 0, this.yardSize / 2), 0)
369+
}
370+
371+
// South side.
372+
createFenceSection(new Vector3(0, 0, -this.yardSize / 2), 0)
373+
374+
// West side.
375+
createFenceSection(new Vector3(-this.yardSize / 2, 0, 0), Math.PI / 2)
376+
377+
// East side (leave opening on the north end).
378+
if (gapStart > -this.yardSize * 0.5) {
379+
createFenceSection(new Vector3(this.yardSize / 2, 0, (gapStart - this.yardSize * 0.5) * 0.5), Math.PI / 2)
380+
}
381+
382+
return fenceGroup
383+
}
384+
385+
private updateFenceToTerrain() {
386+
if (!this.fenceRoot) return
387+
388+
for (const child of this.fenceRoot.children) {
389+
const section = child as Group
390+
const worldPos = new Vector3()
391+
section.getWorldPosition(worldPos)
392+
const groundY = this.terrain.getHeightAt(worldPos.x, worldPos.z)
393+
section.position.y = groundY - this.group.position.y
394+
}
395+
}
396+
264397
private rebuildColliders() {
265398
const { RAPIER, world } = this.physics
266399

0 commit comments

Comments
 (0)