diff --git a/src/geometry/spline-corridor-junction-mesh.ts b/src/geometry/spline-corridor-junction-mesh.ts index be17b4f0..bd51c2b5 100644 --- a/src/geometry/spline-corridor-junction-mesh.ts +++ b/src/geometry/spline-corridor-junction-mesh.ts @@ -1,49 +1,18 @@ import { BufferGeometry, Float32BufferAttribute } from "three"; import type { Vec3 } from "../core/vector"; -import { - resolveScenePath, - sampleResolvedScenePathPosition, - type ScenePathCurveMode, - type ScenePathPoint, - type ScenePathRoadSettings -} from "../document/paths"; import type { SplineCorridorJunction } from "../document/spline-corridor-junctions"; import { sampleTerrainHeightAtWorldPosition, type Terrain } from "../document/terrains"; +import { + buildSplineCorridorJunctionFootprint, + type SplineCorridorJunctionFootprintPathLike +} from "./spline-corridor-junction-footprint"; -export interface SplineCorridorJunctionMeshPathLike { - id: string; - loop: boolean; - curveMode?: ScenePathCurveMode; - sampledResolution?: number; - glueToTerrain?: boolean; - terrainOffset?: number; - road: ScenePathRoadSettings; - points: ReadonlyArray<{ - id?: string; - pointId?: string; - position: Vec3; - }>; -} - -const JUNCTION_SEGMENTS = 32; - -function normalizePathPoint( - point: SplineCorridorJunctionMeshPathLike["points"][number], - index: number -): ScenePathPoint { - return { - id: point.id ?? point.pointId ?? `junction-path-point-${index}`, - position: { - x: point.position.x, - y: point.position.y, - z: point.position.z - } - }; -} +export type SplineCorridorJunctionMeshPathLike = + SplineCorridorJunctionFootprintPathLike; function sampleHighestTerrainWorldY( terrains: readonly Terrain[], @@ -73,53 +42,6 @@ function sampleHighestTerrainWorldY( return highestWorldY; } -function resolveJunctionPath( - path: SplineCorridorJunctionMeshPathLike, - terrains: readonly Terrain[] -) { - return resolveScenePath( - { - loop: path.loop, - curveMode: path.curveMode, - sampledResolution: path.sampledResolution, - glueToTerrain: path.glueToTerrain, - terrainOffset: path.terrainOffset, - points: path.points.map(normalizePathPoint) - }, - { terrains } - ); -} - -function resolveFallbackY(options: { - junction: SplineCorridorJunction; - paths: readonly SplineCorridorJunctionMeshPathLike[]; - terrains: readonly Terrain[]; -}): number { - const sampledPositions = options.junction.connections - .map((connection) => { - const path = - options.paths.find((candidate) => candidate.id === connection.pathId) ?? - null; - - if (path === null) { - return null; - } - - const resolvedPath = resolveJunctionPath(path, options.terrains); - return sampleResolvedScenePathPosition(resolvedPath, connection.progress); - }) - .filter((position): position is Vec3 => position !== null); - - if (sampledPositions.length === 0) { - return options.junction.center.y; - } - - return ( - sampledPositions.reduce((sum, position) => sum + position.y, 0) / - sampledPositions.length - ); -} - function shouldConformToTerrain(options: { junction: SplineCorridorJunction; paths: readonly SplineCorridorJunctionMeshPathLike[]; @@ -140,43 +62,73 @@ export function buildSplineCorridorJunctionMeshGeometry(options: { }): BufferGeometry | null { const { junction, paths } = options; const terrains = options.terrains ?? []; + const footprint = buildSplineCorridorJunctionFootprint({ + junction, + paths, + terrains + }); - if (!junction.enabled || !junction.visible || junction.radius <= 0) { + if (footprint === null) { return null; } const conformToTerrain = shouldConformToTerrain({ junction, paths }); - const fallbackY = resolveFallbackY({ junction, paths, terrains }); const positions: number[] = []; const uvs: number[] = []; const indices: number[] = []; - function resolveY(position: Vec3): number { + function resolveY(position: Vec3, fallbackY: number, heightOffset: number): number { const terrainWorldY = conformToTerrain ? sampleHighestTerrainWorldY(terrains, position) : null; - return terrainWorldY ?? fallbackY; + return terrainWorldY === null ? fallbackY : terrainWorldY + heightOffset; } - positions.push(junction.center.x, resolveY(junction.center), junction.center.z); - uvs.push(0.5, 0.5); - - for (let index = 0; index < JUNCTION_SEGMENTS; index += 1) { - const angle = (index / JUNCTION_SEGMENTS) * Math.PI * 2; - const position = { - x: junction.center.x + Math.cos(angle) * junction.radius, - y: junction.center.y, - z: junction.center.z + Math.sin(angle) * junction.radius - }; - - positions.push(position.x, resolveY(position), position.z); - uvs.push(0.5 + Math.cos(angle) * 0.5, 0.5 + Math.sin(angle) * 0.5); + function pushUv(x: number, z: number) { + const deltaX = x - footprint.center.x; + const deltaZ = z - footprint.center.z; + uvs.push( + deltaX * footprint.rightAxis.x + deltaZ * footprint.rightAxis.z, + deltaX * footprint.forwardAxis.x + deltaZ * footprint.forwardAxis.z + ); } - for (let index = 0; index < JUNCTION_SEGMENTS; index += 1) { + positions.push( + footprint.center.x, + resolveY( + { + x: footprint.center.x, + y: junction.center.y, + z: footprint.center.z + }, + footprint.center.fallbackY, + footprint.center.heightOffset + ), + footprint.center.z + ); + pushUv(footprint.center.x, footprint.center.z); + + for (const point of footprint.points) { + positions.push( + point.x, + resolveY( + { + x: point.x, + y: point.fallbackY, + z: point.z + }, + point.fallbackY, + point.heightOffset + ), + point.z + ); + pushUv(point.x, point.z); + } + + for (let index = 0; index < footprint.points.length; index += 1) { const current = index + 1; - const next = index === JUNCTION_SEGMENTS - 1 ? 1 : current + 1; + const next = index === footprint.points.length - 1 ? 1 : current + 1; indices.push(0, current, next); }