diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index 4915063b..0b068d8d 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -77,7 +77,10 @@ import { buildTerrainLodMeshData, resolveTerrainLodLevelIndexWithHysteresis } from "../geometry/terrain-mesh"; -import { buildSplineRoadMeshGeometry } from "../geometry/spline-road-mesh"; +import { + buildSplineRoadEdgeMeshGeometry, + buildSplineRoadMeshGeometry +} from "../geometry/spline-road-mesh"; import { createStarterMaterialSignature, createStarterMaterialTextureSet, diff --git a/src/viewport-three/viewport-host.ts b/src/viewport-three/viewport-host.ts index 08a1bd95..bd747e64 100644 --- a/src/viewport-three/viewport-host.ts +++ b/src/viewport-three/viewport-host.ts @@ -7356,25 +7356,45 @@ export class ViewportHost { return null; } - const geometry = buildSplineRoadMeshGeometry({ + const surfaceGeometry = buildSplineRoadMeshGeometry({ path, terrains }); + const meshes: Array> = []; - if (geometry === null) { - return null; + if (surfaceGeometry !== null) { + const mesh = new Mesh(surfaceGeometry, this.createRoadSurfaceMaterial(path)); + mesh.castShadow = false; + mesh.receiveShadow = true; + mesh.userData.pathId = path.id; + mesh.userData.nonPickable = true; + applyRendererRenderCategoryFromMaterial(mesh); + meshes.push(mesh); } - const mesh = new Mesh(geometry, this.createRoadSurfaceMaterial(path)); - mesh.castShadow = false; - mesh.receiveShadow = true; - mesh.userData.pathId = path.id; - mesh.userData.nonPickable = true; - applyRendererRenderCategoryFromMaterial(mesh); + for (const side of ["left", "right"] as const) { + const edgeGeometry = buildSplineRoadEdgeMeshGeometry({ + path, + side, + terrains + }); - return { - mesh - }; + if (edgeGeometry === null) { + continue; + } + + const edge = path.road.edges[side]; + const edgeMesh = new Mesh(edgeGeometry, this.createRoadEdgeMaterial(edge)); + edgeMesh.castShadow = false; + edgeMesh.receiveShadow = true; + edgeMesh.userData.pathId = path.id; + edgeMesh.userData.roadEdgeSide = side; + edgeMesh.userData.nonPickable = true; + applyRendererRenderCategoryFromMaterial(edgeMesh); + meshes.push(edgeMesh); + } + + return meshes.length === 0 ? null : { meshes }; } private rebuildRoadSurfaces(document: SceneDocument) { @@ -7390,7 +7410,9 @@ export class ViewportHost { continue; } - this.roadSurfaceGroup.add(renderObjects.mesh); + for (const mesh of renderObjects.meshes) { + this.roadSurfaceGroup.add(mesh); + } this.roadSurfaceRenderObjects.set(path.id, renderObjects); } @@ -7401,9 +7423,11 @@ export class ViewportHost { const existingRenderObjects = this.roadSurfaceRenderObjects.get(path.id); if (existingRenderObjects !== undefined) { - this.roadSurfaceGroup.remove(existingRenderObjects.mesh); - existingRenderObjects.mesh.geometry.dispose(); - existingRenderObjects.mesh.material.dispose(); + for (const mesh of existingRenderObjects.meshes) { + this.roadSurfaceGroup.remove(mesh); + mesh.geometry.dispose(); + mesh.material.dispose(); + } this.roadSurfaceRenderObjects.delete(path.id); } @@ -7423,7 +7447,9 @@ export class ViewportHost { return; } - this.roadSurfaceGroup.add(nextRenderObjects.mesh); + for (const mesh of nextRenderObjects.meshes) { + this.roadSurfaceGroup.add(mesh); + } this.roadSurfaceRenderObjects.set(path.id, nextRenderObjects); this.applyShadowState(); }