@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,170 @@
|
|
|
1
|
+
import * as THREE from 'three'
|
|
2
|
+
|
|
3
|
+
import { isStaticTrackColorBatchSource } from '../../play/race/track-shadow-batching'
|
|
4
|
+
|
|
5
|
+
const objectBounds = new THREE.Box3()
|
|
6
|
+
|
|
7
|
+
export function computeEditorBounds(root: THREE.Object3D): THREE.Box3 {
|
|
8
|
+
root.updateWorldMatrix(true, true)
|
|
9
|
+
const bounds = new THREE.Box3()
|
|
10
|
+
const canonicalOverride = editorBoundsOverride(root)
|
|
11
|
+
if (canonicalOverride) {
|
|
12
|
+
bounds.min.fromArray(canonicalOverride.min)
|
|
13
|
+
bounds.max.fromArray(canonicalOverride.max)
|
|
14
|
+
return bounds.applyMatrix4(root.matrixWorld)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
root.traverse(object => {
|
|
18
|
+
if (!isEditorGeometry(object)) return
|
|
19
|
+
expandByGeometryObject(bounds, object)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return bounds
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function collectEditorSelectables(root: THREE.Object3D): THREE.Object3D[] {
|
|
26
|
+
const selectables: THREE.Object3D[] = []
|
|
27
|
+
root.traverse(object => {
|
|
28
|
+
if (object instanceof THREE.Mesh && !hasEditorExclusion(object, 'decorative')) {
|
|
29
|
+
selectables.push(object)
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
return selectables
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Builds a swept canonical selection cage when a surfaced piece provides one. */
|
|
36
|
+
export function createCanonicalEditorOutlineGeometry(
|
|
37
|
+
root: THREE.Object3D,
|
|
38
|
+
relativeTo: THREE.Object3D,
|
|
39
|
+
padding: number,
|
|
40
|
+
): THREE.BufferGeometry | undefined {
|
|
41
|
+
// Composite mechanisms can contain canonical lane proxies without those
|
|
42
|
+
// proxies describing the mechanism's complete footprint. In that case the
|
|
43
|
+
// caller must use the full canonical editor bounds instead of mistaking a
|
|
44
|
+
// nested entry or outlet lane for the whole selection outline.
|
|
45
|
+
if (root.userData.editorOutlineMode === 'bounds') return undefined
|
|
46
|
+
|
|
47
|
+
root.updateWorldMatrix(true, true)
|
|
48
|
+
relativeTo.updateWorldMatrix(true, true)
|
|
49
|
+
const inverseRelative = relativeTo.matrixWorld.clone().invert()
|
|
50
|
+
const positions: number[] = []
|
|
51
|
+
const right = new THREE.Vector3()
|
|
52
|
+
const up = new THREE.Vector3()
|
|
53
|
+
const transform = new THREE.Matrix4()
|
|
54
|
+
const appendSegment = (left: THREE.Vector3, rightPoint: THREE.Vector3): void => {
|
|
55
|
+
positions.push(left.x, left.y, left.z, rightPoint.x, rightPoint.y, rightPoint.z)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
root.traverse(object => {
|
|
59
|
+
const rawSections = object.userData.editorBoundsSections as unknown
|
|
60
|
+
if (!Array.isArray(rawSections) || rawSections.length < 2) return
|
|
61
|
+
const sections = rawSections.flatMap(rawSection => {
|
|
62
|
+
if (!Array.isArray(rawSection) || rawSection.length !== 4) return []
|
|
63
|
+
const section = rawSection.map(rawPoint => {
|
|
64
|
+
if (!Array.isArray(rawPoint) || rawPoint.length !== 3 || !rawPoint.every(Number.isFinite)) return undefined
|
|
65
|
+
return new THREE.Vector3().fromArray(rawPoint)
|
|
66
|
+
})
|
|
67
|
+
if (section.some(candidate => candidate === undefined)) return []
|
|
68
|
+
const prepared = section as THREE.Vector3[]
|
|
69
|
+
right.copy(prepared[1]!).sub(prepared[0]!).normalize()
|
|
70
|
+
up.copy(prepared[2]!).sub(prepared[0]!).normalize()
|
|
71
|
+
prepared[0]!.addScaledVector(right, -padding).addScaledVector(up, -padding)
|
|
72
|
+
prepared[1]!.addScaledVector(right, padding).addScaledVector(up, -padding)
|
|
73
|
+
prepared[2]!.addScaledVector(right, -padding).addScaledVector(up, padding)
|
|
74
|
+
prepared[3]!.addScaledVector(right, padding).addScaledVector(up, padding)
|
|
75
|
+
return [prepared]
|
|
76
|
+
})
|
|
77
|
+
if (sections.length < 2) return
|
|
78
|
+
transform.copy(inverseRelative).multiply(object.matrixWorld)
|
|
79
|
+
sections.forEach(section => section.forEach(candidate => candidate.applyMatrix4(transform)))
|
|
80
|
+
// A swept cage needs every section to keep its four longitudinal edges on
|
|
81
|
+
// a curve or grade, but drawing a complete rectangle at every tessellation
|
|
82
|
+
// sample turns a long piece into a stack of wire boxes. Only the two real
|
|
83
|
+
// terminals own cross-sections; interior samples bend the four rails.
|
|
84
|
+
for (const section of [sections[0]!, sections.at(-1)!]) {
|
|
85
|
+
appendSegment(section[0]!, section[1]!)
|
|
86
|
+
appendSegment(section[2]!, section[3]!)
|
|
87
|
+
appendSegment(section[0]!, section[2]!)
|
|
88
|
+
appendSegment(section[1]!, section[3]!)
|
|
89
|
+
}
|
|
90
|
+
for (let index = 0; index < sections.length - 1; index += 1) {
|
|
91
|
+
for (let corner = 0; corner < 4; corner += 1) {
|
|
92
|
+
appendSegment(sections[index]![corner]!, sections[index + 1]![corner]!)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
if (positions.length === 0) return undefined
|
|
97
|
+
const geometry = new THREE.BufferGeometry()
|
|
98
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))
|
|
99
|
+
geometry.userData.terminalCrossSectionCount = 2
|
|
100
|
+
geometry.userData.intermediateCrossSectionCount = 0
|
|
101
|
+
geometry.computeBoundingBox()
|
|
102
|
+
return geometry
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isEditorObjectVisible(object: THREE.Object3D): boolean {
|
|
106
|
+
for (let current: THREE.Object3D | null = object; current; current = current.parent) {
|
|
107
|
+
// Static color batching hides the rendered source mesh but deliberately
|
|
108
|
+
// retains it as the exact per-piece ray-picking surface.
|
|
109
|
+
if (!current.visible && !(current === object && isStaticTrackColorBatchSource(current))) return false
|
|
110
|
+
}
|
|
111
|
+
return true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isEditorGeometry(object: THREE.Object3D): object is THREE.Mesh | THREE.Line | THREE.Points {
|
|
115
|
+
const visualOnly = hasEditorExclusion(object, 'visualOnly')
|
|
116
|
+
return (
|
|
117
|
+
!hasEditorExclusion(object, 'decorative') &&
|
|
118
|
+
!hasEditorExclusion(object, 'excludeFromEditorBounds') &&
|
|
119
|
+
(!visualOnly || editorBoundsOverride(object) !== undefined) &&
|
|
120
|
+
(object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.Points)
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function hasEditorExclusion(
|
|
125
|
+
object: THREE.Object3D,
|
|
126
|
+
key: 'decorative' | 'excludeFromEditorBounds' | 'visualOnly',
|
|
127
|
+
): boolean {
|
|
128
|
+
for (let current: THREE.Object3D | null = object; current; current = current.parent) {
|
|
129
|
+
if (current.userData[key] === true) return true
|
|
130
|
+
}
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function expandByGeometryObject(bounds: THREE.Box3, object: THREE.Mesh | THREE.Line | THREE.Points): void {
|
|
135
|
+
const override = editorBoundsOverride(object)
|
|
136
|
+
if (override) {
|
|
137
|
+
objectBounds.min.fromArray(override.min)
|
|
138
|
+
objectBounds.max.fromArray(override.max)
|
|
139
|
+
bounds.union(objectBounds.applyMatrix4(object.matrixWorld))
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
if (object instanceof THREE.InstancedMesh) {
|
|
143
|
+
if (object.boundingBox === null) object.computeBoundingBox()
|
|
144
|
+
if (object.boundingBox) bounds.union(objectBounds.copy(object.boundingBox).applyMatrix4(object.matrixWorld))
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (object.geometry.boundingBox === null) object.geometry.computeBoundingBox()
|
|
149
|
+
if (object.geometry.boundingBox) {
|
|
150
|
+
bounds.union(objectBounds.copy(object.geometry.boundingBox).applyMatrix4(object.matrixWorld))
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function editorBoundsOverride(
|
|
155
|
+
object: THREE.Object3D,
|
|
156
|
+
): { min: readonly [number, number, number]; max: readonly [number, number, number] } | undefined {
|
|
157
|
+
const override = object.userData.editorBoundsOverride as
|
|
158
|
+
{ min?: readonly number[]; max?: readonly number[] } | undefined
|
|
159
|
+
if (
|
|
160
|
+
override?.min?.length !== 3 ||
|
|
161
|
+
override.max?.length !== 3 ||
|
|
162
|
+
![...override.min, ...override.max].every(Number.isFinite)
|
|
163
|
+
) {
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
min: override.min as readonly [number, number, number],
|
|
168
|
+
max: override.max as readonly [number, number, number],
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
connectorWorldDirection,
|
|
3
|
+
connectorWorldPosition,
|
|
4
|
+
initialTrackCatalog,
|
|
5
|
+
resolveTemplateConnectors,
|
|
6
|
+
rotateTrackVector,
|
|
7
|
+
type TrackDocument,
|
|
8
|
+
} from '@marble.fun/physics-engine/browser'
|
|
9
|
+
|
|
10
|
+
import { fluidSurfaceStyleFromId } from '../../../three/water-appearance'
|
|
11
|
+
import { underTrackSupportStylesConnect } from '../../play/race/under-track-support-architecture'
|
|
12
|
+
import { createSharedRailConnectorFrame, type RailConnectorFrame } from '../track-piece-layout'
|
|
13
|
+
|
|
14
|
+
export interface TrackLabVisualConnectionState {
|
|
15
|
+
connectedConnectorIdsByPiece: Map<string, Set<string>>
|
|
16
|
+
continuousFluidConnectorIdsByPiece: Map<string, Set<string>>
|
|
17
|
+
continuousSupportConnectorIdsByPiece: Map<string, Set<string>>
|
|
18
|
+
connectorFramesByPiece: Map<string, Map<string, RailConnectorFrame>>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function supportsCanonicalRailPorts(materialSlots: readonly string[]): boolean {
|
|
22
|
+
// Match Test Run's surfaced-piece contract. The previous editor-only
|
|
23
|
+
// runtime-kind allowlist excluded cones, elevators, starts, finishes,
|
|
24
|
+
// portals, and vortices, so Material Debug showed gaps that runtime tests
|
|
25
|
+
// could not reproduce.
|
|
26
|
+
return materialSlots.includes('lane.surface')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function visualConnectionState(document: TrackDocument): TrackLabVisualConnectionState {
|
|
30
|
+
const connectedConnectorIdsByPiece = new Map<string, Set<string>>()
|
|
31
|
+
const continuousFluidConnectorIdsByPiece = new Map<string, Set<string>>()
|
|
32
|
+
const continuousSupportConnectorIdsByPiece = new Map<string, Set<string>>()
|
|
33
|
+
const connectorFramesByPiece = new Map<string, Map<string, RailConnectorFrame>>()
|
|
34
|
+
const piecesById = new Map(document.pieces.map(piece => [piece.id, piece]))
|
|
35
|
+
for (const connection of document.connections) {
|
|
36
|
+
for (const endpoint of [connection.from, connection.to]) {
|
|
37
|
+
const connectorIds = connectedConnectorIdsByPiece.get(endpoint.pieceId) ?? new Set<string>()
|
|
38
|
+
connectorIds.add(endpoint.connectorId)
|
|
39
|
+
connectedConnectorIdsByPiece.set(endpoint.pieceId, connectorIds)
|
|
40
|
+
}
|
|
41
|
+
const fromPiece = piecesById.get(connection.from.pieceId)
|
|
42
|
+
const toPiece = piecesById.get(connection.to.pieceId)
|
|
43
|
+
if (!fromPiece || !toPiece) continue
|
|
44
|
+
const fromTemplate = initialTrackCatalog.require(fromPiece)
|
|
45
|
+
const toTemplate = initialTrackCatalog.require(toPiece)
|
|
46
|
+
const fromConnector = resolveTemplateConnectors(fromTemplate, fromPiece.parameters).find(
|
|
47
|
+
connector => connector.id === connection.from.connectorId,
|
|
48
|
+
)
|
|
49
|
+
const toConnector = resolveTemplateConnectors(toTemplate, toPiece.parameters).find(
|
|
50
|
+
connector => connector.id === connection.to.connectorId,
|
|
51
|
+
)
|
|
52
|
+
const fromFluidStyle = fluidSurfaceStyleFromId(
|
|
53
|
+
fromTemplate.runtimeKind === 'vortex' && fromConnector?.kind.endsWith('input')
|
|
54
|
+
? fromPiece.appearance?.vortexWaterId
|
|
55
|
+
: fromPiece.appearance?.waterOverlayId,
|
|
56
|
+
)
|
|
57
|
+
const toFluidStyle = fluidSurfaceStyleFromId(
|
|
58
|
+
toTemplate.runtimeKind === 'vortex' && toConnector?.kind.endsWith('input')
|
|
59
|
+
? toPiece.appearance?.vortexWaterId
|
|
60
|
+
: toPiece.appearance?.waterOverlayId,
|
|
61
|
+
)
|
|
62
|
+
if (fromFluidStyle && fromFluidStyle === toFluidStyle) {
|
|
63
|
+
for (const endpoint of [connection.from, connection.to]) {
|
|
64
|
+
const connectorIds = continuousFluidConnectorIdsByPiece.get(endpoint.pieceId) ?? new Set<string>()
|
|
65
|
+
connectorIds.add(endpoint.connectorId)
|
|
66
|
+
continuousFluidConnectorIdsByPiece.set(endpoint.pieceId, connectorIds)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
underTrackSupportStylesConnect(fromPiece.appearance?.supportMaterialId, toPiece.appearance?.supportMaterialId)
|
|
71
|
+
) {
|
|
72
|
+
for (const endpoint of [connection.from, connection.to]) {
|
|
73
|
+
const connectorIds = continuousSupportConnectorIdsByPiece.get(endpoint.pieceId) ?? new Set<string>()
|
|
74
|
+
connectorIds.add(endpoint.connectorId)
|
|
75
|
+
continuousSupportConnectorIdsByPiece.set(endpoint.pieceId, connectorIds)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!fromConnector || !toConnector) continue
|
|
79
|
+
if (fromConnector.troughProfile !== 'concave' || toConnector.troughProfile !== 'concave') continue
|
|
80
|
+
const fromSupportsCanonicalPorts = supportsCanonicalRailPorts(fromTemplate.materialSlots)
|
|
81
|
+
const toSupportsCanonicalPorts = supportsCanonicalRailPorts(toTemplate.materialSlots)
|
|
82
|
+
if (!fromSupportsCanonicalPorts && !toSupportsCanonicalPorts) continue
|
|
83
|
+
const worldFrame = createSharedRailConnectorFrame(
|
|
84
|
+
fromConnector,
|
|
85
|
+
toConnector,
|
|
86
|
+
connectorWorldDirection(fromPiece, fromConnector),
|
|
87
|
+
)
|
|
88
|
+
const fromPosition = connectorWorldPosition(fromPiece, fromConnector)
|
|
89
|
+
const toPosition = connectorWorldPosition(toPiece, toConnector)
|
|
90
|
+
const sharedWorldPosition: [number, number, number] = [
|
|
91
|
+
(fromPosition[0] + toPosition[0]) / 2,
|
|
92
|
+
(fromPosition[1] + toPosition[1]) / 2,
|
|
93
|
+
(fromPosition[2] + toPosition[2]) / 2,
|
|
94
|
+
]
|
|
95
|
+
for (const [piece, connectorId, supportsCanonicalPorts] of [
|
|
96
|
+
[fromPiece, fromConnector.id, fromSupportsCanonicalPorts],
|
|
97
|
+
[toPiece, toConnector.id, toSupportsCanonicalPorts],
|
|
98
|
+
] as const) {
|
|
99
|
+
if (!supportsCanonicalPorts) continue
|
|
100
|
+
const frames = connectorFramesByPiece.get(piece.id) ?? new Map<string, RailConnectorFrame>()
|
|
101
|
+
const inverseYaw = ((4 - piece.transform.yawSteps) % 4) as 0 | 1 | 2 | 3
|
|
102
|
+
frames.set(connectorId, {
|
|
103
|
+
...worldFrame,
|
|
104
|
+
position: rotateTrackVector(
|
|
105
|
+
[
|
|
106
|
+
sharedWorldPosition[0] - piece.transform.position[0],
|
|
107
|
+
sharedWorldPosition[1] - piece.transform.position[1],
|
|
108
|
+
sharedWorldPosition[2] - piece.transform.position[2],
|
|
109
|
+
],
|
|
110
|
+
inverseYaw,
|
|
111
|
+
),
|
|
112
|
+
tangent: rotateTrackVector(worldFrame.tangent, inverseYaw),
|
|
113
|
+
})
|
|
114
|
+
connectorFramesByPiece.set(piece.id, frames)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
connectedConnectorIdsByPiece,
|
|
119
|
+
continuousFluidConnectorIdsByPiece,
|
|
120
|
+
continuousSupportConnectorIdsByPiece,
|
|
121
|
+
connectorFramesByPiece,
|
|
122
|
+
}
|
|
123
|
+
}
|