@operato/scene-storage 10.0.0-beta.41 → 10.0.0-beta.43
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 +20 -0
- package/MIGRATION-plan-a-slot-api.md +266 -0
- package/PLAN-A-rack-as-slot-holder.md +164 -0
- package/dist/crane.js +1 -1
- 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/parcel-3d.js +42 -9
- package/dist/parcel-3d.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 +131 -30
- package/dist/storage-rack-3d.js.map +1 -1
- package/dist/storage-rack.d.ts +242 -45
- package/dist/storage-rack.js +684 -106
- package/dist/storage-rack.js.map +1 -1
- package/package.json +3 -3
- package/src/crane.ts +1 -1
- package/src/index.ts +3 -4
- package/src/parcel-3d.ts +41 -9
- 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 +144 -30
- package/src/storage-rack.ts +763 -111
- package/test/test-carrier-lifecycle.ts +361 -0
- package/test/test-coord-alignment.ts +201 -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-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 +2 -0
- package/translations/ja.json +2 -0
- package/translations/ko.json +2 -0
- package/translations/ms.json +2 -0
- package/translations/zh.json +2 -0
- 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 -73
- package/dist/storage-cell.js +0 -215
- 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 -267
- package/test/test-cell-position.ts +0 -105
- 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,11 +23,41 @@ 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
|
|
|
@@ -47,21 +77,7 @@ export class StorageRack3D extends RealObjectGroup {
|
|
|
47
77
|
const beamH = postW * 1.2
|
|
48
78
|
const braceT = postW * 0.6
|
|
49
79
|
|
|
50
|
-
|
|
51
|
-
color: POST_COLOR,
|
|
52
|
-
metalness: 0.7,
|
|
53
|
-
roughness: 0.4
|
|
54
|
-
})
|
|
55
|
-
const beamMaterial = new THREE.MeshStandardMaterial({
|
|
56
|
-
color: BEAM_COLOR,
|
|
57
|
-
metalness: 0.7,
|
|
58
|
-
roughness: 0.4
|
|
59
|
-
})
|
|
60
|
-
const braceMaterial = new THREE.MeshStandardMaterial({
|
|
61
|
-
color: BRACE_COLOR,
|
|
62
|
-
metalness: 0.7,
|
|
63
|
-
roughness: 0.4
|
|
64
|
-
})
|
|
80
|
+
// Material 은 module-level singleton (rack-materials.ts) — 인스턴스 별 생성 X.
|
|
65
81
|
|
|
66
82
|
// ── Uprights (vertical posts at every bay boundary) ──────────────
|
|
67
83
|
// bays + 1 vertical positions; for each, one front post + one back post.
|
|
@@ -76,7 +92,7 @@ export class StorageRack3D extends RealObjectGroup {
|
|
|
76
92
|
postGeos.push(post)
|
|
77
93
|
}
|
|
78
94
|
}
|
|
79
|
-
const postMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(postGeos),
|
|
95
|
+
const postMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(postGeos), POST_MATERIAL)
|
|
80
96
|
postMesh.castShadow = true
|
|
81
97
|
postMesh.receiveShadow = true
|
|
82
98
|
this.object3d.add(postMesh)
|
|
@@ -94,10 +110,14 @@ export class StorageRack3D extends RealObjectGroup {
|
|
|
94
110
|
beamGeos.push(beam)
|
|
95
111
|
}
|
|
96
112
|
}
|
|
97
|
-
const beamMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(beamGeos),
|
|
113
|
+
const beamMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(beamGeos), BEAM_MATERIAL)
|
|
98
114
|
beamMesh.castShadow = true
|
|
99
115
|
beamMesh.receiveShadow = true
|
|
100
|
-
|
|
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)
|
|
101
121
|
|
|
102
122
|
// ── Diagonal cross-bracing on the back face (the "X" pattern) ────
|
|
103
123
|
// Two diagonals per level — "/" and "\" — making an X across each
|
|
@@ -127,7 +147,7 @@ export class StorageRack3D extends RealObjectGroup {
|
|
|
127
147
|
}
|
|
128
148
|
}
|
|
129
149
|
if (braceGeos.length > 0) {
|
|
130
|
-
const braceMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(braceGeos),
|
|
150
|
+
const braceMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(braceGeos), BRACE_MATERIAL)
|
|
131
151
|
braceMesh.castShadow = true
|
|
132
152
|
this.object3d.add(braceMesh)
|
|
133
153
|
}
|
|
@@ -143,14 +163,7 @@ export class StorageRack3D extends RealObjectGroup {
|
|
|
143
163
|
const shelfD = Math.max(0, height - 2 * beamH)
|
|
144
164
|
const shelfGeo = new THREE.PlaneGeometry(shelfW, shelfD)
|
|
145
165
|
shelfGeo.rotateX(-Math.PI / 2) // X-Y plane → X-Z plane (= horizontal)
|
|
146
|
-
const shelfMaterial =
|
|
147
|
-
color: BEAM_COLOR,
|
|
148
|
-
metalness: 0.3,
|
|
149
|
-
roughness: 0.6,
|
|
150
|
-
transparent: true,
|
|
151
|
-
opacity: 0.25,
|
|
152
|
-
side: THREE.DoubleSide
|
|
153
|
-
})
|
|
166
|
+
const shelfMaterial = SHELF_MATERIAL
|
|
154
167
|
for (let lv = 0; lv < levels; lv++) {
|
|
155
168
|
// shelf plane Y = 해당 level 의 *load beam top* 정확 일치 (cell 바닥 면).
|
|
156
169
|
// beam center Y = shelfBaseY + yFrac*shelfZone - beamH/2 + (lv===0 ? beamH : 0)
|
|
@@ -162,6 +175,107 @@ export class StorageRack3D extends RealObjectGroup {
|
|
|
162
175
|
shelf.receiveShadow = true
|
|
163
176
|
this.object3d.add(shelf)
|
|
164
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
|
|
165
279
|
}
|
|
166
280
|
|
|
167
281
|
updateDimension() {}
|