Feature: Add road junction management UI and functionality

This commit is contained in:
2026-05-13 13:27:36 +02:00
parent 21693e9963
commit b477774bbb

View File

@@ -22700,6 +22700,187 @@ export function App({
</div>
);
})}
<div className="form-section">
<div className="label">Road Junctions</div>
<div className="material-summary">
Junctions are visual corridor merges only. They clip
road edges, repeaters, and generated colliders near the
merge without changing NPC or camera path routing.
</div>
{selectedPathJunctionCandidates.length === 0 ? (
<div className="material-summary">
No uncreated road junction candidates for this path.
</div>
) : (
<div className="stacked-list">
{selectedPathJunctionCandidates
.slice(0, 6)
.map((candidate, candidateIndex) => (
<div
className="compact-card"
key={candidate.id}
>
<div className="compact-card__header">
<strong>
Candidate {candidateIndex + 1}
</strong>
<button
className="toolbar__button"
data-testid={`create-spline-corridor-junction-${candidateIndex}`}
type="button"
onClick={() =>
handleCreateSplineCorridorJunction(
candidate
)
}
>
Create Junction
</button>
</div>
<div className="material-summary">
{candidate.connections.length} connections at
X {candidate.center.x.toFixed(2)}, Z{" "}
{candidate.center.z.toFixed(2)}
</div>
</div>
))}
</div>
)}
{selectedPathJunctions.length === 0 ? (
<div className="material-summary">
No persisted junctions are connected to this path.
</div>
) : (
<div className="stacked-list">
{selectedPathJunctions.map((junction) => (
<div className="compact-card" key={junction.id}>
<div className="compact-card__header">
<strong>
{junction.name ?? "Road Junction"}
</strong>
<button
className="toolbar__button toolbar__button--danger"
data-testid={`delete-spline-corridor-junction-${junction.id}`}
type="button"
onClick={() =>
handleDeleteSplineCorridorJunction(
junction.id
)
}
>
Delete
</button>
</div>
<label className="form-field form-field--toggle">
<span className="label">Enabled</span>
<input
data-testid={`spline-corridor-junction-${junction.id}-enabled`}
type="checkbox"
checked={junction.enabled}
onChange={(event) =>
handleSplineCorridorJunctionChange(
createSplineCorridorJunction({
...junction,
enabled: event.currentTarget.checked
}),
event.currentTarget.checked
? "Enabled spline corridor junction."
: "Disabled spline corridor junction."
)
}
/>
</label>
<label className="form-field">
<span className="label">Radius</span>
<input
className="text-input"
data-testid={`spline-corridor-junction-${junction.id}-radius`}
type="number"
min={0.05}
step={0.1}
value={junction.radius}
onChange={(event) => {
const radius =
normalizeSplineCorridorJunctionRadius(
Number(event.currentTarget.value)
);
handleSplineCorridorJunctionChange(
createSplineCorridorJunction({
...junction,
radius,
connections: junction.connections.map(
(connection) => ({
...connection,
clipDistance: radius
})
)
}),
"Updated spline corridor junction radius."
);
}}
/>
</label>
<label className="form-field">
<span className="label">Material</span>
<select
className="select-input"
data-testid={`spline-corridor-junction-${junction.id}-material`}
value={junction.materialId ?? ""}
onChange={(event) =>
handleSplineCorridorJunctionChange(
createSplineCorridorJunction({
...junction,
materialId:
normalizeSplineCorridorJunctionMaterialId(
event.currentTarget.value
)
}),
"Updated spline corridor junction material."
)
}
>
<option value="">Unassigned preview</option>
{materialList.map((material) => (
<option
key={material.id}
value={material.id}
>
{material.name}
</option>
))}
</select>
</label>
<label className="form-field">
<span className="label">Terrain Mode</span>
<select
className="select-input"
data-testid={`spline-corridor-junction-${junction.id}-terrain-mode`}
value={junction.terrainMode}
onChange={(event) =>
handleSplineCorridorJunctionChange(
createSplineCorridorJunction({
...junction,
terrainMode:
normalizeSplineCorridorJunctionTerrainMode(
event.currentTarget.value
) as SplineCorridorJunctionTerrainMode
}),
"Updated spline corridor junction terrain mode."
)
}
>
<option value="flattenAndPaint">
Flatten and paint
</option>
<option value="paintOnly">Paint only</option>
<option value="none">None</option>
</select>
</label>
</div>
))}
</div>
)}
</div>
<div className="inline-actions">
<button
className="toolbar__button"