@tonybfox/threejs-tools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +321 -0
  2. package/dist/asset-loader/index.cjs +376 -0
  3. package/dist/asset-loader/index.cjs.map +1 -0
  4. package/dist/asset-loader/index.d.mts +101 -0
  5. package/dist/asset-loader/index.d.ts +101 -0
  6. package/dist/asset-loader/index.mjs +7 -0
  7. package/dist/asset-loader/index.mjs.map +1 -0
  8. package/dist/camera/index.cjs +313 -0
  9. package/dist/camera/index.cjs.map +1 -0
  10. package/dist/camera/index.d.mts +82 -0
  11. package/dist/camera/index.d.ts +82 -0
  12. package/dist/camera/index.mjs +7 -0
  13. package/dist/camera/index.mjs.map +1 -0
  14. package/dist/chunk-5DP6WDB3.mjs +1161 -0
  15. package/dist/chunk-5DP6WDB3.mjs.map +1 -0
  16. package/dist/chunk-BJKSICFA.mjs +1579 -0
  17. package/dist/chunk-BJKSICFA.mjs.map +1 -0
  18. package/dist/chunk-BYRZCHE7.mjs +277 -0
  19. package/dist/chunk-BYRZCHE7.mjs.map +1 -0
  20. package/dist/chunk-EIROAPF7.mjs +387 -0
  21. package/dist/chunk-EIROAPF7.mjs.map +1 -0
  22. package/dist/chunk-EQDOX34V.mjs +164 -0
  23. package/dist/chunk-EQDOX34V.mjs.map +1 -0
  24. package/dist/chunk-IIAZ2WJJ.mjs +405 -0
  25. package/dist/chunk-IIAZ2WJJ.mjs.map +1 -0
  26. package/dist/chunk-L4VIIJZD.mjs +340 -0
  27. package/dist/chunk-L4VIIJZD.mjs.map +1 -0
  28. package/dist/chunk-P35QJCOG.mjs +339 -0
  29. package/dist/chunk-P35QJCOG.mjs.map +1 -0
  30. package/dist/chunk-R64RVBRM.mjs +394 -0
  31. package/dist/chunk-R64RVBRM.mjs.map +1 -0
  32. package/dist/compass/index.cjs +375 -0
  33. package/dist/compass/index.cjs.map +1 -0
  34. package/dist/compass/index.d.mts +58 -0
  35. package/dist/compass/index.d.ts +58 -0
  36. package/dist/compass/index.mjs +7 -0
  37. package/dist/compass/index.mjs.map +1 -0
  38. package/dist/grid/index.cjs +200 -0
  39. package/dist/grid/index.cjs.map +1 -0
  40. package/dist/grid/index.d.mts +43 -0
  41. package/dist/grid/index.d.ts +43 -0
  42. package/dist/grid/index.mjs +7 -0
  43. package/dist/grid/index.mjs.map +1 -0
  44. package/dist/index.cjs +5049 -0
  45. package/dist/index.cjs.map +1 -0
  46. package/dist/index.d.mts +13 -0
  47. package/dist/index.d.ts +13 -0
  48. package/dist/index.mjs +47 -0
  49. package/dist/index.mjs.map +1 -0
  50. package/dist/measurements/index.cjs +1198 -0
  51. package/dist/measurements/index.cjs.map +1 -0
  52. package/dist/measurements/index.d.mts +449 -0
  53. package/dist/measurements/index.d.ts +449 -0
  54. package/dist/measurements/index.mjs +9 -0
  55. package/dist/measurements/index.mjs.map +1 -0
  56. package/dist/sunlight/index.cjs +441 -0
  57. package/dist/sunlight/index.cjs.map +1 -0
  58. package/dist/sunlight/index.d.mts +92 -0
  59. package/dist/sunlight/index.d.ts +92 -0
  60. package/dist/sunlight/index.mjs +7 -0
  61. package/dist/sunlight/index.mjs.map +1 -0
  62. package/dist/terrain/index.cjs +423 -0
  63. package/dist/terrain/index.cjs.map +1 -0
  64. package/dist/terrain/index.d.mts +219 -0
  65. package/dist/terrain/index.d.ts +219 -0
  66. package/dist/terrain/index.mjs +7 -0
  67. package/dist/terrain/index.mjs.map +1 -0
  68. package/dist/transform-controls/index.cjs +1587 -0
  69. package/dist/transform-controls/index.cjs.map +1 -0
  70. package/dist/transform-controls/index.d.mts +162 -0
  71. package/dist/transform-controls/index.d.ts +162 -0
  72. package/dist/transform-controls/index.mjs +13 -0
  73. package/dist/transform-controls/index.mjs.map +1 -0
  74. package/dist/view-helper/index.cjs +430 -0
  75. package/dist/view-helper/index.cjs.map +1 -0
  76. package/dist/view-helper/index.d.mts +75 -0
  77. package/dist/view-helper/index.d.ts +75 -0
  78. package/dist/view-helper/index.mjs +7 -0
  79. package/dist/view-helper/index.mjs.map +1 -0
  80. package/package.json +124 -0
@@ -0,0 +1,339 @@
1
+ // packages/compass/src/CompassOverlay.ts
2
+ import * as THREE from "three";
3
+ var CompassOverlay = class extends THREE.EventDispatcher {
4
+ constructor(camera, options = {}) {
5
+ super();
6
+ this.isActive = false;
7
+ this.currentRotation = 0;
8
+ this.camera = camera;
9
+ this.options = {
10
+ container: options.container || document.body,
11
+ size: options.size || 100,
12
+ position: options.position || "bottom-right",
13
+ offset: options.offset || { x: 20, y: 20 },
14
+ colors: {
15
+ background: options.colors?.background || "#1a1a1a",
16
+ border: options.colors?.border || "#333333",
17
+ arrow: options.colors?.arrow || "#ff4444",
18
+ text: options.colors?.text || "#ffffff",
19
+ ticks: options.colors?.ticks || "#666666",
20
+ ...options.colors
21
+ }
22
+ };
23
+ this.container = this.options.container;
24
+ this.createCompassElement();
25
+ this.setupStyles();
26
+ }
27
+ createCompassElement() {
28
+ this.compassElement = document.createElement("div");
29
+ this.compassElement.className = "threejs-compass-overlay";
30
+ const compassBg = document.createElement("div");
31
+ compassBg.className = "compass-background";
32
+ const directions = [
33
+ { label: "N", angle: 0 },
34
+ { label: "E", angle: 90 },
35
+ { label: "S", angle: 180 },
36
+ { label: "W", angle: 270 }
37
+ ];
38
+ for (let i = 0; i < 360; i += 11.25) {
39
+ const tick = document.createElement("div");
40
+ tick.className = i % 45 === 0 ? "compass-tick-major" : "compass-tick-minor";
41
+ const tickLength = i % 30 === 0 ? 8 : 4;
42
+ tick.style.transform = `rotate(${i}deg) translateY(-${this.options.size / 2 - tickLength - 2}px)`;
43
+ compassBg.appendChild(tick);
44
+ }
45
+ directions.forEach(({ label, angle }) => {
46
+ const dirLabel = document.createElement("div");
47
+ dirLabel.className = "compass-label";
48
+ dirLabel.textContent = label;
49
+ dirLabel.style.transform = `rotate(${angle}deg) translateY(-${this.options.size / 2 - 15}px) rotate(-${angle}deg)`;
50
+ compassBg.appendChild(dirLabel);
51
+ });
52
+ this.arrowElement = document.createElement("div");
53
+ this.arrowElement.className = "compass-arrow";
54
+ this.arrowElement.innerHTML = `
55
+ <svg width="20" height="20" viewBox="0 0 20 20">
56
+ <polygon points="10,2 14,12 10,10 6,12" fill="${this.options.colors.arrow}" stroke="#000" stroke-width="0.5"/>
57
+ </svg>
58
+ `;
59
+ this.compassElement.appendChild(compassBg);
60
+ this.compassElement.appendChild(this.arrowElement);
61
+ this.container.appendChild(this.compassElement);
62
+ this.setupDoubleClickHandler();
63
+ }
64
+ setupStyles() {
65
+ if (document.getElementById("threejs-compass-styles")) {
66
+ return;
67
+ }
68
+ const style = document.createElement("style");
69
+ style.id = "threejs-compass-styles";
70
+ style.textContent = `
71
+ .threejs-compass-overlay {
72
+ position: fixed;
73
+ width: ${this.options.size}px;
74
+ height: ${this.options.size}px;
75
+ background: ${this.options.colors.background};
76
+ border: 2px solid ${this.options.colors.border};
77
+ border-radius: 50%;
78
+ z-index: 1000;
79
+ pointer-events: none;
80
+ user-select: none;
81
+ transition: opacity 0.2s ease;
82
+ ${this.getPositionStyles()};
83
+ }
84
+
85
+ .compass-background {
86
+ position: relative;
87
+ width: 100%;
88
+ height: 100%;
89
+ border-radius: 50%;
90
+ }
91
+
92
+ .compass-label {
93
+ position: absolute;
94
+ top: 50%;
95
+ left: 50%;
96
+ transform-origin: center;
97
+ color: ${this.options.colors.text};
98
+ font-family: Arial, sans-serif;
99
+ font-size: 12px;
100
+ font-weight: bold;
101
+ text-align: center;
102
+ width: 12px;
103
+ height: 12px;
104
+ line-height: 12px;
105
+ margin-left: -6px;
106
+ margin-top: -6px;
107
+ }
108
+
109
+ .compass-tick-major {
110
+ position: absolute;
111
+ top: 50%;
112
+ left: 50%;
113
+ width: 2px;
114
+ height: 8px;
115
+ background: ${this.options.colors.ticks};
116
+ transform-origin: center top;
117
+ margin-left: -1px;
118
+ margin-top: -4px;
119
+ }
120
+
121
+ .compass-tick-minor {
122
+ position: absolute;
123
+ top: 50%;
124
+ left: 50%;
125
+ width: 1px;
126
+ height: 4px;
127
+ background: ${this.options.colors.ticks};
128
+ transform-origin: center top;
129
+ margin-left: -0.5px;
130
+ margin-top: -2px;
131
+ opacity: 0.6;
132
+ }
133
+
134
+ .compass-arrow {
135
+ position: absolute;
136
+ top: 50%;
137
+ left: 50%;
138
+ transform: translate(-50%, -50%);
139
+ transform-origin: center;
140
+ transition: transform 0.1s ease-out;
141
+ z-index: 1;
142
+ }
143
+
144
+ .compass-arrow svg {
145
+ display: block;
146
+ filter: drop-shadow(0 0 2px rgba(0,0,0,0.5));
147
+ }
148
+ `;
149
+ document.head.appendChild(style);
150
+ }
151
+ setupDoubleClickHandler() {
152
+ this.compassElement.style.pointerEvents = "auto";
153
+ this.compassElement.style.cursor = "pointer";
154
+ this.compassElement.addEventListener("dblclick", (event) => {
155
+ event.preventDefault();
156
+ event.stopPropagation();
157
+ this.dispatchEvent({ type: "resetToNorth" });
158
+ });
159
+ this.compassElement.addEventListener("mouseenter", () => {
160
+ this.compassElement.style.opacity = "0.8";
161
+ });
162
+ this.compassElement.addEventListener("mouseleave", () => {
163
+ this.compassElement.style.opacity = "1";
164
+ });
165
+ }
166
+ updateTicksAndLabels() {
167
+ const majorTicks = this.compassElement.querySelectorAll(
168
+ ".compass-tick-major"
169
+ );
170
+ const minorTicks = this.compassElement.querySelectorAll(
171
+ ".compass-tick-minor"
172
+ );
173
+ majorTicks.forEach((tick, index) => {
174
+ const angle = index * 30 % 360;
175
+ const tickLength = 8;
176
+ tick.style.transform = `rotate(${angle}deg) translateY(-${this.options.size / 2 - tickLength - 2}px)`;
177
+ });
178
+ minorTicks.forEach((tick, index) => {
179
+ const allAngles = [];
180
+ for (let i = 0; i < 360; i += 10) {
181
+ if (i % 30 !== 0) {
182
+ allAngles.push(i);
183
+ }
184
+ }
185
+ const angle = allAngles[index] || 0;
186
+ const tickLength = 4;
187
+ tick.style.transform = `rotate(${angle}deg) translateY(-${this.options.size / 2 - tickLength - 2}px)`;
188
+ });
189
+ const labels = this.compassElement.querySelectorAll(".compass-label");
190
+ labels.forEach((label, index) => {
191
+ const directions = [
192
+ { label: "N", angle: 0 },
193
+ { label: "E", angle: 90 },
194
+ { label: "S", angle: 180 },
195
+ { label: "W", angle: 270 }
196
+ ];
197
+ const { angle } = directions[index];
198
+ label.style.transform = `rotate(${angle}deg) translateY(-${this.options.size / 2 - 15}px) rotate(-${angle}deg)`;
199
+ });
200
+ }
201
+ getPositionStyles() {
202
+ const { position, offset } = this.options;
203
+ switch (position) {
204
+ case "top-left":
205
+ return `top: ${offset.y}px; left: ${offset.x}px;`;
206
+ case "top-right":
207
+ return `top: ${offset.y}px; right: ${offset.x}px;`;
208
+ case "bottom-left":
209
+ return `bottom: ${offset.y}px; left: ${offset.x}px;`;
210
+ case "bottom-right":
211
+ default:
212
+ return `bottom: ${offset.y}px; right: ${offset.x}px;`;
213
+ }
214
+ }
215
+ start() {
216
+ if (this.isActive) return;
217
+ this.isActive = true;
218
+ this.update();
219
+ }
220
+ stop() {
221
+ this.isActive = false;
222
+ }
223
+ update() {
224
+ if (!this.isActive) return;
225
+ const cameraMatrix = new THREE.Matrix4();
226
+ this.camera.updateMatrixWorld();
227
+ cameraMatrix.copy(this.camera.matrixWorld);
228
+ const forward = new THREE.Vector3(0, 0, -1);
229
+ forward.transformDirection(cameraMatrix);
230
+ forward.y = 0;
231
+ forward.normalize();
232
+ const targetAngle = Math.atan2(-forward.x, -forward.z) * (180 / Math.PI);
233
+ let angleDiff = targetAngle - this.currentRotation;
234
+ while (angleDiff > 180) angleDiff -= 360;
235
+ while (angleDiff < -180) angleDiff += 360;
236
+ this.currentRotation += angleDiff;
237
+ this.arrowElement.style.transform = `translate(-50%, -50%) rotate(${-this.currentRotation}deg)`;
238
+ if (this.isActive) {
239
+ requestAnimationFrame(() => this.update());
240
+ }
241
+ }
242
+ setCamera(camera) {
243
+ this.camera = camera;
244
+ }
245
+ setSize(size) {
246
+ this.options.size = size;
247
+ this.compassElement.style.width = `${size}px`;
248
+ this.compassElement.style.height = `${size}px`;
249
+ this.updateTicksAndLabels();
250
+ this.updateStyles();
251
+ }
252
+ setPosition(position, offset) {
253
+ if (position) {
254
+ this.options.position = position;
255
+ }
256
+ if (offset) {
257
+ this.options.offset = offset;
258
+ }
259
+ const positionStyles = this.getPositionStyles();
260
+ const styles = positionStyles.split(";").filter((s) => s.trim());
261
+ this.compassElement.style.top = "";
262
+ this.compassElement.style.right = "";
263
+ this.compassElement.style.bottom = "";
264
+ this.compassElement.style.left = "";
265
+ styles.forEach((style) => {
266
+ const [property, value] = style.split(":").map((s) => s.trim());
267
+ if (property && value) {
268
+ ;
269
+ this.compassElement.style[property] = value;
270
+ }
271
+ });
272
+ }
273
+ setColors(colors) {
274
+ this.options.colors = { ...this.options.colors, ...colors };
275
+ this.updateStyles();
276
+ }
277
+ /**
278
+ * Helper method to reset camera to look north (world Z- direction)
279
+ * This is a convenience method that can be called when handling the 'resetToNorth' event
280
+ */
281
+ static resetCameraToNorth(camera, smooth = true) {
282
+ if (smooth) {
283
+ const startQuaternion = camera.quaternion.clone();
284
+ const targetQuaternion = new THREE.Quaternion();
285
+ const targetMatrix = new THREE.Matrix4();
286
+ targetMatrix.lookAt(
287
+ camera.position,
288
+ camera.position.clone().add(new THREE.Vector3(0, 0, -1)),
289
+ new THREE.Vector3(0, 1, 0)
290
+ );
291
+ targetQuaternion.setFromRotationMatrix(targetMatrix);
292
+ const duration = 500;
293
+ const startTime = Date.now();
294
+ const animate = () => {
295
+ const elapsed = Date.now() - startTime;
296
+ const progress = Math.min(elapsed / duration, 1);
297
+ const easeProgress = 1 - Math.pow(1 - progress, 3);
298
+ camera.quaternion.slerpQuaternions(
299
+ startQuaternion,
300
+ targetQuaternion,
301
+ easeProgress
302
+ );
303
+ if (progress < 1) {
304
+ requestAnimationFrame(animate);
305
+ }
306
+ };
307
+ animate();
308
+ } else {
309
+ camera.lookAt(camera.position.clone().add(new THREE.Vector3(0, 0, -1)));
310
+ }
311
+ }
312
+ updateStyles() {
313
+ const existingStyle = document.getElementById("threejs-compass-styles");
314
+ if (existingStyle) {
315
+ existingStyle.remove();
316
+ }
317
+ this.setupStyles();
318
+ }
319
+ dispose() {
320
+ this.stop();
321
+ if (this.compassElement && this.compassElement.parentNode) {
322
+ this.compassElement.parentNode.removeChild(this.compassElement);
323
+ }
324
+ const compassElements = document.querySelectorAll(
325
+ ".threejs-compass-overlay"
326
+ );
327
+ if (compassElements.length === 0) {
328
+ const style = document.getElementById("threejs-compass-styles");
329
+ if (style) {
330
+ style.remove();
331
+ }
332
+ }
333
+ }
334
+ };
335
+
336
+ export {
337
+ CompassOverlay
338
+ };
339
+ //# sourceMappingURL=chunk-P35QJCOG.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../packages/compass/src/CompassOverlay.ts"],"sourcesContent":["import * as THREE from 'three'\n\nexport interface CompassOverlayOptions {\n container?: HTMLElement\n size?: number\n position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n offset?: { x: number; y: number }\n colors?: {\n background: string\n border: string\n arrow: string\n text: string\n ticks: string\n }\n}\n\n// Define event types for the compass\ninterface CompassOverlayEventMap {\n resetToNorth: {}\n}\n\nexport interface CompassOverlayEvent {\n type: 'resetToNorth'\n}\n\nexport class CompassOverlay extends THREE.EventDispatcher<CompassOverlayEventMap> {\n private camera: THREE.Camera\n private container: HTMLElement\n private compassElement!: HTMLElement\n private arrowElement!: HTMLElement\n private isActive: boolean = false\n private options: Required<CompassOverlayOptions>\n private currentRotation: number = 0\n\n constructor(camera: THREE.Camera, options: CompassOverlayOptions = {}) {\n super()\n this.camera = camera\n\n // Set default options\n this.options = {\n container: options.container || document.body,\n size: options.size || 100,\n position: options.position || 'bottom-right',\n offset: options.offset || { x: 20, y: 20 },\n colors: {\n background: options.colors?.background || '#1a1a1a',\n border: options.colors?.border || '#333333',\n arrow: options.colors?.arrow || '#ff4444',\n text: options.colors?.text || '#ffffff',\n ticks: options.colors?.ticks || '#666666',\n ...options.colors,\n },\n }\n\n this.container = this.options.container\n this.createCompassElement()\n this.setupStyles()\n }\n\n private createCompassElement(): void {\n // Create main compass container\n this.compassElement = document.createElement('div')\n this.compassElement.className = 'threejs-compass-overlay'\n\n // Create compass background with ticks\n const compassBg = document.createElement('div')\n compassBg.className = 'compass-background'\n\n // Create cardinal direction labels\n const directions = [\n { label: 'N', angle: 0 },\n { label: 'E', angle: 90 },\n { label: 'S', angle: 180 },\n { label: 'W', angle: 270 },\n ]\n\n // Create tick marks\n for (let i = 0; i < 360; i += 11.25) {\n const tick = document.createElement('div')\n tick.className =\n i % 45 === 0 ? 'compass-tick-major' : 'compass-tick-minor'\n const tickLength = i % 30 === 0 ? 8 : 4\n tick.style.transform = `rotate(${i}deg) translateY(-${this.options.size / 2 - tickLength - 2}px)`\n compassBg.appendChild(tick)\n }\n\n directions.forEach(({ label, angle }) => {\n const dirLabel = document.createElement('div')\n dirLabel.className = 'compass-label'\n dirLabel.textContent = label\n dirLabel.style.transform = `rotate(${angle}deg) translateY(-${this.options.size / 2 - 15}px) rotate(-${angle}deg)`\n compassBg.appendChild(dirLabel)\n })\n\n // Create arrow element\n this.arrowElement = document.createElement('div')\n this.arrowElement.className = 'compass-arrow'\n\n // Arrow SVG\n this.arrowElement.innerHTML = `\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <polygon points=\"10,2 14,12 10,10 6,12\" fill=\"${this.options.colors.arrow}\" stroke=\"#000\" stroke-width=\"0.5\"/>\n </svg>\n `\n\n this.compassElement.appendChild(compassBg)\n this.compassElement.appendChild(this.arrowElement)\n this.container.appendChild(this.compassElement)\n\n // Add double-click event to reset camera to north\n this.setupDoubleClickHandler()\n }\n\n private setupStyles(): void {\n // Check if styles already exist\n if (document.getElementById('threejs-compass-styles')) {\n return\n }\n\n const style = document.createElement('style')\n style.id = 'threejs-compass-styles'\n style.textContent = `\n .threejs-compass-overlay {\n position: fixed;\n width: ${this.options.size}px;\n height: ${this.options.size}px;\n background: ${this.options.colors.background};\n border: 2px solid ${this.options.colors.border};\n border-radius: 50%;\n z-index: 1000;\n pointer-events: none;\n user-select: none;\n transition: opacity 0.2s ease;\n ${this.getPositionStyles()};\n }\n\n .compass-background {\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n }\n\n .compass-label {\n position: absolute;\n top: 50%;\n left: 50%;\n transform-origin: center;\n color: ${this.options.colors.text};\n font-family: Arial, sans-serif;\n font-size: 12px;\n font-weight: bold;\n text-align: center;\n width: 12px;\n height: 12px;\n line-height: 12px;\n margin-left: -6px;\n margin-top: -6px;\n }\n\n .compass-tick-major {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 2px;\n height: 8px;\n background: ${this.options.colors.ticks};\n transform-origin: center top;\n margin-left: -1px;\n margin-top: -4px;\n }\n\n .compass-tick-minor {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 1px;\n height: 4px;\n background: ${this.options.colors.ticks};\n transform-origin: center top;\n margin-left: -0.5px;\n margin-top: -2px;\n opacity: 0.6;\n }\n\n .compass-arrow {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transform-origin: center;\n transition: transform 0.1s ease-out;\n z-index: 1;\n }\n\n .compass-arrow svg {\n display: block;\n filter: drop-shadow(0 0 2px rgba(0,0,0,0.5));\n }\n `\n\n document.head.appendChild(style)\n }\n\n private setupDoubleClickHandler(): void {\n // Enable pointer events for interaction\n this.compassElement.style.pointerEvents = 'auto'\n this.compassElement.style.cursor = 'pointer'\n\n // Add double-click event listener\n this.compassElement.addEventListener('dblclick', (event) => {\n event.preventDefault()\n event.stopPropagation()\n\n // Dispatch custom event\n this.dispatchEvent({ type: 'resetToNorth' })\n })\n\n // Add visual feedback on hover\n this.compassElement.addEventListener('mouseenter', () => {\n this.compassElement.style.opacity = '0.8'\n })\n\n this.compassElement.addEventListener('mouseleave', () => {\n this.compassElement.style.opacity = '1'\n })\n }\n\n private updateTicksAndLabels(): void {\n // Update compass labels\n\n // Update tick marks\n const majorTicks = this.compassElement.querySelectorAll(\n '.compass-tick-major'\n )\n const minorTicks = this.compassElement.querySelectorAll(\n '.compass-tick-minor'\n )\n\n majorTicks.forEach((tick, index) => {\n const angle = (index * 30) % 360 // Major ticks every 30 degrees\n const tickLength = 8\n ;(tick as HTMLElement).style.transform =\n `rotate(${angle}deg) translateY(-${this.options.size / 2 - tickLength - 2}px)`\n })\n\n minorTicks.forEach((tick, index) => {\n // Minor ticks are at angles that aren't multiples of 30\n const allAngles = []\n for (let i = 0; i < 360; i += 10) {\n if (i % 30 !== 0) {\n // Skip major tick positions\n allAngles.push(i)\n }\n }\n const angle = allAngles[index] || 0\n const tickLength = 4\n ;(tick as HTMLElement).style.transform =\n `rotate(${angle}deg) translateY(-${this.options.size / 2 - tickLength - 2}px)`\n })\n\n const labels = this.compassElement.querySelectorAll('.compass-label')\n labels.forEach((label, index) => {\n const directions = [\n { label: 'N', angle: 0 },\n { label: 'E', angle: 90 },\n { label: 'S', angle: 180 },\n { label: 'W', angle: 270 },\n ]\n const { angle } = directions[index]\n ;(label as HTMLElement).style.transform =\n `rotate(${angle}deg) translateY(-${this.options.size / 2 - 15}px) rotate(-${angle}deg)`\n })\n }\n\n private getPositionStyles(): string {\n const { position, offset } = this.options\n\n switch (position) {\n case 'top-left':\n return `top: ${offset.y}px; left: ${offset.x}px;`\n case 'top-right':\n return `top: ${offset.y}px; right: ${offset.x}px;`\n case 'bottom-left':\n return `bottom: ${offset.y}px; left: ${offset.x}px;`\n case 'bottom-right':\n default:\n return `bottom: ${offset.y}px; right: ${offset.x}px;`\n }\n }\n\n public start(): void {\n if (this.isActive) return\n this.isActive = true\n this.update()\n }\n\n public stop(): void {\n this.isActive = false\n }\n\n public update(): void {\n if (!this.isActive) return\n\n // Get the camera's world matrix\n const cameraMatrix = new THREE.Matrix4()\n this.camera.updateMatrixWorld()\n cameraMatrix.copy(this.camera.matrixWorld)\n\n // Extract the camera's forward direction (negative Z in camera space)\n const forward = new THREE.Vector3(0, 0, -1)\n forward.transformDirection(cameraMatrix)\n\n // Project to XZ plane (ignore Y component for top-down compass view)\n forward.y = 0\n forward.normalize()\n\n // Calculate angle from world -Z axis (north)\n // World Z- is \"north\", X- is \"east\" (flipped X axis)\n const targetAngle = Math.atan2(-forward.x, -forward.z) * (180 / Math.PI)\n\n // Calculate the shortest rotation path to avoid spinning around\n let angleDiff = targetAngle - this.currentRotation\n\n // Normalize the angle difference to be between -180 and 180\n while (angleDiff > 180) angleDiff -= 360\n while (angleDiff < -180) angleDiff += 360\n\n // Update current rotation with smooth transition\n this.currentRotation += angleDiff\n\n // Update arrow rotation (negative because we want the arrow to point to north, not where camera is facing)\n this.arrowElement.style.transform = `translate(-50%, -50%) rotate(${-this.currentRotation}deg)`\n\n // Continue updating if active\n if (this.isActive) {\n requestAnimationFrame(() => this.update())\n }\n }\n\n public setCamera(camera: THREE.Camera): void {\n this.camera = camera\n }\n\n public setSize(size: number): void {\n this.options.size = size\n this.compassElement.style.width = `${size}px`\n this.compassElement.style.height = `${size}px`\n this.updateTicksAndLabels()\n this.updateStyles()\n }\n\n public setPosition(\n position: CompassOverlayOptions['position'],\n offset?: { x: number; y: number }\n ): void {\n if (position) {\n this.options.position = position\n }\n if (offset) {\n this.options.offset = offset\n }\n\n // Update position styles\n const positionStyles = this.getPositionStyles()\n const styles = positionStyles.split(';').filter((s) => s.trim())\n\n // Clear existing position styles\n this.compassElement.style.top = ''\n this.compassElement.style.right = ''\n this.compassElement.style.bottom = ''\n this.compassElement.style.left = ''\n\n // Apply new position styles\n styles.forEach((style) => {\n const [property, value] = style.split(':').map((s) => s.trim())\n if (property && value) {\n ;(this.compassElement.style as any)[property] = value\n }\n })\n }\n\n public setColors(colors: Partial<CompassOverlayOptions['colors']>): void {\n this.options.colors = { ...this.options.colors, ...colors }\n this.updateStyles()\n }\n\n /**\n * Helper method to reset camera to look north (world Z- direction)\n * This is a convenience method that can be called when handling the 'resetToNorth' event\n */\n public static resetCameraToNorth(\n camera: THREE.Camera,\n smooth: boolean = true\n ): void {\n if (smooth) {\n // Create a smooth rotation animation to north\n const startQuaternion = camera.quaternion.clone()\n const targetQuaternion = new THREE.Quaternion()\n\n // Set target rotation to look at north (Z-)\n const targetMatrix = new THREE.Matrix4()\n targetMatrix.lookAt(\n camera.position,\n camera.position.clone().add(new THREE.Vector3(0, 0, -1)),\n new THREE.Vector3(0, 1, 0)\n )\n targetQuaternion.setFromRotationMatrix(targetMatrix)\n\n // Animate rotation\n const duration = 500 // milliseconds\n const startTime = Date.now()\n\n const animate = () => {\n const elapsed = Date.now() - startTime\n const progress = Math.min(elapsed / duration, 1)\n\n // Use smooth easing\n const easeProgress = 1 - Math.pow(1 - progress, 3)\n\n camera.quaternion.slerpQuaternions(\n startQuaternion,\n targetQuaternion,\n easeProgress\n )\n\n if (progress < 1) {\n requestAnimationFrame(animate)\n }\n }\n animate()\n } else {\n // Instant rotation to north\n camera.lookAt(camera.position.clone().add(new THREE.Vector3(0, 0, -1)))\n }\n }\n\n private updateStyles(): void {\n // Remove existing styles\n const existingStyle = document.getElementById('threejs-compass-styles')\n if (existingStyle) {\n existingStyle.remove()\n }\n\n // Recreate styles with new values\n this.setupStyles()\n }\n\n public dispose(): void {\n this.stop()\n\n if (this.compassElement && this.compassElement.parentNode) {\n this.compassElement.parentNode.removeChild(this.compassElement)\n }\n\n // Remove styles if no other compass instances exist\n const compassElements = document.querySelectorAll(\n '.threejs-compass-overlay'\n )\n if (compassElements.length === 0) {\n const style = document.getElementById('threejs-compass-styles')\n if (style) {\n style.remove()\n }\n }\n }\n}\n"],"mappings":"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","names":[]}