@codexo/exojs 0.6.7 → 0.6.9
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/CHANGELOG.md +119 -0
- package/README.md +0 -36
- package/dist/esm/index.d.ts +0 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/rendering/index.d.ts +3 -0
- package/dist/esm/rendering/text/DynamicGlyphAtlas.d.ts +33 -0
- package/dist/esm/rendering/text/DynamicGlyphAtlas.js +140 -0
- package/dist/esm/rendering/text/DynamicGlyphAtlas.js.map +1 -0
- package/dist/esm/rendering/text/Text.d.ts +21 -17
- package/dist/esm/rendering/text/Text.js +93 -93
- package/dist/esm/rendering/text/Text.js.map +1 -1
- package/dist/esm/rendering/text/TextLayout.d.ts +13 -0
- package/dist/esm/rendering/text/TextLayout.js +63 -0
- package/dist/esm/rendering/text/TextLayout.js.map +1 -0
- package/dist/esm/rendering/text/TextStyle.d.ts +26 -3
- package/dist/esm/rendering/text/TextStyle.js +45 -0
- package/dist/esm/rendering/text/TextStyle.js.map +1 -1
- package/dist/esm/rendering/text/atlas-singleton.d.ts +7 -0
- package/dist/esm/rendering/text/atlas-singleton.js +17 -0
- package/dist/esm/rendering/text/atlas-singleton.js.map +1 -0
- package/dist/esm/rendering/text/types.d.ts +45 -0
- package/dist/esm/rendering/utils.js +1 -14
- package/dist/esm/rendering/utils.js.map +1 -1
- package/dist/exo.esm.js +344 -573
- package/dist/exo.esm.js.map +1 -1
- package/package.json +1 -9
- package/dist/esm/physics/RapierPhysicsWorld.d.ts +0 -136
- package/dist/esm/physics/RapierPhysicsWorld.js +0 -475
- package/dist/esm/physics/RapierPhysicsWorld.js.map +0 -1
- package/dist/esm/physics/index.d.ts +0 -1
package/dist/exo.esm.js
CHANGED
|
@@ -6211,19 +6211,6 @@ const createCanvas = (options = {}) => {
|
|
|
6211
6211
|
context.fillRect(0, 0, newCanvas.width, newCanvas.height);
|
|
6212
6212
|
return newCanvas;
|
|
6213
6213
|
};
|
|
6214
|
-
const heightCache = new Map();
|
|
6215
|
-
const determineFontHeight = (font) => {
|
|
6216
|
-
if (!heightCache.has(font)) {
|
|
6217
|
-
const body = document.body;
|
|
6218
|
-
const dummy = document.createElement('div');
|
|
6219
|
-
dummy.appendChild(document.createTextNode('M'));
|
|
6220
|
-
dummy.setAttribute('style', `font: ${font};position:absolute;top:0;left:0`);
|
|
6221
|
-
body.appendChild(dummy);
|
|
6222
|
-
heightCache.set(font, dummy.offsetHeight);
|
|
6223
|
-
body.removeChild(dummy);
|
|
6224
|
-
}
|
|
6225
|
-
return heightCache.get(font);
|
|
6226
|
-
};
|
|
6227
6214
|
|
|
6228
6215
|
class Texture {
|
|
6229
6216
|
static _black = null;
|
|
@@ -17629,14 +17616,227 @@ class AnimatedSprite extends Sprite {
|
|
|
17629
17616
|
}
|
|
17630
17617
|
}
|
|
17631
17618
|
|
|
17619
|
+
const glyphPadding = 2;
|
|
17620
|
+
class ShelfPacker {
|
|
17621
|
+
_shelves = [];
|
|
17622
|
+
_width;
|
|
17623
|
+
_height;
|
|
17624
|
+
constructor(width, height) {
|
|
17625
|
+
this._width = width;
|
|
17626
|
+
this._height = height;
|
|
17627
|
+
}
|
|
17628
|
+
insert(width, height) {
|
|
17629
|
+
// Try existing shelves in order (ascending y)
|
|
17630
|
+
for (const shelf of this._shelves) {
|
|
17631
|
+
if (shelf.height >= height && shelf.cursorX + width <= this._width) {
|
|
17632
|
+
const x = shelf.cursorX;
|
|
17633
|
+
shelf.cursorX += width;
|
|
17634
|
+
return { x, y: shelf.y };
|
|
17635
|
+
}
|
|
17636
|
+
}
|
|
17637
|
+
// Create a new shelf at the bottom
|
|
17638
|
+
const last = this._shelves[this._shelves.length - 1];
|
|
17639
|
+
const bottomY = last === undefined ? 0 : last.y + last.height;
|
|
17640
|
+
if (bottomY + height > this._height) {
|
|
17641
|
+
throw new Error(`GlyphAtlas full — clear() and re-render, or instantiate with larger dims`);
|
|
17642
|
+
}
|
|
17643
|
+
this._shelves.push({ y: bottomY, height, cursorX: width });
|
|
17644
|
+
return { x: 0, y: bottomY };
|
|
17645
|
+
}
|
|
17646
|
+
reset() {
|
|
17647
|
+
this._shelves.length = 0;
|
|
17648
|
+
}
|
|
17649
|
+
}
|
|
17650
|
+
/**
|
|
17651
|
+
* A shared atlas that rasterizes glyphs on demand into an offscreen canvas
|
|
17652
|
+
* and wraps it as a Texture for use by the Mesh-based Text renderer.
|
|
17653
|
+
*
|
|
17654
|
+
* Glyphs are always rasterized in white so that runtime tinting via
|
|
17655
|
+
* `Mesh.tint` applies the fill color without requiring re-rasterization.
|
|
17656
|
+
*
|
|
17657
|
+
* Use `getDefaultGlyphAtlas()` from `atlas-singleton.ts` rather than
|
|
17658
|
+
* constructing directly.
|
|
17659
|
+
*/
|
|
17660
|
+
class DynamicGlyphAtlas {
|
|
17661
|
+
texture;
|
|
17662
|
+
_canvas;
|
|
17663
|
+
_ctx;
|
|
17664
|
+
_packer;
|
|
17665
|
+
_cache = new Map();
|
|
17666
|
+
_width;
|
|
17667
|
+
_height;
|
|
17668
|
+
constructor(width = 1024, height = 1024) {
|
|
17669
|
+
this._width = width;
|
|
17670
|
+
this._height = height;
|
|
17671
|
+
// Use OffscreenCanvas when available, fall back to HTMLCanvasElement.
|
|
17672
|
+
// In jsdom / Node the global may be absent; createCanvas falls through
|
|
17673
|
+
// to document.createElement which jsdom provides.
|
|
17674
|
+
const canvas = typeof OffscreenCanvas !== 'undefined'
|
|
17675
|
+
? new OffscreenCanvas(width, height)
|
|
17676
|
+
: document.createElement('canvas');
|
|
17677
|
+
if ('width' in canvas) {
|
|
17678
|
+
canvas.width = width;
|
|
17679
|
+
canvas.height = height;
|
|
17680
|
+
}
|
|
17681
|
+
this._canvas = canvas;
|
|
17682
|
+
const ctx = canvas.getContext('2d');
|
|
17683
|
+
if (ctx === null) {
|
|
17684
|
+
throw new Error('DynamicGlyphAtlas: could not obtain a 2D context.');
|
|
17685
|
+
}
|
|
17686
|
+
this._ctx = ctx;
|
|
17687
|
+
this._packer = new ShelfPacker(width, height);
|
|
17688
|
+
this.texture = new Texture(canvas);
|
|
17689
|
+
this.texture.setSize(width, height);
|
|
17690
|
+
}
|
|
17691
|
+
/**
|
|
17692
|
+
* Returns the cached GlyphInfo for the given character + font parameters,
|
|
17693
|
+
* rasterizing it into the atlas if not already present.
|
|
17694
|
+
*/
|
|
17695
|
+
getGlyph(char, family, size, weight, style) {
|
|
17696
|
+
const key = `${char}:${family}:${size}:${weight}:${style}`;
|
|
17697
|
+
const cached = this._cache.get(key);
|
|
17698
|
+
if (cached !== undefined) {
|
|
17699
|
+
return cached;
|
|
17700
|
+
}
|
|
17701
|
+
const info = this._rasterize(char, family, size, weight, style, key);
|
|
17702
|
+
this._cache.set(key, info);
|
|
17703
|
+
// Bump texture version so GPU backends re-upload the canvas data.
|
|
17704
|
+
this.texture.updateSource();
|
|
17705
|
+
return info;
|
|
17706
|
+
}
|
|
17707
|
+
/**
|
|
17708
|
+
* Clears all cached glyphs and resets the atlas packer.
|
|
17709
|
+
* The underlying canvas pixels are also cleared.
|
|
17710
|
+
*/
|
|
17711
|
+
clear() {
|
|
17712
|
+
this._cache.clear();
|
|
17713
|
+
this._packer.reset();
|
|
17714
|
+
this._ctx.clearRect(0, 0, this._width, this._height);
|
|
17715
|
+
this.texture.updateSource();
|
|
17716
|
+
}
|
|
17717
|
+
// -----------------------------------------------------------------------
|
|
17718
|
+
_rasterize(char, family, size, weight, fontStyle, _key) {
|
|
17719
|
+
const ctx = this._ctx;
|
|
17720
|
+
const padding = glyphPadding;
|
|
17721
|
+
ctx.font = `${fontStyle} ${weight} ${size}px ${family}`;
|
|
17722
|
+
ctx.textBaseline = 'alphabetic';
|
|
17723
|
+
ctx.fillStyle = '#ffffff';
|
|
17724
|
+
const metrics = ctx.measureText(char);
|
|
17725
|
+
const ascent = Math.ceil(metrics.fontBoundingBoxAscent
|
|
17726
|
+
?? metrics.actualBoundingBoxAscent
|
|
17727
|
+
?? size * 0.8);
|
|
17728
|
+
const descent = Math.ceil(metrics.fontBoundingBoxDescent
|
|
17729
|
+
?? metrics.actualBoundingBoxDescent
|
|
17730
|
+
?? size * 0.2);
|
|
17731
|
+
const advance = metrics.width;
|
|
17732
|
+
const glyphWidth = Math.max(1, Math.ceil((metrics.actualBoundingBoxLeft ?? 0) + (metrics.actualBoundingBoxRight ?? 0)) || Math.ceil(advance));
|
|
17733
|
+
const glyphHeight = Math.max(1, ascent + descent);
|
|
17734
|
+
const slotW = glyphWidth + padding * 2;
|
|
17735
|
+
const slotH = glyphHeight + padding * 2;
|
|
17736
|
+
const slot = this._packer.insert(slotW, slotH);
|
|
17737
|
+
// Draw the glyph white into the atlas slot
|
|
17738
|
+
ctx.fillText(char, slot.x + padding + (metrics.actualBoundingBoxLeft ?? 0), slot.y + padding + ascent);
|
|
17739
|
+
const info = {
|
|
17740
|
+
x: slot.x,
|
|
17741
|
+
y: slot.y,
|
|
17742
|
+
width: glyphWidth,
|
|
17743
|
+
height: glyphHeight,
|
|
17744
|
+
advance,
|
|
17745
|
+
ascent,
|
|
17746
|
+
uvLeft: slot.x / this._width,
|
|
17747
|
+
uvTop: slot.y / this._height,
|
|
17748
|
+
uvRight: (slot.x + slotW) / this._width,
|
|
17749
|
+
uvBottom: (slot.y + slotH) / this._height,
|
|
17750
|
+
};
|
|
17751
|
+
return info;
|
|
17752
|
+
}
|
|
17753
|
+
}
|
|
17754
|
+
|
|
17755
|
+
let _defaultAtlas = null;
|
|
17756
|
+
/**
|
|
17757
|
+
* Returns the shared process-wide glyph atlas, creating it lazily on first
|
|
17758
|
+
* call. All `Text` instances share this atlas so that identical glyph shapes
|
|
17759
|
+
* (same char + family + size + weight + style) are rasterized only once.
|
|
17760
|
+
*/
|
|
17761
|
+
function getDefaultGlyphAtlas() {
|
|
17762
|
+
if (_defaultAtlas === null) {
|
|
17763
|
+
_defaultAtlas = new DynamicGlyphAtlas();
|
|
17764
|
+
}
|
|
17765
|
+
return _defaultAtlas;
|
|
17766
|
+
}
|
|
17767
|
+
|
|
17768
|
+
/**
|
|
17769
|
+
* Computes per-glyph quad placements for the given text and style.
|
|
17770
|
+
*
|
|
17771
|
+
* Handles `\n` line breaks and left/center/right alignment. No word-wrap,
|
|
17772
|
+
* no RTL, no ligature shaping — Unicode/diacritics are delegated to the
|
|
17773
|
+
* browser's font engine via canvas `fillText`.
|
|
17774
|
+
*
|
|
17775
|
+
* Returns an empty array for empty text.
|
|
17776
|
+
*/
|
|
17777
|
+
function layoutText(text, style, atlas) {
|
|
17778
|
+
if (text.length === 0) {
|
|
17779
|
+
return [];
|
|
17780
|
+
}
|
|
17781
|
+
const { fontSize, fontFamily, fontWeight, fontStyle, lineHeight, align } = style;
|
|
17782
|
+
const computedLineHeight = fontSize * lineHeight;
|
|
17783
|
+
const lines = text.split('\n');
|
|
17784
|
+
// Pass 1: gather glyph info per line, track line widths
|
|
17785
|
+
const linePlacements = [];
|
|
17786
|
+
let maxLineWidth = 0;
|
|
17787
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
17788
|
+
const line = lines[lineIndex];
|
|
17789
|
+
const y = lineIndex * computedLineHeight;
|
|
17790
|
+
let cursorX = 0;
|
|
17791
|
+
const placements = [];
|
|
17792
|
+
for (const char of line) {
|
|
17793
|
+
const info = atlas.getGlyph(char, fontFamily, fontSize, fontWeight, fontStyle);
|
|
17794
|
+
placements.push({ info, x: cursorX, y });
|
|
17795
|
+
cursorX += info.advance;
|
|
17796
|
+
}
|
|
17797
|
+
const lineWidth = cursorX;
|
|
17798
|
+
if (lineWidth > maxLineWidth) {
|
|
17799
|
+
maxLineWidth = lineWidth;
|
|
17800
|
+
}
|
|
17801
|
+
linePlacements.push({ placements, width: lineWidth });
|
|
17802
|
+
}
|
|
17803
|
+
// Pass 2: apply alignment and build final GlyphPlacement array
|
|
17804
|
+
const result = [];
|
|
17805
|
+
for (const line of linePlacements) {
|
|
17806
|
+
let offsetX = 0;
|
|
17807
|
+
if (align === 'right') {
|
|
17808
|
+
offsetX = maxLineWidth - line.width;
|
|
17809
|
+
}
|
|
17810
|
+
else if (align === 'center') {
|
|
17811
|
+
offsetX = (maxLineWidth - line.width) / 2;
|
|
17812
|
+
}
|
|
17813
|
+
for (const { info, x, y } of line.placements) {
|
|
17814
|
+
result.push({
|
|
17815
|
+
x: x + offsetX,
|
|
17816
|
+
y,
|
|
17817
|
+
width: info.width,
|
|
17818
|
+
height: info.height,
|
|
17819
|
+
uvLeft: info.uvLeft,
|
|
17820
|
+
uvTop: info.uvTop,
|
|
17821
|
+
uvRight: info.uvRight,
|
|
17822
|
+
uvBottom: info.uvBottom,
|
|
17823
|
+
});
|
|
17824
|
+
}
|
|
17825
|
+
}
|
|
17826
|
+
return result;
|
|
17827
|
+
}
|
|
17828
|
+
|
|
17632
17829
|
class TextStyle {
|
|
17633
17830
|
_align;
|
|
17634
17831
|
_fill;
|
|
17832
|
+
_fillColor;
|
|
17833
|
+
_lineHeight;
|
|
17635
17834
|
_stroke;
|
|
17636
17835
|
_strokeThickness;
|
|
17637
17836
|
_fontSize;
|
|
17638
17837
|
_fontWeight;
|
|
17639
17838
|
_fontFamily;
|
|
17839
|
+
_fontStyle;
|
|
17640
17840
|
_wordWrap;
|
|
17641
17841
|
_wordWrapWidth;
|
|
17642
17842
|
_baseline;
|
|
@@ -17647,11 +17847,14 @@ class TextStyle {
|
|
|
17647
17847
|
constructor(options = {}) {
|
|
17648
17848
|
this._align = options.align ?? 'left';
|
|
17649
17849
|
this._fill = options.fill ?? 'black';
|
|
17850
|
+
this._fillColor = options.fillColor ?? Color.white.clone();
|
|
17851
|
+
this._lineHeight = options.lineHeight ?? 1.2;
|
|
17650
17852
|
this._stroke = options.stroke ?? 'black';
|
|
17651
17853
|
this._strokeThickness = options.strokeThickness ?? 1;
|
|
17652
17854
|
this._fontSize = options.fontSize ?? 20;
|
|
17653
17855
|
this._fontWeight = options.fontWeight ?? 'bold';
|
|
17654
17856
|
this._fontFamily = options.fontFamily ?? 'Arial';
|
|
17857
|
+
this._fontStyle = options.fontStyle ?? 'normal';
|
|
17655
17858
|
this._wordWrap = options.wordWrap ?? false;
|
|
17656
17859
|
this._wordWrapWidth = options.wordWrapWidth ?? 100;
|
|
17657
17860
|
this._baseline = options.baseline ?? 'alphabetic';
|
|
@@ -17677,6 +17880,31 @@ class TextStyle {
|
|
|
17677
17880
|
this._dirty = true;
|
|
17678
17881
|
}
|
|
17679
17882
|
}
|
|
17883
|
+
/**
|
|
17884
|
+
* Runtime fill color applied via `Mesh.tint`. Glyphs are always
|
|
17885
|
+
* rasterized white; this color multiplies them at draw time so that
|
|
17886
|
+
* changing the color never requires re-rasterizing cached glyphs.
|
|
17887
|
+
*/
|
|
17888
|
+
get fillColor() {
|
|
17889
|
+
return this._fillColor;
|
|
17890
|
+
}
|
|
17891
|
+
set fillColor(color) {
|
|
17892
|
+
this._fillColor = color;
|
|
17893
|
+
this._dirty = true;
|
|
17894
|
+
}
|
|
17895
|
+
/**
|
|
17896
|
+
* Line-height multiplier applied to `fontSize` when computing vertical
|
|
17897
|
+
* spacing between lines. Defaults to 1.2.
|
|
17898
|
+
*/
|
|
17899
|
+
get lineHeight() {
|
|
17900
|
+
return this._lineHeight;
|
|
17901
|
+
}
|
|
17902
|
+
set lineHeight(lineHeight) {
|
|
17903
|
+
if (this._lineHeight !== lineHeight) {
|
|
17904
|
+
this._lineHeight = lineHeight;
|
|
17905
|
+
this._dirty = true;
|
|
17906
|
+
}
|
|
17907
|
+
}
|
|
17680
17908
|
get stroke() {
|
|
17681
17909
|
return this._stroke;
|
|
17682
17910
|
}
|
|
@@ -17722,6 +17950,15 @@ class TextStyle {
|
|
|
17722
17950
|
this._dirty = true;
|
|
17723
17951
|
}
|
|
17724
17952
|
}
|
|
17953
|
+
get fontStyle() {
|
|
17954
|
+
return this._fontStyle;
|
|
17955
|
+
}
|
|
17956
|
+
set fontStyle(fontStyle) {
|
|
17957
|
+
if (this._fontStyle !== fontStyle) {
|
|
17958
|
+
this._fontStyle = fontStyle;
|
|
17959
|
+
this._dirty = true;
|
|
17960
|
+
}
|
|
17961
|
+
}
|
|
17725
17962
|
get wordWrap() {
|
|
17726
17963
|
return this._wordWrap;
|
|
17727
17964
|
}
|
|
@@ -17799,11 +18036,14 @@ class TextStyle {
|
|
|
17799
18036
|
if (style !== this) {
|
|
17800
18037
|
this.align = style.align;
|
|
17801
18038
|
this.fill = style.fill;
|
|
18039
|
+
this._fillColor = style.fillColor.clone();
|
|
18040
|
+
this.lineHeight = style.lineHeight;
|
|
17802
18041
|
this.stroke = style.stroke;
|
|
17803
18042
|
this.strokeThickness = style.strokeThickness;
|
|
17804
18043
|
this.fontSize = style.fontSize;
|
|
17805
18044
|
this.fontWeight = style.fontWeight;
|
|
17806
18045
|
this.fontFamily = style.fontFamily;
|
|
18046
|
+
this.fontStyle = style.fontStyle;
|
|
17807
18047
|
this.wordWrap = style.wordWrap;
|
|
17808
18048
|
this.wordWrapWidth = style.wordWrapWidth;
|
|
17809
18049
|
this.baseline = style.baseline;
|
|
@@ -17819,26 +18059,81 @@ class TextStyle {
|
|
|
17819
18059
|
}
|
|
17820
18060
|
}
|
|
17821
18061
|
|
|
17822
|
-
|
|
17823
|
-
|
|
18062
|
+
function buildMesh(placements, style) {
|
|
18063
|
+
const n = placements.length;
|
|
18064
|
+
const vertices = new Float32Array(n * 4 * 2);
|
|
18065
|
+
const uvs = new Float32Array(n * 4 * 2);
|
|
18066
|
+
const indices = new Uint16Array(n * 6);
|
|
18067
|
+
for (let i = 0; i < n; i++) {
|
|
18068
|
+
const p = placements[i];
|
|
18069
|
+
const v = i * 8;
|
|
18070
|
+
const u = i * 8;
|
|
18071
|
+
const idx = i * 6;
|
|
18072
|
+
const baseV = i * 4;
|
|
18073
|
+
// Vertices: TL, TR, BR, BL
|
|
18074
|
+
vertices[v + 0] = p.x;
|
|
18075
|
+
vertices[v + 1] = p.y;
|
|
18076
|
+
vertices[v + 2] = p.x + p.width;
|
|
18077
|
+
vertices[v + 3] = p.y;
|
|
18078
|
+
vertices[v + 4] = p.x + p.width;
|
|
18079
|
+
vertices[v + 5] = p.y + p.height;
|
|
18080
|
+
vertices[v + 6] = p.x;
|
|
18081
|
+
vertices[v + 7] = p.y + p.height;
|
|
18082
|
+
// UVs: TL, TR, BR, BL
|
|
18083
|
+
uvs[u + 0] = p.uvLeft;
|
|
18084
|
+
uvs[u + 1] = p.uvTop;
|
|
18085
|
+
uvs[u + 2] = p.uvRight;
|
|
18086
|
+
uvs[u + 3] = p.uvTop;
|
|
18087
|
+
uvs[u + 4] = p.uvRight;
|
|
18088
|
+
uvs[u + 5] = p.uvBottom;
|
|
18089
|
+
uvs[u + 6] = p.uvLeft;
|
|
18090
|
+
uvs[u + 7] = p.uvBottom;
|
|
18091
|
+
// Indices: [TL, TR, BR, TL, BR, BL]
|
|
18092
|
+
indices[idx + 0] = baseV + 0;
|
|
18093
|
+
indices[idx + 1] = baseV + 1;
|
|
18094
|
+
indices[idx + 2] = baseV + 2;
|
|
18095
|
+
indices[idx + 3] = baseV + 0;
|
|
18096
|
+
indices[idx + 4] = baseV + 2;
|
|
18097
|
+
indices[idx + 5] = baseV + 3;
|
|
18098
|
+
}
|
|
18099
|
+
const atlas = getDefaultGlyphAtlas();
|
|
18100
|
+
const mesh = new Mesh({
|
|
18101
|
+
vertices,
|
|
18102
|
+
uvs,
|
|
18103
|
+
indices,
|
|
18104
|
+
texture: atlas.texture,
|
|
18105
|
+
});
|
|
18106
|
+
mesh.tint = style.fillColor;
|
|
18107
|
+
return mesh;
|
|
18108
|
+
}
|
|
18109
|
+
/**
|
|
18110
|
+
* GPU-accelerated text node that rasterizes individual glyphs into a shared
|
|
18111
|
+
* atlas ({@link DynamicGlyphAtlas}) and renders them as a single quad-per-
|
|
18112
|
+
* glyph {@link Mesh} (one draw call per Text instance).
|
|
18113
|
+
*
|
|
18114
|
+
* Glyphs are always rasterized in white and tinted at runtime via
|
|
18115
|
+
* `Mesh.tint`; changing `style.fillColor` only updates the mesh tint —
|
|
18116
|
+
* no atlas re-rasterization is needed.
|
|
18117
|
+
*
|
|
18118
|
+
* The internal {@link Mesh} is the sole child of this {@link Container}.
|
|
18119
|
+
* All transform properties (position, rotation, scale, origin) are
|
|
18120
|
+
* inherited from {@link Container} → {@link RenderNode}.
|
|
18121
|
+
*/
|
|
18122
|
+
class Text extends Container {
|
|
17824
18123
|
_text;
|
|
17825
18124
|
_style;
|
|
17826
|
-
|
|
17827
|
-
|
|
17828
|
-
|
|
17829
|
-
constructor(text, style, samplerOptions, canvas = document.createElement('canvas')) {
|
|
17830
|
-
super(new Texture(canvas, samplerOptions));
|
|
18125
|
+
_mesh = null;
|
|
18126
|
+
constructor(text, style) {
|
|
18127
|
+
super();
|
|
17831
18128
|
this._text = text;
|
|
17832
18129
|
this._style = (style && style instanceof TextStyle) ? style : new TextStyle(style);
|
|
17833
|
-
this.
|
|
17834
|
-
this._context = canvas.getContext('2d');
|
|
17835
|
-
this.updateTexture();
|
|
18130
|
+
this._rebuild();
|
|
17836
18131
|
}
|
|
17837
18132
|
get text() {
|
|
17838
18133
|
return this._text;
|
|
17839
18134
|
}
|
|
17840
|
-
set text(
|
|
17841
|
-
this.setText(
|
|
18135
|
+
set text(value) {
|
|
18136
|
+
this.setText(value);
|
|
17842
18137
|
}
|
|
17843
18138
|
get style() {
|
|
17844
18139
|
return this._style;
|
|
@@ -17846,98 +18141,43 @@ class Text extends Sprite {
|
|
|
17846
18141
|
set style(style) {
|
|
17847
18142
|
this.setStyle(style);
|
|
17848
18143
|
}
|
|
17849
|
-
get canvas() {
|
|
17850
|
-
return this._canvas;
|
|
17851
|
-
}
|
|
17852
|
-
set canvas(canvas) {
|
|
17853
|
-
this.setCanvas(canvas);
|
|
17854
|
-
}
|
|
17855
18144
|
setText(text) {
|
|
17856
18145
|
if (this._text !== text) {
|
|
17857
18146
|
this._text = text;
|
|
17858
|
-
this.
|
|
18147
|
+
this._rebuild();
|
|
17859
18148
|
}
|
|
17860
18149
|
return this;
|
|
17861
18150
|
}
|
|
17862
18151
|
setStyle(style) {
|
|
17863
18152
|
this._style = (style instanceof TextStyle) ? style : new TextStyle(style);
|
|
17864
|
-
this.
|
|
17865
|
-
return this;
|
|
17866
|
-
}
|
|
17867
|
-
setCanvas(canvas) {
|
|
17868
|
-
if (this._canvas !== canvas) {
|
|
17869
|
-
this._canvas = canvas;
|
|
17870
|
-
this._context = this._getContext(canvas);
|
|
17871
|
-
this._dirty = true;
|
|
17872
|
-
this.texture.setSource.call(this.texture, canvas);
|
|
17873
|
-
this.setTextureFrame(Rectangle.temp.set(0, 0, canvas.width, canvas.height));
|
|
17874
|
-
}
|
|
18153
|
+
this._rebuild();
|
|
17875
18154
|
return this;
|
|
17876
18155
|
}
|
|
17877
|
-
|
|
17878
|
-
if (this.
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
canvas.width = canvasWidth;
|
|
17882
|
-
canvas.height = canvasHeight;
|
|
17883
|
-
this.setTextureFrame(Rectangle.temp.set(0, 0, canvasWidth, canvasHeight));
|
|
17884
|
-
}
|
|
17885
|
-
else {
|
|
17886
|
-
context.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
17887
|
-
}
|
|
17888
|
-
style.apply(context);
|
|
17889
|
-
for (let i = 0; i < lines.length; i++) {
|
|
17890
|
-
const metrics = lineMetrics[i], lineWidth = (maxLineWidth - metrics.width), offset = (style.align === 'right') ? lineWidth : lineWidth / 2, padding = style.padding + (style.strokeThickness / 2), lineX = metrics.actualBoundingBoxLeft + (style.align === 'left' ? 0 : offset) + padding, lineY = metrics.actualBoundingBoxAscent + (lineHeight * i) + padding;
|
|
17891
|
-
if (style.stroke && style.strokeThickness) {
|
|
17892
|
-
context.strokeText(lines[i], lineX, lineY);
|
|
17893
|
-
}
|
|
17894
|
-
if (style.fill) {
|
|
17895
|
-
context.fillText(lines[i], lineX, lineY);
|
|
17896
|
-
}
|
|
17897
|
-
}
|
|
17898
|
-
this.texture.updateSource();
|
|
17899
|
-
this._dirty = false;
|
|
17900
|
-
this._style.dirty = false;
|
|
17901
|
-
}
|
|
17902
|
-
return this;
|
|
17903
|
-
}
|
|
17904
|
-
getWordWrappedText() {
|
|
17905
|
-
const context = this._context, wrapWidth = this._style.wordWrapWidth, lines = this._text.split('\n'), spaceWidth = context.measureText(' ').width;
|
|
17906
|
-
let spaceLeft = wrapWidth, result = '';
|
|
17907
|
-
for (let y = 0; y < lines.length; y++) {
|
|
17908
|
-
const words = lines[y].split(' ');
|
|
17909
|
-
if (y > 0) {
|
|
17910
|
-
result += '\n';
|
|
17911
|
-
}
|
|
17912
|
-
for (let x = 0; x < words.length; x++) {
|
|
17913
|
-
const word = words[x], wordWidth = context.measureText(word).width, pairWidth = wordWidth + spaceWidth;
|
|
17914
|
-
if (pairWidth > spaceLeft) {
|
|
17915
|
-
if (x > 0) {
|
|
17916
|
-
result += '\n';
|
|
17917
|
-
}
|
|
17918
|
-
spaceLeft = wrapWidth;
|
|
17919
|
-
}
|
|
17920
|
-
else {
|
|
17921
|
-
spaceLeft -= pairWidth;
|
|
17922
|
-
}
|
|
17923
|
-
result += `${word} `;
|
|
17924
|
-
}
|
|
18156
|
+
destroy() {
|
|
18157
|
+
if (this._mesh !== null) {
|
|
18158
|
+
this._mesh.destroy();
|
|
18159
|
+
this._mesh = null;
|
|
17925
18160
|
}
|
|
17926
|
-
|
|
18161
|
+
super.destroy();
|
|
17927
18162
|
}
|
|
17928
|
-
|
|
17929
|
-
|
|
17930
|
-
|
|
17931
|
-
|
|
18163
|
+
// -----------------------------------------------------------------------
|
|
18164
|
+
_rebuild() {
|
|
18165
|
+
// Remove and discard the old mesh (if any).
|
|
18166
|
+
if (this._mesh !== null) {
|
|
18167
|
+
this.removeChild(this._mesh);
|
|
18168
|
+
this._mesh.destroy();
|
|
18169
|
+
this._mesh = null;
|
|
18170
|
+
}
|
|
18171
|
+
if (this._text.length === 0) {
|
|
18172
|
+
return;
|
|
17932
18173
|
}
|
|
17933
|
-
|
|
17934
|
-
|
|
17935
|
-
|
|
17936
|
-
|
|
17937
|
-
if (context === null) {
|
|
17938
|
-
throw new Error('Could not create a 2D canvas context.');
|
|
18174
|
+
const atlas = getDefaultGlyphAtlas();
|
|
18175
|
+
const placements = layoutText(this._text, this._style, atlas);
|
|
18176
|
+
if (placements.length === 0) {
|
|
18177
|
+
return;
|
|
17939
18178
|
}
|
|
17940
|
-
|
|
18179
|
+
this._mesh = buildMesh(placements, this._style);
|
|
18180
|
+
this.addChild(this._mesh);
|
|
17941
18181
|
}
|
|
17942
18182
|
}
|
|
17943
18183
|
|
|
@@ -18519,474 +18759,5 @@ class IndexedDbStore {
|
|
|
18519
18759
|
}
|
|
18520
18760
|
}
|
|
18521
18761
|
|
|
18522
|
-
|
|
18523
|
-
const maxCollisionGroup = 15;
|
|
18524
|
-
const fullGroupMask = 0xFFFF;
|
|
18525
|
-
const defaultDeltaSeconds = 1 / 60;
|
|
18526
|
-
const defaultDebugDrawOptions = {
|
|
18527
|
-
lineWidth: 1,
|
|
18528
|
-
solidLineColor: new Color(64, 196, 255, 1),
|
|
18529
|
-
solidFillColor: new Color(64, 196, 255, 0.12),
|
|
18530
|
-
triggerLineColor: new Color(255, 180, 48, 1),
|
|
18531
|
-
triggerFillColor: new Color(255, 180, 48, 0.08),
|
|
18532
|
-
};
|
|
18533
|
-
const resolveRapierModule = (module) => {
|
|
18534
|
-
const rapier = module;
|
|
18535
|
-
const vector2 = rapier.Vector2;
|
|
18536
|
-
const worldConstructor = rapier.World;
|
|
18537
|
-
const eventQueueConstructor = rapier.EventQueue;
|
|
18538
|
-
const rigidBodyDescFactory = rapier.RigidBodyDesc;
|
|
18539
|
-
const colliderDescFactory = rapier.ColliderDesc;
|
|
18540
|
-
if (typeof vector2 !== 'function' || typeof worldConstructor !== 'function' || typeof eventQueueConstructor !== 'function') {
|
|
18541
|
-
throw new Error('Invalid Rapier module loader result. Expected Vector2, World, and EventQueue exports.');
|
|
18542
|
-
}
|
|
18543
|
-
if (typeof rigidBodyDescFactory !== 'object'
|
|
18544
|
-
|| rigidBodyDescFactory === null
|
|
18545
|
-
|| typeof colliderDescFactory !== 'object'
|
|
18546
|
-
|| colliderDescFactory === null) {
|
|
18547
|
-
throw new Error('Invalid Rapier module loader result. Expected RigidBodyDesc and ColliderDesc exports.');
|
|
18548
|
-
}
|
|
18549
|
-
const activeEvents = rapier.ActiveEvents;
|
|
18550
|
-
const activeCollisionEvents = typeof activeEvents?.COLLISION_EVENTS === 'number'
|
|
18551
|
-
? activeEvents.COLLISION_EVENTS
|
|
18552
|
-
: undefined;
|
|
18553
|
-
return {
|
|
18554
|
-
init: typeof rapier.init === 'function'
|
|
18555
|
-
? rapier.init
|
|
18556
|
-
: undefined,
|
|
18557
|
-
vector2: vector2,
|
|
18558
|
-
worldConstructor: worldConstructor,
|
|
18559
|
-
eventQueueConstructor: eventQueueConstructor,
|
|
18560
|
-
rigidBodyDescFactory: rigidBodyDescFactory,
|
|
18561
|
-
colliderDescFactory: colliderDescFactory,
|
|
18562
|
-
activeCollisionEvents,
|
|
18563
|
-
};
|
|
18564
|
-
};
|
|
18565
|
-
const defaultRapierModuleLoader = async () => {
|
|
18566
|
-
return await import(rapierModuleName);
|
|
18567
|
-
};
|
|
18568
|
-
const assertFiniteNumber = (value, label) => {
|
|
18569
|
-
if (!Number.isFinite(value)) {
|
|
18570
|
-
throw new Error(`${label} must be a finite number.`);
|
|
18571
|
-
}
|
|
18572
|
-
};
|
|
18573
|
-
const assertPositiveNumber = (value, label) => {
|
|
18574
|
-
assertFiniteNumber(value, label);
|
|
18575
|
-
if (value <= 0) {
|
|
18576
|
-
throw new Error(`${label} must be greater than zero.`);
|
|
18577
|
-
}
|
|
18578
|
-
};
|
|
18579
|
-
const assertGroup = (group, label) => {
|
|
18580
|
-
if (!Number.isInteger(group) || group < 0 || group > maxCollisionGroup) {
|
|
18581
|
-
throw new Error(`${label} must be an integer between 0 and ${maxCollisionGroup}.`);
|
|
18582
|
-
}
|
|
18583
|
-
};
|
|
18584
|
-
const toGroupMask = (groups, fallback) => {
|
|
18585
|
-
if (groups === undefined) {
|
|
18586
|
-
return fallback;
|
|
18587
|
-
}
|
|
18588
|
-
if (typeof groups === 'number') {
|
|
18589
|
-
assertGroup(groups, 'collision group');
|
|
18590
|
-
return 1 << groups;
|
|
18591
|
-
}
|
|
18592
|
-
if (groups.length === 0) {
|
|
18593
|
-
return 0;
|
|
18594
|
-
}
|
|
18595
|
-
return groups.reduce((mask, group, index) => {
|
|
18596
|
-
assertGroup(group, `collision groups[${index}]`);
|
|
18597
|
-
return mask | (1 << group);
|
|
18598
|
-
}, 0);
|
|
18599
|
-
};
|
|
18600
|
-
const toPackedCollisionGroups = (filter) => {
|
|
18601
|
-
const membershipMask = toGroupMask(filter?.membership, 1 << 0);
|
|
18602
|
-
const collisionMask = toGroupMask(filter?.collidesWith, fullGroupMask);
|
|
18603
|
-
return ((membershipMask & fullGroupMask) << 16) | (collisionMask & fullGroupMask);
|
|
18604
|
-
};
|
|
18605
|
-
const getOffset = (shape) => ({
|
|
18606
|
-
x: shape.offsetX ?? 0,
|
|
18607
|
-
y: shape.offsetY ?? 0,
|
|
18608
|
-
});
|
|
18609
|
-
const rotatePoint = (x, y, rotation) => {
|
|
18610
|
-
const cos = Math.cos(rotation);
|
|
18611
|
-
const sin = Math.sin(rotation);
|
|
18612
|
-
return {
|
|
18613
|
-
x: (x * cos) - (y * sin),
|
|
18614
|
-
y: (x * sin) + (y * cos),
|
|
18615
|
-
};
|
|
18616
|
-
};
|
|
18617
|
-
const assertShape = (shape) => {
|
|
18618
|
-
if (shape.type === 'box') {
|
|
18619
|
-
assertPositiveNumber(shape.width, 'Box width');
|
|
18620
|
-
assertPositiveNumber(shape.height, 'Box height');
|
|
18621
|
-
assertFiniteNumber(shape.offsetX ?? 0, 'Box offsetX');
|
|
18622
|
-
assertFiniteNumber(shape.offsetY ?? 0, 'Box offsetY');
|
|
18623
|
-
assertFiniteNumber(shape.offsetRotation ?? 0, 'Box offsetRotation');
|
|
18624
|
-
return;
|
|
18625
|
-
}
|
|
18626
|
-
assertPositiveNumber(shape.radius, 'Circle radius');
|
|
18627
|
-
assertFiniteNumber(shape.offsetX ?? 0, 'Circle offsetX');
|
|
18628
|
-
assertFiniteNumber(shape.offsetY ?? 0, 'Circle offsetY');
|
|
18629
|
-
};
|
|
18630
|
-
const assertBodyOptions = (options) => {
|
|
18631
|
-
assertShape(options.shape);
|
|
18632
|
-
assertFiniteNumber(options.friction ?? 0, 'friction');
|
|
18633
|
-
assertFiniteNumber(options.restitution ?? 0, 'restitution');
|
|
18634
|
-
assertFiniteNumber(options.density ?? 0, 'density');
|
|
18635
|
-
assertFiniteNumber(options.gravityScale ?? 1, 'gravityScale');
|
|
18636
|
-
assertFiniteNumber(options.linearDamping ?? 0, 'linearDamping');
|
|
18637
|
-
assertFiniteNumber(options.angularDamping ?? 0, 'angularDamping');
|
|
18638
|
-
toPackedCollisionGroups(options.collisionFilter);
|
|
18639
|
-
};
|
|
18640
|
-
class RapierPhysicsBinding {
|
|
18641
|
-
_world;
|
|
18642
|
-
_body;
|
|
18643
|
-
_collider;
|
|
18644
|
-
_syncMode;
|
|
18645
|
-
node;
|
|
18646
|
-
bodyType;
|
|
18647
|
-
shape;
|
|
18648
|
-
trigger;
|
|
18649
|
-
constructor(world, node, body, collider, options) {
|
|
18650
|
-
this._world = world;
|
|
18651
|
-
this.node = node;
|
|
18652
|
-
this._body = body;
|
|
18653
|
-
this._collider = collider;
|
|
18654
|
-
this.bodyType = options.type ?? 'dynamic';
|
|
18655
|
-
this.shape = options.shape;
|
|
18656
|
-
this.trigger = options.trigger ?? false;
|
|
18657
|
-
this._syncMode = options.syncMode ?? 'physicsToNode';
|
|
18658
|
-
}
|
|
18659
|
-
get bodyHandle() {
|
|
18660
|
-
return this._body.handle;
|
|
18661
|
-
}
|
|
18662
|
-
get colliderHandle() {
|
|
18663
|
-
return this._collider.handle;
|
|
18664
|
-
}
|
|
18665
|
-
getBody() {
|
|
18666
|
-
return this._body;
|
|
18667
|
-
}
|
|
18668
|
-
get syncMode() {
|
|
18669
|
-
return this._syncMode;
|
|
18670
|
-
}
|
|
18671
|
-
set syncMode(syncMode) {
|
|
18672
|
-
this._syncMode = syncMode;
|
|
18673
|
-
}
|
|
18674
|
-
get x() {
|
|
18675
|
-
return this._body.translation().x;
|
|
18676
|
-
}
|
|
18677
|
-
get y() {
|
|
18678
|
-
return this._body.translation().y;
|
|
18679
|
-
}
|
|
18680
|
-
get rotation() {
|
|
18681
|
-
return this._body.rotation();
|
|
18682
|
-
}
|
|
18683
|
-
setSyncMode(syncMode) {
|
|
18684
|
-
this._syncMode = syncMode;
|
|
18685
|
-
return this;
|
|
18686
|
-
}
|
|
18687
|
-
teleport(x, y, rotation = this.node.rotation) {
|
|
18688
|
-
this._world.writeBodyTransform(this._body, x, y, rotation, true);
|
|
18689
|
-
this.node.setPosition(x, y);
|
|
18690
|
-
this.node.setRotation(rotation);
|
|
18691
|
-
return this;
|
|
18692
|
-
}
|
|
18693
|
-
syncBodyFromNode(wakeUp = true) {
|
|
18694
|
-
this._world.writeBodyTransform(this._body, this.node.x, this.node.y, this.node.rotation, wakeUp);
|
|
18695
|
-
return this;
|
|
18696
|
-
}
|
|
18697
|
-
syncNodeFromBody() {
|
|
18698
|
-
const translation = this._body.translation();
|
|
18699
|
-
this.node.setPosition(translation.x, translation.y);
|
|
18700
|
-
this.node.setRotation(this._body.rotation());
|
|
18701
|
-
return this;
|
|
18702
|
-
}
|
|
18703
|
-
setCollisionFilter(filter) {
|
|
18704
|
-
const groups = toPackedCollisionGroups(filter);
|
|
18705
|
-
this._collider.setCollisionGroups(groups);
|
|
18706
|
-
this._collider.setSolverGroups(groups);
|
|
18707
|
-
return this;
|
|
18708
|
-
}
|
|
18709
|
-
destroy() {
|
|
18710
|
-
this._world.removeBinding(this);
|
|
18711
|
-
}
|
|
18712
|
-
}
|
|
18713
|
-
class RapierPhysicsWorld {
|
|
18714
|
-
onCollisionEnter = new Signal();
|
|
18715
|
-
onCollisionExit = new Signal();
|
|
18716
|
-
onTriggerEnter = new Signal();
|
|
18717
|
-
onTriggerExit = new Signal();
|
|
18718
|
-
_rapier;
|
|
18719
|
-
_world;
|
|
18720
|
-
_eventQueue;
|
|
18721
|
-
_bindings = new Set();
|
|
18722
|
-
_nodeBindings = new Map();
|
|
18723
|
-
_colliderBindings = new Map();
|
|
18724
|
-
constructor(rapier, world, eventQueue) {
|
|
18725
|
-
this._rapier = rapier;
|
|
18726
|
-
this._world = world;
|
|
18727
|
-
this._eventQueue = eventQueue;
|
|
18728
|
-
}
|
|
18729
|
-
static async create(options = {}) {
|
|
18730
|
-
const moduleLoader = options.moduleLoader ?? defaultRapierModuleLoader;
|
|
18731
|
-
let loadedModule = null;
|
|
18732
|
-
try {
|
|
18733
|
-
loadedModule = await moduleLoader();
|
|
18734
|
-
}
|
|
18735
|
-
catch (error) {
|
|
18736
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
18737
|
-
throw new Error(`Rapier physics module is unavailable. Install "${rapierModuleName}" or provide a custom module loader. Original error: ${message}`, { cause: error });
|
|
18738
|
-
}
|
|
18739
|
-
const rapier = resolveRapierModule(loadedModule);
|
|
18740
|
-
if (typeof rapier.init === 'function') {
|
|
18741
|
-
await rapier.init();
|
|
18742
|
-
}
|
|
18743
|
-
const gravityX = options.gravityX ?? 0;
|
|
18744
|
-
const gravityY = options.gravityY ?? 9.81;
|
|
18745
|
-
assertFiniteNumber(gravityX, 'gravityX');
|
|
18746
|
-
assertFiniteNumber(gravityY, 'gravityY');
|
|
18747
|
-
const world = new rapier.worldConstructor(new rapier.vector2(gravityX, gravityY));
|
|
18748
|
-
const eventQueue = new rapier.eventQueueConstructor(true);
|
|
18749
|
-
return new RapierPhysicsWorld(rapier, world, eventQueue);
|
|
18750
|
-
}
|
|
18751
|
-
get gravity() {
|
|
18752
|
-
return {
|
|
18753
|
-
x: this._world.gravity.x,
|
|
18754
|
-
y: this._world.gravity.y,
|
|
18755
|
-
};
|
|
18756
|
-
}
|
|
18757
|
-
setGravity(x, y) {
|
|
18758
|
-
assertFiniteNumber(x, 'gravityX');
|
|
18759
|
-
assertFiniteNumber(y, 'gravityY');
|
|
18760
|
-
this._world.gravity.x = x;
|
|
18761
|
-
this._world.gravity.y = y;
|
|
18762
|
-
return this;
|
|
18763
|
-
}
|
|
18764
|
-
hasNode(node) {
|
|
18765
|
-
return this._nodeBindings.has(node);
|
|
18766
|
-
}
|
|
18767
|
-
getBinding(node) {
|
|
18768
|
-
return this._nodeBindings.get(node) ?? null;
|
|
18769
|
-
}
|
|
18770
|
-
attachNode(node, options) {
|
|
18771
|
-
if (this._nodeBindings.has(node)) {
|
|
18772
|
-
throw new Error('This SceneNode is already attached to a physics body.');
|
|
18773
|
-
}
|
|
18774
|
-
assertBodyOptions(options);
|
|
18775
|
-
const bodyDescriptor = this.createBodyDescriptor(node, options);
|
|
18776
|
-
const body = this._world.createRigidBody(bodyDescriptor);
|
|
18777
|
-
const colliderDescriptor = this.createColliderDescriptor(options);
|
|
18778
|
-
const collider = this._world.createCollider(colliderDescriptor, body);
|
|
18779
|
-
const binding = new RapierPhysicsBinding(this, node, body, collider, options);
|
|
18780
|
-
this._bindings.add(binding);
|
|
18781
|
-
this._nodeBindings.set(node, binding);
|
|
18782
|
-
this._colliderBindings.set(collider.handle, binding);
|
|
18783
|
-
return binding;
|
|
18784
|
-
}
|
|
18785
|
-
detachNode(node) {
|
|
18786
|
-
const binding = this._nodeBindings.get(node);
|
|
18787
|
-
if (binding) {
|
|
18788
|
-
this.removeBinding(binding);
|
|
18789
|
-
}
|
|
18790
|
-
return this;
|
|
18791
|
-
}
|
|
18792
|
-
step(deltaSeconds = defaultDeltaSeconds) {
|
|
18793
|
-
assertFiniteNumber(deltaSeconds, 'deltaSeconds');
|
|
18794
|
-
if (deltaSeconds < 0) {
|
|
18795
|
-
throw new Error('deltaSeconds must be zero or greater.');
|
|
18796
|
-
}
|
|
18797
|
-
this.applyStepDelta(deltaSeconds);
|
|
18798
|
-
this._world.step(this._eventQueue);
|
|
18799
|
-
this.drainCollisionEvents();
|
|
18800
|
-
this.syncNodeTransforms();
|
|
18801
|
-
return this;
|
|
18802
|
-
}
|
|
18803
|
-
syncNodeTransforms() {
|
|
18804
|
-
for (const binding of this._bindings) {
|
|
18805
|
-
if (binding.syncMode === 'physicsToNode') {
|
|
18806
|
-
binding.syncNodeFromBody();
|
|
18807
|
-
}
|
|
18808
|
-
}
|
|
18809
|
-
return this;
|
|
18810
|
-
}
|
|
18811
|
-
createDebugGraphics(options = {}) {
|
|
18812
|
-
const graphics = new Graphics();
|
|
18813
|
-
graphics.setCullable(false);
|
|
18814
|
-
this.updateDebugGraphics(graphics, options);
|
|
18815
|
-
return graphics;
|
|
18816
|
-
}
|
|
18817
|
-
updateDebugGraphics(graphics, options = {}) {
|
|
18818
|
-
const lineWidth = options.lineWidth ?? defaultDebugDrawOptions.lineWidth;
|
|
18819
|
-
const solidLineColor = options.solidLineColor ?? defaultDebugDrawOptions.solidLineColor;
|
|
18820
|
-
const solidFillColor = options.solidFillColor ?? defaultDebugDrawOptions.solidFillColor;
|
|
18821
|
-
const triggerLineColor = options.triggerLineColor ?? defaultDebugDrawOptions.triggerLineColor;
|
|
18822
|
-
const triggerFillColor = options.triggerFillColor ?? defaultDebugDrawOptions.triggerFillColor;
|
|
18823
|
-
graphics.clear();
|
|
18824
|
-
graphics.lineWidth = lineWidth;
|
|
18825
|
-
for (const binding of this._bindings) {
|
|
18826
|
-
const bodyTranslation = {
|
|
18827
|
-
x: binding.x,
|
|
18828
|
-
y: binding.y,
|
|
18829
|
-
};
|
|
18830
|
-
const rotation = binding.rotation;
|
|
18831
|
-
const lineColor = binding.trigger ? triggerLineColor : solidLineColor;
|
|
18832
|
-
const fillColor = binding.trigger ? triggerFillColor : solidFillColor;
|
|
18833
|
-
graphics.lineColor = lineColor;
|
|
18834
|
-
graphics.fillColor = fillColor;
|
|
18835
|
-
if (binding.shape.type === 'box') {
|
|
18836
|
-
const path = this.buildBoxPath(binding.shape, bodyTranslation.x, bodyTranslation.y, rotation);
|
|
18837
|
-
graphics.drawPolygon(path);
|
|
18838
|
-
}
|
|
18839
|
-
else {
|
|
18840
|
-
const offset = getOffset(binding.shape);
|
|
18841
|
-
const rotatedOffset = rotatePoint(offset.x, offset.y, rotation);
|
|
18842
|
-
graphics.drawCircle(bodyTranslation.x + rotatedOffset.x, bodyTranslation.y + rotatedOffset.y, binding.shape.radius);
|
|
18843
|
-
}
|
|
18844
|
-
}
|
|
18845
|
-
return graphics;
|
|
18846
|
-
}
|
|
18847
|
-
destroy() {
|
|
18848
|
-
for (const binding of Array.from(this._bindings)) {
|
|
18849
|
-
this.removeBinding(binding);
|
|
18850
|
-
}
|
|
18851
|
-
this.onCollisionEnter.destroy();
|
|
18852
|
-
this.onCollisionExit.destroy();
|
|
18853
|
-
this.onTriggerEnter.destroy();
|
|
18854
|
-
this.onTriggerExit.destroy();
|
|
18855
|
-
}
|
|
18856
|
-
writeBodyTransform(body, x, y, rotation, wakeUp) {
|
|
18857
|
-
body.setTranslation(new this._rapier.vector2(x, y), wakeUp);
|
|
18858
|
-
body.setRotation(rotation, wakeUp);
|
|
18859
|
-
}
|
|
18860
|
-
removeBinding(binding) {
|
|
18861
|
-
if (!this._bindings.has(binding)) {
|
|
18862
|
-
return;
|
|
18863
|
-
}
|
|
18864
|
-
this._bindings.delete(binding);
|
|
18865
|
-
this._nodeBindings.delete(binding.node);
|
|
18866
|
-
this._colliderBindings.delete(binding.colliderHandle);
|
|
18867
|
-
this._world.removeRigidBody(binding.getBody());
|
|
18868
|
-
}
|
|
18869
|
-
createBodyDescriptor(node, options) {
|
|
18870
|
-
const type = options.type ?? 'dynamic';
|
|
18871
|
-
let descriptor = this._rapier.rigidBodyDescFactory.dynamic();
|
|
18872
|
-
switch (type) {
|
|
18873
|
-
case 'static':
|
|
18874
|
-
descriptor = this._rapier.rigidBodyDescFactory.fixed();
|
|
18875
|
-
break;
|
|
18876
|
-
case 'kinematic':
|
|
18877
|
-
if (typeof this._rapier.rigidBodyDescFactory.kinematicPositionBased === 'function') {
|
|
18878
|
-
descriptor = this._rapier.rigidBodyDescFactory.kinematicPositionBased();
|
|
18879
|
-
}
|
|
18880
|
-
else if (typeof this._rapier.rigidBodyDescFactory.kinematicVelocityBased === 'function') {
|
|
18881
|
-
descriptor = this._rapier.rigidBodyDescFactory.kinematicVelocityBased();
|
|
18882
|
-
}
|
|
18883
|
-
else {
|
|
18884
|
-
throw new Error('Rapier module does not expose a kinematic rigid-body descriptor.');
|
|
18885
|
-
}
|
|
18886
|
-
break;
|
|
18887
|
-
default:
|
|
18888
|
-
descriptor = this._rapier.rigidBodyDescFactory.dynamic();
|
|
18889
|
-
break;
|
|
18890
|
-
}
|
|
18891
|
-
descriptor
|
|
18892
|
-
.setTranslation(node.x, node.y)
|
|
18893
|
-
.setRotation(node.rotation)
|
|
18894
|
-
.setLinearDamping(options.linearDamping ?? 0)
|
|
18895
|
-
.setAngularDamping(options.angularDamping ?? 0)
|
|
18896
|
-
.setGravityScale(options.gravityScale ?? 1)
|
|
18897
|
-
.lockRotations(options.lockRotation ?? false);
|
|
18898
|
-
return descriptor;
|
|
18899
|
-
}
|
|
18900
|
-
createColliderDescriptor(options) {
|
|
18901
|
-
const shape = options.shape;
|
|
18902
|
-
const descriptor = shape.type === 'box'
|
|
18903
|
-
? this._rapier.colliderDescFactory.cuboid(shape.width / 2, shape.height / 2)
|
|
18904
|
-
: this._rapier.colliderDescFactory.ball(shape.radius);
|
|
18905
|
-
const offset = getOffset(shape);
|
|
18906
|
-
const groups = toPackedCollisionGroups(options.collisionFilter);
|
|
18907
|
-
descriptor
|
|
18908
|
-
.setTranslation(offset.x, offset.y)
|
|
18909
|
-
.setRotation(shape.type === 'box' ? (shape.offsetRotation ?? 0) : 0)
|
|
18910
|
-
.setSensor(options.trigger ?? false)
|
|
18911
|
-
.setFriction(options.friction ?? 0.5)
|
|
18912
|
-
.setRestitution(options.restitution ?? 0)
|
|
18913
|
-
.setDensity(options.density ?? 1)
|
|
18914
|
-
.setCollisionGroups(groups)
|
|
18915
|
-
.setSolverGroups(groups);
|
|
18916
|
-
const collisionEvents = this._rapier.activeCollisionEvents;
|
|
18917
|
-
if (typeof collisionEvents === 'number') {
|
|
18918
|
-
descriptor.setActiveEvents(collisionEvents);
|
|
18919
|
-
}
|
|
18920
|
-
return descriptor;
|
|
18921
|
-
}
|
|
18922
|
-
applyStepDelta(deltaSeconds) {
|
|
18923
|
-
const worldWithTimestep = this._world;
|
|
18924
|
-
if (typeof worldWithTimestep.timestep === 'number') {
|
|
18925
|
-
worldWithTimestep.timestep = deltaSeconds;
|
|
18926
|
-
}
|
|
18927
|
-
if (worldWithTimestep.integrationParameters
|
|
18928
|
-
&& typeof worldWithTimestep.integrationParameters.dt === 'number') {
|
|
18929
|
-
worldWithTimestep.integrationParameters.dt = deltaSeconds;
|
|
18930
|
-
}
|
|
18931
|
-
}
|
|
18932
|
-
drainCollisionEvents() {
|
|
18933
|
-
this._eventQueue.drainCollisionEvents((handleA, handleB, started) => {
|
|
18934
|
-
const first = this._colliderBindings.get(handleA);
|
|
18935
|
-
const second = this._colliderBindings.get(handleB);
|
|
18936
|
-
if (!first || !second) {
|
|
18937
|
-
return;
|
|
18938
|
-
}
|
|
18939
|
-
const trigger = first.trigger || second.trigger;
|
|
18940
|
-
const event = {
|
|
18941
|
-
started,
|
|
18942
|
-
trigger,
|
|
18943
|
-
first,
|
|
18944
|
-
second,
|
|
18945
|
-
};
|
|
18946
|
-
if (trigger) {
|
|
18947
|
-
if (started) {
|
|
18948
|
-
this.onTriggerEnter.dispatch(event);
|
|
18949
|
-
}
|
|
18950
|
-
else {
|
|
18951
|
-
this.onTriggerExit.dispatch(event);
|
|
18952
|
-
}
|
|
18953
|
-
return;
|
|
18954
|
-
}
|
|
18955
|
-
if (started) {
|
|
18956
|
-
this.onCollisionEnter.dispatch(event);
|
|
18957
|
-
}
|
|
18958
|
-
else {
|
|
18959
|
-
this.onCollisionExit.dispatch(event);
|
|
18960
|
-
}
|
|
18961
|
-
});
|
|
18962
|
-
}
|
|
18963
|
-
buildBoxPath(shape, bodyX, bodyY, bodyRotation) {
|
|
18964
|
-
const halfWidth = shape.width / 2;
|
|
18965
|
-
const halfHeight = shape.height / 2;
|
|
18966
|
-
const corners = [
|
|
18967
|
-
{ x: -halfWidth, y: -halfHeight },
|
|
18968
|
-
{ x: halfWidth, y: -halfHeight },
|
|
18969
|
-
{ x: halfWidth, y: halfHeight },
|
|
18970
|
-
{ x: -halfWidth, y: halfHeight },
|
|
18971
|
-
];
|
|
18972
|
-
const offset = getOffset(shape);
|
|
18973
|
-
const offsetRotation = shape.offsetRotation ?? 0;
|
|
18974
|
-
const path = [];
|
|
18975
|
-
for (const corner of corners) {
|
|
18976
|
-
const local = rotatePoint(corner.x, corner.y, offsetRotation);
|
|
18977
|
-
const withOffset = {
|
|
18978
|
-
x: local.x + offset.x,
|
|
18979
|
-
y: local.y + offset.y,
|
|
18980
|
-
};
|
|
18981
|
-
const worldPoint = rotatePoint(withOffset.x, withOffset.y, bodyRotation);
|
|
18982
|
-
path.push(bodyX + worldPoint.x, bodyY + worldPoint.y);
|
|
18983
|
-
}
|
|
18984
|
-
return path;
|
|
18985
|
-
}
|
|
18986
|
-
}
|
|
18987
|
-
const createRapierPhysicsWorld = async (options = {}) => {
|
|
18988
|
-
return await RapierPhysicsWorld.create(options);
|
|
18989
|
-
};
|
|
18990
|
-
|
|
18991
|
-
export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, Circle, Clock, CollisionType, Color, ColorAffector, ColorFilter, Container, Drawable, Ellipse, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad, GamepadChannel, GamepadControl, GamepadMapping, GamepadMappingFamily, GamepadPromptLayouts, GenericDualAnalogGamepadMapping, Graphics, ImageFactory, IndexedDbDatabase, IndexedDbStore, Input, InputManager, Interval, JoyConLeftGamepadMapping, JoyConRightGamepadMapping, Json, JsonFactory, Keyboard, Line, Loader, Matrix, Mesh, Music, MusicFactory, NetworkOnlyStrategy, ObservableSize, ObservableVector, Particle, ParticleOptions, ParticleSystem, PlayStationGamepadMapping, Pointer, PointerState, PointerStateFlag, PolarVector, Polygon, Quadtree, Random, RapierPhysicsBinding, RapierPhysicsWorld, Rectangle, RenderBackendType, RenderNode, RenderTarget, RenderTargetPass, RenderTexture, RendererRegistry, RenderingPrimitives, Sampler, ScaleAffector, ScaleModes, Scene, SceneManager, SceneNode, Segment, Shader, ShaderAttribute, ShaderPrimitives, ShaderUniform, Signal, Size, Sound, SoundFactory, Sprite, SpriteFlags, Spritesheet, SteamControllerGamepadMapping, SvgAsset, SvgFactory, SwitchProGamepadMapping, Text, TextAsset, TextFactory, TextStyle, Texture, TextureFactory, Time, Timer, TorqueAffector, UniversalEmitter, Vector, Video, VideoFactory, View, ViewFlags, VoronoiRegion, VttAsset, VttFactory, WasmFactory, WebGl2Backend, WebGl2MeshRenderer, WebGl2ParticleRenderer, WebGl2RenderBuffer, WebGl2ShaderBlock, WebGl2SpriteRenderer, WebGl2VertexArrayObject, WebGpuBackend, WebGpuMeshRenderer, WebGpuParticleRenderer, WebGpuSpriteRenderer, WrapModes, XboxGamepadMapping, bezierCurveTo, buildCircle, buildEllipse, buildLine, buildPath, buildPolygon, buildRectangle, buildStar, builtInGamepadDefinitions, canvasSourceToDataUrl, clamp, createRapierPhysicsWorld, createRenderStats, createWebGl2ShaderProgram, decodeAudioData, defineAssetManifest, degreesPerRadian, degreesToRadians, determineMimeType, emptyArrayBuffer, getAudioContext, getCanvasSourceSize, getCollisionCircleCircle, getCollisionCircleRectangle, getCollisionPolygonCircle, getCollisionRectangleRectangle, getCollisionSat, getDistance, getOfflineAudioContext, getPreciseTime, getTextureSourceSize, getVoronoiRegion$1 as getVoronoiRegion, getWebGpuBlendState, hours, inRange, intersectionCircleCircle, intersectionCircleEllipse, intersectionCirclePoly, intersectionEllipseEllipse, intersectionEllipsePoly, intersectionLineCircle, intersectionLineEllipse, intersectionLineLine, intersectionLinePoly, intersectionLineRect, intersectionPointCircle, intersectionPointEllipse, intersectionPointLine, intersectionPointPoint, intersectionPointPoly, intersectionPointRect, intersectionPolyPoly, intersectionRectCircle, intersectionRectEllipse, intersectionRectPoly, intersectionRectRect, intersectionSat, isAudioContextReady, isPowerOfTwo, lerp, matchesIds, maxPointers, milliseconds, minutes, noop$1 as noop, normalizeIds, onAudioContextReady, parseGamepadDescriptor, pointerSlotSize, quadraticCurveTo, radiansPerDegree, radiansToDegrees, rand, removeArrayItems, resetRenderStats, resolveDefinition, resolveGamepadDefinition, seconds, sign$1 as sign, stopEvent, supportsCodec, supportsEventOptions, supportsIndexedDb, supportsPointerEvents, supportsTouchEvents, supportsWebAudio, tau, trimRotation, webGl2PrimitiveArrayConstructors, webGl2PrimitiveByteSizeMapping, webGl2PrimitiveTypeNames };
|
|
18762
|
+
export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, Circle, Clock, CollisionType, Color, ColorAffector, ColorFilter, Container, Drawable, DynamicGlyphAtlas, Ellipse, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad, GamepadChannel, GamepadControl, GamepadMapping, GamepadMappingFamily, GamepadPromptLayouts, GenericDualAnalogGamepadMapping, Graphics, ImageFactory, IndexedDbDatabase, IndexedDbStore, Input, InputManager, Interval, JoyConLeftGamepadMapping, JoyConRightGamepadMapping, Json, JsonFactory, Keyboard, Line, Loader, Matrix, Mesh, Music, MusicFactory, NetworkOnlyStrategy, ObservableSize, ObservableVector, Particle, ParticleOptions, ParticleSystem, PlayStationGamepadMapping, Pointer, PointerState, PointerStateFlag, PolarVector, Polygon, Quadtree, Random, Rectangle, RenderBackendType, RenderNode, RenderTarget, RenderTargetPass, RenderTexture, RendererRegistry, RenderingPrimitives, Sampler, ScaleAffector, ScaleModes, Scene, SceneManager, SceneNode, Segment, Shader, ShaderAttribute, ShaderPrimitives, ShaderUniform, Signal, Size, Sound, SoundFactory, Sprite, SpriteFlags, Spritesheet, SteamControllerGamepadMapping, SvgAsset, SvgFactory, SwitchProGamepadMapping, Text, TextAsset, TextFactory, TextStyle, Texture, TextureFactory, Time, Timer, TorqueAffector, UniversalEmitter, Vector, Video, VideoFactory, View, ViewFlags, VoronoiRegion, VttAsset, VttFactory, WasmFactory, WebGl2Backend, WebGl2MeshRenderer, WebGl2ParticleRenderer, WebGl2RenderBuffer, WebGl2ShaderBlock, WebGl2SpriteRenderer, WebGl2VertexArrayObject, WebGpuBackend, WebGpuMeshRenderer, WebGpuParticleRenderer, WebGpuSpriteRenderer, WrapModes, XboxGamepadMapping, bezierCurveTo, buildCircle, buildEllipse, buildLine, buildPath, buildPolygon, buildRectangle, buildStar, builtInGamepadDefinitions, canvasSourceToDataUrl, clamp, createRenderStats, createWebGl2ShaderProgram, decodeAudioData, defineAssetManifest, degreesPerRadian, degreesToRadians, determineMimeType, emptyArrayBuffer, getAudioContext, getCanvasSourceSize, getCollisionCircleCircle, getCollisionCircleRectangle, getCollisionPolygonCircle, getCollisionRectangleRectangle, getCollisionSat, getDistance, getOfflineAudioContext, getPreciseTime, getTextureSourceSize, getVoronoiRegion$1 as getVoronoiRegion, getWebGpuBlendState, hours, inRange, intersectionCircleCircle, intersectionCircleEllipse, intersectionCirclePoly, intersectionEllipseEllipse, intersectionEllipsePoly, intersectionLineCircle, intersectionLineEllipse, intersectionLineLine, intersectionLinePoly, intersectionLineRect, intersectionPointCircle, intersectionPointEllipse, intersectionPointLine, intersectionPointPoint, intersectionPointPoly, intersectionPointRect, intersectionPolyPoly, intersectionRectCircle, intersectionRectEllipse, intersectionRectPoly, intersectionRectRect, intersectionSat, isAudioContextReady, isPowerOfTwo, layoutText, lerp, matchesIds, maxPointers, milliseconds, minutes, noop$1 as noop, normalizeIds, onAudioContextReady, parseGamepadDescriptor, pointerSlotSize, quadraticCurveTo, radiansPerDegree, radiansToDegrees, rand, removeArrayItems, resetRenderStats, resolveDefinition, resolveGamepadDefinition, seconds, sign$1 as sign, stopEvent, supportsCodec, supportsEventOptions, supportsIndexedDb, supportsPointerEvents, supportsTouchEvents, supportsWebAudio, tau, trimRotation, webGl2PrimitiveArrayConstructors, webGl2PrimitiveByteSizeMapping, webGl2PrimitiveTypeNames };
|
|
18992
18763
|
//# sourceMappingURL=exo.esm.js.map
|