@almadar/ui 5.142.0 → 5.144.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.
@@ -1,8 +1,8 @@
1
- import React3, { forwardRef, useRef, useState, useMemo, useEffect, useImperativeHandle, useCallback, createContext, useContext, Component, useReducer } from 'react';
1
+ import React3, { createContext, forwardRef, useRef, useState, useMemo, useEffect, useImperativeHandle, useCallback, useContext, Component, useReducer } from 'react';
2
2
  import { EventBusContext, useTraitScopeChain } from '@almadar/ui/providers';
3
3
  import { createLogger } from '@almadar/logger';
4
4
  import { Canvas, useThree, useFrame } from '@react-three/fiber';
5
- import * as THREE10 from 'three';
5
+ import * as THREE5 from 'three';
6
6
  import { Vector3, QuadraticBezierCurve3, MathUtils, Quaternion } from 'three';
7
7
  import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
8
8
  import { Grid, OrbitControls, Billboard, Text, Stars, Sparkles, Html, RoundedBox } from '@react-three/drei';
@@ -507,7 +507,7 @@ function Lighting3D({
507
507
  "directionalLightHelper",
508
508
  {
509
509
  args: [
510
- new THREE10.DirectionalLight(directionalColor, directionalIntensity),
510
+ new THREE5.DirectionalLight(directionalColor, directionalIntensity),
511
511
  5
512
512
  ]
513
513
  }
@@ -564,8 +564,8 @@ function FollowCamera3D({
564
564
  offset
565
565
  }) {
566
566
  const { camera } = useThree();
567
- const look = useRef(new THREE10.Vector3(target[0], target[1], target[2]));
568
- const goal = useRef(new THREE10.Vector3());
567
+ const look = useRef(new THREE5.Vector3(target[0], target[1], target[2]));
568
+ const goal = useRef(new THREE5.Vector3());
569
569
  useFrame((_, delta) => {
570
570
  const t = Math.min(1, delta * 5);
571
571
  goal.current.set(target[0] + offset[0], target[1] + offset[1], target[2] + offset[2]);
@@ -586,7 +586,109 @@ function create3DProjector(opts = {}) {
586
586
  toWorld: (pos) => [pos.x * cellSize + offsetX, pos.z ?? 0, pos.y * cellSize + offsetZ]
587
587
  };
588
588
  }
589
+ var BoneStore = class {
590
+ constructor() {
591
+ __publicField(this, "bones", /* @__PURE__ */ new Map());
592
+ __publicField(this, "listeners", /* @__PURE__ */ new Set());
593
+ }
594
+ register(name, bone) {
595
+ this.bones.set(name, bone);
596
+ this.notify();
597
+ return () => {
598
+ if (this.bones.get(name) === bone) {
599
+ this.bones.delete(name);
600
+ this.notify();
601
+ }
602
+ };
603
+ }
604
+ get(name) {
605
+ return this.bones.get(name);
606
+ }
607
+ subscribe(listener) {
608
+ this.listeners.add(listener);
609
+ return () => this.listeners.delete(listener);
610
+ }
611
+ notify() {
612
+ for (const l of this.listeners) l();
613
+ }
614
+ };
615
+ var BoneRegistryContext = createContext(null);
616
+ function isAnimatedGroup(node) {
617
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
618
+ }
619
+ var MESH_SEGMENTS_DEFAULT = 24;
620
+ function polyhedronBounds(vertices) {
621
+ if (!vertices || vertices.length < 3) return null;
622
+ const min = [Infinity, Infinity, Infinity];
623
+ const max = [-Infinity, -Infinity, -Infinity];
624
+ for (const v of vertices) {
625
+ if (v.length < 3 || !v.every((c) => Number.isFinite(c))) return null;
626
+ for (let axis = 0; axis < 3; axis++) {
627
+ min[axis] = Math.min(min[axis], v[axis]);
628
+ max[axis] = Math.max(max[axis], v[axis]);
629
+ }
630
+ }
631
+ return { min, max };
632
+ }
633
+ function clampSegments(segments) {
634
+ const s = segments ?? MESH_SEGMENTS_DEFAULT;
635
+ return Math.min(64, Math.max(3, Math.round(s)));
636
+ }
637
+ function isAnimatedMesh(node) {
638
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
639
+ }
640
+ var lerp = (a, b, k) => a + (b - a) * k;
589
641
  var NUMERIC_TRACKS = [
642
+ "offsetX",
643
+ "offsetY",
644
+ "offsetZ",
645
+ "rotateX",
646
+ "rotateY",
647
+ "rotateZ",
648
+ "scale",
649
+ "opacity",
650
+ "emissiveIntensity"
651
+ ];
652
+ function applyMeshAnimation(node, timeMs) {
653
+ const anim = node.animation;
654
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return null;
655
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
656
+ const cycle = timeMs / anim.durationMs;
657
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
658
+ const trackValue = (key) => {
659
+ const defined = frames.filter((f) => f[key] !== void 0);
660
+ if (defined.length === 0) return void 0;
661
+ let prev;
662
+ let next;
663
+ for (const f of defined) {
664
+ if (f.at <= t) prev = f;
665
+ else if (!next) next = f;
666
+ }
667
+ if (!prev) return defined[0][key];
668
+ if (!next) return prev[key];
669
+ const span = next.at - prev.at;
670
+ const k = span > 0 ? (t - prev.at) / span : 1;
671
+ const a = prev[key];
672
+ const b = next[key];
673
+ if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
674
+ return a;
675
+ };
676
+ const num = {};
677
+ for (const key of NUMERIC_TRACKS) {
678
+ const v = trackValue(key);
679
+ if (v !== void 0) num[key] = v;
680
+ }
681
+ return {
682
+ offset: [num.offsetX ?? 0, num.offsetY ?? 0, num.offsetZ ?? 0],
683
+ rotate: [num.rotateX ?? 0, num.rotateY ?? 0, num.rotateZ ?? 0],
684
+ scale: num.scale ?? 1,
685
+ opacity: num.opacity,
686
+ emissiveIntensity: num.emissiveIntensity,
687
+ color: trackValue("color"),
688
+ emissive: trackValue("emissive")
689
+ };
690
+ }
691
+ var NUMERIC_TRACKS2 = [
590
692
  "offsetX",
591
693
  "offsetY",
592
694
  "rotate",
@@ -602,7 +704,7 @@ var NUMERIC_TRACKS = [
602
704
  function isAnimatedShape(node) {
603
705
  return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
604
706
  }
605
- var lerp = (a, b, k) => a + (b - a) * k;
707
+ var lerp2 = (a, b, k) => a + (b - a) * k;
606
708
  function applyShapeAnimation(node, timeMs) {
607
709
  const anim = node.animation;
608
710
  if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return node;
@@ -625,10 +727,10 @@ function applyShapeAnimation(node, timeMs) {
625
727
  const k = span > 0 ? (t - prev.at) / span : 1;
626
728
  const a = prev[key];
627
729
  const b = next[key];
628
- if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
730
+ if (typeof a === "number" && typeof b === "number") return lerp2(a, b, k);
629
731
  return a;
630
732
  };
631
- for (const key of NUMERIC_TRACKS) {
733
+ for (const key of NUMERIC_TRACKS2) {
632
734
  const v = trackValue(key);
633
735
  if (v !== void 0) out[key] = v;
634
736
  }
@@ -649,7 +751,7 @@ function applyShapeAnimation(node, timeMs) {
649
751
  if (prevSh?.shadow && nextSh?.shadow) {
650
752
  const span = nextSh.at - prevSh.at;
651
753
  const k = span > 0 ? (t - prevSh.at) / span : 1;
652
- out.shadow = { color: prevSh.shadow.color, blur: lerp(prevSh.shadow.blur, nextSh.shadow.blur, k) };
754
+ out.shadow = { color: prevSh.shadow.color, blur: lerp2(prevSh.shadow.blur, nextSh.shadow.blur, k) };
653
755
  } else {
654
756
  out.shadow = sh;
655
757
  }
@@ -663,6 +765,10 @@ var atlasCache = /* @__PURE__ */ new Map();
663
765
  function isTilesheet(a) {
664
766
  return typeof a.tileWidth === "number";
665
767
  }
768
+ function isSpriteSheetAtlas(a) {
769
+ const s = a;
770
+ return typeof s.frameWidth === "number" && typeof s.frameHeight === "number" && typeof s.animations === "object";
771
+ }
666
772
  function getAtlas(url, onReady) {
667
773
  if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
668
774
  atlasCache.set(url, void 0);
@@ -699,6 +805,7 @@ function subRectFor(atlas, sprite) {
699
805
  sh: atlas.tileHeight
700
806
  };
701
807
  }
808
+ if (isSpriteSheetAtlas(atlas)) return null;
702
809
  const st = atlas.subTextures[sprite];
703
810
  if (!st) return null;
704
811
  return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
@@ -792,12 +899,12 @@ function ModelLoader({
792
899
  if (!loadedModel) return null;
793
900
  const cloned = clone(loadedModel);
794
901
  cloned.updateMatrixWorld(true);
795
- const tintColor = tint ? new THREE10.Color(tint) : null;
902
+ const tintColor = tint ? new THREE5.Color(tint) : null;
796
903
  cloned.traverse((child) => {
797
- if (child instanceof THREE10.Mesh) {
904
+ if (child instanceof THREE5.Mesh) {
798
905
  child.castShadow = castShadow;
799
906
  child.receiveShadow = receiveShadow;
800
- if (tintColor && child.material instanceof THREE10.MeshStandardMaterial) {
907
+ if (tintColor && child.material instanceof THREE5.MeshStandardMaterial) {
801
908
  const mat = child.material.clone();
802
909
  mat.color.multiply(tintColor);
803
910
  child.material = mat;
@@ -806,7 +913,7 @@ function ModelLoader({
806
913
  });
807
914
  return cloned;
808
915
  }, [loadedModel, castShadow, receiveShadow, tint]);
809
- const mixer = useMemo(() => model ? new THREE10.AnimationMixer(model) : null, [model]);
916
+ const mixer = useMemo(() => model ? new THREE5.AnimationMixer(model) : null, [model]);
810
917
  useEffect(() => {
811
918
  if (!mixer || !animation || clips.length === 0) return;
812
919
  const wanted = animation.toLowerCase();
@@ -823,8 +930,8 @@ function ModelLoader({
823
930
  });
824
931
  const normFactor = useMemo(() => {
825
932
  if (!model) return 1;
826
- const box = new THREE10.Box3().setFromObject(model);
827
- const size = new THREE10.Vector3();
933
+ const box = new THREE5.Box3().setFromObject(model);
934
+ const size = new THREE5.Vector3();
828
935
  box.getSize(size);
829
936
  const maxDim = Math.max(size.x, size.y, size.z);
830
937
  if (!Number.isFinite(maxDim) || maxDim < 0.05) return 1;
@@ -914,7 +1021,7 @@ var warnUnsupported3d = (kind) => {
914
1021
  warnedUnsupported.add(kind);
915
1022
  mesh3dLog.warn("unsupported drawable kind on the 3D backend \u2014 skipped", { kind });
916
1023
  };
917
- var CrossOriginTextureLoader = class extends THREE10.TextureLoader {
1024
+ var CrossOriginTextureLoader = class extends THREE5.TextureLoader {
918
1025
  constructor() {
919
1026
  super();
920
1027
  this.crossOrigin = "anonymous";
@@ -932,7 +1039,7 @@ function useBillboardTexture(url) {
932
1039
  url,
933
1040
  (texture) => {
934
1041
  if (!active) return;
935
- texture.colorSpace = THREE10.SRGBColorSpace;
1042
+ texture.colorSpace = THREE5.SRGBColorSpace;
936
1043
  setState({ texture, error: false });
937
1044
  },
938
1045
  void 0,
@@ -985,7 +1092,7 @@ function SpriteBillboard({ node, world, cellSize = 1, groupOpacity = 1 }) {
985
1092
  }, [texture, frame, node.height, node.width, anchor, cellSize]);
986
1093
  const groundGeometry = React3.useMemo(() => {
987
1094
  if (anchor !== "top-left" || !texture || !atlasReady) return null;
988
- const g = new THREE10.PlaneGeometry(size.width, size.height);
1095
+ const g = new THREE5.PlaneGeometry(size.width, size.height);
989
1096
  g.rotateX(-Math.PI / 2);
990
1097
  return g;
991
1098
  }, [anchor, texture, atlasReady, size.width, size.height]);
@@ -993,15 +1100,15 @@ function SpriteBillboard({ node, world, cellSize = 1, groupOpacity = 1 }) {
993
1100
  if (anchor === "top-left") {
994
1101
  return /* @__PURE__ */ jsx("group", { position: [world[0] + size.width / 2, 0.02, world[2] + size.height / 2], children: /* @__PURE__ */ jsxs("mesh", { rotation: [-Math.PI / 2, 0, 0], children: [
995
1102
  /* @__PURE__ */ jsx("planeGeometry", { args: [size.width, size.height] }),
996
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE10.DoubleSide })
1103
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE5.DoubleSide })
997
1104
  ] }) });
998
1105
  }
999
1106
  return /* @__PURE__ */ jsx("group", { position: [world[0], world[1] + size.height / 2, world[2]], children: /* @__PURE__ */ jsxs("mesh", { children: [
1000
1107
  /* @__PURE__ */ jsx("planeGeometry", { args: [size.width, size.height] }),
1001
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE10.DoubleSide })
1108
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE5.DoubleSide })
1002
1109
  ] }) });
1003
1110
  }
1004
- texture.magFilter = texture.minFilter = THREE10.NearestFilter;
1111
+ texture.magFilter = texture.minFilter = THREE5.NearestFilter;
1005
1112
  texture.needsUpdate = true;
1006
1113
  if (frame) {
1007
1114
  texture.repeat.set(frame.w / size.imgW, frame.h / size.imgH);
@@ -1014,7 +1121,7 @@ function SpriteBillboard({ node, world, cellSize = 1, groupOpacity = 1 }) {
1014
1121
  map: texture,
1015
1122
  transparent: true,
1016
1123
  alphaTest: 0.1,
1017
- side: THREE10.DoubleSide,
1124
+ side: THREE5.DoubleSide,
1018
1125
  opacity: (node.opacity ?? 1) * groupOpacity
1019
1126
  }
1020
1127
  ) }) });
@@ -1027,7 +1134,7 @@ function SpriteBillboard({ node, world, cellSize = 1, groupOpacity = 1 }) {
1027
1134
  map: texture,
1028
1135
  transparent: true,
1029
1136
  alphaTest: 0.1,
1030
- side: THREE10.DoubleSide,
1137
+ side: THREE5.DoubleSide,
1031
1138
  opacity: node.opacity ?? 1
1032
1139
  }
1033
1140
  )
@@ -1096,7 +1203,7 @@ function Shape3D({ node, projector, groupOpacity = 1 }) {
1096
1203
  }
1097
1204
  case "poly": {
1098
1205
  if (!node.points || node.points.length === 0) return null;
1099
- const s = new THREE10.Shape();
1206
+ const s = new THREE5.Shape();
1100
1207
  node.points.forEach((p, i) => {
1101
1208
  if (i === 0) s.moveTo(p.x, p.y);
1102
1209
  else s.lineTo(p.x, p.y);
@@ -1114,7 +1221,7 @@ function Shape3D({ node, projector, groupOpacity = 1 }) {
1114
1221
  }
1115
1222
  return /* @__PURE__ */ jsx("group", { ref: groupRef, position: [world[0], world[1] + 0.02, world[2]], children: /* @__PURE__ */ jsxs("mesh", { rotation: [-Math.PI / 2, 0, 0], children: [
1116
1223
  geometry,
1117
- /* @__PURE__ */ jsx("meshBasicMaterial", { ref: materialRef, color, transparent: true, opacity: (node.opacity ?? 1) * groupOpacity, side: THREE10.DoubleSide })
1224
+ /* @__PURE__ */ jsx("meshBasicMaterial", { ref: materialRef, color, transparent: true, opacity: (node.opacity ?? 1) * groupOpacity, side: THREE5.DoubleSide })
1118
1225
  ] }) });
1119
1226
  }
1120
1227
  function Text3D({ node, projector, groupOpacity = 1 }) {
@@ -1135,64 +1242,66 @@ function Text3D({ node, projector, groupOpacity = 1 }) {
1135
1242
  }
1136
1243
  ) });
1137
1244
  }
1138
- var MESH_SEGMENTS_DEFAULT = 24;
1139
- function clampSegments(segments) {
1140
- const s = segments ?? MESH_SEGMENTS_DEFAULT;
1141
- return Math.min(64, Math.max(3, Math.round(s)));
1245
+ var GROUND_ROTATION = [-Math.PI / 2, 0, 0];
1246
+ var textureCache = /* @__PURE__ */ new Map();
1247
+ function getMeshTexture(url) {
1248
+ const cached = textureCache.get(url);
1249
+ if (cached) return cached;
1250
+ const texture = new THREE5.TextureLoader().load(url);
1251
+ texture.flipY = false;
1252
+ texture.colorSpace = THREE5.SRGBColorSpace;
1253
+ texture.wrapS = THREE5.RepeatWrapping;
1254
+ texture.wrapT = THREE5.RepeatWrapping;
1255
+ textureCache.set(url, texture);
1256
+ return texture;
1142
1257
  }
1143
- function isAnimatedMesh(node) {
1144
- return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
1145
- }
1146
- var lerp2 = (a, b, k) => a + (b - a) * k;
1147
- var NUMERIC_TRACKS2 = [
1148
- "offsetX",
1149
- "offsetY",
1150
- "offsetZ",
1151
- "rotateX",
1152
- "rotateY",
1153
- "rotateZ",
1154
- "scale",
1155
- "opacity",
1156
- "emissiveIntensity"
1157
- ];
1158
- function applyMeshAnimation(node, timeMs) {
1159
- const anim = node.animation;
1160
- if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return null;
1161
- const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
1162
- const cycle = timeMs / anim.durationMs;
1163
- const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
1164
- const trackValue = (key) => {
1165
- const defined = frames.filter((f) => f[key] !== void 0);
1166
- if (defined.length === 0) return void 0;
1167
- let prev;
1168
- let next;
1169
- for (const f of defined) {
1170
- if (f.at <= t) prev = f;
1171
- else if (!next) next = f;
1258
+ function polyhedronGeometry(node, cellSize) {
1259
+ const verts = node.vertices;
1260
+ const faces = node.faces;
1261
+ const bounds = polyhedronBounds(verts);
1262
+ if (!verts || !bounds || !faces || faces.length === 0) return null;
1263
+ const positions = new Float32Array(verts.length * 3);
1264
+ for (let i = 0; i < verts.length; i++) {
1265
+ positions[i * 3] = verts[i][0] * cellSize;
1266
+ positions[i * 3 + 1] = verts[i][2] * cellSize;
1267
+ positions[i * 3 + 2] = verts[i][1] * cellSize;
1268
+ }
1269
+ const index = [];
1270
+ for (const face of faces) {
1271
+ if (face.length < 3) continue;
1272
+ const [a, b, c] = face;
1273
+ if (a === b || b === c || a === c) continue;
1274
+ if (![a, b, c].every((i) => Number.isInteger(i) && i >= 0 && i < verts.length)) continue;
1275
+ index.push(a, c, b);
1276
+ }
1277
+ if (index.length === 0) return null;
1278
+ const geometry = new THREE5.BufferGeometry();
1279
+ geometry.setAttribute("position", new THREE5.BufferAttribute(positions, 3));
1280
+ if (node.uvs && node.uvs.length === verts.length) {
1281
+ const uv = new Float32Array(verts.length * 2);
1282
+ for (let i = 0; i < verts.length; i++) {
1283
+ uv[i * 2] = node.uvs[i][0];
1284
+ uv[i * 2 + 1] = node.uvs[i][1];
1172
1285
  }
1173
- if (!prev) return defined[0][key];
1174
- if (!next) return prev[key];
1175
- const span = next.at - prev.at;
1176
- const k = span > 0 ? (t - prev.at) / span : 1;
1177
- const a = prev[key];
1178
- const b = next[key];
1179
- if (typeof a === "number" && typeof b === "number") return lerp2(a, b, k);
1180
- return a;
1181
- };
1182
- const num = {};
1183
- for (const key of NUMERIC_TRACKS2) {
1184
- const v = trackValue(key);
1185
- if (v !== void 0) num[key] = v;
1286
+ geometry.setAttribute("uv", new THREE5.BufferAttribute(uv, 2));
1186
1287
  }
1187
- return {
1188
- offset: [num.offsetX ?? 0, num.offsetY ?? 0, num.offsetZ ?? 0],
1189
- rotate: [num.rotateX ?? 0, num.rotateY ?? 0, num.rotateZ ?? 0],
1190
- scale: num.scale ?? 1,
1191
- opacity: num.opacity,
1192
- emissiveIntensity: num.emissiveIntensity,
1193
- color: trackValue("color"),
1194
- emissive: trackValue("emissive")
1195
- };
1288
+ geometry.setIndex(index);
1289
+ geometry.computeVertexNormals();
1290
+ const skin = node.skin;
1291
+ if (skin && skin.indices.length === verts.length && skin.weights.length === verts.length) {
1292
+ const skinIndex = new Uint16Array(verts.length * 4);
1293
+ const skinWeight = new Float32Array(verts.length * 4);
1294
+ for (let i = 0; i < verts.length; i++) {
1295
+ for (let k = 0; k < 4; k++) {
1296
+ skinIndex[i * 4 + k] = skin.indices[i][k] ?? 0;
1297
+ skinWeight[i * 4 + k] = skin.weights[i][k] ?? 0;
1298
+ }
1299
+ }
1300
+ geometry.setAttribute("skinIndex", new THREE5.BufferAttribute(skinIndex, 4));
1301
+ geometry.setAttribute("skinWeight", new THREE5.BufferAttribute(skinWeight, 4));
1302
+ }
1303
+ const size = Math.max(bounds.max[0] - bounds.min[0], bounds.max[1] - bounds.min[1], bounds.max[2] - bounds.min[2]) * cellSize;
1304
+ return { geometry, lift: -bounds.min[2] * cellSize, size };
1196
1305
  }
1197
1306
  function meshGeometry(node, cellSize) {
1198
1307
  const seg = clampSegments(node.segments);
@@ -1220,17 +1329,20 @@ function meshGeometry(node, cellSize) {
1220
1329
  return { element: /* @__PURE__ */ jsx("torusGeometry", { args: [r, tube, Math.max(3, Math.round(seg / 2)), seg] }), lift: r + tube, size: (r + tube) * 2 };
1221
1330
  }
1222
1331
  case "plane":
1223
- return { element: /* @__PURE__ */ jsx("planeGeometry", { args: [w, d] }), lift: 0, size: Math.max(w, d) };
1332
+ return { element: /* @__PURE__ */ jsx("planeGeometry", { args: [w, d] }), lift: 0, size: Math.max(w, d), baseRotation: GROUND_ROTATION };
1224
1333
  case "circle":
1225
- return { element: /* @__PURE__ */ jsx("circleGeometry", { args: [r, seg] }), lift: 0, size: r * 2 };
1334
+ return { element: /* @__PURE__ */ jsx("circleGeometry", { args: [r, seg] }), lift: 0, size: r * 2, baseRotation: GROUND_ROTATION };
1335
+ case "polyhedron":
1336
+ return polyhedronGeometry(node, cellSize);
1226
1337
  default:
1227
1338
  return null;
1228
1339
  }
1229
1340
  }
1341
+ var warnedPolyhedronOutline = false;
1230
1342
  var SIDE_MAP = {
1231
- front: THREE10.FrontSide,
1232
- back: THREE10.BackSide,
1233
- double: THREE10.DoubleSide
1343
+ front: THREE5.FrontSide,
1344
+ back: THREE5.BackSide,
1345
+ double: THREE5.DoubleSide
1234
1346
  };
1235
1347
  function meshMaterial(mat, opacity, ref) {
1236
1348
  const m = mat ?? {};
@@ -1239,7 +1351,8 @@ function meshMaterial(mat, opacity, ref) {
1239
1351
  color: m.color ?? "#ffffff",
1240
1352
  transparent: opacity < 1,
1241
1353
  opacity,
1242
- side: SIDE_MAP[m.side ?? "front"]
1354
+ side: SIDE_MAP[m.side ?? "front"],
1355
+ ...m.map ? { map: getMeshTexture(m.map) } : {}
1243
1356
  };
1244
1357
  const emissive = m.emissive ? { emissive: m.emissive, emissiveIntensity: m.emissiveIntensity ?? 1 } : {};
1245
1358
  switch (m.kind ?? "standard") {
@@ -1274,6 +1387,53 @@ function meshMaterial(mat, opacity, ref) {
1274
1387
  );
1275
1388
  }
1276
1389
  }
1390
+ function SkinnedPolyhedron3D({
1391
+ node,
1392
+ skin,
1393
+ geometry,
1394
+ cellSize,
1395
+ opacity
1396
+ }) {
1397
+ const boneStore = useContext(BoneRegistryContext);
1398
+ const meshRef = useRef(null);
1399
+ const [registryTick, setRegistryTick] = useState(0);
1400
+ useEffect(() => boneStore?.subscribe(() => setRegistryTick((t) => t + 1)), [boneStore]);
1401
+ useEffect(() => {
1402
+ const mesh = meshRef.current;
1403
+ if (!mesh || !boneStore) return;
1404
+ const bones = [];
1405
+ for (const name of skin.bones) {
1406
+ const bone = boneStore.get(name);
1407
+ if (!bone) return;
1408
+ bones.push(bone);
1409
+ }
1410
+ if (mesh.skeleton && mesh.skeleton.bones.length === bones.length && mesh.skeleton.bones.every((b, i) => b === bones[i])) return;
1411
+ if (skin.inverseBindMatrices.length !== bones.length) return;
1412
+ const inverses = skin.inverseBindMatrices.map((m) => {
1413
+ const mat = new THREE5.Matrix4().fromArray(m);
1414
+ mat.elements[12] *= cellSize;
1415
+ mat.elements[13] *= cellSize;
1416
+ mat.elements[14] *= cellSize;
1417
+ return mat;
1418
+ });
1419
+ mesh.bind(new THREE5.Skeleton(bones, inverses), new THREE5.Matrix4());
1420
+ }, [registryTick, skin, boneStore, geometry, cellSize]);
1421
+ useEffect(() => {
1422
+ const mesh = meshRef.current;
1423
+ return () => mesh?.skeleton?.dispose();
1424
+ }, []);
1425
+ return /* @__PURE__ */ jsx(
1426
+ "skinnedMesh",
1427
+ {
1428
+ ref: meshRef,
1429
+ geometry,
1430
+ frustumCulled: false,
1431
+ castShadow: node.castShadow ?? true,
1432
+ receiveShadow: node.receiveShadow ?? true,
1433
+ children: meshMaterial(node.material, opacity)
1434
+ }
1435
+ );
1436
+ }
1277
1437
  function Mesh3D({
1278
1438
  node,
1279
1439
  projector,
@@ -1286,6 +1446,10 @@ function Mesh3D({
1286
1446
  const validPos = isValidScenePos(node.position);
1287
1447
  const baseWorld = validPos ? projector.toWorld(node.position) : [0, 0, 0];
1288
1448
  const geo = useMemo(() => meshGeometry(node, projector.cellSize), [node, projector.cellSize]);
1449
+ useEffect(() => {
1450
+ const g = geo?.geometry;
1451
+ return () => g?.dispose();
1452
+ }, [geo]);
1289
1453
  useFrame(({ clock }) => {
1290
1454
  if (!animated || !groupRef.current) return;
1291
1455
  const state = applyMeshAnimation(node, clock.elapsedTime * 1e3);
@@ -1298,8 +1462,13 @@ function Mesh3D({
1298
1462
  );
1299
1463
  groupRef.current.scale.setScalar(state.scale);
1300
1464
  if (meshRef.current) {
1465
+ const shapeRot = geo?.baseRotation ?? [0, 0, 0];
1301
1466
  const base = node.rotation ?? [0, 0, 0];
1302
- meshRef.current.rotation.set(base[0] + state.rotate[0], base[1] + state.rotate[1], base[2] + state.rotate[2]);
1467
+ meshRef.current.rotation.set(
1468
+ shapeRot[0] + base[0] + state.rotate[0],
1469
+ shapeRot[1] + base[1] + state.rotate[1],
1470
+ shapeRot[2] + base[2] + state.rotate[2]
1471
+ );
1303
1472
  }
1304
1473
  const mat = materialRef.current;
1305
1474
  if (mat) {
@@ -1316,13 +1485,28 @@ function Mesh3D({
1316
1485
  if (!validPos || !geo) return null;
1317
1486
  const lift = (node.pivot ?? "bottom") === "bottom" ? geo.lift : 0;
1318
1487
  const opacity = (node.opacity ?? 1) * (node.material?.opacity ?? 1) * groupOpacity;
1319
- const rotation = node.rotation ?? [0, 0, 0];
1488
+ if (node.skin && geo.geometry) {
1489
+ return /* @__PURE__ */ jsx("group", { position: baseWorld, children: /* @__PURE__ */ jsx(SkinnedPolyhedron3D, { node, skin: node.skin, geometry: geo.geometry, cellSize: projector.cellSize, opacity }) });
1490
+ }
1491
+ const nodeRotation = node.rotation ?? [0, 0, 0];
1492
+ const shapeRotation = geo.baseRotation ?? [0, 0, 0];
1493
+ const rotation = [
1494
+ shapeRotation[0] + nodeRotation[0],
1495
+ shapeRotation[1] + nodeRotation[1],
1496
+ shapeRotation[2] + nodeRotation[2]
1497
+ ];
1320
1498
  const outlineScale = geo.size > 0 ? 1 + (node.outline?.width ?? 0.05) * projector.cellSize / geo.size : 1;
1499
+ if (node.outline && geo.geometry && !warnedPolyhedronOutline) {
1500
+ warnedPolyhedronOutline = true;
1501
+ console.warn('[draw-mesh] outline is not yet supported on shape "polyhedron" \u2014 skipped');
1502
+ }
1503
+ const geometryProp = geo.geometry ? { geometry: geo.geometry } : {};
1321
1504
  return /* @__PURE__ */ jsxs("group", { ref: groupRef, position: baseWorld, children: [
1322
1505
  /* @__PURE__ */ jsxs(
1323
1506
  "mesh",
1324
1507
  {
1325
1508
  ref: meshRef,
1509
+ ...geometryProp,
1326
1510
  position: [0, lift, 0],
1327
1511
  rotation,
1328
1512
  castShadow: node.castShadow ?? true,
@@ -1333,13 +1517,13 @@ function Mesh3D({
1333
1517
  ]
1334
1518
  }
1335
1519
  ),
1336
- node.outline && /* @__PURE__ */ jsxs("mesh", { position: [0, lift, 0], rotation, scale: outlineScale, children: [
1520
+ node.outline && !geo.geometry && /* @__PURE__ */ jsxs("mesh", { position: [0, lift, 0], rotation, scale: outlineScale, children: [
1337
1521
  geo.element,
1338
1522
  /* @__PURE__ */ jsx(
1339
1523
  "meshBasicMaterial",
1340
1524
  {
1341
1525
  color: node.outline.color ?? "#101014",
1342
- side: THREE10.BackSide,
1526
+ side: THREE5.BackSide,
1343
1527
  transparent: opacity < 1,
1344
1528
  opacity
1345
1529
  }
@@ -1366,14 +1550,53 @@ function Drawable3D({ node, projector, groupOpacity = 1 }) {
1366
1550
  case "draw-group": {
1367
1551
  if (!isValidScenePos(node.position) || !Array.isArray(node.items)) return null;
1368
1552
  if (node.clip) warnUnsupported3d("draw-group:clip");
1369
- const world = projector.toWorld(node.position);
1370
- const inner = create3DProjector({ cellSize: projector.cellSize });
1371
- const opacity = (node.opacity ?? 1) * groupOpacity;
1372
- const s = node.scale ?? 1;
1373
- return /* @__PURE__ */ jsx("group", { position: world, rotation: [0, -(node.rotate ?? 0), 0], scale: [s, s, s], children: node.items.map((item, i) => /* @__PURE__ */ jsx(Drawable3D, { node: item, projector: inner, groupOpacity: opacity }, i)) });
1553
+ return /* @__PURE__ */ jsx(Group3D, { node, projector, groupOpacity });
1374
1554
  }
1375
1555
  }
1376
1556
  }
1557
+ function Group3D({
1558
+ node,
1559
+ projector,
1560
+ groupOpacity
1561
+ }) {
1562
+ const ref = useRef(null);
1563
+ const animated = isAnimatedGroup(node);
1564
+ const world = projector.toWorld(node.position);
1565
+ const inner = create3DProjector({ cellSize: projector.cellSize });
1566
+ const opacity = (node.opacity ?? 1) * groupOpacity;
1567
+ const s = node.scale ?? 1;
1568
+ const baseRotation = node.rotation ?? [0, -(node.rotate ?? 0), 0];
1569
+ const parentStore = useContext(BoneRegistryContext);
1570
+ const scopedStore = useMemo(() => node.skeleton ? new BoneStore() : null, [node.skeleton]);
1571
+ const boneStore = scopedStore ?? parentStore;
1572
+ const boneObject = useMemo(() => node.bone ? new THREE5.Bone() : null, [node.bone]);
1573
+ useEffect(() => {
1574
+ if (!node.bone || !boneStore || !boneObject) return;
1575
+ return boneStore.register(node.bone, boneObject);
1576
+ }, [node.bone, boneStore, boneObject]);
1577
+ useFrame(({ clock }) => {
1578
+ if (!animated || !ref.current) return;
1579
+ const state = applyMeshAnimation(node, clock.elapsedTime * 1e3);
1580
+ if (!state) return;
1581
+ const cell = projector.cellSize;
1582
+ ref.current.position.set(
1583
+ world[0] + state.offset[0] * cell,
1584
+ world[1] + state.offset[2] * cell,
1585
+ world[2] + state.offset[1] * cell
1586
+ );
1587
+ ref.current.rotation.set(
1588
+ baseRotation[0] + state.rotate[0],
1589
+ baseRotation[1] + state.rotate[1],
1590
+ baseRotation[2] + state.rotate[2]
1591
+ );
1592
+ ref.current.scale.setScalar(s * state.scale);
1593
+ });
1594
+ const children = /* @__PURE__ */ jsxs("group", { ref, position: world, rotation: baseRotation, scale: [s, s, s], children: [
1595
+ boneObject && /* @__PURE__ */ jsx("primitive", { object: boneObject }),
1596
+ node.items.map((item, i) => /* @__PURE__ */ jsx(Drawable3D, { node: item, projector: inner, groupOpacity: opacity }, i))
1597
+ ] });
1598
+ return scopedStore ? /* @__PURE__ */ jsx(BoneRegistryContext.Provider, { value: scopedStore, children }) : children;
1599
+ }
1377
1600
 
1378
1601
  // lib/drawable/three/game3dTheme.ts
1379
1602
  var GRID_COLORS_3D = {
@@ -1386,10 +1609,26 @@ function cn(...inputs) {
1386
1609
  }
1387
1610
  var DEFAULT_GRID_CONFIG = {
1388
1611
  cellSize: 1};
1612
+ function CameraPose({
1613
+ position,
1614
+ fov,
1615
+ controls
1616
+ }) {
1617
+ const camera = useThree((s) => s.camera);
1618
+ useEffect(() => {
1619
+ camera.position.set(position[0], position[1], position[2]);
1620
+ if (camera.isPerspectiveCamera) {
1621
+ camera.fov = fov;
1622
+ camera.updateProjectionMatrix();
1623
+ }
1624
+ controls.current?.update();
1625
+ }, [camera, position, fov, controls]);
1626
+ return null;
1627
+ }
1389
1628
  function RoomEnvironment3D() {
1390
1629
  const { gl, scene } = useThree(({ gl: gl2, scene: scene2 }) => ({ gl: gl2, scene: scene2 }));
1391
1630
  useEffect(() => {
1392
- const pmremGenerator = new THREE10.PMREMGenerator(gl);
1631
+ const pmremGenerator = new THREE5.PMREMGenerator(gl);
1393
1632
  const envTexture = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
1394
1633
  scene.environment = envTexture;
1395
1634
  return () => {
@@ -1425,6 +1664,7 @@ var Canvas3DHost = forwardRef(
1425
1664
  keyUpMap,
1426
1665
  pixelsPerUnit,
1427
1666
  fov,
1667
+ azimuth,
1428
1668
  lighting,
1429
1669
  post,
1430
1670
  children,
@@ -1505,6 +1745,7 @@ var Canvas3DHost = forwardRef(
1505
1745
  }),
1506
1746
  [gridBounds, cellSize]
1507
1747
  );
1748
+ const boneStore = useMemo(() => new BoneStore(), []);
1508
1749
  const drawableProjector = useMemo(
1509
1750
  () => create3DProjector({
1510
1751
  cellSize: gridConfig.cellSize,
@@ -1517,7 +1758,7 @@ var Canvas3DHost = forwardRef(
1517
1758
  getCameraPosition: () => {
1518
1759
  if (controlsRef.current) {
1519
1760
  const pos = controlsRef.current.object.position;
1520
- return new THREE10.Vector3(pos.x, pos.y, pos.z);
1761
+ return new THREE5.Vector3(pos.x, pos.y, pos.z);
1521
1762
  }
1522
1763
  return null;
1523
1764
  },
@@ -1557,18 +1798,28 @@ var Canvas3DHost = forwardRef(
1557
1798
  const cz = cameraTarget[2];
1558
1799
  const d = size * 1;
1559
1800
  const fovDeg = fov ?? 45;
1560
- switch (cameraMode) {
1561
- case "isometric":
1562
- return { position: [cx + d, d * 0.8, cz + d], fov: fovDeg };
1563
- case "top-down":
1564
- return { position: [cx, d * 2, cz + d * 0.35], fov: fovDeg };
1565
- case "follow":
1566
- return { position: [cx, d * 0.5, cz + d], fov: fovDeg };
1567
- case "perspective":
1568
- default:
1569
- return { position: [cx + d, d, cz + d], fov: fovDeg };
1570
- }
1571
- }, [cameraMode, gridBounds, cellSize, cameraTarget, fov]);
1801
+ const base = (() => {
1802
+ switch (cameraMode) {
1803
+ case "isometric":
1804
+ return { position: [cx + d, d * 0.8, cz + d], fov: fovDeg };
1805
+ case "top-down":
1806
+ return { position: [cx, d * 2, cz + d * 0.35], fov: fovDeg };
1807
+ case "front":
1808
+ return { position: [cx, d * 0.32, cz + d * 1.15], fov: fovDeg };
1809
+ case "follow":
1810
+ return { position: [cx, d * 0.5, cz + d], fov: fovDeg };
1811
+ case "perspective":
1812
+ default:
1813
+ return { position: [cx + d, d, cz + d], fov: fovDeg };
1814
+ }
1815
+ })();
1816
+ if (!azimuth) return base;
1817
+ const ox = base.position[0] - cx;
1818
+ const oz = base.position[2] - cz;
1819
+ const c = Math.cos(azimuth);
1820
+ const s = Math.sin(azimuth);
1821
+ return { position: [cx + ox * c - oz * s, base.position[1], cz + ox * s + oz * c], fov: base.fov };
1822
+ }, [cameraMode, gridBounds, cellSize, cameraTarget, fov, azimuth]);
1572
1823
  const followWorld = useMemo(() => {
1573
1824
  if (followTarget) return drawableProjector.toWorld(followTarget);
1574
1825
  return cameraTarget;
@@ -1628,6 +1879,8 @@ var Canvas3DHost = forwardRef(
1628
1879
  Canvas,
1629
1880
  {
1630
1881
  shadows,
1882
+ flat: lighting?.toneMapping === "none",
1883
+ gl: { preserveDrawingBuffer: true },
1631
1884
  camera: {
1632
1885
  position: cameraConfig.position,
1633
1886
  fov: cameraConfig.fov,
@@ -1642,6 +1895,7 @@ var Canvas3DHost = forwardRef(
1642
1895
  },
1643
1896
  children: [
1644
1897
  /* @__PURE__ */ jsx(CameraController3D, { onCameraChange: eventHandlers.handleCameraChange }),
1898
+ /* @__PURE__ */ jsx(CameraPose, { position: cameraConfig.position, fov: cameraConfig.fov, controls: controlsRef }),
1645
1899
  (cameraMode === "follow" || cameraMode === "chase") && /* @__PURE__ */ jsx(FollowCamera3D, { target: followWorld, offset: followOffset }),
1646
1900
  /* @__PURE__ */ jsx(
1647
1901
  Lighting3D,
@@ -1686,7 +1940,7 @@ var Canvas3DHost = forwardRef(
1686
1940
  fadeStrength: 1
1687
1941
  }
1688
1942
  ),
1689
- allDrawables.length > 0 && /* @__PURE__ */ jsx("group", { children: allDrawables.map((node, i) => /* @__PURE__ */ jsx(Drawable3D, { node, projector: drawableProjector }, i)) }),
1943
+ allDrawables.length > 0 && /* @__PURE__ */ jsx(BoneRegistryContext.Provider, { value: boneStore, children: /* @__PURE__ */ jsx("group", { children: allDrawables.map((node, i) => /* @__PURE__ */ jsx(Drawable3D, { node, projector: drawableProjector }, i)) }) }),
1690
1944
  (tileClickEvent || unitClickEvent) && /* @__PURE__ */ jsxs(
1691
1945
  "mesh",
1692
1946
  {
@@ -1722,7 +1976,7 @@ var Canvas3DHost = forwardRef(
1722
1976
  dampingFactor: 0.05,
1723
1977
  enableZoom: true,
1724
1978
  enablePan: true,
1725
- touches: { ONE: THREE10.TOUCH.ROTATE, TWO: THREE10.TOUCH.DOLLY_PAN },
1979
+ touches: { ONE: THREE5.TOUCH.ROTATE, TWO: THREE5.TOUCH.DOLLY_PAN },
1726
1980
  minDistance: 2,
1727
1981
  maxDistance: 100,
1728
1982
  maxPolarAngle: Math.PI / 2 - 0.1
@@ -1745,15 +1999,15 @@ function Scene3D({ background = "#1a1a2e", fog, children }) {
1745
1999
  if (initializedRef.current) return;
1746
2000
  initializedRef.current = true;
1747
2001
  if (background.startsWith("#") || background.startsWith("rgb")) {
1748
- scene.background = new THREE10.Color(background);
2002
+ scene.background = new THREE5.Color(background);
1749
2003
  } else {
1750
- const loader = new THREE10.TextureLoader();
2004
+ const loader = new THREE5.TextureLoader();
1751
2005
  loader.load(background, (texture) => {
1752
2006
  scene.background = texture;
1753
2007
  });
1754
2008
  }
1755
2009
  if (fog) {
1756
- scene.fog = new THREE10.Fog(fog.color, fog.near, fog.far);
2010
+ scene.fog = new THREE5.Fog(fog.color, fog.near, fog.far);
1757
2011
  }
1758
2012
  return () => {
1759
2013
  scene.background = null;
@@ -1776,14 +2030,14 @@ var Camera3D = forwardRef(
1776
2030
  }, ref) => {
1777
2031
  const { camera, set, viewport } = useThree();
1778
2032
  const controlsRef = useRef(null);
1779
- const initialPosition = useRef(new THREE10.Vector3(...position));
1780
- const initialTarget = useRef(new THREE10.Vector3(...target));
2033
+ const initialPosition = useRef(new THREE5.Vector3(...position));
2034
+ const initialTarget = useRef(new THREE5.Vector3(...target));
1781
2035
  useEffect(() => {
1782
2036
  let newCamera;
1783
2037
  if (mode === "isometric") {
1784
2038
  const aspect = viewport.aspect;
1785
2039
  const size = 10 / zoom;
1786
- newCamera = new THREE10.OrthographicCamera(
2040
+ newCamera = new THREE5.OrthographicCamera(
1787
2041
  -size * aspect,
1788
2042
  size * aspect,
1789
2043
  size,
@@ -1792,7 +2046,7 @@ var Camera3D = forwardRef(
1792
2046
  1e3
1793
2047
  );
1794
2048
  } else {
1795
- newCamera = new THREE10.PerspectiveCamera(fov, viewport.aspect, 0.1, 1e3);
2049
+ newCamera = new THREE5.PerspectiveCamera(fov, viewport.aspect, 0.1, 1e3);
1796
2050
  }
1797
2051
  newCamera.position.copy(initialPosition.current);
1798
2052
  newCamera.lookAt(initialTarget.current.x, initialTarget.current.y, initialTarget.current.z);
@@ -1833,8 +2087,8 @@ var Camera3D = forwardRef(
1833
2087
  }
1834
2088
  },
1835
2089
  getViewBounds: () => {
1836
- const min = new THREE10.Vector3(-10, -10, -10);
1837
- const max = new THREE10.Vector3(10, 10, 10);
2090
+ const min = new THREE5.Vector3(-10, -10, -10);
2091
+ const max = new THREE5.Vector3(10, 10, 10);
1838
2092
  return { min, max };
1839
2093
  }
1840
2094
  }));
@@ -1876,7 +2130,7 @@ var AssetLoader = class {
1876
2130
  __publicField(this, "textureCache");
1877
2131
  __publicField(this, "loadingPromises");
1878
2132
  this.objLoader = new OBJLoader();
1879
- this.textureLoader = new THREE10.TextureLoader();
2133
+ this.textureLoader = new THREE5.TextureLoader();
1880
2134
  this.modelCache = /* @__PURE__ */ new Map();
1881
2135
  this.textureCache = /* @__PURE__ */ new Map();
1882
2136
  this.loadingPromises = /* @__PURE__ */ new Map();
@@ -1950,7 +2204,7 @@ var AssetLoader = class {
1950
2204
  return this.loadingPromises.get(`texture:${url}`);
1951
2205
  }
1952
2206
  const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
1953
- texture.colorSpace = THREE10.SRGBColorSpace;
2207
+ texture.colorSpace = THREE5.SRGBColorSpace;
1954
2208
  this.textureCache.set(url, texture);
1955
2209
  this.loadingPromises.delete(`texture:${url}`);
1956
2210
  return texture;
@@ -2024,7 +2278,7 @@ var AssetLoader = class {
2024
2278
  });
2025
2279
  this.modelCache.forEach((model) => {
2026
2280
  model.scene.traverse((child) => {
2027
- if (child instanceof THREE10.Mesh) {
2281
+ if (child instanceof THREE5.Mesh) {
2028
2282
  child.geometry.dispose();
2029
2283
  if (Array.isArray(child.material)) {
2030
2284
  child.material.forEach((m) => m.dispose());
@@ -2074,21 +2328,21 @@ function useThree5(options = {}) {
2074
2328
  const [isReady, setIsReady] = useState(false);
2075
2329
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
2076
2330
  const initialCameraPosition = useMemo(
2077
- () => new THREE10.Vector3(...opts.cameraPosition),
2331
+ () => new THREE5.Vector3(...opts.cameraPosition),
2078
2332
  []
2079
2333
  );
2080
2334
  useEffect(() => {
2081
2335
  if (!containerRef.current) return;
2082
2336
  const container = containerRef.current;
2083
2337
  const { clientWidth, clientHeight } = container;
2084
- const scene = new THREE10.Scene();
2085
- scene.background = new THREE10.Color(opts.backgroundColor);
2338
+ const scene = new THREE5.Scene();
2339
+ scene.background = new THREE5.Color(opts.backgroundColor);
2086
2340
  sceneRef.current = scene;
2087
2341
  let camera;
2088
2342
  const aspect = clientWidth / clientHeight;
2089
2343
  if (opts.cameraMode === "isometric") {
2090
2344
  const size = 10;
2091
- camera = new THREE10.OrthographicCamera(
2345
+ camera = new THREE5.OrthographicCamera(
2092
2346
  -size * aspect,
2093
2347
  size * aspect,
2094
2348
  size,
@@ -2097,11 +2351,11 @@ function useThree5(options = {}) {
2097
2351
  1e3
2098
2352
  );
2099
2353
  } else {
2100
- camera = new THREE10.PerspectiveCamera(45, aspect, 0.1, 1e3);
2354
+ camera = new THREE5.PerspectiveCamera(45, aspect, 0.1, 1e3);
2101
2355
  }
2102
2356
  camera.position.copy(initialCameraPosition);
2103
2357
  cameraRef.current = camera;
2104
- const renderer = new THREE10.WebGLRenderer({
2358
+ const renderer = new THREE5.WebGLRenderer({
2105
2359
  antialias: true,
2106
2360
  alpha: true,
2107
2361
  canvas: canvasRef.current || void 0
@@ -2109,7 +2363,7 @@ function useThree5(options = {}) {
2109
2363
  renderer.setSize(clientWidth, clientHeight);
2110
2364
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
2111
2365
  renderer.shadowMap.enabled = opts.shadows;
2112
- renderer.shadowMap.type = THREE10.PCFSoftShadowMap;
2366
+ renderer.shadowMap.type = THREE5.PCFSoftShadowMap;
2113
2367
  rendererRef.current = renderer;
2114
2368
  const controls = new OrbitControls$1(camera, renderer.domElement);
2115
2369
  controls.enableDamping = true;
@@ -2118,16 +2372,16 @@ function useThree5(options = {}) {
2118
2372
  controls.maxDistance = 100;
2119
2373
  controls.maxPolarAngle = Math.PI / 2 - 0.1;
2120
2374
  controlsRef.current = controls;
2121
- const ambientLight = new THREE10.AmbientLight(16777215, 0.6);
2375
+ const ambientLight = new THREE5.AmbientLight(16777215, 0.6);
2122
2376
  scene.add(ambientLight);
2123
- const directionalLight = new THREE10.DirectionalLight(16777215, 0.8);
2377
+ const directionalLight = new THREE5.DirectionalLight(16777215, 0.8);
2124
2378
  directionalLight.position.set(10, 20, 10);
2125
2379
  directionalLight.castShadow = opts.shadows;
2126
2380
  directionalLight.shadow.mapSize.width = 2048;
2127
2381
  directionalLight.shadow.mapSize.height = 2048;
2128
2382
  scene.add(directionalLight);
2129
2383
  if (opts.showGrid) {
2130
- const gridHelper = new THREE10.GridHelper(
2384
+ const gridHelper = new THREE5.GridHelper(
2131
2385
  opts.gridSize,
2132
2386
  opts.gridSize,
2133
2387
  4473924,
@@ -2145,10 +2399,10 @@ function useThree5(options = {}) {
2145
2399
  const handleResize = () => {
2146
2400
  const { clientWidth: width, clientHeight: height } = container;
2147
2401
  setDimensions({ width, height });
2148
- if (camera instanceof THREE10.PerspectiveCamera) {
2402
+ if (camera instanceof THREE5.PerspectiveCamera) {
2149
2403
  camera.aspect = width / height;
2150
2404
  camera.updateProjectionMatrix();
2151
- } else if (camera instanceof THREE10.OrthographicCamera) {
2405
+ } else if (camera instanceof THREE5.OrthographicCamera) {
2152
2406
  const aspect2 = width / height;
2153
2407
  const size = 10;
2154
2408
  camera.left = -size * aspect2;
@@ -2179,7 +2433,7 @@ function useThree5(options = {}) {
2179
2433
  let newCamera;
2180
2434
  if (opts.cameraMode === "isometric") {
2181
2435
  const size = 10;
2182
- newCamera = new THREE10.OrthographicCamera(
2436
+ newCamera = new THREE5.OrthographicCamera(
2183
2437
  -size * aspect,
2184
2438
  size * aspect,
2185
2439
  size,
@@ -2188,7 +2442,7 @@ function useThree5(options = {}) {
2188
2442
  1e3
2189
2443
  );
2190
2444
  } else {
2191
- newCamera = new THREE10.PerspectiveCamera(45, aspect, 0.1, 1e3);
2445
+ newCamera = new THREE5.PerspectiveCamera(45, aspect, 0.1, 1e3);
2192
2446
  }
2193
2447
  newCamera.position.copy(currentPos);
2194
2448
  cameraRef.current = newCamera;
@@ -2518,8 +2772,8 @@ function useSceneGraph() {
2518
2772
  }
2519
2773
  function useRaycaster(options) {
2520
2774
  const { camera, canvas, cellSize = 1, offsetX = 0, offsetZ = 0 } = options;
2521
- const raycaster = useRef(new THREE10.Raycaster());
2522
- const mouse = useRef(new THREE10.Vector2());
2775
+ const raycaster = useRef(new THREE5.Raycaster());
2776
+ const mouse = useRef(new THREE5.Vector2());
2523
2777
  const clientToNDC = useCallback(
2524
2778
  (clientX, clientY) => {
2525
2779
  if (!canvas) {
@@ -2589,8 +2843,8 @@ function useRaycaster(options) {
2589
2843
  const ndc = clientToNDC(clientX, clientY);
2590
2844
  mouse.current.set(ndc.x, ndc.y);
2591
2845
  raycaster.current.setFromCamera(mouse.current, camera);
2592
- const plane = new THREE10.Plane(new THREE10.Vector3(0, 1, 0), 0);
2593
- const target = new THREE10.Vector3();
2846
+ const plane = new THREE5.Plane(new THREE5.Vector3(0, 1, 0), 0);
2847
+ const target = new THREE5.Vector3();
2594
2848
  const intersection = raycaster.current.ray.intersectPlane(plane, target);
2595
2849
  if (intersection) {
2596
2850
  const gridX = Math.round((target.x - offsetX) / cellSize);
@@ -2628,7 +2882,7 @@ function useRaycaster(options) {
2628
2882
  return {
2629
2883
  gridX: gridCoords.x,
2630
2884
  gridZ: gridCoords.z,
2631
- worldPosition: new THREE10.Vector3(
2885
+ worldPosition: new THREE5.Vector3(
2632
2886
  gridCoords.x * cellSize + offsetX,
2633
2887
  0,
2634
2888
  gridCoords.z * cellSize + offsetZ
@@ -2658,7 +2912,7 @@ var DEFAULT_CONFIG = {
2658
2912
  };
2659
2913
  function gridToWorld(gridX, gridZ, config = DEFAULT_CONFIG) {
2660
2914
  const opts = { ...DEFAULT_CONFIG, ...config };
2661
- return new THREE10.Vector3(
2915
+ return new THREE5.Vector3(
2662
2916
  gridX * opts.cellSize + opts.offsetX,
2663
2917
  opts.elevation,
2664
2918
  gridZ * opts.cellSize + opts.offsetZ
@@ -2672,17 +2926,17 @@ function worldToGrid(worldX, worldZ, config = DEFAULT_CONFIG) {
2672
2926
  };
2673
2927
  }
2674
2928
  function raycastToPlane(camera, mouseX, mouseY, planeY = 0) {
2675
- const raycaster = new THREE10.Raycaster();
2676
- const mouse = new THREE10.Vector2(mouseX, mouseY);
2929
+ const raycaster = new THREE5.Raycaster();
2930
+ const mouse = new THREE5.Vector2(mouseX, mouseY);
2677
2931
  raycaster.setFromCamera(mouse, camera);
2678
- const plane = new THREE10.Plane(new THREE10.Vector3(0, 1, 0), -planeY);
2679
- const target = new THREE10.Vector3();
2932
+ const plane = new THREE5.Plane(new THREE5.Vector3(0, 1, 0), -planeY);
2933
+ const target = new THREE5.Vector3();
2680
2934
  const intersection = raycaster.ray.intersectPlane(plane, target);
2681
2935
  return intersection ? target : null;
2682
2936
  }
2683
2937
  function raycastToObjects(camera, mouseX, mouseY, objects) {
2684
- const raycaster = new THREE10.Raycaster();
2685
- const mouse = new THREE10.Vector2(mouseX, mouseY);
2938
+ const raycaster = new THREE5.Raycaster();
2939
+ const mouse = new THREE5.Vector2(mouseX, mouseY);
2686
2940
  raycaster.setFromCamera(mouse, camera);
2687
2941
  const intersects = raycaster.intersectObjects(objects, true);
2688
2942
  return intersects.length > 0 ? intersects[0] : null;
@@ -2734,14 +2988,14 @@ function getCellsInRadius(centerX, centerZ, radius) {
2734
2988
  return cells;
2735
2989
  }
2736
2990
  function createGridHighlight(color = 16776960, opacity = 0.3) {
2737
- const geometry = new THREE10.PlaneGeometry(0.95, 0.95);
2738
- const material = new THREE10.MeshBasicMaterial({
2991
+ const geometry = new THREE5.PlaneGeometry(0.95, 0.95);
2992
+ const material = new THREE5.MeshBasicMaterial({
2739
2993
  color,
2740
2994
  transparent: true,
2741
2995
  opacity,
2742
- side: THREE10.DoubleSide
2996
+ side: THREE5.DoubleSide
2743
2997
  });
2744
- const mesh = new THREE10.Mesh(geometry, material);
2998
+ const mesh = new THREE5.Mesh(geometry, material);
2745
2999
  mesh.rotation.x = -Math.PI / 2;
2746
3000
  mesh.position.y = 0.01;
2747
3001
  return mesh;
@@ -2754,31 +3008,31 @@ function normalizeMouseCoordinates(clientX, clientY, element) {
2754
3008
  };
2755
3009
  }
2756
3010
  function isInFrustum(position, camera, padding = 0) {
2757
- const frustum = new THREE10.Frustum();
2758
- const projScreenMatrix = new THREE10.Matrix4();
3011
+ const frustum = new THREE5.Frustum();
3012
+ const projScreenMatrix = new THREE5.Matrix4();
2759
3013
  projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
2760
3014
  frustum.setFromProjectionMatrix(projScreenMatrix);
2761
- const sphere = new THREE10.Sphere(position, padding);
3015
+ const sphere = new THREE5.Sphere(position, padding);
2762
3016
  return frustum.intersectsSphere(sphere);
2763
3017
  }
2764
3018
  function filterByFrustum(positions, camera, padding = 0) {
2765
- const frustum = new THREE10.Frustum();
2766
- const projScreenMatrix = new THREE10.Matrix4();
3019
+ const frustum = new THREE5.Frustum();
3020
+ const projScreenMatrix = new THREE5.Matrix4();
2767
3021
  projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
2768
3022
  frustum.setFromProjectionMatrix(projScreenMatrix);
2769
3023
  return positions.filter((position) => {
2770
- const sphere = new THREE10.Sphere(position, padding);
3024
+ const sphere = new THREE5.Sphere(position, padding);
2771
3025
  return frustum.intersectsSphere(sphere);
2772
3026
  });
2773
3027
  }
2774
3028
  function getVisibleIndices(positions, camera, padding = 0) {
2775
- const frustum = new THREE10.Frustum();
2776
- const projScreenMatrix = new THREE10.Matrix4();
3029
+ const frustum = new THREE5.Frustum();
3030
+ const projScreenMatrix = new THREE5.Matrix4();
2777
3031
  const visible = /* @__PURE__ */ new Set();
2778
3032
  projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
2779
3033
  frustum.setFromProjectionMatrix(projScreenMatrix);
2780
3034
  positions.forEach((position, index) => {
2781
- const sphere = new THREE10.Sphere(position, padding);
3035
+ const sphere = new THREE5.Sphere(position, padding);
2782
3036
  if (frustum.intersectsSphere(sphere)) {
2783
3037
  visible.add(index);
2784
3038
  }
@@ -2802,7 +3056,7 @@ function updateInstanceLOD(instancedMesh, positions, camera, lodDistances) {
2802
3056
  return lodIndices;
2803
3057
  }
2804
3058
  function cullInstancedMesh(instancedMesh, positions, visibleIndices) {
2805
- const dummy = new THREE10.Object3D();
3059
+ const dummy = new THREE5.Object3D();
2806
3060
  let visibleCount = 0;
2807
3061
  positions.forEach((position, index) => {
2808
3062
  if (visibleIndices.has(index)) {
@@ -5585,12 +5839,12 @@ function useAvl3DConfig() {
5585
5839
  }
5586
5840
  function CameraController({ targetPosition, targetLookAt, animated }) {
5587
5841
  const { camera } = useThree();
5588
- const targetPosVec = useRef(new THREE10.Vector3(...targetPosition));
5589
- const targetLookVec = useRef(new THREE10.Vector3(...targetLookAt));
5842
+ const targetPosVec = useRef(new THREE5.Vector3(...targetPosition));
5843
+ const targetLookVec = useRef(new THREE5.Vector3(...targetLookAt));
5590
5844
  const isAnimating = useRef(false);
5591
5845
  useEffect(() => {
5592
- const newTarget = new THREE10.Vector3(...targetPosition);
5593
- const newLookAt = new THREE10.Vector3(...targetLookAt);
5846
+ const newTarget = new THREE5.Vector3(...targetPosition);
5847
+ const newLookAt = new THREE5.Vector3(...targetLookAt);
5594
5848
  if (!newTarget.equals(targetPosVec.current) || !newLookAt.equals(targetLookVec.current)) {
5595
5849
  targetPosVec.current.copy(newTarget);
5596
5850
  targetLookVec.current.copy(newLookAt);
@@ -5605,9 +5859,9 @@ function CameraController({ targetPosition, targetLookAt, animated }) {
5605
5859
  useFrame((_, delta) => {
5606
5860
  if (!isAnimating.current) return;
5607
5861
  const speed = 3;
5608
- camera.position.x = THREE10.MathUtils.damp(camera.position.x, targetPosVec.current.x, speed, delta);
5609
- camera.position.y = THREE10.MathUtils.damp(camera.position.y, targetPosVec.current.y, speed, delta);
5610
- camera.position.z = THREE10.MathUtils.damp(camera.position.z, targetPosVec.current.z, speed, delta);
5862
+ camera.position.x = THREE5.MathUtils.damp(camera.position.x, targetPosVec.current.x, speed, delta);
5863
+ camera.position.y = THREE5.MathUtils.damp(camera.position.y, targetPosVec.current.y, speed, delta);
5864
+ camera.position.z = THREE5.MathUtils.damp(camera.position.z, targetPosVec.current.z, speed, delta);
5611
5865
  camera.lookAt(targetLookVec.current);
5612
5866
  const dist = camera.position.distanceTo(targetPosVec.current);
5613
5867
  if (dist < 0.05) {
@@ -5622,7 +5876,7 @@ function SceneFade({ animating, children }) {
5622
5876
  useFrame((_, delta) => {
5623
5877
  if (!groupRef.current) return;
5624
5878
  const target = animating ? 0 : 1;
5625
- opacityRef.current = THREE10.MathUtils.damp(opacityRef.current, target, 5, delta);
5879
+ opacityRef.current = THREE5.MathUtils.damp(opacityRef.current, target, 5, delta);
5626
5880
  groupRef.current.visible = opacityRef.current > 0.05;
5627
5881
  const s = 0.9 + opacityRef.current * 0.1;
5628
5882
  groupRef.current.scale.setScalar(s);