@almadar/ui 5.140.0 → 5.142.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,11 +1,13 @@
1
- import React3, { forwardRef, useRef, useState, useEffect, useMemo, useImperativeHandle, useCallback, createContext, useContext, Component, useReducer } from 'react';
1
+ import React3, { forwardRef, useRef, useState, useMemo, useEffect, useImperativeHandle, useCallback, createContext, 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 THREE9 from 'three';
5
+ import * as THREE10 from 'three';
6
6
  import { Vector3, QuadraticBezierCurve3, MathUtils, Quaternion } from 'three';
7
+ import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
7
8
  import { Grid, OrbitControls, Billboard, Text, Stars, Sparkles, Html, RoundedBox } from '@react-three/drei';
8
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
+ import { Bloom, Vignette, EffectComposer, DepthOfField } from '@react-three/postprocessing';
9
11
  import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
10
12
  import { clone } from 'three/examples/jsm/utils/SkeletonUtils';
11
13
  import { clsx } from 'clsx';
@@ -13,7 +15,6 @@ import { twMerge } from 'tailwind-merge';
13
15
  import { OrbitControls as OrbitControls$1 } from 'three/examples/jsm/controls/OrbitControls.js';
14
16
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
15
17
  import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
16
- import { EffectComposer, Bloom, DepthOfField, Vignette } from '@react-three/postprocessing';
17
18
  import { useTranslate } from '@almadar/ui/hooks';
18
19
 
19
20
  var __defProp = Object.defineProperty;
@@ -165,6 +166,7 @@ function collectDrawnItems(nodes) {
165
166
  case "draw-shape":
166
167
  case "draw-text":
167
168
  case "draw-group":
169
+ case "draw-mesh":
168
170
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
169
171
  break;
170
172
  case "draw-sprite-layer":
@@ -449,6 +451,10 @@ function Lighting3D({
449
451
  directionalIntensity = 0.8,
450
452
  directionalColor = "#ffffff",
451
453
  directionalPosition = [10, 20, 10],
454
+ hemisphereIntensity = 0.3,
455
+ hemisphereColor = "#87ceeb",
456
+ hemisphereGroundColor = "#362d1d",
457
+ points,
452
458
  shadows = true,
453
459
  shadowMapSize = 2048,
454
460
  shadowCameraSize = 20,
@@ -481,22 +487,63 @@ function Lighting3D({
481
487
  /* @__PURE__ */ jsx(
482
488
  "hemisphereLight",
483
489
  {
484
- intensity: 0.3,
485
- color: "#87ceeb",
486
- groundColor: "#362d1d"
490
+ intensity: hemisphereIntensity,
491
+ color: hemisphereColor,
492
+ groundColor: hemisphereGroundColor
487
493
  }
488
494
  ),
495
+ points?.map((p, i) => /* @__PURE__ */ jsx(
496
+ "pointLight",
497
+ {
498
+ position: p.position,
499
+ intensity: p.intensity,
500
+ color: p.color,
501
+ distance: p.distance,
502
+ decay: p.decay
503
+ },
504
+ i
505
+ )),
489
506
  showHelpers && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
490
507
  "directionalLightHelper",
491
508
  {
492
509
  args: [
493
- new THREE9.DirectionalLight(directionalColor, directionalIntensity),
510
+ new THREE10.DirectionalLight(directionalColor, directionalIntensity),
494
511
  5
495
512
  ]
496
513
  }
497
514
  ) })
498
515
  ] });
499
516
  }
517
+ function Effects3D({ post }) {
518
+ if (!post || !post.bloom && !post.vignette) return null;
519
+ const passes = [];
520
+ if (post.bloom) {
521
+ passes.push(
522
+ /* @__PURE__ */ jsx(
523
+ Bloom,
524
+ {
525
+ intensity: post.bloom.intensity ?? 1,
526
+ luminanceThreshold: post.bloom.threshold ?? 0.9,
527
+ luminanceSmoothing: post.bloom.smoothing ?? 0.3
528
+ },
529
+ "bloom"
530
+ )
531
+ );
532
+ }
533
+ if (post.vignette) {
534
+ passes.push(
535
+ /* @__PURE__ */ jsx(
536
+ Vignette,
537
+ {
538
+ offset: post.vignette.offset ?? 0.3,
539
+ darkness: post.vignette.darkness ?? 0.6
540
+ },
541
+ "vignette"
542
+ )
543
+ );
544
+ }
545
+ return /* @__PURE__ */ jsx(EffectComposer, { children: passes });
546
+ }
500
547
  function CameraController3D({
501
548
  onCameraChange
502
549
  }) {
@@ -517,8 +564,8 @@ function FollowCamera3D({
517
564
  offset
518
565
  }) {
519
566
  const { camera } = useThree();
520
- const look = useRef(new THREE9.Vector3(target[0], target[1], target[2]));
521
- const goal = useRef(new THREE9.Vector3());
567
+ const look = useRef(new THREE10.Vector3(target[0], target[1], target[2]));
568
+ const goal = useRef(new THREE10.Vector3());
522
569
  useFrame((_, delta) => {
523
570
  const t = Math.min(1, delta * 5);
524
571
  goal.current.set(target[0] + offset[0], target[1] + offset[1], target[2] + offset[2]);
@@ -529,6 +576,88 @@ function FollowCamera3D({
529
576
  return null;
530
577
  }
531
578
 
579
+ // lib/drawable/projector3d.ts
580
+ function create3DProjector(opts = {}) {
581
+ const cellSize = opts.cellSize ?? 1;
582
+ const offsetX = opts.offsetX ?? 0;
583
+ const offsetZ = opts.offsetZ ?? 0;
584
+ return {
585
+ cellSize,
586
+ toWorld: (pos) => [pos.x * cellSize + offsetX, pos.z ?? 0, pos.y * cellSize + offsetZ]
587
+ };
588
+ }
589
+ var NUMERIC_TRACKS = [
590
+ "offsetX",
591
+ "offsetY",
592
+ "rotate",
593
+ "opacity",
594
+ "radiusX",
595
+ "radiusY",
596
+ "width",
597
+ "height",
598
+ "strokeWidth",
599
+ "strokeDashOffset",
600
+ "blur"
601
+ ];
602
+ function isAnimatedShape(node) {
603
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
604
+ }
605
+ var lerp = (a, b, k) => a + (b - a) * k;
606
+ function applyShapeAnimation(node, timeMs) {
607
+ const anim = node.animation;
608
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return node;
609
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
610
+ const cycle = timeMs / anim.durationMs;
611
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
612
+ const out = { ...node };
613
+ const trackValue = (key) => {
614
+ const defined = frames.filter((f) => f[key] !== void 0);
615
+ if (defined.length === 0) return void 0;
616
+ let prev;
617
+ let next;
618
+ for (const f of defined) {
619
+ if (f.at <= t) prev = f;
620
+ else if (!next) next = f;
621
+ }
622
+ if (!prev) return defined[0][key];
623
+ if (!next) return prev[key];
624
+ const span = next.at - prev.at;
625
+ const k = span > 0 ? (t - prev.at) / span : 1;
626
+ const a = prev[key];
627
+ const b = next[key];
628
+ if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
629
+ return a;
630
+ };
631
+ for (const key of NUMERIC_TRACKS) {
632
+ const v = trackValue(key);
633
+ if (v !== void 0) out[key] = v;
634
+ }
635
+ const fill = trackValue("fill");
636
+ if (fill !== void 0) out.fill = fill;
637
+ const stroke = trackValue("stroke");
638
+ if (stroke !== void 0) out.stroke = stroke;
639
+ const shadowFrames = frames.filter((f) => f.shadow !== void 0);
640
+ if (shadowFrames.length > 0) {
641
+ const sh = trackValue("shadow");
642
+ if (sh !== void 0) {
643
+ let prevSh;
644
+ let nextSh;
645
+ for (const f of shadowFrames) {
646
+ if (f.at <= t) prevSh = f;
647
+ else if (!nextSh) nextSh = f;
648
+ }
649
+ if (prevSh?.shadow && nextSh?.shadow) {
650
+ const span = nextSh.at - prevSh.at;
651
+ 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) };
653
+ } else {
654
+ out.shadow = sh;
655
+ }
656
+ }
657
+ }
658
+ return out;
659
+ }
660
+
532
661
  // lib/atlasSlice.ts
533
662
  var atlasCache = /* @__PURE__ */ new Map();
534
663
  function isTilesheet(a) {
@@ -663,12 +792,12 @@ function ModelLoader({
663
792
  if (!loadedModel) return null;
664
793
  const cloned = clone(loadedModel);
665
794
  cloned.updateMatrixWorld(true);
666
- const tintColor = tint ? new THREE9.Color(tint) : null;
795
+ const tintColor = tint ? new THREE10.Color(tint) : null;
667
796
  cloned.traverse((child) => {
668
- if (child instanceof THREE9.Mesh) {
797
+ if (child instanceof THREE10.Mesh) {
669
798
  child.castShadow = castShadow;
670
799
  child.receiveShadow = receiveShadow;
671
- if (tintColor && child.material instanceof THREE9.MeshStandardMaterial) {
800
+ if (tintColor && child.material instanceof THREE10.MeshStandardMaterial) {
672
801
  const mat = child.material.clone();
673
802
  mat.color.multiply(tintColor);
674
803
  child.material = mat;
@@ -677,7 +806,7 @@ function ModelLoader({
677
806
  });
678
807
  return cloned;
679
808
  }, [loadedModel, castShadow, receiveShadow, tint]);
680
- const mixer = useMemo(() => model ? new THREE9.AnimationMixer(model) : null, [model]);
809
+ const mixer = useMemo(() => model ? new THREE10.AnimationMixer(model) : null, [model]);
681
810
  useEffect(() => {
682
811
  if (!mixer || !animation || clips.length === 0) return;
683
812
  const wanted = animation.toLowerCase();
@@ -694,8 +823,8 @@ function ModelLoader({
694
823
  });
695
824
  const normFactor = useMemo(() => {
696
825
  if (!model) return 1;
697
- const box = new THREE9.Box3().setFromObject(model);
698
- const size = new THREE9.Vector3();
826
+ const box = new THREE10.Box3().setFromObject(model);
827
+ const size = new THREE10.Vector3();
699
828
  box.getSize(size);
700
829
  const maxDim = Math.max(size.x, size.y, size.z);
701
830
  if (!Number.isFinite(maxDim) || maxDim < 0.05) return 1;
@@ -785,7 +914,7 @@ var warnUnsupported3d = (kind) => {
785
914
  warnedUnsupported.add(kind);
786
915
  mesh3dLog.warn("unsupported drawable kind on the 3D backend \u2014 skipped", { kind });
787
916
  };
788
- var CrossOriginTextureLoader = class extends THREE9.TextureLoader {
917
+ var CrossOriginTextureLoader = class extends THREE10.TextureLoader {
789
918
  constructor() {
790
919
  super();
791
920
  this.crossOrigin = "anonymous";
@@ -803,7 +932,7 @@ function useBillboardTexture(url) {
803
932
  url,
804
933
  (texture) => {
805
934
  if (!active) return;
806
- texture.colorSpace = THREE9.SRGBColorSpace;
935
+ texture.colorSpace = THREE10.SRGBColorSpace;
807
936
  setState({ texture, error: false });
808
937
  },
809
938
  void 0,
@@ -835,7 +964,7 @@ function useAtlasFrame(asset) {
835
964
  return { frame: { x: r.sx, y: r.sy, w: r.sw, h: r.sh }, ready: true };
836
965
  }, [asset, tick]);
837
966
  }
838
- function SpriteBillboard({ node, world, cellSize = 1 }) {
967
+ function SpriteBillboard({ node, world, cellSize = 1, groupOpacity = 1 }) {
839
968
  const { texture, error: textureError } = useBillboardTexture(node.asset.url);
840
969
  const { frame: atlasFrame, ready: atlasReady } = useAtlasFrame(node.asset);
841
970
  const frame = node.frame ?? atlasFrame;
@@ -856,7 +985,7 @@ function SpriteBillboard({ node, world, cellSize = 1 }) {
856
985
  }, [texture, frame, node.height, node.width, anchor, cellSize]);
857
986
  const groundGeometry = React3.useMemo(() => {
858
987
  if (anchor !== "top-left" || !texture || !atlasReady) return null;
859
- const g = new THREE9.PlaneGeometry(size.width, size.height);
988
+ const g = new THREE10.PlaneGeometry(size.width, size.height);
860
989
  g.rotateX(-Math.PI / 2);
861
990
  return g;
862
991
  }, [anchor, texture, atlasReady, size.width, size.height]);
@@ -864,15 +993,15 @@ function SpriteBillboard({ node, world, cellSize = 1 }) {
864
993
  if (anchor === "top-left") {
865
994
  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
995
  /* @__PURE__ */ jsx("planeGeometry", { args: [size.width, size.height] }),
867
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE9.DoubleSide })
996
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE10.DoubleSide })
868
997
  ] }) });
869
998
  }
870
999
  return /* @__PURE__ */ jsx("group", { position: [world[0], world[1] + size.height / 2, world[2]], children: /* @__PURE__ */ jsxs("mesh", { children: [
871
1000
  /* @__PURE__ */ jsx("planeGeometry", { args: [size.width, size.height] }),
872
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE9.DoubleSide })
1001
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: textureError ? 16729156 : 8947848, transparent: true, opacity: 0.6, side: THREE10.DoubleSide })
873
1002
  ] }) });
874
1003
  }
875
- texture.magFilter = texture.minFilter = THREE9.NearestFilter;
1004
+ texture.magFilter = texture.minFilter = THREE10.NearestFilter;
876
1005
  texture.needsUpdate = true;
877
1006
  if (frame) {
878
1007
  texture.repeat.set(frame.w / size.imgW, frame.h / size.imgH);
@@ -885,8 +1014,8 @@ function SpriteBillboard({ node, world, cellSize = 1 }) {
885
1014
  map: texture,
886
1015
  transparent: true,
887
1016
  alphaTest: 0.1,
888
- side: THREE9.DoubleSide,
889
- opacity: node.opacity ?? 1
1017
+ side: THREE10.DoubleSide,
1018
+ opacity: (node.opacity ?? 1) * groupOpacity
890
1019
  }
891
1020
  ) }) });
892
1021
  }
@@ -898,13 +1027,13 @@ function SpriteBillboard({ node, world, cellSize = 1 }) {
898
1027
  map: texture,
899
1028
  transparent: true,
900
1029
  alphaTest: 0.1,
901
- side: THREE9.DoubleSide,
1030
+ side: THREE10.DoubleSide,
902
1031
  opacity: node.opacity ?? 1
903
1032
  }
904
1033
  )
905
1034
  ] }) });
906
1035
  }
907
- function Sprite3D({ node, projector }) {
1036
+ function Sprite3D({ node, projector, groupOpacity = 1 }) {
908
1037
  const asset = node.asset;
909
1038
  if (!asset?.url || !isValidScenePos(node.position)) return null;
910
1039
  if (asset.dimension === "3d") {
@@ -918,7 +1047,7 @@ function Sprite3D({ node, projector }) {
918
1047
  {
919
1048
  url: asset.url,
920
1049
  scale,
921
- rotation: [0, node.rotation ?? 0, 0],
1050
+ rotation: [0, (node.rotation ?? 0) * 180 / Math.PI, 0],
922
1051
  animation: node.animation,
923
1052
  fallbackGeometry: "box",
924
1053
  castShadow: true,
@@ -926,11 +1055,30 @@ function Sprite3D({ node, projector }) {
926
1055
  }
927
1056
  ) });
928
1057
  }
929
- return /* @__PURE__ */ jsx(SpriteBillboard, { node, world: projector.toWorld(node.position), cellSize: projector.cellSize });
1058
+ return /* @__PURE__ */ jsx(SpriteBillboard, { node, world: projector.toWorld(node.position), cellSize: projector.cellSize, groupOpacity });
930
1059
  }
931
- function Shape3D({ node, projector }) {
932
- if (!isValidScenePos(node.position)) return null;
933
- const world = projector.toWorld(node.position);
1060
+ function Shape3D({ node, projector, groupOpacity = 1 }) {
1061
+ const groupRef = React3.useRef(null);
1062
+ const materialRef = React3.useRef(null);
1063
+ const animated = isAnimatedShape(node);
1064
+ const validPos = isValidScenePos(node.position);
1065
+ const world = validPos ? projector.toWorld(node.position) : [0, 0, 0];
1066
+ useFrame(({ clock }) => {
1067
+ if (!animated || !groupRef.current) return;
1068
+ const view = applyShapeAnimation(node, clock.elapsedTime * 1e3);
1069
+ groupRef.current.position.set(
1070
+ world[0] + (view.offsetX ?? 0) * projector.cellSize,
1071
+ world[1] + 0.02,
1072
+ world[2] + (view.offsetY ?? 0) * projector.cellSize
1073
+ );
1074
+ groupRef.current.rotation.y = -(view.rotate ?? 0);
1075
+ const mat = materialRef.current;
1076
+ if (mat) {
1077
+ mat.opacity = (view.opacity ?? node.opacity ?? 1) * groupOpacity;
1078
+ if (view.fill) mat.color.set(view.fill);
1079
+ }
1080
+ });
1081
+ if (!validPos) return null;
934
1082
  const color = node.fill ?? node.stroke ?? "#ffffff";
935
1083
  let geometry;
936
1084
  switch (node.shape) {
@@ -948,7 +1096,7 @@ function Shape3D({ node, projector }) {
948
1096
  }
949
1097
  case "poly": {
950
1098
  if (!node.points || node.points.length === 0) return null;
951
- const s = new THREE9.Shape();
1099
+ const s = new THREE10.Shape();
952
1100
  node.points.forEach((p, i) => {
953
1101
  if (i === 0) s.moveTo(p.x, p.y);
954
1102
  else s.lineTo(p.x, p.y);
@@ -964,12 +1112,12 @@ function Shape3D({ node, projector }) {
964
1112
  default:
965
1113
  return null;
966
1114
  }
967
- return /* @__PURE__ */ jsxs("mesh", { rotation: [-Math.PI / 2, 0, 0], position: [world[0], world[1] + 0.02, world[2]], children: [
1115
+ 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: [
968
1116
  geometry,
969
- /* @__PURE__ */ jsx("meshBasicMaterial", { color, transparent: true, opacity: node.opacity ?? 1, side: THREE9.DoubleSide })
970
- ] });
1117
+ /* @__PURE__ */ jsx("meshBasicMaterial", { ref: materialRef, color, transparent: true, opacity: (node.opacity ?? 1) * groupOpacity, side: THREE10.DoubleSide })
1118
+ ] }) });
971
1119
  }
972
- function Text3D({ node, projector }) {
1120
+ function Text3D({ node, projector, groupOpacity = 1 }) {
973
1121
  if (!isValidScenePos(node.position)) return null;
974
1122
  const world = projector.toWorld(node.position);
975
1123
  return /* @__PURE__ */ jsx(Billboard, { position: [world[0], world[1] + 1.2, world[2]], children: /* @__PURE__ */ jsx(
@@ -981,41 +1129,252 @@ function Text3D({ node, projector }) {
981
1129
  anchorY: "middle",
982
1130
  outlineWidth: 0.02,
983
1131
  outlineColor: "#000000",
1132
+ fillOpacity: groupOpacity,
1133
+ outlineOpacity: groupOpacity,
984
1134
  children: node.text
985
1135
  }
986
1136
  ) });
987
1137
  }
988
- function Drawable3D({ node, projector }) {
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)));
1142
+ }
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;
1172
+ }
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;
1186
+ }
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
+ };
1196
+ }
1197
+ function meshGeometry(node, cellSize) {
1198
+ const seg = clampSegments(node.segments);
1199
+ const r = (node.radius ?? 0.4) * cellSize;
1200
+ const w = (node.width ?? 1) * cellSize;
1201
+ const h = (node.height ?? 1) * cellSize;
1202
+ const d = (node.depth ?? node.width ?? 1) * cellSize;
1203
+ switch (node.shape) {
1204
+ case "box":
1205
+ return { element: /* @__PURE__ */ jsx("boxGeometry", { args: [w, h, d] }), lift: h / 2, size: Math.max(w, h, d) };
1206
+ case "sphere":
1207
+ return { element: /* @__PURE__ */ jsx("sphereGeometry", { args: [r, seg, seg] }), lift: r, size: r * 2 };
1208
+ case "capsule":
1209
+ return { element: /* @__PURE__ */ jsx("capsuleGeometry", { args: [r, h, Math.max(2, Math.round(seg / 2)), seg] }), lift: h / 2 + r, size: h + r * 2 };
1210
+ case "cylinder":
1211
+ return {
1212
+ element: /* @__PURE__ */ jsx("cylinderGeometry", { args: [(node.radiusTop ?? node.radius ?? 0.4) * cellSize, (node.radiusBottom ?? node.radius ?? 0.4) * cellSize, h, seg] }),
1213
+ lift: h / 2,
1214
+ size: Math.max(h, r * 2)
1215
+ };
1216
+ case "cone":
1217
+ return { element: /* @__PURE__ */ jsx("coneGeometry", { args: [r, h, seg] }), lift: h / 2, size: Math.max(h, r * 2) };
1218
+ case "torus": {
1219
+ const tube = (node.tube ?? (node.radius ?? 0.4) / 3) * cellSize;
1220
+ return { element: /* @__PURE__ */ jsx("torusGeometry", { args: [r, tube, Math.max(3, Math.round(seg / 2)), seg] }), lift: r + tube, size: (r + tube) * 2 };
1221
+ }
1222
+ case "plane":
1223
+ return { element: /* @__PURE__ */ jsx("planeGeometry", { args: [w, d] }), lift: 0, size: Math.max(w, d) };
1224
+ case "circle":
1225
+ return { element: /* @__PURE__ */ jsx("circleGeometry", { args: [r, seg] }), lift: 0, size: r * 2 };
1226
+ default:
1227
+ return null;
1228
+ }
1229
+ }
1230
+ var SIDE_MAP = {
1231
+ front: THREE10.FrontSide,
1232
+ back: THREE10.BackSide,
1233
+ double: THREE10.DoubleSide
1234
+ };
1235
+ function meshMaterial(mat, opacity, ref) {
1236
+ const m = mat ?? {};
1237
+ const common = {
1238
+ ref,
1239
+ color: m.color ?? "#ffffff",
1240
+ transparent: opacity < 1,
1241
+ opacity,
1242
+ side: SIDE_MAP[m.side ?? "front"]
1243
+ };
1244
+ const emissive = m.emissive ? { emissive: m.emissive, emissiveIntensity: m.emissiveIntensity ?? 1 } : {};
1245
+ switch (m.kind ?? "standard") {
1246
+ case "basic":
1247
+ return /* @__PURE__ */ jsx("meshBasicMaterial", { ...common });
1248
+ case "toon":
1249
+ return /* @__PURE__ */ jsx("meshToonMaterial", { ...common, ...emissive });
1250
+ case "physical":
1251
+ return /* @__PURE__ */ jsx(
1252
+ "meshPhysicalMaterial",
1253
+ {
1254
+ ...common,
1255
+ ...emissive,
1256
+ metalness: m.metalness ?? 0,
1257
+ roughness: m.roughness ?? 0.5,
1258
+ transmission: m.transmission ?? 0,
1259
+ ior: m.ior ?? 1.5,
1260
+ flatShading: m.flatShading ?? false
1261
+ }
1262
+ );
1263
+ case "standard":
1264
+ default:
1265
+ return /* @__PURE__ */ jsx(
1266
+ "meshStandardMaterial",
1267
+ {
1268
+ ...common,
1269
+ ...emissive,
1270
+ metalness: m.metalness ?? 0,
1271
+ roughness: m.roughness ?? 0.5,
1272
+ flatShading: m.flatShading ?? false
1273
+ }
1274
+ );
1275
+ }
1276
+ }
1277
+ function Mesh3D({
1278
+ node,
1279
+ projector,
1280
+ groupOpacity = 1
1281
+ }) {
1282
+ const groupRef = useRef(null);
1283
+ const meshRef = useRef(null);
1284
+ const materialRef = useRef(null);
1285
+ const animated = isAnimatedMesh(node);
1286
+ const validPos = isValidScenePos(node.position);
1287
+ const baseWorld = validPos ? projector.toWorld(node.position) : [0, 0, 0];
1288
+ const geo = useMemo(() => meshGeometry(node, projector.cellSize), [node, projector.cellSize]);
1289
+ useFrame(({ clock }) => {
1290
+ if (!animated || !groupRef.current) return;
1291
+ const state = applyMeshAnimation(node, clock.elapsedTime * 1e3);
1292
+ if (!state) return;
1293
+ const [wx, wy, wz] = baseWorld;
1294
+ groupRef.current.position.set(
1295
+ wx + state.offset[0] * projector.cellSize,
1296
+ wy + state.offset[2] * projector.cellSize,
1297
+ wz + state.offset[1] * projector.cellSize
1298
+ );
1299
+ groupRef.current.scale.setScalar(state.scale);
1300
+ if (meshRef.current) {
1301
+ 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]);
1303
+ }
1304
+ const mat = materialRef.current;
1305
+ if (mat) {
1306
+ const effective = (state.opacity ?? node.opacity ?? 1) * (node.material?.opacity ?? 1) * groupOpacity;
1307
+ mat.opacity = effective;
1308
+ mat.transparent = effective < 1;
1309
+ if (state.color && mat.color) mat.color.set(state.color);
1310
+ if (state.emissive && mat.emissive) mat.emissive.set(state.emissive);
1311
+ if (state.emissiveIntensity !== void 0 && mat.emissiveIntensity !== void 0) {
1312
+ mat.emissiveIntensity = state.emissiveIntensity;
1313
+ }
1314
+ }
1315
+ });
1316
+ if (!validPos || !geo) return null;
1317
+ const lift = (node.pivot ?? "bottom") === "bottom" ? geo.lift : 0;
1318
+ const opacity = (node.opacity ?? 1) * (node.material?.opacity ?? 1) * groupOpacity;
1319
+ const rotation = node.rotation ?? [0, 0, 0];
1320
+ const outlineScale = geo.size > 0 ? 1 + (node.outline?.width ?? 0.05) * projector.cellSize / geo.size : 1;
1321
+ return /* @__PURE__ */ jsxs("group", { ref: groupRef, position: baseWorld, children: [
1322
+ /* @__PURE__ */ jsxs(
1323
+ "mesh",
1324
+ {
1325
+ ref: meshRef,
1326
+ position: [0, lift, 0],
1327
+ rotation,
1328
+ castShadow: node.castShadow ?? true,
1329
+ receiveShadow: node.receiveShadow ?? true,
1330
+ children: [
1331
+ geo.element,
1332
+ meshMaterial(node.material, opacity, materialRef)
1333
+ ]
1334
+ }
1335
+ ),
1336
+ node.outline && /* @__PURE__ */ jsxs("mesh", { position: [0, lift, 0], rotation, scale: outlineScale, children: [
1337
+ geo.element,
1338
+ /* @__PURE__ */ jsx(
1339
+ "meshBasicMaterial",
1340
+ {
1341
+ color: node.outline.color ?? "#101014",
1342
+ side: THREE10.BackSide,
1343
+ transparent: opacity < 1,
1344
+ opacity
1345
+ }
1346
+ )
1347
+ ] })
1348
+ ] });
1349
+ }
1350
+ function Drawable3D({ node, projector, groupOpacity = 1 }) {
989
1351
  switch (node.type) {
990
1352
  case "draw-sprite":
991
- return /* @__PURE__ */ jsx(Sprite3D, { node, projector });
1353
+ return /* @__PURE__ */ jsx(Sprite3D, { node, projector, groupOpacity });
992
1354
  case "draw-shape":
993
- return /* @__PURE__ */ jsx(Shape3D, { node, projector });
1355
+ return /* @__PURE__ */ jsx(Shape3D, { node, projector, groupOpacity });
994
1356
  case "draw-text":
995
- return /* @__PURE__ */ jsx(Text3D, { node, projector });
1357
+ return /* @__PURE__ */ jsx(Text3D, { node, projector, groupOpacity });
1358
+ case "draw-mesh":
1359
+ return /* @__PURE__ */ jsx(Mesh3D, { node, projector, groupOpacity });
996
1360
  case "draw-sprite-layer":
997
- return /* @__PURE__ */ jsx(Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsx(Sprite3D, { node: item, projector }, i)) });
1361
+ return /* @__PURE__ */ jsx(Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsx(Sprite3D, { node: item, projector, groupOpacity }, i)) });
998
1362
  case "draw-shape-layer":
999
- return /* @__PURE__ */ jsx(Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsx(Shape3D, { node: item, projector }, i)) });
1363
+ return /* @__PURE__ */ jsx(Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsx(Shape3D, { node: item, projector, groupOpacity }, i)) });
1000
1364
  case "draw-text-layer":
1001
- 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;
1365
+ return /* @__PURE__ */ jsx(Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsx(Text3D, { node: item, projector, groupOpacity }, i)) });
1366
+ case "draw-group": {
1367
+ if (!isValidScenePos(node.position) || !Array.isArray(node.items)) return null;
1368
+ 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)) });
1374
+ }
1005
1375
  }
1006
1376
  }
1007
1377
 
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
1378
  // lib/drawable/three/game3dTheme.ts
1020
1379
  var GRID_COLORS_3D = {
1021
1380
  cell: "#444444",
@@ -1027,6 +1386,20 @@ function cn(...inputs) {
1027
1386
  }
1028
1387
  var DEFAULT_GRID_CONFIG = {
1029
1388
  cellSize: 1};
1389
+ function RoomEnvironment3D() {
1390
+ const { gl, scene } = useThree(({ gl: gl2, scene: scene2 }) => ({ gl: gl2, scene: scene2 }));
1391
+ useEffect(() => {
1392
+ const pmremGenerator = new THREE10.PMREMGenerator(gl);
1393
+ const envTexture = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
1394
+ scene.environment = envTexture;
1395
+ return () => {
1396
+ scene.environment = null;
1397
+ envTexture.dispose();
1398
+ pmremGenerator.dispose();
1399
+ };
1400
+ }, [gl, scene]);
1401
+ return null;
1402
+ }
1030
1403
  var Canvas3DHost = forwardRef(
1031
1404
  ({
1032
1405
  cameraMode = "isometric",
@@ -1051,6 +1424,9 @@ var Canvas3DHost = forwardRef(
1051
1424
  keyMap,
1052
1425
  keyUpMap,
1053
1426
  pixelsPerUnit,
1427
+ fov,
1428
+ lighting,
1429
+ post,
1054
1430
  children,
1055
1431
  drawables
1056
1432
  }, ref) => {
@@ -1059,6 +1435,7 @@ var Canvas3DHost = forwardRef(
1059
1435
  const [internalError, setInternalError] = useState(null);
1060
1436
  const eventBus = useEventBus();
1061
1437
  const keysRef = useRef(/* @__PURE__ */ new Set());
1438
+ const allDrawables = useMemo(() => drawables ?? [], [drawables]);
1062
1439
  useEffect(() => {
1063
1440
  if (!keyMap && !keyUpMap) return;
1064
1441
  const down = (e) => {
@@ -1092,7 +1469,7 @@ var Canvas3DHost = forwardRef(
1092
1469
  unitAnimationEvent,
1093
1470
  cameraChangeEvent
1094
1471
  });
1095
- const drawnItems = useMemo(() => collectDrawnItems(drawables ?? []), [drawables]);
1472
+ const drawnItems = useMemo(() => collectDrawnItems(allDrawables), [allDrawables]);
1096
1473
  const scenePositions = useMemo(() => drawnItems.map((i) => i.pos), [drawnItems]);
1097
1474
  const hitIndex = useMemo(() => buildHitIndex(drawnItems), [drawnItems]);
1098
1475
  const gridBounds = useMemo(() => {
@@ -1140,7 +1517,7 @@ var Canvas3DHost = forwardRef(
1140
1517
  getCameraPosition: () => {
1141
1518
  if (controlsRef.current) {
1142
1519
  const pos = controlsRef.current.object.position;
1143
- return new THREE9.Vector3(pos.x, pos.y, pos.z);
1520
+ return new THREE10.Vector3(pos.x, pos.y, pos.z);
1144
1521
  }
1145
1522
  return null;
1146
1523
  },
@@ -1179,18 +1556,19 @@ var Canvas3DHost = forwardRef(
1179
1556
  const cx = cameraTarget[0];
1180
1557
  const cz = cameraTarget[2];
1181
1558
  const d = size * 1;
1559
+ const fovDeg = fov ?? 45;
1182
1560
  switch (cameraMode) {
1183
1561
  case "isometric":
1184
- return { position: [cx + d, d * 0.8, cz + d], fov: 45 };
1562
+ return { position: [cx + d, d * 0.8, cz + d], fov: fovDeg };
1185
1563
  case "top-down":
1186
- return { position: [cx, d * 2, cz + d * 0.35], fov: 45 };
1564
+ return { position: [cx, d * 2, cz + d * 0.35], fov: fovDeg };
1187
1565
  case "follow":
1188
- return { position: [cx, d * 0.5, cz + d], fov: 45 };
1566
+ return { position: [cx, d * 0.5, cz + d], fov: fovDeg };
1189
1567
  case "perspective":
1190
1568
  default:
1191
- return { position: [cx + d, d, cz + d], fov: 45 };
1569
+ return { position: [cx + d, d, cz + d], fov: fovDeg };
1192
1570
  }
1193
- }, [cameraMode, gridBounds, cellSize, cameraTarget]);
1571
+ }, [cameraMode, gridBounds, cellSize, cameraTarget, fov]);
1194
1572
  const followWorld = useMemo(() => {
1195
1573
  if (followTarget) return drawableProjector.toWorld(followTarget);
1196
1574
  return cameraTarget;
@@ -1273,9 +1651,19 @@ var Canvas3DHost = forwardRef(
1273
1651
  shadowNormalBias: 0.04,
1274
1652
  shadowCameraSize: 5,
1275
1653
  shadowCameraNear: 0.5,
1276
- shadowCameraFar: 500
1654
+ shadowCameraFar: 500,
1655
+ ambientIntensity: lighting?.ambient?.intensity,
1656
+ ambientColor: lighting?.ambient?.color,
1657
+ directionalIntensity: lighting?.directional?.intensity,
1658
+ directionalColor: lighting?.directional?.color,
1659
+ directionalPosition: lighting?.directional?.position,
1660
+ hemisphereIntensity: lighting?.hemisphere?.intensity,
1661
+ hemisphereColor: lighting?.hemisphere?.color,
1662
+ hemisphereGroundColor: lighting?.hemisphere?.groundColor,
1663
+ points: lighting?.points
1277
1664
  }
1278
1665
  ),
1666
+ lighting?.environment === "room" && /* @__PURE__ */ jsx(RoomEnvironment3D, {}),
1279
1667
  showGrid && /* @__PURE__ */ jsx(
1280
1668
  Grid,
1281
1669
  {
@@ -1298,7 +1686,7 @@ var Canvas3DHost = forwardRef(
1298
1686
  fadeStrength: 1
1299
1687
  }
1300
1688
  ),
1301
- drawables && drawables.length > 0 && /* @__PURE__ */ jsx("group", { children: drawables.map((node, i) => /* @__PURE__ */ jsx(Drawable3D, { node, projector: drawableProjector }, i)) }),
1689
+ allDrawables.length > 0 && /* @__PURE__ */ jsx("group", { children: allDrawables.map((node, i) => /* @__PURE__ */ jsx(Drawable3D, { node, projector: drawableProjector }, i)) }),
1302
1690
  (tileClickEvent || unitClickEvent) && /* @__PURE__ */ jsxs(
1303
1691
  "mesh",
1304
1692
  {
@@ -1323,7 +1711,7 @@ var Canvas3DHost = forwardRef(
1323
1711
  ]
1324
1712
  }
1325
1713
  ),
1326
- children,
1714
+ post && (post.bloom || post.vignette) ? /* @__PURE__ */ jsx(Effects3D, { post }) : null,
1327
1715
  /* @__PURE__ */ jsx(
1328
1716
  OrbitControls,
1329
1717
  {
@@ -1334,7 +1722,7 @@ var Canvas3DHost = forwardRef(
1334
1722
  dampingFactor: 0.05,
1335
1723
  enableZoom: true,
1336
1724
  enablePan: true,
1337
- touches: { ONE: THREE9.TOUCH.ROTATE, TWO: THREE9.TOUCH.DOLLY_PAN },
1725
+ touches: { ONE: THREE10.TOUCH.ROTATE, TWO: THREE10.TOUCH.DOLLY_PAN },
1338
1726
  minDistance: 2,
1339
1727
  maxDistance: 100,
1340
1728
  maxPolarAngle: Math.PI / 2 - 0.1
@@ -1357,15 +1745,15 @@ function Scene3D({ background = "#1a1a2e", fog, children }) {
1357
1745
  if (initializedRef.current) return;
1358
1746
  initializedRef.current = true;
1359
1747
  if (background.startsWith("#") || background.startsWith("rgb")) {
1360
- scene.background = new THREE9.Color(background);
1748
+ scene.background = new THREE10.Color(background);
1361
1749
  } else {
1362
- const loader = new THREE9.TextureLoader();
1750
+ const loader = new THREE10.TextureLoader();
1363
1751
  loader.load(background, (texture) => {
1364
1752
  scene.background = texture;
1365
1753
  });
1366
1754
  }
1367
1755
  if (fog) {
1368
- scene.fog = new THREE9.Fog(fog.color, fog.near, fog.far);
1756
+ scene.fog = new THREE10.Fog(fog.color, fog.near, fog.far);
1369
1757
  }
1370
1758
  return () => {
1371
1759
  scene.background = null;
@@ -1388,14 +1776,14 @@ var Camera3D = forwardRef(
1388
1776
  }, ref) => {
1389
1777
  const { camera, set, viewport } = useThree();
1390
1778
  const controlsRef = useRef(null);
1391
- const initialPosition = useRef(new THREE9.Vector3(...position));
1392
- const initialTarget = useRef(new THREE9.Vector3(...target));
1779
+ const initialPosition = useRef(new THREE10.Vector3(...position));
1780
+ const initialTarget = useRef(new THREE10.Vector3(...target));
1393
1781
  useEffect(() => {
1394
1782
  let newCamera;
1395
1783
  if (mode === "isometric") {
1396
1784
  const aspect = viewport.aspect;
1397
1785
  const size = 10 / zoom;
1398
- newCamera = new THREE9.OrthographicCamera(
1786
+ newCamera = new THREE10.OrthographicCamera(
1399
1787
  -size * aspect,
1400
1788
  size * aspect,
1401
1789
  size,
@@ -1404,7 +1792,7 @@ var Camera3D = forwardRef(
1404
1792
  1e3
1405
1793
  );
1406
1794
  } else {
1407
- newCamera = new THREE9.PerspectiveCamera(fov, viewport.aspect, 0.1, 1e3);
1795
+ newCamera = new THREE10.PerspectiveCamera(fov, viewport.aspect, 0.1, 1e3);
1408
1796
  }
1409
1797
  newCamera.position.copy(initialPosition.current);
1410
1798
  newCamera.lookAt(initialTarget.current.x, initialTarget.current.y, initialTarget.current.z);
@@ -1445,8 +1833,8 @@ var Camera3D = forwardRef(
1445
1833
  }
1446
1834
  },
1447
1835
  getViewBounds: () => {
1448
- const min = new THREE9.Vector3(-10, -10, -10);
1449
- const max = new THREE9.Vector3(10, 10, 10);
1836
+ const min = new THREE10.Vector3(-10, -10, -10);
1837
+ const max = new THREE10.Vector3(10, 10, 10);
1450
1838
  return { min, max };
1451
1839
  }
1452
1840
  }));
@@ -1488,7 +1876,7 @@ var AssetLoader = class {
1488
1876
  __publicField(this, "textureCache");
1489
1877
  __publicField(this, "loadingPromises");
1490
1878
  this.objLoader = new OBJLoader();
1491
- this.textureLoader = new THREE9.TextureLoader();
1879
+ this.textureLoader = new THREE10.TextureLoader();
1492
1880
  this.modelCache = /* @__PURE__ */ new Map();
1493
1881
  this.textureCache = /* @__PURE__ */ new Map();
1494
1882
  this.loadingPromises = /* @__PURE__ */ new Map();
@@ -1562,7 +1950,7 @@ var AssetLoader = class {
1562
1950
  return this.loadingPromises.get(`texture:${url}`);
1563
1951
  }
1564
1952
  const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
1565
- texture.colorSpace = THREE9.SRGBColorSpace;
1953
+ texture.colorSpace = THREE10.SRGBColorSpace;
1566
1954
  this.textureCache.set(url, texture);
1567
1955
  this.loadingPromises.delete(`texture:${url}`);
1568
1956
  return texture;
@@ -1636,7 +2024,7 @@ var AssetLoader = class {
1636
2024
  });
1637
2025
  this.modelCache.forEach((model) => {
1638
2026
  model.scene.traverse((child) => {
1639
- if (child instanceof THREE9.Mesh) {
2027
+ if (child instanceof THREE10.Mesh) {
1640
2028
  child.geometry.dispose();
1641
2029
  if (Array.isArray(child.material)) {
1642
2030
  child.material.forEach((m) => m.dispose());
@@ -1673,7 +2061,7 @@ var DEFAULT_OPTIONS = {
1673
2061
  gridSize: 20,
1674
2062
  assetLoader: new AssetLoader()
1675
2063
  };
1676
- function useThree4(options = {}) {
2064
+ function useThree5(options = {}) {
1677
2065
  const opts = { ...DEFAULT_OPTIONS, ...options };
1678
2066
  const containerRef = useRef(null);
1679
2067
  const canvasRef = useRef(null);
@@ -1686,21 +2074,21 @@ function useThree4(options = {}) {
1686
2074
  const [isReady, setIsReady] = useState(false);
1687
2075
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
1688
2076
  const initialCameraPosition = useMemo(
1689
- () => new THREE9.Vector3(...opts.cameraPosition),
2077
+ () => new THREE10.Vector3(...opts.cameraPosition),
1690
2078
  []
1691
2079
  );
1692
2080
  useEffect(() => {
1693
2081
  if (!containerRef.current) return;
1694
2082
  const container = containerRef.current;
1695
2083
  const { clientWidth, clientHeight } = container;
1696
- const scene = new THREE9.Scene();
1697
- scene.background = new THREE9.Color(opts.backgroundColor);
2084
+ const scene = new THREE10.Scene();
2085
+ scene.background = new THREE10.Color(opts.backgroundColor);
1698
2086
  sceneRef.current = scene;
1699
2087
  let camera;
1700
2088
  const aspect = clientWidth / clientHeight;
1701
2089
  if (opts.cameraMode === "isometric") {
1702
2090
  const size = 10;
1703
- camera = new THREE9.OrthographicCamera(
2091
+ camera = new THREE10.OrthographicCamera(
1704
2092
  -size * aspect,
1705
2093
  size * aspect,
1706
2094
  size,
@@ -1709,11 +2097,11 @@ function useThree4(options = {}) {
1709
2097
  1e3
1710
2098
  );
1711
2099
  } else {
1712
- camera = new THREE9.PerspectiveCamera(45, aspect, 0.1, 1e3);
2100
+ camera = new THREE10.PerspectiveCamera(45, aspect, 0.1, 1e3);
1713
2101
  }
1714
2102
  camera.position.copy(initialCameraPosition);
1715
2103
  cameraRef.current = camera;
1716
- const renderer = new THREE9.WebGLRenderer({
2104
+ const renderer = new THREE10.WebGLRenderer({
1717
2105
  antialias: true,
1718
2106
  alpha: true,
1719
2107
  canvas: canvasRef.current || void 0
@@ -1721,7 +2109,7 @@ function useThree4(options = {}) {
1721
2109
  renderer.setSize(clientWidth, clientHeight);
1722
2110
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
1723
2111
  renderer.shadowMap.enabled = opts.shadows;
1724
- renderer.shadowMap.type = THREE9.PCFSoftShadowMap;
2112
+ renderer.shadowMap.type = THREE10.PCFSoftShadowMap;
1725
2113
  rendererRef.current = renderer;
1726
2114
  const controls = new OrbitControls$1(camera, renderer.domElement);
1727
2115
  controls.enableDamping = true;
@@ -1730,16 +2118,16 @@ function useThree4(options = {}) {
1730
2118
  controls.maxDistance = 100;
1731
2119
  controls.maxPolarAngle = Math.PI / 2 - 0.1;
1732
2120
  controlsRef.current = controls;
1733
- const ambientLight = new THREE9.AmbientLight(16777215, 0.6);
2121
+ const ambientLight = new THREE10.AmbientLight(16777215, 0.6);
1734
2122
  scene.add(ambientLight);
1735
- const directionalLight = new THREE9.DirectionalLight(16777215, 0.8);
2123
+ const directionalLight = new THREE10.DirectionalLight(16777215, 0.8);
1736
2124
  directionalLight.position.set(10, 20, 10);
1737
2125
  directionalLight.castShadow = opts.shadows;
1738
2126
  directionalLight.shadow.mapSize.width = 2048;
1739
2127
  directionalLight.shadow.mapSize.height = 2048;
1740
2128
  scene.add(directionalLight);
1741
2129
  if (opts.showGrid) {
1742
- const gridHelper = new THREE9.GridHelper(
2130
+ const gridHelper = new THREE10.GridHelper(
1743
2131
  opts.gridSize,
1744
2132
  opts.gridSize,
1745
2133
  4473924,
@@ -1757,10 +2145,10 @@ function useThree4(options = {}) {
1757
2145
  const handleResize = () => {
1758
2146
  const { clientWidth: width, clientHeight: height } = container;
1759
2147
  setDimensions({ width, height });
1760
- if (camera instanceof THREE9.PerspectiveCamera) {
2148
+ if (camera instanceof THREE10.PerspectiveCamera) {
1761
2149
  camera.aspect = width / height;
1762
2150
  camera.updateProjectionMatrix();
1763
- } else if (camera instanceof THREE9.OrthographicCamera) {
2151
+ } else if (camera instanceof THREE10.OrthographicCamera) {
1764
2152
  const aspect2 = width / height;
1765
2153
  const size = 10;
1766
2154
  camera.left = -size * aspect2;
@@ -1791,7 +2179,7 @@ function useThree4(options = {}) {
1791
2179
  let newCamera;
1792
2180
  if (opts.cameraMode === "isometric") {
1793
2181
  const size = 10;
1794
- newCamera = new THREE9.OrthographicCamera(
2182
+ newCamera = new THREE10.OrthographicCamera(
1795
2183
  -size * aspect,
1796
2184
  size * aspect,
1797
2185
  size,
@@ -1800,7 +2188,7 @@ function useThree4(options = {}) {
1800
2188
  1e3
1801
2189
  );
1802
2190
  } else {
1803
- newCamera = new THREE9.PerspectiveCamera(45, aspect, 0.1, 1e3);
2191
+ newCamera = new THREE10.PerspectiveCamera(45, aspect, 0.1, 1e3);
1804
2192
  }
1805
2193
  newCamera.position.copy(currentPos);
1806
2194
  cameraRef.current = newCamera;
@@ -2130,8 +2518,8 @@ function useSceneGraph() {
2130
2518
  }
2131
2519
  function useRaycaster(options) {
2132
2520
  const { camera, canvas, cellSize = 1, offsetX = 0, offsetZ = 0 } = options;
2133
- const raycaster = useRef(new THREE9.Raycaster());
2134
- const mouse = useRef(new THREE9.Vector2());
2521
+ const raycaster = useRef(new THREE10.Raycaster());
2522
+ const mouse = useRef(new THREE10.Vector2());
2135
2523
  const clientToNDC = useCallback(
2136
2524
  (clientX, clientY) => {
2137
2525
  if (!canvas) {
@@ -2201,8 +2589,8 @@ function useRaycaster(options) {
2201
2589
  const ndc = clientToNDC(clientX, clientY);
2202
2590
  mouse.current.set(ndc.x, ndc.y);
2203
2591
  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();
2592
+ const plane = new THREE10.Plane(new THREE10.Vector3(0, 1, 0), 0);
2593
+ const target = new THREE10.Vector3();
2206
2594
  const intersection = raycaster.current.ray.intersectPlane(plane, target);
2207
2595
  if (intersection) {
2208
2596
  const gridX = Math.round((target.x - offsetX) / cellSize);
@@ -2240,7 +2628,7 @@ function useRaycaster(options) {
2240
2628
  return {
2241
2629
  gridX: gridCoords.x,
2242
2630
  gridZ: gridCoords.z,
2243
- worldPosition: new THREE9.Vector3(
2631
+ worldPosition: new THREE10.Vector3(
2244
2632
  gridCoords.x * cellSize + offsetX,
2245
2633
  0,
2246
2634
  gridCoords.z * cellSize + offsetZ
@@ -2270,7 +2658,7 @@ var DEFAULT_CONFIG = {
2270
2658
  };
2271
2659
  function gridToWorld(gridX, gridZ, config = DEFAULT_CONFIG) {
2272
2660
  const opts = { ...DEFAULT_CONFIG, ...config };
2273
- return new THREE9.Vector3(
2661
+ return new THREE10.Vector3(
2274
2662
  gridX * opts.cellSize + opts.offsetX,
2275
2663
  opts.elevation,
2276
2664
  gridZ * opts.cellSize + opts.offsetZ
@@ -2284,17 +2672,17 @@ function worldToGrid(worldX, worldZ, config = DEFAULT_CONFIG) {
2284
2672
  };
2285
2673
  }
2286
2674
  function raycastToPlane(camera, mouseX, mouseY, planeY = 0) {
2287
- const raycaster = new THREE9.Raycaster();
2288
- const mouse = new THREE9.Vector2(mouseX, mouseY);
2675
+ const raycaster = new THREE10.Raycaster();
2676
+ const mouse = new THREE10.Vector2(mouseX, mouseY);
2289
2677
  raycaster.setFromCamera(mouse, camera);
2290
- const plane = new THREE9.Plane(new THREE9.Vector3(0, 1, 0), -planeY);
2291
- const target = new THREE9.Vector3();
2678
+ const plane = new THREE10.Plane(new THREE10.Vector3(0, 1, 0), -planeY);
2679
+ const target = new THREE10.Vector3();
2292
2680
  const intersection = raycaster.ray.intersectPlane(plane, target);
2293
2681
  return intersection ? target : null;
2294
2682
  }
2295
2683
  function raycastToObjects(camera, mouseX, mouseY, objects) {
2296
- const raycaster = new THREE9.Raycaster();
2297
- const mouse = new THREE9.Vector2(mouseX, mouseY);
2684
+ const raycaster = new THREE10.Raycaster();
2685
+ const mouse = new THREE10.Vector2(mouseX, mouseY);
2298
2686
  raycaster.setFromCamera(mouse, camera);
2299
2687
  const intersects = raycaster.intersectObjects(objects, true);
2300
2688
  return intersects.length > 0 ? intersects[0] : null;
@@ -2346,14 +2734,14 @@ function getCellsInRadius(centerX, centerZ, radius) {
2346
2734
  return cells;
2347
2735
  }
2348
2736
  function createGridHighlight(color = 16776960, opacity = 0.3) {
2349
- const geometry = new THREE9.PlaneGeometry(0.95, 0.95);
2350
- const material = new THREE9.MeshBasicMaterial({
2737
+ const geometry = new THREE10.PlaneGeometry(0.95, 0.95);
2738
+ const material = new THREE10.MeshBasicMaterial({
2351
2739
  color,
2352
2740
  transparent: true,
2353
2741
  opacity,
2354
- side: THREE9.DoubleSide
2742
+ side: THREE10.DoubleSide
2355
2743
  });
2356
- const mesh = new THREE9.Mesh(geometry, material);
2744
+ const mesh = new THREE10.Mesh(geometry, material);
2357
2745
  mesh.rotation.x = -Math.PI / 2;
2358
2746
  mesh.position.y = 0.01;
2359
2747
  return mesh;
@@ -2366,31 +2754,31 @@ function normalizeMouseCoordinates(clientX, clientY, element) {
2366
2754
  };
2367
2755
  }
2368
2756
  function isInFrustum(position, camera, padding = 0) {
2369
- const frustum = new THREE9.Frustum();
2370
- const projScreenMatrix = new THREE9.Matrix4();
2757
+ const frustum = new THREE10.Frustum();
2758
+ const projScreenMatrix = new THREE10.Matrix4();
2371
2759
  projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
2372
2760
  frustum.setFromProjectionMatrix(projScreenMatrix);
2373
- const sphere = new THREE9.Sphere(position, padding);
2761
+ const sphere = new THREE10.Sphere(position, padding);
2374
2762
  return frustum.intersectsSphere(sphere);
2375
2763
  }
2376
2764
  function filterByFrustum(positions, camera, padding = 0) {
2377
- const frustum = new THREE9.Frustum();
2378
- const projScreenMatrix = new THREE9.Matrix4();
2765
+ const frustum = new THREE10.Frustum();
2766
+ const projScreenMatrix = new THREE10.Matrix4();
2379
2767
  projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
2380
2768
  frustum.setFromProjectionMatrix(projScreenMatrix);
2381
2769
  return positions.filter((position) => {
2382
- const sphere = new THREE9.Sphere(position, padding);
2770
+ const sphere = new THREE10.Sphere(position, padding);
2383
2771
  return frustum.intersectsSphere(sphere);
2384
2772
  });
2385
2773
  }
2386
2774
  function getVisibleIndices(positions, camera, padding = 0) {
2387
- const frustum = new THREE9.Frustum();
2388
- const projScreenMatrix = new THREE9.Matrix4();
2775
+ const frustum = new THREE10.Frustum();
2776
+ const projScreenMatrix = new THREE10.Matrix4();
2389
2777
  const visible = /* @__PURE__ */ new Set();
2390
2778
  projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
2391
2779
  frustum.setFromProjectionMatrix(projScreenMatrix);
2392
2780
  positions.forEach((position, index) => {
2393
- const sphere = new THREE9.Sphere(position, padding);
2781
+ const sphere = new THREE10.Sphere(position, padding);
2394
2782
  if (frustum.intersectsSphere(sphere)) {
2395
2783
  visible.add(index);
2396
2784
  }
@@ -2414,7 +2802,7 @@ function updateInstanceLOD(instancedMesh, positions, camera, lodDistances) {
2414
2802
  return lodIndices;
2415
2803
  }
2416
2804
  function cullInstancedMesh(instancedMesh, positions, visibleIndices) {
2417
- const dummy = new THREE9.Object3D();
2805
+ const dummy = new THREE10.Object3D();
2418
2806
  let visibleCount = 0;
2419
2807
  positions.forEach((position, index) => {
2420
2808
  if (visibleIndices.has(index)) {
@@ -5197,12 +5585,12 @@ function useAvl3DConfig() {
5197
5585
  }
5198
5586
  function CameraController({ targetPosition, targetLookAt, animated }) {
5199
5587
  const { camera } = useThree();
5200
- const targetPosVec = useRef(new THREE9.Vector3(...targetPosition));
5201
- const targetLookVec = useRef(new THREE9.Vector3(...targetLookAt));
5588
+ const targetPosVec = useRef(new THREE10.Vector3(...targetPosition));
5589
+ const targetLookVec = useRef(new THREE10.Vector3(...targetLookAt));
5202
5590
  const isAnimating = useRef(false);
5203
5591
  useEffect(() => {
5204
- const newTarget = new THREE9.Vector3(...targetPosition);
5205
- const newLookAt = new THREE9.Vector3(...targetLookAt);
5592
+ const newTarget = new THREE10.Vector3(...targetPosition);
5593
+ const newLookAt = new THREE10.Vector3(...targetLookAt);
5206
5594
  if (!newTarget.equals(targetPosVec.current) || !newLookAt.equals(targetLookVec.current)) {
5207
5595
  targetPosVec.current.copy(newTarget);
5208
5596
  targetLookVec.current.copy(newLookAt);
@@ -5217,9 +5605,9 @@ function CameraController({ targetPosition, targetLookAt, animated }) {
5217
5605
  useFrame((_, delta) => {
5218
5606
  if (!isAnimating.current) return;
5219
5607
  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);
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);
5223
5611
  camera.lookAt(targetLookVec.current);
5224
5612
  const dist = camera.position.distanceTo(targetPosVec.current);
5225
5613
  if (dist < 0.05) {
@@ -5234,7 +5622,7 @@ function SceneFade({ animating, children }) {
5234
5622
  useFrame((_, delta) => {
5235
5623
  if (!groupRef.current) return;
5236
5624
  const target = animating ? 0 : 1;
5237
- opacityRef.current = THREE9.MathUtils.damp(opacityRef.current, target, 5, delta);
5625
+ opacityRef.current = THREE10.MathUtils.damp(opacityRef.current, target, 5, delta);
5238
5626
  groupRef.current.visible = opacityRef.current > 0.05;
5239
5627
  const s = 0.9 + opacityRef.current * 0.1;
5240
5628
  groupRef.current.scale.setScalar(s);
@@ -5487,4 +5875,4 @@ var Avl3DViewer = ({
5487
5875
  };
5488
5876
  Avl3DViewer.displayName = "Avl3DViewer";
5489
5877
 
5490
- export { AVL_3D_COLORS, AssetLoader, Avl3DApplicationScene, Avl3DContext, Avl3DEffects, Avl3DOrbitalScene, Avl3DTraitScene, Avl3DTransitionScene, Avl3DViewer, CAMERA_POSITIONS, Camera3D, Canvas3DErrorBoundary, Canvas3DHost, Canvas3DLoadingState, Canvas3DHost as GameCanvas3D, Lighting3D, ModelLoader, Scene3D, SpatialHashGrid, arcCurve3D, assetLoader, calculateLODLevel, createGridHighlight, cullInstancedMesh, fibonacciSpherePositions, filterByFrustum, getCellsInRadius, getNeighbors, getVisibleIndices, goldenSpiralPositions, gridDistance, gridManhattanDistance, gridToWorld, isInBounds, isInFrustum, normalizeMouseCoordinates, orbitRingPositions, raycastToObjects, raycastToPlane, selfLoopCurve3D, treeLayout3D, updateInstanceLOD, useAssetLoader, useAvl3DConfig, useGameCanvas3DEvents, useRaycaster, useSceneGraph, useThree4 as useThree, worldToGrid };
5878
+ export { AVL_3D_COLORS, AssetLoader, Avl3DApplicationScene, Avl3DContext, Avl3DEffects, Avl3DOrbitalScene, Avl3DTraitScene, Avl3DTransitionScene, Avl3DViewer, CAMERA_POSITIONS, Camera3D, Canvas3DErrorBoundary, Canvas3DHost, Canvas3DLoadingState, Canvas3DHost as GameCanvas3D, Lighting3D, ModelLoader, Scene3D, SpatialHashGrid, arcCurve3D, assetLoader, calculateLODLevel, createGridHighlight, cullInstancedMesh, fibonacciSpherePositions, filterByFrustum, getCellsInRadius, getNeighbors, getVisibleIndices, goldenSpiralPositions, gridDistance, gridManhattanDistance, gridToWorld, isInBounds, isInFrustum, normalizeMouseCoordinates, orbitRingPositions, raycastToObjects, raycastToPlane, selfLoopCurve3D, treeLayout3D, updateInstanceLOD, useAssetLoader, useAvl3DConfig, useGameCanvas3DEvents, useRaycaster, useSceneGraph, useThree5 as useThree, worldToGrid };