@marble.fun/track-catalog 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/assets.json +346 -0
- package/package.json +32 -0
- package/release.json +135 -0
- package/src/audio/game-audio-preference.ts +9 -0
- package/src/authoritative.ts +6 -0
- package/src/catalog.ts +1 -0
- package/src/index.ts +14 -0
- package/src/legacy-stock-catalog.ts +130 -0
- package/src/pages/lobby/lobby-types.ts +140 -0
- package/src/pages/lobby/marble-factory.ts +931 -0
- package/src/pages/play/race/egypt-elevator-blender.ts +159 -0
- package/src/pages/play/race/egypt-portal-frame.ts +382 -0
- package/src/pages/play/race/finish-detection.ts +1 -0
- package/src/pages/play/race/japanese-sakura-castle-support-blender.ts +510 -0
- package/src/pages/play/race/japanese-sakura-kawara-roof.ts +201 -0
- package/src/pages/play/race/japanese-sakura-portal-frame.ts +325 -0
- package/src/pages/play/race/jungle-portal-frame.ts +308 -0
- package/src/pages/play/race/jungle-temple-lift.ts +366 -0
- package/src/pages/play/race/marble-entity.ts +257 -0
- package/src/pages/play/race/marble-material.ts +20 -0
- package/src/pages/play/race/marble-race-engine.ts +2652 -0
- package/src/pages/play/race/path-pattern-layout.ts +142 -0
- package/src/pages/play/race/peg-obstacle-visual.ts +171 -0
- package/src/pages/play/race/physics-debug.ts +1 -0
- package/src/pages/play/race/physics-dry-run.ts +90 -0
- package/src/pages/play/race/portal-derived-elevator-skins.ts +729 -0
- package/src/pages/play/race/portal-pair-color.ts +20 -0
- package/src/pages/play/race/portal-proportions.ts +25 -0
- package/src/pages/play/race/portal-visual.ts +1108 -0
- package/src/pages/play/race/race-camera-director.ts +586 -0
- package/src/pages/play/race/race-camera-obstruction.ts +37 -0
- package/src/pages/play/race/race-camera-tour-planner.ts +274 -0
- package/src/pages/play/race/race-camera.ts +206 -0
- package/src/pages/play/race/race-completion.ts +5 -0
- package/src/pages/play/race/race-configuration.ts +57 -0
- package/src/pages/play/race/race-marble-focus-renderer.ts +134 -0
- package/src/pages/play/race/race-physics-profile.ts +7 -0
- package/src/pages/play/race/race-physics.ts +16 -0
- package/src/pages/play/race/race-playback.ts +41 -0
- package/src/pages/play/race/race-rolling-audio.ts +616 -0
- package/src/pages/play/race/race-simulation-renderer.ts +221 -0
- package/src/pages/play/race/race-terminal-checker-strip.ts +226 -0
- package/src/pages/play/race/race-transform-interpolation.ts +50 -0
- package/src/pages/play/race/race-types.ts +12 -0
- package/src/pages/play/race/rebound-visual.ts +70 -0
- package/src/pages/play/race/seeded-random.ts +23 -0
- package/src/pages/play/race/space-station-portal-frame.ts +275 -0
- package/src/pages/play/race/track-appearance-canopy.ts +946 -0
- package/src/pages/play/race/track-appearance-floor-runner.ts +206 -0
- package/src/pages/play/race/track-appearance-jungle-flower-hedge.ts +483 -0
- package/src/pages/play/race/track-appearance-rope-wall.ts +443 -0
- package/src/pages/play/race/track-appearance-sakura-bridge-fence.ts +305 -0
- package/src/pages/play/race/track-appearance-sakura-castle-wall.ts +1031 -0
- package/src/pages/play/race/track-appearance-space-station-wall.ts +497 -0
- package/src/pages/play/race/track-appearance-stone-block-shell.ts +517 -0
- package/src/pages/play/race/track-appearance-support.ts +983 -0
- package/src/pages/play/race/track-appearance-vampire-carved-wall.ts +545 -0
- package/src/pages/play/race/track-appearance-wall-terminal.ts +167 -0
- package/src/pages/play/race/track-course.ts +271 -0
- package/src/pages/play/race/track-definition.ts +443 -0
- package/src/pages/play/race/track-fence-architecture.ts +33 -0
- package/src/pages/play/race/track-floor-material-rules.ts +51 -0
- package/src/pages/play/race/track-mechanisms.ts +1350 -0
- package/src/pages/play/race/track-physics-surface.ts +6 -0
- package/src/pages/play/race/track-route-network.ts +9 -0
- package/src/pages/play/race/track-shadow-batching.ts +760 -0
- package/src/pages/play/race/track-surface-basis.ts +1 -0
- package/src/pages/play/race/track-surface-topology.ts +326 -0
- package/src/pages/play/race/track-world.ts +10181 -0
- package/src/pages/play/race/under-track-support-architecture.ts +122 -0
- package/src/pages/play/race/vortex-profile.ts +5 -0
- package/src/pages/play/race/vortex-water-effect.ts +863 -0
- package/src/pages/play/race/wild-west-mine-lift.ts +281 -0
- package/src/pages/play/race/wild-west-vortex-housing.ts +572 -0
- package/src/pages/track-lab/atlas-vortex-layout.ts +1 -0
- package/src/pages/track-lab/cone-field-layout.ts +1 -0
- package/src/pages/track-lab/conveyor-layout.ts +1 -0
- package/src/pages/track-lab/engine/canonical-track-assembly.ts +64 -0
- package/src/pages/track-lab/engine/editor-object-geometry.ts +170 -0
- package/src/pages/track-lab/engine/track-lab-connection-state.ts +123 -0
- package/src/pages/track-lab/engine/track-piece-visual.ts +1420 -0
- package/src/pages/track-lab/flipper-bank-layout.ts +1 -0
- package/src/pages/track-lab/flipper-component.ts +127 -0
- package/src/pages/track-lab/rising-wall-layout.ts +1 -0
- package/src/pages/track-lab/simple-obstacle-layout.ts +1 -0
- package/src/pages/track-lab/track-piece-layout.ts +28 -0
- package/src/pages/track-lab/track-runtime-adapter.ts +1152 -0
- package/src/three/geometry-attribute-aliases.ts +24 -0
- package/src/three/geometry-detail.ts +36 -0
- package/src/three/homepage-marble-geometry.ts +15 -0
- package/src/three/japanese-sakura-appearance.ts +252 -0
- package/src/three/jungle-flower-hedge-appearance.ts +7 -0
- package/src/three/jungle-temple-appearance.ts +5 -0
- package/src/three/low-quality-scene-renderer.ts +132 -0
- package/src/three/marble-sphere-geometry.ts +13 -0
- package/src/three/mobile-rendering-budget.ts +11 -0
- package/src/three/painted-valley-appearance.ts +270 -0
- package/src/three/railroad-appearance.ts +71 -0
- package/src/three/renderer-resize.ts +32 -0
- package/src/three/rope-wall-appearance.ts +91 -0
- package/src/three/rugged-track-appearance.ts +2 -0
- package/src/three/scene-lighting.ts +183 -0
- package/src/three/scene-shader-preparation.ts +64 -0
- package/src/three/scene-theme-controller.ts +461 -0
- package/src/three/shadow-shader-preparation.ts +162 -0
- package/src/three/shared-obstacle-metal.ts +44 -0
- package/src/three/soft-additive-particles.ts +57 -0
- package/src/three/space-station-appearance.ts +213 -0
- package/src/three/static-track-batched-mesh.ts +132 -0
- package/src/three/stone-track-shell-appearance.ts +12 -0
- package/src/three/theme-ambience-controller.ts +226 -0
- package/src/three/theme-asset-cache.ts +120 -0
- package/src/three/theme-audio-preference.ts +43 -0
- package/src/three/theme-catalog.ts +1457 -0
- package/src/three/theme-material-assets.ts +258 -0
- package/src/three/theme-material-ktx2-manifest.ts +190 -0
- package/src/three/theme-material-registry.ts +1952 -0
- package/src/three/theme-world-factory.ts +590 -0
- package/src/three/track-connector-batch.ts +125 -0
- package/src/three/track-material-continuity.ts +442 -0
- package/src/three/track-theme.ts +535 -0
- package/src/three/vampire-castle-appearance.ts +258 -0
- package/src/three/visual-frame-pacing.ts +19 -0
- package/src/three/visual-geometry.ts +120 -0
- package/src/three/visual-quality.ts +83 -0
- package/src/three/water-appearance.ts +29 -0
- package/src/three/wild-west-sand-appearance.ts +2 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
export {
|
|
2
|
+
DEFAULT_TRACK_TROUGH_FLAT_FRACTION,
|
|
3
|
+
TRACK_RAIL_CROSS_SECTION_SEGMENTS,
|
|
4
|
+
TRACK_RAIL_WALL_THICKNESS,
|
|
5
|
+
defaultTrackTroughDepth,
|
|
6
|
+
railSurfacePoseAt,
|
|
7
|
+
railTroughCrossSectionOffset,
|
|
8
|
+
railTroughDepthAt,
|
|
9
|
+
railTroughFlatFraction,
|
|
10
|
+
railWidthAt,
|
|
11
|
+
type RailSurfacePose,
|
|
12
|
+
} from '@marble.fun/physics-engine/browser'
|
|
13
|
+
import type {
|
|
14
|
+
PhysicsConveyorPieceDefinition,
|
|
15
|
+
PhysicsDipperPieceDefinition,
|
|
16
|
+
PhysicsFinishPieceDefinition,
|
|
17
|
+
PhysicsFlipperBankPieceDefinition,
|
|
18
|
+
PhysicsJumpPieceDefinition,
|
|
19
|
+
PhysicsPegFieldPieceDefinition,
|
|
20
|
+
PhysicsPortalPieceDefinition,
|
|
21
|
+
PhysicsRailPieceDefinition,
|
|
22
|
+
PhysicsReboundPieceDefinition,
|
|
23
|
+
PhysicsRisingWallForestPieceDefinition,
|
|
24
|
+
PhysicsSplitterPieceDefinition,
|
|
25
|
+
PhysicsTrackDefinition,
|
|
26
|
+
PhysicsTurntablePieceDefinition,
|
|
27
|
+
PhysicsVortexPieceDefinition,
|
|
28
|
+
TrackDocument,
|
|
29
|
+
TrackPieceAppearance,
|
|
30
|
+
} from '@marble.fun/physics-engine/browser'
|
|
31
|
+
|
|
32
|
+
export type TrackVector = readonly [x: number, y: number, z: number]
|
|
33
|
+
|
|
34
|
+
export interface TrackSkin {
|
|
35
|
+
id: string
|
|
36
|
+
label: string
|
|
37
|
+
background: number
|
|
38
|
+
fog: number
|
|
39
|
+
trackTop: number
|
|
40
|
+
trackEdge: number
|
|
41
|
+
rail: number
|
|
42
|
+
structure: number
|
|
43
|
+
signal: number
|
|
44
|
+
hazard: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface TrackPieceBase {
|
|
48
|
+
id: string
|
|
49
|
+
label: string
|
|
50
|
+
appearance?: TrackPieceAppearance
|
|
51
|
+
/** Visual-only absolute metric offset inherited across connected pieces. */
|
|
52
|
+
materialConnectionPhase?: number
|
|
53
|
+
/** Hidden previews can defer expensive masonry until continuity supplies its final phase. */
|
|
54
|
+
deferStoneArchitectureUntilMaterialContinuity?: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface RailPieceDefinition extends TrackPieceBase, PhysicsRailPieceDefinition {
|
|
58
|
+
type: 'rail'
|
|
59
|
+
/** Visual-only topology capability. Vortex-owned rails reject roof/canopy wall architecture. */
|
|
60
|
+
supportsAppearanceCanopy?: boolean
|
|
61
|
+
/** Track Lab-only bounds and ray-picking meshes; runtime race worlds omit them. */
|
|
62
|
+
editorProxies?: boolean
|
|
63
|
+
/** Visual-only connector locations in the same coordinate space as `points`, used to inherit exact material frames. */
|
|
64
|
+
materialConnectors?: readonly {
|
|
65
|
+
id: string
|
|
66
|
+
position: TrackVector
|
|
67
|
+
}[]
|
|
68
|
+
/**
|
|
69
|
+
* Visual-only longitudinal sampling for a non-physical overview preview.
|
|
70
|
+
* Runtime rails and colliders always retain the canonical 2.5 cm sampling.
|
|
71
|
+
*/
|
|
72
|
+
previewProfileSamplePitch?: number
|
|
73
|
+
/** Visual-only fluid continuity at the first/input terminal. */
|
|
74
|
+
waterFlowStartConnected?: boolean
|
|
75
|
+
/** Visual-only fluid continuity at the last/output terminal. */
|
|
76
|
+
waterFlowEndConnected?: boolean
|
|
77
|
+
/** Keep the selected floor recipe on one continuous carrier mesh instead of material-authored separated boards. */
|
|
78
|
+
continuousFloorMaterialOnly?: boolean
|
|
79
|
+
/** Visual race landmark role. This never changes rail physics. */
|
|
80
|
+
raceRole?: 'start' | 'finish'
|
|
81
|
+
/** Optional visual railway topology for themed raised rails. */
|
|
82
|
+
railroadProfile?: 'routing-split' | 'routing-merge' | 'intersection-merge'
|
|
83
|
+
/**
|
|
84
|
+
* Turns one continuous ribbon into two standard lane troughs without
|
|
85
|
+
* stacking separate floor meshes or colliders. Used by passive routing
|
|
86
|
+
* pieces whose terminal is a pair of side-by-side connectors.
|
|
87
|
+
*/
|
|
88
|
+
routingBranchProfile?: {
|
|
89
|
+
mode: 'split' | 'merge'
|
|
90
|
+
branchOffset: number
|
|
91
|
+
branchWidth: number
|
|
92
|
+
startProgress: number
|
|
93
|
+
endProgress: number
|
|
94
|
+
/** Metric radius of each circular half of the branch S-bend. */
|
|
95
|
+
bendRadius?: number
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Additional ingress centerlines that are fused with `points` into one
|
|
99
|
+
* junction floor and one floor collider. They are geometry, not separate
|
|
100
|
+
* track pieces, so intersections cannot leave coplanar overlapping shells.
|
|
101
|
+
*/
|
|
102
|
+
junctionBranches?: readonly {
|
|
103
|
+
connectorId: string
|
|
104
|
+
/** Visual-only absolute metric phase at this branch input connector. */
|
|
105
|
+
materialConnectionPhase?: number
|
|
106
|
+
/** Visual-only fluid continuity at this branch input connector. */
|
|
107
|
+
waterFlowStartConnected?: boolean
|
|
108
|
+
points: readonly TrackVector[]
|
|
109
|
+
startTangent?: TrackVector
|
|
110
|
+
endTangent?: TrackVector
|
|
111
|
+
startPortWidth?: number
|
|
112
|
+
startPortTroughDepth?: number
|
|
113
|
+
startPortTroughFlatFraction?: number
|
|
114
|
+
/** Whether the branch input wall volumes close at their exposed terminal. */
|
|
115
|
+
capStart?: boolean
|
|
116
|
+
}[]
|
|
117
|
+
/** Connector that owns the first point of the junction's primary route. */
|
|
118
|
+
junctionMainConnectorId?: string
|
|
119
|
+
points: readonly TrackVector[]
|
|
120
|
+
/** Exact forward tangent at the first port. Prevents sampled curves from rotating their terminal cross-section. */
|
|
121
|
+
startTangent?: TrackVector
|
|
122
|
+
/** Exact forward tangent at the last port. Prevents sampled curves from rotating their terminal cross-section. */
|
|
123
|
+
endTangent?: TrackVector
|
|
124
|
+
/** Negotiated width of the first connected port, independent of the rail's interior width. */
|
|
125
|
+
startPortWidth?: number
|
|
126
|
+
/** Negotiated width of the last connected port, independent of the rail's interior width. */
|
|
127
|
+
endPortWidth?: number
|
|
128
|
+
/** Negotiated trough depth of the first connected port. */
|
|
129
|
+
startPortTroughDepth?: number
|
|
130
|
+
/** Negotiated trough depth of the last connected port. */
|
|
131
|
+
endPortTroughDepth?: number
|
|
132
|
+
/** Negotiated flat-bottom fraction of the first connected trough. */
|
|
133
|
+
startPortTroughFlatFraction?: number
|
|
134
|
+
/** Negotiated flat-bottom fraction of the last connected trough. */
|
|
135
|
+
endPortTroughFlatFraction?: number
|
|
136
|
+
/** Whether the floor and wall shells close across the first cross-section. Connected ports leave this open. */
|
|
137
|
+
capStart?: boolean
|
|
138
|
+
/** Whether the floor and wall shells close across the last cross-section. Connected ports leave this open. */
|
|
139
|
+
capEnd?: boolean
|
|
140
|
+
width: number
|
|
141
|
+
startWidth?: number
|
|
142
|
+
pointWidths?: readonly number[]
|
|
143
|
+
widthProfile?: 'linear' | 'smoothstep'
|
|
144
|
+
startTroughDepth?: number
|
|
145
|
+
troughDepth?: number
|
|
146
|
+
pointTroughDepths?: readonly number[]
|
|
147
|
+
/** Optional per-control-point trough profiles, interpolated with the floor geometry. */
|
|
148
|
+
pointTroughFlatFractions?: readonly number[]
|
|
149
|
+
troughProfile?: 'full' | 'fade-in' | 'fade-out' | 'fade-both'
|
|
150
|
+
troughFadeFraction?: number
|
|
151
|
+
troughFlatFraction?: number
|
|
152
|
+
wallHeight?: number
|
|
153
|
+
/** Optional side-wall collider height, independent of the rendered wall height. */
|
|
154
|
+
wallColliderHeight?: number
|
|
155
|
+
verticalWalls?: boolean
|
|
156
|
+
floorFriction?: number
|
|
157
|
+
wallFriction?: number
|
|
158
|
+
openFirstSegment?: boolean
|
|
159
|
+
openLastSegment?: boolean
|
|
160
|
+
openNegativeWallSegments?: readonly number[]
|
|
161
|
+
openPositiveWallSegments?: readonly number[]
|
|
162
|
+
closedStart?: boolean
|
|
163
|
+
closedEnd?: boolean
|
|
164
|
+
/** Optional collider height for a closed start, independent of the side-wall height. */
|
|
165
|
+
closedStartHeight?: number
|
|
166
|
+
/** Optional collider height for a closed end, independent of the side-wall height. */
|
|
167
|
+
closedEndHeight?: number
|
|
168
|
+
centerDivider?: {
|
|
169
|
+
/** A divider is independent geometry; this controls whether it looks like a wall or an obstacle. */
|
|
170
|
+
visualRole?: 'wall' | 'obstacle'
|
|
171
|
+
startProgress: number
|
|
172
|
+
endProgress: number
|
|
173
|
+
startWidth: number
|
|
174
|
+
endWidth: number
|
|
175
|
+
/** Floor-profile span used to anchor the start edge before wall-thickness overhang. */
|
|
176
|
+
startSurfaceWidth?: number
|
|
177
|
+
/** Floor-profile span used to anchor the end edge before wall-thickness overhang. */
|
|
178
|
+
endSurfaceWidth?: number
|
|
179
|
+
/** Total terminal span whose excess over endWidth closes adjacent wall ends. */
|
|
180
|
+
endWallClosureWidth?: number
|
|
181
|
+
startHeight: number
|
|
182
|
+
endHeight: number
|
|
183
|
+
lateralOffset?: number
|
|
184
|
+
distanceBased?: boolean
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export interface VortexPieceDefinition extends TrackPieceBase, PhysicsVortexPieceDefinition {
|
|
188
|
+
type: 'vortex'
|
|
189
|
+
/** The negotiated input is carrying the same fluid style into this Vortex. */
|
|
190
|
+
waterFlowInputConnected?: boolean
|
|
191
|
+
center: TrackVector
|
|
192
|
+
entry: TrackVector
|
|
193
|
+
entryWidth: number
|
|
194
|
+
integratedEntry?: boolean
|
|
195
|
+
dropEntry?: boolean
|
|
196
|
+
outerRadius: number
|
|
197
|
+
innerRadius: number
|
|
198
|
+
depth: number
|
|
199
|
+
profileExponent?: number
|
|
200
|
+
/** Smooths the bowl into its outer apron while preserving an inward drain slope. */
|
|
201
|
+
smoothProfile?: boolean
|
|
202
|
+
floorFriction?: number
|
|
203
|
+
segmentCount: number
|
|
204
|
+
exitFunnel?: {
|
|
205
|
+
mouthRadius: number
|
|
206
|
+
throatRadius: number
|
|
207
|
+
depth: number
|
|
208
|
+
ringCount: number
|
|
209
|
+
friction?: number
|
|
210
|
+
tunnelFriction?: number
|
|
211
|
+
throatLength?: number
|
|
212
|
+
outletRadius?: number
|
|
213
|
+
tunnelExit?: TrackVector
|
|
214
|
+
tunnelDirection?: TrackVector
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface SplitterPieceDefinition extends TrackPieceBase, PhysicsSplitterPieceDefinition {
|
|
219
|
+
type: 'splitter'
|
|
220
|
+
center: TrackVector
|
|
221
|
+
capture: TrackVector
|
|
222
|
+
direction: TrackVector
|
|
223
|
+
surfaceAligned?: boolean
|
|
224
|
+
captureRadius: number
|
|
225
|
+
barLength: number
|
|
226
|
+
barHeight: number
|
|
227
|
+
barDepth: number
|
|
228
|
+
swingRadians: number
|
|
229
|
+
cycleSeconds: number
|
|
230
|
+
phaseIndex: number
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface JumpPieceDefinition extends TrackPieceBase, PhysicsJumpPieceDefinition {
|
|
234
|
+
type: 'jump'
|
|
235
|
+
points: readonly TrackVector[]
|
|
236
|
+
width: number
|
|
237
|
+
wallHeight: number
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface ConveyorPieceDefinition extends TrackPieceBase, PhysicsConveyorPieceDefinition {
|
|
241
|
+
type: 'conveyor'
|
|
242
|
+
bottom: TrackVector
|
|
243
|
+
top: TrackVector
|
|
244
|
+
direction: TrackVector
|
|
245
|
+
width: number
|
|
246
|
+
shelfDepth: number
|
|
247
|
+
shelfCount: number
|
|
248
|
+
speed: number
|
|
249
|
+
friction: number
|
|
250
|
+
shelfTiltRadians: number
|
|
251
|
+
releaseHeight: number
|
|
252
|
+
phaseIndex: number
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface FlipperMountDefinition {
|
|
256
|
+
anchor: TrackVector
|
|
257
|
+
side: 'left' | 'right'
|
|
258
|
+
direction?: TrackVector
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface FlipperBankPieceDefinition extends TrackPieceBase, PhysicsFlipperBankPieceDefinition {
|
|
262
|
+
type: 'flipper-bank'
|
|
263
|
+
direction: TrackVector
|
|
264
|
+
mounts: readonly FlipperMountDefinition[]
|
|
265
|
+
paddleLength: number
|
|
266
|
+
paddleWidth: number
|
|
267
|
+
paddleHeight: number
|
|
268
|
+
swingRadians: number
|
|
269
|
+
strikeSeconds: number
|
|
270
|
+
holdSeconds: number
|
|
271
|
+
returnSeconds: number
|
|
272
|
+
minimumIntervalSeconds: number
|
|
273
|
+
maximumIntervalSeconds: number
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export interface TurntablePieceDefinition extends TrackPieceBase, PhysicsTurntablePieceDefinition {
|
|
277
|
+
type: 'turntable'
|
|
278
|
+
center: TrackVector
|
|
279
|
+
direction: TrackVector
|
|
280
|
+
radius: number
|
|
281
|
+
channelWidth: number
|
|
282
|
+
wallHeight: number
|
|
283
|
+
routeAngles: readonly number[]
|
|
284
|
+
rotationSeconds: number
|
|
285
|
+
settleSeconds: number
|
|
286
|
+
triggerRadius: number
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export interface DipperPieceDefinition extends TrackPieceBase, PhysicsDipperPieceDefinition {
|
|
290
|
+
type: 'dipper'
|
|
291
|
+
hinge: TrackVector
|
|
292
|
+
direction: TrackVector
|
|
293
|
+
length: number
|
|
294
|
+
width: number
|
|
295
|
+
wallHeight: number
|
|
296
|
+
dropRadians: number
|
|
297
|
+
lowerSeconds: number
|
|
298
|
+
holdSeconds: number
|
|
299
|
+
resetSeconds: number
|
|
300
|
+
triggerRadius: number
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface PegFieldPieceDefinition extends TrackPieceBase, PhysicsPegFieldPieceDefinition {
|
|
304
|
+
type: 'peg-field'
|
|
305
|
+
direction: TrackVector
|
|
306
|
+
pegs: readonly TrackVector[]
|
|
307
|
+
pegDirections?: readonly TrackVector[]
|
|
308
|
+
pegRadius: number
|
|
309
|
+
pegHeight: number
|
|
310
|
+
colliderShape?: 'cone' | 'cylinder'
|
|
311
|
+
friction?: number
|
|
312
|
+
restitution?: number
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface ReboundMountDefinition {
|
|
316
|
+
anchor: TrackVector
|
|
317
|
+
angle: number
|
|
318
|
+
side: 'left' | 'right'
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface ReboundPieceDefinition extends TrackPieceBase, PhysicsReboundPieceDefinition {
|
|
322
|
+
type: 'rebounds'
|
|
323
|
+
direction: TrackVector
|
|
324
|
+
mounts: readonly ReboundMountDefinition[]
|
|
325
|
+
reboundLength: number
|
|
326
|
+
reboundWidth: number
|
|
327
|
+
reboundHeight: number
|
|
328
|
+
friction: number
|
|
329
|
+
restitution: number
|
|
330
|
+
strikeDistance: number
|
|
331
|
+
strikeSeconds: number
|
|
332
|
+
holdSeconds: number
|
|
333
|
+
returnSeconds: number
|
|
334
|
+
cooldownSeconds: number
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export interface RisingWallAnchorDefinition {
|
|
338
|
+
position: TrackVector
|
|
339
|
+
row: number
|
|
340
|
+
column: number
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export interface RisingWallForestPieceDefinition extends TrackPieceBase, PhysicsRisingWallForestPieceDefinition {
|
|
344
|
+
type: 'rising-wall-forest'
|
|
345
|
+
direction: TrackVector
|
|
346
|
+
anchors: readonly RisingWallAnchorDefinition[]
|
|
347
|
+
rows: number
|
|
348
|
+
columns: number
|
|
349
|
+
density: 'sparse' | 'dense'
|
|
350
|
+
wallWidth: number
|
|
351
|
+
wallHeight: number
|
|
352
|
+
wallDepth: number
|
|
353
|
+
cycleSeconds: number
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export interface PortalPieceDefinition extends TrackPieceBase, PhysicsPortalPieceDefinition {
|
|
357
|
+
type: 'portal'
|
|
358
|
+
entryVisualStyle?: PortalVisualStyle
|
|
359
|
+
exitVisualStyle?: PortalVisualStyle
|
|
360
|
+
color: string
|
|
361
|
+
entry: TrackVector
|
|
362
|
+
entryDirection?: TrackVector
|
|
363
|
+
exit: TrackVector
|
|
364
|
+
exitDirection: TrackVector
|
|
365
|
+
entryVisualCenter: TrackVector
|
|
366
|
+
entryVisualDirection: TrackVector
|
|
367
|
+
exitVisualCenter: TrackVector
|
|
368
|
+
exitVisualDirection: TrackVector
|
|
369
|
+
captureRadius?: number
|
|
370
|
+
captureHalfExtents?: TrackVector
|
|
371
|
+
releaseSpeed: number
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export type PortalVisualStyle = 'live-window'
|
|
375
|
+
|
|
376
|
+
export interface FinishPieceDefinition extends TrackPieceBase, PhysicsFinishPieceDefinition {
|
|
377
|
+
type: 'finish'
|
|
378
|
+
center: TrackVector
|
|
379
|
+
direction: TrackVector
|
|
380
|
+
width: number
|
|
381
|
+
channelEnd: TrackVector
|
|
382
|
+
rankingSlots: number
|
|
383
|
+
rankingSpacing: number
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export type TrackPieceDefinition =
|
|
387
|
+
| RailPieceDefinition
|
|
388
|
+
| VortexPieceDefinition
|
|
389
|
+
| SplitterPieceDefinition
|
|
390
|
+
| JumpPieceDefinition
|
|
391
|
+
| ConveyorPieceDefinition
|
|
392
|
+
| FlipperBankPieceDefinition
|
|
393
|
+
| TurntablePieceDefinition
|
|
394
|
+
| DipperPieceDefinition
|
|
395
|
+
| PegFieldPieceDefinition
|
|
396
|
+
| ReboundPieceDefinition
|
|
397
|
+
| RisingWallForestPieceDefinition
|
|
398
|
+
| PortalPieceDefinition
|
|
399
|
+
| FinishPieceDefinition
|
|
400
|
+
|
|
401
|
+
export interface TrackGuidePoint {
|
|
402
|
+
position: TrackVector
|
|
403
|
+
progress: number
|
|
404
|
+
pieceId: string
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export interface TrackGuidePath {
|
|
408
|
+
id: string
|
|
409
|
+
points: readonly TrackGuidePoint[]
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export interface TrackDefinition extends PhysicsTrackDefinition {
|
|
413
|
+
id: string
|
|
414
|
+
version: number
|
|
415
|
+
label: string
|
|
416
|
+
description: string
|
|
417
|
+
targetDurationSeconds: readonly [number, number]
|
|
418
|
+
spawnProgress: number
|
|
419
|
+
spawnWidth: number
|
|
420
|
+
themeId?: string
|
|
421
|
+
skin: TrackSkin
|
|
422
|
+
pieces: readonly TrackPieceDefinition[]
|
|
423
|
+
cameraPath: TrackGuidePath
|
|
424
|
+
projectionPaths: readonly TrackGuidePath[]
|
|
425
|
+
/**
|
|
426
|
+
* Authored source for the canonical Lab visual assembly. This is runtime
|
|
427
|
+
* context rather than simulation identity and is omitted by legacy tracks.
|
|
428
|
+
*/
|
|
429
|
+
sourceDocument?: TrackDocument
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export const NEUTRAL_STUDIO_SKIN: TrackSkin = {
|
|
433
|
+
id: 'neutral-studio',
|
|
434
|
+
label: 'Neutral Studio',
|
|
435
|
+
background: 0x080b10,
|
|
436
|
+
fog: 0x0b1016,
|
|
437
|
+
trackTop: 0xaeb9be,
|
|
438
|
+
trackEdge: 0x27313a,
|
|
439
|
+
rail: 0xe3eaeb,
|
|
440
|
+
structure: 0x35414a,
|
|
441
|
+
signal: 0xdfff65,
|
|
442
|
+
hazard: 0xff765f,
|
|
443
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Shared visual proportions for open timber fences, independent of theme materials. */
|
|
2
|
+
export const WOODEN_FENCE_HEIGHT_RATIO = 0.65
|
|
3
|
+
export const WOODEN_FENCE_POST_SPAN_RATIO = 1.82
|
|
4
|
+
export const WOODEN_FENCE_MINIMUM_POST_GAP = 0.08
|
|
5
|
+
/** Visual breathing room beyond the physical wall's outer face. */
|
|
6
|
+
export const WOODEN_FENCE_EDGE_ALLOWANCE = 0.025
|
|
7
|
+
|
|
8
|
+
export function woodenFenceHeight(wallHeight: number): number {
|
|
9
|
+
return Math.max(0, wallHeight) * WOODEN_FENCE_HEIGHT_RATIO
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function woodenFencePostCount(totalLength: number, fenceHeight: number): number {
|
|
13
|
+
if (totalLength <= 0 || fenceHeight <= 0) return 0
|
|
14
|
+
return Math.max(2, Math.ceil(totalLength / (fenceHeight * WOODEN_FENCE_POST_SPAN_RATIO)) + 1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Spatial exclusion shared by path-following fence schedulers. Junctions can
|
|
19
|
+
* contain several boundary runs whose independent arc-length stations meet in
|
|
20
|
+
* world space; this keeps their post surfaces separated by a real gap.
|
|
21
|
+
*/
|
|
22
|
+
export function woodenFenceMinimumPostCenterDistance(maximumPostWidth: number): number {
|
|
23
|
+
return Math.max(0, maximumPostWidth) + WOODEN_FENCE_MINIMUM_POST_GAP
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Canonical wall runs describe the middle of a physical wall. Fence members
|
|
28
|
+
* live just outside its exterior face, matching the placement used by normal
|
|
29
|
+
* ribbon lanes.
|
|
30
|
+
*/
|
|
31
|
+
export function woodenFenceOutwardPlacementOffset(wallThickness: number): number {
|
|
32
|
+
return Math.max(0, wallThickness) / 2 + WOODEN_FENCE_EDGE_ALLOWANCE
|
|
33
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ThemeMaterialRegistry } from '../../../three/theme-material-registry'
|
|
2
|
+
import { TRACK_THEMES } from '../../../three/theme-catalog'
|
|
3
|
+
import { resolvedFloorMaterialIdForTheme } from '../../../three/railroad-appearance'
|
|
4
|
+
import type { MaterialRecipe, TrackThemeDefinition } from '../../../three/track-theme'
|
|
5
|
+
import type { FloorSurfaceTreatment, TrackMaterials } from './track-mechanisms'
|
|
6
|
+
|
|
7
|
+
function floorRecipeForTheme(theme: TrackThemeDefinition, floorMaterialId: string | undefined): MaterialRecipe {
|
|
8
|
+
const resolvedId = resolvedFloorMaterialIdForTheme(theme, floorMaterialId)
|
|
9
|
+
if (!resolvedId) return theme.materials['lane.surface']
|
|
10
|
+
return (
|
|
11
|
+
theme.detailMaterials?.[resolvedId] ??
|
|
12
|
+
theme.appearancePresets?.floor?.find(preset => preset.id === resolvedId)?.material ??
|
|
13
|
+
theme.materials['lane.surface']
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveFloorSurfaceTreatment(
|
|
18
|
+
registry: ThemeMaterialRegistry,
|
|
19
|
+
floorMaterialId: string | undefined,
|
|
20
|
+
): TrackMaterials['floorSurfaceTreatment'] {
|
|
21
|
+
const treatment = registry.getAppearanceRecipe(floorMaterialId, 'lane.surface').surfaceTreatment
|
|
22
|
+
return treatment
|
|
23
|
+
? {
|
|
24
|
+
...treatment,
|
|
25
|
+
material: registry.getAppearance(treatment.materialId, 'lane.edge'),
|
|
26
|
+
themeId: registry.activeTheme.id,
|
|
27
|
+
}
|
|
28
|
+
: undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resolveFloorSurfaceTreatments(
|
|
32
|
+
registry: ThemeMaterialRegistry,
|
|
33
|
+
floorMaterialId: string | undefined,
|
|
34
|
+
): readonly FloorSurfaceTreatment[] {
|
|
35
|
+
const grouped = new Map<string, { treatment: NonNullable<MaterialRecipe['surfaceTreatment']>; themeIds: string[] }>()
|
|
36
|
+
for (const theme of TRACK_THEMES) {
|
|
37
|
+
const treatment = floorRecipeForTheme(theme, floorMaterialId).surfaceTreatment
|
|
38
|
+
if (!treatment) continue
|
|
39
|
+
const key = `${treatment.kind}:${treatment.materialId}:${treatment.bandWidth}:${treatment.featherWidth}:${treatment.normalOffset}`
|
|
40
|
+
const group = grouped.get(key) ?? { treatment, themeIds: [] }
|
|
41
|
+
group.themeIds.push(theme.id)
|
|
42
|
+
grouped.set(key, group)
|
|
43
|
+
}
|
|
44
|
+
return [...grouped.values()].map(({ treatment, themeIds }) => ({
|
|
45
|
+
...treatment,
|
|
46
|
+
get material() {
|
|
47
|
+
return registry.getAppearance(treatment.materialId, 'lane.edge')
|
|
48
|
+
},
|
|
49
|
+
themeId: themeIds.length === 1 ? themeIds[0]! : themeIds,
|
|
50
|
+
}))
|
|
51
|
+
}
|