@operato/scene-storage 10.0.0-beta.42 → 10.0.0-beta.44
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 +16 -0
- package/dist/box-3d.d.ts +2 -0
- package/dist/box-3d.js +103 -64
- package/dist/box-3d.js.map +1 -1
- package/dist/pallet-3d.d.ts +2 -0
- package/dist/pallet-3d.js +103 -53
- package/dist/pallet-3d.js.map +1 -1
- package/dist/parcel-3d.js +42 -9
- package/dist/parcel-3d.js.map +1 -1
- package/package.json +2 -2
- package/src/box-3d.ts +121 -68
- package/src/pallet-3d.ts +122 -55
- package/src/parcel-3d.ts +41 -9
- package/tsconfig.tsbuildinfo +1 -1
package/dist/pallet-3d.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pallet-3d.js","sourceRoot":"","sources":["../src/pallet-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Pallet 3D — wood and plastic variants.\n *\n * LO-POLY but structurally distinguishing the two materials:\n *\n * - wood: parallel top slats with gaps between + 3 perpendicular\n * stringers (the classic EUR pallet silhouette) + parallel\n * bottom slats. The forklift entry holes between stringers are\n * the wood pallet's visual signature.\n * - plastic: solid molded top deck (with a few suggestion cutouts as visual\n * detail) + ribbed underside / feet. No discrete slats — the\n * plastic pallet's signature is the seamless one-piece look.\n *\n * Color comes from `state.bodyColor` (Legendable, driven by `material`).\n * Stringer / underside colors are slightly darker tints derived from bodyColor.\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nexport class Pallet3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 150 } = this.component.state\n const material = (this.component.state.material as string) || 'wood'\n const bodyColor = (this.component.state.bodyColor as string) || '#a87644'\n\n if (material === 'plastic') {\n this.buildPlastic(width, height, depth, bodyColor)\n } else {\n this.buildWood(width, height, depth, bodyColor)\n }\n }\n\n /** Wood EUR-style: 7 top slats + 3 stringers + 5 bottom slats. */\n private buildWood(width: number, height: number, depth: number, bodyColor: string) {\n const baseY = -depth / 2\n const slatThickness = depth * 0.15\n const stringerThickness = depth * 0.45\n const bottomSlatThickness = depth * 0.13\n\n const woodMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n metalness: 0.0,\n roughness: 0.85\n })\n const stringerColor = new THREE.Color(bodyColor).multiplyScalar(0.85)\n const stringerMaterial = new THREE.MeshStandardMaterial({\n color: stringerColor,\n metalness: 0.0,\n roughness: 0.9\n })\n\n // ── Top + bottom slats — same count, same z-positions, paired vertically ─\n // EUR-pallet style: 5 boards on top, 5 below (under the same z ranges so\n // they read as a single skeleton rather than two unrelated grids).\n const slatCount = 5\n const slatW = width\n const slatD = (height * 0.92) / (slatCount + (slatCount - 1) * 0.4)\n const gapD = slatD * 0.4\n const totalSpan = slatCount * slatD + (slatCount - 1) * gapD\n const startZ = -totalSpan / 2\n\n const slatPositions: number[] = []\n for (let i = 0; i < slatCount; i++) {\n slatPositions.push(startZ + i * (slatD + gapD) + slatD / 2)\n }\n\n const topSlatGeos: THREE.BufferGeometry[] = []\n for (const z of slatPositions) {\n const slat = new THREE.BoxGeometry(slatW, slatThickness, slatD)\n slat.translate(0, baseY + depth - slatThickness / 2, z)\n topSlatGeos.push(slat)\n }\n const topSlatMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(topSlatGeos), woodMaterial)\n topSlatMesh.castShadow = true\n topSlatMesh.receiveShadow = true\n this.object3d.add(topSlatMesh)\n\n // ── Stringers (3 perpendicular blocks between top and bottom decks) ─\n const stringerCount = 3\n const stringerW = width * 0.07\n const stringerY = baseY + bottomSlatThickness + stringerThickness / 2\n const stringerGeos: THREE.BufferGeometry[] = []\n for (let i = 0; i < stringerCount; i++) {\n const xFrac = i / (stringerCount - 1) - 0.5\n const x = xFrac * (width * 0.85)\n const stringer = new THREE.BoxGeometry(stringerW, stringerThickness, height)\n stringer.translate(x, stringerY, 0)\n stringerGeos.push(stringer)\n }\n const stringerMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(stringerGeos), stringerMaterial)\n stringerMesh.castShadow = true\n this.object3d.add(stringerMesh)\n\n // ── Bottom slats — same z-positions as top so the deck reads as paired ─\n const botSlatGeos: THREE.BufferGeometry[] = []\n for (const z of slatPositions) {\n const slat = new THREE.BoxGeometry(width, bottomSlatThickness, slatD)\n slat.translate(0, baseY + bottomSlatThickness / 2, z)\n botSlatGeos.push(slat)\n }\n const botSlatMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(botSlatGeos), woodMaterial)\n botSlatMesh.receiveShadow = true\n this.object3d.add(botSlatMesh)\n }\n\n /** Plastic molded: solid top deck + ribbed underside / feet. */\n private buildPlastic(width: number, height: number, depth: number, bodyColor: string) {\n const baseY = -depth / 2\n const deckThickness = depth * 0.30\n const footH = depth * 0.55\n const footW = width * 0.12\n\n const deckMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n metalness: 0.1,\n roughness: 0.55\n })\n const footColor = new THREE.Color(bodyColor).multiplyScalar(0.85)\n const footMaterial = new THREE.MeshStandardMaterial({\n color: footColor,\n metalness: 0.1,\n roughness: 0.65\n })\n\n // ── Solid top deck ───────────────────────────────────────────────\n const deckGeo = new THREE.BoxGeometry(width * 0.98, deckThickness, height * 0.98)\n const deckMesh = new THREE.Mesh(deckGeo, deckMaterial)\n deckMesh.position.set(0, baseY + depth - deckThickness / 2, 0)\n deckMesh.castShadow = true\n deckMesh.receiveShadow = true\n this.object3d.add(deckMesh)\n\n // ── 9 feet (3×3 grid — typical plastic pallet underside) ─────────\n const footGeos: THREE.BufferGeometry[] = []\n for (let i = -1; i <= 1; i++) {\n for (let j = -1; j <= 1; j++) {\n const x = i * (width * 0.4)\n const z = j * (height * 0.4)\n const foot = new THREE.BoxGeometry(footW, footH, footW)\n foot.translate(x, baseY + footH / 2, z)\n footGeos.push(foot)\n }\n }\n const footMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(footGeos), footMaterial)\n footMesh.castShadow = true\n this.object3d.add(footMesh)\n\n // ── Cross-bracing along underside (suggests molded reinforcement) ─\n const braceH = depth * 0.10\n const braceGeo = new THREE.BoxGeometry(width * 0.95, braceH, height * 0.04)\n for (const zSign of [-1, 0, 1]) {\n const brace = new THREE.Mesh(braceGeo.clone(), footMaterial)\n brace.position.set(0, baseY + footH - braceH / 2, zSign * height * 0.4)\n this.object3d.add(brace)\n }\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'material' in after ||\n 'bodyColor' in after ||\n 'width' in after ||\n 'height' in after ||\n 'depth' in after\n ) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n updateAlpha() {}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pallet-3d.js","sourceRoot":"","sources":["../src/pallet-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Pallet 3D — wood and plastic variants.\n *\n * LO-POLY but structurally distinguishing the two materials:\n *\n * - wood: parallel top slats with gaps between + 3 perpendicular\n * stringers (the classic EUR pallet silhouette) + parallel\n * bottom slats. The forklift entry holes between stringers are\n * the wood pallet's visual signature.\n * - plastic: solid molded top deck (with a few suggestion cutouts as visual\n * detail) + ribbed underside / feet. No discrete slats — the\n * plastic pallet's signature is the seamless one-piece look.\n *\n * Color comes from `state.bodyColor` (Legendable, driven by `material`).\n * Stringer / underside colors are slightly darker tints derived from bodyColor.\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\n// ── Material cache by bodyColor (shared across all Pallet3D instances) ──\n// 동일 bodyColor 의 pallet 수십~수백 개 → GPU material 인스턴스 *1개로 통합*.\nconst woodBodyMaterials = new Map<string, THREE.MeshStandardMaterial>()\nconst woodStringerMaterials = new Map<string, THREE.MeshStandardMaterial>()\nconst plasticDeckMaterials = new Map<string, THREE.MeshStandardMaterial>()\nconst plasticFootMaterials = new Map<string, THREE.MeshStandardMaterial>()\n\nfunction getWoodBodyMaterial(bodyColor: string): THREE.MeshStandardMaterial {\n let m = woodBodyMaterials.get(bodyColor)\n if (!m) {\n m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.0, roughness: 0.85 })\n woodBodyMaterials.set(bodyColor, m)\n }\n return m\n}\n\nfunction getWoodStringerMaterial(bodyColor: string): THREE.MeshStandardMaterial {\n let m = woodStringerMaterials.get(bodyColor)\n if (!m) {\n const tint = new THREE.Color(bodyColor).multiplyScalar(0.85)\n m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0.0, roughness: 0.9 })\n woodStringerMaterials.set(bodyColor, m)\n }\n return m\n}\n\nfunction getPlasticDeckMaterial(bodyColor: string): THREE.MeshStandardMaterial {\n let m = plasticDeckMaterials.get(bodyColor)\n if (!m) {\n m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.1, roughness: 0.55 })\n plasticDeckMaterials.set(bodyColor, m)\n }\n return m\n}\n\nfunction getPlasticFootMaterial(bodyColor: string): THREE.MeshStandardMaterial {\n let m = plasticFootMaterials.get(bodyColor)\n if (!m) {\n const tint = new THREE.Color(bodyColor).multiplyScalar(0.85)\n m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0.1, roughness: 0.65 })\n plasticFootMaterials.set(bodyColor, m)\n }\n return m\n}\n\n// ── Geometry cache by size (shared across all Pallet3D instances) ──\n// 동일 width × height × depth 의 pallet → merged geometry *1세트*만 GPU 에 업로드.\n// Translation / merge 비용도 동일 size pallet 끼리 *1회*만 발생.\nconst woodGeoCache = new Map<string, { top: THREE.BufferGeometry; stringer: THREE.BufferGeometry; bottom: THREE.BufferGeometry }>()\nconst plasticGeoCache = new Map<string, { deck: THREE.BufferGeometry; feet: THREE.BufferGeometry; brace: THREE.BufferGeometry }>()\n\nexport class Pallet3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 150 } = this.component.state\n const material = (this.component.state.material as string) || 'wood'\n const bodyColor = (this.component.state.bodyColor as string) || '#a87644'\n\n if (material === 'plastic') {\n this.buildPlastic(width, height, depth, bodyColor)\n } else {\n this.buildWood(width, height, depth, bodyColor)\n }\n }\n\n /** Wood EUR-style: 7 top slats + 3 stringers + 5 bottom slats. */\n private buildWood(width: number, height: number, depth: number, bodyColor: string) {\n const { top, stringer, bottom } = this.getWoodGeometries(width, height, depth)\n\n const woodMaterial = getWoodBodyMaterial(bodyColor)\n const stringerMaterial = getWoodStringerMaterial(bodyColor)\n\n const topSlatMesh = new THREE.Mesh(top, woodMaterial)\n topSlatMesh.castShadow = true\n topSlatMesh.receiveShadow = true\n this.object3d.add(topSlatMesh)\n\n const stringerMesh = new THREE.Mesh(stringer, stringerMaterial)\n stringerMesh.castShadow = true\n this.object3d.add(stringerMesh)\n\n const botSlatMesh = new THREE.Mesh(bottom, woodMaterial)\n botSlatMesh.receiveShadow = true\n this.object3d.add(botSlatMesh)\n }\n\n private getWoodGeometries(width: number, height: number, depth: number) {\n const key = `${width}|${height}|${depth}`\n let cached = woodGeoCache.get(key)\n if (cached) return cached\n\n const baseY = -depth / 2\n const slatThickness = depth * 0.15\n const stringerThickness = depth * 0.45\n const bottomSlatThickness = depth * 0.13\n\n const slatCount = 5\n const slatD = (height * 0.92) / (slatCount + (slatCount - 1) * 0.4)\n const gapD = slatD * 0.4\n const totalSpan = slatCount * slatD + (slatCount - 1) * gapD\n const startZ = -totalSpan / 2\n\n const slatPositions: number[] = []\n for (let i = 0; i < slatCount; i++) {\n slatPositions.push(startZ + i * (slatD + gapD) + slatD / 2)\n }\n\n const topSlatGeos: THREE.BufferGeometry[] = []\n for (const z of slatPositions) {\n const slat = new THREE.BoxGeometry(width, slatThickness, slatD)\n slat.translate(0, baseY + depth - slatThickness / 2, z)\n topSlatGeos.push(slat)\n }\n const top = BufferGeometryUtils.mergeGeometries(topSlatGeos)\n\n const stringerCount = 3\n const stringerW = width * 0.07\n const stringerY = baseY + bottomSlatThickness + stringerThickness / 2\n const stringerGeos: THREE.BufferGeometry[] = []\n for (let i = 0; i < stringerCount; i++) {\n const xFrac = i / (stringerCount - 1) - 0.5\n const x = xFrac * (width * 0.85)\n const stringer = new THREE.BoxGeometry(stringerW, stringerThickness, height)\n stringer.translate(x, stringerY, 0)\n stringerGeos.push(stringer)\n }\n const stringer = BufferGeometryUtils.mergeGeometries(stringerGeos)\n\n const botSlatGeos: THREE.BufferGeometry[] = []\n for (const z of slatPositions) {\n const slat = new THREE.BoxGeometry(width, bottomSlatThickness, slatD)\n slat.translate(0, baseY + bottomSlatThickness / 2, z)\n botSlatGeos.push(slat)\n }\n const bottom = BufferGeometryUtils.mergeGeometries(botSlatGeos)\n\n cached = { top, stringer, bottom }\n woodGeoCache.set(key, cached)\n return cached\n }\n\n /** Plastic molded: solid top deck + ribbed underside / feet. */\n private buildPlastic(width: number, height: number, depth: number, bodyColor: string) {\n const { deck, feet, brace } = this.getPlasticGeometries(width, height, depth)\n\n const deckMaterial = getPlasticDeckMaterial(bodyColor)\n const footMaterial = getPlasticFootMaterial(bodyColor)\n\n const baseY = -depth / 2\n const deckThickness = depth * 0.30\n\n const deckMesh = new THREE.Mesh(deck, deckMaterial)\n deckMesh.position.set(0, baseY + depth - deckThickness / 2, 0)\n deckMesh.castShadow = true\n deckMesh.receiveShadow = true\n this.object3d.add(deckMesh)\n\n const footMesh = new THREE.Mesh(feet, footMaterial)\n footMesh.castShadow = true\n this.object3d.add(footMesh)\n\n const braceMesh = new THREE.Mesh(brace, footMaterial)\n this.object3d.add(braceMesh)\n }\n\n private getPlasticGeometries(width: number, height: number, depth: number) {\n const key = `${width}|${height}|${depth}`\n let cached = plasticGeoCache.get(key)\n if (cached) return cached\n\n const baseY = -depth / 2\n const deckThickness = depth * 0.30\n const footH = depth * 0.55\n const footW = width * 0.12\n\n // Deck geo — local space; mesh position applied at instantiation.\n const deck = new THREE.BoxGeometry(width * 0.98, deckThickness, height * 0.98)\n\n // 9 feet (3×3 grid) → merged geometry with translations baked in.\n const footGeos: THREE.BufferGeometry[] = []\n for (let i = -1; i <= 1; i++) {\n for (let j = -1; j <= 1; j++) {\n const x = i * (width * 0.4)\n const z = j * (height * 0.4)\n const foot = new THREE.BoxGeometry(footW, footH, footW)\n foot.translate(x, baseY + footH / 2, z)\n footGeos.push(foot)\n }\n }\n const feet = BufferGeometryUtils.mergeGeometries(footGeos)\n\n // 3 braces → merged into single mesh (이전에 mesh 3개 분리 = drawcall 3개).\n const braceH = depth * 0.10\n const braceGeos: THREE.BufferGeometry[] = []\n for (const zSign of [-1, 0, 1]) {\n const b = new THREE.BoxGeometry(width * 0.95, braceH, height * 0.04)\n b.translate(0, baseY + footH - braceH / 2, zSign * height * 0.4)\n braceGeos.push(b)\n }\n const brace = BufferGeometryUtils.mergeGeometries(braceGeos)\n\n cached = { deck, feet, brace }\n plasticGeoCache.set(key, cached)\n return cached\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'material' in after ||\n 'bodyColor' in after ||\n 'width' in after ||\n 'height' in after ||\n 'depth' in after\n ) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n updateAlpha() {}\n}\n"]}
|
package/dist/parcel-3d.js
CHANGED
|
@@ -37,14 +37,48 @@ const PARCEL_LABEL_MATERIAL = new THREE.MeshStandardMaterial({
|
|
|
37
37
|
metalness: 0,
|
|
38
38
|
roughness: 0.4
|
|
39
39
|
});
|
|
40
|
+
// ── Geometry cache — 같은 (w,h,d) 인 parcel 들 BoxGeometry 공유. 수백 parcel 의
|
|
41
|
+
// GPU memory + setup cost 폭감.
|
|
42
|
+
const _BODY_GEO_CACHE = new Map();
|
|
43
|
+
const _TAPE_GEO_CACHE = new Map();
|
|
44
|
+
const _LABEL_GEO_CACHE = new Map();
|
|
45
|
+
function _key3(a, b, c) {
|
|
46
|
+
return `${a.toFixed(1)}-${b.toFixed(1)}-${c.toFixed(1)}`;
|
|
47
|
+
}
|
|
48
|
+
function _getBodyGeo(w, d, h) {
|
|
49
|
+
const k = _key3(w, d, h);
|
|
50
|
+
let g = _BODY_GEO_CACHE.get(k);
|
|
51
|
+
if (!g) {
|
|
52
|
+
g = new THREE.BoxGeometry(w, d, h);
|
|
53
|
+
_BODY_GEO_CACHE.set(k, g);
|
|
54
|
+
}
|
|
55
|
+
return g;
|
|
56
|
+
}
|
|
57
|
+
function _getTapeGeo(w, t, l) {
|
|
58
|
+
const k = _key3(w, t, l);
|
|
59
|
+
let g = _TAPE_GEO_CACHE.get(k);
|
|
60
|
+
if (!g) {
|
|
61
|
+
g = new THREE.BoxGeometry(w, t, l);
|
|
62
|
+
_TAPE_GEO_CACHE.set(k, g);
|
|
63
|
+
}
|
|
64
|
+
return g;
|
|
65
|
+
}
|
|
66
|
+
function _getLabelGeo(w, t, h) {
|
|
67
|
+
const k = _key3(w, t, h);
|
|
68
|
+
let g = _LABEL_GEO_CACHE.get(k);
|
|
69
|
+
if (!g) {
|
|
70
|
+
g = new THREE.BoxGeometry(w, t, h);
|
|
71
|
+
_LABEL_GEO_CACHE.set(k, g);
|
|
72
|
+
}
|
|
73
|
+
return g;
|
|
74
|
+
}
|
|
40
75
|
export class Parcel3D extends RealObjectGroup {
|
|
41
76
|
build() {
|
|
42
77
|
super.build();
|
|
43
78
|
const { width, height, depth = 150 } = this.component.state;
|
|
44
79
|
const baseY = -depth / 2;
|
|
45
80
|
// ── Main body ────────────────────────────────────────────────────
|
|
46
|
-
const
|
|
47
|
-
const bodyMesh = new THREE.Mesh(bodyGeo, PARCEL_BODY_MATERIAL);
|
|
81
|
+
const bodyMesh = new THREE.Mesh(_getBodyGeo(width, depth, height), PARCEL_BODY_MATERIAL);
|
|
48
82
|
bodyMesh.position.set(0, 0, 0);
|
|
49
83
|
bodyMesh.castShadow = true;
|
|
50
84
|
bodyMesh.receiveShadow = true;
|
|
@@ -53,24 +87,23 @@ export class Parcel3D extends RealObjectGroup {
|
|
|
53
87
|
const tapeW = Math.min(width, height) * 0.10;
|
|
54
88
|
const tapeT = depth * 0.02;
|
|
55
89
|
const tapeAlongLong = width >= height;
|
|
56
|
-
const
|
|
57
|
-
?
|
|
58
|
-
:
|
|
59
|
-
const tapeMesh = new THREE.Mesh(tapeGeo, PARCEL_TAPE_MATERIAL);
|
|
90
|
+
const tapeMesh = new THREE.Mesh(tapeAlongLong
|
|
91
|
+
? _getTapeGeo(width * 1.005, tapeT, tapeW)
|
|
92
|
+
: _getTapeGeo(tapeW, tapeT, height * 1.005), PARCEL_TAPE_MATERIAL);
|
|
60
93
|
tapeMesh.position.set(0, baseY + depth + tapeT / 2 - 0.01, 0);
|
|
94
|
+
// shadow 부담 줄임 — tape 는 얇아 shadow 시각 영향 미미
|
|
61
95
|
this.object3d.add(tapeMesh);
|
|
62
96
|
// ── Shipping label (small white rectangle on top) ────────────────
|
|
63
97
|
const labelW = Math.min(width, height) * 0.35;
|
|
64
98
|
const labelH = labelW * 0.6;
|
|
65
|
-
const
|
|
66
|
-
const labelMesh = new THREE.Mesh(labelGeo, PARCEL_LABEL_MATERIAL);
|
|
67
|
-
// Position on top, off-center by ~25% of long axis
|
|
99
|
+
const labelMesh = new THREE.Mesh(_getLabelGeo(labelW, depth * 0.005, labelH), PARCEL_LABEL_MATERIAL);
|
|
68
100
|
if (tapeAlongLong) {
|
|
69
101
|
labelMesh.position.set(width * 0.2, baseY + depth + depth * 0.0025, -height * 0.15);
|
|
70
102
|
}
|
|
71
103
|
else {
|
|
72
104
|
labelMesh.position.set(width * 0.15, baseY + depth + depth * 0.0025, height * 0.2);
|
|
73
105
|
}
|
|
106
|
+
// shadow 부담 줄임 — label 도 얇아 shadow 시각 영향 미미
|
|
74
107
|
this.object3d.add(labelMesh);
|
|
75
108
|
}
|
|
76
109
|
updateDimension() { }
|
package/dist/parcel-3d.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parcel-3d.js","sourceRoot":"","sources":["../src/parcel-3d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,MAAM,eAAe,GAAG,QAAQ,CAAA;AAChC,MAAM,UAAU,GAAG,QAAQ,CAAA;AAC3B,MAAM,WAAW,GAAG,QAAQ,CAAA;AAE5B,+EAA+E;AAC/E,+DAA+D;AAC/D,iEAAiE;AACjE,8CAA8C;AAC9C,MAAM,oBAAoB,GAAG,IAAI,KAAK,CAAC,oBAAoB,CAAC;IAC1D,KAAK,EAAE,eAAe;IACtB,SAAS,EAAE,CAAC;IACZ,SAAS,EAAE,GAAG;CACf,CAAC,CAAA;AACF,MAAM,oBAAoB,GAAG,IAAI,KAAK,CAAC,oBAAoB,CAAC;IAC1D,KAAK,EAAE,UAAU;IACjB,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,GAAG;CACf,CAAC,CAAA;AACF,MAAM,qBAAqB,GAAG,IAAI,KAAK,CAAC,oBAAoB,CAAC;IAC3D,KAAK,EAAE,WAAW;IAClB,SAAS,EAAE,CAAC;IACZ,SAAS,EAAE,GAAG;CACf,CAAC,CAAA;AAEF,MAAM,
|
|
1
|
+
{"version":3,"file":"parcel-3d.js","sourceRoot":"","sources":["../src/parcel-3d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,MAAM,eAAe,GAAG,QAAQ,CAAA;AAChC,MAAM,UAAU,GAAG,QAAQ,CAAA;AAC3B,MAAM,WAAW,GAAG,QAAQ,CAAA;AAE5B,+EAA+E;AAC/E,+DAA+D;AAC/D,iEAAiE;AACjE,8CAA8C;AAC9C,MAAM,oBAAoB,GAAG,IAAI,KAAK,CAAC,oBAAoB,CAAC;IAC1D,KAAK,EAAE,eAAe;IACtB,SAAS,EAAE,CAAC;IACZ,SAAS,EAAE,GAAG;CACf,CAAC,CAAA;AACF,MAAM,oBAAoB,GAAG,IAAI,KAAK,CAAC,oBAAoB,CAAC;IAC1D,KAAK,EAAE,UAAU;IACjB,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,GAAG;CACf,CAAC,CAAA;AACF,MAAM,qBAAqB,GAAG,IAAI,KAAK,CAAC,oBAAoB,CAAC;IAC3D,KAAK,EAAE,WAAW;IAClB,SAAS,EAAE,CAAC;IACZ,SAAS,EAAE,GAAG;CACf,CAAC,CAAA;AAEF,wEAAwE;AACxE,gCAAgC;AAChC,MAAM,eAAe,GAAG,IAAI,GAAG,EAA6B,CAAA;AAC5D,MAAM,eAAe,GAAG,IAAI,GAAG,EAA6B,CAAA;AAC5D,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAA6B,CAAA;AAE7D,SAAS,KAAK,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IAC5C,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;AAC1D,CAAC;AACD,SAAS,WAAW,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IAClD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACxB,IAAI,CAAC,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAC9B,IAAI,CAAC,CAAC,EAAE,CAAC;QAAC,CAAC,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAC,eAAe,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAAC,CAAC;IACzE,OAAO,CAAC,CAAA;AACV,CAAC;AACD,SAAS,WAAW,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IAClD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACxB,IAAI,CAAC,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAC9B,IAAI,CAAC,CAAC,EAAE,CAAC;QAAC,CAAC,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAC,eAAe,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAAC,CAAC;IACzE,OAAO,CAAC,CAAA;AACV,CAAC;AACD,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IACnD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACxB,IAAI,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAC/B,IAAI,CAAC,CAAC,EAAE,CAAC;QAAC,CAAC,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAAC,CAAC;IAC1E,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,OAAO,QAAS,SAAQ,eAAe;IAC3C,KAAK;QACH,KAAK,CAAC,KAAK,EAAE,CAAA;QAEb,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAA;QAC3D,MAAM,KAAK,GAAG,CAAC,KAAK,GAAG,CAAC,CAAA;QAExB,oEAAoE;QACpE,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,oBAAoB,CAAC,CAAA;QACxF,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QAC9B,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAA;QAC1B,QAAQ,CAAC,aAAa,GAAG,IAAI,CAAA;QAC7B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAE3B,oEAAoE;QACpE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;QAC5C,MAAM,KAAK,GAAG,KAAK,GAAG,IAAI,CAAA;QAC1B,MAAM,aAAa,GAAG,KAAK,IAAI,MAAM,CAAA;QACrC,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,IAAI,CAC7B,aAAa;YACX,CAAC,CAAC,WAAW,CAAC,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC;YAC1C,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,EAC7C,oBAAoB,CACrB,CAAA;QACD,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAA;QAC7D,2CAA2C;QAC3C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAE3B,oEAAoE;QACpE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;QAC7C,MAAM,MAAM,GAAG,MAAM,GAAG,GAAG,CAAA;QAC3B,MAAM,SAAS,GAAG,IAAI,KAAK,CAAC,IAAI,CAC9B,YAAY,CAAC,MAAM,EAAE,KAAK,GAAG,KAAK,EAAE,MAAM,CAAC,EAC3C,qBAAqB,CACtB,CAAA;QACD,IAAI,aAAa,EAAE,CAAC;YAClB,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,GAAG,GAAG,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;QACrF,CAAC;aAAM,CAAC;YACN,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,CAAA;QACpF,CAAC;QACD,4CAA4C;QAC5C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAC9B,CAAC;IAED,eAAe,KAAI,CAAC;IAEpB,QAAQ,CAAC,KAA8B,EAAE,MAA+B;QACtE,IAAI,OAAO,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC;YAC9D,IAAI,CAAC,MAAM,EAAE,CAAA;YACb,OAAM;QACR,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAC/B,CAAC;IAED,WAAW,KAAI,CAAC;CACjB","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Parcel 3D — a cardboard package.\n *\n * Structure:\n * - main body box (cardboard color)\n * - tape line running across the top (the visual signature — what makes\n * this read as a \"shipping parcel\" rather than a generic box)\n * - small label area on top (white rectangle suggesting a shipping label)\n *\n * Kept very simple — parcels in a logistics scene are typically present in\n * large numbers (sortation lines, fulfillment bays), so polygon count\n * matters more than it does for one-off equipment.\n */\n\nimport * as THREE from 'three'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nconst CARDBOARD_COLOR = 0xc8a878\nconst TAPE_COLOR = 0xddc899\nconst LABEL_COLOR = 0xeeeeee\n\n// ── Module-level shared materials ───────────────────────────────────────────\n// Parcel 인스턴스가 수백~수천 가능. instance 별 new MeshStandardMaterial 시\n// material 개수 폭증 → GPU memory + draw call 비효율. static color 라 단일\n// instance 공유. 색 변경 시 모든 Parcel 에 자동 반영 (의도).\nconst PARCEL_BODY_MATERIAL = new THREE.MeshStandardMaterial({\n color: CARDBOARD_COLOR,\n metalness: 0,\n roughness: 0.9\n})\nconst PARCEL_TAPE_MATERIAL = new THREE.MeshStandardMaterial({\n color: TAPE_COLOR,\n metalness: 0.05,\n roughness: 0.5\n})\nconst PARCEL_LABEL_MATERIAL = new THREE.MeshStandardMaterial({\n color: LABEL_COLOR,\n metalness: 0,\n roughness: 0.4\n})\n\n// ── Geometry cache — 같은 (w,h,d) 인 parcel 들 BoxGeometry 공유. 수백 parcel 의\n// GPU memory + setup cost 폭감.\nconst _BODY_GEO_CACHE = new Map<string, THREE.BoxGeometry>()\nconst _TAPE_GEO_CACHE = new Map<string, THREE.BoxGeometry>()\nconst _LABEL_GEO_CACHE = new Map<string, THREE.BoxGeometry>()\n\nfunction _key3(a: number, b: number, c: number): string {\n return `${a.toFixed(1)}-${b.toFixed(1)}-${c.toFixed(1)}`\n}\nfunction _getBodyGeo(w: number, d: number, h: number): THREE.BoxGeometry {\n const k = _key3(w, d, h)\n let g = _BODY_GEO_CACHE.get(k)\n if (!g) { g = new THREE.BoxGeometry(w, d, h); _BODY_GEO_CACHE.set(k, g) }\n return g\n}\nfunction _getTapeGeo(w: number, t: number, l: number): THREE.BoxGeometry {\n const k = _key3(w, t, l)\n let g = _TAPE_GEO_CACHE.get(k)\n if (!g) { g = new THREE.BoxGeometry(w, t, l); _TAPE_GEO_CACHE.set(k, g) }\n return g\n}\nfunction _getLabelGeo(w: number, t: number, h: number): THREE.BoxGeometry {\n const k = _key3(w, t, h)\n let g = _LABEL_GEO_CACHE.get(k)\n if (!g) { g = new THREE.BoxGeometry(w, t, h); _LABEL_GEO_CACHE.set(k, g) }\n return g\n}\n\nexport class Parcel3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 150 } = this.component.state\n const baseY = -depth / 2\n\n // ── Main body ────────────────────────────────────────────────────\n const bodyMesh = new THREE.Mesh(_getBodyGeo(width, depth, height), PARCEL_BODY_MATERIAL)\n bodyMesh.position.set(0, 0, 0)\n bodyMesh.castShadow = true\n bodyMesh.receiveShadow = true\n this.object3d.add(bodyMesh)\n\n // ── Tape line on top (running along the long axis) ───────────────\n const tapeW = Math.min(width, height) * 0.10\n const tapeT = depth * 0.02\n const tapeAlongLong = width >= height\n const tapeMesh = new THREE.Mesh(\n tapeAlongLong\n ? _getTapeGeo(width * 1.005, tapeT, tapeW)\n : _getTapeGeo(tapeW, tapeT, height * 1.005),\n PARCEL_TAPE_MATERIAL\n )\n tapeMesh.position.set(0, baseY + depth + tapeT / 2 - 0.01, 0)\n // shadow 부담 줄임 — tape 는 얇아 shadow 시각 영향 미미\n this.object3d.add(tapeMesh)\n\n // ── Shipping label (small white rectangle on top) ────────────────\n const labelW = Math.min(width, height) * 0.35\n const labelH = labelW * 0.6\n const labelMesh = new THREE.Mesh(\n _getLabelGeo(labelW, depth * 0.005, labelH),\n PARCEL_LABEL_MATERIAL\n )\n if (tapeAlongLong) {\n labelMesh.position.set(width * 0.2, baseY + depth + depth * 0.0025, -height * 0.15)\n } else {\n labelMesh.position.set(width * 0.15, baseY + depth + depth * 0.0025, height * 0.2)\n }\n // shadow 부담 줄임 — label 도 얇아 shadow 시각 영향 미미\n this.object3d.add(labelMesh)\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if ('width' in after || 'height' in after || 'depth' in after) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n updateAlpha() {}\n}\n"]}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@operato/scene-storage",
|
|
3
3
|
"description": "Storage-domain components for things-scene (smart factory / logistics) — pallet, box, parcel; AS/RS and shelves planned.",
|
|
4
4
|
"author": "heartyoh",
|
|
5
|
-
"version": "10.0.0-beta.
|
|
5
|
+
"version": "10.0.0-beta.44",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"module": "dist/index.js",
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"typescript": "^5.0.4"
|
|
46
46
|
},
|
|
47
47
|
"prettier": "@hatiolab/prettier-config",
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "c9abb002847f21c028102ba5eb489fb57b2dd95e"
|
|
49
49
|
}
|
package/src/box-3d.ts
CHANGED
|
@@ -16,6 +16,55 @@ import * as THREE from 'three'
|
|
|
16
16
|
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
|
17
17
|
import { RealObjectGroup } from '@hatiolab/things-scene'
|
|
18
18
|
|
|
19
|
+
// ── Material cache by bodyColor (shared across all Box3D instances) ──
|
|
20
|
+
const woodBodyMaterials = new Map<string, THREE.MeshStandardMaterial>()
|
|
21
|
+
const woodPostMaterials = new Map<string, THREE.MeshStandardMaterial>()
|
|
22
|
+
const plasticToteMaterials = new Map<string, THREE.MeshStandardMaterial>()
|
|
23
|
+
const plasticLipMaterials = new Map<string, THREE.MeshStandardMaterial>()
|
|
24
|
+
|
|
25
|
+
function getWoodBodyMaterial(bodyColor: string): THREE.MeshStandardMaterial {
|
|
26
|
+
let m = woodBodyMaterials.get(bodyColor)
|
|
27
|
+
if (!m) {
|
|
28
|
+
m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0, roughness: 0.85 })
|
|
29
|
+
woodBodyMaterials.set(bodyColor, m)
|
|
30
|
+
}
|
|
31
|
+
return m
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getWoodPostMaterial(bodyColor: string): THREE.MeshStandardMaterial {
|
|
35
|
+
let m = woodPostMaterials.get(bodyColor)
|
|
36
|
+
if (!m) {
|
|
37
|
+
const tint = new THREE.Color(bodyColor).multiplyScalar(0.8)
|
|
38
|
+
m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0, roughness: 0.9 })
|
|
39
|
+
woodPostMaterials.set(bodyColor, m)
|
|
40
|
+
}
|
|
41
|
+
return m
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getPlasticToteMaterial(bodyColor: string): THREE.MeshStandardMaterial {
|
|
45
|
+
let m = plasticToteMaterials.get(bodyColor)
|
|
46
|
+
if (!m) {
|
|
47
|
+
m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.05, roughness: 0.55 })
|
|
48
|
+
plasticToteMaterials.set(bodyColor, m)
|
|
49
|
+
}
|
|
50
|
+
return m
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getPlasticLipMaterial(bodyColor: string): THREE.MeshStandardMaterial {
|
|
54
|
+
let m = plasticLipMaterials.get(bodyColor)
|
|
55
|
+
if (!m) {
|
|
56
|
+
const tint = new THREE.Color(bodyColor).multiplyScalar(0.85)
|
|
57
|
+
m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0.05, roughness: 0.55 })
|
|
58
|
+
plasticLipMaterials.set(bodyColor, m)
|
|
59
|
+
}
|
|
60
|
+
return m
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Geometry cache by size (shared across all Box3D instances) ──
|
|
64
|
+
// Floor geo는 local 좌표 + mesh position 으로 두어 mesh 별 position 만 다름.
|
|
65
|
+
const woodGeoCache = new Map<string, { posts: THREE.BufferGeometry; slats: THREE.BufferGeometry; floor: THREE.BufferGeometry; floorY: number }>()
|
|
66
|
+
const plasticGeoCache = new Map<string, { walls: THREE.BufferGeometry; lip: THREE.BufferGeometry; floor: THREE.BufferGeometry; floorY: number }>()
|
|
67
|
+
|
|
19
68
|
export class Box3D extends RealObjectGroup {
|
|
20
69
|
build() {
|
|
21
70
|
super.build()
|
|
@@ -33,6 +82,31 @@ export class Box3D extends RealObjectGroup {
|
|
|
33
82
|
|
|
34
83
|
/** Wood crate — visible slats, 4 corner posts, open top. */
|
|
35
84
|
private buildWoodCrate(width: number, height: number, depth: number, bodyColor: string) {
|
|
85
|
+
const { posts, slats, floor, floorY } = this.getWoodGeometries(width, height, depth)
|
|
86
|
+
|
|
87
|
+
const woodMaterial = getWoodBodyMaterial(bodyColor)
|
|
88
|
+
const postMaterial = getWoodPostMaterial(bodyColor)
|
|
89
|
+
|
|
90
|
+
const postMesh = new THREE.Mesh(posts, postMaterial)
|
|
91
|
+
postMesh.castShadow = true
|
|
92
|
+
this.object3d.add(postMesh)
|
|
93
|
+
|
|
94
|
+
const slatMesh = new THREE.Mesh(slats, woodMaterial)
|
|
95
|
+
slatMesh.castShadow = true
|
|
96
|
+
slatMesh.receiveShadow = true
|
|
97
|
+
this.object3d.add(slatMesh)
|
|
98
|
+
|
|
99
|
+
const floorMesh = new THREE.Mesh(floor, woodMaterial)
|
|
100
|
+
floorMesh.position.set(0, floorY, 0)
|
|
101
|
+
floorMesh.receiveShadow = true
|
|
102
|
+
this.object3d.add(floorMesh)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private getWoodGeometries(width: number, height: number, depth: number) {
|
|
106
|
+
const key = `${width}|${height}|${depth}`
|
|
107
|
+
let cached = woodGeoCache.get(key)
|
|
108
|
+
if (cached) return cached
|
|
109
|
+
|
|
36
110
|
const baseY = -depth / 2
|
|
37
111
|
const wallThickness = Math.min(width, height) * 0.04
|
|
38
112
|
const postW = wallThickness * 1.6
|
|
@@ -40,19 +114,6 @@ export class Box3D extends RealObjectGroup {
|
|
|
40
114
|
const slatGap = slatH * 0.6
|
|
41
115
|
const floorH = depth * 0.05
|
|
42
116
|
|
|
43
|
-
const woodMaterial = new THREE.MeshStandardMaterial({
|
|
44
|
-
color: bodyColor,
|
|
45
|
-
metalness: 0,
|
|
46
|
-
roughness: 0.85
|
|
47
|
-
})
|
|
48
|
-
const postColor = new THREE.Color(bodyColor).multiplyScalar(0.8)
|
|
49
|
-
const postMaterial = new THREE.MeshStandardMaterial({
|
|
50
|
-
color: postColor,
|
|
51
|
-
metalness: 0,
|
|
52
|
-
roughness: 0.9
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
// ── 4 corner posts ───────────────────────────────────────────────
|
|
56
117
|
const postGeos: THREE.BufferGeometry[] = []
|
|
57
118
|
for (const xSign of [-1, 1]) {
|
|
58
119
|
for (const zSign of [-1, 1]) {
|
|
@@ -65,108 +126,100 @@ export class Box3D extends RealObjectGroup {
|
|
|
65
126
|
postGeos.push(post)
|
|
66
127
|
}
|
|
67
128
|
}
|
|
68
|
-
const
|
|
69
|
-
postMesh.castShadow = true
|
|
70
|
-
this.object3d.add(postMesh)
|
|
129
|
+
const posts = BufferGeometryUtils.mergeGeometries(postGeos)
|
|
71
130
|
|
|
72
|
-
// ── Slatted walls (horizontal slats with gaps) ───────────────────
|
|
73
131
|
const slatRowCount = Math.max(2, Math.floor((depth - floorH) / (slatH + slatGap)))
|
|
74
132
|
const slatGeos: THREE.BufferGeometry[] = []
|
|
75
|
-
|
|
76
133
|
for (let row = 0; row < slatRowCount; row++) {
|
|
77
134
|
const y = baseY + floorH + slatGap + row * (slatH + slatGap) + slatH / 2
|
|
78
|
-
// Long walls (front / back)
|
|
79
135
|
for (const zSign of [-1, 1]) {
|
|
80
136
|
const slat = new THREE.BoxGeometry(width - postW * 2, slatH, wallThickness)
|
|
81
137
|
slat.translate(0, y, zSign * (height / 2 - wallThickness / 2))
|
|
82
138
|
slatGeos.push(slat)
|
|
83
139
|
}
|
|
84
|
-
// Short walls (left / right)
|
|
85
140
|
for (const xSign of [-1, 1]) {
|
|
86
141
|
const slat = new THREE.BoxGeometry(wallThickness, slatH, height - postW * 2)
|
|
87
142
|
slat.translate(xSign * (width / 2 - wallThickness / 2), y, 0)
|
|
88
143
|
slatGeos.push(slat)
|
|
89
144
|
}
|
|
90
145
|
}
|
|
91
|
-
const
|
|
92
|
-
slatMesh.castShadow = true
|
|
93
|
-
slatMesh.receiveShadow = true
|
|
94
|
-
this.object3d.add(slatMesh)
|
|
146
|
+
const slats = BufferGeometryUtils.mergeGeometries(slatGeos)
|
|
95
147
|
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
148
|
+
const floor = new THREE.BoxGeometry(width - postW * 2, floorH, height - postW * 2)
|
|
149
|
+
const floorY = baseY + floorH / 2
|
|
150
|
+
|
|
151
|
+
cached = { posts, slats, floor, floorY }
|
|
152
|
+
woodGeoCache.set(key, cached)
|
|
153
|
+
return cached
|
|
102
154
|
}
|
|
103
155
|
|
|
104
156
|
/** Plastic tote — solid molded walls + top stackable lip. */
|
|
105
157
|
private buildPlasticTote(width: number, height: number, depth: number, bodyColor: string) {
|
|
158
|
+
const { walls, lip, floor, floorY } = this.getPlasticGeometries(width, height, depth)
|
|
159
|
+
|
|
160
|
+
const totMaterial = getPlasticToteMaterial(bodyColor)
|
|
161
|
+
const lipMaterial = getPlasticLipMaterial(bodyColor)
|
|
162
|
+
|
|
163
|
+
const wallMesh = new THREE.Mesh(walls, totMaterial)
|
|
164
|
+
wallMesh.castShadow = true
|
|
165
|
+
wallMesh.receiveShadow = true
|
|
166
|
+
this.object3d.add(wallMesh)
|
|
167
|
+
|
|
168
|
+
const lipMesh = new THREE.Mesh(lip, lipMaterial)
|
|
169
|
+
lipMesh.castShadow = true
|
|
170
|
+
this.object3d.add(lipMesh)
|
|
171
|
+
|
|
172
|
+
const floorMesh = new THREE.Mesh(floor, totMaterial)
|
|
173
|
+
floorMesh.position.set(0, floorY, 0)
|
|
174
|
+
floorMesh.receiveShadow = true
|
|
175
|
+
this.object3d.add(floorMesh)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private getPlasticGeometries(width: number, height: number, depth: number) {
|
|
179
|
+
const key = `${width}|${height}|${depth}`
|
|
180
|
+
let cached = plasticGeoCache.get(key)
|
|
181
|
+
if (cached) return cached
|
|
182
|
+
|
|
106
183
|
const baseY = -depth / 2
|
|
107
184
|
const wallThickness = Math.min(width, height) * 0.05
|
|
108
185
|
const lipH = depth * 0.06
|
|
109
186
|
const floorH = depth * 0.06
|
|
110
187
|
|
|
111
|
-
const totMaterial = new THREE.MeshStandardMaterial({
|
|
112
|
-
color: bodyColor,
|
|
113
|
-
metalness: 0.05,
|
|
114
|
-
roughness: 0.55
|
|
115
|
-
})
|
|
116
|
-
const lipColor = new THREE.Color(bodyColor).multiplyScalar(0.85)
|
|
117
|
-
const lipMaterial = new THREE.MeshStandardMaterial({
|
|
118
|
-
color: lipColor,
|
|
119
|
-
metalness: 0.05,
|
|
120
|
-
roughness: 0.55
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
// ── 4 solid walls ────────────────────────────────────────────────
|
|
124
188
|
const wallGeos: THREE.BufferGeometry[] = []
|
|
125
189
|
const wallH = depth - lipH - floorH
|
|
126
190
|
const wallY = baseY + floorH + wallH / 2
|
|
127
|
-
|
|
128
|
-
// Long walls
|
|
129
191
|
for (const zSign of [-1, 1]) {
|
|
130
192
|
const wall = new THREE.BoxGeometry(width, wallH, wallThickness)
|
|
131
193
|
wall.translate(0, wallY, zSign * (height / 2 - wallThickness / 2))
|
|
132
194
|
wallGeos.push(wall)
|
|
133
195
|
}
|
|
134
|
-
// Short walls
|
|
135
196
|
for (const xSign of [-1, 1]) {
|
|
136
197
|
const wall = new THREE.BoxGeometry(wallThickness, wallH, height - 2 * wallThickness)
|
|
137
198
|
wall.translate(xSign * (width / 2 - wallThickness / 2), wallY, 0)
|
|
138
199
|
wallGeos.push(wall)
|
|
139
200
|
}
|
|
140
|
-
const
|
|
141
|
-
wallMesh.castShadow = true
|
|
142
|
-
wallMesh.receiveShadow = true
|
|
143
|
-
this.object3d.add(wallMesh)
|
|
201
|
+
const walls = BufferGeometryUtils.mergeGeometries(wallGeos)
|
|
144
202
|
|
|
145
|
-
// ── Top lip (stackable rim — slightly wider than walls) ──────────
|
|
146
203
|
const lipGeos: THREE.BufferGeometry[] = []
|
|
147
204
|
const lipY = baseY + depth - lipH / 2
|
|
148
|
-
// Long sides
|
|
149
205
|
for (const zSign of [-1, 1]) {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
lipGeos.push(
|
|
206
|
+
const lipG = new THREE.BoxGeometry(width * 1.02, lipH, wallThickness * 1.5)
|
|
207
|
+
lipG.translate(0, lipY, zSign * (height / 2 - wallThickness * 0.75))
|
|
208
|
+
lipGeos.push(lipG)
|
|
153
209
|
}
|
|
154
|
-
// Short sides
|
|
155
210
|
for (const xSign of [-1, 1]) {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
lipGeos.push(
|
|
211
|
+
const lipG = new THREE.BoxGeometry(wallThickness * 1.5, lipH, height * 1.02 - 2 * wallThickness * 1.5)
|
|
212
|
+
lipG.translate(xSign * (width / 2 - wallThickness * 0.75), lipY, 0)
|
|
213
|
+
lipGeos.push(lipG)
|
|
159
214
|
}
|
|
160
|
-
const
|
|
161
|
-
lipMesh.castShadow = true
|
|
162
|
-
this.object3d.add(lipMesh)
|
|
215
|
+
const lip = BufferGeometryUtils.mergeGeometries(lipGeos)
|
|
163
216
|
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
217
|
+
const floor = new THREE.BoxGeometry(width, floorH, height)
|
|
218
|
+
const floorY = baseY + floorH / 2
|
|
219
|
+
|
|
220
|
+
cached = { walls, lip, floor, floorY }
|
|
221
|
+
plasticGeoCache.set(key, cached)
|
|
222
|
+
return cached
|
|
170
223
|
}
|
|
171
224
|
|
|
172
225
|
updateDimension() {}
|