From 074385ce7805a2ab91edd24d3c4aba64134cce4b Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Wed, 13 May 2026 18:48:20 +0200 Subject: [PATCH] Refactor road mesh generation to integrate junction edge seams --- src/geometry/spline-road-mesh.ts | 76 ++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/src/geometry/spline-road-mesh.ts b/src/geometry/spline-road-mesh.ts index 77a77fcf..7d460e17 100644 --- a/src/geometry/spline-road-mesh.ts +++ b/src/geometry/spline-road-mesh.ts @@ -13,7 +13,10 @@ import { sampleTerrainHeightAtWorldPosition, type Terrain } from "../document/terrains"; -import type { SplineCorridorPathClipInterval } from "../spline-corridor/spline-corridor-clips"; +import type { + SplineCorridorPathClipInterval, + SplineCorridorRoadEdgeSeam +} from "../spline-corridor/spline-corridor-clips"; export interface SplineRoadPathPointLike { id?: string; @@ -431,22 +434,18 @@ function isClipBoundaryDistance( ); } -function expandClipIntervalsForRoadEdge(options: { - clipIntervals: readonly SplineCorridorPathClipInterval[]; - edgeWidth: number; - totalLength: number; -}): SplineCorridorPathClipInterval[] { - if (options.clipIntervals.length === 0 || options.edgeWidth <= 0) { - return [...options.clipIntervals]; - } - - const insetDistance = options.edgeWidth * 0.45; - - return options.clipIntervals.map((clipInterval) => ({ - junctionId: clipInterval.junctionId, - startDistance: Math.max(0, clipInterval.startDistance - insetDistance), - endDistance: Math.min(options.totalLength, clipInterval.endDistance + insetDistance) - })); +function findRoadEdgeSeam(options: { + distance: number; + side: ScenePathRoadEdgeSide; + seams: readonly SplineCorridorRoadEdgeSeam[]; +}): SplineCorridorRoadEdgeSeam | null { + return ( + options.seams.find( + (seam) => + seam.side === options.side && + Math.abs(seam.distance - options.distance) <= 1e-6 + ) ?? null + ); } export function buildSplineRoadMeshData(options: { @@ -550,6 +549,7 @@ export function buildSplineRoadEdgeMeshData(options: { side: ScenePathRoadEdgeSide; terrains?: readonly Terrain[]; clipIntervals?: readonly SplineCorridorPathClipInterval[]; + junctionEdgeSeams?: readonly SplineCorridorRoadEdgeSeam[]; }): SplineRoadEdgeMeshData | null { const { path, side } = options; const edge = path.road.edges[side]; @@ -580,11 +580,7 @@ export function buildSplineRoadEdgeMeshData(options: { const uvs: number[] = []; const indices: number[] = []; const profileLastIndex = Math.max(1, profile.length - 1); - const edgeClipIntervals = expandClipIntervalsForRoadEdge({ - clipIntervals: options.clipIntervals ?? [], - edgeWidth: edge.width, - totalLength: stations[stations.length - 1]?.distance ?? 0 - }); + const edgeClipIntervals = options.clipIntervals ?? []; const stationRuns = buildVisibleRoadStationRuns(stations, edgeClipIntervals); let stationCount = 0; @@ -604,14 +600,40 @@ export function buildSplineRoadEdgeMeshData(options: { y: 0, z: tangent.x }; + const sideSign = side === "left" ? 1 : -1; + const innerOffset = sideSign * path.road.width * 0.5; + const seam = findRoadEdgeSeam({ + distance: station.distance, + side, + seams: options.junctionEdgeSeams ?? [] + }); + const seamInnerPoint = + seam === null + ? null + : addVec3(station.center, { + x: leftDirection.x * innerOffset, + y: 0, + z: leftDirection.z * innerOffset + }); for (let profileIndex = 0; profileIndex < profile.length; profileIndex += 1) { const profilePoint = profile[profileIndex]!; - const edgePoint = addVec3(station.center, { - x: leftDirection.x * profilePoint.offset, - y: 0, - z: leftDirection.z * profilePoint.offset - }); + const profileOffsetRatio = + edge.width <= 1e-8 + ? 0 + : (profilePoint.offset - innerOffset) / (sideSign * edge.width); + const edgePoint = + seam === null || seamInnerPoint === null + ? addVec3(station.center, { + x: leftDirection.x * profilePoint.offset, + y: 0, + z: leftDirection.z * profilePoint.offset + }) + : { + x: seamInnerPoint.x + seam.outerOffset.x * profileOffsetRatio, + y: seamInnerPoint.y, + z: seamInnerPoint.z + seam.outerOffset.z * profileOffsetRatio + }; const vertex = resolveRoadVertexPosition({ path, terrains,