Feature: Add road junction management UI and functionality
This commit is contained in:
181
src/app/App.tsx
181
src/app/App.tsx
@@ -22700,6 +22700,187 @@ export function App({
|
|||||||
</div>
|
</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">
|
<div className="inline-actions">
|
||||||
<button
|
<button
|
||||||
className="toolbar__button"
|
className="toolbar__button"
|
||||||
|
|||||||
Reference in New Issue
Block a user