@almadar/ui 2.15.8 → 2.15.10
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.
- package/dist/components/atoms/ContentSection.d.ts +16 -0
- package/dist/components/atoms/SectionHeader.d.ts +14 -0
- package/dist/components/atoms/StatCard.d.ts +13 -0
- package/dist/components/atoms/index.d.ts +3 -0
- package/dist/components/atoms/svg/SvgBranch.d.ts +12 -0
- package/dist/components/atoms/svg/SvgConnection.d.ts +13 -0
- package/dist/components/atoms/svg/SvgFlow.d.ts +10 -0
- package/dist/components/atoms/svg/SvgGrid.d.ts +14 -0
- package/dist/components/atoms/svg/SvgLobe.d.ts +13 -0
- package/dist/components/atoms/svg/SvgMesh.d.ts +12 -0
- package/dist/components/atoms/svg/SvgMorph.d.ts +11 -0
- package/dist/components/atoms/svg/SvgNode.d.ts +12 -0
- package/dist/components/atoms/svg/SvgPulse.d.ts +12 -0
- package/dist/components/atoms/svg/SvgRing.d.ts +13 -0
- package/dist/components/atoms/svg/SvgShield.d.ts +11 -0
- package/dist/components/atoms/svg/SvgStack.d.ts +13 -0
- package/dist/components/atoms/svg/index.d.ts +12 -0
- package/dist/{chunk-ACUO2BBW.js → components/index.cjs} +24764 -16677
- package/dist/components/index.js +37757 -889
- package/dist/components/molecules/AnimatedCounter.d.ts +18 -0
- package/dist/components/molecules/ArticleSection.d.ts +18 -0
- package/dist/components/molecules/CTABanner.d.ts +31 -0
- package/dist/components/molecules/CaseStudyCard.d.ts +24 -0
- package/dist/components/molecules/CodeExample.d.ts +23 -0
- package/dist/components/molecules/CommunityLinks.d.ts +25 -0
- package/dist/components/molecules/DocBreadcrumb.d.ts +20 -0
- package/dist/components/molecules/DocCodeBlock.d.ts +13 -0
- package/dist/components/molecules/DocPagination.d.ts +14 -0
- package/dist/components/molecules/DocSearch.d.ts +15 -0
- package/dist/components/molecules/DocSidebar.d.ts +24 -0
- package/dist/components/molecules/DocTOC.d.ts +24 -0
- package/dist/components/molecules/FeatureCard.d.ts +26 -0
- package/dist/components/molecules/FeatureGrid.d.ts +19 -0
- package/dist/components/molecules/GradientDivider.d.ts +14 -0
- package/dist/components/molecules/HeroSection.d.ts +36 -0
- package/dist/components/molecules/InstallBox.d.ts +16 -0
- package/dist/components/molecules/MarketingFooter.d.ts +27 -0
- package/dist/components/molecules/PricingCard.d.ts +21 -0
- package/dist/components/molecules/PricingGrid.d.ts +13 -0
- package/dist/components/molecules/PullQuote.d.ts +14 -0
- package/dist/components/molecules/ServiceCatalog.d.ts +19 -0
- package/dist/components/molecules/ShowcaseCard.d.ts +20 -0
- package/dist/components/molecules/SocialProof.d.ts +25 -0
- package/dist/components/molecules/SplitSection.d.ts +21 -0
- package/dist/components/molecules/StatsGrid.d.ts +17 -0
- package/dist/components/molecules/StepFlow.d.ts +20 -0
- package/dist/components/molecules/TagCloud.d.ts +18 -0
- package/dist/components/molecules/TeamCard.d.ts +18 -0
- package/dist/components/molecules/index.d.ts +19 -0
- package/dist/components/molecules/svg/AIGenerates.d.ts +7 -0
- package/dist/components/molecules/svg/ClosedCircuit.d.ts +7 -0
- package/dist/components/molecules/svg/CommunityOwnership.d.ts +7 -0
- package/dist/components/molecules/svg/CompileAnywhere.d.ts +7 -0
- package/dist/components/molecules/svg/ComposableModels.d.ts +7 -0
- package/dist/components/molecules/svg/DescribeProveDeploy.d.ts +7 -0
- package/dist/components/molecules/svg/DomainGrid.d.ts +7 -0
- package/dist/components/molecules/svg/EventBus.d.ts +7 -0
- package/dist/components/molecules/svg/OrbitalUnit.d.ts +7 -0
- package/dist/components/molecules/svg/PlanVerifyRemember.d.ts +7 -0
- package/dist/components/molecules/svg/ProveCorrect.d.ts +7 -0
- package/dist/components/molecules/svg/ServiceLayers.d.ts +7 -0
- package/dist/components/molecules/svg/SharedReality.d.ts +7 -0
- package/dist/components/molecules/svg/StandardLibrary.d.ts +7 -0
- package/dist/components/molecules/svg/StateMachine.d.ts +7 -0
- package/dist/components/molecules/svg/WorldModel.d.ts +7 -0
- package/dist/components/molecules/svg/index.d.ts +16 -0
- package/dist/components/organisms/CaseStudyOrganism.d.ts +19 -0
- package/dist/components/organisms/FeatureGridOrganism.d.ts +20 -0
- package/dist/components/organisms/HeroOrganism.d.ts +18 -0
- package/dist/components/organisms/PricingOrganism.d.ts +19 -0
- package/dist/components/organisms/ShowcaseOrganism.d.ts +20 -0
- package/dist/components/organisms/StatsOrganism.d.ts +17 -0
- package/dist/components/organisms/StepFlowOrganism.d.ts +20 -0
- package/dist/components/organisms/TeamOrganism.d.ts +18 -0
- package/dist/components/organisms/game/three/index.cjs +2525 -0
- package/dist/components/organisms/game/three/index.js +1795 -50
- package/dist/components/organisms/index.d.ts +9 -0
- package/dist/components/organisms/marketing-types.d.ts +87 -0
- package/dist/components/templates/AboutPageTemplate.d.ts +26 -0
- package/dist/components/templates/FeatureDetailPageTemplate.d.ts +27 -0
- package/dist/components/templates/LandingPageTemplate.d.ts +31 -0
- package/dist/components/templates/PricingPageTemplate.d.ts +26 -0
- package/dist/components/templates/index.d.ts +4 -0
- package/dist/context/index.cjs +550 -0
- package/dist/context/index.js +420 -6
- package/dist/docs/index.cjs +4015 -0
- package/dist/docs/index.d.cts +412 -0
- package/dist/docs/index.d.ts +29 -0
- package/dist/docs/index.js +3977 -0
- package/dist/hooks/index.cjs +2606 -0
- package/dist/hooks/index.js +2535 -8
- package/dist/illustrations/index.cjs +3004 -0
- package/dist/illustrations/index.d.cts +261 -0
- package/dist/illustrations/index.d.ts +35 -0
- package/dist/illustrations/index.js +2971 -0
- package/dist/{chunk-XL7WB2O5.js → lib/index.cjs} +454 -274
- package/dist/lib/index.js +1407 -3
- package/dist/locales/index.cjs +340 -0
- package/dist/locales/index.js +105 -2
- package/dist/marketing/index.cjs +4683 -0
- package/dist/marketing/index.d.cts +831 -0
- package/dist/marketing/index.d.ts +62 -0
- package/dist/marketing/index.js +4626 -0
- package/dist/providers/index.cjs +4811 -0
- package/dist/providers/index.js +4765 -11
- package/dist/{chunk-K2D5D3WK.js → renderer/index.cjs} +101 -42
- package/dist/renderer/index.js +1036 -2
- package/dist/runtime/index.cjs +4400 -0
- package/dist/runtime/index.js +3615 -19
- package/dist/{chunk-N7MVUW4R.js → stores/index.cjs} +24 -1
- package/dist/stores/index.js +194 -2
- package/dist/tsup.config.d.ts +2 -1
- package/package.json +27 -12
- package/tailwind-preset.cjs +9 -0
- package/themes/index.css +22 -20
- package/dist/chunk-3HJHHULT.js +0 -93
- package/dist/chunk-3JGAROCW.js +0 -149
- package/dist/chunk-4N3BAPDB.js +0 -1667
- package/dist/chunk-CDIOHSKG.js +0 -661
- package/dist/chunk-DKQN5FVU.js +0 -279
- package/dist/chunk-JJHCOO34.js +0 -375
- package/dist/chunk-PKBMQBKP.js +0 -5
- package/dist/chunk-QIABKRCN.js +0 -107
- package/dist/chunk-SD3KVCY6.js +0 -1465
- package/dist/chunk-YXZM3WCF.js +0 -222
|
@@ -1,11 +1,753 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import '
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
1
|
+
import React8, { forwardRef, useRef, useEffect, useImperativeHandle, createContext, Component, useMemo, useState, useCallback, useContext } from 'react';
|
|
2
|
+
import { useThree, useFrame } from '@react-three/fiber';
|
|
3
|
+
import * as THREE6 from 'three';
|
|
4
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
import { OrbitControls } from '@react-three/drei';
|
|
6
|
+
import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
|
|
7
|
+
import { OrbitControls as OrbitControls$1 } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
8
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
9
|
+
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
|
|
8
10
|
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
14
|
+
function Scene3D({ background = "#1a1a2e", fog, children }) {
|
|
15
|
+
const { scene } = useThree();
|
|
16
|
+
const initializedRef = useRef(false);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (initializedRef.current) return;
|
|
19
|
+
initializedRef.current = true;
|
|
20
|
+
if (background.startsWith("#") || background.startsWith("rgb")) {
|
|
21
|
+
scene.background = new THREE6.Color(background);
|
|
22
|
+
} else {
|
|
23
|
+
const loader = new THREE6.TextureLoader();
|
|
24
|
+
loader.load(background, (texture) => {
|
|
25
|
+
scene.background = texture;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (fog) {
|
|
29
|
+
scene.fog = new THREE6.Fog(fog.color, fog.near, fog.far);
|
|
30
|
+
}
|
|
31
|
+
return () => {
|
|
32
|
+
scene.background = null;
|
|
33
|
+
scene.fog = null;
|
|
34
|
+
};
|
|
35
|
+
}, [scene, background, fog]);
|
|
36
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
37
|
+
}
|
|
38
|
+
var Camera3D = forwardRef(
|
|
39
|
+
({
|
|
40
|
+
mode = "isometric",
|
|
41
|
+
position = [10, 10, 10],
|
|
42
|
+
target = [0, 0, 0],
|
|
43
|
+
zoom = 1,
|
|
44
|
+
fov = 45,
|
|
45
|
+
enableOrbit = true,
|
|
46
|
+
minDistance = 2,
|
|
47
|
+
maxDistance = 100,
|
|
48
|
+
onChange
|
|
49
|
+
}, ref) => {
|
|
50
|
+
const { camera, set, viewport } = useThree();
|
|
51
|
+
const controlsRef = useRef(null);
|
|
52
|
+
const initialPosition = useRef(new THREE6.Vector3(...position));
|
|
53
|
+
const initialTarget = useRef(new THREE6.Vector3(...target));
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
let newCamera;
|
|
56
|
+
if (mode === "isometric") {
|
|
57
|
+
const aspect = viewport.aspect;
|
|
58
|
+
const size = 10 / zoom;
|
|
59
|
+
newCamera = new THREE6.OrthographicCamera(
|
|
60
|
+
-size * aspect,
|
|
61
|
+
size * aspect,
|
|
62
|
+
size,
|
|
63
|
+
-size,
|
|
64
|
+
0.1,
|
|
65
|
+
1e3
|
|
66
|
+
);
|
|
67
|
+
} else {
|
|
68
|
+
newCamera = new THREE6.PerspectiveCamera(fov, viewport.aspect, 0.1, 1e3);
|
|
69
|
+
}
|
|
70
|
+
newCamera.position.copy(initialPosition.current);
|
|
71
|
+
newCamera.lookAt(initialTarget.current);
|
|
72
|
+
set({ camera: newCamera });
|
|
73
|
+
if (mode === "top-down") {
|
|
74
|
+
newCamera.position.set(0, 20 / zoom, 0);
|
|
75
|
+
newCamera.lookAt(0, 0, 0);
|
|
76
|
+
}
|
|
77
|
+
return () => {
|
|
78
|
+
};
|
|
79
|
+
}, [mode, fov, zoom, viewport.aspect, set]);
|
|
80
|
+
useFrame(() => {
|
|
81
|
+
if (onChange) {
|
|
82
|
+
onChange(camera);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
useImperativeHandle(ref, () => ({
|
|
86
|
+
getCamera: () => camera,
|
|
87
|
+
setPosition: (x, y, z) => {
|
|
88
|
+
camera.position.set(x, y, z);
|
|
89
|
+
if (controlsRef.current) {
|
|
90
|
+
controlsRef.current.update();
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
lookAt: (x, y, z) => {
|
|
94
|
+
camera.lookAt(x, y, z);
|
|
95
|
+
if (controlsRef.current) {
|
|
96
|
+
controlsRef.current.target.set(x, y, z);
|
|
97
|
+
controlsRef.current.update();
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
reset: () => {
|
|
101
|
+
camera.position.copy(initialPosition.current);
|
|
102
|
+
camera.lookAt(initialTarget.current);
|
|
103
|
+
if (controlsRef.current) {
|
|
104
|
+
controlsRef.current.target.copy(initialTarget.current);
|
|
105
|
+
controlsRef.current.update();
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
getViewBounds: () => {
|
|
109
|
+
const min = new THREE6.Vector3(-10, -10, -10);
|
|
110
|
+
const max = new THREE6.Vector3(10, 10, 10);
|
|
111
|
+
return { min, max };
|
|
112
|
+
}
|
|
113
|
+
}));
|
|
114
|
+
const maxPolarAngle = mode === "top-down" ? 0.1 : Math.PI / 2 - 0.1;
|
|
115
|
+
return /* @__PURE__ */ jsx(
|
|
116
|
+
OrbitControls,
|
|
117
|
+
{
|
|
118
|
+
ref: controlsRef,
|
|
119
|
+
camera,
|
|
120
|
+
enabled: enableOrbit,
|
|
121
|
+
target: initialTarget.current,
|
|
122
|
+
minDistance,
|
|
123
|
+
maxDistance,
|
|
124
|
+
maxPolarAngle,
|
|
125
|
+
enableDamping: true,
|
|
126
|
+
dampingFactor: 0.05
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
Camera3D.displayName = "Camera3D";
|
|
132
|
+
function Lighting3D({
|
|
133
|
+
ambientIntensity = 0.6,
|
|
134
|
+
ambientColor = "#ffffff",
|
|
135
|
+
directionalIntensity = 0.8,
|
|
136
|
+
directionalColor = "#ffffff",
|
|
137
|
+
directionalPosition = [10, 20, 10],
|
|
138
|
+
shadows = true,
|
|
139
|
+
shadowMapSize = 2048,
|
|
140
|
+
shadowCameraSize = 20,
|
|
141
|
+
showHelpers = false
|
|
142
|
+
}) {
|
|
143
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
144
|
+
/* @__PURE__ */ jsx("ambientLight", { intensity: ambientIntensity, color: ambientColor }),
|
|
145
|
+
/* @__PURE__ */ jsx(
|
|
146
|
+
"directionalLight",
|
|
147
|
+
{
|
|
148
|
+
position: directionalPosition,
|
|
149
|
+
intensity: directionalIntensity,
|
|
150
|
+
color: directionalColor,
|
|
151
|
+
castShadow: shadows,
|
|
152
|
+
"shadow-mapSize": [shadowMapSize, shadowMapSize],
|
|
153
|
+
"shadow-camera-left": -shadowCameraSize,
|
|
154
|
+
"shadow-camera-right": shadowCameraSize,
|
|
155
|
+
"shadow-camera-top": shadowCameraSize,
|
|
156
|
+
"shadow-camera-bottom": -shadowCameraSize,
|
|
157
|
+
"shadow-camera-near": 0.1,
|
|
158
|
+
"shadow-camera-far": 100,
|
|
159
|
+
"shadow-bias": -1e-3
|
|
160
|
+
}
|
|
161
|
+
),
|
|
162
|
+
/* @__PURE__ */ jsx(
|
|
163
|
+
"hemisphereLight",
|
|
164
|
+
{
|
|
165
|
+
intensity: 0.3,
|
|
166
|
+
color: "#87ceeb",
|
|
167
|
+
groundColor: "#362d1d"
|
|
168
|
+
}
|
|
169
|
+
),
|
|
170
|
+
showHelpers && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
|
|
171
|
+
"directionalLightHelper",
|
|
172
|
+
{
|
|
173
|
+
args: [
|
|
174
|
+
new THREE6.DirectionalLight(directionalColor, directionalIntensity),
|
|
175
|
+
5
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
) })
|
|
179
|
+
] });
|
|
180
|
+
}
|
|
181
|
+
function Canvas3DLoadingState({
|
|
182
|
+
progress = 0,
|
|
183
|
+
loaded = 0,
|
|
184
|
+
total = 0,
|
|
185
|
+
message = "Loading 3D Scene...",
|
|
186
|
+
details,
|
|
187
|
+
showSpinner = true,
|
|
188
|
+
className
|
|
189
|
+
}) {
|
|
190
|
+
const clampedProgress = Math.max(0, Math.min(100, progress));
|
|
191
|
+
const hasProgress = total > 0;
|
|
192
|
+
return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
|
|
193
|
+
/* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
|
|
194
|
+
showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
|
|
195
|
+
/* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
|
|
196
|
+
/* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
|
|
197
|
+
] }),
|
|
198
|
+
/* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
|
|
199
|
+
details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
|
|
200
|
+
hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
|
|
201
|
+
/* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
|
|
202
|
+
"div",
|
|
203
|
+
{
|
|
204
|
+
className: "progress__fill",
|
|
205
|
+
style: { width: `${clampedProgress}%` }
|
|
206
|
+
}
|
|
207
|
+
) }),
|
|
208
|
+
/* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
|
|
209
|
+
/* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
|
|
210
|
+
clampedProgress,
|
|
211
|
+
"%"
|
|
212
|
+
] }),
|
|
213
|
+
/* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
|
|
214
|
+
"(",
|
|
215
|
+
loaded,
|
|
216
|
+
"/",
|
|
217
|
+
total,
|
|
218
|
+
")"
|
|
219
|
+
] })
|
|
220
|
+
] })
|
|
221
|
+
] })
|
|
222
|
+
] }),
|
|
223
|
+
/* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
|
|
224
|
+
] });
|
|
225
|
+
}
|
|
226
|
+
var Canvas3DErrorBoundary = class extends Component {
|
|
227
|
+
constructor(props) {
|
|
228
|
+
super(props);
|
|
229
|
+
__publicField(this, "handleReset", () => {
|
|
230
|
+
this.setState({
|
|
231
|
+
hasError: false,
|
|
232
|
+
error: null,
|
|
233
|
+
errorInfo: null
|
|
234
|
+
});
|
|
235
|
+
this.props.onReset?.();
|
|
236
|
+
});
|
|
237
|
+
this.state = {
|
|
238
|
+
hasError: false,
|
|
239
|
+
error: null,
|
|
240
|
+
errorInfo: null
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
static getDerivedStateFromError(error) {
|
|
244
|
+
return {
|
|
245
|
+
hasError: true,
|
|
246
|
+
error,
|
|
247
|
+
errorInfo: null
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
componentDidCatch(error, errorInfo) {
|
|
251
|
+
this.setState({ errorInfo });
|
|
252
|
+
this.props.onError?.(error, errorInfo);
|
|
253
|
+
console.error("[Canvas3DErrorBoundary] Error caught:", error);
|
|
254
|
+
console.error("[Canvas3DErrorBoundary] Component stack:", errorInfo.componentStack);
|
|
255
|
+
}
|
|
256
|
+
render() {
|
|
257
|
+
if (this.state.hasError) {
|
|
258
|
+
if (this.props.fallback) {
|
|
259
|
+
return this.props.fallback;
|
|
260
|
+
}
|
|
261
|
+
return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
|
|
262
|
+
/* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
|
|
263
|
+
/* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
|
|
264
|
+
/* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
|
|
265
|
+
this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
|
|
266
|
+
/* @__PURE__ */ jsx("summary", { children: "Error Details" }),
|
|
267
|
+
/* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
|
|
268
|
+
this.state.error.message,
|
|
269
|
+
"\n",
|
|
270
|
+
this.state.error.stack
|
|
271
|
+
] }),
|
|
272
|
+
this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
|
|
273
|
+
] }),
|
|
274
|
+
/* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
|
|
275
|
+
/* @__PURE__ */ jsx(
|
|
276
|
+
"button",
|
|
277
|
+
{
|
|
278
|
+
className: "error__button error__button--primary",
|
|
279
|
+
onClick: this.handleReset,
|
|
280
|
+
children: "Try Again"
|
|
281
|
+
}
|
|
282
|
+
),
|
|
283
|
+
/* @__PURE__ */ jsx(
|
|
284
|
+
"button",
|
|
285
|
+
{
|
|
286
|
+
className: "error__button error__button--secondary",
|
|
287
|
+
onClick: () => window.location.reload(),
|
|
288
|
+
children: "Reload Page"
|
|
289
|
+
}
|
|
290
|
+
)
|
|
291
|
+
] })
|
|
292
|
+
] }) });
|
|
293
|
+
}
|
|
294
|
+
return this.props.children;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
function detectAssetRoot(modelUrl) {
|
|
298
|
+
const idx = modelUrl.indexOf("/3d/");
|
|
299
|
+
if (idx !== -1) {
|
|
300
|
+
return modelUrl.substring(0, idx + 4);
|
|
301
|
+
}
|
|
302
|
+
return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
|
|
303
|
+
}
|
|
304
|
+
function useGLTFModel(url, resourceBasePath) {
|
|
305
|
+
const [state, setState] = useState({
|
|
306
|
+
model: null,
|
|
307
|
+
isLoading: false,
|
|
308
|
+
error: null
|
|
309
|
+
});
|
|
310
|
+
useEffect(() => {
|
|
311
|
+
if (!url) {
|
|
312
|
+
setState({ model: null, isLoading: false, error: null });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
console.log("[ModelLoader] Loading:", url);
|
|
316
|
+
setState((prev) => ({ ...prev, isLoading: true, error: null }));
|
|
317
|
+
const assetRoot = resourceBasePath || detectAssetRoot(url);
|
|
318
|
+
const loader = new GLTFLoader$1();
|
|
319
|
+
loader.setResourcePath(assetRoot);
|
|
320
|
+
loader.load(
|
|
321
|
+
url,
|
|
322
|
+
(gltf) => {
|
|
323
|
+
console.log("[ModelLoader] Loaded:", url);
|
|
324
|
+
setState({
|
|
325
|
+
model: gltf.scene,
|
|
326
|
+
isLoading: false,
|
|
327
|
+
error: null
|
|
328
|
+
});
|
|
329
|
+
},
|
|
330
|
+
void 0,
|
|
331
|
+
(err) => {
|
|
332
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
333
|
+
console.warn("[ModelLoader] Failed:", url, errorMsg);
|
|
334
|
+
setState({
|
|
335
|
+
model: null,
|
|
336
|
+
isLoading: false,
|
|
337
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
);
|
|
341
|
+
}, [url, resourceBasePath]);
|
|
342
|
+
return state;
|
|
343
|
+
}
|
|
344
|
+
function ModelLoader({
|
|
345
|
+
url,
|
|
346
|
+
position = [0, 0, 0],
|
|
347
|
+
scale = 1,
|
|
348
|
+
rotation = [0, 0, 0],
|
|
349
|
+
isSelected = false,
|
|
350
|
+
isHovered = false,
|
|
351
|
+
onClick,
|
|
352
|
+
onHover,
|
|
353
|
+
fallbackGeometry = "box",
|
|
354
|
+
castShadow = true,
|
|
355
|
+
receiveShadow = true,
|
|
356
|
+
resourceBasePath
|
|
357
|
+
}) {
|
|
358
|
+
const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
|
|
359
|
+
const model = useMemo(() => {
|
|
360
|
+
if (!loadedModel) return null;
|
|
361
|
+
const cloned = loadedModel.clone();
|
|
362
|
+
cloned.traverse((child) => {
|
|
363
|
+
if (child instanceof THREE6.Mesh) {
|
|
364
|
+
child.castShadow = castShadow;
|
|
365
|
+
child.receiveShadow = receiveShadow;
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
return cloned;
|
|
369
|
+
}, [loadedModel, castShadow, receiveShadow]);
|
|
370
|
+
const scaleArray = useMemo(() => {
|
|
371
|
+
if (typeof scale === "number") {
|
|
372
|
+
return [scale, scale, scale];
|
|
373
|
+
}
|
|
374
|
+
return scale;
|
|
375
|
+
}, [scale]);
|
|
376
|
+
const rotationRad = useMemo(() => {
|
|
377
|
+
return [
|
|
378
|
+
rotation[0] * Math.PI / 180,
|
|
379
|
+
rotation[1] * Math.PI / 180,
|
|
380
|
+
rotation[2] * Math.PI / 180
|
|
381
|
+
];
|
|
382
|
+
}, [rotation]);
|
|
383
|
+
if (isLoading) {
|
|
384
|
+
return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
|
|
385
|
+
/* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
|
|
386
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
|
|
387
|
+
] }) });
|
|
388
|
+
}
|
|
389
|
+
if (error || !model) {
|
|
390
|
+
if (fallbackGeometry === "none") {
|
|
391
|
+
return /* @__PURE__ */ jsx("group", { position });
|
|
392
|
+
}
|
|
393
|
+
const fallbackProps = {
|
|
394
|
+
onClick,
|
|
395
|
+
onPointerOver: () => onHover?.(true),
|
|
396
|
+
onPointerOut: () => onHover?.(false)
|
|
397
|
+
};
|
|
398
|
+
return /* @__PURE__ */ jsxs("group", { position, children: [
|
|
399
|
+
(isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
|
|
400
|
+
/* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
|
|
401
|
+
/* @__PURE__ */ jsx(
|
|
402
|
+
"meshBasicMaterial",
|
|
403
|
+
{
|
|
404
|
+
color: isSelected ? 16755200 : 16777215,
|
|
405
|
+
transparent: true,
|
|
406
|
+
opacity: 0.5
|
|
407
|
+
}
|
|
408
|
+
)
|
|
409
|
+
] }),
|
|
410
|
+
fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
|
|
411
|
+
/* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
|
|
412
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
|
|
413
|
+
] }),
|
|
414
|
+
fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
|
|
415
|
+
/* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
|
|
416
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
|
|
417
|
+
] }),
|
|
418
|
+
fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
|
|
419
|
+
/* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
|
|
420
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
|
|
421
|
+
] })
|
|
422
|
+
] });
|
|
423
|
+
}
|
|
424
|
+
return /* @__PURE__ */ jsxs(
|
|
425
|
+
"group",
|
|
426
|
+
{
|
|
427
|
+
position,
|
|
428
|
+
rotation: rotationRad,
|
|
429
|
+
onClick,
|
|
430
|
+
onPointerOver: () => onHover?.(true),
|
|
431
|
+
onPointerOut: () => onHover?.(false),
|
|
432
|
+
children: [
|
|
433
|
+
(isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
|
|
434
|
+
/* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
|
|
435
|
+
/* @__PURE__ */ jsx(
|
|
436
|
+
"meshBasicMaterial",
|
|
437
|
+
{
|
|
438
|
+
color: isSelected ? 16755200 : 16777215,
|
|
439
|
+
transparent: true,
|
|
440
|
+
opacity: 0.5
|
|
441
|
+
}
|
|
442
|
+
)
|
|
443
|
+
] }),
|
|
444
|
+
/* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
|
|
445
|
+
]
|
|
446
|
+
}
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
function PhysicsObject3D({
|
|
450
|
+
entityId,
|
|
451
|
+
modelUrl,
|
|
452
|
+
initialPosition = [0, 0, 0],
|
|
453
|
+
initialVelocity = [0, 0, 0],
|
|
454
|
+
mass = 1,
|
|
455
|
+
gravity = 9.8,
|
|
456
|
+
groundY = 0,
|
|
457
|
+
scale = 1,
|
|
458
|
+
onPhysicsUpdate,
|
|
459
|
+
onGroundHit,
|
|
460
|
+
onCollision
|
|
461
|
+
}) {
|
|
462
|
+
const groupRef = useRef(null);
|
|
463
|
+
const physicsStateRef = useRef({
|
|
464
|
+
id: entityId,
|
|
465
|
+
x: initialPosition[0],
|
|
466
|
+
y: initialPosition[1],
|
|
467
|
+
z: initialPosition[2],
|
|
468
|
+
vx: initialVelocity[0],
|
|
469
|
+
vy: initialVelocity[1],
|
|
470
|
+
vz: initialVelocity[2],
|
|
471
|
+
rx: 0,
|
|
472
|
+
ry: 0,
|
|
473
|
+
rz: 0,
|
|
474
|
+
isGrounded: false,
|
|
475
|
+
gravity,
|
|
476
|
+
friction: 0.8,
|
|
477
|
+
mass,
|
|
478
|
+
state: "Active"
|
|
479
|
+
});
|
|
480
|
+
const groundHitRef = useRef(false);
|
|
481
|
+
useEffect(() => {
|
|
482
|
+
if (groupRef.current) {
|
|
483
|
+
groupRef.current.position.set(
|
|
484
|
+
initialPosition[0],
|
|
485
|
+
initialPosition[1],
|
|
486
|
+
initialPosition[2]
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
}, []);
|
|
490
|
+
useFrame((state, delta) => {
|
|
491
|
+
const physics = physicsStateRef.current;
|
|
492
|
+
if (physics.state !== "Active") return;
|
|
493
|
+
const dt = Math.min(delta, 0.1);
|
|
494
|
+
if (!physics.isGrounded) {
|
|
495
|
+
physics.vy -= physics.gravity * dt;
|
|
496
|
+
}
|
|
497
|
+
physics.x += physics.vx * dt;
|
|
498
|
+
physics.y += physics.vy * dt;
|
|
499
|
+
physics.z += physics.vz * dt;
|
|
500
|
+
const airResistance = Math.pow(0.99, dt * 60);
|
|
501
|
+
physics.vx *= airResistance;
|
|
502
|
+
physics.vz *= airResistance;
|
|
503
|
+
if (physics.y <= groundY) {
|
|
504
|
+
physics.y = groundY;
|
|
505
|
+
if (!physics.isGrounded) {
|
|
506
|
+
physics.isGrounded = true;
|
|
507
|
+
groundHitRef.current = true;
|
|
508
|
+
physics.vx *= physics.friction;
|
|
509
|
+
physics.vz *= physics.friction;
|
|
510
|
+
onGroundHit?.();
|
|
511
|
+
}
|
|
512
|
+
physics.vy = 0;
|
|
513
|
+
} else {
|
|
514
|
+
physics.isGrounded = false;
|
|
515
|
+
}
|
|
516
|
+
if (groupRef.current) {
|
|
517
|
+
groupRef.current.position.set(physics.x, physics.y, physics.z);
|
|
518
|
+
if (!physics.isGrounded) {
|
|
519
|
+
physics.rx += physics.vz * dt * 0.5;
|
|
520
|
+
physics.rz -= physics.vx * dt * 0.5;
|
|
521
|
+
groupRef.current.rotation.set(physics.rx, physics.ry, physics.rz);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
onPhysicsUpdate?.({ ...physics });
|
|
525
|
+
});
|
|
526
|
+
const scaleArray = typeof scale === "number" ? [scale, scale, scale] : scale;
|
|
527
|
+
return /* @__PURE__ */ jsx("group", { ref: groupRef, scale: scaleArray, children: /* @__PURE__ */ jsx(
|
|
528
|
+
ModelLoader,
|
|
529
|
+
{
|
|
530
|
+
url: modelUrl,
|
|
531
|
+
fallbackGeometry: "box"
|
|
532
|
+
}
|
|
533
|
+
) });
|
|
534
|
+
}
|
|
535
|
+
function usePhysics3DController(entityId) {
|
|
536
|
+
const applyForce = (fx, fy, fz) => {
|
|
537
|
+
console.log(`Apply force to ${entityId}:`, { fx, fy, fz });
|
|
538
|
+
};
|
|
539
|
+
const setVelocity = (vx, vy, vz) => {
|
|
540
|
+
console.log(`Set velocity for ${entityId}:`, { vx, vy, vz });
|
|
541
|
+
};
|
|
542
|
+
const setPosition = (x, y, z) => {
|
|
543
|
+
console.log(`Set position for ${entityId}:`, { x, y, z });
|
|
544
|
+
};
|
|
545
|
+
const jump = (force = 10) => {
|
|
546
|
+
applyForce(0, force, 0);
|
|
547
|
+
};
|
|
548
|
+
return {
|
|
549
|
+
applyForce,
|
|
550
|
+
setVelocity,
|
|
551
|
+
setPosition,
|
|
552
|
+
jump
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function detectAssetRoot2(modelUrl) {
|
|
556
|
+
const idx = modelUrl.indexOf("/3d/");
|
|
557
|
+
if (idx !== -1) {
|
|
558
|
+
return modelUrl.substring(0, idx + 4);
|
|
559
|
+
}
|
|
560
|
+
return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
|
|
561
|
+
}
|
|
562
|
+
function createGLTFLoaderForUrl(url) {
|
|
563
|
+
const loader = new GLTFLoader();
|
|
564
|
+
loader.setResourcePath(detectAssetRoot2(url));
|
|
565
|
+
return loader;
|
|
566
|
+
}
|
|
567
|
+
var AssetLoader = class {
|
|
568
|
+
constructor() {
|
|
569
|
+
__publicField(this, "objLoader");
|
|
570
|
+
__publicField(this, "textureLoader");
|
|
571
|
+
__publicField(this, "modelCache");
|
|
572
|
+
__publicField(this, "textureCache");
|
|
573
|
+
__publicField(this, "loadingPromises");
|
|
574
|
+
this.objLoader = new OBJLoader();
|
|
575
|
+
this.textureLoader = new THREE6.TextureLoader();
|
|
576
|
+
this.modelCache = /* @__PURE__ */ new Map();
|
|
577
|
+
this.textureCache = /* @__PURE__ */ new Map();
|
|
578
|
+
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Load a GLB/GLTF model
|
|
582
|
+
* @param url - URL to the .glb or .gltf file
|
|
583
|
+
* @returns Promise with loaded model scene and animations
|
|
584
|
+
*/
|
|
585
|
+
async loadModel(url) {
|
|
586
|
+
if (this.modelCache.has(url)) {
|
|
587
|
+
return this.modelCache.get(url);
|
|
588
|
+
}
|
|
589
|
+
if (this.loadingPromises.has(url)) {
|
|
590
|
+
return this.loadingPromises.get(url);
|
|
591
|
+
}
|
|
592
|
+
const loader = createGLTFLoaderForUrl(url);
|
|
593
|
+
const loadPromise = loader.loadAsync(url).then((gltf) => {
|
|
594
|
+
const result = {
|
|
595
|
+
scene: gltf.scene,
|
|
596
|
+
animations: gltf.animations || []
|
|
597
|
+
};
|
|
598
|
+
this.modelCache.set(url, result);
|
|
599
|
+
this.loadingPromises.delete(url);
|
|
600
|
+
return result;
|
|
601
|
+
}).catch((error) => {
|
|
602
|
+
this.loadingPromises.delete(url);
|
|
603
|
+
throw new Error(`Failed to load model ${url}: ${error.message}`);
|
|
604
|
+
});
|
|
605
|
+
this.loadingPromises.set(url, loadPromise);
|
|
606
|
+
return loadPromise;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Load an OBJ model (fallback for non-GLB assets)
|
|
610
|
+
* @param url - URL to the .obj file
|
|
611
|
+
* @returns Promise with loaded object group
|
|
612
|
+
*/
|
|
613
|
+
async loadOBJ(url) {
|
|
614
|
+
if (this.modelCache.has(url)) {
|
|
615
|
+
return this.modelCache.get(url).scene;
|
|
616
|
+
}
|
|
617
|
+
if (this.loadingPromises.has(url)) {
|
|
618
|
+
const result = await this.loadingPromises.get(url);
|
|
619
|
+
return result.scene;
|
|
620
|
+
}
|
|
621
|
+
const loadPromise = this.objLoader.loadAsync(url).then((group) => {
|
|
622
|
+
const result = {
|
|
623
|
+
scene: group,
|
|
624
|
+
animations: []
|
|
625
|
+
};
|
|
626
|
+
this.modelCache.set(url, result);
|
|
627
|
+
this.loadingPromises.delete(url);
|
|
628
|
+
return result;
|
|
629
|
+
}).catch((error) => {
|
|
630
|
+
this.loadingPromises.delete(url);
|
|
631
|
+
throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
|
|
632
|
+
});
|
|
633
|
+
this.loadingPromises.set(url, loadPromise);
|
|
634
|
+
return (await loadPromise).scene;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Load a texture
|
|
638
|
+
* @param url - URL to the texture image
|
|
639
|
+
* @returns Promise with loaded texture
|
|
640
|
+
*/
|
|
641
|
+
async loadTexture(url) {
|
|
642
|
+
if (this.textureCache.has(url)) {
|
|
643
|
+
return this.textureCache.get(url);
|
|
644
|
+
}
|
|
645
|
+
if (this.loadingPromises.has(`texture:${url}`)) {
|
|
646
|
+
return this.loadingPromises.get(`texture:${url}`);
|
|
647
|
+
}
|
|
648
|
+
const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
|
|
649
|
+
texture.colorSpace = THREE6.SRGBColorSpace;
|
|
650
|
+
this.textureCache.set(url, texture);
|
|
651
|
+
this.loadingPromises.delete(`texture:${url}`);
|
|
652
|
+
return texture;
|
|
653
|
+
}).catch((error) => {
|
|
654
|
+
this.loadingPromises.delete(`texture:${url}`);
|
|
655
|
+
throw new Error(`Failed to load texture ${url}: ${error.message}`);
|
|
656
|
+
});
|
|
657
|
+
this.loadingPromises.set(`texture:${url}`, loadPromise);
|
|
658
|
+
return loadPromise;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Preload multiple assets
|
|
662
|
+
* @param urls - Array of asset URLs to preload
|
|
663
|
+
* @returns Promise that resolves when all assets are loaded
|
|
664
|
+
*/
|
|
665
|
+
async preload(urls) {
|
|
666
|
+
const promises = urls.map((url) => {
|
|
667
|
+
if (url.endsWith(".glb") || url.endsWith(".gltf")) {
|
|
668
|
+
return this.loadModel(url).catch(() => null);
|
|
669
|
+
} else if (url.endsWith(".obj")) {
|
|
670
|
+
return this.loadOBJ(url).catch(() => null);
|
|
671
|
+
} else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
|
|
672
|
+
return this.loadTexture(url).catch(() => null);
|
|
673
|
+
}
|
|
674
|
+
return Promise.resolve(null);
|
|
675
|
+
});
|
|
676
|
+
await Promise.all(promises);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Check if a model is cached
|
|
680
|
+
* @param url - Model URL
|
|
681
|
+
*/
|
|
682
|
+
hasModel(url) {
|
|
683
|
+
return this.modelCache.has(url);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Check if a texture is cached
|
|
687
|
+
* @param url - Texture URL
|
|
688
|
+
*/
|
|
689
|
+
hasTexture(url) {
|
|
690
|
+
return this.textureCache.has(url);
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Get cached model (throws if not cached)
|
|
694
|
+
* @param url - Model URL
|
|
695
|
+
*/
|
|
696
|
+
getModel(url) {
|
|
697
|
+
const model = this.modelCache.get(url);
|
|
698
|
+
if (!model) {
|
|
699
|
+
throw new Error(`Model ${url} not in cache`);
|
|
700
|
+
}
|
|
701
|
+
return model;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Get cached texture (throws if not cached)
|
|
705
|
+
* @param url - Texture URL
|
|
706
|
+
*/
|
|
707
|
+
getTexture(url) {
|
|
708
|
+
const texture = this.textureCache.get(url);
|
|
709
|
+
if (!texture) {
|
|
710
|
+
throw new Error(`Texture ${url} not in cache`);
|
|
711
|
+
}
|
|
712
|
+
return texture;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Clear all caches
|
|
716
|
+
*/
|
|
717
|
+
clearCache() {
|
|
718
|
+
this.textureCache.forEach((texture) => {
|
|
719
|
+
texture.dispose();
|
|
720
|
+
});
|
|
721
|
+
this.modelCache.forEach((model) => {
|
|
722
|
+
model.scene.traverse((child) => {
|
|
723
|
+
if (child instanceof THREE6.Mesh) {
|
|
724
|
+
child.geometry.dispose();
|
|
725
|
+
if (Array.isArray(child.material)) {
|
|
726
|
+
child.material.forEach((m) => m.dispose());
|
|
727
|
+
} else {
|
|
728
|
+
child.material.dispose();
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
this.modelCache.clear();
|
|
734
|
+
this.textureCache.clear();
|
|
735
|
+
this.loadingPromises.clear();
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Get cache statistics
|
|
739
|
+
*/
|
|
740
|
+
getStats() {
|
|
741
|
+
return {
|
|
742
|
+
models: this.modelCache.size,
|
|
743
|
+
textures: this.textureCache.size,
|
|
744
|
+
loading: this.loadingPromises.size
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
var assetLoader = new AssetLoader();
|
|
749
|
+
|
|
750
|
+
// components/organisms/game/three/hooks/useThree.ts
|
|
9
751
|
var DEFAULT_OPTIONS = {
|
|
10
752
|
cameraMode: "isometric",
|
|
11
753
|
cameraPosition: [10, 10, 10],
|
|
@@ -15,7 +757,7 @@ var DEFAULT_OPTIONS = {
|
|
|
15
757
|
gridSize: 20,
|
|
16
758
|
assetLoader: new AssetLoader()
|
|
17
759
|
};
|
|
18
|
-
function
|
|
760
|
+
function useThree3(options = {}) {
|
|
19
761
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
20
762
|
const containerRef = useRef(null);
|
|
21
763
|
const canvasRef = useRef(null);
|
|
@@ -28,21 +770,21 @@ function useThree(options = {}) {
|
|
|
28
770
|
const [isReady, setIsReady] = useState(false);
|
|
29
771
|
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
|
30
772
|
const initialCameraPosition = useMemo(
|
|
31
|
-
() => new
|
|
773
|
+
() => new THREE6.Vector3(...opts.cameraPosition),
|
|
32
774
|
[]
|
|
33
775
|
);
|
|
34
776
|
useEffect(() => {
|
|
35
777
|
if (!containerRef.current) return;
|
|
36
778
|
const container = containerRef.current;
|
|
37
779
|
const { clientWidth, clientHeight } = container;
|
|
38
|
-
const scene = new
|
|
39
|
-
scene.background = new
|
|
780
|
+
const scene = new THREE6.Scene();
|
|
781
|
+
scene.background = new THREE6.Color(opts.backgroundColor);
|
|
40
782
|
sceneRef.current = scene;
|
|
41
783
|
let camera;
|
|
42
784
|
const aspect = clientWidth / clientHeight;
|
|
43
785
|
if (opts.cameraMode === "isometric") {
|
|
44
786
|
const size = 10;
|
|
45
|
-
camera = new
|
|
787
|
+
camera = new THREE6.OrthographicCamera(
|
|
46
788
|
-size * aspect,
|
|
47
789
|
size * aspect,
|
|
48
790
|
size,
|
|
@@ -51,11 +793,11 @@ function useThree(options = {}) {
|
|
|
51
793
|
1e3
|
|
52
794
|
);
|
|
53
795
|
} else {
|
|
54
|
-
camera = new
|
|
796
|
+
camera = new THREE6.PerspectiveCamera(45, aspect, 0.1, 1e3);
|
|
55
797
|
}
|
|
56
798
|
camera.position.copy(initialCameraPosition);
|
|
57
799
|
cameraRef.current = camera;
|
|
58
|
-
const renderer = new
|
|
800
|
+
const renderer = new THREE6.WebGLRenderer({
|
|
59
801
|
antialias: true,
|
|
60
802
|
alpha: true,
|
|
61
803
|
canvas: canvasRef.current || void 0
|
|
@@ -63,25 +805,25 @@ function useThree(options = {}) {
|
|
|
63
805
|
renderer.setSize(clientWidth, clientHeight);
|
|
64
806
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
65
807
|
renderer.shadowMap.enabled = opts.shadows;
|
|
66
|
-
renderer.shadowMap.type =
|
|
808
|
+
renderer.shadowMap.type = THREE6.PCFSoftShadowMap;
|
|
67
809
|
rendererRef.current = renderer;
|
|
68
|
-
const controls = new OrbitControls(camera, renderer.domElement);
|
|
810
|
+
const controls = new OrbitControls$1(camera, renderer.domElement);
|
|
69
811
|
controls.enableDamping = true;
|
|
70
812
|
controls.dampingFactor = 0.05;
|
|
71
813
|
controls.minDistance = 2;
|
|
72
814
|
controls.maxDistance = 100;
|
|
73
815
|
controls.maxPolarAngle = Math.PI / 2 - 0.1;
|
|
74
816
|
controlsRef.current = controls;
|
|
75
|
-
const ambientLight = new
|
|
817
|
+
const ambientLight = new THREE6.AmbientLight(16777215, 0.6);
|
|
76
818
|
scene.add(ambientLight);
|
|
77
|
-
const directionalLight = new
|
|
819
|
+
const directionalLight = new THREE6.DirectionalLight(16777215, 0.8);
|
|
78
820
|
directionalLight.position.set(10, 20, 10);
|
|
79
821
|
directionalLight.castShadow = opts.shadows;
|
|
80
822
|
directionalLight.shadow.mapSize.width = 2048;
|
|
81
823
|
directionalLight.shadow.mapSize.height = 2048;
|
|
82
824
|
scene.add(directionalLight);
|
|
83
825
|
if (opts.showGrid) {
|
|
84
|
-
const gridHelper = new
|
|
826
|
+
const gridHelper = new THREE6.GridHelper(
|
|
85
827
|
opts.gridSize,
|
|
86
828
|
opts.gridSize,
|
|
87
829
|
4473924,
|
|
@@ -99,10 +841,10 @@ function useThree(options = {}) {
|
|
|
99
841
|
const handleResize = () => {
|
|
100
842
|
const { clientWidth: width, clientHeight: height } = container;
|
|
101
843
|
setDimensions({ width, height });
|
|
102
|
-
if (camera instanceof
|
|
844
|
+
if (camera instanceof THREE6.PerspectiveCamera) {
|
|
103
845
|
camera.aspect = width / height;
|
|
104
846
|
camera.updateProjectionMatrix();
|
|
105
|
-
} else if (camera instanceof
|
|
847
|
+
} else if (camera instanceof THREE6.OrthographicCamera) {
|
|
106
848
|
const aspect2 = width / height;
|
|
107
849
|
const size = 10;
|
|
108
850
|
camera.left = -size * aspect2;
|
|
@@ -133,7 +875,7 @@ function useThree(options = {}) {
|
|
|
133
875
|
let newCamera;
|
|
134
876
|
if (opts.cameraMode === "isometric") {
|
|
135
877
|
const size = 10;
|
|
136
|
-
newCamera = new
|
|
878
|
+
newCamera = new THREE6.OrthographicCamera(
|
|
137
879
|
-size * aspect,
|
|
138
880
|
size * aspect,
|
|
139
881
|
size,
|
|
@@ -142,7 +884,7 @@ function useThree(options = {}) {
|
|
|
142
884
|
1e3
|
|
143
885
|
);
|
|
144
886
|
} else {
|
|
145
|
-
newCamera = new
|
|
887
|
+
newCamera = new THREE6.PerspectiveCamera(45, aspect, 0.1, 1e3);
|
|
146
888
|
}
|
|
147
889
|
newCamera.position.copy(currentPos);
|
|
148
890
|
cameraRef.current = newCamera;
|
|
@@ -211,6 +953,180 @@ function useThree(options = {}) {
|
|
|
211
953
|
fitView
|
|
212
954
|
};
|
|
213
955
|
}
|
|
956
|
+
function useAssetLoader(options = {}) {
|
|
957
|
+
const { preloadUrls = [], loader: customLoader } = options;
|
|
958
|
+
const loaderRef = useRef(customLoader || new AssetLoader());
|
|
959
|
+
const [state, setState] = useState({
|
|
960
|
+
isLoading: false,
|
|
961
|
+
progress: 0,
|
|
962
|
+
loaded: 0,
|
|
963
|
+
total: 0,
|
|
964
|
+
errors: []
|
|
965
|
+
});
|
|
966
|
+
useEffect(() => {
|
|
967
|
+
if (preloadUrls.length > 0) {
|
|
968
|
+
preload(preloadUrls);
|
|
969
|
+
}
|
|
970
|
+
}, []);
|
|
971
|
+
const updateProgress = useCallback((loaded, total) => {
|
|
972
|
+
setState((prev) => ({
|
|
973
|
+
...prev,
|
|
974
|
+
loaded,
|
|
975
|
+
total,
|
|
976
|
+
progress: total > 0 ? Math.round(loaded / total * 100) : 0
|
|
977
|
+
}));
|
|
978
|
+
}, []);
|
|
979
|
+
const loadModel = useCallback(
|
|
980
|
+
async (url) => {
|
|
981
|
+
setState((prev) => ({ ...prev, isLoading: true }));
|
|
982
|
+
try {
|
|
983
|
+
const model = await loaderRef.current.loadModel(url);
|
|
984
|
+
setState((prev) => ({
|
|
985
|
+
...prev,
|
|
986
|
+
isLoading: false,
|
|
987
|
+
loaded: prev.loaded + 1
|
|
988
|
+
}));
|
|
989
|
+
return model;
|
|
990
|
+
} catch (error) {
|
|
991
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
992
|
+
setState((prev) => ({
|
|
993
|
+
...prev,
|
|
994
|
+
isLoading: false,
|
|
995
|
+
errors: [...prev.errors, errorMsg]
|
|
996
|
+
}));
|
|
997
|
+
throw error;
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
[]
|
|
1001
|
+
);
|
|
1002
|
+
const loadOBJ = useCallback(
|
|
1003
|
+
async (url) => {
|
|
1004
|
+
setState((prev) => ({ ...prev, isLoading: true }));
|
|
1005
|
+
try {
|
|
1006
|
+
const model = await loaderRef.current.loadOBJ(url);
|
|
1007
|
+
setState((prev) => ({
|
|
1008
|
+
...prev,
|
|
1009
|
+
isLoading: false,
|
|
1010
|
+
loaded: prev.loaded + 1
|
|
1011
|
+
}));
|
|
1012
|
+
return model;
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1015
|
+
setState((prev) => ({
|
|
1016
|
+
...prev,
|
|
1017
|
+
isLoading: false,
|
|
1018
|
+
errors: [...prev.errors, errorMsg]
|
|
1019
|
+
}));
|
|
1020
|
+
throw error;
|
|
1021
|
+
}
|
|
1022
|
+
},
|
|
1023
|
+
[]
|
|
1024
|
+
);
|
|
1025
|
+
const loadTexture = useCallback(
|
|
1026
|
+
async (url) => {
|
|
1027
|
+
setState((prev) => ({ ...prev, isLoading: true }));
|
|
1028
|
+
try {
|
|
1029
|
+
const texture = await loaderRef.current.loadTexture(url);
|
|
1030
|
+
setState((prev) => ({
|
|
1031
|
+
...prev,
|
|
1032
|
+
isLoading: false,
|
|
1033
|
+
loaded: prev.loaded + 1
|
|
1034
|
+
}));
|
|
1035
|
+
return texture;
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1038
|
+
setState((prev) => ({
|
|
1039
|
+
...prev,
|
|
1040
|
+
isLoading: false,
|
|
1041
|
+
errors: [...prev.errors, errorMsg]
|
|
1042
|
+
}));
|
|
1043
|
+
throw error;
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
[]
|
|
1047
|
+
);
|
|
1048
|
+
const preload = useCallback(
|
|
1049
|
+
async (urls) => {
|
|
1050
|
+
setState((prev) => ({
|
|
1051
|
+
...prev,
|
|
1052
|
+
isLoading: true,
|
|
1053
|
+
total: urls.length,
|
|
1054
|
+
loaded: 0,
|
|
1055
|
+
errors: []
|
|
1056
|
+
}));
|
|
1057
|
+
let completed = 0;
|
|
1058
|
+
const errors = [];
|
|
1059
|
+
await Promise.all(
|
|
1060
|
+
urls.map(async (url) => {
|
|
1061
|
+
try {
|
|
1062
|
+
if (url.endsWith(".glb") || url.endsWith(".gltf")) {
|
|
1063
|
+
await loaderRef.current.loadModel(url);
|
|
1064
|
+
} else if (url.endsWith(".obj")) {
|
|
1065
|
+
await loaderRef.current.loadOBJ(url);
|
|
1066
|
+
} else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
|
|
1067
|
+
await loaderRef.current.loadTexture(url);
|
|
1068
|
+
}
|
|
1069
|
+
completed++;
|
|
1070
|
+
updateProgress(completed, urls.length);
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1073
|
+
errors.push(`${url}: ${errorMsg}`);
|
|
1074
|
+
completed++;
|
|
1075
|
+
updateProgress(completed, urls.length);
|
|
1076
|
+
}
|
|
1077
|
+
})
|
|
1078
|
+
);
|
|
1079
|
+
setState((prev) => ({
|
|
1080
|
+
...prev,
|
|
1081
|
+
isLoading: false,
|
|
1082
|
+
errors
|
|
1083
|
+
}));
|
|
1084
|
+
},
|
|
1085
|
+
[updateProgress]
|
|
1086
|
+
);
|
|
1087
|
+
const hasModel = useCallback((url) => {
|
|
1088
|
+
return loaderRef.current.hasModel(url);
|
|
1089
|
+
}, []);
|
|
1090
|
+
const hasTexture = useCallback((url) => {
|
|
1091
|
+
return loaderRef.current.hasTexture(url);
|
|
1092
|
+
}, []);
|
|
1093
|
+
const getModel = useCallback((url) => {
|
|
1094
|
+
try {
|
|
1095
|
+
return loaderRef.current.getModel(url);
|
|
1096
|
+
} catch {
|
|
1097
|
+
return void 0;
|
|
1098
|
+
}
|
|
1099
|
+
}, []);
|
|
1100
|
+
const getTexture = useCallback((url) => {
|
|
1101
|
+
try {
|
|
1102
|
+
return loaderRef.current.getTexture(url);
|
|
1103
|
+
} catch {
|
|
1104
|
+
return void 0;
|
|
1105
|
+
}
|
|
1106
|
+
}, []);
|
|
1107
|
+
const clearCache = useCallback(() => {
|
|
1108
|
+
loaderRef.current.clearCache();
|
|
1109
|
+
setState({
|
|
1110
|
+
isLoading: false,
|
|
1111
|
+
progress: 0,
|
|
1112
|
+
loaded: 0,
|
|
1113
|
+
total: 0,
|
|
1114
|
+
errors: []
|
|
1115
|
+
});
|
|
1116
|
+
}, []);
|
|
1117
|
+
return {
|
|
1118
|
+
...state,
|
|
1119
|
+
loadModel,
|
|
1120
|
+
loadOBJ,
|
|
1121
|
+
loadTexture,
|
|
1122
|
+
preload,
|
|
1123
|
+
hasModel,
|
|
1124
|
+
hasTexture,
|
|
1125
|
+
getModel,
|
|
1126
|
+
getTexture,
|
|
1127
|
+
clearCache
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
214
1130
|
function useSceneGraph() {
|
|
215
1131
|
const nodesRef = useRef(/* @__PURE__ */ new Map());
|
|
216
1132
|
const addNode = useCallback((node) => {
|
|
@@ -298,8 +1214,8 @@ function useSceneGraph() {
|
|
|
298
1214
|
}
|
|
299
1215
|
function useRaycaster(options) {
|
|
300
1216
|
const { camera, canvas, cellSize = 1, offsetX = 0, offsetZ = 0 } = options;
|
|
301
|
-
const raycaster = useRef(new
|
|
302
|
-
const mouse = useRef(new
|
|
1217
|
+
const raycaster = useRef(new THREE6.Raycaster());
|
|
1218
|
+
const mouse = useRef(new THREE6.Vector2());
|
|
303
1219
|
const clientToNDC = useCallback(
|
|
304
1220
|
(clientX, clientY) => {
|
|
305
1221
|
if (!canvas) {
|
|
@@ -369,8 +1285,8 @@ function useRaycaster(options) {
|
|
|
369
1285
|
const ndc = clientToNDC(clientX, clientY);
|
|
370
1286
|
mouse.current.set(ndc.x, ndc.y);
|
|
371
1287
|
raycaster.current.setFromCamera(mouse.current, camera);
|
|
372
|
-
const plane = new
|
|
373
|
-
const target = new
|
|
1288
|
+
const plane = new THREE6.Plane(new THREE6.Vector3(0, 1, 0), 0);
|
|
1289
|
+
const target = new THREE6.Vector3();
|
|
374
1290
|
const intersection = raycaster.current.ray.intersectPlane(plane, target);
|
|
375
1291
|
if (intersection) {
|
|
376
1292
|
const gridX = Math.round((target.x - offsetX) / cellSize);
|
|
@@ -408,7 +1324,7 @@ function useRaycaster(options) {
|
|
|
408
1324
|
return {
|
|
409
1325
|
gridX: gridCoords.x,
|
|
410
1326
|
gridZ: gridCoords.z,
|
|
411
|
-
worldPosition: new
|
|
1327
|
+
worldPosition: new THREE6.Vector3(
|
|
412
1328
|
gridCoords.x * cellSize + offsetX,
|
|
413
1329
|
0,
|
|
414
1330
|
gridCoords.z * cellSize + offsetZ
|
|
@@ -430,6 +1346,835 @@ function useRaycaster(options) {
|
|
|
430
1346
|
isWithinCanvas
|
|
431
1347
|
};
|
|
432
1348
|
}
|
|
1349
|
+
var EventBusContext = createContext(null);
|
|
1350
|
+
|
|
1351
|
+
// hooks/useEventBus.ts
|
|
1352
|
+
function getGlobalEventBus() {
|
|
1353
|
+
if (typeof window !== "undefined") {
|
|
1354
|
+
return window.__kflowEventBus ?? null;
|
|
1355
|
+
}
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1358
|
+
var fallbackListeners = /* @__PURE__ */ new Map();
|
|
1359
|
+
var fallbackAnyListeners = /* @__PURE__ */ new Set();
|
|
1360
|
+
var fallbackEventBus = {
|
|
1361
|
+
emit: (type, payload) => {
|
|
1362
|
+
const event = {
|
|
1363
|
+
type,
|
|
1364
|
+
payload,
|
|
1365
|
+
timestamp: Date.now()
|
|
1366
|
+
};
|
|
1367
|
+
const handlers = fallbackListeners.get(type);
|
|
1368
|
+
if (handlers) {
|
|
1369
|
+
handlers.forEach((handler) => {
|
|
1370
|
+
try {
|
|
1371
|
+
handler(event);
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
console.error(`[EventBus] Error in listener for '${type}':`, error);
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
fallbackAnyListeners.forEach((handler) => {
|
|
1378
|
+
try {
|
|
1379
|
+
handler(event);
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
console.error(`[EventBus] Error in onAny listener for '${type}':`, error);
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
},
|
|
1385
|
+
on: (type, listener) => {
|
|
1386
|
+
if (!fallbackListeners.has(type)) {
|
|
1387
|
+
fallbackListeners.set(type, /* @__PURE__ */ new Set());
|
|
1388
|
+
}
|
|
1389
|
+
fallbackListeners.get(type).add(listener);
|
|
1390
|
+
return () => {
|
|
1391
|
+
const handlers = fallbackListeners.get(type);
|
|
1392
|
+
if (handlers) {
|
|
1393
|
+
handlers.delete(listener);
|
|
1394
|
+
if (handlers.size === 0) {
|
|
1395
|
+
fallbackListeners.delete(type);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
},
|
|
1400
|
+
once: (type, listener) => {
|
|
1401
|
+
const wrappedListener = (event) => {
|
|
1402
|
+
fallbackListeners.get(type)?.delete(wrappedListener);
|
|
1403
|
+
listener(event);
|
|
1404
|
+
};
|
|
1405
|
+
return fallbackEventBus.on(type, wrappedListener);
|
|
1406
|
+
},
|
|
1407
|
+
hasListeners: (type) => {
|
|
1408
|
+
const handlers = fallbackListeners.get(type);
|
|
1409
|
+
return handlers !== void 0 && handlers.size > 0;
|
|
1410
|
+
},
|
|
1411
|
+
onAny: (listener) => {
|
|
1412
|
+
fallbackAnyListeners.add(listener);
|
|
1413
|
+
return () => {
|
|
1414
|
+
fallbackAnyListeners.delete(listener);
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
function useEventBus() {
|
|
1419
|
+
const context = useContext(EventBusContext);
|
|
1420
|
+
return context ?? getGlobalEventBus() ?? fallbackEventBus;
|
|
1421
|
+
}
|
|
1422
|
+
function useEmitEvent() {
|
|
1423
|
+
const eventBus = useEventBus();
|
|
1424
|
+
return useCallback(
|
|
1425
|
+
(type, payload) => {
|
|
1426
|
+
eventBus.emit(type, payload);
|
|
1427
|
+
},
|
|
1428
|
+
[eventBus]
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// components/organisms/game/three/hooks/useGameCanvas3DEvents.ts
|
|
1433
|
+
function useGameCanvas3DEvents(options) {
|
|
1434
|
+
const {
|
|
1435
|
+
tileClickEvent,
|
|
1436
|
+
unitClickEvent,
|
|
1437
|
+
featureClickEvent,
|
|
1438
|
+
canvasClickEvent,
|
|
1439
|
+
tileHoverEvent,
|
|
1440
|
+
tileLeaveEvent,
|
|
1441
|
+
unitAnimationEvent,
|
|
1442
|
+
cameraChangeEvent,
|
|
1443
|
+
onTileClick,
|
|
1444
|
+
onUnitClick,
|
|
1445
|
+
onFeatureClick,
|
|
1446
|
+
onCanvasClick,
|
|
1447
|
+
onTileHover,
|
|
1448
|
+
onUnitAnimation
|
|
1449
|
+
} = options;
|
|
1450
|
+
const emit = useEmitEvent();
|
|
1451
|
+
const optionsRef = useRef(options);
|
|
1452
|
+
optionsRef.current = options;
|
|
1453
|
+
const handleTileClick = useCallback(
|
|
1454
|
+
(tile, event) => {
|
|
1455
|
+
if (tileClickEvent) {
|
|
1456
|
+
emit(tileClickEvent, {
|
|
1457
|
+
tileId: tile.id,
|
|
1458
|
+
x: tile.x,
|
|
1459
|
+
z: tile.z ?? tile.y ?? 0,
|
|
1460
|
+
type: tile.type,
|
|
1461
|
+
terrain: tile.terrain,
|
|
1462
|
+
elevation: tile.elevation
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
optionsRef.current.onTileClick?.(tile, event);
|
|
1466
|
+
},
|
|
1467
|
+
[tileClickEvent, emit]
|
|
1468
|
+
);
|
|
1469
|
+
const handleUnitClick = useCallback(
|
|
1470
|
+
(unit, event) => {
|
|
1471
|
+
if (unitClickEvent) {
|
|
1472
|
+
emit(unitClickEvent, {
|
|
1473
|
+
unitId: unit.id,
|
|
1474
|
+
x: unit.x,
|
|
1475
|
+
z: unit.z ?? unit.y ?? 0,
|
|
1476
|
+
unitType: unit.unitType,
|
|
1477
|
+
name: unit.name,
|
|
1478
|
+
team: unit.team,
|
|
1479
|
+
faction: unit.faction,
|
|
1480
|
+
health: unit.health,
|
|
1481
|
+
maxHealth: unit.maxHealth
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
optionsRef.current.onUnitClick?.(unit, event);
|
|
1485
|
+
},
|
|
1486
|
+
[unitClickEvent, emit]
|
|
1487
|
+
);
|
|
1488
|
+
const handleFeatureClick = useCallback(
|
|
1489
|
+
(feature, event) => {
|
|
1490
|
+
if (featureClickEvent) {
|
|
1491
|
+
emit(featureClickEvent, {
|
|
1492
|
+
featureId: feature.id,
|
|
1493
|
+
x: feature.x,
|
|
1494
|
+
z: feature.z ?? feature.y ?? 0,
|
|
1495
|
+
type: feature.type,
|
|
1496
|
+
elevation: feature.elevation
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
optionsRef.current.onFeatureClick?.(feature, event);
|
|
1500
|
+
},
|
|
1501
|
+
[featureClickEvent, emit]
|
|
1502
|
+
);
|
|
1503
|
+
const handleCanvasClick = useCallback(
|
|
1504
|
+
(event) => {
|
|
1505
|
+
if (canvasClickEvent) {
|
|
1506
|
+
emit(canvasClickEvent, {
|
|
1507
|
+
clientX: event.clientX,
|
|
1508
|
+
clientY: event.clientY,
|
|
1509
|
+
button: event.button
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
optionsRef.current.onCanvasClick?.(event);
|
|
1513
|
+
},
|
|
1514
|
+
[canvasClickEvent, emit]
|
|
1515
|
+
);
|
|
1516
|
+
const handleTileHover = useCallback(
|
|
1517
|
+
(tile, event) => {
|
|
1518
|
+
if (tile) {
|
|
1519
|
+
if (tileHoverEvent) {
|
|
1520
|
+
emit(tileHoverEvent, {
|
|
1521
|
+
tileId: tile.id,
|
|
1522
|
+
x: tile.x,
|
|
1523
|
+
z: tile.z ?? tile.y ?? 0,
|
|
1524
|
+
type: tile.type
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
} else {
|
|
1528
|
+
if (tileLeaveEvent) {
|
|
1529
|
+
emit(tileLeaveEvent, {});
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
optionsRef.current.onTileHover?.(tile, event);
|
|
1533
|
+
},
|
|
1534
|
+
[tileHoverEvent, tileLeaveEvent, emit]
|
|
1535
|
+
);
|
|
1536
|
+
const handleUnitAnimation = useCallback(
|
|
1537
|
+
(unitId, state) => {
|
|
1538
|
+
if (unitAnimationEvent) {
|
|
1539
|
+
emit(unitAnimationEvent, {
|
|
1540
|
+
unitId,
|
|
1541
|
+
state,
|
|
1542
|
+
timestamp: Date.now()
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
optionsRef.current.onUnitAnimation?.(unitId, state);
|
|
1546
|
+
},
|
|
1547
|
+
[unitAnimationEvent, emit]
|
|
1548
|
+
);
|
|
1549
|
+
const handleCameraChange = useCallback(
|
|
1550
|
+
(position) => {
|
|
1551
|
+
if (cameraChangeEvent) {
|
|
1552
|
+
emit(cameraChangeEvent, {
|
|
1553
|
+
position,
|
|
1554
|
+
timestamp: Date.now()
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
},
|
|
1558
|
+
[cameraChangeEvent, emit]
|
|
1559
|
+
);
|
|
1560
|
+
return {
|
|
1561
|
+
handleTileClick,
|
|
1562
|
+
handleUnitClick,
|
|
1563
|
+
handleFeatureClick,
|
|
1564
|
+
handleCanvasClick,
|
|
1565
|
+
handleTileHover,
|
|
1566
|
+
handleUnitAnimation,
|
|
1567
|
+
handleCameraChange
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
var DEFAULT_TERRAIN_COLORS = {
|
|
1571
|
+
grass: "#44aa44",
|
|
1572
|
+
dirt: "#8b7355",
|
|
1573
|
+
sand: "#ddcc88",
|
|
1574
|
+
water: "#4488cc",
|
|
1575
|
+
rock: "#888888",
|
|
1576
|
+
snow: "#eeeeee",
|
|
1577
|
+
forest: "#228b22",
|
|
1578
|
+
desert: "#d4a574",
|
|
1579
|
+
mountain: "#696969",
|
|
1580
|
+
swamp: "#556b2f"
|
|
1581
|
+
};
|
|
1582
|
+
function TileRenderer({
|
|
1583
|
+
tiles,
|
|
1584
|
+
cellSize = 1,
|
|
1585
|
+
offsetX = 0,
|
|
1586
|
+
offsetZ = 0,
|
|
1587
|
+
useInstancing = true,
|
|
1588
|
+
terrainColors = DEFAULT_TERRAIN_COLORS,
|
|
1589
|
+
onTileClick,
|
|
1590
|
+
onTileHover,
|
|
1591
|
+
selectedTileIds = [],
|
|
1592
|
+
validMoves = [],
|
|
1593
|
+
attackTargets = []
|
|
1594
|
+
}) {
|
|
1595
|
+
const meshRef = useRef(null);
|
|
1596
|
+
const geometry = useMemo(() => {
|
|
1597
|
+
return new THREE6.BoxGeometry(cellSize * 0.95, 0.2, cellSize * 0.95);
|
|
1598
|
+
}, [cellSize]);
|
|
1599
|
+
const material = useMemo(() => {
|
|
1600
|
+
return new THREE6.MeshStandardMaterial({
|
|
1601
|
+
roughness: 0.8,
|
|
1602
|
+
metalness: 0.1
|
|
1603
|
+
});
|
|
1604
|
+
}, []);
|
|
1605
|
+
const { positions, colors, tileMap } = useMemo(() => {
|
|
1606
|
+
const pos = [];
|
|
1607
|
+
const cols = [];
|
|
1608
|
+
const map = /* @__PURE__ */ new Map();
|
|
1609
|
+
tiles.forEach((tile) => {
|
|
1610
|
+
const x = (tile.x - offsetX) * cellSize;
|
|
1611
|
+
const z = ((tile.z ?? tile.y ?? 0) - offsetZ) * cellSize;
|
|
1612
|
+
const y = (tile.elevation ?? 0) * 0.1;
|
|
1613
|
+
pos.push(new THREE6.Vector3(x, y, z));
|
|
1614
|
+
const colorHex = terrainColors[tile.type || ""] || terrainColors[tile.terrain || ""] || "#808080";
|
|
1615
|
+
const color = new THREE6.Color(colorHex);
|
|
1616
|
+
const isValidMove = validMoves.some(
|
|
1617
|
+
(m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
|
|
1618
|
+
);
|
|
1619
|
+
const isAttackTarget = attackTargets.some(
|
|
1620
|
+
(m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
|
|
1621
|
+
);
|
|
1622
|
+
const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
|
|
1623
|
+
if (isSelected) {
|
|
1624
|
+
color.addScalar(0.3);
|
|
1625
|
+
} else if (isAttackTarget) {
|
|
1626
|
+
color.setHex(16729156);
|
|
1627
|
+
} else if (isValidMove) {
|
|
1628
|
+
color.setHex(4521796);
|
|
1629
|
+
}
|
|
1630
|
+
cols.push(color);
|
|
1631
|
+
map.set(`${tile.x},${tile.z ?? tile.y ?? 0}`, tile);
|
|
1632
|
+
});
|
|
1633
|
+
return { positions: pos, colors: cols, tileMap: map };
|
|
1634
|
+
}, [tiles, cellSize, offsetX, offsetZ, terrainColors, selectedTileIds, validMoves, attackTargets]);
|
|
1635
|
+
useEffect(() => {
|
|
1636
|
+
if (!meshRef.current || !useInstancing) return;
|
|
1637
|
+
const mesh = meshRef.current;
|
|
1638
|
+
mesh.count = positions.length;
|
|
1639
|
+
const dummy = new THREE6.Object3D();
|
|
1640
|
+
positions.forEach((pos, i) => {
|
|
1641
|
+
dummy.position.copy(pos);
|
|
1642
|
+
dummy.updateMatrix();
|
|
1643
|
+
mesh.setMatrixAt(i, dummy.matrix);
|
|
1644
|
+
if (mesh.setColorAt) {
|
|
1645
|
+
mesh.setColorAt(i, colors[i]);
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
1649
|
+
if (mesh.instanceColor) {
|
|
1650
|
+
mesh.instanceColor.needsUpdate = true;
|
|
1651
|
+
}
|
|
1652
|
+
}, [positions, colors, useInstancing]);
|
|
1653
|
+
const handlePointerMove = (e) => {
|
|
1654
|
+
if (!onTileHover) return;
|
|
1655
|
+
const instanceId = e.instanceId;
|
|
1656
|
+
if (instanceId !== void 0) {
|
|
1657
|
+
const pos = positions[instanceId];
|
|
1658
|
+
if (pos) {
|
|
1659
|
+
const gridX = Math.round(pos.x / cellSize + offsetX);
|
|
1660
|
+
const gridZ = Math.round(pos.z / cellSize + offsetZ);
|
|
1661
|
+
const tile = tileMap.get(`${gridX},${gridZ}`);
|
|
1662
|
+
if (tile) {
|
|
1663
|
+
onTileHover(tile);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
};
|
|
1668
|
+
const handleClick = (e) => {
|
|
1669
|
+
if (!onTileClick) return;
|
|
1670
|
+
const instanceId = e.instanceId;
|
|
1671
|
+
if (instanceId !== void 0) {
|
|
1672
|
+
const pos = positions[instanceId];
|
|
1673
|
+
if (pos) {
|
|
1674
|
+
const gridX = Math.round(pos.x / cellSize + offsetX);
|
|
1675
|
+
const gridZ = Math.round(pos.z / cellSize + offsetZ);
|
|
1676
|
+
const tile = tileMap.get(`${gridX},${gridZ}`);
|
|
1677
|
+
if (tile) {
|
|
1678
|
+
onTileClick(tile);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
const renderIndividualTiles = () => {
|
|
1684
|
+
return tiles.map((tile) => {
|
|
1685
|
+
const x = (tile.x - offsetX) * cellSize;
|
|
1686
|
+
const z = ((tile.z ?? tile.y ?? 0) - offsetZ) * cellSize;
|
|
1687
|
+
const y = (tile.elevation ?? 0) * 0.1;
|
|
1688
|
+
const colorHex = terrainColors[tile.type || ""] || terrainColors[tile.terrain || ""] || "#808080";
|
|
1689
|
+
const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
|
|
1690
|
+
const isValidMove = validMoves.some(
|
|
1691
|
+
(m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
|
|
1692
|
+
);
|
|
1693
|
+
const isAttackTarget = attackTargets.some(
|
|
1694
|
+
(m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
|
|
1695
|
+
);
|
|
1696
|
+
let emissive = "#000000";
|
|
1697
|
+
if (isSelected) emissive = "#444444";
|
|
1698
|
+
else if (isAttackTarget) emissive = "#440000";
|
|
1699
|
+
else if (isValidMove) emissive = "#004400";
|
|
1700
|
+
return /* @__PURE__ */ jsxs(
|
|
1701
|
+
"mesh",
|
|
1702
|
+
{
|
|
1703
|
+
position: [x, y, z],
|
|
1704
|
+
userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
|
|
1705
|
+
onClick: () => onTileClick?.(tile),
|
|
1706
|
+
onPointerEnter: () => onTileHover?.(tile),
|
|
1707
|
+
onPointerLeave: () => onTileHover?.(null),
|
|
1708
|
+
children: [
|
|
1709
|
+
/* @__PURE__ */ jsx("boxGeometry", { args: [cellSize * 0.95, 0.2, cellSize * 0.95] }),
|
|
1710
|
+
/* @__PURE__ */ jsx(
|
|
1711
|
+
"meshStandardMaterial",
|
|
1712
|
+
{
|
|
1713
|
+
color: colorHex,
|
|
1714
|
+
emissive,
|
|
1715
|
+
roughness: 0.8,
|
|
1716
|
+
metalness: 0.1
|
|
1717
|
+
}
|
|
1718
|
+
)
|
|
1719
|
+
]
|
|
1720
|
+
},
|
|
1721
|
+
tile.id ?? `tile-${tile.x}-${tile.y}`
|
|
1722
|
+
);
|
|
1723
|
+
});
|
|
1724
|
+
};
|
|
1725
|
+
if (useInstancing && tiles.length > 0) {
|
|
1726
|
+
return /* @__PURE__ */ jsx(
|
|
1727
|
+
"instancedMesh",
|
|
1728
|
+
{
|
|
1729
|
+
ref: meshRef,
|
|
1730
|
+
args: [geometry, material, tiles.length],
|
|
1731
|
+
onPointerMove: handlePointerMove,
|
|
1732
|
+
onClick: handleClick
|
|
1733
|
+
}
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
return /* @__PURE__ */ jsx("group", { children: renderIndividualTiles() });
|
|
1737
|
+
}
|
|
1738
|
+
function UnitVisual({ unit, position, isSelected, onClick }) {
|
|
1739
|
+
const groupRef = useRef(null);
|
|
1740
|
+
const [animationState, setAnimationState] = useState("idle");
|
|
1741
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
1742
|
+
const teamColor = useMemo(() => {
|
|
1743
|
+
if (unit.faction === "player" || unit.team === "player") return 4491519;
|
|
1744
|
+
if (unit.faction === "enemy" || unit.team === "enemy") return 16729156;
|
|
1745
|
+
if (unit.faction === "neutral" || unit.team === "neutral") return 16777028;
|
|
1746
|
+
return 8947848;
|
|
1747
|
+
}, [unit.faction, unit.team]);
|
|
1748
|
+
useFrame((state) => {
|
|
1749
|
+
if (groupRef.current && animationState === "idle") {
|
|
1750
|
+
const y = position[1] + Math.sin(state.clock.elapsedTime * 2 + position[0]) * 0.05;
|
|
1751
|
+
groupRef.current.position.y = y;
|
|
1752
|
+
}
|
|
1753
|
+
});
|
|
1754
|
+
const healthPercent = useMemo(() => {
|
|
1755
|
+
if (unit.health === void 0 || unit.maxHealth === void 0) return 1;
|
|
1756
|
+
return Math.max(0, Math.min(1, unit.health / unit.maxHealth));
|
|
1757
|
+
}, [unit.health, unit.maxHealth]);
|
|
1758
|
+
const healthColor = useMemo(() => {
|
|
1759
|
+
if (healthPercent > 0.5) return "#44aa44";
|
|
1760
|
+
if (healthPercent > 0.25) return "#aaaa44";
|
|
1761
|
+
return "#ff4444";
|
|
1762
|
+
}, [healthPercent]);
|
|
1763
|
+
return /* @__PURE__ */ jsxs(
|
|
1764
|
+
"group",
|
|
1765
|
+
{
|
|
1766
|
+
ref: groupRef,
|
|
1767
|
+
position,
|
|
1768
|
+
onClick,
|
|
1769
|
+
onPointerEnter: () => setIsHovered(true),
|
|
1770
|
+
onPointerLeave: () => setIsHovered(false),
|
|
1771
|
+
userData: { type: "unit", unitId: unit.id },
|
|
1772
|
+
children: [
|
|
1773
|
+
isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
|
|
1774
|
+
/* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
1775
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
1776
|
+
] }),
|
|
1777
|
+
isHovered && !isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
|
|
1778
|
+
/* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
1779
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffffff", transparent: true, opacity: 0.5 })
|
|
1780
|
+
] }),
|
|
1781
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, 0.1, 0], children: [
|
|
1782
|
+
/* @__PURE__ */ jsx("cylinderGeometry", { args: [0.25, 0.25, 0.1, 8] }),
|
|
1783
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: teamColor })
|
|
1784
|
+
] }),
|
|
1785
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, 0.5, 0], children: [
|
|
1786
|
+
/* @__PURE__ */ jsx("capsuleGeometry", { args: [0.15, 0.5, 4, 8] }),
|
|
1787
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: teamColor })
|
|
1788
|
+
] }),
|
|
1789
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
|
|
1790
|
+
/* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
|
|
1791
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: teamColor })
|
|
1792
|
+
] }),
|
|
1793
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, 1.3, 0], children: [
|
|
1794
|
+
/* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.06] }),
|
|
1795
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#333333" })
|
|
1796
|
+
] }),
|
|
1797
|
+
/* @__PURE__ */ jsxs("mesh", { position: [-0.25 + 0.25 * healthPercent, 1.3, 0.01], children: [
|
|
1798
|
+
/* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * healthPercent, 0.04] }),
|
|
1799
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: healthColor })
|
|
1800
|
+
] }),
|
|
1801
|
+
unit.name && /* @__PURE__ */ jsxs("mesh", { position: [0, 1.5, 0], children: [
|
|
1802
|
+
/* @__PURE__ */ jsx("planeGeometry", { args: [0.4, 0.1] }),
|
|
1803
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#000000", transparent: true, opacity: 0.5 })
|
|
1804
|
+
] })
|
|
1805
|
+
]
|
|
1806
|
+
}
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
function UnitRenderer({
|
|
1810
|
+
units,
|
|
1811
|
+
cellSize = 1,
|
|
1812
|
+
offsetX = 0,
|
|
1813
|
+
offsetZ = 0,
|
|
1814
|
+
selectedUnitId,
|
|
1815
|
+
onUnitClick,
|
|
1816
|
+
onAnimationStateChange,
|
|
1817
|
+
animationSpeed = 1
|
|
1818
|
+
}) {
|
|
1819
|
+
const handleUnitClick = React8.useCallback(
|
|
1820
|
+
(unit) => {
|
|
1821
|
+
onUnitClick?.(unit);
|
|
1822
|
+
},
|
|
1823
|
+
[onUnitClick]
|
|
1824
|
+
);
|
|
1825
|
+
return /* @__PURE__ */ jsx("group", { children: units.map((unit) => {
|
|
1826
|
+
const unitX = unit.x ?? unit.position?.x ?? 0;
|
|
1827
|
+
const unitY = unit.z ?? unit.y ?? unit.position?.y ?? 0;
|
|
1828
|
+
const x = (unitX - offsetX) * cellSize;
|
|
1829
|
+
const z = (unitY - offsetZ) * cellSize;
|
|
1830
|
+
const y = (unit.elevation ?? 0) * 0.1 + 0.5;
|
|
1831
|
+
return /* @__PURE__ */ jsx(
|
|
1832
|
+
UnitVisual,
|
|
1833
|
+
{
|
|
1834
|
+
unit,
|
|
1835
|
+
position: [x, y, z],
|
|
1836
|
+
isSelected: selectedUnitId === unit.id,
|
|
1837
|
+
onClick: () => handleUnitClick(unit)
|
|
1838
|
+
},
|
|
1839
|
+
unit.id
|
|
1840
|
+
);
|
|
1841
|
+
}) });
|
|
1842
|
+
}
|
|
1843
|
+
var DEFAULT_FEATURE_CONFIGS = {
|
|
1844
|
+
tree: { color: 2263842, height: 1.5, scale: 1, geometry: "tree" },
|
|
1845
|
+
rock: { color: 8421504, height: 0.5, scale: 0.8, geometry: "rock" },
|
|
1846
|
+
bush: { color: 3329330, height: 0.4, scale: 0.6, geometry: "bush" },
|
|
1847
|
+
house: { color: 9127187, height: 1.2, scale: 1.2, geometry: "house" },
|
|
1848
|
+
tower: { color: 6908265, height: 2.5, scale: 1, geometry: "tower" },
|
|
1849
|
+
wall: { color: 8421504, height: 1, scale: 1, geometry: "wall" },
|
|
1850
|
+
mountain: { color: 5597999, height: 2, scale: 1.5, geometry: "mountain" },
|
|
1851
|
+
hill: { color: 7048739, height: 0.8, scale: 1.2, geometry: "hill" },
|
|
1852
|
+
water: { color: 4491468, height: 0.1, scale: 1, geometry: "water" },
|
|
1853
|
+
chest: { color: 16766720, height: 0.3, scale: 0.4, geometry: "chest" },
|
|
1854
|
+
sign: { color: 9127187, height: 0.8, scale: 0.3, geometry: "sign" },
|
|
1855
|
+
portal: { color: 10040012, height: 1.5, scale: 1, geometry: "portal" }
|
|
1856
|
+
};
|
|
1857
|
+
function TreeFeature({ height, color }) {
|
|
1858
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1859
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.3, 0], children: [
|
|
1860
|
+
/* @__PURE__ */ jsx("cylinderGeometry", { args: [0.08, 0.1, height * 0.6, 6] }),
|
|
1861
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
|
|
1862
|
+
] }),
|
|
1863
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.7, 0], children: [
|
|
1864
|
+
/* @__PURE__ */ jsx("coneGeometry", { args: [0.4, height * 0.5, 8] }),
|
|
1865
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
1866
|
+
] }),
|
|
1867
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.9, 0], children: [
|
|
1868
|
+
/* @__PURE__ */ jsx("coneGeometry", { args: [0.3, height * 0.4, 8] }),
|
|
1869
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
1870
|
+
] }),
|
|
1871
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height * 1.05, 0], children: [
|
|
1872
|
+
/* @__PURE__ */ jsx("coneGeometry", { args: [0.15, height * 0.25, 8] }),
|
|
1873
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
1874
|
+
] })
|
|
1875
|
+
] });
|
|
1876
|
+
}
|
|
1877
|
+
function RockFeature({ height, color }) {
|
|
1878
|
+
return /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.4, 0], children: [
|
|
1879
|
+
/* @__PURE__ */ jsx("dodecahedronGeometry", { args: [height * 0.5, 0] }),
|
|
1880
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color, roughness: 0.9 })
|
|
1881
|
+
] });
|
|
1882
|
+
}
|
|
1883
|
+
function BushFeature({ height, color }) {
|
|
1884
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1885
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.3, 0], children: [
|
|
1886
|
+
/* @__PURE__ */ jsx("sphereGeometry", { args: [height * 0.4, 8, 8] }),
|
|
1887
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
1888
|
+
] }),
|
|
1889
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0.1, height * 0.4, 0.1], children: [
|
|
1890
|
+
/* @__PURE__ */ jsx("sphereGeometry", { args: [height * 0.25, 8, 8] }),
|
|
1891
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
1892
|
+
] })
|
|
1893
|
+
] });
|
|
1894
|
+
}
|
|
1895
|
+
function HouseFeature({ height, color }) {
|
|
1896
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1897
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.4, 0], children: [
|
|
1898
|
+
/* @__PURE__ */ jsx("boxGeometry", { args: [0.8, height * 0.8, 0.8] }),
|
|
1899
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: 13808780 })
|
|
1900
|
+
] }),
|
|
1901
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.9, 0], children: [
|
|
1902
|
+
/* @__PURE__ */ jsx("coneGeometry", { args: [0.6, height * 0.4, 4] }),
|
|
1903
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
1904
|
+
] }),
|
|
1905
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.25, 0.41], children: [
|
|
1906
|
+
/* @__PURE__ */ jsx("planeGeometry", { args: [0.25, height * 0.5] }),
|
|
1907
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: 4863784 })
|
|
1908
|
+
] })
|
|
1909
|
+
] });
|
|
1910
|
+
}
|
|
1911
|
+
function TowerFeature({ height, color }) {
|
|
1912
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1913
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.5, 0], children: [
|
|
1914
|
+
/* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.35, height, 8] }),
|
|
1915
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
1916
|
+
] }),
|
|
1917
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height + 0.05, 0], children: [
|
|
1918
|
+
/* @__PURE__ */ jsx("cylinderGeometry", { args: [0.35, 0.35, 0.1, 8] }),
|
|
1919
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
1920
|
+
] })
|
|
1921
|
+
] });
|
|
1922
|
+
}
|
|
1923
|
+
function ChestFeature({ height, color }) {
|
|
1924
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1925
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.5, 0], children: [
|
|
1926
|
+
/* @__PURE__ */ jsx("boxGeometry", { args: [0.3, height, 0.2] }),
|
|
1927
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color, metalness: 0.6, roughness: 0.3 })
|
|
1928
|
+
] }),
|
|
1929
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, height + 0.05, 0], children: [
|
|
1930
|
+
/* @__PURE__ */ jsx("cylinderGeometry", { args: [0.15, 0.15, 0.3, 8, 1, false, 0, Math.PI] }),
|
|
1931
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color, metalness: 0.6, roughness: 0.3 })
|
|
1932
|
+
] })
|
|
1933
|
+
] });
|
|
1934
|
+
}
|
|
1935
|
+
function DefaultFeature({ height, color }) {
|
|
1936
|
+
return /* @__PURE__ */ jsxs("mesh", { position: [0, height * 0.5, 0], children: [
|
|
1937
|
+
/* @__PURE__ */ jsx("boxGeometry", { args: [0.5, height, 0.5] }),
|
|
1938
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
1939
|
+
] });
|
|
1940
|
+
}
|
|
1941
|
+
function FeatureVisual({
|
|
1942
|
+
feature,
|
|
1943
|
+
position,
|
|
1944
|
+
isSelected,
|
|
1945
|
+
onClick,
|
|
1946
|
+
onHover
|
|
1947
|
+
}) {
|
|
1948
|
+
const config = DEFAULT_FEATURE_CONFIGS[feature.type] || {
|
|
1949
|
+
color: 8947848,
|
|
1950
|
+
height: 0.5,
|
|
1951
|
+
scale: 1,
|
|
1952
|
+
geometry: "default"
|
|
1953
|
+
};
|
|
1954
|
+
const color = feature.color ? parseInt(feature.color.replace("#", ""), 16) : config.color;
|
|
1955
|
+
const renderGeometry = () => {
|
|
1956
|
+
switch (config.geometry) {
|
|
1957
|
+
case "tree":
|
|
1958
|
+
return /* @__PURE__ */ jsx(TreeFeature, { height: config.height, color });
|
|
1959
|
+
case "rock":
|
|
1960
|
+
return /* @__PURE__ */ jsx(RockFeature, { height: config.height, color });
|
|
1961
|
+
case "bush":
|
|
1962
|
+
return /* @__PURE__ */ jsx(BushFeature, { height: config.height, color });
|
|
1963
|
+
case "house":
|
|
1964
|
+
return /* @__PURE__ */ jsx(HouseFeature, { height: config.height, color });
|
|
1965
|
+
case "tower":
|
|
1966
|
+
return /* @__PURE__ */ jsx(TowerFeature, { height: config.height, color });
|
|
1967
|
+
case "chest":
|
|
1968
|
+
return /* @__PURE__ */ jsx(ChestFeature, { height: config.height, color });
|
|
1969
|
+
default:
|
|
1970
|
+
return /* @__PURE__ */ jsx(DefaultFeature, { height: config.height, color });
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
return /* @__PURE__ */ jsxs(
|
|
1974
|
+
"group",
|
|
1975
|
+
{
|
|
1976
|
+
position,
|
|
1977
|
+
scale: config.scale,
|
|
1978
|
+
onClick,
|
|
1979
|
+
onPointerEnter: () => onHover(true),
|
|
1980
|
+
onPointerLeave: () => onHover(false),
|
|
1981
|
+
userData: { type: "feature", featureId: feature.id, featureType: feature.type },
|
|
1982
|
+
children: [
|
|
1983
|
+
isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
|
|
1984
|
+
/* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
1985
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
1986
|
+
] }),
|
|
1987
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, 0.01, 0], rotation: [-Math.PI / 2, 0, 0], children: [
|
|
1988
|
+
/* @__PURE__ */ jsx("circleGeometry", { args: [0.35, 16] }),
|
|
1989
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#000000", transparent: true, opacity: 0.2 })
|
|
1990
|
+
] }),
|
|
1991
|
+
renderGeometry()
|
|
1992
|
+
]
|
|
1993
|
+
}
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1996
|
+
function FeatureRenderer({
|
|
1997
|
+
features,
|
|
1998
|
+
cellSize = 1,
|
|
1999
|
+
offsetX = 0,
|
|
2000
|
+
offsetZ = 0,
|
|
2001
|
+
onFeatureClick,
|
|
2002
|
+
onFeatureHover,
|
|
2003
|
+
selectedFeatureIds = [],
|
|
2004
|
+
featureColors
|
|
2005
|
+
}) {
|
|
2006
|
+
return /* @__PURE__ */ jsx("group", { children: features.map((feature) => {
|
|
2007
|
+
const x = (feature.x - offsetX) * cellSize;
|
|
2008
|
+
const z = ((feature.z ?? feature.y ?? 0) - offsetZ) * cellSize;
|
|
2009
|
+
const y = (feature.elevation ?? 0) * 0.1;
|
|
2010
|
+
const isSelected = feature.id ? selectedFeatureIds.includes(feature.id) : false;
|
|
2011
|
+
return /* @__PURE__ */ jsx(
|
|
2012
|
+
FeatureVisual,
|
|
2013
|
+
{
|
|
2014
|
+
feature,
|
|
2015
|
+
position: [x, y, z],
|
|
2016
|
+
isSelected,
|
|
2017
|
+
onClick: () => onFeatureClick?.(feature),
|
|
2018
|
+
onHover: (hovered) => onFeatureHover?.(hovered ? feature : null)
|
|
2019
|
+
},
|
|
2020
|
+
feature.id ?? `feature-${feature.x}-${feature.y}`
|
|
2021
|
+
);
|
|
2022
|
+
}) });
|
|
2023
|
+
}
|
|
2024
|
+
function detectAssetRoot3(modelUrl) {
|
|
2025
|
+
const idx = modelUrl.indexOf("/3d/");
|
|
2026
|
+
if (idx !== -1) {
|
|
2027
|
+
return modelUrl.substring(0, idx + 4);
|
|
2028
|
+
}
|
|
2029
|
+
return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
|
|
2030
|
+
}
|
|
2031
|
+
function useGLTFModel2(url) {
|
|
2032
|
+
const [model, setModel] = useState(null);
|
|
2033
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
2034
|
+
const [error, setError] = useState(null);
|
|
2035
|
+
useEffect(() => {
|
|
2036
|
+
if (!url) {
|
|
2037
|
+
setModel(null);
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
setIsLoading(true);
|
|
2041
|
+
setError(null);
|
|
2042
|
+
const assetRoot = detectAssetRoot3(url);
|
|
2043
|
+
const loader = new GLTFLoader$1();
|
|
2044
|
+
loader.setResourcePath(assetRoot);
|
|
2045
|
+
loader.load(
|
|
2046
|
+
url,
|
|
2047
|
+
(gltf) => {
|
|
2048
|
+
setModel(gltf.scene);
|
|
2049
|
+
setIsLoading(false);
|
|
2050
|
+
},
|
|
2051
|
+
void 0,
|
|
2052
|
+
(err) => {
|
|
2053
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
2054
|
+
setIsLoading(false);
|
|
2055
|
+
}
|
|
2056
|
+
);
|
|
2057
|
+
}, [url]);
|
|
2058
|
+
return { model, isLoading, error };
|
|
2059
|
+
}
|
|
2060
|
+
function FeatureModel({
|
|
2061
|
+
feature,
|
|
2062
|
+
position,
|
|
2063
|
+
isSelected,
|
|
2064
|
+
onClick,
|
|
2065
|
+
onHover
|
|
2066
|
+
}) {
|
|
2067
|
+
const groupRef = useRef(null);
|
|
2068
|
+
const { model: loadedModel, isLoading } = useGLTFModel2(feature.assetUrl);
|
|
2069
|
+
const model = useMemo(() => {
|
|
2070
|
+
if (!loadedModel) return null;
|
|
2071
|
+
const cloned = loadedModel.clone();
|
|
2072
|
+
cloned.scale.setScalar(0.3);
|
|
2073
|
+
cloned.traverse((child) => {
|
|
2074
|
+
if (child instanceof THREE6.Mesh) {
|
|
2075
|
+
child.castShadow = true;
|
|
2076
|
+
child.receiveShadow = true;
|
|
2077
|
+
}
|
|
2078
|
+
});
|
|
2079
|
+
return cloned;
|
|
2080
|
+
}, [loadedModel]);
|
|
2081
|
+
useFrame((state) => {
|
|
2082
|
+
if (groupRef.current) {
|
|
2083
|
+
const featureRotation = feature.rotation;
|
|
2084
|
+
const baseRotation = featureRotation !== void 0 ? featureRotation * Math.PI / 180 - Math.PI / 4 : -Math.PI / 4;
|
|
2085
|
+
const wobble = isSelected ? Math.sin(state.clock.elapsedTime * 2) * 0.1 : 0;
|
|
2086
|
+
groupRef.current.rotation.y = baseRotation + wobble;
|
|
2087
|
+
}
|
|
2088
|
+
});
|
|
2089
|
+
if (isLoading) {
|
|
2090
|
+
return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
|
|
2091
|
+
/* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
|
|
2092
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
|
|
2093
|
+
] }) });
|
|
2094
|
+
}
|
|
2095
|
+
if (!model && !feature.assetUrl) {
|
|
2096
|
+
return /* @__PURE__ */ jsxs(
|
|
2097
|
+
"group",
|
|
2098
|
+
{
|
|
2099
|
+
position,
|
|
2100
|
+
onClick,
|
|
2101
|
+
onPointerEnter: () => onHover(true),
|
|
2102
|
+
onPointerLeave: () => onHover(false),
|
|
2103
|
+
userData: { type: "feature", featureId: feature.id, featureType: feature.type },
|
|
2104
|
+
children: [
|
|
2105
|
+
isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
|
|
2106
|
+
/* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
2107
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
2108
|
+
] }),
|
|
2109
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, 0.5, 0], children: [
|
|
2110
|
+
/* @__PURE__ */ jsx("boxGeometry", { args: [0.4, 0.4, 0.4] }),
|
|
2111
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: 8947848 })
|
|
2112
|
+
] })
|
|
2113
|
+
]
|
|
2114
|
+
}
|
|
2115
|
+
);
|
|
2116
|
+
}
|
|
2117
|
+
return /* @__PURE__ */ jsxs(
|
|
2118
|
+
"group",
|
|
2119
|
+
{
|
|
2120
|
+
ref: groupRef,
|
|
2121
|
+
position,
|
|
2122
|
+
onClick,
|
|
2123
|
+
onPointerEnter: () => onHover(true),
|
|
2124
|
+
onPointerLeave: () => onHover(false),
|
|
2125
|
+
userData: { type: "feature", featureId: feature.id, featureType: feature.type },
|
|
2126
|
+
children: [
|
|
2127
|
+
isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
|
|
2128
|
+
/* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
2129
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
2130
|
+
] }),
|
|
2131
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, 0.01, 0], rotation: [-Math.PI / 2, 0, 0], children: [
|
|
2132
|
+
/* @__PURE__ */ jsx("circleGeometry", { args: [0.35, 16] }),
|
|
2133
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#000000", transparent: true, opacity: 0.2 })
|
|
2134
|
+
] }),
|
|
2135
|
+
model && /* @__PURE__ */ jsx("primitive", { object: model })
|
|
2136
|
+
]
|
|
2137
|
+
}
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
function FeatureRenderer3D({
|
|
2141
|
+
features,
|
|
2142
|
+
cellSize = 1,
|
|
2143
|
+
offsetX = 0,
|
|
2144
|
+
offsetZ = 0,
|
|
2145
|
+
onFeatureClick,
|
|
2146
|
+
onFeatureHover,
|
|
2147
|
+
selectedFeatureIds = []
|
|
2148
|
+
}) {
|
|
2149
|
+
return /* @__PURE__ */ jsx("group", { children: features.map((feature) => {
|
|
2150
|
+
const x = (feature.x - offsetX) * cellSize;
|
|
2151
|
+
const z = ((feature.z ?? feature.y ?? 0) - offsetZ) * cellSize;
|
|
2152
|
+
const y = (feature.elevation ?? 0) * 0.1;
|
|
2153
|
+
const isSelected = feature.id ? selectedFeatureIds.includes(feature.id) : false;
|
|
2154
|
+
return /* @__PURE__ */ jsx(
|
|
2155
|
+
FeatureModel,
|
|
2156
|
+
{
|
|
2157
|
+
feature,
|
|
2158
|
+
position: [x, y, z],
|
|
2159
|
+
isSelected,
|
|
2160
|
+
onClick: () => onFeatureClick?.(feature),
|
|
2161
|
+
onHover: (hovered) => onFeatureHover?.(hovered ? feature : null)
|
|
2162
|
+
},
|
|
2163
|
+
feature.id ?? `feature-${feature.x}-${feature.y}`
|
|
2164
|
+
);
|
|
2165
|
+
}) });
|
|
2166
|
+
}
|
|
2167
|
+
function preloadFeatures(urls) {
|
|
2168
|
+
urls.forEach((url) => {
|
|
2169
|
+
if (url) {
|
|
2170
|
+
const loader = new GLTFLoader$1();
|
|
2171
|
+
loader.setResourcePath(detectAssetRoot3(url));
|
|
2172
|
+
loader.load(url, () => {
|
|
2173
|
+
console.log("[FeatureRenderer3D] Preloaded:", url);
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
433
2178
|
var DEFAULT_CONFIG = {
|
|
434
2179
|
cellSize: 1,
|
|
435
2180
|
offsetX: 0,
|
|
@@ -438,7 +2183,7 @@ var DEFAULT_CONFIG = {
|
|
|
438
2183
|
};
|
|
439
2184
|
function gridToWorld(gridX, gridZ, config = DEFAULT_CONFIG) {
|
|
440
2185
|
const opts = { ...DEFAULT_CONFIG, ...config };
|
|
441
|
-
return new
|
|
2186
|
+
return new THREE6.Vector3(
|
|
442
2187
|
gridX * opts.cellSize + opts.offsetX,
|
|
443
2188
|
opts.elevation,
|
|
444
2189
|
gridZ * opts.cellSize + opts.offsetZ
|
|
@@ -452,17 +2197,17 @@ function worldToGrid(worldX, worldZ, config = DEFAULT_CONFIG) {
|
|
|
452
2197
|
};
|
|
453
2198
|
}
|
|
454
2199
|
function raycastToPlane(camera, mouseX, mouseY, planeY = 0) {
|
|
455
|
-
const raycaster = new
|
|
456
|
-
const mouse = new
|
|
2200
|
+
const raycaster = new THREE6.Raycaster();
|
|
2201
|
+
const mouse = new THREE6.Vector2(mouseX, mouseY);
|
|
457
2202
|
raycaster.setFromCamera(mouse, camera);
|
|
458
|
-
const plane = new
|
|
459
|
-
const target = new
|
|
2203
|
+
const plane = new THREE6.Plane(new THREE6.Vector3(0, 1, 0), -planeY);
|
|
2204
|
+
const target = new THREE6.Vector3();
|
|
460
2205
|
const intersection = raycaster.ray.intersectPlane(plane, target);
|
|
461
2206
|
return intersection ? target : null;
|
|
462
2207
|
}
|
|
463
2208
|
function raycastToObjects(camera, mouseX, mouseY, objects) {
|
|
464
|
-
const raycaster = new
|
|
465
|
-
const mouse = new
|
|
2209
|
+
const raycaster = new THREE6.Raycaster();
|
|
2210
|
+
const mouse = new THREE6.Vector2(mouseX, mouseY);
|
|
466
2211
|
raycaster.setFromCamera(mouse, camera);
|
|
467
2212
|
const intersects = raycaster.intersectObjects(objects, true);
|
|
468
2213
|
return intersects.length > 0 ? intersects[0] : null;
|
|
@@ -514,14 +2259,14 @@ function getCellsInRadius(centerX, centerZ, radius) {
|
|
|
514
2259
|
return cells;
|
|
515
2260
|
}
|
|
516
2261
|
function createGridHighlight(color = 16776960, opacity = 0.3) {
|
|
517
|
-
const geometry = new
|
|
518
|
-
const material = new
|
|
2262
|
+
const geometry = new THREE6.PlaneGeometry(0.95, 0.95);
|
|
2263
|
+
const material = new THREE6.MeshBasicMaterial({
|
|
519
2264
|
color,
|
|
520
2265
|
transparent: true,
|
|
521
2266
|
opacity,
|
|
522
|
-
side:
|
|
2267
|
+
side: THREE6.DoubleSide
|
|
523
2268
|
});
|
|
524
|
-
const mesh = new
|
|
2269
|
+
const mesh = new THREE6.Mesh(geometry, material);
|
|
525
2270
|
mesh.rotation.x = -Math.PI / 2;
|
|
526
2271
|
mesh.position.y = 0.01;
|
|
527
2272
|
return mesh;
|
|
@@ -534,31 +2279,31 @@ function normalizeMouseCoordinates(clientX, clientY, element) {
|
|
|
534
2279
|
};
|
|
535
2280
|
}
|
|
536
2281
|
function isInFrustum(position, camera, padding = 0) {
|
|
537
|
-
const frustum = new
|
|
538
|
-
const projScreenMatrix = new
|
|
2282
|
+
const frustum = new THREE6.Frustum();
|
|
2283
|
+
const projScreenMatrix = new THREE6.Matrix4();
|
|
539
2284
|
projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
540
2285
|
frustum.setFromProjectionMatrix(projScreenMatrix);
|
|
541
|
-
const sphere = new
|
|
2286
|
+
const sphere = new THREE6.Sphere(position, padding);
|
|
542
2287
|
return frustum.intersectsSphere(sphere);
|
|
543
2288
|
}
|
|
544
2289
|
function filterByFrustum(positions, camera, padding = 0) {
|
|
545
|
-
const frustum = new
|
|
546
|
-
const projScreenMatrix = new
|
|
2290
|
+
const frustum = new THREE6.Frustum();
|
|
2291
|
+
const projScreenMatrix = new THREE6.Matrix4();
|
|
547
2292
|
projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
548
2293
|
frustum.setFromProjectionMatrix(projScreenMatrix);
|
|
549
2294
|
return positions.filter((position) => {
|
|
550
|
-
const sphere = new
|
|
2295
|
+
const sphere = new THREE6.Sphere(position, padding);
|
|
551
2296
|
return frustum.intersectsSphere(sphere);
|
|
552
2297
|
});
|
|
553
2298
|
}
|
|
554
2299
|
function getVisibleIndices(positions, camera, padding = 0) {
|
|
555
|
-
const frustum = new
|
|
556
|
-
const projScreenMatrix = new
|
|
2300
|
+
const frustum = new THREE6.Frustum();
|
|
2301
|
+
const projScreenMatrix = new THREE6.Matrix4();
|
|
557
2302
|
const visible = /* @__PURE__ */ new Set();
|
|
558
2303
|
projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
559
2304
|
frustum.setFromProjectionMatrix(projScreenMatrix);
|
|
560
2305
|
positions.forEach((position, index) => {
|
|
561
|
-
const sphere = new
|
|
2306
|
+
const sphere = new THREE6.Sphere(position, padding);
|
|
562
2307
|
if (frustum.intersectsSphere(sphere)) {
|
|
563
2308
|
visible.add(index);
|
|
564
2309
|
}
|
|
@@ -582,7 +2327,7 @@ function updateInstanceLOD(instancedMesh, positions, camera, lodDistances) {
|
|
|
582
2327
|
return lodIndices;
|
|
583
2328
|
}
|
|
584
2329
|
function cullInstancedMesh(instancedMesh, positions, visibleIndices) {
|
|
585
|
-
const dummy = new
|
|
2330
|
+
const dummy = new THREE6.Object3D();
|
|
586
2331
|
let visibleCount = 0;
|
|
587
2332
|
positions.forEach((position, index) => {
|
|
588
2333
|
if (visibleIndices.has(index)) {
|
|
@@ -715,4 +2460,4 @@ var SpatialHashGrid = class {
|
|
|
715
2460
|
}
|
|
716
2461
|
};
|
|
717
2462
|
|
|
718
|
-
export { SpatialHashGrid, calculateLODLevel, createGridHighlight, cullInstancedMesh, filterByFrustum, getCellsInRadius, getNeighbors, getVisibleIndices, gridDistance, gridManhattanDistance, gridToWorld, isInBounds, isInFrustum, normalizeMouseCoordinates, raycastToObjects, raycastToPlane, updateInstanceLOD, useRaycaster, useSceneGraph, useThree, worldToGrid };
|
|
2463
|
+
export { AssetLoader, Camera3D, Canvas3DErrorBoundary, Canvas3DLoadingState, FeatureRenderer, FeatureRenderer3D, Lighting3D, ModelLoader, PhysicsObject3D, Scene3D, SpatialHashGrid, TileRenderer, UnitRenderer, assetLoader, calculateLODLevel, createGridHighlight, cullInstancedMesh, filterByFrustum, getCellsInRadius, getNeighbors, getVisibleIndices, gridDistance, gridManhattanDistance, gridToWorld, isInBounds, isInFrustum, normalizeMouseCoordinates, preloadFeatures, raycastToObjects, raycastToPlane, updateInstanceLOD, useAssetLoader, useGameCanvas3DEvents, usePhysics3DController, useRaycaster, useSceneGraph, useThree3 as useThree, worldToGrid };
|