@almadar/ui 2.16.0 → 2.17.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,5 +1,5 @@
1
1
  import * as React87 from 'react';
2
- import React87__default, { createContext, useCallback, useState, useRef, useLayoutEffect, useEffect, lazy, forwardRef, useImperativeHandle, useMemo, useContext, Component, useId, Suspense, useSyncExternalStore } from 'react';
2
+ import React87__default, { createContext, useCallback, useState, useRef, useLayoutEffect, useEffect, lazy, useContext, useMemo, useId, Suspense, useSyncExternalStore } from 'react';
3
3
  import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import * as LucideIcons from 'lucide-react';
@@ -19,12 +19,6 @@ import 'leaflet/dist/leaflet.css';
19
19
  import { useUISlots } from '@almadar/ui/context';
20
20
  import { getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
21
21
  import { Link, Outlet, useLocation } from 'react-router-dom';
22
- import { useThree, useFrame, Canvas } from '@react-three/fiber';
23
- import { OrbitControls, Grid as Grid$1 } from '@react-three/drei';
24
- import * as THREE from 'three';
25
- import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
26
- import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
27
- import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
28
22
  import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query';
29
23
 
30
24
  var __defProp = Object.defineProperty;
@@ -1026,7 +1020,7 @@ var Box = React87__default.forwardRef(
1026
1020
  position,
1027
1021
  className,
1028
1022
  children,
1029
- as: Component2 = "div",
1023
+ as: Component = "div",
1030
1024
  action,
1031
1025
  actionPayload,
1032
1026
  hoverEvent,
@@ -1056,7 +1050,7 @@ var Box = React87__default.forwardRef(
1056
1050
  onMouseLeave?.(e);
1057
1051
  }, [hoverEvent, eventBus, onMouseLeave]);
1058
1052
  const isClickable = action || onClick;
1059
- const Comp = Component2;
1053
+ const Comp = Component;
1060
1054
  return /* @__PURE__ */ jsx(
1061
1055
  Comp,
1062
1056
  {
@@ -1111,10 +1105,10 @@ var Center = ({
1111
1105
  className,
1112
1106
  style,
1113
1107
  children,
1114
- as: Component2 = "div"
1108
+ as: Component = "div"
1115
1109
  }) => {
1116
1110
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
1117
- const Comp = Component2;
1111
+ const Comp = Component;
1118
1112
  return /* @__PURE__ */ jsx(
1119
1113
  Comp,
1120
1114
  {
@@ -1599,7 +1593,7 @@ var Stack = ({
1599
1593
  className,
1600
1594
  style,
1601
1595
  children,
1602
- as: Component2 = "div",
1596
+ as: Component = "div",
1603
1597
  onClick,
1604
1598
  onKeyDown,
1605
1599
  role,
@@ -1617,7 +1611,7 @@ var Stack = ({
1617
1611
  };
1618
1612
  const isHorizontal = direction === "horizontal";
1619
1613
  const directionClass = responsive && isHorizontal ? reverse ? "flex-col-reverse md:flex-row-reverse" : "flex-col md:flex-row" : isHorizontal ? reverse ? "flex-row-reverse" : "flex-row" : reverse ? "flex-col-reverse" : "flex-col";
1620
- const Comp = Component2;
1614
+ const Comp = Component;
1621
1615
  return /* @__PURE__ */ jsx(
1622
1616
  Comp,
1623
1617
  {
@@ -1788,8 +1782,8 @@ var Typography = ({
1788
1782
  children
1789
1783
  }) => {
1790
1784
  const variant = variantProp ?? (level ? `h${level}` : "body1");
1791
- const Component2 = as || defaultElements[variant];
1792
- const Comp = Component2;
1785
+ const Component = as || defaultElements[variant];
1786
+ const Comp = Component;
1793
1787
  return /* @__PURE__ */ jsx(
1794
1788
  Comp,
1795
1789
  {
@@ -5628,10 +5622,10 @@ var Container = ({
5628
5622
  center = true,
5629
5623
  className,
5630
5624
  children,
5631
- as: Component2 = "div"
5625
+ as: Component = "div"
5632
5626
  }) => {
5633
5627
  const resolvedSize = maxWidth ?? size ?? "lg";
5634
- const Comp = Component2;
5628
+ const Comp = Component;
5635
5629
  return /* @__PURE__ */ jsx(
5636
5630
  Comp,
5637
5631
  {
@@ -5694,7 +5688,7 @@ var Flex = ({
5694
5688
  basis,
5695
5689
  className,
5696
5690
  children,
5697
- as: Component2 = "div"
5691
+ as: Component = "div"
5698
5692
  }) => {
5699
5693
  const flexStyle = {};
5700
5694
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -5706,7 +5700,7 @@ var Flex = ({
5706
5700
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
5707
5701
  }
5708
5702
  }
5709
- const Comp = Component2;
5703
+ const Comp = Component;
5710
5704
  return /* @__PURE__ */ jsx(
5711
5705
  Comp,
5712
5706
  {
@@ -5963,10 +5957,10 @@ var Grid = ({
5963
5957
  className,
5964
5958
  style,
5965
5959
  children,
5966
- as: Component2 = "div"
5960
+ as: Component = "div"
5967
5961
  }) => {
5968
5962
  const mergedStyle = rows ? { gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))`, ...style } : style;
5969
- const Comp = Component2;
5963
+ const Comp = Component;
5970
5964
  return /* @__PURE__ */ jsx(
5971
5965
  Comp,
5972
5966
  {
@@ -17562,10 +17556,10 @@ var Section = ({
17562
17556
  children,
17563
17557
  headerClassName,
17564
17558
  contentClassName,
17565
- as: Component2 = "section"
17559
+ as: Component = "section"
17566
17560
  }) => {
17567
17561
  const hasHeader = title || description || action;
17568
- const Comp = Component2;
17562
+ const Comp = Component;
17569
17563
  return /* @__PURE__ */ jsxs(
17570
17564
  Comp,
17571
17565
  {
@@ -28338,216 +28332,6 @@ function DividerPattern({
28338
28332
  );
28339
28333
  }
28340
28334
  DividerPattern.displayName = "DividerPattern";
28341
- var Camera3D = forwardRef(
28342
- ({
28343
- mode = "isometric",
28344
- position = [10, 10, 10],
28345
- target = [0, 0, 0],
28346
- zoom = 1,
28347
- fov = 45,
28348
- enableOrbit = true,
28349
- minDistance = 2,
28350
- maxDistance = 100,
28351
- onChange
28352
- }, ref) => {
28353
- const { camera, set, viewport } = useThree();
28354
- const controlsRef = useRef(null);
28355
- const initialPosition = useRef(new THREE.Vector3(...position));
28356
- const initialTarget = useRef(new THREE.Vector3(...target));
28357
- useEffect(() => {
28358
- let newCamera;
28359
- if (mode === "isometric") {
28360
- const aspect = viewport.aspect;
28361
- const size = 10 / zoom;
28362
- newCamera = new THREE.OrthographicCamera(
28363
- -size * aspect,
28364
- size * aspect,
28365
- size,
28366
- -size,
28367
- 0.1,
28368
- 1e3
28369
- );
28370
- } else {
28371
- newCamera = new THREE.PerspectiveCamera(fov, viewport.aspect, 0.1, 1e3);
28372
- }
28373
- newCamera.position.copy(initialPosition.current);
28374
- newCamera.lookAt(initialTarget.current);
28375
- set({ camera: newCamera });
28376
- if (mode === "top-down") {
28377
- newCamera.position.set(0, 20 / zoom, 0);
28378
- newCamera.lookAt(0, 0, 0);
28379
- }
28380
- return () => {
28381
- };
28382
- }, [mode, fov, zoom, viewport.aspect, set]);
28383
- useFrame(() => {
28384
- if (onChange) {
28385
- onChange(camera);
28386
- }
28387
- });
28388
- useImperativeHandle(ref, () => ({
28389
- getCamera: () => camera,
28390
- setPosition: (x, y, z) => {
28391
- camera.position.set(x, y, z);
28392
- if (controlsRef.current) {
28393
- controlsRef.current.update();
28394
- }
28395
- },
28396
- lookAt: (x, y, z) => {
28397
- camera.lookAt(x, y, z);
28398
- if (controlsRef.current) {
28399
- controlsRef.current.target.set(x, y, z);
28400
- controlsRef.current.update();
28401
- }
28402
- },
28403
- reset: () => {
28404
- camera.position.copy(initialPosition.current);
28405
- camera.lookAt(initialTarget.current);
28406
- if (controlsRef.current) {
28407
- controlsRef.current.target.copy(initialTarget.current);
28408
- controlsRef.current.update();
28409
- }
28410
- },
28411
- getViewBounds: () => {
28412
- const min = new THREE.Vector3(-10, -10, -10);
28413
- const max = new THREE.Vector3(10, 10, 10);
28414
- return { min, max };
28415
- }
28416
- }));
28417
- const maxPolarAngle = mode === "top-down" ? 0.1 : Math.PI / 2 - 0.1;
28418
- return /* @__PURE__ */ jsx(
28419
- OrbitControls,
28420
- {
28421
- ref: controlsRef,
28422
- camera,
28423
- enabled: enableOrbit,
28424
- target: initialTarget.current,
28425
- minDistance,
28426
- maxDistance,
28427
- maxPolarAngle,
28428
- enableDamping: true,
28429
- dampingFactor: 0.05
28430
- }
28431
- );
28432
- }
28433
- );
28434
- Camera3D.displayName = "Camera3D";
28435
- var Canvas3DErrorBoundary = class extends Component {
28436
- constructor(props) {
28437
- super(props);
28438
- __publicField(this, "handleReset", () => {
28439
- this.setState({
28440
- hasError: false,
28441
- error: null,
28442
- errorInfo: null
28443
- });
28444
- this.props.onReset?.();
28445
- });
28446
- this.state = {
28447
- hasError: false,
28448
- error: null,
28449
- errorInfo: null
28450
- };
28451
- }
28452
- static getDerivedStateFromError(error) {
28453
- return {
28454
- hasError: true,
28455
- error,
28456
- errorInfo: null
28457
- };
28458
- }
28459
- componentDidCatch(error, errorInfo) {
28460
- this.setState({ errorInfo });
28461
- this.props.onError?.(error, errorInfo);
28462
- console.error("[Canvas3DErrorBoundary] Error caught:", error);
28463
- console.error("[Canvas3DErrorBoundary] Component stack:", errorInfo.componentStack);
28464
- }
28465
- render() {
28466
- if (this.state.hasError) {
28467
- if (this.props.fallback) {
28468
- return this.props.fallback;
28469
- }
28470
- return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
28471
- /* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
28472
- /* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
28473
- /* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
28474
- this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
28475
- /* @__PURE__ */ jsx("summary", { children: "Error Details" }),
28476
- /* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
28477
- this.state.error.message,
28478
- "\n",
28479
- this.state.error.stack
28480
- ] }),
28481
- this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
28482
- ] }),
28483
- /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
28484
- /* @__PURE__ */ jsx(
28485
- "button",
28486
- {
28487
- className: "error__button error__button--primary",
28488
- onClick: this.handleReset,
28489
- children: "Try Again"
28490
- }
28491
- ),
28492
- /* @__PURE__ */ jsx(
28493
- "button",
28494
- {
28495
- className: "error__button error__button--secondary",
28496
- onClick: () => window.location.reload(),
28497
- children: "Reload Page"
28498
- }
28499
- )
28500
- ] })
28501
- ] }) });
28502
- }
28503
- return this.props.children;
28504
- }
28505
- };
28506
- function Canvas3DLoadingState({
28507
- progress = 0,
28508
- loaded = 0,
28509
- total = 0,
28510
- message = "Loading 3D Scene...",
28511
- details,
28512
- showSpinner = true,
28513
- className
28514
- }) {
28515
- const clampedProgress = Math.max(0, Math.min(100, progress));
28516
- const hasProgress = total > 0;
28517
- return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
28518
- /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
28519
- showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
28520
- /* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
28521
- /* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
28522
- ] }),
28523
- /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
28524
- details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
28525
- hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
28526
- /* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
28527
- "div",
28528
- {
28529
- className: "progress__fill",
28530
- style: { width: `${clampedProgress}%` }
28531
- }
28532
- ) }),
28533
- /* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
28534
- /* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
28535
- clampedProgress,
28536
- "%"
28537
- ] }),
28538
- /* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
28539
- "(",
28540
- loaded,
28541
- "/",
28542
- total,
28543
- ")"
28544
- ] })
28545
- ] })
28546
- ] })
28547
- ] }),
28548
- /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
28549
- ] });
28550
- }
28551
28335
  function CastleTemplate({
28552
28336
  entity,
28553
28337
  scale = 0.45,
@@ -29908,1771 +29692,113 @@ var DocumentViewer = ({
29908
29692
  );
29909
29693
  }
29910
29694
  return /* @__PURE__ */ jsx(
29911
- Box,
29912
- {
29913
- className: "w-full overflow-auto p-4 font-mono text-sm",
29914
- style: { height, fontSize: `${zoom}%` },
29915
- children: /* @__PURE__ */ jsx(Typography, { variant: "body", className: "whitespace-pre-wrap break-words", children: activeContent })
29916
- }
29917
- );
29918
- };
29919
- return /* @__PURE__ */ jsx(Card, { className: cn("overflow-hidden", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
29920
- tabItems && tabItems.length > 1 && /* @__PURE__ */ jsx(Box, { className: "border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx(
29921
- Tabs,
29922
- {
29923
- tabs: tabItems,
29924
- activeTab: `doc-${activeDocIndex}`,
29925
- onTabChange: (id) => {
29926
- const idx = parseInt(id.replace("doc-", ""), 10);
29927
- setActiveDocIndex(idx);
29928
- }
29929
- }
29930
- ) }),
29931
- showToolbar && /* @__PURE__ */ jsxs(
29932
- HStack,
29933
- {
29934
- gap: "sm",
29935
- align: "center",
29936
- justify: "between",
29937
- className: "px-4 py-2 border-b border-[var(--color-border)] bg-[var(--color-muted)]/30",
29938
- children: [
29939
- /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", children: [
29940
- /* @__PURE__ */ jsx(Icon, { icon: FileText, size: "sm", className: "text-[var(--color-muted-foreground)]" }),
29941
- title && /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "truncate max-w-[200px]", children: title })
29942
- ] }),
29943
- /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
29944
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: ZoomOut, onClick: handleZoomOut }),
29945
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", className: "tabular-nums w-10 text-center", children: [
29946
- zoom,
29947
- "%"
29948
- ] }),
29949
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: ZoomIn, onClick: handleZoomIn }),
29950
- totalPages && totalPages > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
29951
- /* @__PURE__ */ jsx(Box, { className: "w-px h-4 bg-[var(--color-border)] mx-1" }),
29952
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: ChevronLeft, onClick: handlePagePrev, disabled: currentPage <= 1 }),
29953
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", className: "tabular-nums", children: [
29954
- currentPage,
29955
- " / ",
29956
- totalPages
29957
- ] }),
29958
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: ChevronRight, onClick: handlePageNext, disabled: currentPage >= totalPages })
29959
- ] }),
29960
- /* @__PURE__ */ jsx(Box, { className: "w-px h-4 bg-[var(--color-border)] mx-1" }),
29961
- showDownload && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: Download, onClick: handleDownload }),
29962
- showPrint && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: Printer, onClick: handlePrint }),
29963
- actions?.map((action, idx) => /* @__PURE__ */ jsx(
29964
- Badge,
29965
- {
29966
- variant: "default",
29967
- className: "cursor-pointer hover:opacity-80 transition-opacity",
29968
- onClick: () => handleAction(action),
29969
- children: action.label
29970
- },
29971
- idx
29972
- ))
29973
- ] })
29974
- ]
29975
- }
29976
- ),
29977
- /* @__PURE__ */ jsx(Box, { className: "w-full overflow-hidden bg-[var(--color-muted)]/20", children: renderContent() })
29978
- ] }) });
29979
- };
29980
- DocumentViewer.displayName = "DocumentViewer";
29981
- function extractTitle(children) {
29982
- if (!React87__default.isValidElement(children)) return void 0;
29983
- const props = children.props;
29984
- if (typeof props.title === "string") {
29985
- return props.title;
29986
- }
29987
- return void 0;
29988
- }
29989
- var DrawerSlot = ({
29990
- children,
29991
- title: overrideTitle,
29992
- position = "right",
29993
- size = "md",
29994
- className
29995
- }) => {
29996
- const eventBus = useEventBus();
29997
- const isOpen = Boolean(children);
29998
- const title = overrideTitle || extractTitle(children);
29999
- const handleClose = () => {
30000
- eventBus.emit("UI:CLOSE");
30001
- eventBus.emit("UI:CANCEL");
30002
- };
30003
- if (!isOpen) return null;
30004
- return /* @__PURE__ */ jsx(
30005
- Drawer,
30006
- {
30007
- isOpen,
30008
- onClose: handleClose,
30009
- title,
30010
- position,
30011
- width: size,
30012
- className,
30013
- children
30014
- }
30015
- );
30016
- };
30017
- DrawerSlot.displayName = "DrawerSlot";
30018
- var DEFAULT_FEATURE_CONFIGS = {
30019
- tree: { color: 2263842, height: 1.5, scale: 1, geometry: "tree" },
30020
- rock: { color: 8421504, height: 0.5, scale: 0.8, geometry: "rock" },
30021
- bush: { color: 3329330, height: 0.4, scale: 0.6, geometry: "bush" },
30022
- house: { color: 9127187, height: 1.2, scale: 1.2, geometry: "house" },
30023
- tower: { color: 6908265, height: 2.5, scale: 1, geometry: "tower" },
30024
- wall: { color: 8421504, height: 1, scale: 1, geometry: "wall" },
30025
- mountain: { color: 5597999, height: 2, scale: 1.5, geometry: "mountain" },
30026
- hill: { color: 7048739, height: 0.8, scale: 1.2, geometry: "hill" },
30027
- water: { color: 4491468, height: 0.1, scale: 1, geometry: "water" },
30028
- chest: { color: 16766720, height: 0.3, scale: 0.4, geometry: "chest" },
30029
- sign: { color: 9127187, height: 0.8, scale: 0.3, geometry: "sign" },
30030
- portal: { color: 10040012, height: 1.5, scale: 1, geometry: "portal" }
30031
- };
30032
- function TreeFeature({ height, color }) {
30033
- return /* @__PURE__ */ jsxs(Fragment, { children: [
30034
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.3, 0], children: [
30035
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.08, 0.1, height * 0.6, 6] }),
30036
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
30037
- ] }),
30038
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.7, 0], children: [
30039
- /* @__PURE__ */ jsx("coneGeometry", { args: [0.4, height * 0.5, 8] }),
30040
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
30041
- ] }),
30042
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.9, 0], children: [
30043
- /* @__PURE__ */ jsx("coneGeometry", { args: [0.3, height * 0.4, 8] }),
30044
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
30045
- ] }),
30046
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 1.05, 0], children: [
30047
- /* @__PURE__ */ jsx("coneGeometry", { args: [0.15, height * 0.25, 8] }),
30048
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
30049
- ] })
30050
- ] });
30051
- }
30052
- function RockFeature({ height, color }) {
30053
- return /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.4, 0], children: [
30054
- /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [height * 0.5, 0] }),
30055
- /* @__PURE__ */ jsx("meshStandardMaterial", { color, roughness: 0.9 })
30056
- ] });
30057
- }
30058
- function BushFeature({ height, color }) {
30059
- return /* @__PURE__ */ jsxs(Fragment, { children: [
30060
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.3, 0], children: [
30061
- /* @__PURE__ */ jsx("sphereGeometry", { args: [height * 0.4, 8, 8] }),
30062
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
30063
- ] }),
30064
- /* @__PURE__ */ jsxs("mesh", { position: [0.1, height * 0.4, 0.1], children: [
30065
- /* @__PURE__ */ jsx("sphereGeometry", { args: [height * 0.25, 8, 8] }),
30066
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
30067
- ] })
30068
- ] });
30069
- }
30070
- function HouseFeature({ height, color }) {
30071
- return /* @__PURE__ */ jsxs(Fragment, { children: [
30072
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.4, 0], children: [
30073
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, height * 0.8, 0.8] }),
30074
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 13808780 })
30075
- ] }),
30076
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.9, 0], children: [
30077
- /* @__PURE__ */ jsx("coneGeometry", { args: [0.6, height * 0.4, 4] }),
30078
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
30079
- ] }),
30080
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.25, 0.41], children: [
30081
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.25, height * 0.5] }),
30082
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 4863784 })
30083
- ] })
30084
- ] });
30085
- }
30086
- function TowerFeature({ height, color }) {
30087
- return /* @__PURE__ */ jsxs(Fragment, { children: [
30088
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.5, 0], children: [
30089
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.35, height, 8] }),
30090
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
30091
- ] }),
30092
- /* @__PURE__ */ jsxs("mesh", { position: [0, height + 0.05, 0], children: [
30093
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.35, 0.35, 0.1, 8] }),
30094
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
30095
- ] })
30096
- ] });
30097
- }
30098
- function ChestFeature({ height, color }) {
30099
- return /* @__PURE__ */ jsxs(Fragment, { children: [
30100
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.5, 0], children: [
30101
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.3, height, 0.2] }),
30102
- /* @__PURE__ */ jsx("meshStandardMaterial", { color, metalness: 0.6, roughness: 0.3 })
30103
- ] }),
30104
- /* @__PURE__ */ jsxs("mesh", { position: [0, height + 0.05, 0], children: [
30105
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.15, 0.15, 0.3, 8, 1, false, 0, Math.PI] }),
30106
- /* @__PURE__ */ jsx("meshStandardMaterial", { color, metalness: 0.6, roughness: 0.3 })
30107
- ] })
30108
- ] });
30109
- }
30110
- function DefaultFeature({ height, color }) {
30111
- return /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.5, 0], children: [
30112
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.5, height, 0.5] }),
30113
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
30114
- ] });
30115
- }
30116
- function FeatureVisual({
30117
- feature,
30118
- position,
30119
- isSelected,
30120
- onClick,
30121
- onHover
30122
- }) {
30123
- const config = DEFAULT_FEATURE_CONFIGS[feature.type] || {
30124
- color: 8947848,
30125
- height: 0.5,
30126
- scale: 1,
30127
- geometry: "default"
30128
- };
30129
- const color = feature.color ? parseInt(feature.color.replace("#", ""), 16) : config.color;
30130
- const renderGeometry = () => {
30131
- switch (config.geometry) {
30132
- case "tree":
30133
- return /* @__PURE__ */ jsx(TreeFeature, { height: config.height, color });
30134
- case "rock":
30135
- return /* @__PURE__ */ jsx(RockFeature, { height: config.height, color });
30136
- case "bush":
30137
- return /* @__PURE__ */ jsx(BushFeature, { height: config.height, color });
30138
- case "house":
30139
- return /* @__PURE__ */ jsx(HouseFeature, { height: config.height, color });
30140
- case "tower":
30141
- return /* @__PURE__ */ jsx(TowerFeature, { height: config.height, color });
30142
- case "chest":
30143
- return /* @__PURE__ */ jsx(ChestFeature, { height: config.height, color });
30144
- default:
30145
- return /* @__PURE__ */ jsx(DefaultFeature, { height: config.height, color });
30146
- }
30147
- };
30148
- return /* @__PURE__ */ jsxs(
30149
- "group",
30150
- {
30151
- position,
30152
- scale: config.scale,
30153
- onClick,
30154
- onPointerEnter: () => onHover(true),
30155
- onPointerLeave: () => onHover(false),
30156
- userData: { type: "feature", featureId: feature.id, featureType: feature.type },
30157
- children: [
30158
- isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
30159
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
30160
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
30161
- ] }),
30162
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.01, 0], rotation: [-Math.PI / 2, 0, 0], children: [
30163
- /* @__PURE__ */ jsx("circleGeometry", { args: [0.35, 16] }),
30164
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#000000", transparent: true, opacity: 0.2 })
30165
- ] }),
30166
- renderGeometry()
30167
- ]
30168
- }
30169
- );
30170
- }
30171
- function FeatureRenderer({
30172
- features,
30173
- cellSize = 1,
30174
- offsetX = 0,
30175
- offsetZ = 0,
30176
- onFeatureClick,
30177
- onFeatureHover,
30178
- selectedFeatureIds = [],
30179
- featureColors
30180
- }) {
30181
- return /* @__PURE__ */ jsx("group", { children: features.map((feature) => {
30182
- const x = (feature.x - offsetX) * cellSize;
30183
- const z = ((feature.z ?? feature.y ?? 0) - offsetZ) * cellSize;
30184
- const y = (feature.elevation ?? 0) * 0.1;
30185
- const isSelected = feature.id ? selectedFeatureIds.includes(feature.id) : false;
30186
- return /* @__PURE__ */ jsx(
30187
- FeatureVisual,
30188
- {
30189
- feature,
30190
- position: [x, y, z],
30191
- isSelected,
30192
- onClick: () => onFeatureClick?.(feature),
30193
- onHover: (hovered) => onFeatureHover?.(hovered ? feature : null)
30194
- },
30195
- feature.id ?? `feature-${feature.x}-${feature.y}`
30196
- );
30197
- }) });
30198
- }
30199
- function detectAssetRoot(modelUrl) {
30200
- const idx = modelUrl.indexOf("/3d/");
30201
- if (idx !== -1) {
30202
- return modelUrl.substring(0, idx + 4);
30203
- }
30204
- return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
30205
- }
30206
- function useGLTFModel(url) {
30207
- const [model, setModel] = useState(null);
30208
- const [isLoading, setIsLoading] = useState(false);
30209
- const [error, setError] = useState(null);
30210
- useEffect(() => {
30211
- if (!url) {
30212
- setModel(null);
30213
- return;
30214
- }
30215
- setIsLoading(true);
30216
- setError(null);
30217
- const assetRoot = detectAssetRoot(url);
30218
- const loader = new GLTFLoader$1();
30219
- loader.setResourcePath(assetRoot);
30220
- loader.load(
30221
- url,
30222
- (gltf) => {
30223
- setModel(gltf.scene);
30224
- setIsLoading(false);
30225
- },
30226
- void 0,
30227
- (err) => {
30228
- setError(err instanceof Error ? err : new Error(String(err)));
30229
- setIsLoading(false);
30230
- }
30231
- );
30232
- }, [url]);
30233
- return { model, isLoading, error };
30234
- }
30235
- function FeatureModel({
30236
- feature,
30237
- position,
30238
- isSelected,
30239
- onClick,
30240
- onHover
30241
- }) {
30242
- const groupRef = useRef(null);
30243
- const { model: loadedModel, isLoading } = useGLTFModel(feature.assetUrl);
30244
- const model = useMemo(() => {
30245
- if (!loadedModel) return null;
30246
- const cloned = loadedModel.clone();
30247
- cloned.scale.setScalar(0.3);
30248
- cloned.traverse((child) => {
30249
- if (child instanceof THREE.Mesh) {
30250
- child.castShadow = true;
30251
- child.receiveShadow = true;
30252
- }
30253
- });
30254
- return cloned;
30255
- }, [loadedModel]);
30256
- useFrame((state) => {
30257
- if (groupRef.current) {
30258
- const featureRotation = feature.rotation;
30259
- const baseRotation = featureRotation !== void 0 ? featureRotation * Math.PI / 180 - Math.PI / 4 : -Math.PI / 4;
30260
- const wobble = isSelected ? Math.sin(state.clock.elapsedTime * 2) * 0.1 : 0;
30261
- groupRef.current.rotation.y = baseRotation + wobble;
30262
- }
30263
- });
30264
- if (isLoading) {
30265
- return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
30266
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
30267
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
30268
- ] }) });
30269
- }
30270
- if (!model && !feature.assetUrl) {
30271
- return /* @__PURE__ */ jsxs(
30272
- "group",
30273
- {
30274
- position,
30275
- onClick,
30276
- onPointerEnter: () => onHover(true),
30277
- onPointerLeave: () => onHover(false),
30278
- userData: { type: "feature", featureId: feature.id, featureType: feature.type },
30279
- children: [
30280
- isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
30281
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
30282
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
30283
- ] }),
30284
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.5, 0], children: [
30285
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.4, 0.4, 0.4] }),
30286
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8947848 })
30287
- ] })
30288
- ]
30289
- }
30290
- );
30291
- }
30292
- return /* @__PURE__ */ jsxs(
30293
- "group",
30294
- {
30295
- ref: groupRef,
30296
- position,
30297
- onClick,
30298
- onPointerEnter: () => onHover(true),
30299
- onPointerLeave: () => onHover(false),
30300
- userData: { type: "feature", featureId: feature.id, featureType: feature.type },
30301
- children: [
30302
- isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
30303
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
30304
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
30305
- ] }),
30306
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.01, 0], rotation: [-Math.PI / 2, 0, 0], children: [
30307
- /* @__PURE__ */ jsx("circleGeometry", { args: [0.35, 16] }),
30308
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#000000", transparent: true, opacity: 0.2 })
30309
- ] }),
30310
- model && /* @__PURE__ */ jsx("primitive", { object: model })
30311
- ]
30312
- }
30313
- );
30314
- }
30315
- function FeatureRenderer3D({
30316
- features,
30317
- cellSize = 1,
30318
- offsetX = 0,
30319
- offsetZ = 0,
30320
- onFeatureClick,
30321
- onFeatureHover,
30322
- selectedFeatureIds = []
30323
- }) {
30324
- return /* @__PURE__ */ jsx("group", { children: features.map((feature) => {
30325
- const x = (feature.x - offsetX) * cellSize;
30326
- const z = ((feature.z ?? feature.y ?? 0) - offsetZ) * cellSize;
30327
- const y = (feature.elevation ?? 0) * 0.1;
30328
- const isSelected = feature.id ? selectedFeatureIds.includes(feature.id) : false;
30329
- return /* @__PURE__ */ jsx(
30330
- FeatureModel,
30331
- {
30332
- feature,
30333
- position: [x, y, z],
30334
- isSelected,
30335
- onClick: () => onFeatureClick?.(feature),
30336
- onHover: (hovered) => onFeatureHover?.(hovered ? feature : null)
30337
- },
30338
- feature.id ?? `feature-${feature.x}-${feature.y}`
30339
- );
30340
- }) });
30341
- }
30342
- function detectAssetRoot2(modelUrl) {
30343
- const idx = modelUrl.indexOf("/3d/");
30344
- if (idx !== -1) {
30345
- return modelUrl.substring(0, idx + 4);
30346
- }
30347
- return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
30348
- }
30349
- function createGLTFLoaderForUrl(url) {
30350
- const loader = new GLTFLoader();
30351
- loader.setResourcePath(detectAssetRoot2(url));
30352
- return loader;
30353
- }
30354
- var AssetLoader = class {
30355
- constructor() {
30356
- __publicField(this, "objLoader");
30357
- __publicField(this, "textureLoader");
30358
- __publicField(this, "modelCache");
30359
- __publicField(this, "textureCache");
30360
- __publicField(this, "loadingPromises");
30361
- this.objLoader = new OBJLoader();
30362
- this.textureLoader = new THREE.TextureLoader();
30363
- this.modelCache = /* @__PURE__ */ new Map();
30364
- this.textureCache = /* @__PURE__ */ new Map();
30365
- this.loadingPromises = /* @__PURE__ */ new Map();
30366
- }
30367
- /**
30368
- * Load a GLB/GLTF model
30369
- * @param url - URL to the .glb or .gltf file
30370
- * @returns Promise with loaded model scene and animations
30371
- */
30372
- async loadModel(url) {
30373
- if (this.modelCache.has(url)) {
30374
- return this.modelCache.get(url);
30375
- }
30376
- if (this.loadingPromises.has(url)) {
30377
- return this.loadingPromises.get(url);
30378
- }
30379
- const loader = createGLTFLoaderForUrl(url);
30380
- const loadPromise = loader.loadAsync(url).then((gltf) => {
30381
- const result = {
30382
- scene: gltf.scene,
30383
- animations: gltf.animations || []
30384
- };
30385
- this.modelCache.set(url, result);
30386
- this.loadingPromises.delete(url);
30387
- return result;
30388
- }).catch((error) => {
30389
- this.loadingPromises.delete(url);
30390
- throw new Error(`Failed to load model ${url}: ${error.message}`);
30391
- });
30392
- this.loadingPromises.set(url, loadPromise);
30393
- return loadPromise;
30394
- }
30395
- /**
30396
- * Load an OBJ model (fallback for non-GLB assets)
30397
- * @param url - URL to the .obj file
30398
- * @returns Promise with loaded object group
30399
- */
30400
- async loadOBJ(url) {
30401
- if (this.modelCache.has(url)) {
30402
- return this.modelCache.get(url).scene;
30403
- }
30404
- if (this.loadingPromises.has(url)) {
30405
- const result = await this.loadingPromises.get(url);
30406
- return result.scene;
30407
- }
30408
- const loadPromise = this.objLoader.loadAsync(url).then((group) => {
30409
- const result = {
30410
- scene: group,
30411
- animations: []
30412
- };
30413
- this.modelCache.set(url, result);
30414
- this.loadingPromises.delete(url);
30415
- return result;
30416
- }).catch((error) => {
30417
- this.loadingPromises.delete(url);
30418
- throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
30419
- });
30420
- this.loadingPromises.set(url, loadPromise);
30421
- return (await loadPromise).scene;
30422
- }
30423
- /**
30424
- * Load a texture
30425
- * @param url - URL to the texture image
30426
- * @returns Promise with loaded texture
30427
- */
30428
- async loadTexture(url) {
30429
- if (this.textureCache.has(url)) {
30430
- return this.textureCache.get(url);
30431
- }
30432
- if (this.loadingPromises.has(`texture:${url}`)) {
30433
- return this.loadingPromises.get(`texture:${url}`);
30434
- }
30435
- const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
30436
- texture.colorSpace = THREE.SRGBColorSpace;
30437
- this.textureCache.set(url, texture);
30438
- this.loadingPromises.delete(`texture:${url}`);
30439
- return texture;
30440
- }).catch((error) => {
30441
- this.loadingPromises.delete(`texture:${url}`);
30442
- throw new Error(`Failed to load texture ${url}: ${error.message}`);
30443
- });
30444
- this.loadingPromises.set(`texture:${url}`, loadPromise);
30445
- return loadPromise;
30446
- }
30447
- /**
30448
- * Preload multiple assets
30449
- * @param urls - Array of asset URLs to preload
30450
- * @returns Promise that resolves when all assets are loaded
30451
- */
30452
- async preload(urls) {
30453
- const promises = urls.map((url) => {
30454
- if (url.endsWith(".glb") || url.endsWith(".gltf")) {
30455
- return this.loadModel(url).catch(() => null);
30456
- } else if (url.endsWith(".obj")) {
30457
- return this.loadOBJ(url).catch(() => null);
30458
- } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
30459
- return this.loadTexture(url).catch(() => null);
30460
- }
30461
- return Promise.resolve(null);
30462
- });
30463
- await Promise.all(promises);
30464
- }
30465
- /**
30466
- * Check if a model is cached
30467
- * @param url - Model URL
30468
- */
30469
- hasModel(url) {
30470
- return this.modelCache.has(url);
30471
- }
30472
- /**
30473
- * Check if a texture is cached
30474
- * @param url - Texture URL
30475
- */
30476
- hasTexture(url) {
30477
- return this.textureCache.has(url);
30478
- }
30479
- /**
30480
- * Get cached model (throws if not cached)
30481
- * @param url - Model URL
30482
- */
30483
- getModel(url) {
30484
- const model = this.modelCache.get(url);
30485
- if (!model) {
30486
- throw new Error(`Model ${url} not in cache`);
30487
- }
30488
- return model;
30489
- }
30490
- /**
30491
- * Get cached texture (throws if not cached)
30492
- * @param url - Texture URL
30493
- */
30494
- getTexture(url) {
30495
- const texture = this.textureCache.get(url);
30496
- if (!texture) {
30497
- throw new Error(`Texture ${url} not in cache`);
30498
- }
30499
- return texture;
30500
- }
30501
- /**
30502
- * Clear all caches
30503
- */
30504
- clearCache() {
30505
- this.textureCache.forEach((texture) => {
30506
- texture.dispose();
30507
- });
30508
- this.modelCache.forEach((model) => {
30509
- model.scene.traverse((child) => {
30510
- if (child instanceof THREE.Mesh) {
30511
- child.geometry.dispose();
30512
- if (Array.isArray(child.material)) {
30513
- child.material.forEach((m) => m.dispose());
30514
- } else {
30515
- child.material.dispose();
30516
- }
30517
- }
30518
- });
30519
- });
30520
- this.modelCache.clear();
30521
- this.textureCache.clear();
30522
- this.loadingPromises.clear();
30523
- }
30524
- /**
30525
- * Get cache statistics
30526
- */
30527
- getStats() {
30528
- return {
30529
- models: this.modelCache.size,
30530
- textures: this.textureCache.size,
30531
- loading: this.loadingPromises.size
30532
- };
30533
- }
30534
- };
30535
- new AssetLoader();
30536
- function useAssetLoader(options = {}) {
30537
- const { preloadUrls = [], loader: customLoader } = options;
30538
- const loaderRef = useRef(customLoader || new AssetLoader());
30539
- const [state, setState] = useState({
30540
- isLoading: false,
30541
- progress: 0,
30542
- loaded: 0,
30543
- total: 0,
30544
- errors: []
30545
- });
30546
- useEffect(() => {
30547
- if (preloadUrls.length > 0) {
30548
- preload(preloadUrls);
30549
- }
30550
- }, []);
30551
- const updateProgress = useCallback((loaded, total) => {
30552
- setState((prev) => ({
30553
- ...prev,
30554
- loaded,
30555
- total,
30556
- progress: total > 0 ? Math.round(loaded / total * 100) : 0
30557
- }));
30558
- }, []);
30559
- const loadModel = useCallback(
30560
- async (url) => {
30561
- setState((prev) => ({ ...prev, isLoading: true }));
30562
- try {
30563
- const model = await loaderRef.current.loadModel(url);
30564
- setState((prev) => ({
30565
- ...prev,
30566
- isLoading: false,
30567
- loaded: prev.loaded + 1
30568
- }));
30569
- return model;
30570
- } catch (error) {
30571
- const errorMsg = error instanceof Error ? error.message : String(error);
30572
- setState((prev) => ({
30573
- ...prev,
30574
- isLoading: false,
30575
- errors: [...prev.errors, errorMsg]
30576
- }));
30577
- throw error;
30578
- }
30579
- },
30580
- []
30581
- );
30582
- const loadOBJ = useCallback(
30583
- async (url) => {
30584
- setState((prev) => ({ ...prev, isLoading: true }));
30585
- try {
30586
- const model = await loaderRef.current.loadOBJ(url);
30587
- setState((prev) => ({
30588
- ...prev,
30589
- isLoading: false,
30590
- loaded: prev.loaded + 1
30591
- }));
30592
- return model;
30593
- } catch (error) {
30594
- const errorMsg = error instanceof Error ? error.message : String(error);
30595
- setState((prev) => ({
30596
- ...prev,
30597
- isLoading: false,
30598
- errors: [...prev.errors, errorMsg]
30599
- }));
30600
- throw error;
30601
- }
30602
- },
30603
- []
30604
- );
30605
- const loadTexture = useCallback(
30606
- async (url) => {
30607
- setState((prev) => ({ ...prev, isLoading: true }));
30608
- try {
30609
- const texture = await loaderRef.current.loadTexture(url);
30610
- setState((prev) => ({
30611
- ...prev,
30612
- isLoading: false,
30613
- loaded: prev.loaded + 1
30614
- }));
30615
- return texture;
30616
- } catch (error) {
30617
- const errorMsg = error instanceof Error ? error.message : String(error);
30618
- setState((prev) => ({
30619
- ...prev,
30620
- isLoading: false,
30621
- errors: [...prev.errors, errorMsg]
30622
- }));
30623
- throw error;
30624
- }
30625
- },
30626
- []
30627
- );
30628
- const preload = useCallback(
30629
- async (urls) => {
30630
- setState((prev) => ({
30631
- ...prev,
30632
- isLoading: true,
30633
- total: urls.length,
30634
- loaded: 0,
30635
- errors: []
30636
- }));
30637
- let completed = 0;
30638
- const errors = [];
30639
- await Promise.all(
30640
- urls.map(async (url) => {
30641
- try {
30642
- if (url.endsWith(".glb") || url.endsWith(".gltf")) {
30643
- await loaderRef.current.loadModel(url);
30644
- } else if (url.endsWith(".obj")) {
30645
- await loaderRef.current.loadOBJ(url);
30646
- } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
30647
- await loaderRef.current.loadTexture(url);
30648
- }
30649
- completed++;
30650
- updateProgress(completed, urls.length);
30651
- } catch (error) {
30652
- const errorMsg = error instanceof Error ? error.message : String(error);
30653
- errors.push(`${url}: ${errorMsg}`);
30654
- completed++;
30655
- updateProgress(completed, urls.length);
30656
- }
30657
- })
30658
- );
30659
- setState((prev) => ({
30660
- ...prev,
30661
- isLoading: false,
30662
- errors
30663
- }));
30664
- },
30665
- [updateProgress]
30666
- );
30667
- const hasModel = useCallback((url) => {
30668
- return loaderRef.current.hasModel(url);
30669
- }, []);
30670
- const hasTexture = useCallback((url) => {
30671
- return loaderRef.current.hasTexture(url);
30672
- }, []);
30673
- const getModel = useCallback((url) => {
30674
- try {
30675
- return loaderRef.current.getModel(url);
30676
- } catch {
30677
- return void 0;
30678
- }
30679
- }, []);
30680
- const getTexture = useCallback((url) => {
30681
- try {
30682
- return loaderRef.current.getTexture(url);
30683
- } catch {
30684
- return void 0;
30685
- }
30686
- }, []);
30687
- const clearCache = useCallback(() => {
30688
- loaderRef.current.clearCache();
30689
- setState({
30690
- isLoading: false,
30691
- progress: 0,
30692
- loaded: 0,
30693
- total: 0,
30694
- errors: []
30695
- });
30696
- }, []);
30697
- return {
30698
- ...state,
30699
- loadModel,
30700
- loadOBJ,
30701
- loadTexture,
30702
- preload,
30703
- hasModel,
30704
- hasTexture,
30705
- getModel,
30706
- getTexture,
30707
- clearCache
30708
- };
30709
- }
30710
- function useGameCanvas3DEvents(options) {
30711
- const {
30712
- tileClickEvent,
30713
- unitClickEvent,
30714
- featureClickEvent,
30715
- canvasClickEvent,
30716
- tileHoverEvent,
30717
- tileLeaveEvent,
30718
- unitAnimationEvent,
30719
- cameraChangeEvent,
30720
- onTileClick,
30721
- onUnitClick,
30722
- onFeatureClick,
30723
- onCanvasClick,
30724
- onTileHover,
30725
- onUnitAnimation
30726
- } = options;
30727
- const emit = useEmitEvent();
30728
- const optionsRef = useRef(options);
30729
- optionsRef.current = options;
30730
- const handleTileClick = useCallback(
30731
- (tile, event) => {
30732
- if (tileClickEvent) {
30733
- emit(tileClickEvent, {
30734
- tileId: tile.id,
30735
- x: tile.x,
30736
- z: tile.z ?? tile.y ?? 0,
30737
- type: tile.type,
30738
- terrain: tile.terrain,
30739
- elevation: tile.elevation
30740
- });
30741
- }
30742
- optionsRef.current.onTileClick?.(tile, event);
30743
- },
30744
- [tileClickEvent, emit]
30745
- );
30746
- const handleUnitClick = useCallback(
30747
- (unit, event) => {
30748
- if (unitClickEvent) {
30749
- emit(unitClickEvent, {
30750
- unitId: unit.id,
30751
- x: unit.x,
30752
- z: unit.z ?? unit.y ?? 0,
30753
- unitType: unit.unitType,
30754
- name: unit.name,
30755
- team: unit.team,
30756
- faction: unit.faction,
30757
- health: unit.health,
30758
- maxHealth: unit.maxHealth
30759
- });
30760
- }
30761
- optionsRef.current.onUnitClick?.(unit, event);
30762
- },
30763
- [unitClickEvent, emit]
30764
- );
30765
- const handleFeatureClick = useCallback(
30766
- (feature, event) => {
30767
- if (featureClickEvent) {
30768
- emit(featureClickEvent, {
30769
- featureId: feature.id,
30770
- x: feature.x,
30771
- z: feature.z ?? feature.y ?? 0,
30772
- type: feature.type,
30773
- elevation: feature.elevation
30774
- });
30775
- }
30776
- optionsRef.current.onFeatureClick?.(feature, event);
30777
- },
30778
- [featureClickEvent, emit]
30779
- );
30780
- const handleCanvasClick = useCallback(
30781
- (event) => {
30782
- if (canvasClickEvent) {
30783
- emit(canvasClickEvent, {
30784
- clientX: event.clientX,
30785
- clientY: event.clientY,
30786
- button: event.button
30787
- });
30788
- }
30789
- optionsRef.current.onCanvasClick?.(event);
30790
- },
30791
- [canvasClickEvent, emit]
30792
- );
30793
- const handleTileHover = useCallback(
30794
- (tile, event) => {
30795
- if (tile) {
30796
- if (tileHoverEvent) {
30797
- emit(tileHoverEvent, {
30798
- tileId: tile.id,
30799
- x: tile.x,
30800
- z: tile.z ?? tile.y ?? 0,
30801
- type: tile.type
30802
- });
30803
- }
30804
- } else {
30805
- if (tileLeaveEvent) {
30806
- emit(tileLeaveEvent, {});
30807
- }
30808
- }
30809
- optionsRef.current.onTileHover?.(tile, event);
30810
- },
30811
- [tileHoverEvent, tileLeaveEvent, emit]
30812
- );
30813
- const handleUnitAnimation = useCallback(
30814
- (unitId, state) => {
30815
- if (unitAnimationEvent) {
30816
- emit(unitAnimationEvent, {
30817
- unitId,
30818
- state,
30819
- timestamp: Date.now()
30820
- });
30821
- }
30822
- optionsRef.current.onUnitAnimation?.(unitId, state);
30823
- },
30824
- [unitAnimationEvent, emit]
30825
- );
30826
- const handleCameraChange = useCallback(
30827
- (position) => {
30828
- if (cameraChangeEvent) {
30829
- emit(cameraChangeEvent, {
30830
- position,
30831
- timestamp: Date.now()
30832
- });
30833
- }
30834
- },
30835
- [cameraChangeEvent, emit]
30836
- );
30837
- return {
30838
- handleTileClick,
30839
- handleUnitClick,
30840
- handleFeatureClick,
30841
- handleCanvasClick,
30842
- handleTileHover,
30843
- handleUnitAnimation,
30844
- handleCameraChange
30845
- };
30846
- }
30847
- function detectAssetRoot3(modelUrl) {
30848
- const idx = modelUrl.indexOf("/3d/");
30849
- if (idx !== -1) {
30850
- return modelUrl.substring(0, idx + 4);
30851
- }
30852
- return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
30853
- }
30854
- function useGLTFModel2(url, resourceBasePath) {
30855
- const [state, setState] = useState({
30856
- model: null,
30857
- isLoading: false,
30858
- error: null
30859
- });
30860
- useEffect(() => {
30861
- if (!url) {
30862
- setState({ model: null, isLoading: false, error: null });
30863
- return;
30864
- }
30865
- console.log("[ModelLoader] Loading:", url);
30866
- setState((prev) => ({ ...prev, isLoading: true, error: null }));
30867
- const assetRoot = resourceBasePath || detectAssetRoot3(url);
30868
- const loader = new GLTFLoader$1();
30869
- loader.setResourcePath(assetRoot);
30870
- loader.load(
30871
- url,
30872
- (gltf) => {
30873
- console.log("[ModelLoader] Loaded:", url);
30874
- setState({
30875
- model: gltf.scene,
30876
- isLoading: false,
30877
- error: null
30878
- });
30879
- },
30880
- void 0,
30881
- (err) => {
30882
- const errorMsg = err instanceof Error ? err.message : String(err);
30883
- console.warn("[ModelLoader] Failed:", url, errorMsg);
30884
- setState({
30885
- model: null,
30886
- isLoading: false,
30887
- error: err instanceof Error ? err : new Error(String(err))
30888
- });
30889
- }
30890
- );
30891
- }, [url, resourceBasePath]);
30892
- return state;
30893
- }
30894
- function ModelLoader({
30895
- url,
30896
- position = [0, 0, 0],
30897
- scale = 1,
30898
- rotation = [0, 0, 0],
30899
- isSelected = false,
30900
- isHovered = false,
30901
- onClick,
30902
- onHover,
30903
- fallbackGeometry = "box",
30904
- castShadow = true,
30905
- receiveShadow = true,
30906
- resourceBasePath
30907
- }) {
30908
- const { model: loadedModel, isLoading, error } = useGLTFModel2(url, resourceBasePath);
30909
- const model = useMemo(() => {
30910
- if (!loadedModel) return null;
30911
- const cloned = loadedModel.clone();
30912
- cloned.traverse((child) => {
30913
- if (child instanceof THREE.Mesh) {
30914
- child.castShadow = castShadow;
30915
- child.receiveShadow = receiveShadow;
30916
- }
30917
- });
30918
- return cloned;
30919
- }, [loadedModel, castShadow, receiveShadow]);
30920
- const scaleArray = useMemo(() => {
30921
- if (typeof scale === "number") {
30922
- return [scale, scale, scale];
30923
- }
30924
- return scale;
30925
- }, [scale]);
30926
- const rotationRad = useMemo(() => {
30927
- return [
30928
- rotation[0] * Math.PI / 180,
30929
- rotation[1] * Math.PI / 180,
30930
- rotation[2] * Math.PI / 180
30931
- ];
30932
- }, [rotation]);
30933
- if (isLoading) {
30934
- return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
30935
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
30936
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
30937
- ] }) });
30938
- }
30939
- if (error || !model) {
30940
- if (fallbackGeometry === "none") {
30941
- return /* @__PURE__ */ jsx("group", { position });
30942
- }
30943
- const fallbackProps = {
30944
- onClick,
30945
- onPointerOver: () => onHover?.(true),
30946
- onPointerOut: () => onHover?.(false)
30947
- };
30948
- return /* @__PURE__ */ jsxs("group", { position, children: [
30949
- (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
30950
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
30951
- /* @__PURE__ */ jsx(
30952
- "meshBasicMaterial",
30953
- {
30954
- color: isSelected ? 16755200 : 16777215,
30955
- transparent: true,
30956
- opacity: 0.5
30957
- }
30958
- )
30959
- ] }),
30960
- fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
30961
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
30962
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
30963
- ] }),
30964
- fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
30965
- /* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
30966
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
30967
- ] }),
30968
- fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
30969
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
30970
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
30971
- ] })
30972
- ] });
30973
- }
30974
- return /* @__PURE__ */ jsxs(
30975
- "group",
30976
- {
30977
- position,
30978
- rotation: rotationRad,
30979
- onClick,
30980
- onPointerOver: () => onHover?.(true),
30981
- onPointerOut: () => onHover?.(false),
30982
- children: [
30983
- (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
30984
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
30985
- /* @__PURE__ */ jsx(
30986
- "meshBasicMaterial",
30987
- {
30988
- color: isSelected ? 16755200 : 16777215,
30989
- transparent: true,
30990
- opacity: 0.5
30991
- }
30992
- )
30993
- ] }),
30994
- /* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
30995
- ]
30996
- }
30997
- );
30998
- }
30999
- var DEFAULT_GRID_CONFIG = {
31000
- cellSize: 1,
31001
- offsetX: 0,
31002
- offsetZ: 0
31003
- };
31004
- function CameraController({
31005
- onCameraChange
31006
- }) {
31007
- const { camera } = useThree();
31008
- useEffect(() => {
31009
- if (onCameraChange) {
31010
- onCameraChange({
31011
- x: camera.position.x,
31012
- y: camera.position.y,
31013
- z: camera.position.z
31014
- });
31015
- }
31016
- }, [camera.position, onCameraChange]);
31017
- return null;
31018
- }
31019
- var GameCanvas3D = forwardRef(
31020
- ({
31021
- tiles = [],
31022
- units = [],
31023
- features = [],
31024
- events: events2 = [],
31025
- orientation = "standard",
31026
- cameraMode = "isometric",
31027
- showGrid = true,
31028
- showCoordinates = false,
31029
- showTileInfo = false,
31030
- overlay = "default",
31031
- shadows = true,
31032
- backgroundColor = "#1a1a2e",
31033
- onTileClick,
31034
- onUnitClick,
31035
- onFeatureClick,
31036
- onCanvasClick,
31037
- onTileHover,
31038
- onUnitAnimation,
31039
- assetLoader: customAssetLoader,
31040
- tileRenderer: CustomTileRenderer,
31041
- unitRenderer: CustomUnitRenderer,
31042
- featureRenderer: CustomFeatureRenderer,
31043
- className,
31044
- isLoading: externalLoading,
31045
- error: externalError,
31046
- entity,
31047
- preloadAssets = [],
31048
- tileClickEvent,
31049
- unitClickEvent,
31050
- featureClickEvent,
31051
- canvasClickEvent,
31052
- tileHoverEvent,
31053
- tileLeaveEvent,
31054
- unitAnimationEvent,
31055
- cameraChangeEvent,
31056
- loadingMessage = "Loading 3D Scene...",
31057
- useInstancing = true,
31058
- validMoves = [],
31059
- attackTargets = [],
31060
- selectedTileIds = [],
31061
- selectedUnitId = null,
31062
- children
31063
- }, ref) => {
31064
- const containerRef = useRef(null);
31065
- const controlsRef = useRef(null);
31066
- const [hoveredTile, setHoveredTile] = useState(null);
31067
- const [internalError, setInternalError] = useState(null);
31068
- const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
31069
- preloadUrls: preloadAssets,
31070
- loader: customAssetLoader
31071
- });
31072
- const eventHandlers = useGameCanvas3DEvents({
31073
- tileClickEvent,
31074
- unitClickEvent,
31075
- featureClickEvent,
31076
- canvasClickEvent,
31077
- tileHoverEvent,
31078
- tileLeaveEvent,
31079
- unitAnimationEvent,
31080
- cameraChangeEvent,
31081
- onTileClick,
31082
- onUnitClick,
31083
- onFeatureClick,
31084
- onCanvasClick,
31085
- onTileHover,
31086
- onUnitAnimation
31087
- });
31088
- const gridBounds = useMemo(() => {
31089
- if (tiles.length === 0) {
31090
- return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
31091
- }
31092
- const xs = tiles.map((t) => t.x);
31093
- const zs = tiles.map((t) => t.z || t.y || 0);
31094
- return {
31095
- minX: Math.min(...xs),
31096
- maxX: Math.max(...xs),
31097
- minZ: Math.min(...zs),
31098
- maxZ: Math.max(...zs)
31099
- };
31100
- }, [tiles]);
31101
- const cameraTarget = useMemo(() => {
31102
- return [
31103
- (gridBounds.minX + gridBounds.maxX) / 2,
31104
- 0,
31105
- (gridBounds.minZ + gridBounds.maxZ) / 2
31106
- ];
31107
- }, [gridBounds]);
31108
- const gridConfig = useMemo(
31109
- () => ({
31110
- ...DEFAULT_GRID_CONFIG,
31111
- offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
31112
- offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
31113
- }),
31114
- [gridBounds]
31115
- );
31116
- const gridToWorld = useCallback(
31117
- (x, z, y = 0) => {
31118
- const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
31119
- const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
31120
- return [worldX, y * gridConfig.cellSize, worldZ];
31121
- },
31122
- [gridBounds, gridConfig]
31123
- );
31124
- useImperativeHandle(ref, () => ({
31125
- getCameraPosition: () => {
31126
- if (controlsRef.current) {
31127
- const pos = controlsRef.current.object.position;
31128
- return new THREE.Vector3(pos.x, pos.y, pos.z);
31129
- }
31130
- return null;
31131
- },
31132
- setCameraPosition: (x, y, z) => {
31133
- if (controlsRef.current) {
31134
- controlsRef.current.object.position.set(x, y, z);
31135
- controlsRef.current.update();
31136
- }
31137
- },
31138
- lookAt: (x, y, z) => {
31139
- if (controlsRef.current) {
31140
- controlsRef.current.target.set(x, y, z);
31141
- controlsRef.current.update();
31142
- }
31143
- },
31144
- resetCamera: () => {
31145
- if (controlsRef.current) {
31146
- controlsRef.current.reset();
31147
- }
31148
- },
31149
- screenshot: () => {
31150
- const canvas = containerRef.current?.querySelector("canvas");
31151
- if (canvas) {
31152
- return canvas.toDataURL("image/png");
31153
- }
31154
- return null;
31155
- },
31156
- export: () => ({
31157
- tiles,
31158
- units,
31159
- features
31160
- })
31161
- }));
31162
- const handleTileClick = useCallback(
31163
- (tile, event) => {
31164
- eventHandlers.handleTileClick(tile, event);
31165
- },
31166
- [eventHandlers]
31167
- );
31168
- const handleUnitClick = useCallback(
31169
- (unit, event) => {
31170
- eventHandlers.handleUnitClick(unit, event);
31171
- },
31172
- [eventHandlers]
31173
- );
31174
- const handleFeatureClick = useCallback(
31175
- (feature, event) => {
31176
- if (event) {
31177
- eventHandlers.handleFeatureClick(feature, event);
31178
- }
31179
- },
31180
- [eventHandlers]
31181
- );
31182
- const handleTileHover = useCallback(
31183
- (tile, event) => {
31184
- setHoveredTile(tile);
31185
- if (event) {
31186
- eventHandlers.handleTileHover(tile, event);
31187
- }
31188
- },
31189
- [eventHandlers]
31190
- );
31191
- const cameraConfig = useMemo(() => {
31192
- const size = Math.max(
31193
- gridBounds.maxX - gridBounds.minX,
31194
- gridBounds.maxZ - gridBounds.minZ
31195
- );
31196
- const distance = size * 1.5;
31197
- switch (cameraMode) {
31198
- case "isometric":
31199
- return {
31200
- position: [distance, distance * 0.8, distance],
31201
- fov: 45
31202
- };
31203
- case "top-down":
31204
- return {
31205
- position: [0, distance * 2, 0],
31206
- fov: 45
31207
- };
31208
- case "perspective":
31209
- default:
31210
- return {
31211
- position: [distance, distance, distance],
31212
- fov: 45
31213
- };
31214
- }
31215
- }, [cameraMode, gridBounds]);
31216
- const DefaultTileRenderer = useCallback(
31217
- ({ tile, position }) => {
31218
- const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
31219
- const isHovered = hoveredTile?.id === tile.id;
31220
- const isValidMove = validMoves.some(
31221
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
31222
- );
31223
- const isAttackTarget = attackTargets.some(
31224
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
31225
- );
31226
- let color = 8421504;
31227
- if (tile.type === "water") color = 4491468;
31228
- else if (tile.type === "grass") color = 4500036;
31229
- else if (tile.type === "sand") color = 14535816;
31230
- else if (tile.type === "rock") color = 8947848;
31231
- else if (tile.type === "snow") color = 15658734;
31232
- let emissive = 0;
31233
- if (isSelected) emissive = 4473924;
31234
- else if (isAttackTarget) emissive = 4456448;
31235
- else if (isValidMove) emissive = 17408;
31236
- else if (isHovered) emissive = 2236962;
31237
- return /* @__PURE__ */ jsxs(
31238
- "mesh",
31239
- {
31240
- position,
31241
- onClick: (e) => handleTileClick(tile, e),
31242
- onPointerEnter: (e) => handleTileHover(tile, e),
31243
- onPointerLeave: (e) => handleTileHover(null, e),
31244
- userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
31245
- children: [
31246
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
31247
- /* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
31248
- ]
31249
- }
31250
- );
31251
- },
31252
- [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
31253
- );
31254
- const DefaultUnitRenderer = useCallback(
31255
- ({ unit, position }) => {
31256
- const isSelected = selectedUnitId === unit.id;
31257
- const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
31258
- return /* @__PURE__ */ jsxs(
31259
- "group",
31260
- {
31261
- position,
31262
- onClick: (e) => handleUnitClick(unit, e),
31263
- userData: { type: "unit", unitId: unit.id },
31264
- children: [
31265
- isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
31266
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
31267
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
31268
- ] }),
31269
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.3, 0], children: [
31270
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
31271
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
31272
- ] }),
31273
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.6, 0], children: [
31274
- /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
31275
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
31276
- ] }),
31277
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
31278
- /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
31279
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
31280
- ] }),
31281
- unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, 1.2, 0], children: [
31282
- /* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
31283
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
31284
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
31285
- ] }),
31286
- /* @__PURE__ */ jsxs(
31287
- "mesh",
31288
- {
31289
- position: [
31290
- -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
31291
- 0,
31292
- 0.01
31293
- ],
31294
- children: [
31295
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
31296
- /* @__PURE__ */ jsx(
31297
- "meshBasicMaterial",
31298
- {
31299
- color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
31300
- }
31301
- )
31302
- ]
31303
- }
31304
- )
31305
- ] })
31306
- ]
31307
- }
31308
- );
31309
- },
31310
- [selectedUnitId, handleUnitClick]
31311
- );
31312
- const DefaultFeatureRenderer = useCallback(
31313
- ({
31314
- feature,
31315
- position
31316
- }) => {
31317
- if (feature.assetUrl) {
31318
- return /* @__PURE__ */ jsx(
31319
- ModelLoader,
31320
- {
31321
- url: feature.assetUrl,
31322
- position,
31323
- scale: 0.5,
31324
- rotation: [0, feature.rotation ?? 0, 0],
31325
- onClick: () => handleFeatureClick(feature, null),
31326
- fallbackGeometry: "box"
31327
- },
31328
- feature.id
31329
- );
31330
- }
31331
- if (feature.type === "tree") {
31332
- return /* @__PURE__ */ jsxs(
31333
- "group",
31334
- {
31335
- position,
31336
- onClick: (e) => handleFeatureClick(feature, e),
31337
- userData: { type: "feature", featureId: feature.id },
31338
- children: [
31339
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
31340
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
31341
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
31342
- ] }),
31343
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
31344
- /* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
31345
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
31346
- ] })
31347
- ]
31348
- }
31349
- );
31350
- }
31351
- if (feature.type === "rock") {
31352
- return /* @__PURE__ */ jsxs(
31353
- "mesh",
31354
- {
31355
- position: [position[0], position[1] + 0.3, position[2]],
31356
- onClick: (e) => handleFeatureClick(feature, e),
31357
- userData: { type: "feature", featureId: feature.id },
31358
- children: [
31359
- /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
31360
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
31361
- ]
31362
- }
31363
- );
31364
- }
31365
- return null;
31366
- },
31367
- [handleFeatureClick]
31368
- );
31369
- if (externalLoading || assetsLoading && preloadAssets.length > 0) {
31370
- return /* @__PURE__ */ jsx(
31371
- Canvas3DLoadingState,
31372
- {
31373
- progress,
31374
- loaded,
31375
- total,
31376
- message: loadingMessage,
31377
- className
31378
- }
31379
- );
31380
- }
31381
- const displayError = externalError || internalError;
31382
- if (displayError) {
31383
- return /* @__PURE__ */ jsx(Canvas3DErrorBoundary, { children: /* @__PURE__ */ jsx("div", { className: "game-canvas-3d game-canvas-3d--error", children: /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__error", children: [
31384
- "Error: ",
31385
- displayError
31386
- ] }) }) });
31387
- }
31388
- return /* @__PURE__ */ jsx(
31389
- Canvas3DErrorBoundary,
31390
- {
31391
- onError: (err) => setInternalError(err.message),
31392
- onReset: () => setInternalError(null),
31393
- children: /* @__PURE__ */ jsxs(
31394
- "div",
31395
- {
31396
- ref: containerRef,
31397
- className: `game-canvas-3d ${className || ""}`,
31398
- "data-orientation": orientation,
31399
- "data-camera-mode": cameraMode,
31400
- "data-overlay": overlay,
31401
- children: [
31402
- /* @__PURE__ */ jsxs(
31403
- Canvas,
31404
- {
31405
- shadows,
31406
- camera: {
31407
- position: cameraConfig.position,
31408
- fov: cameraConfig.fov,
31409
- near: 0.1,
31410
- far: 1e3
31411
- },
31412
- style: { background: backgroundColor },
31413
- onClick: (e) => {
31414
- if (e.target === e.currentTarget) {
31415
- eventHandlers.handleCanvasClick(e);
31416
- }
31417
- },
31418
- children: [
31419
- /* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
31420
- /* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
31421
- /* @__PURE__ */ jsx(
31422
- "directionalLight",
31423
- {
31424
- position: [10, 20, 10],
31425
- intensity: 0.8,
31426
- castShadow: shadows,
31427
- "shadow-mapSize": [2048, 2048]
31428
- }
31429
- ),
31430
- /* @__PURE__ */ jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
31431
- showGrid && /* @__PURE__ */ jsx(
31432
- Grid$1,
31433
- {
31434
- args: [
31435
- Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
31436
- Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
31437
- ],
31438
- position: [
31439
- (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
31440
- 0,
31441
- (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
31442
- ],
31443
- cellSize: 1,
31444
- cellThickness: 1,
31445
- cellColor: "#444444",
31446
- sectionSize: 5,
31447
- sectionThickness: 1.5,
31448
- sectionColor: "#666666",
31449
- fadeDistance: 50,
31450
- fadeStrength: 1
31451
- }
31452
- ),
31453
- tiles.map((tile, index) => {
31454
- const position = gridToWorld(
31455
- tile.x,
31456
- tile.z ?? tile.y ?? 0,
31457
- tile.elevation ?? 0
31458
- );
31459
- const Renderer = CustomTileRenderer || DefaultTileRenderer;
31460
- return /* @__PURE__ */ jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
31461
- }),
31462
- features.map((feature, index) => {
31463
- const position = gridToWorld(
31464
- feature.x,
31465
- feature.z ?? feature.y ?? 0,
31466
- (feature.elevation ?? 0) + 0.5
31467
- );
31468
- const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
31469
- return /* @__PURE__ */ jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
31470
- }),
31471
- units.map((unit) => {
31472
- const position = gridToWorld(
31473
- unit.x ?? 0,
31474
- unit.z ?? unit.y ?? 0,
31475
- (unit.elevation ?? 0) + 0.5
31476
- );
31477
- const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
31478
- return /* @__PURE__ */ jsx(Renderer, { unit, position }, unit.id);
31479
- }),
31480
- children,
31481
- /* @__PURE__ */ jsx(
31482
- OrbitControls,
31483
- {
31484
- ref: controlsRef,
31485
- target: cameraTarget,
31486
- enableDamping: true,
31487
- dampingFactor: 0.05,
31488
- minDistance: 2,
31489
- maxDistance: 100,
31490
- maxPolarAngle: Math.PI / 2 - 0.1
31491
- }
31492
- )
31493
- ]
31494
- }
31495
- ),
31496
- showCoordinates && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__coordinates", children: [
31497
- "X: ",
31498
- hoveredTile.x,
31499
- ", Z: ",
31500
- hoveredTile.z ?? hoveredTile.y ?? 0
31501
- ] }),
31502
- showTileInfo && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__tile-info", children: [
31503
- /* @__PURE__ */ jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
31504
- hoveredTile.terrain && /* @__PURE__ */ jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
31505
- ] })
31506
- ]
31507
- }
31508
- )
29695
+ Box,
29696
+ {
29697
+ className: "w-full overflow-auto p-4 font-mono text-sm",
29698
+ style: { height, fontSize: `${zoom}%` },
29699
+ children: /* @__PURE__ */ jsx(Typography, { variant: "body", className: "whitespace-pre-wrap break-words", children: activeContent })
31509
29700
  }
31510
29701
  );
31511
- }
31512
- );
31513
- GameCanvas3D.displayName = "GameCanvas3D";
31514
- function GameCanvas3DBattleTemplate({
31515
- entity,
31516
- cameraMode = "perspective",
31517
- showGrid = true,
31518
- shadows = true,
31519
- backgroundColor = "#2a1a1a",
31520
- tileClickEvent,
31521
- unitClickEvent,
31522
- unitAttackEvent,
31523
- unitMoveEvent,
31524
- endTurnEvent,
31525
- exitEvent,
31526
- selectedUnitId,
31527
- validMoves,
31528
- attackTargets,
31529
- className
31530
- }) {
31531
- const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
31532
- if (!resolved) return null;
31533
- return /* @__PURE__ */ jsxs(Box, { className: cn("game-canvas-3d-battle-template", className), children: [
31534
- /* @__PURE__ */ jsx(
31535
- GameCanvas3D,
29702
+ };
29703
+ return /* @__PURE__ */ jsx(Card, { className: cn("overflow-hidden", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
29704
+ tabItems && tabItems.length > 1 && /* @__PURE__ */ jsx(Box, { className: "border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx(
29705
+ Tabs,
31536
29706
  {
31537
- tiles: resolved.tiles,
31538
- units: resolved.units,
31539
- features: resolved.features,
31540
- cameraMode,
31541
- showGrid,
31542
- showCoordinates: false,
31543
- showTileInfo: false,
31544
- shadows,
31545
- backgroundColor,
31546
- tileClickEvent,
31547
- unitClickEvent,
31548
- selectedUnitId,
31549
- validMoves,
31550
- attackTargets,
31551
- className: "game-canvas-3d-battle-template__canvas"
29707
+ tabs: tabItems,
29708
+ activeTab: `doc-${activeDocIndex}`,
29709
+ onTabChange: (id) => {
29710
+ const idx = parseInt(id.replace("doc-", ""), 10);
29711
+ setActiveDocIndex(idx);
29712
+ }
31552
29713
  }
31553
- ),
31554
- resolved.currentTurn && /* @__PURE__ */ jsxs(
29714
+ ) }),
29715
+ showToolbar && /* @__PURE__ */ jsxs(
31555
29716
  HStack,
31556
29717
  {
31557
29718
  gap: "sm",
31558
29719
  align: "center",
31559
- className: cn("battle-template__turn-indicator", `battle-template__turn-indicator--${resolved.currentTurn}`),
29720
+ justify: "between",
29721
+ className: "px-4 py-2 border-b border-[var(--color-border)] bg-[var(--color-muted)]/30",
31560
29722
  children: [
31561
- /* @__PURE__ */ jsx(Typography, { variant: "body", className: "turn-indicator__label", children: resolved.currentTurn === "player" ? "Your Turn" : "Enemy's Turn" }),
31562
- resolved.round && /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "turn-indicator__round", children: [
31563
- "Round ",
31564
- resolved.round
29723
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", children: [
29724
+ /* @__PURE__ */ jsx(Icon, { icon: FileText, size: "sm", className: "text-[var(--color-muted-foreground)]" }),
29725
+ title && /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "truncate max-w-[200px]", children: title })
29726
+ ] }),
29727
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
29728
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: ZoomOut, onClick: handleZoomOut }),
29729
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", className: "tabular-nums w-10 text-center", children: [
29730
+ zoom,
29731
+ "%"
29732
+ ] }),
29733
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: ZoomIn, onClick: handleZoomIn }),
29734
+ totalPages && totalPages > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
29735
+ /* @__PURE__ */ jsx(Box, { className: "w-px h-4 bg-[var(--color-border)] mx-1" }),
29736
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: ChevronLeft, onClick: handlePagePrev, disabled: currentPage <= 1 }),
29737
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", className: "tabular-nums", children: [
29738
+ currentPage,
29739
+ " / ",
29740
+ totalPages
29741
+ ] }),
29742
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: ChevronRight, onClick: handlePageNext, disabled: currentPage >= totalPages })
29743
+ ] }),
29744
+ /* @__PURE__ */ jsx(Box, { className: "w-px h-4 bg-[var(--color-border)] mx-1" }),
29745
+ showDownload && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: Download, onClick: handleDownload }),
29746
+ showPrint && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", icon: Printer, onClick: handlePrint }),
29747
+ actions?.map((action, idx) => /* @__PURE__ */ jsx(
29748
+ Badge,
29749
+ {
29750
+ variant: "default",
29751
+ className: "cursor-pointer hover:opacity-80 transition-opacity",
29752
+ onClick: () => handleAction(action),
29753
+ children: action.label
29754
+ },
29755
+ idx
29756
+ ))
31565
29757
  ] })
31566
29758
  ]
31567
29759
  }
31568
- )
31569
- ] });
31570
- }
31571
- GameCanvas3DBattleTemplate.displayName = "GameCanvas3DBattleTemplate";
31572
- function GameCanvas3DCastleTemplate({
31573
- entity,
31574
- cameraMode = "isometric",
31575
- showGrid = true,
31576
- shadows = true,
31577
- backgroundColor = "#1e1e2e",
31578
- buildingClickEvent,
31579
- unitClickEvent,
31580
- buildEvent,
31581
- recruitEvent,
31582
- exitEvent,
31583
- selectedBuildingId,
31584
- selectedTileIds = [],
31585
- availableBuildSites,
31586
- showHeader = true,
31587
- className
31588
- }) {
31589
- const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
31590
- if (!resolved) return null;
31591
- return /* @__PURE__ */ jsxs(VStack, { className: cn("game-canvas-3d-castle-template", className), children: [
31592
- showHeader && resolved.name && /* @__PURE__ */ jsxs(HStack, { gap: "md", align: "center", className: "castle-template__header", children: [
31593
- /* @__PURE__ */ jsx(Typography, { variant: "h2", className: "header__name", children: resolved.name }),
31594
- resolved.level && /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "header__level", children: [
31595
- "Level ",
31596
- resolved.level
31597
- ] }),
31598
- resolved.owner && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", className: "header__owner", children: resolved.owner })
31599
- ] }),
31600
- /* @__PURE__ */ jsx(
31601
- GameCanvas3D,
31602
- {
31603
- tiles: resolved.tiles,
31604
- units: resolved.units,
31605
- features: resolved.features,
31606
- cameraMode,
31607
- showGrid,
31608
- showCoordinates: false,
31609
- showTileInfo: false,
31610
- shadows,
31611
- backgroundColor,
31612
- featureClickEvent: buildingClickEvent,
31613
- unitClickEvent,
31614
- selectedTileIds,
31615
- validMoves: availableBuildSites,
31616
- className: "game-canvas-3d-castle-template__canvas"
31617
- }
31618
29760
  ),
31619
- resolved.units.length > 0 && /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", className: "castle-template__garrison-info", children: [
31620
- /* @__PURE__ */ jsx(Typography, { variant: "small", className: "garrison-info__label", children: "Garrison:" }),
31621
- /* @__PURE__ */ jsxs(Typography, { variant: "small", weight: "bold", className: "garrison-info__count", children: [
31622
- resolved.units.length,
31623
- " units"
31624
- ] })
31625
- ] })
31626
- ] });
29761
+ /* @__PURE__ */ jsx(Box, { className: "w-full overflow-hidden bg-[var(--color-muted)]/20", children: renderContent() })
29762
+ ] }) });
29763
+ };
29764
+ DocumentViewer.displayName = "DocumentViewer";
29765
+ function extractTitle(children) {
29766
+ if (!React87__default.isValidElement(children)) return void 0;
29767
+ const props = children.props;
29768
+ if (typeof props.title === "string") {
29769
+ return props.title;
29770
+ }
29771
+ return void 0;
31627
29772
  }
31628
- GameCanvas3DCastleTemplate.displayName = "GameCanvas3DCastleTemplate";
31629
- function GameCanvas3DWorldMapTemplate({
31630
- entity,
31631
- cameraMode = "isometric",
31632
- showGrid = true,
31633
- showCoordinates = true,
31634
- showTileInfo = true,
31635
- shadows = true,
31636
- backgroundColor = "#1a1a2e",
31637
- tileClickEvent,
31638
- unitClickEvent,
31639
- featureClickEvent,
31640
- tileHoverEvent,
31641
- tileLeaveEvent,
31642
- cameraChangeEvent,
31643
- selectedUnitId,
31644
- validMoves,
31645
- attackTargets,
29773
+ var DrawerSlot = ({
29774
+ children,
29775
+ title: overrideTitle,
29776
+ position = "right",
29777
+ size = "md",
31646
29778
  className
31647
- }) {
31648
- const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
31649
- if (!resolved) return null;
29779
+ }) => {
29780
+ const eventBus = useEventBus();
29781
+ const isOpen = Boolean(children);
29782
+ const title = overrideTitle || extractTitle(children);
29783
+ const handleClose = () => {
29784
+ eventBus.emit("UI:CLOSE");
29785
+ eventBus.emit("UI:CANCEL");
29786
+ };
29787
+ if (!isOpen) return null;
31650
29788
  return /* @__PURE__ */ jsx(
31651
- GameCanvas3D,
29789
+ Drawer,
31652
29790
  {
31653
- tiles: resolved.tiles,
31654
- units: resolved.units,
31655
- features: resolved.features,
31656
- cameraMode,
31657
- showGrid,
31658
- showCoordinates,
31659
- showTileInfo,
31660
- shadows,
31661
- backgroundColor,
31662
- tileClickEvent,
31663
- unitClickEvent,
31664
- featureClickEvent,
31665
- tileHoverEvent,
31666
- tileLeaveEvent,
31667
- cameraChangeEvent,
31668
- selectedUnitId,
31669
- validMoves,
31670
- attackTargets,
31671
- className
29791
+ isOpen,
29792
+ onClose: handleClose,
29793
+ title,
29794
+ position,
29795
+ width: size,
29796
+ className,
29797
+ children
31672
29798
  }
31673
29799
  );
31674
- }
31675
- GameCanvas3DWorldMapTemplate.displayName = "GameCanvas3DWorldMapTemplate";
29800
+ };
29801
+ DrawerSlot.displayName = "DrawerSlot";
31676
29802
  var GameShell = ({
31677
29803
  appName = "Game",
31678
29804
  hud,
@@ -32189,55 +30315,6 @@ var GraphCanvas = ({
32189
30315
  ] }) });
32190
30316
  };
32191
30317
  GraphCanvas.displayName = "GraphCanvas";
32192
- function Lighting3D({
32193
- ambientIntensity = 0.6,
32194
- ambientColor = "#ffffff",
32195
- directionalIntensity = 0.8,
32196
- directionalColor = "#ffffff",
32197
- directionalPosition = [10, 20, 10],
32198
- shadows = true,
32199
- shadowMapSize = 2048,
32200
- shadowCameraSize = 20,
32201
- showHelpers = false
32202
- }) {
32203
- return /* @__PURE__ */ jsxs(Fragment, { children: [
32204
- /* @__PURE__ */ jsx("ambientLight", { intensity: ambientIntensity, color: ambientColor }),
32205
- /* @__PURE__ */ jsx(
32206
- "directionalLight",
32207
- {
32208
- position: directionalPosition,
32209
- intensity: directionalIntensity,
32210
- color: directionalColor,
32211
- castShadow: shadows,
32212
- "shadow-mapSize": [shadowMapSize, shadowMapSize],
32213
- "shadow-camera-left": -shadowCameraSize,
32214
- "shadow-camera-right": shadowCameraSize,
32215
- "shadow-camera-top": shadowCameraSize,
32216
- "shadow-camera-bottom": -shadowCameraSize,
32217
- "shadow-camera-near": 0.1,
32218
- "shadow-camera-far": 100,
32219
- "shadow-bias": -1e-3
32220
- }
32221
- ),
32222
- /* @__PURE__ */ jsx(
32223
- "hemisphereLight",
32224
- {
32225
- intensity: 0.3,
32226
- color: "#87ceeb",
32227
- groundColor: "#362d1d"
32228
- }
32229
- ),
32230
- showHelpers && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
32231
- "directionalLightHelper",
32232
- {
32233
- args: [
32234
- new THREE.DirectionalLight(directionalColor, directionalIntensity),
32235
- 5
32236
- ]
32237
- }
32238
- ) })
32239
- ] });
32240
- }
32241
30318
  var COLUMN_CLASSES = {
32242
30319
  2: "grid-cols-2",
32243
30320
  3: "grid-cols-2 sm:grid-cols-3",
@@ -32480,92 +30557,6 @@ var ModalSlot = ({
32480
30557
  );
32481
30558
  };
32482
30559
  ModalSlot.displayName = "ModalSlot";
32483
- function PhysicsObject3D({
32484
- entityId,
32485
- modelUrl,
32486
- initialPosition = [0, 0, 0],
32487
- initialVelocity = [0, 0, 0],
32488
- mass = 1,
32489
- gravity = 9.8,
32490
- groundY = 0,
32491
- scale = 1,
32492
- onPhysicsUpdate,
32493
- onGroundHit,
32494
- onCollision
32495
- }) {
32496
- const groupRef = useRef(null);
32497
- const physicsStateRef = useRef({
32498
- id: entityId,
32499
- x: initialPosition[0],
32500
- y: initialPosition[1],
32501
- z: initialPosition[2],
32502
- vx: initialVelocity[0],
32503
- vy: initialVelocity[1],
32504
- vz: initialVelocity[2],
32505
- rx: 0,
32506
- ry: 0,
32507
- rz: 0,
32508
- isGrounded: false,
32509
- gravity,
32510
- friction: 0.8,
32511
- mass,
32512
- state: "Active"
32513
- });
32514
- const groundHitRef = useRef(false);
32515
- useEffect(() => {
32516
- if (groupRef.current) {
32517
- groupRef.current.position.set(
32518
- initialPosition[0],
32519
- initialPosition[1],
32520
- initialPosition[2]
32521
- );
32522
- }
32523
- }, []);
32524
- useFrame((state, delta) => {
32525
- const physics = physicsStateRef.current;
32526
- if (physics.state !== "Active") return;
32527
- const dt = Math.min(delta, 0.1);
32528
- if (!physics.isGrounded) {
32529
- physics.vy -= physics.gravity * dt;
32530
- }
32531
- physics.x += physics.vx * dt;
32532
- physics.y += physics.vy * dt;
32533
- physics.z += physics.vz * dt;
32534
- const airResistance = Math.pow(0.99, dt * 60);
32535
- physics.vx *= airResistance;
32536
- physics.vz *= airResistance;
32537
- if (physics.y <= groundY) {
32538
- physics.y = groundY;
32539
- if (!physics.isGrounded) {
32540
- physics.isGrounded = true;
32541
- groundHitRef.current = true;
32542
- physics.vx *= physics.friction;
32543
- physics.vz *= physics.friction;
32544
- onGroundHit?.();
32545
- }
32546
- physics.vy = 0;
32547
- } else {
32548
- physics.isGrounded = false;
32549
- }
32550
- if (groupRef.current) {
32551
- groupRef.current.position.set(physics.x, physics.y, physics.z);
32552
- if (!physics.isGrounded) {
32553
- physics.rx += physics.vz * dt * 0.5;
32554
- physics.rz -= physics.vx * dt * 0.5;
32555
- groupRef.current.rotation.set(physics.rx, physics.ry, physics.rz);
32556
- }
32557
- }
32558
- onPhysicsUpdate?.({ ...physics });
32559
- });
32560
- const scaleArray = typeof scale === "number" ? [scale, scale, scale] : scale;
32561
- return /* @__PURE__ */ jsx("group", { ref: groupRef, scale: scaleArray, children: /* @__PURE__ */ jsx(
32562
- ModelLoader,
32563
- {
32564
- url: modelUrl,
32565
- fallbackGeometry: "box"
32566
- }
32567
- ) });
32568
- }
32569
30560
 
32570
30561
  // lib/traitRegistry.ts
32571
30562
  var traits = /* @__PURE__ */ new Map();
@@ -33713,30 +31704,6 @@ function RuntimeDebugger({
33713
31704
  );
33714
31705
  }
33715
31706
  RuntimeDebugger.displayName = "RuntimeDebugger";
33716
- function Scene3D({ background = "#1a1a2e", fog, children }) {
33717
- const { scene } = useThree();
33718
- const initializedRef = useRef(false);
33719
- useEffect(() => {
33720
- if (initializedRef.current) return;
33721
- initializedRef.current = true;
33722
- if (background.startsWith("#") || background.startsWith("rgb")) {
33723
- scene.background = new THREE.Color(background);
33724
- } else {
33725
- const loader = new THREE.TextureLoader();
33726
- loader.load(background, (texture) => {
33727
- scene.background = texture;
33728
- });
33729
- }
33730
- if (fog) {
33731
- scene.fog = new THREE.Fog(fog.color, fog.near, fog.far);
33732
- }
33733
- return () => {
33734
- scene.background = null;
33735
- scene.fog = null;
33736
- };
33737
- }, [scene, background, fog]);
33738
- return /* @__PURE__ */ jsx(Fragment, { children });
33739
- }
33740
31707
  var SignaturePad = ({
33741
31708
  label = "Signature",
33742
31709
  helperText = "Draw your signature above",
@@ -33913,174 +31880,6 @@ var SignaturePad = ({
33913
31880
  ] }) });
33914
31881
  };
33915
31882
  SignaturePad.displayName = "SignaturePad";
33916
- var DEFAULT_TERRAIN_COLORS = {
33917
- grass: "#44aa44",
33918
- dirt: "#8b7355",
33919
- sand: "#ddcc88",
33920
- water: "#4488cc",
33921
- rock: "#888888",
33922
- snow: "#eeeeee",
33923
- forest: "#228b22",
33924
- desert: "#d4a574",
33925
- mountain: "#696969",
33926
- swamp: "#556b2f"
33927
- };
33928
- function TileRenderer({
33929
- tiles,
33930
- cellSize = 1,
33931
- offsetX = 0,
33932
- offsetZ = 0,
33933
- useInstancing = true,
33934
- terrainColors = DEFAULT_TERRAIN_COLORS,
33935
- onTileClick,
33936
- onTileHover,
33937
- selectedTileIds = [],
33938
- validMoves = [],
33939
- attackTargets = []
33940
- }) {
33941
- const meshRef = useRef(null);
33942
- const geometry = useMemo(() => {
33943
- return new THREE.BoxGeometry(cellSize * 0.95, 0.2, cellSize * 0.95);
33944
- }, [cellSize]);
33945
- const material = useMemo(() => {
33946
- return new THREE.MeshStandardMaterial({
33947
- roughness: 0.8,
33948
- metalness: 0.1
33949
- });
33950
- }, []);
33951
- const { positions, colors, tileMap } = useMemo(() => {
33952
- const pos = [];
33953
- const cols = [];
33954
- const map = /* @__PURE__ */ new Map();
33955
- tiles.forEach((tile) => {
33956
- const x = (tile.x - offsetX) * cellSize;
33957
- const z = ((tile.z ?? tile.y ?? 0) - offsetZ) * cellSize;
33958
- const y = (tile.elevation ?? 0) * 0.1;
33959
- pos.push(new THREE.Vector3(x, y, z));
33960
- const colorHex = terrainColors[tile.type || ""] || terrainColors[tile.terrain || ""] || "#808080";
33961
- const color = new THREE.Color(colorHex);
33962
- const isValidMove = validMoves.some(
33963
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
33964
- );
33965
- const isAttackTarget = attackTargets.some(
33966
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
33967
- );
33968
- const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
33969
- if (isSelected) {
33970
- color.addScalar(0.3);
33971
- } else if (isAttackTarget) {
33972
- color.setHex(16729156);
33973
- } else if (isValidMove) {
33974
- color.setHex(4521796);
33975
- }
33976
- cols.push(color);
33977
- map.set(`${tile.x},${tile.z ?? tile.y ?? 0}`, tile);
33978
- });
33979
- return { positions: pos, colors: cols, tileMap: map };
33980
- }, [tiles, cellSize, offsetX, offsetZ, terrainColors, selectedTileIds, validMoves, attackTargets]);
33981
- useEffect(() => {
33982
- if (!meshRef.current || !useInstancing) return;
33983
- const mesh = meshRef.current;
33984
- mesh.count = positions.length;
33985
- const dummy = new THREE.Object3D();
33986
- positions.forEach((pos, i) => {
33987
- dummy.position.copy(pos);
33988
- dummy.updateMatrix();
33989
- mesh.setMatrixAt(i, dummy.matrix);
33990
- if (mesh.setColorAt) {
33991
- mesh.setColorAt(i, colors[i]);
33992
- }
33993
- });
33994
- mesh.instanceMatrix.needsUpdate = true;
33995
- if (mesh.instanceColor) {
33996
- mesh.instanceColor.needsUpdate = true;
33997
- }
33998
- }, [positions, colors, useInstancing]);
33999
- const handlePointerMove = (e) => {
34000
- if (!onTileHover) return;
34001
- const instanceId = e.instanceId;
34002
- if (instanceId !== void 0) {
34003
- const pos = positions[instanceId];
34004
- if (pos) {
34005
- const gridX = Math.round(pos.x / cellSize + offsetX);
34006
- const gridZ = Math.round(pos.z / cellSize + offsetZ);
34007
- const tile = tileMap.get(`${gridX},${gridZ}`);
34008
- if (tile) {
34009
- onTileHover(tile);
34010
- }
34011
- }
34012
- }
34013
- };
34014
- const handleClick = (e) => {
34015
- if (!onTileClick) return;
34016
- const instanceId = e.instanceId;
34017
- if (instanceId !== void 0) {
34018
- const pos = positions[instanceId];
34019
- if (pos) {
34020
- const gridX = Math.round(pos.x / cellSize + offsetX);
34021
- const gridZ = Math.round(pos.z / cellSize + offsetZ);
34022
- const tile = tileMap.get(`${gridX},${gridZ}`);
34023
- if (tile) {
34024
- onTileClick(tile);
34025
- }
34026
- }
34027
- }
34028
- };
34029
- const renderIndividualTiles = () => {
34030
- return tiles.map((tile) => {
34031
- const x = (tile.x - offsetX) * cellSize;
34032
- const z = ((tile.z ?? tile.y ?? 0) - offsetZ) * cellSize;
34033
- const y = (tile.elevation ?? 0) * 0.1;
34034
- const colorHex = terrainColors[tile.type || ""] || terrainColors[tile.terrain || ""] || "#808080";
34035
- const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
34036
- const isValidMove = validMoves.some(
34037
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
34038
- );
34039
- const isAttackTarget = attackTargets.some(
34040
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
34041
- );
34042
- let emissive = "#000000";
34043
- if (isSelected) emissive = "#444444";
34044
- else if (isAttackTarget) emissive = "#440000";
34045
- else if (isValidMove) emissive = "#004400";
34046
- return /* @__PURE__ */ jsxs(
34047
- "mesh",
34048
- {
34049
- position: [x, y, z],
34050
- userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
34051
- onClick: () => onTileClick?.(tile),
34052
- onPointerEnter: () => onTileHover?.(tile),
34053
- onPointerLeave: () => onTileHover?.(null),
34054
- children: [
34055
- /* @__PURE__ */ jsx("boxGeometry", { args: [cellSize * 0.95, 0.2, cellSize * 0.95] }),
34056
- /* @__PURE__ */ jsx(
34057
- "meshStandardMaterial",
34058
- {
34059
- color: colorHex,
34060
- emissive,
34061
- roughness: 0.8,
34062
- metalness: 0.1
34063
- }
34064
- )
34065
- ]
34066
- },
34067
- tile.id ?? `tile-${tile.x}-${tile.y}`
34068
- );
34069
- });
34070
- };
34071
- if (useInstancing && tiles.length > 0) {
34072
- return /* @__PURE__ */ jsx(
34073
- "instancedMesh",
34074
- {
34075
- ref: meshRef,
34076
- args: [geometry, material, tiles.length],
34077
- onPointerMove: handlePointerMove,
34078
- onClick: handleClick
34079
- }
34080
- );
34081
- }
34082
- return /* @__PURE__ */ jsx("group", { children: renderIndividualTiles() });
34083
- }
34084
31883
  var STATUS_STYLES3 = {
34085
31884
  complete: {
34086
31885
  dotColor: "text-[var(--color-success)]",
@@ -34262,111 +32061,6 @@ var ToastSlot = ({
34262
32061
  ) });
34263
32062
  };
34264
32063
  ToastSlot.displayName = "ToastSlot";
34265
- function UnitVisual({ unit, position, isSelected, onClick }) {
34266
- const groupRef = useRef(null);
34267
- const [animationState, setAnimationState] = useState("idle");
34268
- const [isHovered, setIsHovered] = useState(false);
34269
- const teamColor = useMemo(() => {
34270
- if (unit.faction === "player" || unit.team === "player") return 4491519;
34271
- if (unit.faction === "enemy" || unit.team === "enemy") return 16729156;
34272
- if (unit.faction === "neutral" || unit.team === "neutral") return 16777028;
34273
- return 8947848;
34274
- }, [unit.faction, unit.team]);
34275
- useFrame((state) => {
34276
- if (groupRef.current && animationState === "idle") {
34277
- const y = position[1] + Math.sin(state.clock.elapsedTime * 2 + position[0]) * 0.05;
34278
- groupRef.current.position.y = y;
34279
- }
34280
- });
34281
- const healthPercent = useMemo(() => {
34282
- if (unit.health === void 0 || unit.maxHealth === void 0) return 1;
34283
- return Math.max(0, Math.min(1, unit.health / unit.maxHealth));
34284
- }, [unit.health, unit.maxHealth]);
34285
- const healthColor = useMemo(() => {
34286
- if (healthPercent > 0.5) return "#44aa44";
34287
- if (healthPercent > 0.25) return "#aaaa44";
34288
- return "#ff4444";
34289
- }, [healthPercent]);
34290
- return /* @__PURE__ */ jsxs(
34291
- "group",
34292
- {
34293
- ref: groupRef,
34294
- position,
34295
- onClick,
34296
- onPointerEnter: () => setIsHovered(true),
34297
- onPointerLeave: () => setIsHovered(false),
34298
- userData: { type: "unit", unitId: unit.id },
34299
- children: [
34300
- isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
34301
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
34302
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
34303
- ] }),
34304
- isHovered && !isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
34305
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
34306
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffffff", transparent: true, opacity: 0.5 })
34307
- ] }),
34308
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.1, 0], children: [
34309
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.25, 0.25, 0.1, 8] }),
34310
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: teamColor })
34311
- ] }),
34312
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.5, 0], children: [
34313
- /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.15, 0.5, 4, 8] }),
34314
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: teamColor })
34315
- ] }),
34316
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
34317
- /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
34318
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: teamColor })
34319
- ] }),
34320
- /* @__PURE__ */ jsxs("mesh", { position: [0, 1.3, 0], children: [
34321
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.06] }),
34322
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#333333" })
34323
- ] }),
34324
- /* @__PURE__ */ jsxs("mesh", { position: [-0.25 + 0.25 * healthPercent, 1.3, 0.01], children: [
34325
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * healthPercent, 0.04] }),
34326
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: healthColor })
34327
- ] }),
34328
- unit.name && /* @__PURE__ */ jsxs("mesh", { position: [0, 1.5, 0], children: [
34329
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.4, 0.1] }),
34330
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#000000", transparent: true, opacity: 0.5 })
34331
- ] })
34332
- ]
34333
- }
34334
- );
34335
- }
34336
- function UnitRenderer({
34337
- units,
34338
- cellSize = 1,
34339
- offsetX = 0,
34340
- offsetZ = 0,
34341
- selectedUnitId,
34342
- onUnitClick,
34343
- onAnimationStateChange,
34344
- animationSpeed = 1
34345
- }) {
34346
- const handleUnitClick = React87__default.useCallback(
34347
- (unit) => {
34348
- onUnitClick?.(unit);
34349
- },
34350
- [onUnitClick]
34351
- );
34352
- return /* @__PURE__ */ jsx("group", { children: units.map((unit) => {
34353
- const unitX = unit.x ?? unit.position?.x ?? 0;
34354
- const unitY = unit.z ?? unit.y ?? unit.position?.y ?? 0;
34355
- const x = (unitX - offsetX) * cellSize;
34356
- const z = (unitY - offsetZ) * cellSize;
34357
- const y = (unit.elevation ?? 0) * 0.1 + 0.5;
34358
- return /* @__PURE__ */ jsx(
34359
- UnitVisual,
34360
- {
34361
- unit,
34362
- position: [x, y, z],
34363
- isSelected: selectedUnitId === unit.id,
34364
- onClick: () => handleUnitClick(unit)
34365
- },
34366
- unit.id
34367
- );
34368
- }) });
34369
- }
34370
32064
  function WorldMapTemplate({
34371
32065
  entity,
34372
32066
  scale = 0.4,
@@ -34394,6 +32088,33 @@ function WorldMapTemplate({
34394
32088
  WorldMapTemplate.displayName = "WorldMapTemplate";
34395
32089
 
34396
32090
  // components/organisms/component-registry.generated.ts
32091
+ function lazyThree(name, loader) {
32092
+ const Lazy = React87__default.lazy(() => loader().then((m) => ({ default: m[name] })));
32093
+ function ThreeWrapper(props) {
32094
+ return React87__default.createElement(
32095
+ React87__default.Suspense,
32096
+ { fallback: null },
32097
+ React87__default.createElement(Lazy, props)
32098
+ );
32099
+ }
32100
+ ThreeWrapper.displayName = `Lazy(${name})`;
32101
+ return ThreeWrapper;
32102
+ }
32103
+ var Camera3D = lazyThree("Camera3D", () => import('@almadar/ui/components/organisms/game/three'));
32104
+ var Canvas3DErrorBoundary = lazyThree("Canvas3DErrorBoundary", () => import('@almadar/ui/components/organisms/game/three'));
32105
+ var Canvas3DLoadingState = lazyThree("Canvas3DLoadingState", () => import('@almadar/ui/components/organisms/game/three'));
32106
+ var FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/organisms/game/three'));
32107
+ var FeatureRenderer3D = lazyThree("FeatureRenderer3D", () => import('@almadar/ui/components/organisms/game/three'));
32108
+ var GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/organisms/game/three'));
32109
+ var GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/organisms/game/three'));
32110
+ var GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/organisms/game/three'));
32111
+ var GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/organisms/game/three'));
32112
+ var Lighting3D = lazyThree("Lighting3D", () => import('@almadar/ui/components/organisms/game/three'));
32113
+ var ModelLoader = lazyThree("ModelLoader", () => import('@almadar/ui/components/organisms/game/three'));
32114
+ var PhysicsObject3D = lazyThree("PhysicsObject3D", () => import('@almadar/ui/components/organisms/game/three'));
32115
+ var Scene3D = lazyThree("Scene3D", () => import('@almadar/ui/components/organisms/game/three'));
32116
+ var TileRenderer = lazyThree("TileRenderer", () => import('@almadar/ui/components/organisms/game/three'));
32117
+ var UnitRenderer = lazyThree("UnitRenderer", () => import('@almadar/ui/components/organisms/game/three'));
34397
32118
  var COMPONENT_REGISTRY = {
34398
32119
  "Accordion": AccordionPattern,
34399
32120
  "AccordionPattern": AccordionPattern,