@almadar/ui 5.139.0 → 5.141.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.
@@ -2,7 +2,7 @@ import React3, { forwardRef, useRef, useState, useEffect, useMemo, useImperative
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 THREE9 from 'three';
5
+ import * as THREE10 from 'three';
6
6
  import { Vector3, QuadraticBezierCurve3, MathUtils, Quaternion } from 'three';
7
7
  import { Grid, OrbitControls, Billboard, Text, Stars, Sparkles, Html, RoundedBox } from '@react-three/drei';
8
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -165,6 +165,7 @@ function collectDrawnItems(nodes) {
165
165
  case "draw-shape":
166
166
  case "draw-text":
167
167
  case "draw-group":
168
+ case "draw-mesh":
168
169
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
169
170
  break;
170
171
  case "draw-sprite-layer":
@@ -490,7 +491,7 @@ function Lighting3D({
490
491
  "directionalLightHelper",
491
492
  {
492
493
  args: [
493
- new THREE9.DirectionalLight(directionalColor, directionalIntensity),
494
+ new THREE10.DirectionalLight(directionalColor, directionalIntensity),
494
495
  5
495
496
  ]
496
497
  }
@@ -517,8 +518,8 @@ function FollowCamera3D({
517
518
  offset
518
519
  }) {
519
520
  const { camera } = useThree();
520
- const look = useRef(new THREE9.Vector3(target[0], target[1], target[2]));
521
- const goal = useRef(new THREE9.Vector3());
521
+ const look = useRef(new THREE10.Vector3(target[0], target[1], target[2]));
522
+ const goal = useRef(new THREE10.Vector3());
522
523
  useFrame((_, delta) => {
523
524
  const t = Math.min(1, delta * 5);
524
525
  goal.current.set(target[0] + offset[0], target[1] + offset[1], target[2] + offset[2]);
@@ -529,6 +530,17 @@ function FollowCamera3D({
529
530
  return null;
530
531
  }
531
532
 
533
+ // lib/drawable/projector3d.ts
534
+ function create3DProjector(opts = {}) {
535
+ const cellSize = opts.cellSize ?? 1;
536
+ const offsetX = opts.offsetX ?? 0;
537
+ const offsetZ = opts.offsetZ ?? 0;
538
+ return {
539
+ cellSize,
540
+ toWorld: (pos) => [pos.x * cellSize + offsetX, pos.z ?? 0, pos.y * cellSize + offsetZ]
541
+ };
542
+ }
543
+
532
544
  // lib/atlasSlice.ts
533
545
  var atlasCache = /* @__PURE__ */ new Map();
534
546
  function isTilesheet(a) {
@@ -663,12 +675,12 @@ function ModelLoader({
663
675
  if (!loadedModel) return null;
664
676
  const cloned = clone(loadedModel);
665
677
  cloned.updateMatrixWorld(true);
666
- const tintColor = tint ? new THREE9.Color(tint) : null;
678
+ const tintColor = tint ? new THREE10.Color(tint) : null;
667
679
  cloned.traverse((child) => {
668
- if (child instanceof THREE9.Mesh) {
680
+ if (child instanceof THREE10.Mesh) {
669
681
  child.castShadow = castShadow;
670
682
  child.receiveShadow = receiveShadow;
671
- if (tintColor && child.material instanceof THREE9.MeshStandardMaterial) {
683
+ if (tintColor && child.material instanceof THREE10.MeshStandardMaterial) {
672
684
  const mat = child.material.clone();
673
685
  mat.color.multiply(tintColor);
674
686
  child.material = mat;
@@ -677,7 +689,7 @@ function ModelLoader({
677
689
  });
678
690
  return cloned;
679
691
  }, [loadedModel, castShadow, receiveShadow, tint]);
680
- const mixer = useMemo(() => model ? new THREE9.AnimationMixer(model) : null, [model]);
692
+ const mixer = useMemo(() => model ? new THREE10.AnimationMixer(model) : null, [model]);
681
693
  useEffect(() => {
682
694
  if (!mixer || !animation || clips.length === 0) return;
683
695
  const wanted = animation.toLowerCase();
@@ -694,8 +706,8 @@ function ModelLoader({
694
706
  });
695
707
  const normFactor = useMemo(() => {
696
708
  if (!model) return 1;
697
- const box = new THREE9.Box3().setFromObject(model);
698
- const size = new THREE9.Vector3();
709
+ const box = new THREE10.Box3().setFromObject(model);
710
+ const size = new THREE10.Vector3();
699
711
  box.getSize(size);
700
712
  const maxDim = Math.max(size.x, size.y, size.z);
701
713
  if (!Number.isFinite(maxDim) || maxDim < 0.05) return 1;
@@ -785,7 +797,7 @@ var warnUnsupported3d = (kind) => {
785
797
  warnedUnsupported.add(kind);
786
798
  mesh3dLog.warn("unsupported drawable kind on the 3D backend \u2014 skipped", { kind });
787
799
  };
788
- var CrossOriginTextureLoader = class extends THREE9.TextureLoader {
800
+ var CrossOriginTextureLoader = class extends THREE10.TextureLoader {
789
801
  constructor() {
790
802
  super();
791
803
  this.crossOrigin = "anonymous";
@@ -803,7 +815,7 @@ function useBillboardTexture(url) {
803
815
  url,
804
816
  (texture) => {
805
817
  if (!active) return;
806
- texture.colorSpace = THREE9.SRGBColorSpace;
818
+ texture.colorSpace = THREE10.SRGBColorSpace;
807
819
  setState({ texture, error: false });
808
820
  },
809
821
  void 0,
@@ -835,7 +847,7 @@ function useAtlasFrame(asset) {
835
847
  return { frame: { x: r.sx, y: r.sy, w: r.sw, h: r.sh }, ready: true };
836
848
  }, [asset, tick]);
837
849
  }
838
- function SpriteBillboard({ node, world, cellSize = 1 }) {
850
+ function SpriteBillboard({ node, world, cellSize = 1, groupOpacity = 1 }) {
839
851
  const { texture, error: textureError } = useBillboardTexture(node.asset.url);
840
852
  const { frame: atlasFrame, ready: atlasReady } = useAtlasFrame(node.asset);
841
853
  const frame = node.frame ?? atlasFrame;
@@ -856,7 +868,7 @@ function SpriteBillboard({ node, world, cellSize = 1 }) {
856
868
  }, [texture, frame, node.height, node.width, anchor, cellSize]);
857
869
  const groundGeometry = React3.useMemo(() => {
858
870
  if (anchor !== "top-left" || !texture || !atlasReady) return null;
859
- const g = new THREE9.PlaneGeometry(size.width, size.height);
871
+ const g = new THREE10.PlaneGeometry(size.width, size.height);
860
872
  g.rotateX(-Math.PI / 2);
861
873
  return g;
862
874
  }, [anchor, texture, atlasReady, size.width, size.height]);
@@ -864,15 +876,15 @@ function SpriteBillboard({ node, world, cellSize = 1 }) {
864
876
  if (anchor === "top-left") {
865
877
  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: [
866
878
  /* @__PURE__ */ jsx("planeGeometry", { args: [size.width, size.height] }),
867
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE9.DoubleSide })
879
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE10.DoubleSide })
868
880
  ] }) });
869
881
  }
870
882
  return /* @__PURE__ */ jsx("group", { position: [world[0], world[1] + size.height / 2, world[2]], children: /* @__PURE__ */ jsxs("mesh", { children: [
871
883
  /* @__PURE__ */ jsx("planeGeometry", { args: [size.width, size.height] }),
872
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE9.DoubleSide })
884
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE10.DoubleSide })
873
885
  ] }) });
874
886
  }
875
- texture.magFilter = texture.minFilter = THREE9.NearestFilter;
887
+ texture.magFilter = texture.minFilter = THREE10.NearestFilter;
876
888
  texture.needsUpdate = true;
877
889
  if (frame) {
878
890
  texture.repeat.set(frame.w / size.imgW, frame.h / size.imgH);
@@ -885,8 +897,8 @@ function SpriteBillboard({ node, world, cellSize = 1 }) {
885
897
  map: texture,
886
898
  transparent: true,
887
899
  alphaTest: 0.1,
888
- side: THREE9.DoubleSide,
889
- opacity: node.opacity ?? 1
900
+ side: THREE10.DoubleSide,
901
+ opacity: (node.opacity ?? 1) * groupOpacity
890
902
  }
891
903
  ) }) });
892
904
  }
@@ -898,13 +910,13 @@ function SpriteBillboard({ node, world, cellSize = 1 }) {
898
910
  map: texture,
899
911
  transparent: true,
900
912
  alphaTest: 0.1,
901
- side: THREE9.DoubleSide,
913
+ side: THREE10.DoubleSide,
902
914
  opacity: node.opacity ?? 1
903
915
  }
904
916
  )
905
917
  ] }) });
906
918
  }
907
- function Sprite3D({ node, projector }) {
919
+ function Sprite3D({ node, projector, groupOpacity = 1 }) {
908
920
  const asset = node.asset;
909
921
  if (!asset?.url || !isValidScenePos(node.position)) return null;
910
922
  if (asset.dimension === "3d") {
@@ -918,7 +930,7 @@ function Sprite3D({ node, projector }) {
918
930
  {
919
931
  url: asset.url,
920
932
  scale,
921
- rotation: [0, node.rotation ?? 0, 0],
933
+ rotation: [0, (node.rotation ?? 0) * 180 / Math.PI, 0],
922
934
  animation: node.animation,
923
935
  fallbackGeometry: "box",
924
936
  castShadow: true,
@@ -926,9 +938,9 @@ function Sprite3D({ node, projector }) {
926
938
  }
927
939
  ) });
928
940
  }
929
- return /* @__PURE__ */ jsx(SpriteBillboard, { node, world: projector.toWorld(node.position), cellSize: projector.cellSize });
941
+ return /* @__PURE__ */ jsx(SpriteBillboard, { node, world: projector.toWorld(node.position), cellSize: projector.cellSize, groupOpacity });
930
942
  }
931
- function Shape3D({ node, projector }) {
943
+ function Shape3D({ node, projector, groupOpacity = 1 }) {
932
944
  if (!isValidScenePos(node.position)) return null;
933
945
  const world = projector.toWorld(node.position);
934
946
  const color = node.fill ?? node.stroke ?? "#ffffff";
@@ -948,7 +960,7 @@ function Shape3D({ node, projector }) {
948
960
  }
949
961
  case "poly": {
950
962
  if (!node.points || node.points.length === 0) return null;
951
- const s = new THREE9.Shape();
963
+ const s = new THREE10.Shape();
952
964
  node.points.forEach((p, i) => {
953
965
  if (i === 0) s.moveTo(p.x, p.y);
954
966
  else s.lineTo(p.x, p.y);
@@ -966,7 +978,7 @@ function Shape3D({ node, projector }) {
966
978
  }
967
979
  return /* @__PURE__ */ jsxs("mesh", { rotation: [-Math.PI / 2, 0, 0], position: [world[0], world[1] + 0.02, world[2]], children: [
968
980
  geometry,
969
- /* @__PURE__ */ jsx("meshBasicMaterial", { color, transparent: true, opacity: node.opacity ?? 1, side: THREE9.DoubleSide })
981
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color, transparent: true, opacity: (node.opacity ?? 1) * groupOpacity, side: THREE10.DoubleSide })
970
982
  ] });
971
983
  }
972
984
  function Text3D({ node, projector }) {
@@ -985,37 +997,246 @@ function Text3D({ node, projector }) {
985
997
  }
986
998
  ) });
987
999
  }
988
- function Drawable3D({ node, projector }) {
1000
+ var MESH_SEGMENTS_DEFAULT = 24;
1001
+ function clampSegments(segments) {
1002
+ const s = segments ?? MESH_SEGMENTS_DEFAULT;
1003
+ return Math.min(64, Math.max(3, Math.round(s)));
1004
+ }
1005
+ function isAnimatedMesh(node) {
1006
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
1007
+ }
1008
+ var lerp = (a, b, k) => a + (b - a) * k;
1009
+ var NUMERIC_TRACKS = [
1010
+ "offsetX",
1011
+ "offsetY",
1012
+ "offsetZ",
1013
+ "rotateX",
1014
+ "rotateY",
1015
+ "rotateZ",
1016
+ "scale",
1017
+ "opacity",
1018
+ "emissiveIntensity"
1019
+ ];
1020
+ function applyMeshAnimation(node, timeMs) {
1021
+ const anim = node.animation;
1022
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return null;
1023
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
1024
+ const cycle = timeMs / anim.durationMs;
1025
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
1026
+ const trackValue = (key) => {
1027
+ const defined = frames.filter((f) => f[key] !== void 0);
1028
+ if (defined.length === 0) return void 0;
1029
+ let prev;
1030
+ let next;
1031
+ for (const f of defined) {
1032
+ if (f.at <= t) prev = f;
1033
+ else if (!next) next = f;
1034
+ }
1035
+ if (!prev) return defined[0][key];
1036
+ if (!next) return prev[key];
1037
+ const span = next.at - prev.at;
1038
+ const k = span > 0 ? (t - prev.at) / span : 1;
1039
+ const a = prev[key];
1040
+ const b = next[key];
1041
+ if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
1042
+ return a;
1043
+ };
1044
+ const num = {};
1045
+ for (const key of NUMERIC_TRACKS) {
1046
+ const v = trackValue(key);
1047
+ if (v !== void 0) num[key] = v;
1048
+ }
1049
+ return {
1050
+ offset: [num.offsetX ?? 0, num.offsetY ?? 0, num.offsetZ ?? 0],
1051
+ rotate: [num.rotateX ?? 0, num.rotateY ?? 0, num.rotateZ ?? 0],
1052
+ scale: num.scale ?? 1,
1053
+ opacity: num.opacity,
1054
+ emissiveIntensity: num.emissiveIntensity,
1055
+ color: trackValue("color"),
1056
+ emissive: trackValue("emissive")
1057
+ };
1058
+ }
1059
+ function meshGeometry(node, cellSize) {
1060
+ const seg = clampSegments(node.segments);
1061
+ const r = (node.radius ?? 0.4) * cellSize;
1062
+ const w = (node.width ?? 1) * cellSize;
1063
+ const h = (node.height ?? 1) * cellSize;
1064
+ const d = (node.depth ?? node.width ?? 1) * cellSize;
1065
+ switch (node.shape) {
1066
+ case "box":
1067
+ return { element: /* @__PURE__ */ jsx("boxGeometry", { args: [w, h, d] }), lift: h / 2, size: Math.max(w, h, d) };
1068
+ case "sphere":
1069
+ return { element: /* @__PURE__ */ jsx("sphereGeometry", { args: [r, seg, seg] }), lift: r, size: r * 2 };
1070
+ case "capsule":
1071
+ return { element: /* @__PURE__ */ jsx("capsuleGeometry", { args: [r, h, Math.max(2, Math.round(seg / 2)), seg] }), lift: h / 2 + r, size: h + r * 2 };
1072
+ case "cylinder":
1073
+ return {
1074
+ element: /* @__PURE__ */ jsx("cylinderGeometry", { args: [(node.radiusTop ?? node.radius ?? 0.4) * cellSize, (node.radiusBottom ?? node.radius ?? 0.4) * cellSize, h, seg] }),
1075
+ lift: h / 2,
1076
+ size: Math.max(h, r * 2)
1077
+ };
1078
+ case "cone":
1079
+ return { element: /* @__PURE__ */ jsx("coneGeometry", { args: [r, h, seg] }), lift: h / 2, size: Math.max(h, r * 2) };
1080
+ case "torus": {
1081
+ const tube = (node.tube ?? (node.radius ?? 0.4) / 3) * cellSize;
1082
+ return { element: /* @__PURE__ */ jsx("torusGeometry", { args: [r, tube, Math.max(3, Math.round(seg / 2)), seg] }), lift: r + tube, size: (r + tube) * 2 };
1083
+ }
1084
+ case "plane":
1085
+ return { element: /* @__PURE__ */ jsx("planeGeometry", { args: [w, d] }), lift: 0, size: Math.max(w, d) };
1086
+ case "circle":
1087
+ return { element: /* @__PURE__ */ jsx("circleGeometry", { args: [r, seg] }), lift: 0, size: r * 2 };
1088
+ default:
1089
+ return null;
1090
+ }
1091
+ }
1092
+ var SIDE_MAP = {
1093
+ front: THREE10.FrontSide,
1094
+ back: THREE10.BackSide,
1095
+ double: THREE10.DoubleSide
1096
+ };
1097
+ function meshMaterial(mat, opacity, ref) {
1098
+ const m = mat ?? {};
1099
+ const common = {
1100
+ ref,
1101
+ color: m.color ?? "#ffffff",
1102
+ transparent: opacity < 1,
1103
+ opacity,
1104
+ side: SIDE_MAP[m.side ?? "front"]
1105
+ };
1106
+ const emissive = m.emissive ? { emissive: m.emissive, emissiveIntensity: m.emissiveIntensity ?? 1 } : {};
1107
+ switch (m.kind ?? "standard") {
1108
+ case "basic":
1109
+ return /* @__PURE__ */ jsx("meshBasicMaterial", { ...common });
1110
+ case "toon":
1111
+ return /* @__PURE__ */ jsx("meshToonMaterial", { ...common, ...emissive });
1112
+ case "physical":
1113
+ return /* @__PURE__ */ jsx(
1114
+ "meshPhysicalMaterial",
1115
+ {
1116
+ ...common,
1117
+ ...emissive,
1118
+ metalness: m.metalness ?? 0,
1119
+ roughness: m.roughness ?? 0.5,
1120
+ transmission: m.transmission ?? 0,
1121
+ ior: m.ior ?? 1.5,
1122
+ flatShading: m.flatShading ?? false
1123
+ }
1124
+ );
1125
+ case "standard":
1126
+ default:
1127
+ return /* @__PURE__ */ jsx(
1128
+ "meshStandardMaterial",
1129
+ {
1130
+ ...common,
1131
+ ...emissive,
1132
+ metalness: m.metalness ?? 0,
1133
+ roughness: m.roughness ?? 0.5,
1134
+ flatShading: m.flatShading ?? false
1135
+ }
1136
+ );
1137
+ }
1138
+ }
1139
+ function Mesh3D({
1140
+ node,
1141
+ projector,
1142
+ groupOpacity = 1
1143
+ }) {
1144
+ const groupRef = useRef(null);
1145
+ const meshRef = useRef(null);
1146
+ const materialRef = useRef(null);
1147
+ const animated = isAnimatedMesh(node);
1148
+ const validPos = isValidScenePos(node.position);
1149
+ const baseWorld = validPos ? projector.toWorld(node.position) : [0, 0, 0];
1150
+ const geo = useMemo(() => meshGeometry(node, projector.cellSize), [node, projector.cellSize]);
1151
+ useFrame(({ clock }) => {
1152
+ if (!animated || !groupRef.current) return;
1153
+ const state = applyMeshAnimation(node, clock.elapsedTime * 1e3);
1154
+ if (!state) return;
1155
+ const [wx, wy, wz] = baseWorld;
1156
+ groupRef.current.position.set(
1157
+ wx + state.offset[0] * projector.cellSize,
1158
+ wy + state.offset[2] * projector.cellSize,
1159
+ wz + state.offset[1] * projector.cellSize
1160
+ );
1161
+ groupRef.current.scale.setScalar(state.scale);
1162
+ if (meshRef.current) {
1163
+ const base = node.rotation ?? [0, 0, 0];
1164
+ meshRef.current.rotation.set(base[0] + state.rotate[0], base[1] + state.rotate[1], base[2] + state.rotate[2]);
1165
+ }
1166
+ const mat = materialRef.current;
1167
+ if (mat) {
1168
+ const effective = (state.opacity ?? node.opacity ?? 1) * (node.material?.opacity ?? 1) * groupOpacity;
1169
+ mat.opacity = effective;
1170
+ mat.transparent = effective < 1;
1171
+ if (state.color && mat.color) mat.color.set(state.color);
1172
+ if (state.emissive && mat.emissive) mat.emissive.set(state.emissive);
1173
+ if (state.emissiveIntensity !== void 0 && mat.emissiveIntensity !== void 0) {
1174
+ mat.emissiveIntensity = state.emissiveIntensity;
1175
+ }
1176
+ }
1177
+ });
1178
+ if (!validPos || !geo) return null;
1179
+ const lift = (node.pivot ?? "bottom") === "bottom" ? geo.lift : 0;
1180
+ const opacity = (node.opacity ?? 1) * (node.material?.opacity ?? 1) * groupOpacity;
1181
+ const rotation = node.rotation ?? [0, 0, 0];
1182
+ const outlineScale = geo.size > 0 ? 1 + (node.outline?.width ?? 0.05) * projector.cellSize / geo.size : 1;
1183
+ return /* @__PURE__ */ jsxs("group", { ref: groupRef, position: baseWorld, children: [
1184
+ /* @__PURE__ */ jsxs(
1185
+ "mesh",
1186
+ {
1187
+ ref: meshRef,
1188
+ position: [0, lift, 0],
1189
+ rotation,
1190
+ castShadow: node.castShadow ?? true,
1191
+ receiveShadow: node.receiveShadow ?? true,
1192
+ children: [
1193
+ geo.element,
1194
+ meshMaterial(node.material, opacity, materialRef)
1195
+ ]
1196
+ }
1197
+ ),
1198
+ node.outline && /* @__PURE__ */ jsxs("mesh", { position: [0, lift, 0], rotation, scale: outlineScale, children: [
1199
+ geo.element,
1200
+ /* @__PURE__ */ jsx(
1201
+ "meshBasicMaterial",
1202
+ {
1203
+ color: node.outline.color ?? "#101014",
1204
+ side: THREE10.BackSide,
1205
+ transparent: opacity < 1,
1206
+ opacity
1207
+ }
1208
+ )
1209
+ ] })
1210
+ ] });
1211
+ }
1212
+ function Drawable3D({ node, projector, groupOpacity = 1 }) {
989
1213
  switch (node.type) {
990
1214
  case "draw-sprite":
991
- return /* @__PURE__ */ jsx(Sprite3D, { node, projector });
1215
+ return /* @__PURE__ */ jsx(Sprite3D, { node, projector, groupOpacity });
992
1216
  case "draw-shape":
993
- return /* @__PURE__ */ jsx(Shape3D, { node, projector });
1217
+ return /* @__PURE__ */ jsx(Shape3D, { node, projector, groupOpacity });
994
1218
  case "draw-text":
995
1219
  return /* @__PURE__ */ jsx(Text3D, { node, projector });
1220
+ case "draw-mesh":
1221
+ return /* @__PURE__ */ jsx(Mesh3D, { node, projector, groupOpacity });
996
1222
  case "draw-sprite-layer":
997
- return /* @__PURE__ */ jsx(Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsx(Sprite3D, { node: item, projector }, i)) });
1223
+ return /* @__PURE__ */ jsx(Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsx(Sprite3D, { node: item, projector, groupOpacity }, i)) });
998
1224
  case "draw-shape-layer":
999
- return /* @__PURE__ */ jsx(Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsx(Shape3D, { node: item, projector }, i)) });
1225
+ return /* @__PURE__ */ jsx(Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsx(Shape3D, { node: item, projector, groupOpacity }, i)) });
1000
1226
  case "draw-text-layer":
1001
1227
  return /* @__PURE__ */ jsx(Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsx(Text3D, { node: item, projector }, i)) });
1002
- case "draw-group":
1003
- warnUnsupported3d("draw-group");
1004
- return null;
1228
+ case "draw-group": {
1229
+ if (!isValidScenePos(node.position) || !Array.isArray(node.items)) return null;
1230
+ if (node.clip) warnUnsupported3d("draw-group:clip");
1231
+ const world = projector.toWorld(node.position);
1232
+ const inner = create3DProjector({ cellSize: projector.cellSize });
1233
+ const opacity = (node.opacity ?? 1) * groupOpacity;
1234
+ const s = node.scale ?? 1;
1235
+ 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)) });
1236
+ }
1005
1237
  }
1006
1238
  }
1007
1239
 
1008
- // lib/drawable/projector3d.ts
1009
- function create3DProjector(opts = {}) {
1010
- const cellSize = opts.cellSize ?? 1;
1011
- const offsetX = opts.offsetX ?? 0;
1012
- const offsetZ = opts.offsetZ ?? 0;
1013
- return {
1014
- cellSize,
1015
- toWorld: (pos) => [pos.x * cellSize + offsetX, pos.z ?? 0, pos.y * cellSize + offsetZ]
1016
- };
1017
- }
1018
-
1019
1240
  // lib/drawable/three/game3dTheme.ts
1020
1241
  var GRID_COLORS_3D = {
1021
1242
  cell: "#444444",
@@ -1051,6 +1272,7 @@ var Canvas3DHost = forwardRef(
1051
1272
  keyMap,
1052
1273
  keyUpMap,
1053
1274
  pixelsPerUnit,
1275
+ fov,
1054
1276
  children,
1055
1277
  drawables
1056
1278
  }, ref) => {
@@ -1140,7 +1362,7 @@ var Canvas3DHost = forwardRef(
1140
1362
  getCameraPosition: () => {
1141
1363
  if (controlsRef.current) {
1142
1364
  const pos = controlsRef.current.object.position;
1143
- return new THREE9.Vector3(pos.x, pos.y, pos.z);
1365
+ return new THREE10.Vector3(pos.x, pos.y, pos.z);
1144
1366
  }
1145
1367
  return null;
1146
1368
  },
@@ -1179,18 +1401,19 @@ var Canvas3DHost = forwardRef(
1179
1401
  const cx = cameraTarget[0];
1180
1402
  const cz = cameraTarget[2];
1181
1403
  const d = size * 1;
1404
+ const fovDeg = fov ?? 45;
1182
1405
  switch (cameraMode) {
1183
1406
  case "isometric":
1184
- return { position: [cx + d, d * 0.8, cz + d], fov: 45 };
1407
+ return { position: [cx + d, d * 0.8, cz + d], fov: fovDeg };
1185
1408
  case "top-down":
1186
- return { position: [cx, d * 2, cz + d * 0.35], fov: 45 };
1409
+ return { position: [cx, d * 2, cz + d * 0.35], fov: fovDeg };
1187
1410
  case "follow":
1188
- return { position: [cx, d * 0.5, cz + d], fov: 45 };
1411
+ return { position: [cx, d * 0.5, cz + d], fov: fovDeg };
1189
1412
  case "perspective":
1190
1413
  default:
1191
- return { position: [cx + d, d, cz + d], fov: 45 };
1414
+ return { position: [cx + d, d, cz + d], fov: fovDeg };
1192
1415
  }
1193
- }, [cameraMode, gridBounds, cellSize, cameraTarget]);
1416
+ }, [cameraMode, gridBounds, cellSize, cameraTarget, fov]);
1194
1417
  const followWorld = useMemo(() => {
1195
1418
  if (followTarget) return drawableProjector.toWorld(followTarget);
1196
1419
  return cameraTarget;
@@ -1334,7 +1557,7 @@ var Canvas3DHost = forwardRef(
1334
1557
  dampingFactor: 0.05,
1335
1558
  enableZoom: true,
1336
1559
  enablePan: true,
1337
- touches: { ONE: THREE9.TOUCH.ROTATE, TWO: THREE9.TOUCH.DOLLY_PAN },
1560
+ touches: { ONE: THREE10.TOUCH.ROTATE, TWO: THREE10.TOUCH.DOLLY_PAN },
1338
1561
  minDistance: 2,
1339
1562
  maxDistance: 100,
1340
1563
  maxPolarAngle: Math.PI / 2 - 0.1
@@ -1357,15 +1580,15 @@ function Scene3D({ background = "#1a1a2e", fog, children }) {
1357
1580
  if (initializedRef.current) return;
1358
1581
  initializedRef.current = true;
1359
1582
  if (background.startsWith("#") || background.startsWith("rgb")) {
1360
- scene.background = new THREE9.Color(background);
1583
+ scene.background = new THREE10.Color(background);
1361
1584
  } else {
1362
- const loader = new THREE9.TextureLoader();
1585
+ const loader = new THREE10.TextureLoader();
1363
1586
  loader.load(background, (texture) => {
1364
1587
  scene.background = texture;
1365
1588
  });
1366
1589
  }
1367
1590
  if (fog) {
1368
- scene.fog = new THREE9.Fog(fog.color, fog.near, fog.far);
1591
+ scene.fog = new THREE10.Fog(fog.color, fog.near, fog.far);
1369
1592
  }
1370
1593
  return () => {
1371
1594
  scene.background = null;
@@ -1388,14 +1611,14 @@ var Camera3D = forwardRef(
1388
1611
  }, ref) => {
1389
1612
  const { camera, set, viewport } = useThree();
1390
1613
  const controlsRef = useRef(null);
1391
- const initialPosition = useRef(new THREE9.Vector3(...position));
1392
- const initialTarget = useRef(new THREE9.Vector3(...target));
1614
+ const initialPosition = useRef(new THREE10.Vector3(...position));
1615
+ const initialTarget = useRef(new THREE10.Vector3(...target));
1393
1616
  useEffect(() => {
1394
1617
  let newCamera;
1395
1618
  if (mode === "isometric") {
1396
1619
  const aspect = viewport.aspect;
1397
1620
  const size = 10 / zoom;
1398
- newCamera = new THREE9.OrthographicCamera(
1621
+ newCamera = new THREE10.OrthographicCamera(
1399
1622
  -size * aspect,
1400
1623
  size * aspect,
1401
1624
  size,
@@ -1404,7 +1627,7 @@ var Camera3D = forwardRef(
1404
1627
  1e3
1405
1628
  );
1406
1629
  } else {
1407
- newCamera = new THREE9.PerspectiveCamera(fov, viewport.aspect, 0.1, 1e3);
1630
+ newCamera = new THREE10.PerspectiveCamera(fov, viewport.aspect, 0.1, 1e3);
1408
1631
  }
1409
1632
  newCamera.position.copy(initialPosition.current);
1410
1633
  newCamera.lookAt(initialTarget.current.x, initialTarget.current.y, initialTarget.current.z);
@@ -1445,8 +1668,8 @@ var Camera3D = forwardRef(
1445
1668
  }
1446
1669
  },
1447
1670
  getViewBounds: () => {
1448
- const min = new THREE9.Vector3(-10, -10, -10);
1449
- const max = new THREE9.Vector3(10, 10, 10);
1671
+ const min = new THREE10.Vector3(-10, -10, -10);
1672
+ const max = new THREE10.Vector3(10, 10, 10);
1450
1673
  return { min, max };
1451
1674
  }
1452
1675
  }));
@@ -1488,7 +1711,7 @@ var AssetLoader = class {
1488
1711
  __publicField(this, "textureCache");
1489
1712
  __publicField(this, "loadingPromises");
1490
1713
  this.objLoader = new OBJLoader();
1491
- this.textureLoader = new THREE9.TextureLoader();
1714
+ this.textureLoader = new THREE10.TextureLoader();
1492
1715
  this.modelCache = /* @__PURE__ */ new Map();
1493
1716
  this.textureCache = /* @__PURE__ */ new Map();
1494
1717
  this.loadingPromises = /* @__PURE__ */ new Map();
@@ -1562,7 +1785,7 @@ var AssetLoader = class {
1562
1785
  return this.loadingPromises.get(`texture:${url}`);
1563
1786
  }
1564
1787
  const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
1565
- texture.colorSpace = THREE9.SRGBColorSpace;
1788
+ texture.colorSpace = THREE10.SRGBColorSpace;
1566
1789
  this.textureCache.set(url, texture);
1567
1790
  this.loadingPromises.delete(`texture:${url}`);
1568
1791
  return texture;
@@ -1636,7 +1859,7 @@ var AssetLoader = class {
1636
1859
  });
1637
1860
  this.modelCache.forEach((model) => {
1638
1861
  model.scene.traverse((child) => {
1639
- if (child instanceof THREE9.Mesh) {
1862
+ if (child instanceof THREE10.Mesh) {
1640
1863
  child.geometry.dispose();
1641
1864
  if (Array.isArray(child.material)) {
1642
1865
  child.material.forEach((m) => m.dispose());
@@ -1686,21 +1909,21 @@ function useThree4(options = {}) {
1686
1909
  const [isReady, setIsReady] = useState(false);
1687
1910
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
1688
1911
  const initialCameraPosition = useMemo(
1689
- () => new THREE9.Vector3(...opts.cameraPosition),
1912
+ () => new THREE10.Vector3(...opts.cameraPosition),
1690
1913
  []
1691
1914
  );
1692
1915
  useEffect(() => {
1693
1916
  if (!containerRef.current) return;
1694
1917
  const container = containerRef.current;
1695
1918
  const { clientWidth, clientHeight } = container;
1696
- const scene = new THREE9.Scene();
1697
- scene.background = new THREE9.Color(opts.backgroundColor);
1919
+ const scene = new THREE10.Scene();
1920
+ scene.background = new THREE10.Color(opts.backgroundColor);
1698
1921
  sceneRef.current = scene;
1699
1922
  let camera;
1700
1923
  const aspect = clientWidth / clientHeight;
1701
1924
  if (opts.cameraMode === "isometric") {
1702
1925
  const size = 10;
1703
- camera = new THREE9.OrthographicCamera(
1926
+ camera = new THREE10.OrthographicCamera(
1704
1927
  -size * aspect,
1705
1928
  size * aspect,
1706
1929
  size,
@@ -1709,11 +1932,11 @@ function useThree4(options = {}) {
1709
1932
  1e3
1710
1933
  );
1711
1934
  } else {
1712
- camera = new THREE9.PerspectiveCamera(45, aspect, 0.1, 1e3);
1935
+ camera = new THREE10.PerspectiveCamera(45, aspect, 0.1, 1e3);
1713
1936
  }
1714
1937
  camera.position.copy(initialCameraPosition);
1715
1938
  cameraRef.current = camera;
1716
- const renderer = new THREE9.WebGLRenderer({
1939
+ const renderer = new THREE10.WebGLRenderer({
1717
1940
  antialias: true,
1718
1941
  alpha: true,
1719
1942
  canvas: canvasRef.current || void 0
@@ -1721,7 +1944,7 @@ function useThree4(options = {}) {
1721
1944
  renderer.setSize(clientWidth, clientHeight);
1722
1945
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
1723
1946
  renderer.shadowMap.enabled = opts.shadows;
1724
- renderer.shadowMap.type = THREE9.PCFSoftShadowMap;
1947
+ renderer.shadowMap.type = THREE10.PCFSoftShadowMap;
1725
1948
  rendererRef.current = renderer;
1726
1949
  const controls = new OrbitControls$1(camera, renderer.domElement);
1727
1950
  controls.enableDamping = true;
@@ -1730,16 +1953,16 @@ function useThree4(options = {}) {
1730
1953
  controls.maxDistance = 100;
1731
1954
  controls.maxPolarAngle = Math.PI / 2 - 0.1;
1732
1955
  controlsRef.current = controls;
1733
- const ambientLight = new THREE9.AmbientLight(16777215, 0.6);
1956
+ const ambientLight = new THREE10.AmbientLight(16777215, 0.6);
1734
1957
  scene.add(ambientLight);
1735
- const directionalLight = new THREE9.DirectionalLight(16777215, 0.8);
1958
+ const directionalLight = new THREE10.DirectionalLight(16777215, 0.8);
1736
1959
  directionalLight.position.set(10, 20, 10);
1737
1960
  directionalLight.castShadow = opts.shadows;
1738
1961
  directionalLight.shadow.mapSize.width = 2048;
1739
1962
  directionalLight.shadow.mapSize.height = 2048;
1740
1963
  scene.add(directionalLight);
1741
1964
  if (opts.showGrid) {
1742
- const gridHelper = new THREE9.GridHelper(
1965
+ const gridHelper = new THREE10.GridHelper(
1743
1966
  opts.gridSize,
1744
1967
  opts.gridSize,
1745
1968
  4473924,
@@ -1757,10 +1980,10 @@ function useThree4(options = {}) {
1757
1980
  const handleResize = () => {
1758
1981
  const { clientWidth: width, clientHeight: height } = container;
1759
1982
  setDimensions({ width, height });
1760
- if (camera instanceof THREE9.PerspectiveCamera) {
1983
+ if (camera instanceof THREE10.PerspectiveCamera) {
1761
1984
  camera.aspect = width / height;
1762
1985
  camera.updateProjectionMatrix();
1763
- } else if (camera instanceof THREE9.OrthographicCamera) {
1986
+ } else if (camera instanceof THREE10.OrthographicCamera) {
1764
1987
  const aspect2 = width / height;
1765
1988
  const size = 10;
1766
1989
  camera.left = -size * aspect2;
@@ -1791,7 +2014,7 @@ function useThree4(options = {}) {
1791
2014
  let newCamera;
1792
2015
  if (opts.cameraMode === "isometric") {
1793
2016
  const size = 10;
1794
- newCamera = new THREE9.OrthographicCamera(
2017
+ newCamera = new THREE10.OrthographicCamera(
1795
2018
  -size * aspect,
1796
2019
  size * aspect,
1797
2020
  size,
@@ -1800,7 +2023,7 @@ function useThree4(options = {}) {
1800
2023
  1e3
1801
2024
  );
1802
2025
  } else {
1803
- newCamera = new THREE9.PerspectiveCamera(45, aspect, 0.1, 1e3);
2026
+ newCamera = new THREE10.PerspectiveCamera(45, aspect, 0.1, 1e3);
1804
2027
  }
1805
2028
  newCamera.position.copy(currentPos);
1806
2029
  cameraRef.current = newCamera;
@@ -2130,8 +2353,8 @@ function useSceneGraph() {
2130
2353
  }
2131
2354
  function useRaycaster(options) {
2132
2355
  const { camera, canvas, cellSize = 1, offsetX = 0, offsetZ = 0 } = options;
2133
- const raycaster = useRef(new THREE9.Raycaster());
2134
- const mouse = useRef(new THREE9.Vector2());
2356
+ const raycaster = useRef(new THREE10.Raycaster());
2357
+ const mouse = useRef(new THREE10.Vector2());
2135
2358
  const clientToNDC = useCallback(
2136
2359
  (clientX, clientY) => {
2137
2360
  if (!canvas) {
@@ -2201,8 +2424,8 @@ function useRaycaster(options) {
2201
2424
  const ndc = clientToNDC(clientX, clientY);
2202
2425
  mouse.current.set(ndc.x, ndc.y);
2203
2426
  raycaster.current.setFromCamera(mouse.current, camera);
2204
- const plane = new THREE9.Plane(new THREE9.Vector3(0, 1, 0), 0);
2205
- const target = new THREE9.Vector3();
2427
+ const plane = new THREE10.Plane(new THREE10.Vector3(0, 1, 0), 0);
2428
+ const target = new THREE10.Vector3();
2206
2429
  const intersection = raycaster.current.ray.intersectPlane(plane, target);
2207
2430
  if (intersection) {
2208
2431
  const gridX = Math.round((target.x - offsetX) / cellSize);
@@ -2240,7 +2463,7 @@ function useRaycaster(options) {
2240
2463
  return {
2241
2464
  gridX: gridCoords.x,
2242
2465
  gridZ: gridCoords.z,
2243
- worldPosition: new THREE9.Vector3(
2466
+ worldPosition: new THREE10.Vector3(
2244
2467
  gridCoords.x * cellSize + offsetX,
2245
2468
  0,
2246
2469
  gridCoords.z * cellSize + offsetZ
@@ -2270,7 +2493,7 @@ var DEFAULT_CONFIG = {
2270
2493
  };
2271
2494
  function gridToWorld(gridX, gridZ, config = DEFAULT_CONFIG) {
2272
2495
  const opts = { ...DEFAULT_CONFIG, ...config };
2273
- return new THREE9.Vector3(
2496
+ return new THREE10.Vector3(
2274
2497
  gridX * opts.cellSize + opts.offsetX,
2275
2498
  opts.elevation,
2276
2499
  gridZ * opts.cellSize + opts.offsetZ
@@ -2284,17 +2507,17 @@ function worldToGrid(worldX, worldZ, config = DEFAULT_CONFIG) {
2284
2507
  };
2285
2508
  }
2286
2509
  function raycastToPlane(camera, mouseX, mouseY, planeY = 0) {
2287
- const raycaster = new THREE9.Raycaster();
2288
- const mouse = new THREE9.Vector2(mouseX, mouseY);
2510
+ const raycaster = new THREE10.Raycaster();
2511
+ const mouse = new THREE10.Vector2(mouseX, mouseY);
2289
2512
  raycaster.setFromCamera(mouse, camera);
2290
- const plane = new THREE9.Plane(new THREE9.Vector3(0, 1, 0), -planeY);
2291
- const target = new THREE9.Vector3();
2513
+ const plane = new THREE10.Plane(new THREE10.Vector3(0, 1, 0), -planeY);
2514
+ const target = new THREE10.Vector3();
2292
2515
  const intersection = raycaster.ray.intersectPlane(plane, target);
2293
2516
  return intersection ? target : null;
2294
2517
  }
2295
2518
  function raycastToObjects(camera, mouseX, mouseY, objects) {
2296
- const raycaster = new THREE9.Raycaster();
2297
- const mouse = new THREE9.Vector2(mouseX, mouseY);
2519
+ const raycaster = new THREE10.Raycaster();
2520
+ const mouse = new THREE10.Vector2(mouseX, mouseY);
2298
2521
  raycaster.setFromCamera(mouse, camera);
2299
2522
  const intersects = raycaster.intersectObjects(objects, true);
2300
2523
  return intersects.length > 0 ? intersects[0] : null;
@@ -2346,14 +2569,14 @@ function getCellsInRadius(centerX, centerZ, radius) {
2346
2569
  return cells;
2347
2570
  }
2348
2571
  function createGridHighlight(color = 16776960, opacity = 0.3) {
2349
- const geometry = new THREE9.PlaneGeometry(0.95, 0.95);
2350
- const material = new THREE9.MeshBasicMaterial({
2572
+ const geometry = new THREE10.PlaneGeometry(0.95, 0.95);
2573
+ const material = new THREE10.MeshBasicMaterial({
2351
2574
  color,
2352
2575
  transparent: true,
2353
2576
  opacity,
2354
- side: THREE9.DoubleSide
2577
+ side: THREE10.DoubleSide
2355
2578
  });
2356
- const mesh = new THREE9.Mesh(geometry, material);
2579
+ const mesh = new THREE10.Mesh(geometry, material);
2357
2580
  mesh.rotation.x = -Math.PI / 2;
2358
2581
  mesh.position.y = 0.01;
2359
2582
  return mesh;
@@ -2366,31 +2589,31 @@ function normalizeMouseCoordinates(clientX, clientY, element) {
2366
2589
  };
2367
2590
  }
2368
2591
  function isInFrustum(position, camera, padding = 0) {
2369
- const frustum = new THREE9.Frustum();
2370
- const projScreenMatrix = new THREE9.Matrix4();
2592
+ const frustum = new THREE10.Frustum();
2593
+ const projScreenMatrix = new THREE10.Matrix4();
2371
2594
  projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
2372
2595
  frustum.setFromProjectionMatrix(projScreenMatrix);
2373
- const sphere = new THREE9.Sphere(position, padding);
2596
+ const sphere = new THREE10.Sphere(position, padding);
2374
2597
  return frustum.intersectsSphere(sphere);
2375
2598
  }
2376
2599
  function filterByFrustum(positions, camera, padding = 0) {
2377
- const frustum = new THREE9.Frustum();
2378
- const projScreenMatrix = new THREE9.Matrix4();
2600
+ const frustum = new THREE10.Frustum();
2601
+ const projScreenMatrix = new THREE10.Matrix4();
2379
2602
  projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
2380
2603
  frustum.setFromProjectionMatrix(projScreenMatrix);
2381
2604
  return positions.filter((position) => {
2382
- const sphere = new THREE9.Sphere(position, padding);
2605
+ const sphere = new THREE10.Sphere(position, padding);
2383
2606
  return frustum.intersectsSphere(sphere);
2384
2607
  });
2385
2608
  }
2386
2609
  function getVisibleIndices(positions, camera, padding = 0) {
2387
- const frustum = new THREE9.Frustum();
2388
- const projScreenMatrix = new THREE9.Matrix4();
2610
+ const frustum = new THREE10.Frustum();
2611
+ const projScreenMatrix = new THREE10.Matrix4();
2389
2612
  const visible = /* @__PURE__ */ new Set();
2390
2613
  projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
2391
2614
  frustum.setFromProjectionMatrix(projScreenMatrix);
2392
2615
  positions.forEach((position, index) => {
2393
- const sphere = new THREE9.Sphere(position, padding);
2616
+ const sphere = new THREE10.Sphere(position, padding);
2394
2617
  if (frustum.intersectsSphere(sphere)) {
2395
2618
  visible.add(index);
2396
2619
  }
@@ -2414,7 +2637,7 @@ function updateInstanceLOD(instancedMesh, positions, camera, lodDistances) {
2414
2637
  return lodIndices;
2415
2638
  }
2416
2639
  function cullInstancedMesh(instancedMesh, positions, visibleIndices) {
2417
- const dummy = new THREE9.Object3D();
2640
+ const dummy = new THREE10.Object3D();
2418
2641
  let visibleCount = 0;
2419
2642
  positions.forEach((position, index) => {
2420
2643
  if (visibleIndices.has(index)) {
@@ -5197,12 +5420,12 @@ function useAvl3DConfig() {
5197
5420
  }
5198
5421
  function CameraController({ targetPosition, targetLookAt, animated }) {
5199
5422
  const { camera } = useThree();
5200
- const targetPosVec = useRef(new THREE9.Vector3(...targetPosition));
5201
- const targetLookVec = useRef(new THREE9.Vector3(...targetLookAt));
5423
+ const targetPosVec = useRef(new THREE10.Vector3(...targetPosition));
5424
+ const targetLookVec = useRef(new THREE10.Vector3(...targetLookAt));
5202
5425
  const isAnimating = useRef(false);
5203
5426
  useEffect(() => {
5204
- const newTarget = new THREE9.Vector3(...targetPosition);
5205
- const newLookAt = new THREE9.Vector3(...targetLookAt);
5427
+ const newTarget = new THREE10.Vector3(...targetPosition);
5428
+ const newLookAt = new THREE10.Vector3(...targetLookAt);
5206
5429
  if (!newTarget.equals(targetPosVec.current) || !newLookAt.equals(targetLookVec.current)) {
5207
5430
  targetPosVec.current.copy(newTarget);
5208
5431
  targetLookVec.current.copy(newLookAt);
@@ -5217,9 +5440,9 @@ function CameraController({ targetPosition, targetLookAt, animated }) {
5217
5440
  useFrame((_, delta) => {
5218
5441
  if (!isAnimating.current) return;
5219
5442
  const speed = 3;
5220
- camera.position.x = THREE9.MathUtils.damp(camera.position.x, targetPosVec.current.x, speed, delta);
5221
- camera.position.y = THREE9.MathUtils.damp(camera.position.y, targetPosVec.current.y, speed, delta);
5222
- camera.position.z = THREE9.MathUtils.damp(camera.position.z, targetPosVec.current.z, speed, delta);
5443
+ camera.position.x = THREE10.MathUtils.damp(camera.position.x, targetPosVec.current.x, speed, delta);
5444
+ camera.position.y = THREE10.MathUtils.damp(camera.position.y, targetPosVec.current.y, speed, delta);
5445
+ camera.position.z = THREE10.MathUtils.damp(camera.position.z, targetPosVec.current.z, speed, delta);
5223
5446
  camera.lookAt(targetLookVec.current);
5224
5447
  const dist = camera.position.distanceTo(targetPosVec.current);
5225
5448
  if (dist < 0.05) {
@@ -5234,7 +5457,7 @@ function SceneFade({ animating, children }) {
5234
5457
  useFrame((_, delta) => {
5235
5458
  if (!groupRef.current) return;
5236
5459
  const target = animating ? 0 : 1;
5237
- opacityRef.current = THREE9.MathUtils.damp(opacityRef.current, target, 5, delta);
5460
+ opacityRef.current = THREE10.MathUtils.damp(opacityRef.current, target, 5, delta);
5238
5461
  groupRef.current.visible = opacityRef.current > 0.05;
5239
5462
  const s = 0.9 + opacityRef.current * 0.1;
5240
5463
  groupRef.current.scale.setScalar(s);