@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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [10.0.0-beta.44](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.43...v10.0.0-beta.44) (2026-05-21)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @operato/scene-storage
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [10.0.0-beta.43](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.42...v10.0.0-beta.43) (2026-05-21)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @operato/scene-storage
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
## [10.0.0-beta.42](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.41...v10.0.0-beta.42) (2026-05-21)
|
|
7
23
|
|
|
8
24
|
|
package/dist/box-3d.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ export declare class Box3D extends RealObjectGroup {
|
|
|
3
3
|
build(): void;
|
|
4
4
|
/** Wood crate — visible slats, 4 corner posts, open top. */
|
|
5
5
|
private buildWoodCrate;
|
|
6
|
+
private getWoodGeometries;
|
|
6
7
|
/** Plastic tote — solid molded walls + top stackable lip. */
|
|
7
8
|
private buildPlasticTote;
|
|
9
|
+
private getPlasticGeometries;
|
|
8
10
|
updateDimension(): void;
|
|
9
11
|
onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
|
|
10
12
|
updateAlpha(): void;
|
package/dist/box-3d.js
CHANGED
|
@@ -14,6 +14,49 @@
|
|
|
14
14
|
import * as THREE from 'three';
|
|
15
15
|
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
16
16
|
import { RealObjectGroup } from '@hatiolab/things-scene';
|
|
17
|
+
// ── Material cache by bodyColor (shared across all Box3D instances) ──
|
|
18
|
+
const woodBodyMaterials = new Map();
|
|
19
|
+
const woodPostMaterials = new Map();
|
|
20
|
+
const plasticToteMaterials = new Map();
|
|
21
|
+
const plasticLipMaterials = new Map();
|
|
22
|
+
function getWoodBodyMaterial(bodyColor) {
|
|
23
|
+
let m = woodBodyMaterials.get(bodyColor);
|
|
24
|
+
if (!m) {
|
|
25
|
+
m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0, roughness: 0.85 });
|
|
26
|
+
woodBodyMaterials.set(bodyColor, m);
|
|
27
|
+
}
|
|
28
|
+
return m;
|
|
29
|
+
}
|
|
30
|
+
function getWoodPostMaterial(bodyColor) {
|
|
31
|
+
let m = woodPostMaterials.get(bodyColor);
|
|
32
|
+
if (!m) {
|
|
33
|
+
const tint = new THREE.Color(bodyColor).multiplyScalar(0.8);
|
|
34
|
+
m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0, roughness: 0.9 });
|
|
35
|
+
woodPostMaterials.set(bodyColor, m);
|
|
36
|
+
}
|
|
37
|
+
return m;
|
|
38
|
+
}
|
|
39
|
+
function getPlasticToteMaterial(bodyColor) {
|
|
40
|
+
let m = plasticToteMaterials.get(bodyColor);
|
|
41
|
+
if (!m) {
|
|
42
|
+
m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.05, roughness: 0.55 });
|
|
43
|
+
plasticToteMaterials.set(bodyColor, m);
|
|
44
|
+
}
|
|
45
|
+
return m;
|
|
46
|
+
}
|
|
47
|
+
function getPlasticLipMaterial(bodyColor) {
|
|
48
|
+
let m = plasticLipMaterials.get(bodyColor);
|
|
49
|
+
if (!m) {
|
|
50
|
+
const tint = new THREE.Color(bodyColor).multiplyScalar(0.85);
|
|
51
|
+
m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0.05, roughness: 0.55 });
|
|
52
|
+
plasticLipMaterials.set(bodyColor, m);
|
|
53
|
+
}
|
|
54
|
+
return m;
|
|
55
|
+
}
|
|
56
|
+
// ── Geometry cache by size (shared across all Box3D instances) ──
|
|
57
|
+
// Floor geo는 local 좌표 + mesh position 으로 두어 mesh 별 position 만 다름.
|
|
58
|
+
const woodGeoCache = new Map();
|
|
59
|
+
const plasticGeoCache = new Map();
|
|
17
60
|
export class Box3D extends RealObjectGroup {
|
|
18
61
|
build() {
|
|
19
62
|
super.build();
|
|
@@ -29,24 +72,32 @@ export class Box3D extends RealObjectGroup {
|
|
|
29
72
|
}
|
|
30
73
|
/** Wood crate — visible slats, 4 corner posts, open top. */
|
|
31
74
|
buildWoodCrate(width, height, depth, bodyColor) {
|
|
75
|
+
const { posts, slats, floor, floorY } = this.getWoodGeometries(width, height, depth);
|
|
76
|
+
const woodMaterial = getWoodBodyMaterial(bodyColor);
|
|
77
|
+
const postMaterial = getWoodPostMaterial(bodyColor);
|
|
78
|
+
const postMesh = new THREE.Mesh(posts, postMaterial);
|
|
79
|
+
postMesh.castShadow = true;
|
|
80
|
+
this.object3d.add(postMesh);
|
|
81
|
+
const slatMesh = new THREE.Mesh(slats, woodMaterial);
|
|
82
|
+
slatMesh.castShadow = true;
|
|
83
|
+
slatMesh.receiveShadow = true;
|
|
84
|
+
this.object3d.add(slatMesh);
|
|
85
|
+
const floorMesh = new THREE.Mesh(floor, woodMaterial);
|
|
86
|
+
floorMesh.position.set(0, floorY, 0);
|
|
87
|
+
floorMesh.receiveShadow = true;
|
|
88
|
+
this.object3d.add(floorMesh);
|
|
89
|
+
}
|
|
90
|
+
getWoodGeometries(width, height, depth) {
|
|
91
|
+
const key = `${width}|${height}|${depth}`;
|
|
92
|
+
let cached = woodGeoCache.get(key);
|
|
93
|
+
if (cached)
|
|
94
|
+
return cached;
|
|
32
95
|
const baseY = -depth / 2;
|
|
33
96
|
const wallThickness = Math.min(width, height) * 0.04;
|
|
34
97
|
const postW = wallThickness * 1.6;
|
|
35
98
|
const slatH = depth * 0.10;
|
|
36
99
|
const slatGap = slatH * 0.6;
|
|
37
100
|
const floorH = depth * 0.05;
|
|
38
|
-
const woodMaterial = new THREE.MeshStandardMaterial({
|
|
39
|
-
color: bodyColor,
|
|
40
|
-
metalness: 0,
|
|
41
|
-
roughness: 0.85
|
|
42
|
-
});
|
|
43
|
-
const postColor = new THREE.Color(bodyColor).multiplyScalar(0.8);
|
|
44
|
-
const postMaterial = new THREE.MeshStandardMaterial({
|
|
45
|
-
color: postColor,
|
|
46
|
-
metalness: 0,
|
|
47
|
-
roughness: 0.9
|
|
48
|
-
});
|
|
49
|
-
// ── 4 corner posts ───────────────────────────────────────────────
|
|
50
101
|
const postGeos = [];
|
|
51
102
|
for (const xSign of [-1, 1]) {
|
|
52
103
|
for (const zSign of [-1, 1]) {
|
|
@@ -55,99 +106,87 @@ export class Box3D extends RealObjectGroup {
|
|
|
55
106
|
postGeos.push(post);
|
|
56
107
|
}
|
|
57
108
|
}
|
|
58
|
-
const
|
|
59
|
-
postMesh.castShadow = true;
|
|
60
|
-
this.object3d.add(postMesh);
|
|
61
|
-
// ── Slatted walls (horizontal slats with gaps) ───────────────────
|
|
109
|
+
const posts = BufferGeometryUtils.mergeGeometries(postGeos);
|
|
62
110
|
const slatRowCount = Math.max(2, Math.floor((depth - floorH) / (slatH + slatGap)));
|
|
63
111
|
const slatGeos = [];
|
|
64
112
|
for (let row = 0; row < slatRowCount; row++) {
|
|
65
113
|
const y = baseY + floorH + slatGap + row * (slatH + slatGap) + slatH / 2;
|
|
66
|
-
// Long walls (front / back)
|
|
67
114
|
for (const zSign of [-1, 1]) {
|
|
68
115
|
const slat = new THREE.BoxGeometry(width - postW * 2, slatH, wallThickness);
|
|
69
116
|
slat.translate(0, y, zSign * (height / 2 - wallThickness / 2));
|
|
70
117
|
slatGeos.push(slat);
|
|
71
118
|
}
|
|
72
|
-
// Short walls (left / right)
|
|
73
119
|
for (const xSign of [-1, 1]) {
|
|
74
120
|
const slat = new THREE.BoxGeometry(wallThickness, slatH, height - postW * 2);
|
|
75
121
|
slat.translate(xSign * (width / 2 - wallThickness / 2), y, 0);
|
|
76
122
|
slatGeos.push(slat);
|
|
77
123
|
}
|
|
78
124
|
}
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const floorMesh = new THREE.Mesh(floorGeo, woodMaterial);
|
|
86
|
-
floorMesh.position.set(0, baseY + floorH / 2, 0);
|
|
87
|
-
floorMesh.receiveShadow = true;
|
|
88
|
-
this.object3d.add(floorMesh);
|
|
125
|
+
const slats = BufferGeometryUtils.mergeGeometries(slatGeos);
|
|
126
|
+
const floor = new THREE.BoxGeometry(width - postW * 2, floorH, height - postW * 2);
|
|
127
|
+
const floorY = baseY + floorH / 2;
|
|
128
|
+
cached = { posts, slats, floor, floorY };
|
|
129
|
+
woodGeoCache.set(key, cached);
|
|
130
|
+
return cached;
|
|
89
131
|
}
|
|
90
132
|
/** Plastic tote — solid molded walls + top stackable lip. */
|
|
91
133
|
buildPlasticTote(width, height, depth, bodyColor) {
|
|
134
|
+
const { walls, lip, floor, floorY } = this.getPlasticGeometries(width, height, depth);
|
|
135
|
+
const totMaterial = getPlasticToteMaterial(bodyColor);
|
|
136
|
+
const lipMaterial = getPlasticLipMaterial(bodyColor);
|
|
137
|
+
const wallMesh = new THREE.Mesh(walls, totMaterial);
|
|
138
|
+
wallMesh.castShadow = true;
|
|
139
|
+
wallMesh.receiveShadow = true;
|
|
140
|
+
this.object3d.add(wallMesh);
|
|
141
|
+
const lipMesh = new THREE.Mesh(lip, lipMaterial);
|
|
142
|
+
lipMesh.castShadow = true;
|
|
143
|
+
this.object3d.add(lipMesh);
|
|
144
|
+
const floorMesh = new THREE.Mesh(floor, totMaterial);
|
|
145
|
+
floorMesh.position.set(0, floorY, 0);
|
|
146
|
+
floorMesh.receiveShadow = true;
|
|
147
|
+
this.object3d.add(floorMesh);
|
|
148
|
+
}
|
|
149
|
+
getPlasticGeometries(width, height, depth) {
|
|
150
|
+
const key = `${width}|${height}|${depth}`;
|
|
151
|
+
let cached = plasticGeoCache.get(key);
|
|
152
|
+
if (cached)
|
|
153
|
+
return cached;
|
|
92
154
|
const baseY = -depth / 2;
|
|
93
155
|
const wallThickness = Math.min(width, height) * 0.05;
|
|
94
156
|
const lipH = depth * 0.06;
|
|
95
157
|
const floorH = depth * 0.06;
|
|
96
|
-
const totMaterial = new THREE.MeshStandardMaterial({
|
|
97
|
-
color: bodyColor,
|
|
98
|
-
metalness: 0.05,
|
|
99
|
-
roughness: 0.55
|
|
100
|
-
});
|
|
101
|
-
const lipColor = new THREE.Color(bodyColor).multiplyScalar(0.85);
|
|
102
|
-
const lipMaterial = new THREE.MeshStandardMaterial({
|
|
103
|
-
color: lipColor,
|
|
104
|
-
metalness: 0.05,
|
|
105
|
-
roughness: 0.55
|
|
106
|
-
});
|
|
107
|
-
// ── 4 solid walls ────────────────────────────────────────────────
|
|
108
158
|
const wallGeos = [];
|
|
109
159
|
const wallH = depth - lipH - floorH;
|
|
110
160
|
const wallY = baseY + floorH + wallH / 2;
|
|
111
|
-
// Long walls
|
|
112
161
|
for (const zSign of [-1, 1]) {
|
|
113
162
|
const wall = new THREE.BoxGeometry(width, wallH, wallThickness);
|
|
114
163
|
wall.translate(0, wallY, zSign * (height / 2 - wallThickness / 2));
|
|
115
164
|
wallGeos.push(wall);
|
|
116
165
|
}
|
|
117
|
-
// Short walls
|
|
118
166
|
for (const xSign of [-1, 1]) {
|
|
119
167
|
const wall = new THREE.BoxGeometry(wallThickness, wallH, height - 2 * wallThickness);
|
|
120
168
|
wall.translate(xSign * (width / 2 - wallThickness / 2), wallY, 0);
|
|
121
169
|
wallGeos.push(wall);
|
|
122
170
|
}
|
|
123
|
-
const
|
|
124
|
-
wallMesh.castShadow = true;
|
|
125
|
-
wallMesh.receiveShadow = true;
|
|
126
|
-
this.object3d.add(wallMesh);
|
|
127
|
-
// ── Top lip (stackable rim — slightly wider than walls) ──────────
|
|
171
|
+
const walls = BufferGeometryUtils.mergeGeometries(wallGeos);
|
|
128
172
|
const lipGeos = [];
|
|
129
173
|
const lipY = baseY + depth - lipH / 2;
|
|
130
|
-
// Long sides
|
|
131
174
|
for (const zSign of [-1, 1]) {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
lipGeos.push(
|
|
175
|
+
const lipG = new THREE.BoxGeometry(width * 1.02, lipH, wallThickness * 1.5);
|
|
176
|
+
lipG.translate(0, lipY, zSign * (height / 2 - wallThickness * 0.75));
|
|
177
|
+
lipGeos.push(lipG);
|
|
135
178
|
}
|
|
136
|
-
// Short sides
|
|
137
179
|
for (const xSign of [-1, 1]) {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
lipGeos.push(
|
|
180
|
+
const lipG = new THREE.BoxGeometry(wallThickness * 1.5, lipH, height * 1.02 - 2 * wallThickness * 1.5);
|
|
181
|
+
lipG.translate(xSign * (width / 2 - wallThickness * 0.75), lipY, 0);
|
|
182
|
+
lipGeos.push(lipG);
|
|
141
183
|
}
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
floorMesh.position.set(0, baseY + floorH / 2, 0);
|
|
149
|
-
floorMesh.receiveShadow = true;
|
|
150
|
-
this.object3d.add(floorMesh);
|
|
184
|
+
const lip = BufferGeometryUtils.mergeGeometries(lipGeos);
|
|
185
|
+
const floor = new THREE.BoxGeometry(width, floorH, height);
|
|
186
|
+
const floorY = baseY + floorH / 2;
|
|
187
|
+
cached = { walls, lip, floor, floorY };
|
|
188
|
+
plasticGeoCache.set(key, cached);
|
|
189
|
+
return cached;
|
|
151
190
|
}
|
|
152
191
|
updateDimension() { }
|
|
153
192
|
onchange(after, before) {
|
package/dist/box-3d.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"box-3d.js","sourceRoot":"","sources":["../src/box-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Box 3D — wood crate and plastic tote variants.\n *\n * Wood crate: 4 vertical corner posts + horizontal slats with gaps → the\n * typical industrial wooden crate look. Forklift-friendly.\n * Plastic tote: solid 4 walls + visible top lip / handle cutouts. Stackable.\n *\n * Both have a defined floor (so they look like containers, not just walls)\n * and an opening at top — as you'd expect from a real crate / tote that's\n * open or has a removable lid.\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 Box3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 300 } = 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.buildPlasticTote(width, height, depth, bodyColor)\n } else {\n this.buildWoodCrate(width, height, depth, bodyColor)\n }\n }\n\n /** Wood crate — visible slats, 4 corner posts, open top. */\n private buildWoodCrate(width: number, height: number, depth: number, bodyColor: string) {\n const baseY = -depth / 2\n const wallThickness = Math.min(width, height) * 0.04\n const postW = wallThickness * 1.6\n const slatH = depth * 0.10\n const slatGap = slatH * 0.6\n const floorH = depth * 0.05\n\n const woodMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n metalness: 0,\n roughness: 0.85\n })\n const postColor = new THREE.Color(bodyColor).multiplyScalar(0.8)\n const postMaterial = new THREE.MeshStandardMaterial({\n color: postColor,\n metalness: 0,\n roughness: 0.9\n })\n\n // ── 4 corner posts ───────────────────────────────────────────────\n const postGeos: THREE.BufferGeometry[] = []\n for (const xSign of [-1, 1]) {\n for (const zSign of [-1, 1]) {\n const post = new THREE.BoxGeometry(postW, depth, postW)\n post.translate(\n xSign * (width / 2 - postW / 2),\n baseY + depth / 2,\n zSign * (height / 2 - postW / 2)\n )\n postGeos.push(post)\n }\n }\n const postMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(postGeos), postMaterial)\n postMesh.castShadow = true\n this.object3d.add(postMesh)\n\n // ── Slatted walls (horizontal slats with gaps) ───────────────────\n const slatRowCount = Math.max(2, Math.floor((depth - floorH) / (slatH + slatGap)))\n const slatGeos: THREE.BufferGeometry[] = []\n\n for (let row = 0; row < slatRowCount; row++) {\n const y = baseY + floorH + slatGap + row * (slatH + slatGap) + slatH / 2\n // Long walls (front / back)\n for (const zSign of [-1, 1]) {\n const slat = new THREE.BoxGeometry(width - postW * 2, slatH, wallThickness)\n slat.translate(0, y, zSign * (height / 2 - wallThickness / 2))\n slatGeos.push(slat)\n }\n // Short walls (left / right)\n for (const xSign of [-1, 1]) {\n const slat = new THREE.BoxGeometry(wallThickness, slatH, height - postW * 2)\n slat.translate(xSign * (width / 2 - wallThickness / 2), y, 0)\n slatGeos.push(slat)\n }\n }\n const slatMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(slatGeos), woodMaterial)\n slatMesh.castShadow = true\n slatMesh.receiveShadow = true\n this.object3d.add(slatMesh)\n\n // ── Floor (bottom panel) ─────────────────────────────────────────\n const floorGeo = new THREE.BoxGeometry(width - postW * 2, floorH, height - postW * 2)\n const floorMesh = new THREE.Mesh(floorGeo, woodMaterial)\n floorMesh.position.set(0, baseY + floorH / 2, 0)\n floorMesh.receiveShadow = true\n this.object3d.add(floorMesh)\n }\n\n /** Plastic tote — solid molded walls + top stackable lip. */\n private buildPlasticTote(width: number, height: number, depth: number, bodyColor: string) {\n const baseY = -depth / 2\n const wallThickness = Math.min(width, height) * 0.05\n const lipH = depth * 0.06\n const floorH = depth * 0.06\n\n const totMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n metalness: 0.05,\n roughness: 0.55\n })\n const lipColor = new THREE.Color(bodyColor).multiplyScalar(0.85)\n const lipMaterial = new THREE.MeshStandardMaterial({\n color: lipColor,\n metalness: 0.05,\n roughness: 0.55\n })\n\n // ── 4 solid walls ────────────────────────────────────────────────\n const wallGeos: THREE.BufferGeometry[] = []\n const wallH = depth - lipH - floorH\n const wallY = baseY + floorH + wallH / 2\n\n // Long walls\n for (const zSign of [-1, 1]) {\n const wall = new THREE.BoxGeometry(width, wallH, wallThickness)\n wall.translate(0, wallY, zSign * (height / 2 - wallThickness / 2))\n wallGeos.push(wall)\n }\n // Short walls\n for (const xSign of [-1, 1]) {\n const wall = new THREE.BoxGeometry(wallThickness, wallH, height - 2 * wallThickness)\n wall.translate(xSign * (width / 2 - wallThickness / 2), wallY, 0)\n wallGeos.push(wall)\n }\n const wallMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(wallGeos), totMaterial)\n wallMesh.castShadow = true\n wallMesh.receiveShadow = true\n this.object3d.add(wallMesh)\n\n // ── Top lip (stackable rim — slightly wider than walls) ──────────\n const lipGeos: THREE.BufferGeometry[] = []\n const lipY = baseY + depth - lipH / 2\n // Long sides\n for (const zSign of [-1, 1]) {\n const lip = new THREE.BoxGeometry(width * 1.02, lipH, wallThickness * 1.5)\n lip.translate(0, lipY, zSign * (height / 2 - wallThickness * 0.75))\n lipGeos.push(lip)\n }\n // Short sides\n for (const xSign of [-1, 1]) {\n const lip = new THREE.BoxGeometry(wallThickness * 1.5, lipH, height * 1.02 - 2 * wallThickness * 1.5)\n lip.translate(xSign * (width / 2 - wallThickness * 0.75), lipY, 0)\n lipGeos.push(lip)\n }\n const lipMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(lipGeos), lipMaterial)\n lipMesh.castShadow = true\n this.object3d.add(lipMesh)\n\n // ── Floor (solid bottom) ─────────────────────────────────────────\n const floorGeo = new THREE.BoxGeometry(width, floorH, height)\n const floorMesh = new THREE.Mesh(floorGeo, totMaterial)\n floorMesh.position.set(0, baseY + floorH / 2, 0)\n floorMesh.receiveShadow = true\n this.object3d.add(floorMesh)\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":"box-3d.js","sourceRoot":"","sources":["../src/box-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Box 3D — wood crate and plastic tote variants.\n *\n * Wood crate: 4 vertical corner posts + horizontal slats with gaps → the\n * typical industrial wooden crate look. Forklift-friendly.\n * Plastic tote: solid 4 walls + visible top lip / handle cutouts. Stackable.\n *\n * Both have a defined floor (so they look like containers, not just walls)\n * and an opening at top — as you'd expect from a real crate / tote that's\n * open or has a removable lid.\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 Box3D instances) ──\nconst woodBodyMaterials = new Map<string, THREE.MeshStandardMaterial>()\nconst woodPostMaterials = new Map<string, THREE.MeshStandardMaterial>()\nconst plasticToteMaterials = new Map<string, THREE.MeshStandardMaterial>()\nconst plasticLipMaterials = 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, roughness: 0.85 })\n woodBodyMaterials.set(bodyColor, m)\n }\n return m\n}\n\nfunction getWoodPostMaterial(bodyColor: string): THREE.MeshStandardMaterial {\n let m = woodPostMaterials.get(bodyColor)\n if (!m) {\n const tint = new THREE.Color(bodyColor).multiplyScalar(0.8)\n m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0, roughness: 0.9 })\n woodPostMaterials.set(bodyColor, m)\n }\n return m\n}\n\nfunction getPlasticToteMaterial(bodyColor: string): THREE.MeshStandardMaterial {\n let m = plasticToteMaterials.get(bodyColor)\n if (!m) {\n m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.05, roughness: 0.55 })\n plasticToteMaterials.set(bodyColor, m)\n }\n return m\n}\n\nfunction getPlasticLipMaterial(bodyColor: string): THREE.MeshStandardMaterial {\n let m = plasticLipMaterials.get(bodyColor)\n if (!m) {\n const tint = new THREE.Color(bodyColor).multiplyScalar(0.85)\n m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0.05, roughness: 0.55 })\n plasticLipMaterials.set(bodyColor, m)\n }\n return m\n}\n\n// ── Geometry cache by size (shared across all Box3D instances) ──\n// Floor geo는 local 좌표 + mesh position 으로 두어 mesh 별 position 만 다름.\nconst woodGeoCache = new Map<string, { posts: THREE.BufferGeometry; slats: THREE.BufferGeometry; floor: THREE.BufferGeometry; floorY: number }>()\nconst plasticGeoCache = new Map<string, { walls: THREE.BufferGeometry; lip: THREE.BufferGeometry; floor: THREE.BufferGeometry; floorY: number }>()\n\nexport class Box3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 300 } = 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.buildPlasticTote(width, height, depth, bodyColor)\n } else {\n this.buildWoodCrate(width, height, depth, bodyColor)\n }\n }\n\n /** Wood crate — visible slats, 4 corner posts, open top. */\n private buildWoodCrate(width: number, height: number, depth: number, bodyColor: string) {\n const { posts, slats, floor, floorY } = this.getWoodGeometries(width, height, depth)\n\n const woodMaterial = getWoodBodyMaterial(bodyColor)\n const postMaterial = getWoodPostMaterial(bodyColor)\n\n const postMesh = new THREE.Mesh(posts, postMaterial)\n postMesh.castShadow = true\n this.object3d.add(postMesh)\n\n const slatMesh = new THREE.Mesh(slats, woodMaterial)\n slatMesh.castShadow = true\n slatMesh.receiveShadow = true\n this.object3d.add(slatMesh)\n\n const floorMesh = new THREE.Mesh(floor, woodMaterial)\n floorMesh.position.set(0, floorY, 0)\n floorMesh.receiveShadow = true\n this.object3d.add(floorMesh)\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 wallThickness = Math.min(width, height) * 0.04\n const postW = wallThickness * 1.6\n const slatH = depth * 0.10\n const slatGap = slatH * 0.6\n const floorH = depth * 0.05\n\n const postGeos: THREE.BufferGeometry[] = []\n for (const xSign of [-1, 1]) {\n for (const zSign of [-1, 1]) {\n const post = new THREE.BoxGeometry(postW, depth, postW)\n post.translate(\n xSign * (width / 2 - postW / 2),\n baseY + depth / 2,\n zSign * (height / 2 - postW / 2)\n )\n postGeos.push(post)\n }\n }\n const posts = BufferGeometryUtils.mergeGeometries(postGeos)\n\n const slatRowCount = Math.max(2, Math.floor((depth - floorH) / (slatH + slatGap)))\n const slatGeos: THREE.BufferGeometry[] = []\n for (let row = 0; row < slatRowCount; row++) {\n const y = baseY + floorH + slatGap + row * (slatH + slatGap) + slatH / 2\n for (const zSign of [-1, 1]) {\n const slat = new THREE.BoxGeometry(width - postW * 2, slatH, wallThickness)\n slat.translate(0, y, zSign * (height / 2 - wallThickness / 2))\n slatGeos.push(slat)\n }\n for (const xSign of [-1, 1]) {\n const slat = new THREE.BoxGeometry(wallThickness, slatH, height - postW * 2)\n slat.translate(xSign * (width / 2 - wallThickness / 2), y, 0)\n slatGeos.push(slat)\n }\n }\n const slats = BufferGeometryUtils.mergeGeometries(slatGeos)\n\n const floor = new THREE.BoxGeometry(width - postW * 2, floorH, height - postW * 2)\n const floorY = baseY + floorH / 2\n\n cached = { posts, slats, floor, floorY }\n woodGeoCache.set(key, cached)\n return cached\n }\n\n /** Plastic tote — solid molded walls + top stackable lip. */\n private buildPlasticTote(width: number, height: number, depth: number, bodyColor: string) {\n const { walls, lip, floor, floorY } = this.getPlasticGeometries(width, height, depth)\n\n const totMaterial = getPlasticToteMaterial(bodyColor)\n const lipMaterial = getPlasticLipMaterial(bodyColor)\n\n const wallMesh = new THREE.Mesh(walls, totMaterial)\n wallMesh.castShadow = true\n wallMesh.receiveShadow = true\n this.object3d.add(wallMesh)\n\n const lipMesh = new THREE.Mesh(lip, lipMaterial)\n lipMesh.castShadow = true\n this.object3d.add(lipMesh)\n\n const floorMesh = new THREE.Mesh(floor, totMaterial)\n floorMesh.position.set(0, floorY, 0)\n floorMesh.receiveShadow = true\n this.object3d.add(floorMesh)\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 wallThickness = Math.min(width, height) * 0.05\n const lipH = depth * 0.06\n const floorH = depth * 0.06\n\n const wallGeos: THREE.BufferGeometry[] = []\n const wallH = depth - lipH - floorH\n const wallY = baseY + floorH + wallH / 2\n for (const zSign of [-1, 1]) {\n const wall = new THREE.BoxGeometry(width, wallH, wallThickness)\n wall.translate(0, wallY, zSign * (height / 2 - wallThickness / 2))\n wallGeos.push(wall)\n }\n for (const xSign of [-1, 1]) {\n const wall = new THREE.BoxGeometry(wallThickness, wallH, height - 2 * wallThickness)\n wall.translate(xSign * (width / 2 - wallThickness / 2), wallY, 0)\n wallGeos.push(wall)\n }\n const walls = BufferGeometryUtils.mergeGeometries(wallGeos)\n\n const lipGeos: THREE.BufferGeometry[] = []\n const lipY = baseY + depth - lipH / 2\n for (const zSign of [-1, 1]) {\n const lipG = new THREE.BoxGeometry(width * 1.02, lipH, wallThickness * 1.5)\n lipG.translate(0, lipY, zSign * (height / 2 - wallThickness * 0.75))\n lipGeos.push(lipG)\n }\n for (const xSign of [-1, 1]) {\n const lipG = new THREE.BoxGeometry(wallThickness * 1.5, lipH, height * 1.02 - 2 * wallThickness * 1.5)\n lipG.translate(xSign * (width / 2 - wallThickness * 0.75), lipY, 0)\n lipGeos.push(lipG)\n }\n const lip = BufferGeometryUtils.mergeGeometries(lipGeos)\n\n const floor = new THREE.BoxGeometry(width, floorH, height)\n const floorY = baseY + floorH / 2\n\n cached = { walls, lip, floor, floorY }\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/pallet-3d.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ export declare class Pallet3D extends RealObjectGroup {
|
|
|
3
3
|
build(): void;
|
|
4
4
|
/** Wood EUR-style: 7 top slats + 3 stringers + 5 bottom slats. */
|
|
5
5
|
private buildWood;
|
|
6
|
+
private getWoodGeometries;
|
|
6
7
|
/** Plastic molded: solid top deck + ribbed underside / feet. */
|
|
7
8
|
private buildPlastic;
|
|
9
|
+
private getPlasticGeometries;
|
|
8
10
|
updateDimension(): void;
|
|
9
11
|
onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
|
|
10
12
|
updateAlpha(): void;
|
package/dist/pallet-3d.js
CHANGED
|
@@ -19,6 +19,51 @@
|
|
|
19
19
|
import * as THREE from 'three';
|
|
20
20
|
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
21
21
|
import { RealObjectGroup } from '@hatiolab/things-scene';
|
|
22
|
+
// ── Material cache by bodyColor (shared across all Pallet3D instances) ──
|
|
23
|
+
// 동일 bodyColor 의 pallet 수십~수백 개 → GPU material 인스턴스 *1개로 통합*.
|
|
24
|
+
const woodBodyMaterials = new Map();
|
|
25
|
+
const woodStringerMaterials = new Map();
|
|
26
|
+
const plasticDeckMaterials = new Map();
|
|
27
|
+
const plasticFootMaterials = new Map();
|
|
28
|
+
function getWoodBodyMaterial(bodyColor) {
|
|
29
|
+
let m = woodBodyMaterials.get(bodyColor);
|
|
30
|
+
if (!m) {
|
|
31
|
+
m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.0, roughness: 0.85 });
|
|
32
|
+
woodBodyMaterials.set(bodyColor, m);
|
|
33
|
+
}
|
|
34
|
+
return m;
|
|
35
|
+
}
|
|
36
|
+
function getWoodStringerMaterial(bodyColor) {
|
|
37
|
+
let m = woodStringerMaterials.get(bodyColor);
|
|
38
|
+
if (!m) {
|
|
39
|
+
const tint = new THREE.Color(bodyColor).multiplyScalar(0.85);
|
|
40
|
+
m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0.0, roughness: 0.9 });
|
|
41
|
+
woodStringerMaterials.set(bodyColor, m);
|
|
42
|
+
}
|
|
43
|
+
return m;
|
|
44
|
+
}
|
|
45
|
+
function getPlasticDeckMaterial(bodyColor) {
|
|
46
|
+
let m = plasticDeckMaterials.get(bodyColor);
|
|
47
|
+
if (!m) {
|
|
48
|
+
m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.1, roughness: 0.55 });
|
|
49
|
+
plasticDeckMaterials.set(bodyColor, m);
|
|
50
|
+
}
|
|
51
|
+
return m;
|
|
52
|
+
}
|
|
53
|
+
function getPlasticFootMaterial(bodyColor) {
|
|
54
|
+
let m = plasticFootMaterials.get(bodyColor);
|
|
55
|
+
if (!m) {
|
|
56
|
+
const tint = new THREE.Color(bodyColor).multiplyScalar(0.85);
|
|
57
|
+
m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0.1, roughness: 0.65 });
|
|
58
|
+
plasticFootMaterials.set(bodyColor, m);
|
|
59
|
+
}
|
|
60
|
+
return m;
|
|
61
|
+
}
|
|
62
|
+
// ── Geometry cache by size (shared across all Pallet3D instances) ──
|
|
63
|
+
// 동일 width × height × depth 의 pallet → merged geometry *1세트*만 GPU 에 업로드.
|
|
64
|
+
// Translation / merge 비용도 동일 size pallet 끼리 *1회*만 발생.
|
|
65
|
+
const woodGeoCache = new Map();
|
|
66
|
+
const plasticGeoCache = new Map();
|
|
22
67
|
export class Pallet3D extends RealObjectGroup {
|
|
23
68
|
build() {
|
|
24
69
|
super.build();
|
|
@@ -34,26 +79,30 @@ export class Pallet3D extends RealObjectGroup {
|
|
|
34
79
|
}
|
|
35
80
|
/** Wood EUR-style: 7 top slats + 3 stringers + 5 bottom slats. */
|
|
36
81
|
buildWood(width, height, depth, bodyColor) {
|
|
82
|
+
const { top, stringer, bottom } = this.getWoodGeometries(width, height, depth);
|
|
83
|
+
const woodMaterial = getWoodBodyMaterial(bodyColor);
|
|
84
|
+
const stringerMaterial = getWoodStringerMaterial(bodyColor);
|
|
85
|
+
const topSlatMesh = new THREE.Mesh(top, woodMaterial);
|
|
86
|
+
topSlatMesh.castShadow = true;
|
|
87
|
+
topSlatMesh.receiveShadow = true;
|
|
88
|
+
this.object3d.add(topSlatMesh);
|
|
89
|
+
const stringerMesh = new THREE.Mesh(stringer, stringerMaterial);
|
|
90
|
+
stringerMesh.castShadow = true;
|
|
91
|
+
this.object3d.add(stringerMesh);
|
|
92
|
+
const botSlatMesh = new THREE.Mesh(bottom, woodMaterial);
|
|
93
|
+
botSlatMesh.receiveShadow = true;
|
|
94
|
+
this.object3d.add(botSlatMesh);
|
|
95
|
+
}
|
|
96
|
+
getWoodGeometries(width, height, depth) {
|
|
97
|
+
const key = `${width}|${height}|${depth}`;
|
|
98
|
+
let cached = woodGeoCache.get(key);
|
|
99
|
+
if (cached)
|
|
100
|
+
return cached;
|
|
37
101
|
const baseY = -depth / 2;
|
|
38
102
|
const slatThickness = depth * 0.15;
|
|
39
103
|
const stringerThickness = depth * 0.45;
|
|
40
104
|
const bottomSlatThickness = depth * 0.13;
|
|
41
|
-
const woodMaterial = new THREE.MeshStandardMaterial({
|
|
42
|
-
color: bodyColor,
|
|
43
|
-
metalness: 0.0,
|
|
44
|
-
roughness: 0.85
|
|
45
|
-
});
|
|
46
|
-
const stringerColor = new THREE.Color(bodyColor).multiplyScalar(0.85);
|
|
47
|
-
const stringerMaterial = new THREE.MeshStandardMaterial({
|
|
48
|
-
color: stringerColor,
|
|
49
|
-
metalness: 0.0,
|
|
50
|
-
roughness: 0.9
|
|
51
|
-
});
|
|
52
|
-
// ── Top + bottom slats — same count, same z-positions, paired vertically ─
|
|
53
|
-
// EUR-pallet style: 5 boards on top, 5 below (under the same z ranges so
|
|
54
|
-
// they read as a single skeleton rather than two unrelated grids).
|
|
55
105
|
const slatCount = 5;
|
|
56
|
-
const slatW = width;
|
|
57
106
|
const slatD = (height * 0.92) / (slatCount + (slatCount - 1) * 0.4);
|
|
58
107
|
const gapD = slatD * 0.4;
|
|
59
108
|
const totalSpan = slatCount * slatD + (slatCount - 1) * gapD;
|
|
@@ -64,15 +113,11 @@ export class Pallet3D extends RealObjectGroup {
|
|
|
64
113
|
}
|
|
65
114
|
const topSlatGeos = [];
|
|
66
115
|
for (const z of slatPositions) {
|
|
67
|
-
const slat = new THREE.BoxGeometry(
|
|
116
|
+
const slat = new THREE.BoxGeometry(width, slatThickness, slatD);
|
|
68
117
|
slat.translate(0, baseY + depth - slatThickness / 2, z);
|
|
69
118
|
topSlatGeos.push(slat);
|
|
70
119
|
}
|
|
71
|
-
const
|
|
72
|
-
topSlatMesh.castShadow = true;
|
|
73
|
-
topSlatMesh.receiveShadow = true;
|
|
74
|
-
this.object3d.add(topSlatMesh);
|
|
75
|
-
// ── Stringers (3 perpendicular blocks between top and bottom decks) ─
|
|
120
|
+
const top = BufferGeometryUtils.mergeGeometries(topSlatGeos);
|
|
76
121
|
const stringerCount = 3;
|
|
77
122
|
const stringerW = width * 0.07;
|
|
78
123
|
const stringerY = baseY + bottomSlatThickness + stringerThickness / 2;
|
|
@@ -84,45 +129,48 @@ export class Pallet3D extends RealObjectGroup {
|
|
|
84
129
|
stringer.translate(x, stringerY, 0);
|
|
85
130
|
stringerGeos.push(stringer);
|
|
86
131
|
}
|
|
87
|
-
const
|
|
88
|
-
stringerMesh.castShadow = true;
|
|
89
|
-
this.object3d.add(stringerMesh);
|
|
90
|
-
// ── Bottom slats — same z-positions as top so the deck reads as paired ─
|
|
132
|
+
const stringer = BufferGeometryUtils.mergeGeometries(stringerGeos);
|
|
91
133
|
const botSlatGeos = [];
|
|
92
134
|
for (const z of slatPositions) {
|
|
93
135
|
const slat = new THREE.BoxGeometry(width, bottomSlatThickness, slatD);
|
|
94
136
|
slat.translate(0, baseY + bottomSlatThickness / 2, z);
|
|
95
137
|
botSlatGeos.push(slat);
|
|
96
138
|
}
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
139
|
+
const bottom = BufferGeometryUtils.mergeGeometries(botSlatGeos);
|
|
140
|
+
cached = { top, stringer, bottom };
|
|
141
|
+
woodGeoCache.set(key, cached);
|
|
142
|
+
return cached;
|
|
100
143
|
}
|
|
101
144
|
/** Plastic molded: solid top deck + ribbed underside / feet. */
|
|
102
145
|
buildPlastic(width, height, depth, bodyColor) {
|
|
146
|
+
const { deck, feet, brace } = this.getPlasticGeometries(width, height, depth);
|
|
147
|
+
const deckMaterial = getPlasticDeckMaterial(bodyColor);
|
|
148
|
+
const footMaterial = getPlasticFootMaterial(bodyColor);
|
|
103
149
|
const baseY = -depth / 2;
|
|
104
150
|
const deckThickness = depth * 0.30;
|
|
105
|
-
const
|
|
106
|
-
const footW = width * 0.12;
|
|
107
|
-
const deckMaterial = new THREE.MeshStandardMaterial({
|
|
108
|
-
color: bodyColor,
|
|
109
|
-
metalness: 0.1,
|
|
110
|
-
roughness: 0.55
|
|
111
|
-
});
|
|
112
|
-
const footColor = new THREE.Color(bodyColor).multiplyScalar(0.85);
|
|
113
|
-
const footMaterial = new THREE.MeshStandardMaterial({
|
|
114
|
-
color: footColor,
|
|
115
|
-
metalness: 0.1,
|
|
116
|
-
roughness: 0.65
|
|
117
|
-
});
|
|
118
|
-
// ── Solid top deck ───────────────────────────────────────────────
|
|
119
|
-
const deckGeo = new THREE.BoxGeometry(width * 0.98, deckThickness, height * 0.98);
|
|
120
|
-
const deckMesh = new THREE.Mesh(deckGeo, deckMaterial);
|
|
151
|
+
const deckMesh = new THREE.Mesh(deck, deckMaterial);
|
|
121
152
|
deckMesh.position.set(0, baseY + depth - deckThickness / 2, 0);
|
|
122
153
|
deckMesh.castShadow = true;
|
|
123
154
|
deckMesh.receiveShadow = true;
|
|
124
155
|
this.object3d.add(deckMesh);
|
|
125
|
-
|
|
156
|
+
const footMesh = new THREE.Mesh(feet, footMaterial);
|
|
157
|
+
footMesh.castShadow = true;
|
|
158
|
+
this.object3d.add(footMesh);
|
|
159
|
+
const braceMesh = new THREE.Mesh(brace, footMaterial);
|
|
160
|
+
this.object3d.add(braceMesh);
|
|
161
|
+
}
|
|
162
|
+
getPlasticGeometries(width, height, depth) {
|
|
163
|
+
const key = `${width}|${height}|${depth}`;
|
|
164
|
+
let cached = plasticGeoCache.get(key);
|
|
165
|
+
if (cached)
|
|
166
|
+
return cached;
|
|
167
|
+
const baseY = -depth / 2;
|
|
168
|
+
const deckThickness = depth * 0.30;
|
|
169
|
+
const footH = depth * 0.55;
|
|
170
|
+
const footW = width * 0.12;
|
|
171
|
+
// Deck geo — local space; mesh position applied at instantiation.
|
|
172
|
+
const deck = new THREE.BoxGeometry(width * 0.98, deckThickness, height * 0.98);
|
|
173
|
+
// 9 feet (3×3 grid) → merged geometry with translations baked in.
|
|
126
174
|
const footGeos = [];
|
|
127
175
|
for (let i = -1; i <= 1; i++) {
|
|
128
176
|
for (let j = -1; j <= 1; j++) {
|
|
@@ -133,17 +181,19 @@ export class Pallet3D extends RealObjectGroup {
|
|
|
133
181
|
footGeos.push(foot);
|
|
134
182
|
}
|
|
135
183
|
}
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
this.object3d.add(footMesh);
|
|
139
|
-
// ── Cross-bracing along underside (suggests molded reinforcement) ─
|
|
184
|
+
const feet = BufferGeometryUtils.mergeGeometries(footGeos);
|
|
185
|
+
// 3 braces → merged into single mesh (이전에 mesh 3개 분리 = drawcall 3개).
|
|
140
186
|
const braceH = depth * 0.10;
|
|
141
|
-
const
|
|
187
|
+
const braceGeos = [];
|
|
142
188
|
for (const zSign of [-1, 0, 1]) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
189
|
+
const b = new THREE.BoxGeometry(width * 0.95, braceH, height * 0.04);
|
|
190
|
+
b.translate(0, baseY + footH - braceH / 2, zSign * height * 0.4);
|
|
191
|
+
braceGeos.push(b);
|
|
146
192
|
}
|
|
193
|
+
const brace = BufferGeometryUtils.mergeGeometries(braceGeos);
|
|
194
|
+
cached = { deck, feet, brace };
|
|
195
|
+
plasticGeoCache.set(key, cached);
|
|
196
|
+
return cached;
|
|
147
197
|
}
|
|
148
198
|
updateDimension() { }
|
|
149
199
|
onchange(after, before) {
|