@ringozz/godot 4.7.1-555 → 4.7.1-562

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/README.md CHANGED
@@ -97,7 +97,7 @@ function Foo() {
97
97
  </Suspense>
98
98
  ```
99
99
 
100
- Each extension maps to its Godot class (source assets only — `gltf`/`tscn`/`obj` → `PackedScene`, `jpg`/`jpeg`/`png`/`webp` → `CompressedTexture2D`, `svg` → `Texture2D`, `exr`/`hdr` → `TextureLayered`, `wav` → `AudioStreamWAV`, `ogg` → `AudioStreamOggVorbis`, `mp3` → `AudioStreamMP3`, `tres` → `Resource`, `po` → `Translation`), declared in `src/assets.d.ts`. Imported products (`.scn`/`.ctex`) are never imported directly.
100
+ Each extension maps to its Godot class (source assets only — `gltf`/`tscn`/`obj` → `PackedScene`, `jpg`/`jpeg`/`png`/`webp` → `CompressedTexture2D`, `svg` → `Texture2D`, `exr`/`hdr` → `TextureLayered`, `wav` → `AudioStreamWAV`, `ogg` → `AudioStreamOggVorbis`, `mp3` → `AudioStreamMP3`, `ttf`/`otf` → `FontFile`, `tres` → `Resource`, `po` → `Translation`), declared in `src/assets.d.ts`. Imported products (`.scn`/`.ctex`) are never imported directly.
101
101
 
102
102
  Textures are shared only through Godot's `ResourceLoader` path cache. The glTF import pipeline may produce per-scene texture instances (same `res://` path, separate objects), so don't assume two scenes referencing the same image share one texture object.
103
103
 
@@ -116,7 +116,7 @@ initDebug();
116
116
  - `find(pattern?, type?)` returns a JS array of nodes matching a name glob and/or class (`$.find('*', 'RigidBody3D')` lists all physics bodies).
117
117
  - `$.tree` is the live `SceneTree`; `$.paused = true` freezes the scene tree for inspection; `$.paused = false` resumes.
118
118
 
119
- `initDebug` also registers an `uncaughtException` handler that keeps the process alive, polyfills `DOMRect` (not defined in Bun, needed by `getBoundingClientRect()`), and adds `getBoundingClientRect()` to `CanvasItem`/`Node3D`.
119
+ `initDebug` also registers an `uncaughtException` handler that keeps the process alive, polyfills `DOMRect` (not defined in Bun, needed by `getBoundingClientRect()`), and adds `getBoundingClientRect()` to `CanvasItem`/`Node3D` (Node3D covers descendant meshes; returns CSS-pixel coordinates).
120
120
 
121
121
  ## Notes
122
122
 
@@ -729,7 +729,7 @@ export class DisplayServer extends GodotVar {
729
729
  static screenGetDpi(screen?: number /* = -1 */): number { return _C($(), 10248, screen) as number; }
730
730
 
731
731
  /**
732
- * Returns the scale factor of the specified screen by index. Returns `1.0` if `screen` is invalid.
732
+ * Returns the scale factor of the specified screen by index. Returns `1.0` if `screen` is invalid. See also {@link screenGetMaxScale}.
733
733
  * **Note:** One of the following constants can be used as `screen`: {@link SCREEN_OF_MAIN_WINDOW}, {@link SCREEN_PRIMARY}, {@link SCREEN_WITH_MOUSE_FOCUS}, or {@link SCREEN_WITH_KEYBOARD_FOCUS}.
734
734
  * **Note:** On macOS, the returned value is `2.0` for hiDPI (Retina) screens, and `1.0` for all other cases.
735
735
  * **Note:** On Linux (Wayland), the returned value is accurate only when `screen` is {@link SCREEN_OF_MAIN_WINDOW}. Due to API limitations, passing a direct index will return a rounded-up integer, if the screen has a fractional scale (e.g. `1.25` would get rounded up to `2.0`).
@@ -743,9 +743,8 @@ export class DisplayServer extends GodotVar {
743
743
  static isTouchscreenAvailable(): boolean { return _C($(), 8369) as boolean; }
744
744
 
745
745
  /**
746
- * Returns the greatest scale factor of all screens.
747
- * **Note:** On macOS returned value is `2.0` if there is at least one hiDPI (Retina) screen in the system, and `1.0` in all other cases.
748
- * **Note:** This method is implemented only on macOS.
746
+ * Returns the greatest scale factor of all screens. See also {@link screenGetScale}.
747
+ * **Note:** On macOS, the returned value is `2.0` if there is at least one hiDPI (Retina) screen in the system, and `1.0` in all other cases.
749
748
  */
750
749
  static screenGetMaxScale(): number { return _C($(), 10253) as number; }
751
750
 
@@ -296,12 +296,14 @@ export const BakeMode = {
296
296
  */
297
297
  BAKE_DISABLED: 0,
298
298
  /**
299
- * Light is taken into account in static baking ({@link VoxelGI}, {@link LightmapGI}, SDFGI ({@link Environment.sdfgiEnabled})). The light can be moved around or modified, but its global illumination will not update in real-time. This is suitable for subtle changes (such as flickering torches), but generally not large changes such as toggling a light on and off.
299
+ * Light is taken into account in static baking ({@link VoxelGI}, {@link LightmapGI}, SDFGI ({@link Environment.sdfgiEnabled})). The light can be moved around or modified, but its global illumination will not update in real-time.
300
300
  * **Note:** The light is not baked in {@link LightmapGI} if {@link editorOnly} is `true`.
301
+ * **Note:** When using {@link LightmapGI}, both the direct and indirect light are baked. Since direct light is baked, the light doesn't display a specular lobe on static lightmapped meshes. Shadows on static lightmapped meshes will also look less detailed, but the light still casts shadows that can be displayed on dynamic objects. Since real-time light computations are skipped on static lightmapped meshes, this bake mode improves runtime performance compared to {@link BAKE_DYNAMIC} and {@link BAKE_DISABLED}.
301
302
  */
302
303
  BAKE_STATIC: 1,
303
304
  /**
304
- * Light is taken into account in dynamic baking ({@link VoxelGI} and SDFGI ({@link Environment.sdfgiEnabled}) only). The light can be moved around or modified with global illumination updating in real-time. The light's global illumination appearance will be slightly different compared to {@link BAKE_STATIC}. This has a greater performance cost compared to {@link BAKE_STATIC}. When using SDFGI, the update speed of dynamic lights is affected by {@link ProjectSettings.rendering/globalIllumination/sdfgi/framesToUpdateLights}.
305
+ * Light is taken into account in dynamic baking ({@link VoxelGI} and SDFGI ({@link Environment.sdfgiEnabled})). The light can be moved around or modified with global illumination updating in real-time. The light's global illumination appearance will be slightly different compared to {@link BAKE_STATIC}. This has a greater performance cost compared to {@link BAKE_STATIC}. When using SDFGI, the update speed of dynamic lights is affected by {@link ProjectSettings.rendering/globalIllumination/sdfgi/framesToUpdateLights}.
306
+ * **Note:** When using {@link LightmapGI}, the light's indirect light is baked, but direct light and shadows remain real-time. This mode allows performing *subtle* changes to a light's color, energy, and position while still looking fairly correct. For example, you can use this to create flickering static torches that have their indirect light baked.
305
307
  */
306
308
  BAKE_DYNAMIC: 2,
307
309
  } as const;
@@ -48,9 +48,9 @@ export class RandomNumberGenerator extends RefCounted {
48
48
  randiRange(from: number, to: number): number { return _C(this, 9725, from, to) as number; }
49
49
 
50
50
  /**
51
- * Returns a random integer between `0` and the size of the array that is passed as a parameter. Each value in the array should be a floating-point number that represents the relative likelihood that it will be returned as an index. A higher value means the value is more likely to be returned as an index, while a value of `0` means it will never be returned as an index.
51
+ * Returns a random integer between `0` and the size of the array that is passed as a parameter. Each value in the array should be a non-negative floating-point number that represents the relative likelihood that it will be returned as an index. A higher value means the value is more likely to be returned as an index, while a value of `0` means it will never be returned as an index.
52
52
  * For example, if {@link code skip-lint}{@link 0.5, 1, 1, 2} is passed as a parameter, then the method is twice as likely to return `3` (the index of the value `2`) and twice as unlikely to return `0` (the index of the value `0.5`) compared to the indices `1` and `2`.
53
- * Prints an error and returns `-1` if the array is empty.
53
+ * Prints an error and returns `-1` if the array is empty or contains any negative values.
54
54
  *
55
55
  *
56
56
  * ```gdscript
@@ -6,7 +6,10 @@ import type { Callable, StringName, Variant } from './index.ts';
6
6
 
7
7
  /** Godot's Array type — lazy, indexed container. */
8
8
  export class GodotArray<T = any> extends GodotVar implements Iterable<T> {
9
- constructor(...args: any[]) { super(28, ...args); }
9
+ constructor(from?: Iterable<T>) {
10
+ super(28);
11
+ if (from != null) for (const x of from) _C(this, 9630, x);
12
+ }
10
13
 
11
14
  get length(): number { return _C(this, 13608) as number; }
12
15
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ringozz/godot",
3
3
  "author": "Vladimir Davidovich",
4
- "version": "4.7.1-555",
4
+ "version": "4.7.1-562",
5
5
  "description": "Node-API bindings for Godot Engine — call Godot classes from JavaScript",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -32,16 +32,16 @@
32
32
  "godot": {
33
33
  "version_major": 4,
34
34
  "version_minor": 7,
35
- "version_patch": 1,
35
+ "version_patch": 2,
36
36
  "version_status": "stable",
37
37
  "version_build": "official",
38
- "version_full_name": "Godot Engine v4.7.1.stable.official",
38
+ "version_full_name": "Godot Engine v4.7.2.stable.official",
39
39
  "precision": "single"
40
40
  },
41
41
  "optionalDependencies": {
42
- "@ringozz/godot-macos-arm64": "^4.7.1-555",
43
- "@ringozz/godot-windows-x86_64": "^4.7.1-555",
44
- "@ringozz/godot-web-wasm32": "^4.7.1-555"
42
+ "@ringozz/godot-macos-arm64": "^4.7.1-562",
43
+ "@ringozz/godot-windows-x86_64": "^4.7.1-562",
44
+ "@ringozz/godot-web-wasm32": "^4.7.1-562"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@types/bun": "*"
package/src/assets.d.ts CHANGED
@@ -101,6 +101,20 @@ declare module '*.wav' {
101
101
  export const materialize: Promise<unknown>;
102
102
  }
103
103
 
104
+ declare module '*.ttf' {
105
+ import type { FontFile } from '@ringozz/godot/FontFile';
106
+ const asset: Promise<FontFile>;
107
+ export default asset;
108
+ export const materialize: Promise<unknown>;
109
+ }
110
+
111
+ declare module '*.otf' {
112
+ import type { FontFile } from '@ringozz/godot/FontFile';
113
+ const asset: Promise<FontFile>;
114
+ export default asset;
115
+ export const materialize: Promise<unknown>;
116
+ }
117
+
104
118
  declare module '*.ogg' {
105
119
  import type { AudioStreamOggVorbis } from '@ringozz/godot/AudioStreamOggVorbis';
106
120
  const asset: Promise<AudioStreamOggVorbis>;
package/src/debug.ts CHANGED
@@ -14,10 +14,16 @@ import { Node3D } from '../gen/classes/Node3D.ts';
14
14
  import { OS } from '../gen/classes/OS.ts';
15
15
  import { SceneTree } from '../gen/classes/SceneTree.ts';
16
16
  import { Time } from '../gen/classes/Time.ts';
17
+ import type { Viewport } from '../gen/classes/Viewport.ts';
17
18
  import type { Window } from '../gen/classes/Window.ts';
18
19
 
19
20
  const tree = Engine.getMainLoop() as SceneTree;
20
21
 
22
+ /** Does a snapshot line match a string (case-insensitive substring) or `RegExp` pattern? */
23
+ function matches(pattern: string | RegExp | undefined, line: string): boolean {
24
+ return !pattern || (pattern instanceof RegExp ? pattern.test(line) : line.toLowerCase().includes(pattern.toLowerCase()));
25
+ }
26
+
21
27
  /**
22
28
  * Dump a Godot value or object as a string.
23
29
  * Value types (vectors, arrays, dictionaries, …) render via Godot's `str()`.
@@ -57,8 +63,7 @@ export function snapshot(pattern?: string | RegExp, node?: Node, depth = 99): st
57
63
  if (d <= 0) return;
58
64
  const count = n.getChildCount();
59
65
  const line = `${ind}${n.getClass()} "${n.name}" (${count} children)`;
60
- const keep = !pattern || (pattern instanceof RegExp ? pattern.test(line) : line.toLowerCase().includes(pattern.toLowerCase()));
61
- if (keep) lines.push(line);
66
+ if (matches(pattern, line)) lines.push(line);
62
67
  for (let i = 0; i < count; i++) {
63
68
  const c = n.getChild(i);
64
69
  if (c) walk(c as Node, d - 1, ind + ' ');
@@ -102,7 +107,7 @@ export function click(path: string): string | null {
102
107
  const node = resolveNode(path, root);
103
108
  if (!(node instanceof Control)) return null;
104
109
  const rect = node.getGlobalRect();
105
- const scale = root!.contentScaleFactor;
110
+ const scale = root.contentScaleFactor;
106
111
  const position = new Vector2(
107
112
  (rect.position.x + rect.size.x / 2) * scale,
108
113
  (rect.position.y + rect.size.y / 2) * scale,
@@ -113,7 +118,7 @@ export function click(path: string): string | null {
113
118
  event.globalPosition = position;
114
119
  event.buttonIndex = MouseButton.MOUSE_BUTTON_LEFT;
115
120
  event.pressed = down;
116
- root!.pushInput(event);
121
+ root.pushInput(event);
117
122
  };
118
123
  press(true);
119
124
  setTimeout(() => press(false), 50);
@@ -176,6 +181,150 @@ function ensureDOMRect(): void {
176
181
  };
177
182
  }
178
183
 
184
+ type Push = (x: number, y: number) => void;
185
+ type BoundsCollect = (node: any, push: Push, viewport: Viewport) => void;
186
+
187
+ /** Affine 2D point mapper from a transform's basis/origin, optionally scaled. */
188
+ function makeAffine(t: any, scale = 1): (x: number, y: number) => [number, number] {
189
+ const tox = t.origin.x, toy = t.origin.y;
190
+ const txx = t.x.x, txy = t.x.y;
191
+ const tyx = t.y.x, tyy = t.y.y;
192
+ return (x, y) => [(tox + txx * x + tyx * y) * scale, (toy + txy * x + tyy * y) * scale];
193
+ }
194
+
195
+ /**
196
+ * Axis-aligned 2D bounds accumulator in CSS pixels. Non-finite points (e.g.
197
+ * geometry behind the camera) are dropped; `rect()` is `null` when no point
198
+ * was added.
199
+ */
200
+ function cssBounds(): { add(x: number, y: number): void; rect(): [number, number, number, number] | null } {
201
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
202
+ let found = false;
203
+ return {
204
+ add(x, y) {
205
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return;
206
+ if (x < minX) minX = x; if (y < minY) minY = y;
207
+ if (x > maxX) maxX = x; if (y > maxY) maxY = y;
208
+ found = true;
209
+ },
210
+ rect() {
211
+ return found ? [minX, minY, maxX - minX, maxY - minY] : null;
212
+ },
213
+ };
214
+ }
215
+
216
+ /** World-space AABB (min, max) of a local AABB under its global transform, or `null` when degenerate. */
217
+ function worldAabb(aabb: any, gt: any): [number, number, number, number, number, number] | null {
218
+ if (aabb.size.x === 0 && aabb.size.y === 0 && aabb.size.z === 0) return null;
219
+ const bx = gt.basis.x, by = gt.basis.y, bz = gt.basis.z;
220
+ const cx = aabb.position.x + aabb.size.x / 2;
221
+ const cy = aabb.position.y + aabb.size.y / 2;
222
+ const cz = aabb.position.z + aabb.size.z / 2;
223
+ const ex = Math.hypot(bx.x, bx.y, bx.z) * aabb.size.x / 2;
224
+ const ey = Math.hypot(by.x, by.y, by.z) * aabb.size.y / 2;
225
+ const ez = Math.hypot(bz.x, bz.y, bz.z) * aabb.size.z / 2;
226
+ const ox = gt.origin.x, oy = gt.origin.y, oz = gt.origin.z;
227
+ const wx = ox + bx.x * cx + by.x * cy + bz.x * cz;
228
+ const wy = oy + bx.y * cx + by.y * cy + bz.y * cz;
229
+ const wz = oz + bx.z * cx + by.z * cy + bz.z * cz;
230
+ return [wx - ex, wy - ey, wz - ez, wx + ex, wy + ey, wz + ez];
231
+ }
232
+
233
+ /**
234
+ * World-space AABB of a node's visual content: the union of its own AABB (when
235
+ * it is a `VisualInstance3D`) and every descendant `VisualInstance3D`'s, each in
236
+ * its own global transform — mirrors the editor's `_calculate_spatial_bounds`.
237
+ * Returns `null` when there is no non-degenerate visual.
238
+ */
239
+ function worldBounds(node: Node3D): [number, number, number, number, number, number] | null {
240
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
241
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
242
+ const add = (aabb: any, gt: any) => {
243
+ const box = worldAabb(aabb, gt);
244
+ if (!box) return;
245
+ if (box[0] < minX) minX = box[0]; if (box[3] > maxX) maxX = box[3];
246
+ if (box[1] < minY) minY = box[1]; if (box[4] > maxY) maxY = box[4];
247
+ if (box[2] < minZ) minZ = box[2]; if (box[5] > maxZ) maxZ = box[5];
248
+ };
249
+ if (typeof (node as any).getAabb === 'function') {
250
+ add((node as any).getAabb(), node.globalTransform);
251
+ }
252
+ for (const child of node.findChildren('*', 'VisualInstance3D', true, false) as unknown as any[]) {
253
+ if (child && typeof child.getAabb === 'function') {
254
+ add(child.getAabb(), child.globalTransform);
255
+ }
256
+ }
257
+ return minX === Infinity ? null : [minX, minY, minZ, maxX, maxY, maxZ];
258
+ }
259
+
260
+ /**
261
+ * Compute a node's bounding rect in CSS pixels. The callback pushes points in
262
+ * viewport logical units; they're mapped through the viewport's screen transform
263
+ * (content scale, stretch, SubViewport/child-window placement), then divided by
264
+ * screenGetScale to reach CSS pixels. Never throws: React DevTools calls this on
265
+ * every commit and must not see a throw (e.g. a wrapper freed by an unmount).
266
+ */
267
+ function boundsForViewport(node: Node, collect: (push: Push, viewport: Viewport) => void): DOMRect | null {
268
+ try {
269
+ const viewport = node.getViewport();
270
+ if (!viewport) return null;
271
+ const xform = makeAffine(viewport.getScreenTransform(), 1 / DisplayServer.screenGetScale());
272
+ const bounds = cssBounds();
273
+ collect((x, y) => bounds.add(...xform(x, y)), viewport);
274
+ const r = bounds.rect();
275
+ return r ? new DOMRect(r[0], r[1], r[2], r[3]) : null;
276
+ } catch {
277
+ return null;
278
+ }
279
+ }
280
+
281
+ /** Install `getBoundingClientRect` on a node prototype. */
282
+ function defineBoundingClientRect(proto: object, collect: BoundsCollect): void {
283
+ Object.defineProperty(proto, 'getBoundingClientRect', {
284
+ configurable: true,
285
+ value(this: Node): DOMRect | null {
286
+ return boundsForViewport(this, (push, viewport) => collect(this, push, viewport));
287
+ },
288
+ });
289
+ }
290
+
291
+ /** Push the four corners of an axis-aligned rect through `emit`. */
292
+ function pushRect(emit: (x: number, y: number) => void, x0: number, y0: number, x1: number, y1: number): void {
293
+ for (const px of [x0, x1]) {
294
+ for (const py of [y0, y1]) {
295
+ emit(px, py);
296
+ }
297
+ }
298
+ }
299
+
300
+ const canvasItemBounds: BoundsCollect = (node, push) => {
301
+ if (node instanceof Control) {
302
+ const rc = node.getGlobalRect();
303
+ pushRect(push, rc.position.x, rc.position.y, rc.position.x + rc.size.x, rc.position.y + rc.size.y);
304
+ return;
305
+ }
306
+ if (typeof node.getRect !== 'function') return;
307
+ const rect = node.getRect() as Rect2;
308
+ const xform = makeAffine(node.getScreenTransform());
309
+ pushRect((px, py) => push(...xform(px, py)), rect.position.x, rect.position.y, rect.position.x + rect.size.x, rect.position.y + rect.size.y);
310
+ };
311
+
312
+ const node3dBounds: BoundsCollect = (node, push, viewport) => {
313
+ const camera = viewport.getCamera3d();
314
+ if (!camera) return;
315
+ const box = worldBounds(node);
316
+ if (!box) return;
317
+ const [minX, minY, minZ, maxX, maxY, maxZ] = box;
318
+ for (let i = 0; i < 8; i++) {
319
+ const s = camera.unprojectPosition(new Vector3(
320
+ i & 1 ? maxX : minX,
321
+ i & 2 ? maxY : minY,
322
+ i & 4 ? maxZ : minZ,
323
+ ));
324
+ push(s.x, s.y);
325
+ }
326
+ };
327
+
179
328
  /**
180
329
  * Bootstrap the debugging environment.
181
330
  *
@@ -192,101 +341,8 @@ export function initDebug(): void {
192
341
  });
193
342
 
194
343
  ensureDOMRect();
195
-
196
- Object.defineProperty(CanvasItem.prototype, 'getBoundingClientRect', {
197
- configurable: true,
198
- value(this: CanvasItem): DOMRect | null {
199
- const scale = DisplayServer.screenGetScale();
200
- if (this instanceof Control) {
201
- const rc = (this as Control).getGlobalRect();
202
- return new DOMRect(
203
- rc.position.x / scale, rc.position.y / scale,
204
- rc.size.x / scale, rc.size.y / scale,
205
- );
206
- }
207
-
208
- if (typeof (this as any).getRect !== 'function') return null;
209
-
210
- const rect = (this as any).getRect() as Rect2;
211
- const t = this.getScreenTransform();
212
-
213
- const pos = rect.position;
214
- const sz = rect.size;
215
- const tx = t.x;
216
- const ty = t.y;
217
- const to = t.origin;
218
-
219
- const x0 = pos.x, y0 = pos.y;
220
- const x1 = x0 + sz.x, y1 = y0 + sz.y;
221
- const txx = tx.x, txy = tx.y;
222
- const tyx = ty.x, tyy = ty.y;
223
- const tox = to.x, toy = to.y;
224
-
225
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
226
- const accum = (px: number, py: number) => {
227
- if (px < minX) minX = px; if (py < minY) minY = py;
228
- if (px > maxX) maxX = px; if (py > maxY) maxY = py;
229
- };
230
-
231
- accum(txx * x0 + tyx * y0 + tox, txy * x0 + tyy * y0 + toy);
232
- accum(txx * x1 + tyx * y0 + tox, txy * x1 + tyy * y0 + toy);
233
- accum(txx * x0 + tyx * y1 + tox, txy * x0 + tyy * y1 + toy);
234
- accum(txx * x1 + tyx * y1 + tox, txy * x1 + tyy * y1 + toy);
235
-
236
- return new DOMRect(
237
- minX / scale, minY / scale,
238
- (maxX - minX) / scale, (maxY - minY) / scale,
239
- );
240
- },
241
- });
242
-
243
- Object.defineProperty(Node3D.prototype, 'getBoundingClientRect', {
244
- configurable: true,
245
- value(this: Node3D): DOMRect | null {
246
- if (typeof (this as any).getAabb !== 'function') return null;
247
-
248
- const aabb = (this as any).getAabb();
249
- const gt = this.globalTransform;
250
- const viewport = this.getViewport();
251
- if (!viewport) return null;
252
- const camera = viewport.getCamera3d();
253
- if (!camera) return null;
254
- const scale = DisplayServer.screenGetScale();
255
-
256
- const b = gt.basis;
257
- const bx = b.x, by = b.y, bz = b.z;
258
- const o = gt.origin;
259
- const p = aabb.position, e = aabb.end;
260
-
261
- const [px, py, pz] = [p.x, p.y, p.z];
262
- const [ex, ey, ez] = [e.x, e.y, e.z];
263
- const [bxx, bxy, bxz] = [bx.x, bx.y, bx.z];
264
- const [byx, byy, byz] = [by.x, by.y, by.z];
265
- const [bzx, bzy, bzz] = [bz.x, bz.y, bz.z];
266
- const [ox, oy, oz] = [o.x, o.y, o.z];
267
-
268
- const corners = [
269
- [px, py, pz], [ex, py, pz], [px, ey, pz], [ex, ey, pz],
270
- [px, py, ez], [ex, py, ez], [px, ey, ez], [ex, ey, ez],
271
- ];
272
-
273
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
274
-
275
- for (const [cx, cy, cz] of corners) {
276
- const wx = bxx * cx + byx * cy + bzx * cz + ox;
277
- const wy = bxy * cx + byy * cy + bzy * cz + oy;
278
- const wz = bxz * cx + byz * cy + bzz * cz + oz;
279
- const s = camera.unprojectPosition(new Vector3(wx, wy, wz));
280
- if (s.x < minX) minX = s.x; if (s.y < minY) minY = s.y;
281
- if (s.x > maxX) maxX = s.x; if (s.y > maxY) maxY = s.y;
282
- }
283
-
284
- return new DOMRect(
285
- minX / scale, minY / scale,
286
- (maxX - minX) / scale, (maxY - minY) / scale,
287
- );
288
- },
289
- });
344
+ defineBoundingClientRect(CanvasItem.prototype, canvasItemBounds);
345
+ defineBoundingClientRect(Node3D.prototype, node3dBounds);
290
346
 
291
347
  (globalThis as any).$ = {
292
348
  Engine, OS, ClassDB, Time,
package/src/preload.ts CHANGED
@@ -44,6 +44,8 @@ const FORMATS: Record<string, { cls?: string; scan?: boolean; native?: boolean }
44
44
  mp3: { cls: 'AudioStreamMP3' },
45
45
  tres: { cls: 'Resource', scan: true, native: true },
46
46
  po: { cls: 'Translation', native: true },
47
+ ttf: { cls: 'FontFile' },
48
+ otf: { cls: 'FontFile' },
47
49
  mtl: { scan: true },
48
50
  };
49
51