Implement visibility clipping and segmenting logic for spline road meshes
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
sampleTerrainHeightAtWorldPosition,
|
sampleTerrainHeightAtWorldPosition,
|
||||||
type Terrain
|
type Terrain
|
||||||
} from "../document/terrains";
|
} from "../document/terrains";
|
||||||
|
import type { SplineCorridorPathClipInterval } from "../spline-corridor/spline-corridor-clips";
|
||||||
|
|
||||||
export interface SplineRoadPathPointLike {
|
export interface SplineRoadPathPointLike {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -93,6 +94,10 @@ function addVec3(left: Vec3, right: Vec3): Vec3 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lerp(start: number, end: number, alpha: number): number {
|
||||||
|
return start + (end - start) * alpha;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizePathPoint(
|
function normalizePathPoint(
|
||||||
point: SplineRoadPathPointLike,
|
point: SplineRoadPathPointLike,
|
||||||
index: number
|
index: number
|
||||||
@@ -216,6 +221,118 @@ function buildRoadStations(
|
|||||||
return stations;
|
return stations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function interpolateStation(
|
||||||
|
start: RoadStation,
|
||||||
|
end: RoadStation,
|
||||||
|
distance: number
|
||||||
|
): RoadStation {
|
||||||
|
const length = end.distance - start.distance;
|
||||||
|
const alpha = length <= 1e-8 ? 0 : (distance - start.distance) / length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
center: {
|
||||||
|
x: lerp(start.center.x, end.center.x, alpha),
|
||||||
|
y: lerp(start.center.y, end.center.y, alpha),
|
||||||
|
z: lerp(start.center.z, end.center.z, alpha)
|
||||||
|
},
|
||||||
|
distance,
|
||||||
|
tangent: cloneVec3(start.tangent)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function subtractClipIntervalsFromRange(
|
||||||
|
startDistance: number,
|
||||||
|
endDistance: number,
|
||||||
|
clipIntervals: readonly SplineCorridorPathClipInterval[]
|
||||||
|
): Array<{ startDistance: number; endDistance: number }> {
|
||||||
|
let ranges = [{ startDistance, endDistance }];
|
||||||
|
|
||||||
|
for (const clipInterval of clipIntervals) {
|
||||||
|
const nextRanges: typeof ranges = [];
|
||||||
|
|
||||||
|
for (const range of ranges) {
|
||||||
|
if (
|
||||||
|
clipInterval.endDistance <= range.startDistance ||
|
||||||
|
clipInterval.startDistance >= range.endDistance
|
||||||
|
) {
|
||||||
|
nextRanges.push(range);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clipInterval.startDistance > range.startDistance) {
|
||||||
|
nextRanges.push({
|
||||||
|
startDistance: range.startDistance,
|
||||||
|
endDistance: Math.min(clipInterval.startDistance, range.endDistance)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clipInterval.endDistance < range.endDistance) {
|
||||||
|
nextRanges.push({
|
||||||
|
startDistance: Math.max(clipInterval.endDistance, range.startDistance),
|
||||||
|
endDistance: range.endDistance
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges = nextRanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges.filter(
|
||||||
|
(range) => range.endDistance - range.startDistance > 1e-8
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVisibleRoadStationRuns(
|
||||||
|
stations: readonly RoadStation[],
|
||||||
|
clipIntervals: readonly SplineCorridorPathClipInterval[]
|
||||||
|
): RoadStation[][] {
|
||||||
|
if (clipIntervals.length === 0) {
|
||||||
|
return [[...stations]];
|
||||||
|
}
|
||||||
|
|
||||||
|
const runs: RoadStation[][] = [];
|
||||||
|
let currentRun: RoadStation[] = [];
|
||||||
|
|
||||||
|
for (let index = 0; index < stations.length - 1; index += 1) {
|
||||||
|
const start = stations[index]!;
|
||||||
|
const end = stations[index + 1]!;
|
||||||
|
|
||||||
|
if (end.distance - start.distance <= 1e-8) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibleRanges = subtractClipIntervalsFromRange(
|
||||||
|
start.distance,
|
||||||
|
end.distance,
|
||||||
|
clipIntervals
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const range of visibleRanges) {
|
||||||
|
const rangeStart = interpolateStation(start, end, range.startDistance);
|
||||||
|
const rangeEnd = interpolateStation(start, end, range.endDistance);
|
||||||
|
const previous = currentRun[currentRun.length - 1];
|
||||||
|
|
||||||
|
if (
|
||||||
|
previous === undefined ||
|
||||||
|
Math.abs(previous.distance - rangeStart.distance) > 1e-6
|
||||||
|
) {
|
||||||
|
if (currentRun.length >= 2) {
|
||||||
|
runs.push(currentRun);
|
||||||
|
}
|
||||||
|
currentRun = [rangeStart];
|
||||||
|
}
|
||||||
|
|
||||||
|
currentRun.push(rangeEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentRun.length >= 2) {
|
||||||
|
runs.push(currentRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
return runs;
|
||||||
|
}
|
||||||
|
|
||||||
function createGeometryFromMeshData(meshData: SplineRoadMeshData) {
|
function createGeometryFromMeshData(meshData: SplineRoadMeshData) {
|
||||||
const geometry = new BufferGeometry();
|
const geometry = new BufferGeometry();
|
||||||
geometry.setAttribute(
|
geometry.setAttribute(
|
||||||
@@ -274,17 +391,23 @@ function buildEdgeProfile(options: {
|
|||||||
function addEdgeProfileCaps(options: {
|
function addEdgeProfileCaps(options: {
|
||||||
indices: number[];
|
indices: number[];
|
||||||
profileVertexCount: number;
|
profileVertexCount: number;
|
||||||
|
vertexOffset: number;
|
||||||
stationCount: number;
|
stationCount: number;
|
||||||
}) {
|
}) {
|
||||||
if (options.profileVertexCount < 3 || options.stationCount < 2) {
|
if (options.profileVertexCount < 3 || options.stationCount < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastRow = (options.stationCount - 1) * options.profileVertexCount;
|
const lastRow =
|
||||||
|
options.vertexOffset + (options.stationCount - 1) * options.profileVertexCount;
|
||||||
|
|
||||||
for (let index = 1; index < options.profileVertexCount - 1; index += 1) {
|
for (let index = 1; index < options.profileVertexCount - 1; index += 1) {
|
||||||
// Start cap faces backward along the spline; end cap faces forward.
|
// Start cap faces backward along the spline; end cap faces forward.
|
||||||
options.indices.push(0, index + 1, index);
|
options.indices.push(
|
||||||
|
options.vertexOffset,
|
||||||
|
options.vertexOffset + index + 1,
|
||||||
|
options.vertexOffset + index
|
||||||
|
);
|
||||||
options.indices.push(lastRow, lastRow + index, lastRow + index + 1);
|
options.indices.push(lastRow, lastRow + index, lastRow + index + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user