@operato/scene-storage 10.0.0-beta.40 → 10.0.0-beta.42
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/CHANGELOG.md +29 -0
- package/MIGRATION-plan-a-slot-api.md +266 -0
- package/PLAN-A-rack-as-slot-holder.md +164 -0
- package/dist/box.js +18 -0
- package/dist/box.js.map +1 -1
- package/dist/crane-3d.d.ts +47 -2
- package/dist/crane-3d.js +246 -89
- package/dist/crane-3d.js.map +1 -1
- package/dist/crane.d.ts +96 -12
- package/dist/crane.js +395 -100
- package/dist/crane.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/pallet.d.ts +15 -0
- package/dist/pallet.js +38 -2
- package/dist/pallet.js.map +1 -1
- package/dist/parcel-3d.js +22 -18
- package/dist/parcel-3d.js.map +1 -1
- package/dist/parcel.d.ts +4 -3
- package/dist/parcel.js +24 -5
- package/dist/parcel.js.map +1 -1
- package/dist/rack-grid-3d.d.ts +18 -7
- package/dist/rack-grid-3d.js +372 -69
- package/dist/rack-grid-3d.js.map +1 -1
- package/dist/rack-grid-cell.d.ts +21 -72
- package/dist/rack-grid-cell.js +147 -243
- package/dist/rack-grid-cell.js.map +1 -1
- package/dist/rack-grid.d.ts +277 -56
- package/dist/rack-grid.js +1230 -695
- package/dist/rack-grid.js.map +1 -1
- package/dist/rack-materials.d.ts +9 -0
- package/dist/rack-materials.js +55 -0
- package/dist/rack-materials.js.map +1 -0
- package/dist/storage-rack-3d.d.ts +15 -0
- package/dist/storage-rack-3d.js +165 -29
- package/dist/storage-rack-3d.js.map +1 -1
- package/dist/storage-rack.d.ts +253 -32
- package/dist/storage-rack.js +726 -66
- package/dist/storage-rack.js.map +1 -1
- package/package.json +3 -3
- package/src/box.ts +18 -0
- package/src/crane-3d.ts +258 -93
- package/src/crane.ts +445 -110
- package/src/index.ts +3 -4
- package/src/pallet.ts +50 -1
- package/src/parcel-3d.ts +23 -18
- package/src/parcel.ts +24 -5
- package/src/rack-grid-3d.ts +383 -80
- package/src/rack-grid-cell.ts +161 -305
- package/src/rack-grid.ts +1263 -762
- package/src/rack-materials.ts +61 -0
- package/src/storage-rack-3d.ts +182 -29
- package/src/storage-rack.ts +819 -67
- package/test/test-carrier-lifecycle.ts +361 -0
- package/test/test-coord-alignment.ts +201 -0
- package/test/test-crane-geometry.ts +167 -0
- package/test/test-external-to-rack.ts +461 -0
- package/test/test-mover-concurrent-bug.ts +304 -0
- package/test/test-mover-rollback.ts +290 -0
- package/test/test-phase-h-carrier-pickable.ts +4 -3
- package/test/test-r19-place-absorb.ts +174 -0
- package/test/test-rack-3d-attach-real.ts +301 -0
- package/test/test-rack-concurrent.ts +254 -0
- package/test/test-rack-edge-cases.ts +323 -0
- package/test/test-rack-grid-cell.ts +318 -0
- package/test/test-rack-grid-location.ts +657 -0
- package/test/test-real-3d-positioning.ts +158 -0
- package/test/test-slot-center-convention.ts +116 -0
- package/test/test-slot-target.ts +189 -0
- package/test/test-storage-rack-batched.ts +606 -0
- package/test/test-storage-rack-click.ts +329 -0
- package/test/test-storage-rack-slot-api.ts +357 -0
- package/test/test-toscene-convention.ts +162 -0
- package/test/test-user-scenario-sequential.ts +334 -0
- package/translations/en.json +7 -1
- package/translations/ja.json +7 -1
- package/translations/ko.json +7 -1
- package/translations/ms.json +7 -1
- package/translations/zh.json +7 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/rack-column.d.ts +0 -35
- package/dist/rack-column.js +0 -258
- package/dist/rack-column.js.map +0 -1
- package/dist/rack-grid-helpers.d.ts +0 -28
- package/dist/rack-grid-helpers.js +0 -71
- package/dist/rack-grid-helpers.js.map +0 -1
- package/dist/rack-grid-location.d.ts +0 -37
- package/dist/rack-grid-location.js +0 -227
- package/dist/rack-grid-location.js.map +0 -1
- package/dist/storage-cell-3d.d.ts +0 -25
- package/dist/storage-cell-3d.js +0 -88
- package/dist/storage-cell-3d.js.map +0 -1
- package/dist/storage-cell.d.ts +0 -70
- package/dist/storage-cell.js +0 -197
- package/dist/storage-cell.js.map +0 -1
- package/src/rack-column.ts +0 -340
- package/src/rack-grid-helpers.ts +0 -77
- package/src/rack-grid-location.ts +0 -286
- package/src/storage-cell-3d.ts +0 -101
- package/src/storage-cell.ts +0 -247
- package/test/test-rack-grid.ts +0 -77
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Shared frame / shelf / stock materials — module-level singleton.
|
|
5
|
+
*
|
|
6
|
+
* StorageRack 과 RackGrid 의 frame mesh material 을 *공유* — 여러 rack 인스턴스가
|
|
7
|
+
* scene 에 있어도 GPU material 1개. 전체 application lifecycle 동안 살아있음
|
|
8
|
+
* (dispose 안 함).
|
|
9
|
+
*
|
|
10
|
+
* 색상 / 속성을 인스턴스별로 *override* 해야 한다면 인스턴스 내 별도 material 생성
|
|
11
|
+
* (현재는 모든 rack 이 같은 색 — singleton 으로 충분).
|
|
12
|
+
*/
|
|
13
|
+
import * as THREE from 'three'
|
|
14
|
+
|
|
15
|
+
const POST_COLOR = 0x6a7080
|
|
16
|
+
const BEAM_COLOR = 0x556070
|
|
17
|
+
const BRACE_COLOR = 0x556070
|
|
18
|
+
|
|
19
|
+
export const POST_MATERIAL = new THREE.MeshStandardMaterial({
|
|
20
|
+
color: POST_COLOR,
|
|
21
|
+
metalness: 0.7,
|
|
22
|
+
roughness: 0.4
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export const BEAM_MATERIAL = new THREE.MeshStandardMaterial({
|
|
26
|
+
color: BEAM_COLOR,
|
|
27
|
+
metalness: 0.7,
|
|
28
|
+
roughness: 0.4
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const BRACE_MATERIAL = new THREE.MeshStandardMaterial({
|
|
32
|
+
color: BRACE_COLOR,
|
|
33
|
+
metalness: 0.7,
|
|
34
|
+
roughness: 0.4
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const SHELF_MATERIAL = new THREE.MeshStandardMaterial({
|
|
38
|
+
color: BEAM_COLOR,
|
|
39
|
+
metalness: 0.3,
|
|
40
|
+
roughness: 0.6,
|
|
41
|
+
transparent: true,
|
|
42
|
+
opacity: 0.25,
|
|
43
|
+
side: THREE.DoubleSide
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
/** Stock — white base, instanceColor 가 최종 색상이 됨. */
|
|
47
|
+
export const STOCK_MATERIAL = new THREE.MeshStandardMaterial({
|
|
48
|
+
color: 0xffffff,
|
|
49
|
+
metalness: 0,
|
|
50
|
+
roughness: 0.9
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
/** Empty stock (record 없는 cell, hideEmptyStock=off 시) — 회색 반투명. */
|
|
54
|
+
export const EMPTY_STOCK_MATERIAL = new THREE.MeshStandardMaterial({
|
|
55
|
+
color: 0x888888,
|
|
56
|
+
metalness: 0,
|
|
57
|
+
roughness: 0.9,
|
|
58
|
+
transparent: true,
|
|
59
|
+
opacity: 0.2,
|
|
60
|
+
depthWrite: false
|
|
61
|
+
})
|
package/src/storage-rack-3d.ts
CHANGED
|
@@ -23,38 +23,61 @@ import * as THREE from 'three'
|
|
|
23
23
|
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
|
24
24
|
import { RealObjectGroup } from '@hatiolab/things-scene'
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
26
|
+
import { POST_MATERIAL, BEAM_MATERIAL, BRACE_MATERIAL, SHELF_MATERIAL, STOCK_MATERIAL } from './rack-materials.js'
|
|
27
|
+
|
|
28
|
+
const BEAM_COLOR = 0x556070 // shelf material 의 color 와 일치 — 일부 코멘트 참조
|
|
29
|
+
|
|
30
|
+
// ── Stock visualization 공용 자원 ────────────────────────────────────────────
|
|
31
|
+
const STOCK_GEOMETRY_CACHE = new Map<string, THREE.BufferGeometry>()
|
|
32
|
+
const STOCK_DEFAULT_COLOR = '#c8a878' // cardboard 색 (legend 매칭 없을 때)
|
|
33
|
+
|
|
34
|
+
function getStockGeometry(w: number, h: number, d: number): THREE.BufferGeometry {
|
|
35
|
+
const k = `${w.toFixed(1)}-${h.toFixed(1)}-${d.toFixed(1)}`
|
|
36
|
+
let g = STOCK_GEOMETRY_CACHE.get(k)
|
|
37
|
+
if (!g) {
|
|
38
|
+
g = new THREE.BoxGeometry(w, d, h) // X=w, Y=d (vertical), Z=h
|
|
39
|
+
STOCK_GEOMETRY_CACHE.set(k, g)
|
|
40
|
+
}
|
|
41
|
+
return g
|
|
42
|
+
}
|
|
29
43
|
|
|
30
44
|
export class StorageRack3D extends RealObjectGroup {
|
|
45
|
+
/** state.data 기반 stock 시각화 InstancedMesh. rebuildStockMesh 가 관리. */
|
|
46
|
+
private _stockMesh?: THREE.InstancedMesh
|
|
47
|
+
/** Horizontal beam 그룹 — hideHorizontalFrame 시 visibility 토글. */
|
|
48
|
+
private _beamGroup?: THREE.Group
|
|
49
|
+
|
|
50
|
+
/** Public read-only — click 핸들러가 instanceId/record 역참조에 사용. */
|
|
51
|
+
get stockMesh(): THREE.InstancedMesh | undefined {
|
|
52
|
+
return this._stockMesh
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** hideHorizontalFrame 변경 시 RackGrid 의 onchange 가 호출 — 즉시 반영. */
|
|
56
|
+
applyFrameVisibility(): void {
|
|
57
|
+
const hide = !!(this.component.state as any)?.hideHorizontalFrame
|
|
58
|
+
if (this._beamGroup) this._beamGroup.visible = !hide
|
|
59
|
+
}
|
|
60
|
+
|
|
31
61
|
build() {
|
|
32
62
|
super.build()
|
|
33
63
|
|
|
34
64
|
const { width, height, depth = 3000 } = this.component.state
|
|
35
65
|
const levels = Math.max(1, Math.floor((this.component.state.levels as number) || 4))
|
|
36
66
|
const bays = Math.max(1, Math.floor((this.component.state.bays as number) || 5))
|
|
67
|
+
const shelfBase = Math.max(0, Math.min(
|
|
68
|
+
(this.component.state.shelfBaseHeight as number) || 0,
|
|
69
|
+
depth * 0.9
|
|
70
|
+
))
|
|
71
|
+
const shelfZone = depth - shelfBase // 실제 shelf 가 차지하는 Y
|
|
37
72
|
|
|
38
|
-
const baseY = -depth / 2
|
|
73
|
+
const baseY = -depth / 2 // rack 바닥 (3D Y 의 최저)
|
|
74
|
+
const shelfBaseY = baseY + shelfBase // 첫 shelf 의 시작 (= level 1 의 바닥)
|
|
39
75
|
const postW = Math.min(width / bays, height) * 0.06
|
|
40
|
-
|
|
76
|
+
// beam 두께 = post 와 비슷 (산업 beam 이 post 보다 약간 두꺼움 — 1.2배)
|
|
77
|
+
const beamH = postW * 1.2
|
|
41
78
|
const braceT = postW * 0.6
|
|
42
79
|
|
|
43
|
-
|
|
44
|
-
color: POST_COLOR,
|
|
45
|
-
metalness: 0.7,
|
|
46
|
-
roughness: 0.4
|
|
47
|
-
})
|
|
48
|
-
const beamMaterial = new THREE.MeshStandardMaterial({
|
|
49
|
-
color: BEAM_COLOR,
|
|
50
|
-
metalness: 0.7,
|
|
51
|
-
roughness: 0.4
|
|
52
|
-
})
|
|
53
|
-
const braceMaterial = new THREE.MeshStandardMaterial({
|
|
54
|
-
color: BRACE_COLOR,
|
|
55
|
-
metalness: 0.7,
|
|
56
|
-
roughness: 0.4
|
|
57
|
-
})
|
|
80
|
+
// Material 은 module-level singleton (rack-materials.ts) — 인스턴스 별 생성 X.
|
|
58
81
|
|
|
59
82
|
// ── Uprights (vertical posts at every bay boundary) ──────────────
|
|
60
83
|
// bays + 1 vertical positions; for each, one front post + one back post.
|
|
@@ -69,17 +92,17 @@ export class StorageRack3D extends RealObjectGroup {
|
|
|
69
92
|
postGeos.push(post)
|
|
70
93
|
}
|
|
71
94
|
}
|
|
72
|
-
const postMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(postGeos),
|
|
95
|
+
const postMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(postGeos), POST_MATERIAL)
|
|
73
96
|
postMesh.castShadow = true
|
|
74
97
|
postMesh.receiveShadow = true
|
|
75
98
|
this.object3d.add(postMesh)
|
|
76
99
|
|
|
77
100
|
// ── Horizontal beams (front + back faces at each level) ──────────
|
|
78
|
-
// levels
|
|
101
|
+
// shelf zone 안 levels+1 위치 (level 0 = shelfBase, level N = 천장).
|
|
79
102
|
const beamGeos: THREE.BufferGeometry[] = []
|
|
80
103
|
for (let lv = 0; lv <= levels; lv++) {
|
|
81
104
|
const yFrac = lv / levels
|
|
82
|
-
const y =
|
|
105
|
+
const y = shelfBaseY + yFrac * shelfZone - beamH / 2 + (lv === 0 ? beamH : 0)
|
|
83
106
|
|
|
84
107
|
for (const zSign of [-1, 1]) {
|
|
85
108
|
const beam = new THREE.BoxGeometry(width, beamH, beamH)
|
|
@@ -87,17 +110,21 @@ export class StorageRack3D extends RealObjectGroup {
|
|
|
87
110
|
beamGeos.push(beam)
|
|
88
111
|
}
|
|
89
112
|
}
|
|
90
|
-
const beamMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(beamGeos),
|
|
113
|
+
const beamMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(beamGeos), BEAM_MATERIAL)
|
|
91
114
|
beamMesh.castShadow = true
|
|
92
115
|
beamMesh.receiveShadow = true
|
|
93
|
-
|
|
116
|
+
// hideHorizontalFrame 즉시 토글 위한 별도 group
|
|
117
|
+
this._beamGroup = new THREE.Group()
|
|
118
|
+
this._beamGroup.add(beamMesh)
|
|
119
|
+
this._beamGroup.visible = !((this.component.state as any)?.hideHorizontalFrame)
|
|
120
|
+
this.object3d.add(this._beamGroup)
|
|
94
121
|
|
|
95
122
|
// ── Diagonal cross-bracing on the back face (the "X" pattern) ────
|
|
96
123
|
// Two diagonals per level — "/" and "\" — making an X across each
|
|
97
124
|
// bay-tall cell. Visual signature of a load-bearing rack.
|
|
98
125
|
const braceGeos: THREE.BufferGeometry[] = []
|
|
99
126
|
const cellW = width / bays
|
|
100
|
-
const cellH =
|
|
127
|
+
const cellH = shelfZone / levels // cell 높이 (shelf zone 안)
|
|
101
128
|
const braceLen = Math.sqrt(cellW * cellW + cellH * cellH)
|
|
102
129
|
const braceAngle = Math.atan2(cellH, cellW)
|
|
103
130
|
const backZ = height / 2 - postW * 0.6
|
|
@@ -109,7 +136,7 @@ export class StorageRack3D extends RealObjectGroup {
|
|
|
109
136
|
const cellCenterX = (bay - bays / 2 + 0.5) * cellW
|
|
110
137
|
|
|
111
138
|
for (let lv = 0; lv < levels; lv++) {
|
|
112
|
-
const cellCenterY =
|
|
139
|
+
const cellCenterY = shelfBaseY + (lv + 0.5) * cellH
|
|
113
140
|
|
|
114
141
|
for (const sign of [-1, 1]) {
|
|
115
142
|
const brace = new THREE.BoxGeometry(braceLen, braceT, braceT)
|
|
@@ -120,10 +147,135 @@ export class StorageRack3D extends RealObjectGroup {
|
|
|
120
147
|
}
|
|
121
148
|
}
|
|
122
149
|
if (braceGeos.length > 0) {
|
|
123
|
-
const braceMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(braceGeos),
|
|
150
|
+
const braceMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(braceGeos), BRACE_MATERIAL)
|
|
124
151
|
braceMesh.castShadow = true
|
|
125
152
|
this.object3d.add(braceMesh)
|
|
126
153
|
}
|
|
154
|
+
|
|
155
|
+
// ── Shelf planes (level 별 반투명 무볼륨 판) ────────────────────────────
|
|
156
|
+
// 각 level 의 *바닥 면* 에 plane — cell 위치 시각 인식. carrier 가 그 위
|
|
157
|
+
// 에 놓이는 *지지면*. 반투명.
|
|
158
|
+
//
|
|
159
|
+
// X-Z 넓이를 *frame 안쪽* 으로 줄여 mesh 겹침 자체 제거 (Z-fight 회피).
|
|
160
|
+
// X: 양 옆 corner post 안쪽 (-postW 양쪽)
|
|
161
|
+
// Z: 앞/뒤 beam 안쪽 (-beamH 양쪽)
|
|
162
|
+
const shelfW = Math.max(0, width - 2 * postW)
|
|
163
|
+
const shelfD = Math.max(0, height - 2 * beamH)
|
|
164
|
+
const shelfGeo = new THREE.PlaneGeometry(shelfW, shelfD)
|
|
165
|
+
shelfGeo.rotateX(-Math.PI / 2) // X-Y plane → X-Z plane (= horizontal)
|
|
166
|
+
const shelfMaterial = SHELF_MATERIAL
|
|
167
|
+
for (let lv = 0; lv < levels; lv++) {
|
|
168
|
+
// shelf plane Y = 해당 level 의 *load beam top* 정확 일치 (cell 바닥 면).
|
|
169
|
+
// beam center Y = shelfBaseY + yFrac*shelfZone - beamH/2 + (lv===0 ? beamH : 0)
|
|
170
|
+
// beam top Y = beam center + beamH/2 = shelfBaseY + yFrac*shelfZone + (lv===0 ? beamH : 0)
|
|
171
|
+
const yFrac = lv / levels
|
|
172
|
+
const y = shelfBaseY + yFrac * shelfZone + (lv === 0 ? beamH : 0)
|
|
173
|
+
const shelf = new THREE.Mesh(shelfGeo, shelfMaterial)
|
|
174
|
+
shelf.position.set(0, y, 0)
|
|
175
|
+
shelf.receiveShadow = true
|
|
176
|
+
this.object3d.add(shelf)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// state.data 가 있으면 stock InstancedMesh 도 빌드
|
|
180
|
+
this.rebuildStockMesh()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* state.data 의 각 record 를 한 InstancedMesh instance 로 렌더링.
|
|
185
|
+
* cellMap 에 있는 cellId 만 instance 부여 — 나머지는 무시.
|
|
186
|
+
* Mover/pickAndPlace 와 무관 — 순수 시각화.
|
|
187
|
+
*/
|
|
188
|
+
rebuildStockMesh(): void {
|
|
189
|
+
if (this._stockMesh) {
|
|
190
|
+
this.object3d.remove(this._stockMesh)
|
|
191
|
+
this._stockMesh = undefined
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const rack: any = this.component
|
|
195
|
+
const data = rack.state?.data as Array<{ cellId: string;[key: string]: any }> | undefined
|
|
196
|
+
if (!Array.isArray(data) || data.length === 0) return
|
|
197
|
+
|
|
198
|
+
const cellMap = rack.cellMap
|
|
199
|
+
if (!cellMap) return
|
|
200
|
+
|
|
201
|
+
// rack 의 기하 파라미터 — cellMap / _ensureCellAttachObject3d 의 levelHeight
|
|
202
|
+
// 계산과 *반드시* 일치해야 함 (shelfBaseHeight > 0 일 때 stock 시각 위치와
|
|
203
|
+
// crane fork target 이 어긋나는 회귀 차단).
|
|
204
|
+
const rs: any = this.component.state
|
|
205
|
+
const rackWidth = (rs?.width as number) || 1000
|
|
206
|
+
const rackDepth = (rs?.depth as number) || 3000
|
|
207
|
+
const rackHeight = (rs?.height as number) || 600
|
|
208
|
+
const bays = Math.max(1, Math.floor(rs?.bays || 5))
|
|
209
|
+
const levels = Math.max(1, Math.floor(rs?.levels || 4))
|
|
210
|
+
const shelfBase = Math.max(0, Math.min((rs?.shelfBaseHeight as number) || 0, rackDepth * 0.9))
|
|
211
|
+
const shelfZone = rackDepth - shelfBase
|
|
212
|
+
const bayWidth = rackWidth / bays
|
|
213
|
+
const levelHeight = shelfZone / levels // ← shelfZone 기준 (cellMap 과 동일)
|
|
214
|
+
const rowDepth = rackHeight
|
|
215
|
+
|
|
216
|
+
// 한 stock 의 크기 — cell 의 85% × 85% × 70%
|
|
217
|
+
const stockW = bayWidth * 0.85
|
|
218
|
+
const stockH = rowDepth * 0.85
|
|
219
|
+
const stockD = levelHeight * 0.7
|
|
220
|
+
const geo = getStockGeometry(stockW, stockH, stockD)
|
|
221
|
+
|
|
222
|
+
// 유효한 record 만 추출
|
|
223
|
+
const valid: { cell: any; record: any }[] = []
|
|
224
|
+
for (const r of data) {
|
|
225
|
+
if (!r?.cellId) continue
|
|
226
|
+
const cell = cellMap.findById(r.cellId)
|
|
227
|
+
if (cell) valid.push({ cell, record: r })
|
|
228
|
+
}
|
|
229
|
+
if (valid.length === 0) return
|
|
230
|
+
|
|
231
|
+
const inst = new THREE.InstancedMesh(geo, STOCK_MATERIAL, valid.length)
|
|
232
|
+
inst.castShadow = false
|
|
233
|
+
inst.receiveShadow = false
|
|
234
|
+
inst.frustumCulled = false
|
|
235
|
+
// things-scene EventManager3D 의 raycast hit-test 가 walk-up 하며 찾는 키:
|
|
236
|
+
// userData.context 에 RealObject 자체 set.
|
|
237
|
+
// 객체 전체 대체가 아니라 속성만 set 해 Three.js 의 기존 userData 필드 보존.
|
|
238
|
+
inst.userData.context = this
|
|
239
|
+
// 클릭 핸들러가 instanceId 로 record 를 역참조할 수 있도록 순서대로 저장.
|
|
240
|
+
inst.userData._records = valid.map(v => v.record)
|
|
241
|
+
|
|
242
|
+
const m = new THREE.Matrix4()
|
|
243
|
+
const pos = new THREE.Vector3()
|
|
244
|
+
const q = new THREE.Quaternion()
|
|
245
|
+
const s = new THREE.Vector3(1, 1, 1)
|
|
246
|
+
const c = new THREE.Color()
|
|
247
|
+
|
|
248
|
+
for (let i = 0; i < valid.length; i++) {
|
|
249
|
+
const { cell, record } = valid[i]
|
|
250
|
+
// cell 중심 (rack-center 좌표계)
|
|
251
|
+
const x = cell.localPosition.x + bayWidth / 2 - rackWidth / 2
|
|
252
|
+
const cellCenterY = cell.localPosition.y + levelHeight / 2 - rackDepth / 2
|
|
253
|
+
// stock 바닥 = cell 바닥 → stock 중심 Y = cell 중심 Y - levelHeight/2 + stockD/2
|
|
254
|
+
const y = cellCenterY - levelHeight / 2 + stockD / 2
|
|
255
|
+
const z = cell.localPosition.z + rowDepth / 2 - rackHeight / 2
|
|
256
|
+
|
|
257
|
+
pos.set(x, y, z)
|
|
258
|
+
m.compose(pos, q, s)
|
|
259
|
+
inst.setMatrixAt(i, m)
|
|
260
|
+
|
|
261
|
+
// Legend 색상 매핑 — rack 의 resolveLegendColor 호출, 매칭 없으면 default
|
|
262
|
+
const resolved = (rack as any).resolveLegendColor?.(record) ?? STOCK_DEFAULT_COLOR
|
|
263
|
+
c.set(resolved)
|
|
264
|
+
inst.setColorAt(i, c)
|
|
265
|
+
}
|
|
266
|
+
inst.instanceMatrix.needsUpdate = true
|
|
267
|
+
if (inst.instanceColor) inst.instanceColor.needsUpdate = true
|
|
268
|
+
|
|
269
|
+
// ── CRITICAL: bounding sphere 계산 ────────────────────────────────────────
|
|
270
|
+
// Three.js InstancedMesh.raycast 는 먼저 bounding sphere 로 broad-phase 체크.
|
|
271
|
+
// setMatrixAt 만 호출하고 computeBoundingSphere 를 안 부르면 sphere.radius=0
|
|
272
|
+
// → 어떤 ray 도 안 맞음 → click event 발화 안 됨. raycaster 가 instance 인식
|
|
273
|
+
// 못 하는 것의 *결정적 원인*. needsUpdate 후 명시 호출 필수.
|
|
274
|
+
inst.computeBoundingSphere()
|
|
275
|
+
inst.computeBoundingBox?.()
|
|
276
|
+
|
|
277
|
+
this.object3d.add(inst)
|
|
278
|
+
this._stockMesh = inst
|
|
127
279
|
}
|
|
128
280
|
|
|
129
281
|
updateDimension() {}
|
|
@@ -134,7 +286,8 @@ export class StorageRack3D extends RealObjectGroup {
|
|
|
134
286
|
'bays' in after ||
|
|
135
287
|
'width' in after ||
|
|
136
288
|
'height' in after ||
|
|
137
|
-
'depth' in after
|
|
289
|
+
'depth' in after ||
|
|
290
|
+
'shelfBaseHeight' in after
|
|
138
291
|
) {
|
|
139
292
|
this.update()
|
|
140
293
|
return
|