@littlecarlito/blorktools 0.50.3 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/bin/cli.js +69 -0
  2. package/package.json +13 -7
  3. package/src/asset_debugger/axis-indicator/axis-indicator.css +6 -0
  4. package/src/asset_debugger/axis-indicator/axis-indicator.html +20 -0
  5. package/src/asset_debugger/axis-indicator/axis-indicator.js +822 -0
  6. package/src/asset_debugger/debugger-scene/debugger-scene.css +142 -0
  7. package/src/asset_debugger/debugger-scene/debugger-scene.html +80 -0
  8. package/src/asset_debugger/debugger-scene/debugger-scene.js +791 -0
  9. package/src/asset_debugger/header/header.css +73 -0
  10. package/src/asset_debugger/header/header.html +24 -0
  11. package/src/asset_debugger/header/header.js +224 -0
  12. package/src/asset_debugger/index.html +76 -0
  13. package/src/asset_debugger/landing-page/landing-page.css +396 -0
  14. package/src/asset_debugger/landing-page/landing-page.html +81 -0
  15. package/src/asset_debugger/landing-page/landing-page.js +611 -0
  16. package/src/asset_debugger/loading-splash/loading-splash.css +195 -0
  17. package/src/asset_debugger/loading-splash/loading-splash.html +22 -0
  18. package/src/asset_debugger/loading-splash/loading-splash.js +59 -0
  19. package/src/asset_debugger/loading-splash/preview-loading-splash.js +66 -0
  20. package/src/asset_debugger/main.css +14 -0
  21. package/src/asset_debugger/modals/examples-modal/examples-modal.css +41 -0
  22. package/src/asset_debugger/modals/examples-modal/examples-modal.html +18 -0
  23. package/src/asset_debugger/modals/examples-modal/examples-modal.js +111 -0
  24. package/src/asset_debugger/modals/examples-modal/examples.js +125 -0
  25. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.css +452 -0
  26. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.html +87 -0
  27. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.js +675 -0
  28. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.css +219 -0
  29. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.html +20 -0
  30. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.js +548 -0
  31. package/src/asset_debugger/modals/settings-modal/settings-modal.css +103 -0
  32. package/src/asset_debugger/modals/settings-modal/settings-modal.html +158 -0
  33. package/src/asset_debugger/modals/settings-modal/settings-modal.js +475 -0
  34. package/src/asset_debugger/panels/asset-panel/asset-panel.css +263 -0
  35. package/src/asset_debugger/panels/asset-panel/asset-panel.html +123 -0
  36. package/src/asset_debugger/panels/asset-panel/asset-panel.js +136 -0
  37. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.css +94 -0
  38. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.js +312 -0
  39. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.css +129 -0
  40. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.js +486 -0
  41. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.css +545 -0
  42. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.js +538 -0
  43. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.css +70 -0
  44. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.js +586 -0
  45. package/src/asset_debugger/panels/world-panel/world-panel.css +364 -0
  46. package/src/asset_debugger/panels/world-panel/world-panel.html +173 -0
  47. package/src/asset_debugger/panels/world-panel/world-panel.js +1891 -0
  48. package/src/asset_debugger/router.js +190 -0
  49. package/src/asset_debugger/util/animation/playback/animation-playback-controller.js +150 -0
  50. package/src/asset_debugger/util/animation/playback/animation-preview-controller.js +316 -0
  51. package/src/asset_debugger/util/animation/playback/css3d-bounce-controller.js +400 -0
  52. package/src/asset_debugger/util/animation/playback/css3d-reversal-controller.js +821 -0
  53. package/src/asset_debugger/util/animation/render/css3d-prerender-controller.js +696 -0
  54. package/src/asset_debugger/util/animation/render/debug-texture-factory.js +0 -0
  55. package/src/asset_debugger/util/animation/render/iframe2texture-render-controller.js +199 -0
  56. package/src/asset_debugger/util/animation/render/image2texture-prerender-controller.js +461 -0
  57. package/src/asset_debugger/util/animation/render/pbr-material-factory.js +82 -0
  58. package/src/asset_debugger/util/common.css +280 -0
  59. package/src/asset_debugger/util/data/animation-classifier.js +323 -0
  60. package/src/asset_debugger/util/data/duplicate-handler.js +20 -0
  61. package/src/asset_debugger/util/data/glb-buffer-manager.js +407 -0
  62. package/src/asset_debugger/util/data/glb-classifier.js +290 -0
  63. package/src/asset_debugger/util/data/html-formatter.js +76 -0
  64. package/src/asset_debugger/util/data/html-linter.js +276 -0
  65. package/src/asset_debugger/util/data/localstorage-manager.js +265 -0
  66. package/src/asset_debugger/util/data/mesh-html-manager.js +295 -0
  67. package/src/asset_debugger/util/data/string-serder.js +303 -0
  68. package/src/asset_debugger/util/data/texture-classifier.js +663 -0
  69. package/src/asset_debugger/util/data/upload/background-file-handler.js +292 -0
  70. package/src/asset_debugger/util/data/upload/dropzone-preview-controller.js +396 -0
  71. package/src/asset_debugger/util/data/upload/file-upload-manager.js +495 -0
  72. package/src/asset_debugger/util/data/upload/glb-file-handler.js +36 -0
  73. package/src/asset_debugger/util/data/upload/glb-preview-controller.js +317 -0
  74. package/src/asset_debugger/util/data/upload/lighting-file-handler.js +194 -0
  75. package/src/asset_debugger/util/data/upload/model-file-manager.js +104 -0
  76. package/src/asset_debugger/util/data/upload/texture-file-handler.js +166 -0
  77. package/src/asset_debugger/util/data/upload/zip-handler.js +686 -0
  78. package/src/asset_debugger/util/loaders/html2canvas-loader.js +107 -0
  79. package/src/asset_debugger/util/rig/bone-kinematics.js +403 -0
  80. package/src/asset_debugger/util/rig/rig-constraint-manager.js +618 -0
  81. package/src/asset_debugger/util/rig/rig-controller.js +612 -0
  82. package/src/asset_debugger/util/rig/rig-factory.js +628 -0
  83. package/src/asset_debugger/util/rig/rig-handle-factory.js +46 -0
  84. package/src/asset_debugger/util/rig/rig-label-factory.js +441 -0
  85. package/src/asset_debugger/util/rig/rig-mouse-handler.js +377 -0
  86. package/src/asset_debugger/util/rig/rig-state-manager.js +175 -0
  87. package/src/asset_debugger/util/rig/rig-tooltip-manager.js +267 -0
  88. package/src/asset_debugger/util/rig/rig-ui-factory.js +700 -0
  89. package/src/asset_debugger/util/scene/background-manager.js +284 -0
  90. package/src/asset_debugger/util/scene/camera-controller.js +243 -0
  91. package/src/asset_debugger/util/scene/css3d-debug-controller.js +406 -0
  92. package/src/asset_debugger/util/scene/css3d-frame-factory.js +113 -0
  93. package/src/asset_debugger/util/scene/css3d-scene-manager.js +529 -0
  94. package/src/asset_debugger/util/scene/glb-controller.js +208 -0
  95. package/src/asset_debugger/util/scene/lighting-manager.js +690 -0
  96. package/src/asset_debugger/util/scene/threejs-model-manager.js +437 -0
  97. package/src/asset_debugger/util/scene/threejs-preview-manager.js +207 -0
  98. package/src/asset_debugger/util/scene/threejs-preview-setup.js +478 -0
  99. package/src/asset_debugger/util/scene/threejs-scene-controller.js +286 -0
  100. package/src/asset_debugger/util/scene/ui-manager.js +107 -0
  101. package/src/asset_debugger/util/state/animation-state.js +128 -0
  102. package/src/asset_debugger/util/state/css3d-state.js +83 -0
  103. package/src/asset_debugger/util/state/glb-preview-state.js +31 -0
  104. package/src/asset_debugger/util/state/log-util.js +197 -0
  105. package/src/asset_debugger/util/state/scene-state.js +452 -0
  106. package/src/asset_debugger/util/state/threejs-state.js +54 -0
  107. package/src/asset_debugger/util/workers/lighting-worker.js +61 -0
  108. package/src/asset_debugger/util/workers/model-worker.js +109 -0
  109. package/src/asset_debugger/util/workers/texture-worker.js +54 -0
  110. package/src/asset_debugger/util/workers/worker-manager.js +212 -0
  111. package/src/asset_debugger/widgets/mesh-info-widget.js +280 -0
  112. package/src/index.html +261 -0
  113. package/src/index.js +8 -0
  114. package/vite.config.js +66 -0
@@ -0,0 +1,406 @@
1
+ import * as THREE from 'three';
2
+ import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
3
+ import { getState } from '../state/scene-state.js';
4
+ import { calculateMeshTransform, createCSS3DFrame } from './css3d-frame-factory.js';
5
+ import { addFrameToScene } from './css3d-scene-manager.js';
6
+
7
+ const DEBUG_CSS3D_HTML = `<!DOCTYPE html>
8
+ <html>
9
+ <head>
10
+ <style>
11
+ body {
12
+ margin: 0;
13
+ padding: 0;
14
+ background: #000;
15
+ font-family: 'Courier New', monospace;
16
+ color: #fff;
17
+ overflow: hidden;
18
+ height: 100vh;
19
+ width: 100vw;
20
+ display: flex;
21
+ flex-direction: column;
22
+ justify-content: center;
23
+ align-items: center;
24
+ }
25
+ .debug-container {
26
+ text-align: center;
27
+ position: relative;
28
+ width: 100%;
29
+ height: 100%;
30
+ display: flex;
31
+ flex-direction: column;
32
+ justify-content: center;
33
+ align-items: center;
34
+ }
35
+ .rainbow-background {
36
+ position: absolute;
37
+ top: 0;
38
+ left: 0;
39
+ width: 100%;
40
+ height: 100%;
41
+ background: linear-gradient(45deg,
42
+ #ff0000, #ff7f00, #ffff00, #00ff00,
43
+ #0000ff, #4b0082, #9400d3, #ff0000);
44
+ background-size: 400% 400%;
45
+ animation: rainbowShift 3s ease-in-out infinite;
46
+ opacity: 0.8;
47
+ }
48
+ .debug-title {
49
+ font-size: 16px;
50
+ font-weight: bold;
51
+ margin-bottom: 10px;
52
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
53
+ z-index: 10;
54
+ position: relative;
55
+ color: #fff;
56
+ }
57
+ .debug-info {
58
+ font-size: 10px;
59
+ z-index: 10;
60
+ position: relative;
61
+ color: #fff;
62
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
63
+ line-height: 1.4;
64
+ }
65
+ .debug-counter {
66
+ display: inline-block;
67
+ min-width: 20px;
68
+ background: rgba(255, 255, 255, 0.2);
69
+ padding: 1px 4px;
70
+ border-radius: 3px;
71
+ margin: 0 2px;
72
+ }
73
+ .rainbow-overlay {
74
+ position: absolute;
75
+ top: 0;
76
+ left: 0;
77
+ width: 100%;
78
+ height: 100%;
79
+ background: radial-gradient(circle at center,
80
+ transparent 30%,
81
+ rgba(255,255,255,0.1) 70%);
82
+ z-index: 5;
83
+ }
84
+ @keyframes rainbowShift {
85
+ 0% { background-position: 0% 50%; }
86
+ 25% { background-position: 50% 100%; }
87
+ 50% { background-position: 100% 50%; }
88
+ 75% { background-position: 50% 0%; }
89
+ 100% { background-position: 0% 50%; }
90
+ }
91
+ </style>
92
+ </head>
93
+ <body>
94
+ <div class="debug-container">
95
+ <div class="rainbow-background"></div>
96
+ <div class="rainbow-overlay"></div>
97
+ <div class="debug-title">CSS3D DEBUG</div>
98
+ <div class="debug-info">
99
+ FRAME: <span class="debug-counter" id="frameCounter">0</span><br>
100
+ TIME: <span class="debug-counter" id="timeCounter">00:00</span>
101
+ </div>
102
+ </div>
103
+ <script>
104
+ let frameCount = 0;
105
+ let startTime = Date.now();
106
+ function updateCounters() {
107
+ frameCount++;
108
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
109
+ const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0');
110
+ const seconds = (elapsed % 60).toString().padStart(2, '0');
111
+ document.getElementById('frameCounter').textContent = frameCount;
112
+ document.getElementById('timeCounter').textContent = \`\${minutes}:\${seconds}\`;
113
+ }
114
+ setInterval(updateCounters, 100);
115
+ </script>
116
+ </body>
117
+ </html>`;
118
+
119
+ export class CSS3DDebugController {
120
+ constructor() {
121
+ this.renderer = null;
122
+ this.scene = null;
123
+ this.camera = null;
124
+ this.frame = null;
125
+ this.animationId = null;
126
+ this.isInitialized = false;
127
+ this.displayMesh = null;
128
+ this.frameWidth = 300;
129
+ this.frameHeight = 200;
130
+ this.visibilityCheckInterval = null;
131
+ this.lastVisibleState = true;
132
+ this.offsetDistance = 0.001;
133
+ this.dragStateCheckInterval = null;
134
+ }
135
+
136
+ findDisplayMesh() {
137
+ const state = getState();
138
+ if (!state.scene) {
139
+ return null;
140
+ }
141
+
142
+ let foundMesh = null;
143
+
144
+ state.scene.traverse((object) => {
145
+ if (!foundMesh && object.isMesh && object.name && object.name.toLowerCase().includes('display_')) {
146
+ foundMesh = object;
147
+ }
148
+ });
149
+
150
+ return foundMesh;
151
+ }
152
+
153
+ calculateDisplayMeshDimensions(mesh) {
154
+ if (!mesh || !mesh.geometry) {
155
+ throw new Error('Display mesh or geometry not found');
156
+ }
157
+
158
+ const geometry = mesh.geometry;
159
+ const position = geometry.attributes.position;
160
+
161
+ if (!position) {
162
+ throw new Error('Display mesh has no position attribute');
163
+ }
164
+
165
+ geometry.computeBoundingBox();
166
+ const localSize = geometry.boundingBox.getSize(new THREE.Vector3());
167
+
168
+ const worldScale = mesh.getWorldScale(new THREE.Vector3());
169
+ const worldSize = localSize.clone().multiply(worldScale);
170
+
171
+ const dimensions = [
172
+ { size: worldSize.x, axis: 'x' },
173
+ { size: worldSize.y, axis: 'y' },
174
+ { size: worldSize.z, axis: 'z' }
175
+ ].sort((a, b) => b.size - a.size);
176
+
177
+ if (dimensions[0].size <= 0 || dimensions[1].size <= 0) {
178
+ throw new Error('Could not determine rectangular face dimensions from display mesh');
179
+ }
180
+
181
+ const faceWidth = dimensions[0].size;
182
+ const faceHeight = dimensions[1].size;
183
+
184
+ return {
185
+ width: Math.max(50, faceWidth * 1000),
186
+ height: Math.max(50, faceHeight * 1000),
187
+ realWidth: faceWidth,
188
+ realHeight: faceHeight
189
+ };
190
+ }
191
+
192
+ shouldInitialize() {
193
+ const state = getState();
194
+ if (!state.scene) {
195
+ return false;
196
+ }
197
+
198
+ const hasGlbLoaded = state.glbFile || state.modelFile;
199
+ if (!hasGlbLoaded) {
200
+ return false;
201
+ }
202
+
203
+ const displayMesh = this.findDisplayMesh();
204
+ return !!displayMesh;
205
+ }
206
+
207
+ init(parentElement) {
208
+ if (this.isInitialized) {
209
+ return;
210
+ }
211
+
212
+ if (!parentElement) {
213
+ console.error('CSS3DDebugController: Parent element required');
214
+ return;
215
+ }
216
+
217
+ if (!this.shouldInitialize()) {
218
+ return;
219
+ }
220
+
221
+ try {
222
+ this.displayMesh = this.findDisplayMesh();
223
+ if (!this.displayMesh) {
224
+ return;
225
+ }
226
+
227
+ const dimensions = this.calculateDisplayMeshDimensions(this.displayMesh);
228
+ this.frameWidth = dimensions.width;
229
+ this.frameHeight = dimensions.height;
230
+
231
+ const state = getState();
232
+ this.scene = new THREE.Scene();
233
+
234
+ this.camera = state.camera.clone();
235
+
236
+ this.renderer = new CSS3DRenderer();
237
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
238
+ this.renderer.domElement.style.position = 'absolute';
239
+ this.renderer.domElement.style.top = '0px';
240
+ this.renderer.domElement.style.left = '0px';
241
+ this.renderer.domElement.style.width = '100%';
242
+ this.renderer.domElement.style.height = '100%';
243
+ this.renderer.domElement.style.zIndex = '1000';
244
+ this.renderer.domElement.style.pointerEvents = 'none';
245
+
246
+ this.frame = createCSS3DFrame({
247
+ width: this.frameWidth,
248
+ height: this.frameHeight,
249
+ htmlContent: DEBUG_CSS3D_HTML,
250
+ borderColor: '#00ff88',
251
+ boxShadow: '0 0 20px rgba(0, 255, 136, 0.3)'
252
+ });
253
+
254
+ addFrameToScene(this.frame, this.scene, this.displayMesh, {
255
+ realWidth: dimensions.realWidth,
256
+ realHeight: dimensions.realHeight,
257
+ frameWidth: this.frameWidth,
258
+ frameHeight: this.frameHeight,
259
+ offsetDistance: this.offsetDistance
260
+ });
261
+
262
+ parentElement.appendChild(this.renderer.domElement);
263
+
264
+ this.startAnimation();
265
+ this.startVisibilityMonitoring();
266
+ this.startDragStateMonitoring();
267
+ this.isInitialized = true;
268
+ } catch (error) {
269
+ console.error('Error initializing CSS3DDebugController:', error);
270
+ }
271
+ }
272
+
273
+ updateMeshTransform() {
274
+ if (!this.frame || !this.displayMesh) {
275
+ return;
276
+ }
277
+
278
+ const transform = calculateMeshTransform(this.displayMesh, this.offsetDistance);
279
+ this.frame.position.copy(transform.position);
280
+ this.frame.rotation.copy(transform.rotation);
281
+ this.frame.quaternion.copy(transform.quaternion);
282
+ }
283
+
284
+ startVisibilityMonitoring() {
285
+ if (this.visibilityCheckInterval) {
286
+ clearInterval(this.visibilityCheckInterval);
287
+ }
288
+
289
+ this.visibilityCheckInterval = setInterval(() => {
290
+ if (!this.displayMesh || !this.renderer) {
291
+ return;
292
+ }
293
+
294
+ const isVisible = this.displayMesh.visible;
295
+
296
+ if (isVisible !== this.lastVisibleState) {
297
+ this.lastVisibleState = isVisible;
298
+
299
+ if (this.renderer.domElement) {
300
+ this.renderer.domElement.style.display = isVisible ? 'block' : 'none';
301
+ }
302
+ }
303
+
304
+ if (isVisible) {
305
+ this.updateMeshTransform();
306
+ }
307
+ }, 16);
308
+ }
309
+
310
+ startDragStateMonitoring() {
311
+ if (this.dragStateCheckInterval) {
312
+ clearInterval(this.dragStateCheckInterval);
313
+ }
314
+
315
+ this.dragStateCheckInterval = setInterval(() => {
316
+ if (!this.frame || !this.frame.element) {
317
+ return;
318
+ }
319
+
320
+ try {
321
+ const mouseHandlerModule = import('../rig/rig-mouse-handler.js');
322
+ mouseHandlerModule.then(module => {
323
+ const isDragging = module.getIsDragging();
324
+
325
+ if (isDragging) {
326
+ this.frame.element.style.pointerEvents = 'none';
327
+ } else {
328
+ this.frame.element.style.pointerEvents = 'auto';
329
+ }
330
+ }).catch(() => {
331
+ });
332
+ } catch (error) {
333
+ }
334
+ }, 16);
335
+ }
336
+
337
+ startAnimation() {
338
+ if (!this.renderer || !this.scene) {
339
+ return;
340
+ }
341
+
342
+ const animate = () => {
343
+ this.animationId = requestAnimationFrame(animate);
344
+
345
+ if (this.displayMesh && this.displayMesh.visible) {
346
+ this.updateMeshTransform();
347
+ }
348
+
349
+ const state = getState();
350
+ if (state.camera) {
351
+ this.camera.position.copy(state.camera.position);
352
+ this.camera.rotation.copy(state.camera.rotation);
353
+ this.camera.quaternion.copy(state.camera.quaternion);
354
+ this.camera.updateMatrixWorld();
355
+ }
356
+
357
+ this.renderer.render(this.scene, this.camera);
358
+ };
359
+
360
+ animate();
361
+ }
362
+
363
+ cleanup() {
364
+ if (this.visibilityCheckInterval) {
365
+ clearInterval(this.visibilityCheckInterval);
366
+ this.visibilityCheckInterval = null;
367
+ }
368
+
369
+ if (this.dragStateCheckInterval) {
370
+ clearInterval(this.dragStateCheckInterval);
371
+ this.dragStateCheckInterval = null;
372
+ }
373
+
374
+ if (this.animationId) {
375
+ cancelAnimationFrame(this.animationId);
376
+ this.animationId = null;
377
+ }
378
+
379
+ if (this.renderer && this.renderer.domElement) {
380
+ const parent = this.renderer.domElement.parentNode;
381
+ if (parent) {
382
+ parent.removeChild(this.renderer.domElement);
383
+ }
384
+ this.renderer = null;
385
+ }
386
+
387
+ this.scene = null;
388
+ this.camera = null;
389
+ this.frame = null;
390
+ this.displayMesh = null;
391
+ this.lastVisibleState = true;
392
+ this.isInitialized = false;
393
+ }
394
+
395
+ isActive() {
396
+ return this.isInitialized;
397
+ }
398
+
399
+ updatePosition() {
400
+ if (!this.isInitialized || !this.displayMesh || !this.renderer) {
401
+ return;
402
+ }
403
+
404
+ this.updateMeshTransform();
405
+ }
406
+ }
@@ -0,0 +1,113 @@
1
+ import * as THREE from 'three';
2
+ import { CSS3DObject } from "three/examples/jsm/Addons";
3
+
4
+ export function createCSS3DFrame(config) {
5
+ const {
6
+ width,
7
+ height,
8
+ htmlContent,
9
+ borderColor = '#00ff88',
10
+ borderWidth = '2px',
11
+ borderRadius = '8px',
12
+ boxShadow = null,
13
+ pointerEvents = 'auto'
14
+ } = config;
15
+
16
+ const iframe = document.createElement('iframe');
17
+ iframe.style.width = `${width}px`;
18
+ iframe.style.height = `${height}px`;
19
+ iframe.style.border = `${borderWidth} solid ${borderColor}`;
20
+ iframe.style.borderRadius = borderRadius;
21
+ iframe.style.overflow = 'hidden';
22
+ iframe.style.pointerEvents = pointerEvents;
23
+
24
+ if (boxShadow) {
25
+ iframe.style.boxShadow = boxShadow;
26
+ } else {
27
+ iframe.style.boxShadow = `0 0 20px rgba(0, 255, 136, 0.3)`;
28
+ }
29
+
30
+ const css3dObject = new CSS3DObject(iframe);
31
+
32
+ setTimeout(() => {
33
+ if (iframe.contentDocument && htmlContent) {
34
+ iframe.contentDocument.open();
35
+ iframe.contentDocument.write(htmlContent);
36
+ iframe.contentDocument.close();
37
+ }
38
+ }, 100);
39
+
40
+ return css3dObject;
41
+ }
42
+
43
+ export function calculateMeshTransform(mesh, offsetDistance) {
44
+ if (!mesh || !mesh.geometry) {
45
+ return {
46
+ position: new THREE.Vector3(0, 0, 0),
47
+ rotation: new THREE.Euler(0, 0, 0),
48
+ quaternion: new THREE.Quaternion()
49
+ };
50
+ }
51
+
52
+ const geometry = mesh.geometry;
53
+ if (!geometry.boundingBox) {
54
+ geometry.computeBoundingBox();
55
+ }
56
+
57
+ const center = new THREE.Vector3();
58
+ geometry.boundingBox.getCenter(center);
59
+ mesh.localToWorld(center);
60
+
61
+ const meshMatrix = mesh.matrixWorld.clone();
62
+
63
+ const position = new THREE.Vector3();
64
+ const quaternion = new THREE.Quaternion();
65
+ const scale = new THREE.Vector3();
66
+
67
+ meshMatrix.decompose(position, quaternion, scale);
68
+
69
+ // Calculate dimensions to find which axis is thin (the normal)
70
+ const box = geometry.boundingBox;
71
+ const width = box.max.x - box.min.x;
72
+ const height = box.max.y - box.min.y;
73
+ const depth = box.max.z - box.min.z;
74
+
75
+ const dimensions = [
76
+ { size: width, axis: 'x' },
77
+ { size: height, axis: 'y' },
78
+ { size: depth, axis: 'z' }
79
+ ];
80
+
81
+ dimensions.sort((a, b) => a.size - b.size);
82
+
83
+ const tolerance = 0.01;
84
+ if (dimensions[0].size > tolerance) {
85
+ throw new Error(`Display mesh is not rectangular - smallest dimension (${dimensions[0].axis}: ${dimensions[0].size.toFixed(4)}) is too large`);
86
+ }
87
+
88
+ // Determine which plane the mesh lies in based on the thin axis
89
+ const thinAxis = dimensions[0].axis;
90
+
91
+ // CSS3DObject defaults to facing forward (Z+), but we need to rotate it to match the mesh orientation
92
+ let correctionRotation = new THREE.Quaternion();
93
+
94
+ if (thinAxis === 'z') {
95
+ // Mesh is thin along Z, so it lies in XY plane - no correction needed
96
+ correctionRotation.identity();
97
+ } else if (thinAxis === 'y') {
98
+ // Mesh is thin along Y, so it lies in XZ plane - rotate 90° around X to lay flat
99
+ correctionRotation.setFromAxisAngle(new THREE.Vector3(1, 0, 0), -Math.PI / 2);
100
+ } else if (thinAxis === 'x') {
101
+ // Mesh is thin along X, so it lies in YZ plane - rotate 90° around Y to face forward
102
+ correctionRotation.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2);
103
+ }
104
+
105
+ // Combine the mesh's rotation with the correction rotation
106
+ const finalQuaternion = quaternion.clone().multiply(correctionRotation);
107
+
108
+ return {
109
+ position: center,
110
+ rotation: new THREE.Euler().setFromQuaternion(finalQuaternion),
111
+ quaternion: finalQuaternion
112
+ };
113
+ }