@almadar/ui 2.15.8 → 2.15.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/components/atoms/ContentSection.d.ts +16 -0
  2. package/dist/components/atoms/SectionHeader.d.ts +14 -0
  3. package/dist/components/atoms/StatCard.d.ts +13 -0
  4. package/dist/components/atoms/index.d.ts +3 -0
  5. package/dist/components/atoms/svg/SvgBranch.d.ts +12 -0
  6. package/dist/components/atoms/svg/SvgConnection.d.ts +13 -0
  7. package/dist/components/atoms/svg/SvgFlow.d.ts +10 -0
  8. package/dist/components/atoms/svg/SvgGrid.d.ts +14 -0
  9. package/dist/components/atoms/svg/SvgLobe.d.ts +13 -0
  10. package/dist/components/atoms/svg/SvgMesh.d.ts +12 -0
  11. package/dist/components/atoms/svg/SvgMorph.d.ts +11 -0
  12. package/dist/components/atoms/svg/SvgNode.d.ts +12 -0
  13. package/dist/components/atoms/svg/SvgPulse.d.ts +12 -0
  14. package/dist/components/atoms/svg/SvgRing.d.ts +13 -0
  15. package/dist/components/atoms/svg/SvgShield.d.ts +11 -0
  16. package/dist/components/atoms/svg/SvgStack.d.ts +13 -0
  17. package/dist/components/atoms/svg/index.d.ts +12 -0
  18. package/dist/{chunk-ACUO2BBW.js → components/index.cjs} +24831 -16747
  19. package/dist/components/index.js +37754 -889
  20. package/dist/components/molecules/AnimatedCounter.d.ts +18 -0
  21. package/dist/components/molecules/ArticleSection.d.ts +18 -0
  22. package/dist/components/molecules/CTABanner.d.ts +31 -0
  23. package/dist/components/molecules/CaseStudyCard.d.ts +24 -0
  24. package/dist/components/molecules/CodeExample.d.ts +23 -0
  25. package/dist/components/molecules/CommunityLinks.d.ts +25 -0
  26. package/dist/components/molecules/DocBreadcrumb.d.ts +20 -0
  27. package/dist/components/molecules/DocCodeBlock.d.ts +13 -0
  28. package/dist/components/molecules/DocPagination.d.ts +14 -0
  29. package/dist/components/molecules/DocSearch.d.ts +15 -0
  30. package/dist/components/molecules/DocSidebar.d.ts +24 -0
  31. package/dist/components/molecules/DocTOC.d.ts +24 -0
  32. package/dist/components/molecules/FeatureCard.d.ts +26 -0
  33. package/dist/components/molecules/FeatureGrid.d.ts +19 -0
  34. package/dist/components/molecules/GradientDivider.d.ts +14 -0
  35. package/dist/components/molecules/HeroSection.d.ts +36 -0
  36. package/dist/components/molecules/InstallBox.d.ts +16 -0
  37. package/dist/components/molecules/MarketingFooter.d.ts +27 -0
  38. package/dist/components/molecules/PricingCard.d.ts +21 -0
  39. package/dist/components/molecules/PricingGrid.d.ts +13 -0
  40. package/dist/components/molecules/PullQuote.d.ts +14 -0
  41. package/dist/components/molecules/ServiceCatalog.d.ts +19 -0
  42. package/dist/components/molecules/ShowcaseCard.d.ts +20 -0
  43. package/dist/components/molecules/SocialProof.d.ts +25 -0
  44. package/dist/components/molecules/SplitSection.d.ts +21 -0
  45. package/dist/components/molecules/StatsGrid.d.ts +17 -0
  46. package/dist/components/molecules/StepFlow.d.ts +20 -0
  47. package/dist/components/molecules/TagCloud.d.ts +18 -0
  48. package/dist/components/molecules/TeamCard.d.ts +18 -0
  49. package/dist/components/molecules/index.d.ts +19 -0
  50. package/dist/components/molecules/svg/AIGenerates.d.ts +7 -0
  51. package/dist/components/molecules/svg/ClosedCircuit.d.ts +7 -0
  52. package/dist/components/molecules/svg/CommunityOwnership.d.ts +7 -0
  53. package/dist/components/molecules/svg/CompileAnywhere.d.ts +7 -0
  54. package/dist/components/molecules/svg/ComposableModels.d.ts +7 -0
  55. package/dist/components/molecules/svg/DescribeProveDeploy.d.ts +7 -0
  56. package/dist/components/molecules/svg/DomainGrid.d.ts +7 -0
  57. package/dist/components/molecules/svg/EventBus.d.ts +7 -0
  58. package/dist/components/molecules/svg/OrbitalUnit.d.ts +7 -0
  59. package/dist/components/molecules/svg/PlanVerifyRemember.d.ts +7 -0
  60. package/dist/components/molecules/svg/ProveCorrect.d.ts +7 -0
  61. package/dist/components/molecules/svg/ServiceLayers.d.ts +7 -0
  62. package/dist/components/molecules/svg/SharedReality.d.ts +7 -0
  63. package/dist/components/molecules/svg/StandardLibrary.d.ts +7 -0
  64. package/dist/components/molecules/svg/StateMachine.d.ts +7 -0
  65. package/dist/components/molecules/svg/WorldModel.d.ts +7 -0
  66. package/dist/components/molecules/svg/index.d.ts +16 -0
  67. package/dist/components/organisms/CaseStudyOrganism.d.ts +19 -0
  68. package/dist/components/organisms/FeatureGridOrganism.d.ts +20 -0
  69. package/dist/components/organisms/HeroOrganism.d.ts +18 -0
  70. package/dist/components/organisms/PricingOrganism.d.ts +19 -0
  71. package/dist/components/organisms/ShowcaseOrganism.d.ts +20 -0
  72. package/dist/components/organisms/StatsOrganism.d.ts +17 -0
  73. package/dist/components/organisms/StepFlowOrganism.d.ts +20 -0
  74. package/dist/components/organisms/TeamOrganism.d.ts +18 -0
  75. package/dist/components/organisms/game/three/index.cjs +2525 -0
  76. package/dist/components/organisms/game/three/index.js +1795 -50
  77. package/dist/components/organisms/index.d.ts +9 -0
  78. package/dist/components/organisms/marketing-types.d.ts +87 -0
  79. package/dist/components/templates/AboutPageTemplate.d.ts +26 -0
  80. package/dist/components/templates/FeatureDetailPageTemplate.d.ts +27 -0
  81. package/dist/components/templates/LandingPageTemplate.d.ts +31 -0
  82. package/dist/components/templates/PricingPageTemplate.d.ts +26 -0
  83. package/dist/components/templates/index.d.ts +4 -0
  84. package/dist/context/index.cjs +550 -0
  85. package/dist/context/index.js +420 -6
  86. package/dist/docs/index.cjs +4015 -0
  87. package/dist/docs/index.d.cts +412 -0
  88. package/dist/docs/index.d.ts +29 -0
  89. package/dist/docs/index.js +3977 -0
  90. package/dist/hooks/index.cjs +2606 -0
  91. package/dist/hooks/index.js +2535 -8
  92. package/dist/illustrations/index.cjs +3004 -0
  93. package/dist/illustrations/index.d.cts +261 -0
  94. package/dist/illustrations/index.d.ts +35 -0
  95. package/dist/illustrations/index.js +2971 -0
  96. package/dist/{chunk-XL7WB2O5.js → lib/index.cjs} +454 -274
  97. package/dist/lib/index.js +1407 -3
  98. package/dist/locales/index.cjs +340 -0
  99. package/dist/locales/index.js +105 -2
  100. package/dist/marketing/index.cjs +4680 -0
  101. package/dist/marketing/index.d.cts +831 -0
  102. package/dist/marketing/index.d.ts +62 -0
  103. package/dist/marketing/index.js +4623 -0
  104. package/dist/providers/index.cjs +4811 -0
  105. package/dist/providers/index.js +4765 -11
  106. package/dist/{chunk-K2D5D3WK.js → renderer/index.cjs} +101 -42
  107. package/dist/renderer/index.js +1036 -2
  108. package/dist/runtime/index.cjs +4400 -0
  109. package/dist/runtime/index.js +3615 -19
  110. package/dist/{chunk-N7MVUW4R.js → stores/index.cjs} +24 -1
  111. package/dist/stores/index.js +194 -2
  112. package/dist/tsup.config.d.ts +2 -1
  113. package/package.json +27 -12
  114. package/tailwind-preset.cjs +9 -0
  115. package/themes/almadar-website.css +195 -0
  116. package/themes/index.css +23 -20
  117. package/dist/chunk-3HJHHULT.js +0 -93
  118. package/dist/chunk-3JGAROCW.js +0 -149
  119. package/dist/chunk-4N3BAPDB.js +0 -1667
  120. package/dist/chunk-CDIOHSKG.js +0 -661
  121. package/dist/chunk-DKQN5FVU.js +0 -279
  122. package/dist/chunk-JJHCOO34.js +0 -375
  123. package/dist/chunk-PKBMQBKP.js +0 -5
  124. package/dist/chunk-QIABKRCN.js +0 -107
  125. package/dist/chunk-SD3KVCY6.js +0 -1465
  126. package/dist/chunk-YXZM3WCF.js +0 -222
@@ -1,1667 +0,0 @@
1
- import { useEmitEvent } from './chunk-YXZM3WCF.js';
2
- import { __publicField } from './chunk-PKBMQBKP.js';
3
- import React8, { forwardRef, useRef, useEffect, useImperativeHandle, Component, useState, useCallback, useMemo } from 'react';
4
- import { useThree, useFrame } from '@react-three/fiber';
5
- import { OrbitControls } from '@react-three/drei';
6
- import * as THREE from 'three';
7
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
9
- import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
10
- import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
11
-
12
- var Camera3D = forwardRef(
13
- ({
14
- mode = "isometric",
15
- position = [10, 10, 10],
16
- target = [0, 0, 0],
17
- zoom = 1,
18
- fov = 45,
19
- enableOrbit = true,
20
- minDistance = 2,
21
- maxDistance = 100,
22
- onChange
23
- }, ref) => {
24
- const { camera, set, viewport } = useThree();
25
- const controlsRef = useRef(null);
26
- const initialPosition = useRef(new THREE.Vector3(...position));
27
- const initialTarget = useRef(new THREE.Vector3(...target));
28
- useEffect(() => {
29
- let newCamera;
30
- if (mode === "isometric") {
31
- const aspect = viewport.aspect;
32
- const size = 10 / zoom;
33
- newCamera = new THREE.OrthographicCamera(
34
- -size * aspect,
35
- size * aspect,
36
- size,
37
- -size,
38
- 0.1,
39
- 1e3
40
- );
41
- } else {
42
- newCamera = new THREE.PerspectiveCamera(fov, viewport.aspect, 0.1, 1e3);
43
- }
44
- newCamera.position.copy(initialPosition.current);
45
- newCamera.lookAt(initialTarget.current);
46
- set({ camera: newCamera });
47
- if (mode === "top-down") {
48
- newCamera.position.set(0, 20 / zoom, 0);
49
- newCamera.lookAt(0, 0, 0);
50
- }
51
- return () => {
52
- };
53
- }, [mode, fov, zoom, viewport.aspect, set]);
54
- useFrame(() => {
55
- if (onChange) {
56
- onChange(camera);
57
- }
58
- });
59
- useImperativeHandle(ref, () => ({
60
- getCamera: () => camera,
61
- setPosition: (x, y, z) => {
62
- camera.position.set(x, y, z);
63
- if (controlsRef.current) {
64
- controlsRef.current.update();
65
- }
66
- },
67
- lookAt: (x, y, z) => {
68
- camera.lookAt(x, y, z);
69
- if (controlsRef.current) {
70
- controlsRef.current.target.set(x, y, z);
71
- controlsRef.current.update();
72
- }
73
- },
74
- reset: () => {
75
- camera.position.copy(initialPosition.current);
76
- camera.lookAt(initialTarget.current);
77
- if (controlsRef.current) {
78
- controlsRef.current.target.copy(initialTarget.current);
79
- controlsRef.current.update();
80
- }
81
- },
82
- getViewBounds: () => {
83
- const min = new THREE.Vector3(-10, -10, -10);
84
- const max = new THREE.Vector3(10, 10, 10);
85
- return { min, max };
86
- }
87
- }));
88
- const maxPolarAngle = mode === "top-down" ? 0.1 : Math.PI / 2 - 0.1;
89
- return /* @__PURE__ */ jsx(
90
- OrbitControls,
91
- {
92
- ref: controlsRef,
93
- camera,
94
- enabled: enableOrbit,
95
- target: initialTarget.current,
96
- minDistance,
97
- maxDistance,
98
- maxPolarAngle,
99
- enableDamping: true,
100
- dampingFactor: 0.05
101
- }
102
- );
103
- }
104
- );
105
- Camera3D.displayName = "Camera3D";
106
- var Canvas3DErrorBoundary = class extends Component {
107
- constructor(props) {
108
- super(props);
109
- __publicField(this, "handleReset", () => {
110
- this.setState({
111
- hasError: false,
112
- error: null,
113
- errorInfo: null
114
- });
115
- this.props.onReset?.();
116
- });
117
- this.state = {
118
- hasError: false,
119
- error: null,
120
- errorInfo: null
121
- };
122
- }
123
- static getDerivedStateFromError(error) {
124
- return {
125
- hasError: true,
126
- error,
127
- errorInfo: null
128
- };
129
- }
130
- componentDidCatch(error, errorInfo) {
131
- this.setState({ errorInfo });
132
- this.props.onError?.(error, errorInfo);
133
- console.error("[Canvas3DErrorBoundary] Error caught:", error);
134
- console.error("[Canvas3DErrorBoundary] Component stack:", errorInfo.componentStack);
135
- }
136
- render() {
137
- if (this.state.hasError) {
138
- if (this.props.fallback) {
139
- return this.props.fallback;
140
- }
141
- return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
142
- /* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
143
- /* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
144
- /* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
145
- this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
146
- /* @__PURE__ */ jsx("summary", { children: "Error Details" }),
147
- /* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
148
- this.state.error.message,
149
- "\n",
150
- this.state.error.stack
151
- ] }),
152
- this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
153
- ] }),
154
- /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
155
- /* @__PURE__ */ jsx(
156
- "button",
157
- {
158
- className: "error__button error__button--primary",
159
- onClick: this.handleReset,
160
- children: "Try Again"
161
- }
162
- ),
163
- /* @__PURE__ */ jsx(
164
- "button",
165
- {
166
- className: "error__button error__button--secondary",
167
- onClick: () => window.location.reload(),
168
- children: "Reload Page"
169
- }
170
- )
171
- ] })
172
- ] }) });
173
- }
174
- return this.props.children;
175
- }
176
- };
177
- function Canvas3DLoadingState({
178
- progress = 0,
179
- loaded = 0,
180
- total = 0,
181
- message = "Loading 3D Scene...",
182
- details,
183
- showSpinner = true,
184
- className
185
- }) {
186
- const clampedProgress = Math.max(0, Math.min(100, progress));
187
- const hasProgress = total > 0;
188
- return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
189
- /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
190
- showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
191
- /* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
192
- /* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
193
- ] }),
194
- /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
195
- details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
196
- hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
197
- /* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
198
- "div",
199
- {
200
- className: "progress__fill",
201
- style: { width: `${clampedProgress}%` }
202
- }
203
- ) }),
204
- /* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
205
- /* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
206
- clampedProgress,
207
- "%"
208
- ] }),
209
- /* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
210
- "(",
211
- loaded,
212
- "/",
213
- total,
214
- ")"
215
- ] })
216
- ] })
217
- ] })
218
- ] }),
219
- /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
220
- ] });
221
- }
222
- var DEFAULT_FEATURE_CONFIGS = {
223
- tree: { color: 2263842, height: 1.5, scale: 1, geometry: "tree" },
224
- rock: { color: 8421504, height: 0.5, scale: 0.8, geometry: "rock" },
225
- bush: { color: 3329330, height: 0.4, scale: 0.6, geometry: "bush" },
226
- house: { color: 9127187, height: 1.2, scale: 1.2, geometry: "house" },
227
- tower: { color: 6908265, height: 2.5, scale: 1, geometry: "tower" },
228
- wall: { color: 8421504, height: 1, scale: 1, geometry: "wall" },
229
- mountain: { color: 5597999, height: 2, scale: 1.5, geometry: "mountain" },
230
- hill: { color: 7048739, height: 0.8, scale: 1.2, geometry: "hill" },
231
- water: { color: 4491468, height: 0.1, scale: 1, geometry: "water" },
232
- chest: { color: 16766720, height: 0.3, scale: 0.4, geometry: "chest" },
233
- sign: { color: 9127187, height: 0.8, scale: 0.3, geometry: "sign" },
234
- portal: { color: 10040012, height: 1.5, scale: 1, geometry: "portal" }
235
- };
236
- function TreeFeature({ height, color }) {
237
- return /* @__PURE__ */ jsxs(Fragment, { children: [
238
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.3, 0], children: [
239
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.08, 0.1, height * 0.6, 6] }),
240
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
241
- ] }),
242
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.7, 0], children: [
243
- /* @__PURE__ */ jsx("coneGeometry", { args: [0.4, height * 0.5, 8] }),
244
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
245
- ] }),
246
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.9, 0], children: [
247
- /* @__PURE__ */ jsx("coneGeometry", { args: [0.3, height * 0.4, 8] }),
248
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
249
- ] }),
250
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 1.05, 0], children: [
251
- /* @__PURE__ */ jsx("coneGeometry", { args: [0.15, height * 0.25, 8] }),
252
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
253
- ] })
254
- ] });
255
- }
256
- function RockFeature({ height, color }) {
257
- return /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.4, 0], children: [
258
- /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [height * 0.5, 0] }),
259
- /* @__PURE__ */ jsx("meshStandardMaterial", { color, roughness: 0.9 })
260
- ] });
261
- }
262
- function BushFeature({ height, color }) {
263
- return /* @__PURE__ */ jsxs(Fragment, { children: [
264
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.3, 0], children: [
265
- /* @__PURE__ */ jsx("sphereGeometry", { args: [height * 0.4, 8, 8] }),
266
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
267
- ] }),
268
- /* @__PURE__ */ jsxs("mesh", { position: [0.1, height * 0.4, 0.1], children: [
269
- /* @__PURE__ */ jsx("sphereGeometry", { args: [height * 0.25, 8, 8] }),
270
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
271
- ] })
272
- ] });
273
- }
274
- function HouseFeature({ height, color }) {
275
- return /* @__PURE__ */ jsxs(Fragment, { children: [
276
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.4, 0], children: [
277
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, height * 0.8, 0.8] }),
278
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 13808780 })
279
- ] }),
280
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.9, 0], children: [
281
- /* @__PURE__ */ jsx("coneGeometry", { args: [0.6, height * 0.4, 4] }),
282
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
283
- ] }),
284
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.25, 0.41], children: [
285
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.25, height * 0.5] }),
286
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 4863784 })
287
- ] })
288
- ] });
289
- }
290
- function TowerFeature({ height, color }) {
291
- return /* @__PURE__ */ jsxs(Fragment, { children: [
292
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.5, 0], children: [
293
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.35, height, 8] }),
294
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
295
- ] }),
296
- /* @__PURE__ */ jsxs("mesh", { position: [0, height + 0.05, 0], children: [
297
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.35, 0.35, 0.1, 8] }),
298
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
299
- ] })
300
- ] });
301
- }
302
- function ChestFeature({ height, color }) {
303
- return /* @__PURE__ */ jsxs(Fragment, { children: [
304
- /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.5, 0], children: [
305
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.3, height, 0.2] }),
306
- /* @__PURE__ */ jsx("meshStandardMaterial", { color, metalness: 0.6, roughness: 0.3 })
307
- ] }),
308
- /* @__PURE__ */ jsxs("mesh", { position: [0, height + 0.05, 0], children: [
309
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.15, 0.15, 0.3, 8, 1, false, 0, Math.PI] }),
310
- /* @__PURE__ */ jsx("meshStandardMaterial", { color, metalness: 0.6, roughness: 0.3 })
311
- ] })
312
- ] });
313
- }
314
- function DefaultFeature({ height, color }) {
315
- return /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.5, 0], children: [
316
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.5, height, 0.5] }),
317
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
318
- ] });
319
- }
320
- function FeatureVisual({
321
- feature,
322
- position,
323
- isSelected,
324
- onClick,
325
- onHover
326
- }) {
327
- const config = DEFAULT_FEATURE_CONFIGS[feature.type] || {
328
- color: 8947848,
329
- height: 0.5,
330
- scale: 1,
331
- geometry: "default"
332
- };
333
- const color = feature.color ? parseInt(feature.color.replace("#", ""), 16) : config.color;
334
- const renderGeometry = () => {
335
- switch (config.geometry) {
336
- case "tree":
337
- return /* @__PURE__ */ jsx(TreeFeature, { height: config.height, color });
338
- case "rock":
339
- return /* @__PURE__ */ jsx(RockFeature, { height: config.height, color });
340
- case "bush":
341
- return /* @__PURE__ */ jsx(BushFeature, { height: config.height, color });
342
- case "house":
343
- return /* @__PURE__ */ jsx(HouseFeature, { height: config.height, color });
344
- case "tower":
345
- return /* @__PURE__ */ jsx(TowerFeature, { height: config.height, color });
346
- case "chest":
347
- return /* @__PURE__ */ jsx(ChestFeature, { height: config.height, color });
348
- default:
349
- return /* @__PURE__ */ jsx(DefaultFeature, { height: config.height, color });
350
- }
351
- };
352
- return /* @__PURE__ */ jsxs(
353
- "group",
354
- {
355
- position,
356
- scale: config.scale,
357
- onClick,
358
- onPointerEnter: () => onHover(true),
359
- onPointerLeave: () => onHover(false),
360
- userData: { type: "feature", featureId: feature.id, featureType: feature.type },
361
- children: [
362
- isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
363
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
364
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
365
- ] }),
366
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.01, 0], rotation: [-Math.PI / 2, 0, 0], children: [
367
- /* @__PURE__ */ jsx("circleGeometry", { args: [0.35, 16] }),
368
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#000000", transparent: true, opacity: 0.2 })
369
- ] }),
370
- renderGeometry()
371
- ]
372
- }
373
- );
374
- }
375
- function FeatureRenderer({
376
- features,
377
- cellSize = 1,
378
- offsetX = 0,
379
- offsetZ = 0,
380
- onFeatureClick,
381
- onFeatureHover,
382
- selectedFeatureIds = [],
383
- featureColors
384
- }) {
385
- return /* @__PURE__ */ jsx("group", { children: features.map((feature) => {
386
- const x = (feature.x - offsetX) * cellSize;
387
- const z = ((feature.z ?? feature.y ?? 0) - offsetZ) * cellSize;
388
- const y = (feature.elevation ?? 0) * 0.1;
389
- const isSelected = feature.id ? selectedFeatureIds.includes(feature.id) : false;
390
- return /* @__PURE__ */ jsx(
391
- FeatureVisual,
392
- {
393
- feature,
394
- position: [x, y, z],
395
- isSelected,
396
- onClick: () => onFeatureClick?.(feature),
397
- onHover: (hovered) => onFeatureHover?.(hovered ? feature : null)
398
- },
399
- feature.id ?? `feature-${feature.x}-${feature.y}`
400
- );
401
- }) });
402
- }
403
- function detectAssetRoot(modelUrl) {
404
- const idx = modelUrl.indexOf("/3d/");
405
- if (idx !== -1) {
406
- return modelUrl.substring(0, idx + 4);
407
- }
408
- return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
409
- }
410
- function useGLTFModel(url) {
411
- const [model, setModel] = useState(null);
412
- const [isLoading, setIsLoading] = useState(false);
413
- const [error, setError] = useState(null);
414
- useEffect(() => {
415
- if (!url) {
416
- setModel(null);
417
- return;
418
- }
419
- setIsLoading(true);
420
- setError(null);
421
- const assetRoot = detectAssetRoot(url);
422
- const loader = new GLTFLoader$1();
423
- loader.setResourcePath(assetRoot);
424
- loader.load(
425
- url,
426
- (gltf) => {
427
- setModel(gltf.scene);
428
- setIsLoading(false);
429
- },
430
- void 0,
431
- (err) => {
432
- setError(err instanceof Error ? err : new Error(String(err)));
433
- setIsLoading(false);
434
- }
435
- );
436
- }, [url]);
437
- return { model, isLoading, error };
438
- }
439
- function FeatureModel({
440
- feature,
441
- position,
442
- isSelected,
443
- onClick,
444
- onHover
445
- }) {
446
- const groupRef = useRef(null);
447
- const { model: loadedModel, isLoading } = useGLTFModel(feature.assetUrl);
448
- const model = useMemo(() => {
449
- if (!loadedModel) return null;
450
- const cloned = loadedModel.clone();
451
- cloned.scale.setScalar(0.3);
452
- cloned.traverse((child) => {
453
- if (child instanceof THREE.Mesh) {
454
- child.castShadow = true;
455
- child.receiveShadow = true;
456
- }
457
- });
458
- return cloned;
459
- }, [loadedModel]);
460
- useFrame((state) => {
461
- if (groupRef.current) {
462
- const featureRotation = feature.rotation;
463
- const baseRotation = featureRotation !== void 0 ? featureRotation * Math.PI / 180 - Math.PI / 4 : -Math.PI / 4;
464
- const wobble = isSelected ? Math.sin(state.clock.elapsedTime * 2) * 0.1 : 0;
465
- groupRef.current.rotation.y = baseRotation + wobble;
466
- }
467
- });
468
- if (isLoading) {
469
- return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
470
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
471
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
472
- ] }) });
473
- }
474
- if (!model && !feature.assetUrl) {
475
- return /* @__PURE__ */ jsxs(
476
- "group",
477
- {
478
- position,
479
- onClick,
480
- onPointerEnter: () => onHover(true),
481
- onPointerLeave: () => onHover(false),
482
- userData: { type: "feature", featureId: feature.id, featureType: feature.type },
483
- children: [
484
- isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
485
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
486
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
487
- ] }),
488
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.5, 0], children: [
489
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.4, 0.4, 0.4] }),
490
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8947848 })
491
- ] })
492
- ]
493
- }
494
- );
495
- }
496
- return /* @__PURE__ */ jsxs(
497
- "group",
498
- {
499
- ref: groupRef,
500
- position,
501
- onClick,
502
- onPointerEnter: () => onHover(true),
503
- onPointerLeave: () => onHover(false),
504
- userData: { type: "feature", featureId: feature.id, featureType: feature.type },
505
- children: [
506
- isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
507
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
508
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
509
- ] }),
510
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.01, 0], rotation: [-Math.PI / 2, 0, 0], children: [
511
- /* @__PURE__ */ jsx("circleGeometry", { args: [0.35, 16] }),
512
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#000000", transparent: true, opacity: 0.2 })
513
- ] }),
514
- model && /* @__PURE__ */ jsx("primitive", { object: model })
515
- ]
516
- }
517
- );
518
- }
519
- function FeatureRenderer3D({
520
- features,
521
- cellSize = 1,
522
- offsetX = 0,
523
- offsetZ = 0,
524
- onFeatureClick,
525
- onFeatureHover,
526
- selectedFeatureIds = []
527
- }) {
528
- return /* @__PURE__ */ jsx("group", { children: features.map((feature) => {
529
- const x = (feature.x - offsetX) * cellSize;
530
- const z = ((feature.z ?? feature.y ?? 0) - offsetZ) * cellSize;
531
- const y = (feature.elevation ?? 0) * 0.1;
532
- const isSelected = feature.id ? selectedFeatureIds.includes(feature.id) : false;
533
- return /* @__PURE__ */ jsx(
534
- FeatureModel,
535
- {
536
- feature,
537
- position: [x, y, z],
538
- isSelected,
539
- onClick: () => onFeatureClick?.(feature),
540
- onHover: (hovered) => onFeatureHover?.(hovered ? feature : null)
541
- },
542
- feature.id ?? `feature-${feature.x}-${feature.y}`
543
- );
544
- }) });
545
- }
546
- function preloadFeatures(urls) {
547
- urls.forEach((url) => {
548
- if (url) {
549
- const loader = new GLTFLoader$1();
550
- loader.setResourcePath(detectAssetRoot(url));
551
- loader.load(url, () => {
552
- console.log("[FeatureRenderer3D] Preloaded:", url);
553
- });
554
- }
555
- });
556
- }
557
- function detectAssetRoot2(modelUrl) {
558
- const idx = modelUrl.indexOf("/3d/");
559
- if (idx !== -1) {
560
- return modelUrl.substring(0, idx + 4);
561
- }
562
- return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
563
- }
564
- function createGLTFLoaderForUrl(url) {
565
- const loader = new GLTFLoader();
566
- loader.setResourcePath(detectAssetRoot2(url));
567
- return loader;
568
- }
569
- var AssetLoader = class {
570
- constructor() {
571
- __publicField(this, "objLoader");
572
- __publicField(this, "textureLoader");
573
- __publicField(this, "modelCache");
574
- __publicField(this, "textureCache");
575
- __publicField(this, "loadingPromises");
576
- this.objLoader = new OBJLoader();
577
- this.textureLoader = new THREE.TextureLoader();
578
- this.modelCache = /* @__PURE__ */ new Map();
579
- this.textureCache = /* @__PURE__ */ new Map();
580
- this.loadingPromises = /* @__PURE__ */ new Map();
581
- }
582
- /**
583
- * Load a GLB/GLTF model
584
- * @param url - URL to the .glb or .gltf file
585
- * @returns Promise with loaded model scene and animations
586
- */
587
- async loadModel(url) {
588
- if (this.modelCache.has(url)) {
589
- return this.modelCache.get(url);
590
- }
591
- if (this.loadingPromises.has(url)) {
592
- return this.loadingPromises.get(url);
593
- }
594
- const loader = createGLTFLoaderForUrl(url);
595
- const loadPromise = loader.loadAsync(url).then((gltf) => {
596
- const result = {
597
- scene: gltf.scene,
598
- animations: gltf.animations || []
599
- };
600
- this.modelCache.set(url, result);
601
- this.loadingPromises.delete(url);
602
- return result;
603
- }).catch((error) => {
604
- this.loadingPromises.delete(url);
605
- throw new Error(`Failed to load model ${url}: ${error.message}`);
606
- });
607
- this.loadingPromises.set(url, loadPromise);
608
- return loadPromise;
609
- }
610
- /**
611
- * Load an OBJ model (fallback for non-GLB assets)
612
- * @param url - URL to the .obj file
613
- * @returns Promise with loaded object group
614
- */
615
- async loadOBJ(url) {
616
- if (this.modelCache.has(url)) {
617
- return this.modelCache.get(url).scene;
618
- }
619
- if (this.loadingPromises.has(url)) {
620
- const result = await this.loadingPromises.get(url);
621
- return result.scene;
622
- }
623
- const loadPromise = this.objLoader.loadAsync(url).then((group) => {
624
- const result = {
625
- scene: group,
626
- animations: []
627
- };
628
- this.modelCache.set(url, result);
629
- this.loadingPromises.delete(url);
630
- return result;
631
- }).catch((error) => {
632
- this.loadingPromises.delete(url);
633
- throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
634
- });
635
- this.loadingPromises.set(url, loadPromise);
636
- return (await loadPromise).scene;
637
- }
638
- /**
639
- * Load a texture
640
- * @param url - URL to the texture image
641
- * @returns Promise with loaded texture
642
- */
643
- async loadTexture(url) {
644
- if (this.textureCache.has(url)) {
645
- return this.textureCache.get(url);
646
- }
647
- if (this.loadingPromises.has(`texture:${url}`)) {
648
- return this.loadingPromises.get(`texture:${url}`);
649
- }
650
- const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
651
- texture.colorSpace = THREE.SRGBColorSpace;
652
- this.textureCache.set(url, texture);
653
- this.loadingPromises.delete(`texture:${url}`);
654
- return texture;
655
- }).catch((error) => {
656
- this.loadingPromises.delete(`texture:${url}`);
657
- throw new Error(`Failed to load texture ${url}: ${error.message}`);
658
- });
659
- this.loadingPromises.set(`texture:${url}`, loadPromise);
660
- return loadPromise;
661
- }
662
- /**
663
- * Preload multiple assets
664
- * @param urls - Array of asset URLs to preload
665
- * @returns Promise that resolves when all assets are loaded
666
- */
667
- async preload(urls) {
668
- const promises = urls.map((url) => {
669
- if (url.endsWith(".glb") || url.endsWith(".gltf")) {
670
- return this.loadModel(url).catch(() => null);
671
- } else if (url.endsWith(".obj")) {
672
- return this.loadOBJ(url).catch(() => null);
673
- } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
674
- return this.loadTexture(url).catch(() => null);
675
- }
676
- return Promise.resolve(null);
677
- });
678
- await Promise.all(promises);
679
- }
680
- /**
681
- * Check if a model is cached
682
- * @param url - Model URL
683
- */
684
- hasModel(url) {
685
- return this.modelCache.has(url);
686
- }
687
- /**
688
- * Check if a texture is cached
689
- * @param url - Texture URL
690
- */
691
- hasTexture(url) {
692
- return this.textureCache.has(url);
693
- }
694
- /**
695
- * Get cached model (throws if not cached)
696
- * @param url - Model URL
697
- */
698
- getModel(url) {
699
- const model = this.modelCache.get(url);
700
- if (!model) {
701
- throw new Error(`Model ${url} not in cache`);
702
- }
703
- return model;
704
- }
705
- /**
706
- * Get cached texture (throws if not cached)
707
- * @param url - Texture URL
708
- */
709
- getTexture(url) {
710
- const texture = this.textureCache.get(url);
711
- if (!texture) {
712
- throw new Error(`Texture ${url} not in cache`);
713
- }
714
- return texture;
715
- }
716
- /**
717
- * Clear all caches
718
- */
719
- clearCache() {
720
- this.textureCache.forEach((texture) => {
721
- texture.dispose();
722
- });
723
- this.modelCache.forEach((model) => {
724
- model.scene.traverse((child) => {
725
- if (child instanceof THREE.Mesh) {
726
- child.geometry.dispose();
727
- if (Array.isArray(child.material)) {
728
- child.material.forEach((m) => m.dispose());
729
- } else {
730
- child.material.dispose();
731
- }
732
- }
733
- });
734
- });
735
- this.modelCache.clear();
736
- this.textureCache.clear();
737
- this.loadingPromises.clear();
738
- }
739
- /**
740
- * Get cache statistics
741
- */
742
- getStats() {
743
- return {
744
- models: this.modelCache.size,
745
- textures: this.textureCache.size,
746
- loading: this.loadingPromises.size
747
- };
748
- }
749
- };
750
- var assetLoader = new AssetLoader();
751
- function useAssetLoader(options = {}) {
752
- const { preloadUrls = [], loader: customLoader } = options;
753
- const loaderRef = useRef(customLoader || new AssetLoader());
754
- const [state, setState] = useState({
755
- isLoading: false,
756
- progress: 0,
757
- loaded: 0,
758
- total: 0,
759
- errors: []
760
- });
761
- useEffect(() => {
762
- if (preloadUrls.length > 0) {
763
- preload(preloadUrls);
764
- }
765
- }, []);
766
- const updateProgress = useCallback((loaded, total) => {
767
- setState((prev) => ({
768
- ...prev,
769
- loaded,
770
- total,
771
- progress: total > 0 ? Math.round(loaded / total * 100) : 0
772
- }));
773
- }, []);
774
- const loadModel = useCallback(
775
- async (url) => {
776
- setState((prev) => ({ ...prev, isLoading: true }));
777
- try {
778
- const model = await loaderRef.current.loadModel(url);
779
- setState((prev) => ({
780
- ...prev,
781
- isLoading: false,
782
- loaded: prev.loaded + 1
783
- }));
784
- return model;
785
- } catch (error) {
786
- const errorMsg = error instanceof Error ? error.message : String(error);
787
- setState((prev) => ({
788
- ...prev,
789
- isLoading: false,
790
- errors: [...prev.errors, errorMsg]
791
- }));
792
- throw error;
793
- }
794
- },
795
- []
796
- );
797
- const loadOBJ = useCallback(
798
- async (url) => {
799
- setState((prev) => ({ ...prev, isLoading: true }));
800
- try {
801
- const model = await loaderRef.current.loadOBJ(url);
802
- setState((prev) => ({
803
- ...prev,
804
- isLoading: false,
805
- loaded: prev.loaded + 1
806
- }));
807
- return model;
808
- } catch (error) {
809
- const errorMsg = error instanceof Error ? error.message : String(error);
810
- setState((prev) => ({
811
- ...prev,
812
- isLoading: false,
813
- errors: [...prev.errors, errorMsg]
814
- }));
815
- throw error;
816
- }
817
- },
818
- []
819
- );
820
- const loadTexture = useCallback(
821
- async (url) => {
822
- setState((prev) => ({ ...prev, isLoading: true }));
823
- try {
824
- const texture = await loaderRef.current.loadTexture(url);
825
- setState((prev) => ({
826
- ...prev,
827
- isLoading: false,
828
- loaded: prev.loaded + 1
829
- }));
830
- return texture;
831
- } catch (error) {
832
- const errorMsg = error instanceof Error ? error.message : String(error);
833
- setState((prev) => ({
834
- ...prev,
835
- isLoading: false,
836
- errors: [...prev.errors, errorMsg]
837
- }));
838
- throw error;
839
- }
840
- },
841
- []
842
- );
843
- const preload = useCallback(
844
- async (urls) => {
845
- setState((prev) => ({
846
- ...prev,
847
- isLoading: true,
848
- total: urls.length,
849
- loaded: 0,
850
- errors: []
851
- }));
852
- let completed = 0;
853
- const errors = [];
854
- await Promise.all(
855
- urls.map(async (url) => {
856
- try {
857
- if (url.endsWith(".glb") || url.endsWith(".gltf")) {
858
- await loaderRef.current.loadModel(url);
859
- } else if (url.endsWith(".obj")) {
860
- await loaderRef.current.loadOBJ(url);
861
- } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
862
- await loaderRef.current.loadTexture(url);
863
- }
864
- completed++;
865
- updateProgress(completed, urls.length);
866
- } catch (error) {
867
- const errorMsg = error instanceof Error ? error.message : String(error);
868
- errors.push(`${url}: ${errorMsg}`);
869
- completed++;
870
- updateProgress(completed, urls.length);
871
- }
872
- })
873
- );
874
- setState((prev) => ({
875
- ...prev,
876
- isLoading: false,
877
- errors
878
- }));
879
- },
880
- [updateProgress]
881
- );
882
- const hasModel = useCallback((url) => {
883
- return loaderRef.current.hasModel(url);
884
- }, []);
885
- const hasTexture = useCallback((url) => {
886
- return loaderRef.current.hasTexture(url);
887
- }, []);
888
- const getModel = useCallback((url) => {
889
- try {
890
- return loaderRef.current.getModel(url);
891
- } catch {
892
- return void 0;
893
- }
894
- }, []);
895
- const getTexture = useCallback((url) => {
896
- try {
897
- return loaderRef.current.getTexture(url);
898
- } catch {
899
- return void 0;
900
- }
901
- }, []);
902
- const clearCache = useCallback(() => {
903
- loaderRef.current.clearCache();
904
- setState({
905
- isLoading: false,
906
- progress: 0,
907
- loaded: 0,
908
- total: 0,
909
- errors: []
910
- });
911
- }, []);
912
- return {
913
- ...state,
914
- loadModel,
915
- loadOBJ,
916
- loadTexture,
917
- preload,
918
- hasModel,
919
- hasTexture,
920
- getModel,
921
- getTexture,
922
- clearCache
923
- };
924
- }
925
- function useGameCanvas3DEvents(options) {
926
- const {
927
- tileClickEvent,
928
- unitClickEvent,
929
- featureClickEvent,
930
- canvasClickEvent,
931
- tileHoverEvent,
932
- tileLeaveEvent,
933
- unitAnimationEvent,
934
- cameraChangeEvent,
935
- onTileClick,
936
- onUnitClick,
937
- onFeatureClick,
938
- onCanvasClick,
939
- onTileHover,
940
- onUnitAnimation
941
- } = options;
942
- const emit = useEmitEvent();
943
- const optionsRef = useRef(options);
944
- optionsRef.current = options;
945
- const handleTileClick = useCallback(
946
- (tile, event) => {
947
- if (tileClickEvent) {
948
- emit(tileClickEvent, {
949
- tileId: tile.id,
950
- x: tile.x,
951
- z: tile.z ?? tile.y ?? 0,
952
- type: tile.type,
953
- terrain: tile.terrain,
954
- elevation: tile.elevation
955
- });
956
- }
957
- optionsRef.current.onTileClick?.(tile, event);
958
- },
959
- [tileClickEvent, emit]
960
- );
961
- const handleUnitClick = useCallback(
962
- (unit, event) => {
963
- if (unitClickEvent) {
964
- emit(unitClickEvent, {
965
- unitId: unit.id,
966
- x: unit.x,
967
- z: unit.z ?? unit.y ?? 0,
968
- unitType: unit.unitType,
969
- name: unit.name,
970
- team: unit.team,
971
- faction: unit.faction,
972
- health: unit.health,
973
- maxHealth: unit.maxHealth
974
- });
975
- }
976
- optionsRef.current.onUnitClick?.(unit, event);
977
- },
978
- [unitClickEvent, emit]
979
- );
980
- const handleFeatureClick = useCallback(
981
- (feature, event) => {
982
- if (featureClickEvent) {
983
- emit(featureClickEvent, {
984
- featureId: feature.id,
985
- x: feature.x,
986
- z: feature.z ?? feature.y ?? 0,
987
- type: feature.type,
988
- elevation: feature.elevation
989
- });
990
- }
991
- optionsRef.current.onFeatureClick?.(feature, event);
992
- },
993
- [featureClickEvent, emit]
994
- );
995
- const handleCanvasClick = useCallback(
996
- (event) => {
997
- if (canvasClickEvent) {
998
- emit(canvasClickEvent, {
999
- clientX: event.clientX,
1000
- clientY: event.clientY,
1001
- button: event.button
1002
- });
1003
- }
1004
- optionsRef.current.onCanvasClick?.(event);
1005
- },
1006
- [canvasClickEvent, emit]
1007
- );
1008
- const handleTileHover = useCallback(
1009
- (tile, event) => {
1010
- if (tile) {
1011
- if (tileHoverEvent) {
1012
- emit(tileHoverEvent, {
1013
- tileId: tile.id,
1014
- x: tile.x,
1015
- z: tile.z ?? tile.y ?? 0,
1016
- type: tile.type
1017
- });
1018
- }
1019
- } else {
1020
- if (tileLeaveEvent) {
1021
- emit(tileLeaveEvent, {});
1022
- }
1023
- }
1024
- optionsRef.current.onTileHover?.(tile, event);
1025
- },
1026
- [tileHoverEvent, tileLeaveEvent, emit]
1027
- );
1028
- const handleUnitAnimation = useCallback(
1029
- (unitId, state) => {
1030
- if (unitAnimationEvent) {
1031
- emit(unitAnimationEvent, {
1032
- unitId,
1033
- state,
1034
- timestamp: Date.now()
1035
- });
1036
- }
1037
- optionsRef.current.onUnitAnimation?.(unitId, state);
1038
- },
1039
- [unitAnimationEvent, emit]
1040
- );
1041
- const handleCameraChange = useCallback(
1042
- (position) => {
1043
- if (cameraChangeEvent) {
1044
- emit(cameraChangeEvent, {
1045
- position,
1046
- timestamp: Date.now()
1047
- });
1048
- }
1049
- },
1050
- [cameraChangeEvent, emit]
1051
- );
1052
- return {
1053
- handleTileClick,
1054
- handleUnitClick,
1055
- handleFeatureClick,
1056
- handleCanvasClick,
1057
- handleTileHover,
1058
- handleUnitAnimation,
1059
- handleCameraChange
1060
- };
1061
- }
1062
- function detectAssetRoot3(modelUrl) {
1063
- const idx = modelUrl.indexOf("/3d/");
1064
- if (idx !== -1) {
1065
- return modelUrl.substring(0, idx + 4);
1066
- }
1067
- return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
1068
- }
1069
- function useGLTFModel2(url, resourceBasePath) {
1070
- const [state, setState] = useState({
1071
- model: null,
1072
- isLoading: false,
1073
- error: null
1074
- });
1075
- useEffect(() => {
1076
- if (!url) {
1077
- setState({ model: null, isLoading: false, error: null });
1078
- return;
1079
- }
1080
- console.log("[ModelLoader] Loading:", url);
1081
- setState((prev) => ({ ...prev, isLoading: true, error: null }));
1082
- const assetRoot = resourceBasePath || detectAssetRoot3(url);
1083
- const loader = new GLTFLoader$1();
1084
- loader.setResourcePath(assetRoot);
1085
- loader.load(
1086
- url,
1087
- (gltf) => {
1088
- console.log("[ModelLoader] Loaded:", url);
1089
- setState({
1090
- model: gltf.scene,
1091
- isLoading: false,
1092
- error: null
1093
- });
1094
- },
1095
- void 0,
1096
- (err) => {
1097
- const errorMsg = err instanceof Error ? err.message : String(err);
1098
- console.warn("[ModelLoader] Failed:", url, errorMsg);
1099
- setState({
1100
- model: null,
1101
- isLoading: false,
1102
- error: err instanceof Error ? err : new Error(String(err))
1103
- });
1104
- }
1105
- );
1106
- }, [url, resourceBasePath]);
1107
- return state;
1108
- }
1109
- function ModelLoader({
1110
- url,
1111
- position = [0, 0, 0],
1112
- scale = 1,
1113
- rotation = [0, 0, 0],
1114
- isSelected = false,
1115
- isHovered = false,
1116
- onClick,
1117
- onHover,
1118
- fallbackGeometry = "box",
1119
- castShadow = true,
1120
- receiveShadow = true,
1121
- resourceBasePath
1122
- }) {
1123
- const { model: loadedModel, isLoading, error } = useGLTFModel2(url, resourceBasePath);
1124
- const model = useMemo(() => {
1125
- if (!loadedModel) return null;
1126
- const cloned = loadedModel.clone();
1127
- cloned.traverse((child) => {
1128
- if (child instanceof THREE.Mesh) {
1129
- child.castShadow = castShadow;
1130
- child.receiveShadow = receiveShadow;
1131
- }
1132
- });
1133
- return cloned;
1134
- }, [loadedModel, castShadow, receiveShadow]);
1135
- const scaleArray = useMemo(() => {
1136
- if (typeof scale === "number") {
1137
- return [scale, scale, scale];
1138
- }
1139
- return scale;
1140
- }, [scale]);
1141
- const rotationRad = useMemo(() => {
1142
- return [
1143
- rotation[0] * Math.PI / 180,
1144
- rotation[1] * Math.PI / 180,
1145
- rotation[2] * Math.PI / 180
1146
- ];
1147
- }, [rotation]);
1148
- if (isLoading) {
1149
- return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
1150
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
1151
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
1152
- ] }) });
1153
- }
1154
- if (error || !model) {
1155
- if (fallbackGeometry === "none") {
1156
- return /* @__PURE__ */ jsx("group", { position });
1157
- }
1158
- const fallbackProps = {
1159
- onClick,
1160
- onPointerOver: () => onHover?.(true),
1161
- onPointerOut: () => onHover?.(false)
1162
- };
1163
- return /* @__PURE__ */ jsxs("group", { position, children: [
1164
- (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
1165
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
1166
- /* @__PURE__ */ jsx(
1167
- "meshBasicMaterial",
1168
- {
1169
- color: isSelected ? 16755200 : 16777215,
1170
- transparent: true,
1171
- opacity: 0.5
1172
- }
1173
- )
1174
- ] }),
1175
- fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
1176
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
1177
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
1178
- ] }),
1179
- fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
1180
- /* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
1181
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
1182
- ] }),
1183
- fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
1184
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
1185
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
1186
- ] })
1187
- ] });
1188
- }
1189
- return /* @__PURE__ */ jsxs(
1190
- "group",
1191
- {
1192
- position,
1193
- rotation: rotationRad,
1194
- onClick,
1195
- onPointerOver: () => onHover?.(true),
1196
- onPointerOut: () => onHover?.(false),
1197
- children: [
1198
- (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
1199
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
1200
- /* @__PURE__ */ jsx(
1201
- "meshBasicMaterial",
1202
- {
1203
- color: isSelected ? 16755200 : 16777215,
1204
- transparent: true,
1205
- opacity: 0.5
1206
- }
1207
- )
1208
- ] }),
1209
- /* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
1210
- ]
1211
- }
1212
- );
1213
- }
1214
- function Lighting3D({
1215
- ambientIntensity = 0.6,
1216
- ambientColor = "#ffffff",
1217
- directionalIntensity = 0.8,
1218
- directionalColor = "#ffffff",
1219
- directionalPosition = [10, 20, 10],
1220
- shadows = true,
1221
- shadowMapSize = 2048,
1222
- shadowCameraSize = 20,
1223
- showHelpers = false
1224
- }) {
1225
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1226
- /* @__PURE__ */ jsx("ambientLight", { intensity: ambientIntensity, color: ambientColor }),
1227
- /* @__PURE__ */ jsx(
1228
- "directionalLight",
1229
- {
1230
- position: directionalPosition,
1231
- intensity: directionalIntensity,
1232
- color: directionalColor,
1233
- castShadow: shadows,
1234
- "shadow-mapSize": [shadowMapSize, shadowMapSize],
1235
- "shadow-camera-left": -shadowCameraSize,
1236
- "shadow-camera-right": shadowCameraSize,
1237
- "shadow-camera-top": shadowCameraSize,
1238
- "shadow-camera-bottom": -shadowCameraSize,
1239
- "shadow-camera-near": 0.1,
1240
- "shadow-camera-far": 100,
1241
- "shadow-bias": -1e-3
1242
- }
1243
- ),
1244
- /* @__PURE__ */ jsx(
1245
- "hemisphereLight",
1246
- {
1247
- intensity: 0.3,
1248
- color: "#87ceeb",
1249
- groundColor: "#362d1d"
1250
- }
1251
- ),
1252
- showHelpers && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
1253
- "directionalLightHelper",
1254
- {
1255
- args: [
1256
- new THREE.DirectionalLight(directionalColor, directionalIntensity),
1257
- 5
1258
- ]
1259
- }
1260
- ) })
1261
- ] });
1262
- }
1263
- function PhysicsObject3D({
1264
- entityId,
1265
- modelUrl,
1266
- initialPosition = [0, 0, 0],
1267
- initialVelocity = [0, 0, 0],
1268
- mass = 1,
1269
- gravity = 9.8,
1270
- groundY = 0,
1271
- scale = 1,
1272
- onPhysicsUpdate,
1273
- onGroundHit,
1274
- onCollision
1275
- }) {
1276
- const groupRef = useRef(null);
1277
- const physicsStateRef = useRef({
1278
- id: entityId,
1279
- x: initialPosition[0],
1280
- y: initialPosition[1],
1281
- z: initialPosition[2],
1282
- vx: initialVelocity[0],
1283
- vy: initialVelocity[1],
1284
- vz: initialVelocity[2],
1285
- rx: 0,
1286
- ry: 0,
1287
- rz: 0,
1288
- isGrounded: false,
1289
- gravity,
1290
- friction: 0.8,
1291
- mass,
1292
- state: "Active"
1293
- });
1294
- const groundHitRef = useRef(false);
1295
- useEffect(() => {
1296
- if (groupRef.current) {
1297
- groupRef.current.position.set(
1298
- initialPosition[0],
1299
- initialPosition[1],
1300
- initialPosition[2]
1301
- );
1302
- }
1303
- }, []);
1304
- useFrame((state, delta) => {
1305
- const physics = physicsStateRef.current;
1306
- if (physics.state !== "Active") return;
1307
- const dt = Math.min(delta, 0.1);
1308
- if (!physics.isGrounded) {
1309
- physics.vy -= physics.gravity * dt;
1310
- }
1311
- physics.x += physics.vx * dt;
1312
- physics.y += physics.vy * dt;
1313
- physics.z += physics.vz * dt;
1314
- const airResistance = Math.pow(0.99, dt * 60);
1315
- physics.vx *= airResistance;
1316
- physics.vz *= airResistance;
1317
- if (physics.y <= groundY) {
1318
- physics.y = groundY;
1319
- if (!physics.isGrounded) {
1320
- physics.isGrounded = true;
1321
- groundHitRef.current = true;
1322
- physics.vx *= physics.friction;
1323
- physics.vz *= physics.friction;
1324
- onGroundHit?.();
1325
- }
1326
- physics.vy = 0;
1327
- } else {
1328
- physics.isGrounded = false;
1329
- }
1330
- if (groupRef.current) {
1331
- groupRef.current.position.set(physics.x, physics.y, physics.z);
1332
- if (!physics.isGrounded) {
1333
- physics.rx += physics.vz * dt * 0.5;
1334
- physics.rz -= physics.vx * dt * 0.5;
1335
- groupRef.current.rotation.set(physics.rx, physics.ry, physics.rz);
1336
- }
1337
- }
1338
- onPhysicsUpdate?.({ ...physics });
1339
- });
1340
- const scaleArray = typeof scale === "number" ? [scale, scale, scale] : scale;
1341
- return /* @__PURE__ */ jsx("group", { ref: groupRef, scale: scaleArray, children: /* @__PURE__ */ jsx(
1342
- ModelLoader,
1343
- {
1344
- url: modelUrl,
1345
- fallbackGeometry: "box"
1346
- }
1347
- ) });
1348
- }
1349
- function usePhysics3DController(entityId) {
1350
- const applyForce = (fx, fy, fz) => {
1351
- console.log(`Apply force to ${entityId}:`, { fx, fy, fz });
1352
- };
1353
- const setVelocity = (vx, vy, vz) => {
1354
- console.log(`Set velocity for ${entityId}:`, { vx, vy, vz });
1355
- };
1356
- const setPosition = (x, y, z) => {
1357
- console.log(`Set position for ${entityId}:`, { x, y, z });
1358
- };
1359
- const jump = (force = 10) => {
1360
- applyForce(0, force, 0);
1361
- };
1362
- return {
1363
- applyForce,
1364
- setVelocity,
1365
- setPosition,
1366
- jump
1367
- };
1368
- }
1369
- function Scene3D({ background = "#1a1a2e", fog, children }) {
1370
- const { scene } = useThree();
1371
- const initializedRef = useRef(false);
1372
- useEffect(() => {
1373
- if (initializedRef.current) return;
1374
- initializedRef.current = true;
1375
- if (background.startsWith("#") || background.startsWith("rgb")) {
1376
- scene.background = new THREE.Color(background);
1377
- } else {
1378
- const loader = new THREE.TextureLoader();
1379
- loader.load(background, (texture) => {
1380
- scene.background = texture;
1381
- });
1382
- }
1383
- if (fog) {
1384
- scene.fog = new THREE.Fog(fog.color, fog.near, fog.far);
1385
- }
1386
- return () => {
1387
- scene.background = null;
1388
- scene.fog = null;
1389
- };
1390
- }, [scene, background, fog]);
1391
- return /* @__PURE__ */ jsx(Fragment, { children });
1392
- }
1393
- var DEFAULT_TERRAIN_COLORS = {
1394
- grass: "#44aa44",
1395
- dirt: "#8b7355",
1396
- sand: "#ddcc88",
1397
- water: "#4488cc",
1398
- rock: "#888888",
1399
- snow: "#eeeeee",
1400
- forest: "#228b22",
1401
- desert: "#d4a574",
1402
- mountain: "#696969",
1403
- swamp: "#556b2f"
1404
- };
1405
- function TileRenderer({
1406
- tiles,
1407
- cellSize = 1,
1408
- offsetX = 0,
1409
- offsetZ = 0,
1410
- useInstancing = true,
1411
- terrainColors = DEFAULT_TERRAIN_COLORS,
1412
- onTileClick,
1413
- onTileHover,
1414
- selectedTileIds = [],
1415
- validMoves = [],
1416
- attackTargets = []
1417
- }) {
1418
- const meshRef = useRef(null);
1419
- const geometry = useMemo(() => {
1420
- return new THREE.BoxGeometry(cellSize * 0.95, 0.2, cellSize * 0.95);
1421
- }, [cellSize]);
1422
- const material = useMemo(() => {
1423
- return new THREE.MeshStandardMaterial({
1424
- roughness: 0.8,
1425
- metalness: 0.1
1426
- });
1427
- }, []);
1428
- const { positions, colors, tileMap } = useMemo(() => {
1429
- const pos = [];
1430
- const cols = [];
1431
- const map = /* @__PURE__ */ new Map();
1432
- tiles.forEach((tile) => {
1433
- const x = (tile.x - offsetX) * cellSize;
1434
- const z = ((tile.z ?? tile.y ?? 0) - offsetZ) * cellSize;
1435
- const y = (tile.elevation ?? 0) * 0.1;
1436
- pos.push(new THREE.Vector3(x, y, z));
1437
- const colorHex = terrainColors[tile.type || ""] || terrainColors[tile.terrain || ""] || "#808080";
1438
- const color = new THREE.Color(colorHex);
1439
- const isValidMove = validMoves.some(
1440
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
1441
- );
1442
- const isAttackTarget = attackTargets.some(
1443
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
1444
- );
1445
- const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
1446
- if (isSelected) {
1447
- color.addScalar(0.3);
1448
- } else if (isAttackTarget) {
1449
- color.setHex(16729156);
1450
- } else if (isValidMove) {
1451
- color.setHex(4521796);
1452
- }
1453
- cols.push(color);
1454
- map.set(`${tile.x},${tile.z ?? tile.y ?? 0}`, tile);
1455
- });
1456
- return { positions: pos, colors: cols, tileMap: map };
1457
- }, [tiles, cellSize, offsetX, offsetZ, terrainColors, selectedTileIds, validMoves, attackTargets]);
1458
- useEffect(() => {
1459
- if (!meshRef.current || !useInstancing) return;
1460
- const mesh = meshRef.current;
1461
- mesh.count = positions.length;
1462
- const dummy = new THREE.Object3D();
1463
- positions.forEach((pos, i) => {
1464
- dummy.position.copy(pos);
1465
- dummy.updateMatrix();
1466
- mesh.setMatrixAt(i, dummy.matrix);
1467
- if (mesh.setColorAt) {
1468
- mesh.setColorAt(i, colors[i]);
1469
- }
1470
- });
1471
- mesh.instanceMatrix.needsUpdate = true;
1472
- if (mesh.instanceColor) {
1473
- mesh.instanceColor.needsUpdate = true;
1474
- }
1475
- }, [positions, colors, useInstancing]);
1476
- const handlePointerMove = (e) => {
1477
- if (!onTileHover) return;
1478
- const instanceId = e.instanceId;
1479
- if (instanceId !== void 0) {
1480
- const pos = positions[instanceId];
1481
- if (pos) {
1482
- const gridX = Math.round(pos.x / cellSize + offsetX);
1483
- const gridZ = Math.round(pos.z / cellSize + offsetZ);
1484
- const tile = tileMap.get(`${gridX},${gridZ}`);
1485
- if (tile) {
1486
- onTileHover(tile);
1487
- }
1488
- }
1489
- }
1490
- };
1491
- const handleClick = (e) => {
1492
- if (!onTileClick) return;
1493
- const instanceId = e.instanceId;
1494
- if (instanceId !== void 0) {
1495
- const pos = positions[instanceId];
1496
- if (pos) {
1497
- const gridX = Math.round(pos.x / cellSize + offsetX);
1498
- const gridZ = Math.round(pos.z / cellSize + offsetZ);
1499
- const tile = tileMap.get(`${gridX},${gridZ}`);
1500
- if (tile) {
1501
- onTileClick(tile);
1502
- }
1503
- }
1504
- }
1505
- };
1506
- const renderIndividualTiles = () => {
1507
- return tiles.map((tile) => {
1508
- const x = (tile.x - offsetX) * cellSize;
1509
- const z = ((tile.z ?? tile.y ?? 0) - offsetZ) * cellSize;
1510
- const y = (tile.elevation ?? 0) * 0.1;
1511
- const colorHex = terrainColors[tile.type || ""] || terrainColors[tile.terrain || ""] || "#808080";
1512
- const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
1513
- const isValidMove = validMoves.some(
1514
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
1515
- );
1516
- const isAttackTarget = attackTargets.some(
1517
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
1518
- );
1519
- let emissive = "#000000";
1520
- if (isSelected) emissive = "#444444";
1521
- else if (isAttackTarget) emissive = "#440000";
1522
- else if (isValidMove) emissive = "#004400";
1523
- return /* @__PURE__ */ jsxs(
1524
- "mesh",
1525
- {
1526
- position: [x, y, z],
1527
- userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
1528
- onClick: () => onTileClick?.(tile),
1529
- onPointerEnter: () => onTileHover?.(tile),
1530
- onPointerLeave: () => onTileHover?.(null),
1531
- children: [
1532
- /* @__PURE__ */ jsx("boxGeometry", { args: [cellSize * 0.95, 0.2, cellSize * 0.95] }),
1533
- /* @__PURE__ */ jsx(
1534
- "meshStandardMaterial",
1535
- {
1536
- color: colorHex,
1537
- emissive,
1538
- roughness: 0.8,
1539
- metalness: 0.1
1540
- }
1541
- )
1542
- ]
1543
- },
1544
- tile.id ?? `tile-${tile.x}-${tile.y}`
1545
- );
1546
- });
1547
- };
1548
- if (useInstancing && tiles.length > 0) {
1549
- return /* @__PURE__ */ jsx(
1550
- "instancedMesh",
1551
- {
1552
- ref: meshRef,
1553
- args: [geometry, material, tiles.length],
1554
- onPointerMove: handlePointerMove,
1555
- onClick: handleClick
1556
- }
1557
- );
1558
- }
1559
- return /* @__PURE__ */ jsx("group", { children: renderIndividualTiles() });
1560
- }
1561
- function UnitVisual({ unit, position, isSelected, onClick }) {
1562
- const groupRef = useRef(null);
1563
- const [animationState, setAnimationState] = useState("idle");
1564
- const [isHovered, setIsHovered] = useState(false);
1565
- const teamColor = useMemo(() => {
1566
- if (unit.faction === "player" || unit.team === "player") return 4491519;
1567
- if (unit.faction === "enemy" || unit.team === "enemy") return 16729156;
1568
- if (unit.faction === "neutral" || unit.team === "neutral") return 16777028;
1569
- return 8947848;
1570
- }, [unit.faction, unit.team]);
1571
- useFrame((state) => {
1572
- if (groupRef.current && animationState === "idle") {
1573
- const y = position[1] + Math.sin(state.clock.elapsedTime * 2 + position[0]) * 0.05;
1574
- groupRef.current.position.y = y;
1575
- }
1576
- });
1577
- const healthPercent = useMemo(() => {
1578
- if (unit.health === void 0 || unit.maxHealth === void 0) return 1;
1579
- return Math.max(0, Math.min(1, unit.health / unit.maxHealth));
1580
- }, [unit.health, unit.maxHealth]);
1581
- const healthColor = useMemo(() => {
1582
- if (healthPercent > 0.5) return "#44aa44";
1583
- if (healthPercent > 0.25) return "#aaaa44";
1584
- return "#ff4444";
1585
- }, [healthPercent]);
1586
- return /* @__PURE__ */ jsxs(
1587
- "group",
1588
- {
1589
- ref: groupRef,
1590
- position,
1591
- onClick,
1592
- onPointerEnter: () => setIsHovered(true),
1593
- onPointerLeave: () => setIsHovered(false),
1594
- userData: { type: "unit", unitId: unit.id },
1595
- children: [
1596
- isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
1597
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
1598
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
1599
- ] }),
1600
- isHovered && !isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
1601
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
1602
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffffff", transparent: true, opacity: 0.5 })
1603
- ] }),
1604
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.1, 0], children: [
1605
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.25, 0.25, 0.1, 8] }),
1606
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: teamColor })
1607
- ] }),
1608
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.5, 0], children: [
1609
- /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.15, 0.5, 4, 8] }),
1610
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: teamColor })
1611
- ] }),
1612
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
1613
- /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
1614
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: teamColor })
1615
- ] }),
1616
- /* @__PURE__ */ jsxs("mesh", { position: [0, 1.3, 0], children: [
1617
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.06] }),
1618
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#333333" })
1619
- ] }),
1620
- /* @__PURE__ */ jsxs("mesh", { position: [-0.25 + 0.25 * healthPercent, 1.3, 0.01], children: [
1621
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * healthPercent, 0.04] }),
1622
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: healthColor })
1623
- ] }),
1624
- unit.name && /* @__PURE__ */ jsxs("mesh", { position: [0, 1.5, 0], children: [
1625
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.4, 0.1] }),
1626
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#000000", transparent: true, opacity: 0.5 })
1627
- ] })
1628
- ]
1629
- }
1630
- );
1631
- }
1632
- function UnitRenderer({
1633
- units,
1634
- cellSize = 1,
1635
- offsetX = 0,
1636
- offsetZ = 0,
1637
- selectedUnitId,
1638
- onUnitClick,
1639
- onAnimationStateChange,
1640
- animationSpeed = 1
1641
- }) {
1642
- const handleUnitClick = React8.useCallback(
1643
- (unit) => {
1644
- onUnitClick?.(unit);
1645
- },
1646
- [onUnitClick]
1647
- );
1648
- return /* @__PURE__ */ jsx("group", { children: units.map((unit) => {
1649
- const unitX = unit.x ?? unit.position?.x ?? 0;
1650
- const unitY = unit.z ?? unit.y ?? unit.position?.y ?? 0;
1651
- const x = (unitX - offsetX) * cellSize;
1652
- const z = (unitY - offsetZ) * cellSize;
1653
- const y = (unit.elevation ?? 0) * 0.1 + 0.5;
1654
- return /* @__PURE__ */ jsx(
1655
- UnitVisual,
1656
- {
1657
- unit,
1658
- position: [x, y, z],
1659
- isSelected: selectedUnitId === unit.id,
1660
- onClick: () => handleUnitClick(unit)
1661
- },
1662
- unit.id
1663
- );
1664
- }) });
1665
- }
1666
-
1667
- export { AssetLoader, Camera3D, Canvas3DErrorBoundary, Canvas3DLoadingState, FeatureRenderer, FeatureRenderer3D, Lighting3D, ModelLoader, PhysicsObject3D, Scene3D, TileRenderer, UnitRenderer, assetLoader, preloadFeatures, useAssetLoader, useGameCanvas3DEvents, usePhysics3DController };