@nice2dev/ui-3d 1.0.0 → 1.0.3

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 (204) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/dist/cjs/collaborative/collaborativeScene.js +210 -0
  3. package/dist/cjs/collaborative/collaborativeScene.js.map +1 -0
  4. package/dist/cjs/core/i18n.js +3 -3
  5. package/dist/cjs/core/i18n.js.map +1 -1
  6. package/dist/cjs/dance/DanceBridge.js +162 -0
  7. package/dist/cjs/dance/DanceBridge.js.map +1 -0
  8. package/dist/cjs/dance/DanceScoreEngine.js +210 -0
  9. package/dist/cjs/dance/DanceScoreEngine.js.map +1 -0
  10. package/dist/cjs/dance/PoseDetector.js +199 -0
  11. package/dist/cjs/dance/PoseDetector.js.map +1 -0
  12. package/dist/cjs/index.js +254 -0
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/material/MaterialEditor.module.css.js +6 -0
  15. package/dist/cjs/material/MaterialEditor.module.css.js.map +1 -0
  16. package/dist/cjs/material/NiceMaterialEditor.js +737 -0
  17. package/dist/cjs/material/NiceMaterialEditor.js.map +1 -0
  18. package/dist/cjs/material/materialEditorTypes.js +73 -0
  19. package/dist/cjs/material/materialEditorTypes.js.map +1 -0
  20. package/dist/cjs/material/materialEditorUtils.js +841 -0
  21. package/dist/cjs/material/materialEditorUtils.js.map +1 -0
  22. package/dist/cjs/material/materialNodeDefinitions.js +1285 -0
  23. package/dist/cjs/material/materialNodeDefinitions.js.map +1 -0
  24. package/dist/cjs/model/ModelEditor.js +4 -1
  25. package/dist/cjs/model/ModelEditor.js.map +1 -1
  26. package/dist/cjs/model/ModelEditor.module.css.js +1 -1
  27. package/dist/cjs/model/ModelEditorLeftPanel.js +5 -4
  28. package/dist/cjs/model/ModelEditorLeftPanel.js.map +1 -1
  29. package/dist/cjs/model/ModelEditorMenuBar.js +8 -3
  30. package/dist/cjs/model/ModelEditorMenuBar.js.map +1 -1
  31. package/dist/cjs/model/ModelEditorRightPanel.js +27 -26
  32. package/dist/cjs/model/ModelEditorRightPanel.js.map +1 -1
  33. package/dist/cjs/model/ModelEditorSubComponents.js +20 -16
  34. package/dist/cjs/model/ModelEditorSubComponents.js.map +1 -1
  35. package/dist/cjs/model/ModelEditorTimeline.js +5 -4
  36. package/dist/cjs/model/ModelEditorTimeline.js.map +1 -1
  37. package/dist/cjs/model/ModelEditorToolbar.js +4 -3
  38. package/dist/cjs/model/ModelEditorToolbar.js.map +1 -1
  39. package/dist/cjs/model/ModelEditorViewport.js +2 -2
  40. package/dist/cjs/model/ModelEditorViewport.js.map +1 -1
  41. package/dist/cjs/model/ModelViewer.js +68 -0
  42. package/dist/cjs/model/ModelViewer.js.map +1 -0
  43. package/dist/cjs/model/ModelViewer.module.css.js +6 -0
  44. package/dist/cjs/model/ModelViewer.module.css.js.map +1 -0
  45. package/dist/cjs/model/NiceArmatureEditor.js +255 -0
  46. package/dist/cjs/model/NiceArmatureEditor.js.map +1 -0
  47. package/dist/cjs/model/NiceMorphTargetEditor.js +206 -0
  48. package/dist/cjs/model/NiceMorphTargetEditor.js.map +1 -0
  49. package/dist/cjs/model/NiceOctree.js +339 -0
  50. package/dist/cjs/model/NiceOctree.js.map +1 -0
  51. package/dist/cjs/model/NicePhysicsSimulation.js +283 -0
  52. package/dist/cjs/model/NicePhysicsSimulation.js.map +1 -0
  53. package/dist/cjs/model/NiceProceduralGeometry.js +269 -0
  54. package/dist/cjs/model/NiceProceduralGeometry.js.map +1 -0
  55. package/dist/cjs/model/NiceTerrainEditor.js +343 -0
  56. package/dist/cjs/model/NiceTerrainEditor.js.map +1 -0
  57. package/dist/cjs/model/NiceWeightPainter.js +258 -0
  58. package/dist/cjs/model/NiceWeightPainter.js.map +1 -0
  59. package/dist/cjs/model/NiceXRPreview.js +269 -0
  60. package/dist/cjs/model/NiceXRPreview.js.map +1 -0
  61. package/dist/cjs/model/cadModeUtils.js +130 -0
  62. package/dist/cjs/model/cadModeUtils.js.map +1 -0
  63. package/dist/cjs/model/editorShortcuts.js +187 -0
  64. package/dist/cjs/model/editorShortcuts.js.map +1 -0
  65. package/dist/cjs/model/modelEditorTypes.js +11 -0
  66. package/dist/cjs/model/modelEditorTypes.js.map +1 -1
  67. package/dist/cjs/model/modelEditorUtils.js +1049 -0
  68. package/dist/cjs/model/modelEditorUtils.js.map +1 -0
  69. package/dist/cjs/model/simsModeUtils.js +358 -0
  70. package/dist/cjs/model/simsModeUtils.js.map +1 -0
  71. package/dist/cjs/model/useModelEditor.js +319 -115
  72. package/dist/cjs/model/useModelEditor.js.map +1 -1
  73. package/dist/cjs/model/useModelViewer.js +634 -0
  74. package/dist/cjs/model/useModelViewer.js.map +1 -0
  75. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  76. package/dist/cjs/particle/NiceParticleEditor.js +526 -0
  77. package/dist/cjs/particle/NiceParticleEditor.js.map +1 -0
  78. package/dist/cjs/particle/ParticleEditor.module.css.js +6 -0
  79. package/dist/cjs/particle/ParticleEditor.module.css.js.map +1 -0
  80. package/dist/cjs/particle/particleEditorTypes.js +92 -0
  81. package/dist/cjs/particle/particleEditorTypes.js.map +1 -0
  82. package/dist/cjs/particle/particleEditorUtils.js +1084 -0
  83. package/dist/cjs/particle/particleEditorUtils.js.map +1 -0
  84. package/dist/cjs/rendering/NiceCascadedShadows.js +266 -0
  85. package/dist/cjs/rendering/NiceCascadedShadows.js.map +1 -0
  86. package/dist/cjs/rendering/NiceRenderExport.js +341 -0
  87. package/dist/cjs/rendering/NiceRenderExport.js.map +1 -0
  88. package/dist/cjs/rendering/NiceSSAO.js +359 -0
  89. package/dist/cjs/rendering/NiceSSAO.js.map +1 -0
  90. package/dist/cjs/rendering/NiceSSR.js +277 -0
  91. package/dist/cjs/rendering/NiceSSR.js.map +1 -0
  92. package/dist/cjs/rendering/NiceWebGPURenderer.js +215 -0
  93. package/dist/cjs/rendering/NiceWebGPURenderer.js.map +1 -0
  94. package/dist/cjs/ui/dist/index.js +50089 -0
  95. package/dist/cjs/ui/dist/index.js.map +1 -0
  96. package/dist/cjs/uv/NiceUVEditor.js +520 -0
  97. package/dist/cjs/uv/NiceUVEditor.js.map +1 -0
  98. package/dist/cjs/uv/UVEditor.module.css.js +6 -0
  99. package/dist/cjs/uv/UVEditor.module.css.js.map +1 -0
  100. package/dist/cjs/uv/uvEditorTypes.js +98 -0
  101. package/dist/cjs/uv/uvEditorTypes.js.map +1 -0
  102. package/dist/cjs/uv/uvEditorUtils.js +670 -0
  103. package/dist/cjs/uv/uvEditorUtils.js.map +1 -0
  104. package/dist/esm/collaborative/collaborativeScene.js +206 -0
  105. package/dist/esm/collaborative/collaborativeScene.js.map +1 -0
  106. package/dist/esm/dance/DanceBridge.js +158 -0
  107. package/dist/esm/dance/DanceBridge.js.map +1 -0
  108. package/dist/esm/dance/DanceScoreEngine.js +207 -0
  109. package/dist/esm/dance/DanceScoreEngine.js.map +1 -0
  110. package/dist/esm/dance/PoseDetector.js +195 -0
  111. package/dist/esm/dance/PoseDetector.js.map +1 -0
  112. package/dist/esm/index.js +35 -1
  113. package/dist/esm/index.js.map +1 -1
  114. package/dist/esm/material/MaterialEditor.module.css.js +4 -0
  115. package/dist/esm/material/MaterialEditor.module.css.js.map +1 -0
  116. package/dist/esm/material/NiceMaterialEditor.js +734 -0
  117. package/dist/esm/material/NiceMaterialEditor.js.map +1 -0
  118. package/dist/esm/material/materialEditorTypes.js +62 -0
  119. package/dist/esm/material/materialEditorTypes.js.map +1 -0
  120. package/dist/esm/material/materialEditorUtils.js +811 -0
  121. package/dist/esm/material/materialEditorUtils.js.map +1 -0
  122. package/dist/esm/material/materialNodeDefinitions.js +1280 -0
  123. package/dist/esm/material/materialNodeDefinitions.js.map +1 -0
  124. package/dist/esm/model/ModelEditor.js +4 -2
  125. package/dist/esm/model/ModelEditor.js.map +1 -1
  126. package/dist/esm/model/ModelEditor.module.css.js +1 -1
  127. package/dist/esm/model/ModelEditorLeftPanel.js +5 -4
  128. package/dist/esm/model/ModelEditorLeftPanel.js.map +1 -1
  129. package/dist/esm/model/ModelEditorMenuBar.js +8 -3
  130. package/dist/esm/model/ModelEditorMenuBar.js.map +1 -1
  131. package/dist/esm/model/ModelEditorRightPanel.js +27 -26
  132. package/dist/esm/model/ModelEditorRightPanel.js.map +1 -1
  133. package/dist/esm/model/ModelEditorSubComponents.js +17 -13
  134. package/dist/esm/model/ModelEditorSubComponents.js.map +1 -1
  135. package/dist/esm/model/ModelEditorTimeline.js +5 -4
  136. package/dist/esm/model/ModelEditorTimeline.js.map +1 -1
  137. package/dist/esm/model/ModelEditorToolbar.js +4 -3
  138. package/dist/esm/model/ModelEditorToolbar.js.map +1 -1
  139. package/dist/esm/model/ModelEditorViewport.js +2 -2
  140. package/dist/esm/model/ModelEditorViewport.js.map +1 -1
  141. package/dist/esm/model/ModelViewer.js +65 -0
  142. package/dist/esm/model/ModelViewer.js.map +1 -0
  143. package/dist/esm/model/ModelViewer.module.css.js +4 -0
  144. package/dist/esm/model/ModelViewer.module.css.js.map +1 -0
  145. package/dist/esm/model/NiceArmatureEditor.js +233 -0
  146. package/dist/esm/model/NiceArmatureEditor.js.map +1 -0
  147. package/dist/esm/model/NiceMorphTargetEditor.js +184 -0
  148. package/dist/esm/model/NiceMorphTargetEditor.js.map +1 -0
  149. package/dist/esm/model/NiceOctree.js +317 -0
  150. package/dist/esm/model/NiceOctree.js.map +1 -0
  151. package/dist/esm/model/NicePhysicsSimulation.js +261 -0
  152. package/dist/esm/model/NicePhysicsSimulation.js.map +1 -0
  153. package/dist/esm/model/NiceProceduralGeometry.js +242 -0
  154. package/dist/esm/model/NiceProceduralGeometry.js.map +1 -0
  155. package/dist/esm/model/NiceTerrainEditor.js +321 -0
  156. package/dist/esm/model/NiceTerrainEditor.js.map +1 -0
  157. package/dist/esm/model/NiceWeightPainter.js +236 -0
  158. package/dist/esm/model/NiceWeightPainter.js.map +1 -0
  159. package/dist/esm/model/NiceXRPreview.js +247 -0
  160. package/dist/esm/model/NiceXRPreview.js.map +1 -0
  161. package/dist/esm/model/cadModeUtils.js +103 -0
  162. package/dist/esm/model/cadModeUtils.js.map +1 -0
  163. package/dist/esm/model/editorShortcuts.js +185 -0
  164. package/dist/esm/model/editorShortcuts.js.map +1 -0
  165. package/dist/esm/model/modelEditorTypes.js +11 -0
  166. package/dist/esm/model/modelEditorTypes.js.map +1 -1
  167. package/dist/esm/model/modelEditorUtils.js +997 -0
  168. package/dist/esm/model/modelEditorUtils.js.map +1 -0
  169. package/dist/esm/model/simsModeUtils.js +325 -0
  170. package/dist/esm/model/simsModeUtils.js.map +1 -0
  171. package/dist/esm/model/useModelEditor.js +204 -0
  172. package/dist/esm/model/useModelEditor.js.map +1 -1
  173. package/dist/esm/model/useModelViewer.js +613 -0
  174. package/dist/esm/model/useModelViewer.js.map +1 -0
  175. package/dist/esm/nice2dev-ui-3d.css +1 -1
  176. package/dist/esm/particle/NiceParticleEditor.js +523 -0
  177. package/dist/esm/particle/NiceParticleEditor.js.map +1 -0
  178. package/dist/esm/particle/ParticleEditor.module.css.js +4 -0
  179. package/dist/esm/particle/ParticleEditor.module.css.js.map +1 -0
  180. package/dist/esm/particle/particleEditorTypes.js +84 -0
  181. package/dist/esm/particle/particleEditorTypes.js.map +1 -0
  182. package/dist/esm/particle/particleEditorUtils.js +1054 -0
  183. package/dist/esm/particle/particleEditorUtils.js.map +1 -0
  184. package/dist/esm/rendering/NiceCascadedShadows.js +244 -0
  185. package/dist/esm/rendering/NiceCascadedShadows.js.map +1 -0
  186. package/dist/esm/rendering/NiceRenderExport.js +319 -0
  187. package/dist/esm/rendering/NiceRenderExport.js.map +1 -0
  188. package/dist/esm/rendering/NiceSSAO.js +337 -0
  189. package/dist/esm/rendering/NiceSSAO.js.map +1 -0
  190. package/dist/esm/rendering/NiceSSR.js +255 -0
  191. package/dist/esm/rendering/NiceSSR.js.map +1 -0
  192. package/dist/esm/rendering/NiceWebGPURenderer.js +193 -0
  193. package/dist/esm/rendering/NiceWebGPURenderer.js.map +1 -0
  194. package/dist/esm/ui/dist/index.js +49686 -0
  195. package/dist/esm/ui/dist/index.js.map +1 -0
  196. package/dist/esm/uv/NiceUVEditor.js +518 -0
  197. package/dist/esm/uv/NiceUVEditor.js.map +1 -0
  198. package/dist/esm/uv/UVEditor.module.css.js +4 -0
  199. package/dist/esm/uv/UVEditor.module.css.js.map +1 -0
  200. package/dist/esm/uv/uvEditorTypes.js +88 -0
  201. package/dist/esm/uv/uvEditorTypes.js.map +1 -0
  202. package/dist/esm/uv/uvEditorUtils.js +621 -0
  203. package/dist/esm/uv/uvEditorUtils.js.map +1 -0
  204. package/package.json +3 -4
@@ -0,0 +1,634 @@
1
+ 'use strict';
2
+
3
+ var Ae = require('react');
4
+ var THREE = require('three');
5
+ var OrbitControls_js = require('three/examples/jsm/controls/OrbitControls.js');
6
+ var FBXLoader_js = require('three/examples/jsm/loaders/FBXLoader.js');
7
+ var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js');
8
+ var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
9
+ var ColladaLoader_js = require('three/examples/jsm/loaders/ColladaLoader.js');
10
+ var STLLoader_js = require('three/examples/jsm/loaders/STLLoader.js');
11
+ var PLYLoader_js = require('three/examples/jsm/loaders/PLYLoader.js');
12
+
13
+ function _interopNamespaceDefault(e) {
14
+ var n = Object.create(null);
15
+ if (e) {
16
+ Object.keys(e).forEach(function (k) {
17
+ if (k !== 'default') {
18
+ var d = Object.getOwnPropertyDescriptor(e, k);
19
+ Object.defineProperty(n, k, d.get ? d : {
20
+ enumerable: true,
21
+ get: function () { return e[k]; }
22
+ });
23
+ }
24
+ });
25
+ }
26
+ n.default = e;
27
+ return Object.freeze(n);
28
+ }
29
+
30
+ var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
31
+
32
+ /**
33
+ * useModelViewer — lightweight hook for the view-only 3D model viewer.
34
+ *
35
+ * Unlike useModelEditor this hook provides NO editing — just loading,
36
+ * orbit/pan/zoom navigation, optional WASD first-person camera,
37
+ * gamepad support, VR mode, annotations and measurement tools.
38
+ *
39
+ * Designed for ERP real-estate / rental-space visualisation and
40
+ * product presentation.
41
+ */
42
+ /* ═══════════════════════════════════════════
43
+ Quality presets
44
+ ═══════════════════════════════════════════ */
45
+ const QUALITY_MAP = {
46
+ low: { pixelRatio: 0.75, shadowMapSize: 512, antialias: false },
47
+ medium: { pixelRatio: 1, shadowMapSize: 1024, antialias: true },
48
+ high: { pixelRatio: 1, shadowMapSize: 2048, antialias: true },
49
+ ultra: { pixelRatio: 2, shadowMapSize: 4096, antialias: true },
50
+ };
51
+ const TONE_MAP = {
52
+ linear: THREE__namespace.LinearToneMapping,
53
+ reinhard: THREE__namespace.ReinhardToneMapping,
54
+ cineon: THREE__namespace.CineonToneMapping,
55
+ aces: THREE__namespace.ACESFilmicToneMapping,
56
+ agx: THREE__namespace.AgXToneMapping,
57
+ };
58
+ /* ═══════════════════════════════════════════
59
+ Hook
60
+ ═══════════════════════════════════════════ */
61
+ function useModelViewer(props = {}) {
62
+ const { src, format, navigationMode: initNavMode = 'orbit', quality = 'high', autoRotateSpeed: initAutoRotate = 0, backgroundColor = '#1a1a2e', showGrid: initShowGrid = true, shadows = true, gamepad = false, cameraPresets = [], annotations = [], hotspots = [], floorPlans = [], onHotspotClick, onAnnotationClick, onLoadProgress, onLoad, onError, clippingPlanes = [], measureMode: initMeasureMode = false, measurements = [], onMeasure, minDistance = 0.1, maxDistance = 1000, fov = 50, vr = false, walkSpeed = 5, ambientIntensity = 0.5, directionalIntensity = 0.8, antiAlias = true, toneMapping = 'aces', exposure = 1, } = props;
63
+ /* ── refs ── */
64
+ const mountRef = Ae.useRef(null);
65
+ const rendererRef = Ae.useRef(null);
66
+ const sceneRef = Ae.useRef(new THREE__namespace.Scene());
67
+ const cameraRef = Ae.useRef(new THREE__namespace.PerspectiveCamera(fov, 1, 0.01, 10000));
68
+ const orbitRef = Ae.useRef(null);
69
+ const clockRef = Ae.useRef(new THREE__namespace.Clock());
70
+ const mixerRef = Ae.useRef(null);
71
+ const rafRef = Ae.useRef(0);
72
+ const rootRef = Ae.useRef(null);
73
+ const gridRef = Ae.useRef(null);
74
+ const clipsRef = Ae.useRef([]);
75
+ const keysRef = Ae.useRef(new Set());
76
+ Ae.useRef(null);
77
+ const disposeRef = Ae.useRef(false);
78
+ /* ── state ── */
79
+ const [loading, setLoading] = Ae.useState(false);
80
+ const [loadProgress, setLoadProgress] = Ae.useState(0);
81
+ const [error, setError] = Ae.useState(null);
82
+ const [navMode, setNavMode] = Ae.useState(initNavMode);
83
+ const [projection, setProjection] = Ae.useState('perspective');
84
+ const [wireframe, setWireframe] = Ae.useState(false);
85
+ const [gridVisible, setGridVisible] = Ae.useState(initShowGrid);
86
+ const [autoRotate, setAutoRotate] = Ae.useState(initAutoRotate);
87
+ const [polyCount, setPolyCount] = Ae.useState(0);
88
+ const [meshCount, setMeshCount] = Ae.useState(0);
89
+ const [animNames, setAnimNames] = Ae.useState([]);
90
+ const [activeAnimIdx, setActiveAnimIdx] = Ae.useState(-1);
91
+ const [isPlaying, setIsPlaying] = Ae.useState(false);
92
+ const [animTime, setAnimTime] = Ae.useState(0);
93
+ const [animDuration, setAnimDuration] = Ae.useState(0);
94
+ const [animSpeed, setAnimSpeed] = Ae.useState(1);
95
+ const [isFullscreen, setIsFullscreen] = Ae.useState(false);
96
+ const [activeFloorPlanId, setActiveFloorPlanId] = Ae.useState(null);
97
+ const [annotationsVisible, setAnnotationsVisible] = Ae.useState(true);
98
+ const [clippingEnabled, setClippingEnabled] = Ae.useState(clippingPlanes.length > 0);
99
+ const [clippingPosition, setClippingPosition] = Ae.useState(0);
100
+ const [clippingAxis, setClippingAxis] = Ae.useState('y');
101
+ const [measureModeActive, setMeasureModeActive] = Ae.useState(initMeasureMode);
102
+ const [dimensions, setDimensions] = Ae.useState(null);
103
+ const [statusText, setStatusText] = Ae.useState('Ready');
104
+ /* ── scene setup ── */
105
+ Ae.useEffect(() => {
106
+ var _a;
107
+ const mount = mountRef.current;
108
+ if (!mount)
109
+ return;
110
+ disposeRef.current = false;
111
+ const q = QUALITY_MAP[quality];
112
+ const renderer = new THREE__namespace.WebGLRenderer({
113
+ antialias: q.antialias && antiAlias,
114
+ alpha: backgroundColor === 'transparent',
115
+ preserveDrawingBuffer: true,
116
+ });
117
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, q.pixelRatio));
118
+ renderer.setSize(mount.clientWidth, mount.clientHeight);
119
+ renderer.toneMapping = (_a = TONE_MAP[toneMapping]) !== null && _a !== void 0 ? _a : THREE__namespace.ACESFilmicToneMapping;
120
+ renderer.toneMappingExposure = exposure;
121
+ renderer.outputColorSpace = THREE__namespace.SRGBColorSpace;
122
+ if (shadows) {
123
+ renderer.shadowMap.enabled = true;
124
+ renderer.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
125
+ }
126
+ mount.appendChild(renderer.domElement);
127
+ rendererRef.current = renderer;
128
+ const scene = sceneRef.current;
129
+ if (backgroundColor !== 'transparent') {
130
+ scene.background = new THREE__namespace.Color(backgroundColor);
131
+ }
132
+ // Lights
133
+ const amb = new THREE__namespace.AmbientLight(0xffffff, ambientIntensity);
134
+ scene.add(amb);
135
+ const dir = new THREE__namespace.DirectionalLight(0xffffff, directionalIntensity);
136
+ dir.position.set(5, 10, 7);
137
+ if (shadows) {
138
+ dir.castShadow = true;
139
+ dir.shadow.mapSize.setScalar(q.shadowMapSize);
140
+ }
141
+ scene.add(dir);
142
+ const hemi = new THREE__namespace.HemisphereLight(0xaaccff, 0x445522, 0.3);
143
+ scene.add(hemi);
144
+ // Grid
145
+ const grid = new THREE__namespace.GridHelper(50, 50, 0x444444, 0x333333);
146
+ grid.visible = initShowGrid;
147
+ scene.add(grid);
148
+ gridRef.current = grid;
149
+ // Camera
150
+ const camera = cameraRef.current;
151
+ camera.position.set(5, 4, 8);
152
+ camera.lookAt(0, 0, 0);
153
+ // OrbitControls
154
+ const orbit = new OrbitControls_js.OrbitControls(camera, renderer.domElement);
155
+ orbit.enableDamping = true;
156
+ orbit.dampingFactor = 0.08;
157
+ orbit.minDistance = minDistance;
158
+ orbit.maxDistance = maxDistance;
159
+ orbit.autoRotate = initAutoRotate > 0;
160
+ orbit.autoRotateSpeed = initAutoRotate;
161
+ orbitRef.current = orbit;
162
+ // Resize observer
163
+ const ro = new ResizeObserver(() => {
164
+ if (!mount || disposeRef.current)
165
+ return;
166
+ const w = mount.clientWidth;
167
+ const h = mount.clientHeight;
168
+ camera.aspect = w / h;
169
+ camera.updateProjectionMatrix();
170
+ renderer.setSize(w, h);
171
+ });
172
+ ro.observe(mount);
173
+ // Animation loop
174
+ const animate = () => {
175
+ var _a, _b, _c, _d, _e;
176
+ if (disposeRef.current)
177
+ return;
178
+ rafRef.current = requestAnimationFrame(animate);
179
+ const dt = clockRef.current.getDelta();
180
+ // Gamepad FPS-style nav
181
+ if (gamepad && navMode === 'fps') {
182
+ const pads = (_a = navigator.getGamepads) === null || _a === void 0 ? void 0 : _a.call(navigator);
183
+ const gp = (_b = pads === null || pads === void 0 ? void 0 : pads[0]) !== null && _b !== void 0 ? _b : null;
184
+ if (gp) {
185
+ const lx = (_c = gp.axes[0]) !== null && _c !== void 0 ? _c : 0;
186
+ const ly = (_d = gp.axes[1]) !== null && _d !== void 0 ? _d : 0;
187
+ const rx = (_e = gp.axes[2]) !== null && _e !== void 0 ? _e : 0;
188
+ const fwd = new THREE__namespace.Vector3();
189
+ camera.getWorldDirection(fwd);
190
+ fwd.y = 0;
191
+ fwd.normalize();
192
+ const right = new THREE__namespace.Vector3().crossVectors(fwd, camera.up).normalize();
193
+ camera.position.addScaledVector(fwd, -ly * walkSpeed * dt);
194
+ camera.position.addScaledVector(right, lx * walkSpeed * dt);
195
+ orbit.target.addScaledVector(fwd, -ly * walkSpeed * dt);
196
+ orbit.target.addScaledVector(right, lx * walkSpeed * dt);
197
+ camera.rotateY(-rx * 2 * dt);
198
+ }
199
+ }
200
+ // Keyboard WASD
201
+ if (navMode === 'fps' || navMode === 'walkthrough') {
202
+ const fwd = new THREE__namespace.Vector3();
203
+ camera.getWorldDirection(fwd);
204
+ fwd.y = 0;
205
+ fwd.normalize();
206
+ const right = new THREE__namespace.Vector3().crossVectors(fwd, camera.up).normalize();
207
+ const speed = walkSpeed * dt;
208
+ if (keysRef.current.has('w') || keysRef.current.has('arrowup')) {
209
+ camera.position.addScaledVector(fwd, speed);
210
+ orbit.target.addScaledVector(fwd, speed);
211
+ }
212
+ if (keysRef.current.has('s') || keysRef.current.has('arrowdown')) {
213
+ camera.position.addScaledVector(fwd, -speed);
214
+ orbit.target.addScaledVector(fwd, -speed);
215
+ }
216
+ if (keysRef.current.has('a') || keysRef.current.has('arrowleft')) {
217
+ camera.position.addScaledVector(right, -speed);
218
+ orbit.target.addScaledVector(right, -speed);
219
+ }
220
+ if (keysRef.current.has('d') || keysRef.current.has('arrowright')) {
221
+ camera.position.addScaledVector(right, speed);
222
+ orbit.target.addScaledVector(right, speed);
223
+ }
224
+ if (keysRef.current.has('q')) {
225
+ camera.position.y += speed;
226
+ orbit.target.y += speed;
227
+ }
228
+ if (keysRef.current.has('e')) {
229
+ camera.position.y -= speed;
230
+ orbit.target.y -= speed;
231
+ }
232
+ }
233
+ if (mixerRef.current) {
234
+ mixerRef.current.update(dt);
235
+ }
236
+ orbit.update();
237
+ renderer.render(scene, camera);
238
+ };
239
+ animate();
240
+ // Keyboard listeners
241
+ const onKeyDown = (e) => keysRef.current.add(e.key.toLowerCase());
242
+ const onKeyUp = (e) => keysRef.current.delete(e.key.toLowerCase());
243
+ mount.addEventListener('keydown', onKeyDown);
244
+ mount.addEventListener('keyup', onKeyUp);
245
+ // Fullscreen
246
+ const onFSChange = () => setIsFullscreen(!!document.fullscreenElement);
247
+ document.addEventListener('fullscreenchange', onFSChange);
248
+ return () => {
249
+ disposeRef.current = true;
250
+ cancelAnimationFrame(rafRef.current);
251
+ ro.disconnect();
252
+ orbit.dispose();
253
+ renderer.dispose();
254
+ mount.removeEventListener('keydown', onKeyDown);
255
+ mount.removeEventListener('keyup', onKeyUp);
256
+ document.removeEventListener('fullscreenchange', onFSChange);
257
+ if (mount.contains(renderer.domElement)) {
258
+ mount.removeChild(renderer.domElement);
259
+ }
260
+ };
261
+ // eslint-disable-next-line react-hooks/exhaustive-deps
262
+ }, []);
263
+ /* ── update grid visibility ── */
264
+ Ae.useEffect(() => {
265
+ if (gridRef.current)
266
+ gridRef.current.visible = gridVisible;
267
+ }, [gridVisible]);
268
+ /* ── update auto-rotate ── */
269
+ Ae.useEffect(() => {
270
+ if (orbitRef.current) {
271
+ orbitRef.current.autoRotate = autoRotate > 0;
272
+ orbitRef.current.autoRotateSpeed = autoRotate;
273
+ }
274
+ }, [autoRotate]);
275
+ /* ── wireframe toggle ── */
276
+ Ae.useEffect(() => {
277
+ if (!rootRef.current)
278
+ return;
279
+ rootRef.current.traverse((c) => {
280
+ if (c.isMesh) {
281
+ const mat = c.material;
282
+ const mats = Array.isArray(mat) ? mat : [mat];
283
+ mats.forEach((m) => {
284
+ if ('wireframe' in m)
285
+ m.wireframe = wireframe;
286
+ });
287
+ }
288
+ });
289
+ }, [wireframe]);
290
+ /* ── clipping ── */
291
+ Ae.useEffect(() => {
292
+ const renderer = rendererRef.current;
293
+ if (!renderer)
294
+ return;
295
+ if (clippingEnabled) {
296
+ const normal = new THREE__namespace.Vector3(clippingAxis === 'x' ? 1 : 0, clippingAxis === 'y' ? 1 : 0, clippingAxis === 'z' ? 1 : 0);
297
+ const plane = new THREE__namespace.Plane(normal, -clippingPosition);
298
+ renderer.clippingPlanes = [plane];
299
+ }
300
+ else {
301
+ renderer.clippingPlanes = [];
302
+ }
303
+ }, [clippingEnabled, clippingPosition, clippingAxis]);
304
+ /* ── counting helpers ── */
305
+ const countScene = Ae.useCallback(() => {
306
+ let tris = 0;
307
+ let meshes = 0;
308
+ sceneRef.current.traverse((c) => {
309
+ if (c.isMesh) {
310
+ meshes++;
311
+ const geo = c.geometry;
312
+ if (geo.index)
313
+ tris += geo.index.count / 3;
314
+ else if (geo.attributes.position)
315
+ tris += geo.attributes.position.count / 3;
316
+ }
317
+ });
318
+ setPolyCount(Math.round(tris));
319
+ setMeshCount(meshes);
320
+ }, []);
321
+ /* ── detect format from URL ── */
322
+ const detectFormat = Ae.useCallback((url, hint) => {
323
+ var _a, _b, _c;
324
+ if (hint)
325
+ return hint;
326
+ const ext = (_c = (_b = (_a = url.split('.').pop()) === null || _a === void 0 ? void 0 : _a.split('?')[0]) === null || _b === void 0 ? void 0 : _b.toLowerCase()) !== null && _c !== void 0 ? _c : '';
327
+ return ext;
328
+ }, []);
329
+ /* ── load model ── */
330
+ const loadModel = Ae.useCallback(async (url, fmt) => {
331
+ var _a, _b, _c;
332
+ setLoading(true);
333
+ setLoadProgress(0);
334
+ setError(null);
335
+ setStatusText('Loading…');
336
+ const detectedFmt = detectFormat(url, fmt);
337
+ const onProgress = (e) => {
338
+ if (e.lengthComputable) {
339
+ const p = e.loaded / e.total;
340
+ setLoadProgress(p);
341
+ onLoadProgress === null || onLoadProgress === void 0 ? void 0 : onLoadProgress(p);
342
+ }
343
+ };
344
+ try {
345
+ let obj = null;
346
+ let clips = [];
347
+ if (detectedFmt === 'fbx') {
348
+ const loader = new FBXLoader_js.FBXLoader();
349
+ const group = await loader.loadAsync(url, onProgress);
350
+ clips = (_a = group.animations) !== null && _a !== void 0 ? _a : [];
351
+ obj = group;
352
+ }
353
+ else if (detectedFmt === 'glb' || detectedFmt === 'gltf') {
354
+ const loader = new GLTFLoader_js.GLTFLoader();
355
+ const gltf = await loader.loadAsync(url, onProgress);
356
+ clips = (_b = gltf.animations) !== null && _b !== void 0 ? _b : [];
357
+ obj = gltf.scene;
358
+ }
359
+ else if (detectedFmt === 'obj') {
360
+ const loader = new OBJLoader_js.OBJLoader();
361
+ obj = await loader.loadAsync(url, onProgress);
362
+ }
363
+ else if (detectedFmt === 'dae') {
364
+ const loader = new ColladaLoader_js.ColladaLoader();
365
+ const result = await loader.loadAsync(url, onProgress);
366
+ if (result) {
367
+ clips = (_c = result.scene.animations) !== null && _c !== void 0 ? _c : [];
368
+ obj = result.scene;
369
+ }
370
+ }
371
+ else if (detectedFmt === 'stl') {
372
+ const loader = new STLLoader_js.STLLoader();
373
+ const geo = await loader.loadAsync(url, onProgress);
374
+ const mat = new THREE__namespace.MeshStandardMaterial({ color: 0xaaaaaa, metalness: 0.3, roughness: 0.6 });
375
+ const mesh = new THREE__namespace.Mesh(geo, mat);
376
+ mesh.castShadow = true;
377
+ mesh.receiveShadow = true;
378
+ obj = mesh;
379
+ }
380
+ else if (detectedFmt === 'ply') {
381
+ const loader = new PLYLoader_js.PLYLoader();
382
+ const geo = await loader.loadAsync(url, onProgress);
383
+ geo.computeVertexNormals();
384
+ const mat = new THREE__namespace.MeshStandardMaterial({ color: 0xaaaaaa, vertexColors: !!geo.attributes.color });
385
+ obj = new THREE__namespace.Mesh(geo, mat);
386
+ }
387
+ else {
388
+ throw new Error(`Unsupported format: ${detectedFmt}`);
389
+ }
390
+ if (obj) {
391
+ // Remove previous model
392
+ if (rootRef.current) {
393
+ sceneRef.current.remove(rootRef.current);
394
+ }
395
+ // Enable shadows
396
+ obj.traverse((c) => {
397
+ if (c.isMesh) {
398
+ c.castShadow = true;
399
+ c.receiveShadow = true;
400
+ }
401
+ });
402
+ // Auto-scale to normalized size
403
+ const box = new THREE__namespace.Box3().setFromObject(obj);
404
+ const size = box.getSize(new THREE__namespace.Vector3());
405
+ const maxDim = Math.max(size.x, size.y, size.z);
406
+ if (maxDim > 0) {
407
+ const target = 5;
408
+ if (maxDim > target * 2 || maxDim < target * 0.1) {
409
+ const scale = target / maxDim;
410
+ obj.scale.multiplyScalar(scale);
411
+ }
412
+ }
413
+ // Center model
414
+ const box2 = new THREE__namespace.Box3().setFromObject(obj);
415
+ const center = box2.getCenter(new THREE__namespace.Vector3());
416
+ obj.position.sub(center);
417
+ obj.position.y += box2.getSize(new THREE__namespace.Vector3()).y / 2;
418
+ sceneRef.current.add(obj);
419
+ rootRef.current = obj;
420
+ // Update dimensions
421
+ const finalBox = new THREE__namespace.Box3().setFromObject(obj);
422
+ const s = finalBox.getSize(new THREE__namespace.Vector3());
423
+ setDimensions({ width: +s.x.toFixed(2), height: +s.y.toFixed(2), depth: +s.z.toFixed(2) });
424
+ // Animations
425
+ clipsRef.current = clips;
426
+ setAnimNames(clips.map((c, i) => c.name || `Clip ${i + 1}`));
427
+ if (clips.length > 0) {
428
+ const mixer = new THREE__namespace.AnimationMixer(obj);
429
+ mixerRef.current = mixer;
430
+ }
431
+ countScene();
432
+ setStatusText(`Loaded – ${polyCount} tris, ${meshCount} meshes`);
433
+ onLoad === null || onLoad === void 0 ? void 0 : onLoad();
434
+ }
435
+ }
436
+ catch (err) {
437
+ const msg = err instanceof Error ? err.message : String(err);
438
+ setError(msg);
439
+ setStatusText(`Error: ${msg}`);
440
+ onError === null || onError === void 0 ? void 0 : onError(err instanceof Error ? err : new Error(msg));
441
+ }
442
+ finally {
443
+ setLoading(false);
444
+ setLoadProgress(1);
445
+ }
446
+ }, [countScene, detectFormat, onLoad, onError, onLoadProgress, polyCount, meshCount]);
447
+ /* ── auto-load from src ── */
448
+ Ae.useEffect(() => {
449
+ if (src)
450
+ loadModel(src, format);
451
+ }, [src, format, loadModel]);
452
+ /* ── loadFile from disk ── */
453
+ const loadFile = Ae.useCallback(async (file) => {
454
+ var _a;
455
+ const url = URL.createObjectURL(file);
456
+ const ext = (_a = file.name.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
457
+ try {
458
+ await loadModel(url, ext);
459
+ }
460
+ finally {
461
+ URL.revokeObjectURL(url);
462
+ }
463
+ }, [loadModel]);
464
+ /* ── loadUrl ── */
465
+ const loadUrl = Ae.useCallback(async (url, fmt) => {
466
+ await loadModel(url, fmt);
467
+ }, [loadModel]);
468
+ /* ── animation controls ── */
469
+ const playAnimation = Ae.useCallback((index) => {
470
+ if (!mixerRef.current || index < 0 || index >= clipsRef.current.length)
471
+ return;
472
+ mixerRef.current.stopAllAction();
473
+ const clip = clipsRef.current[index];
474
+ const action = mixerRef.current.clipAction(clip);
475
+ action.reset().play();
476
+ setActiveAnimIdx(index);
477
+ setIsPlaying(true);
478
+ setAnimDuration(clip.duration);
479
+ }, []);
480
+ const togglePlay = Ae.useCallback(() => {
481
+ if (!mixerRef.current)
482
+ return;
483
+ if (isPlaying) {
484
+ mixerRef.current.timeScale = 0;
485
+ setIsPlaying(false);
486
+ }
487
+ else {
488
+ mixerRef.current.timeScale = animSpeed;
489
+ setIsPlaying(true);
490
+ if (activeAnimIdx < 0 && clipsRef.current.length > 0)
491
+ playAnimation(0);
492
+ }
493
+ }, [isPlaying, animSpeed, activeAnimIdx, playAnimation]);
494
+ const seekAnim = Ae.useCallback((t) => {
495
+ if (mixerRef.current)
496
+ mixerRef.current.setTime(t);
497
+ setAnimTime(t);
498
+ }, []);
499
+ /* ── camera presets ── */
500
+ const goToPreset = Ae.useCallback((preset) => {
501
+ const cam = cameraRef.current;
502
+ const orbit = orbitRef.current;
503
+ if (!orbit)
504
+ return;
505
+ cam.position.set(...preset.position);
506
+ orbit.target.set(...preset.target);
507
+ if (preset.fov)
508
+ cam.fov = preset.fov;
509
+ cam.updateProjectionMatrix();
510
+ orbit.update();
511
+ }, []);
512
+ const resetCamera = Ae.useCallback(() => {
513
+ const cam = cameraRef.current;
514
+ cam.position.set(5, 4, 8);
515
+ if (orbitRef.current) {
516
+ orbitRef.current.target.set(0, 0, 0);
517
+ orbitRef.current.update();
518
+ }
519
+ }, []);
520
+ const zoomToFit = Ae.useCallback(() => {
521
+ if (!rootRef.current)
522
+ return;
523
+ const box = new THREE__namespace.Box3().setFromObject(rootRef.current);
524
+ const center = box.getCenter(new THREE__namespace.Vector3());
525
+ const size = box.getSize(new THREE__namespace.Vector3());
526
+ const maxDim = Math.max(size.x, size.y, size.z);
527
+ const cam = cameraRef.current;
528
+ const dist = maxDim / (2 * Math.tan((cam.fov * Math.PI) / 360));
529
+ cam.position.copy(center).add(new THREE__namespace.Vector3(0, 0, dist * 1.5));
530
+ if (orbitRef.current) {
531
+ orbitRef.current.target.copy(center);
532
+ orbitRef.current.update();
533
+ }
534
+ }, []);
535
+ /* ── screenshot ── */
536
+ const screenshot = Ae.useCallback((width, height) => {
537
+ const renderer = rendererRef.current;
538
+ if (!renderer)
539
+ return '';
540
+ if (width && height) {
541
+ renderer.setSize(width, height);
542
+ cameraRef.current.aspect = width / height;
543
+ cameraRef.current.updateProjectionMatrix();
544
+ }
545
+ renderer.render(sceneRef.current, cameraRef.current);
546
+ const url = renderer.domElement.toDataURL('image/png');
547
+ if (width && height && mountRef.current) {
548
+ const w = mountRef.current.clientWidth;
549
+ const h = mountRef.current.clientHeight;
550
+ renderer.setSize(w, h);
551
+ cameraRef.current.aspect = w / h;
552
+ cameraRef.current.updateProjectionMatrix();
553
+ }
554
+ return url;
555
+ }, []);
556
+ /* ── fullscreen ── */
557
+ const toggleFullscreen = Ae.useCallback(() => {
558
+ if (!mountRef.current)
559
+ return;
560
+ if (document.fullscreenElement) {
561
+ document.exitFullscreen();
562
+ }
563
+ else {
564
+ mountRef.current.requestFullscreen();
565
+ }
566
+ }, []);
567
+ /* ── dispose ── */
568
+ const dispose = Ae.useCallback(() => {
569
+ var _a;
570
+ disposeRef.current = true;
571
+ cancelAnimationFrame(rafRef.current);
572
+ (_a = rendererRef.current) === null || _a === void 0 ? void 0 : _a.dispose();
573
+ }, []);
574
+ /* ── finalise status text ── */
575
+ Ae.useEffect(() => {
576
+ if (!loading && !error && polyCount > 0) {
577
+ setStatusText(`${polyCount.toLocaleString()} triangles · ${meshCount} meshes · ${animNames.length} animation(s)`);
578
+ }
579
+ }, [loading, error, polyCount, meshCount, animNames.length]);
580
+ return {
581
+ mountRef,
582
+ loading,
583
+ loadProgress,
584
+ error,
585
+ navigationMode: navMode,
586
+ setNavigationMode: setNavMode,
587
+ projection,
588
+ setProjection,
589
+ wireframe,
590
+ setWireframe,
591
+ showGrid: gridVisible,
592
+ setShowGrid: setGridVisible,
593
+ autoRotateSpeed: autoRotate,
594
+ setAutoRotateSpeed: setAutoRotate,
595
+ polyCount,
596
+ meshCount,
597
+ animations: animNames,
598
+ activeAnimIdx,
599
+ playAnimation,
600
+ togglePlay,
601
+ isPlaying,
602
+ animTime,
603
+ animDuration,
604
+ seekAnim,
605
+ animSpeed,
606
+ setAnimSpeed,
607
+ goToPreset,
608
+ resetCamera,
609
+ zoomToFit,
610
+ screenshot,
611
+ toggleFullscreen,
612
+ isFullscreen,
613
+ loadFile,
614
+ loadUrl,
615
+ activeFloorPlanId,
616
+ setActiveFloorPlanId,
617
+ annotationsVisible,
618
+ setAnnotationsVisible,
619
+ measureMode: measureModeActive,
620
+ setMeasureMode: setMeasureModeActive,
621
+ clippingEnabled,
622
+ setClippingEnabled,
623
+ clippingPosition,
624
+ setClippingPosition,
625
+ clippingAxis,
626
+ setClippingAxis,
627
+ dimensions,
628
+ statusText,
629
+ dispose,
630
+ };
631
+ }
632
+
633
+ exports.useModelViewer = useModelViewer;
634
+ //# sourceMappingURL=useModelViewer.js.map