@lightningjs/renderer 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/exports/index.d.ts +3 -0
- package/dist/{src/core/text-rendering/renderers/SdfTextRenderer/internal/findNearestMultiple.js → exports/index.js} +4 -11
- package/dist/exports/index.js.map +1 -0
- package/dist/src/common/IAnimationController.d.ts +58 -1
- package/dist/src/common/IAnimationController.js +0 -18
- package/dist/src/common/IAnimationController.js.map +1 -1
- package/dist/src/core/CoreNode.js +40 -0
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/CoreTextureManager.d.ts +35 -0
- package/dist/src/core/CoreTextureManager.js +1 -1
- package/dist/src/core/CoreTextureManager.js.map +1 -1
- package/dist/src/core/TextureList.js +34 -0
- package/dist/src/core/TextureList.js.map +1 -0
- package/dist/src/core/animations/CoreAnimation.d.ts +2 -2
- package/dist/src/core/animations/CoreAnimation.js +33 -10
- package/dist/src/core/animations/CoreAnimation.js.map +1 -1
- package/dist/src/core/animations/CoreAnimationController.d.ts +8 -12
- package/dist/src/core/animations/CoreAnimationController.js +42 -46
- package/dist/src/core/animations/CoreAnimationController.js.map +1 -1
- package/dist/src/core/lib/ImageWorker.d.ts +0 -1
- package/dist/src/core/lib/ImageWorker.js +8 -10
- package/dist/src/core/lib/ImageWorker.js.map +1 -1
- package/dist/src/core/lib/utils.d.ts +1 -0
- package/dist/src/core/lib/utils.js +20 -0
- package/dist/src/core/lib/utils.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js +25 -0
- package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js.map +1 -1
- package/dist/src/core/renderers/webgl/newShader/effectsMock.d.ts +1 -0
- package/dist/src/core/renderers/webgl/newShader/effectsMock.js +36 -0
- package/dist/src/core/renderers/webgl/newShader/effectsMock.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderEffect.js +2 -1
- package/dist/src/core/renderers/webgl/shaders/effects/BorderEffect.js.map +1 -1
- package/dist/src/core/textures/ImageTexture.d.ts +5 -1
- package/dist/src/core/textures/ImageTexture.js +20 -9
- package/dist/src/core/textures/ImageTexture.js.map +1 -1
- package/dist/src/core/utils.js +1 -6
- package/dist/src/core/utils.js.map +1 -1
- package/dist/src/main-api/INode.d.ts +1 -1
- package/dist/src/main-api/Renderer.d.ts +314 -0
- package/dist/src/main-api/Renderer.js +387 -0
- package/dist/src/main-api/Renderer.js.map +1 -0
- package/dist/src/main-api/utils.d.ts +2 -0
- package/dist/src/main-api/utils.js +34 -0
- package/dist/src/main-api/utils.js.map +1 -0
- package/dist/src/render-drivers/threadx/ThreadXMainAnimationController.d.ts +8 -4
- package/dist/src/render-drivers/threadx/ThreadXMainAnimationController.js +53 -24
- package/dist/src/render-drivers/threadx/ThreadXMainAnimationController.js.map +1 -1
- package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.js +6 -0
- package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.js.map +1 -1
- package/dist/src/render-drivers/utils.js +6 -1
- package/dist/src/render-drivers/utils.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/{dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/roundUpToMultiple.js → exports/index.ts} +4 -14
- package/package.json +1 -1
- package/src/common/IAnimationController.ts +60 -1
- package/src/core/CoreNode.ts +45 -0
- package/src/core/CoreTextureManager.ts +40 -1
- package/src/core/animations/CoreAnimation.ts +35 -11
- package/src/core/animations/CoreAnimationController.ts +48 -53
- package/src/core/lib/ImageWorker.ts +10 -13
- package/src/core/lib/utils.ts +25 -0
- package/src/core/renderers/webgl/WebGlCoreRenderer.ts +27 -0
- package/src/core/renderers/webgl/shaders/effects/BorderEffect.ts +2 -1
- package/src/core/textures/ImageTexture.ts +26 -9
- package/src/core/utils.ts +1 -7
- package/src/main-api/INode.ts +1 -1
- package/src/render-drivers/threadx/ThreadXMainAnimationController.ts +63 -27
- package/src/render-drivers/threadx/worker/ThreadXRendererNode.ts +6 -0
- package/src/render-drivers/utils.ts +6 -1
- package/dist/src/core/lib/WebGlContext.d.ts +0 -414
- package/dist/src/core/lib/WebGlContext.js +0 -640
- package/dist/src/core/lib/WebGlContext.js.map +0 -1
- package/dist/src/core/scene/Scene.d.ts +0 -59
- package/dist/src/core/scene/Scene.js +0 -106
- package/dist/src/core/scene/Scene.js.map +0 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/findNearestMultiple.d.ts +0 -8
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/findNearestMultiple.js.map +0 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/SdfBufferHelper.d.ts +0 -19
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/SdfBufferHelper.js +0 -84
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/SdfBufferHelper.js.map +0 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/layoutLine.d.ts +0 -8
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/layoutLine.js +0 -40
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/layoutLine.js.map +0 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/layoutText2.d.ts +0 -2
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/layoutText2.js +0 -41
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/layoutText2.js.map +0 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/utils.d.ts +0 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/utils.js +0 -4
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2/utils.js.map +0 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2.js +0 -2
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText2.js.map +0 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/makeRenderWindow.d.ts +0 -20
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/makeRenderWindow.js +0 -55
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/makeRenderWindow.js.map +0 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/roundUpToMultiple.d.ts +0 -9
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/roundUpToMultiple.js.map +0 -1
- /package/dist/src/core/{text-rendering/renderers/SdfTextRenderer/internal/layoutText2.d.ts → TextureList.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -16,14 +16,73 @@
|
|
|
16
16
|
* See the License for the specific language governing permissions and
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
|
+
import type { IEventEmitter } from '@lightningjs/threadx';
|
|
19
20
|
|
|
20
21
|
export type AnimationControllerState = 'running' | 'paused' | 'stopped';
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Animation Controller interface
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* This interface is used to control animations. It provides methods to start,
|
|
28
|
+
* stop, pause, and restore animations. It also provides a way to wait for the
|
|
29
|
+
* animation to stop.
|
|
30
|
+
*
|
|
31
|
+
* This interface extends the `IEventEmitter` interface, which means you can
|
|
32
|
+
* listen to these events emitted by the animation controller:
|
|
33
|
+
* - `animating` - Emitted when the animation finishes it's delay phase and
|
|
34
|
+
* starts animating.
|
|
35
|
+
* - `stopped` - Emitted when the animation stops either by calling the `stop()`
|
|
36
|
+
* method or when the animation finishes naturally.
|
|
37
|
+
*/
|
|
38
|
+
export interface IAnimationController extends IEventEmitter {
|
|
39
|
+
/**
|
|
40
|
+
* Start the animation
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* If the animation is paused this method will resume the animation.
|
|
44
|
+
*/
|
|
23
45
|
start(): IAnimationController;
|
|
46
|
+
/**
|
|
47
|
+
* Stop the animation
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* Resets the animation to the start state
|
|
51
|
+
*/
|
|
24
52
|
stop(): IAnimationController;
|
|
53
|
+
/**
|
|
54
|
+
* Pause the animation
|
|
55
|
+
*/
|
|
25
56
|
pause(): IAnimationController;
|
|
57
|
+
/**
|
|
58
|
+
* Restore the animation to the original values
|
|
59
|
+
*/
|
|
26
60
|
restore(): IAnimationController;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Promise that resolves when the last active animation is stopped (including
|
|
64
|
+
* when the animation finishes naturally).
|
|
65
|
+
*
|
|
66
|
+
* @remarks
|
|
67
|
+
* The Promise returned by this method is reset every time the animation
|
|
68
|
+
* enters a new start/stop cycle. This means you must call `start()` before
|
|
69
|
+
* calling this method if you want to wait for the animation to stop.
|
|
70
|
+
*
|
|
71
|
+
* This method always returns a resolved promise if the animation is currently
|
|
72
|
+
* in a stopped state.
|
|
73
|
+
*
|
|
74
|
+
* @returns
|
|
75
|
+
*/
|
|
27
76
|
waitUntilStopped(): Promise<void>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Current state of the animation
|
|
80
|
+
*
|
|
81
|
+
* @remarks
|
|
82
|
+
* - `stopped` - The animation is currently stopped (at the beggining or end
|
|
83
|
+
* of the animation)
|
|
84
|
+
* - `running` - The animation is currently running
|
|
85
|
+
* - `paused` - The animation is currently paused
|
|
86
|
+
*/
|
|
28
87
|
readonly state: AnimationControllerState;
|
|
29
88
|
}
|
package/src/core/CoreNode.ts
CHANGED
|
@@ -339,6 +339,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
|
|
|
339
339
|
|
|
340
340
|
private onTextureLoaded: TextureLoadedEventHandler = (target, dimensions) => {
|
|
341
341
|
this.autosizeNode(dimensions);
|
|
342
|
+
|
|
342
343
|
// Texture was loaded. In case the RAF loop has already stopped, we request
|
|
343
344
|
// a render to ensure the texture is rendered.
|
|
344
345
|
this.stage.requestRender();
|
|
@@ -353,6 +354,11 @@ export class CoreNode extends EventEmitter implements ICoreNode {
|
|
|
353
354
|
type: 'texture',
|
|
354
355
|
dimensions,
|
|
355
356
|
} satisfies NodeTextureLoadedPayload);
|
|
357
|
+
|
|
358
|
+
// Trigger a local update if the texture is loaded and the resizeMode is 'contain'
|
|
359
|
+
if (this.props.textureOptions?.resizeMode?.type === 'contain') {
|
|
360
|
+
this.setUpdateType(UpdateType.Local);
|
|
361
|
+
}
|
|
356
362
|
};
|
|
357
363
|
|
|
358
364
|
private onTextureFailed: TextureFailedEventHandler = (target, error) => {
|
|
@@ -431,6 +437,45 @@ export class CoreNode extends EventEmitter implements ICoreNode {
|
|
|
431
437
|
.multiply(this.scaleRotateTransform)
|
|
432
438
|
.translate(-pivotTranslateX, -pivotTranslateY);
|
|
433
439
|
|
|
440
|
+
// Handle 'contain' resize mode
|
|
441
|
+
const { width, height } = this.props;
|
|
442
|
+
const texture = this.props.texture;
|
|
443
|
+
if (
|
|
444
|
+
texture &&
|
|
445
|
+
texture.dimensions &&
|
|
446
|
+
this.props.textureOptions?.resizeMode?.type === 'contain'
|
|
447
|
+
) {
|
|
448
|
+
let resizeModeScaleX = 1;
|
|
449
|
+
let resizeModeScaleY = 1;
|
|
450
|
+
let extraX = 0;
|
|
451
|
+
let extraY = 0;
|
|
452
|
+
const { width: tw, height: th } = texture.dimensions;
|
|
453
|
+
const txAspectRatio = tw / th;
|
|
454
|
+
const nodeAspectRatio = width / height;
|
|
455
|
+
if (txAspectRatio > nodeAspectRatio) {
|
|
456
|
+
// Texture is wider than node
|
|
457
|
+
// Center the node vertically (shift down by extraY)
|
|
458
|
+
// Scale the node vertically to maintain original aspect ratio
|
|
459
|
+
const scaleX = width / tw;
|
|
460
|
+
const scaledTxHeight = th * scaleX;
|
|
461
|
+
extraY = (height - scaledTxHeight) / 2;
|
|
462
|
+
resizeModeScaleY = scaledTxHeight / height;
|
|
463
|
+
} else {
|
|
464
|
+
// Texture is taller than node (or equal)
|
|
465
|
+
// Center the node horizontally (shift right by extraX)
|
|
466
|
+
// Scale the node horizontally to maintain original aspect ratio
|
|
467
|
+
const scaleY = height / th;
|
|
468
|
+
const scaledTxWidth = tw * scaleY;
|
|
469
|
+
extraX = (width - scaledTxWidth) / 2;
|
|
470
|
+
resizeModeScaleX = scaledTxWidth / width;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Apply the extra translation and scale to the local transform
|
|
474
|
+
this.localTransform
|
|
475
|
+
.translate(extraX, extraY)
|
|
476
|
+
.scale(resizeModeScaleX, resizeModeScaleY);
|
|
477
|
+
}
|
|
478
|
+
|
|
434
479
|
this.setUpdateType(UpdateType.Global);
|
|
435
480
|
}
|
|
436
481
|
|
|
@@ -57,6 +57,36 @@ export interface TextureManagerDebugInfo {
|
|
|
57
57
|
idCacheSize: number;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export type ResizeModeOptions =
|
|
61
|
+
| {
|
|
62
|
+
/**
|
|
63
|
+
* Specifies that the image should be resized to cover the specified dimensions.
|
|
64
|
+
*/
|
|
65
|
+
type: 'cover';
|
|
66
|
+
/**
|
|
67
|
+
* The horizontal clipping position
|
|
68
|
+
* To clip the left, set clipX to 0. To clip the right, set clipX to 1.
|
|
69
|
+
* clipX 0.5 will clip a equal amount from left and right
|
|
70
|
+
*
|
|
71
|
+
* @defaultValue 0.5
|
|
72
|
+
*/
|
|
73
|
+
clipX?: number;
|
|
74
|
+
/**
|
|
75
|
+
* The vertical clipping position
|
|
76
|
+
* To clip the top, set clipY to 0. To clip the bottom, set clipY to 1.
|
|
77
|
+
* clipY 0.5 will clip a equal amount from top and bottom
|
|
78
|
+
*
|
|
79
|
+
* @defaultValue 0.5
|
|
80
|
+
*/
|
|
81
|
+
clipY?: number;
|
|
82
|
+
}
|
|
83
|
+
| {
|
|
84
|
+
/**
|
|
85
|
+
* Specifies that the image should be resized to fit within the specified dimensions.
|
|
86
|
+
*/
|
|
87
|
+
type: 'contain';
|
|
88
|
+
};
|
|
89
|
+
|
|
60
90
|
/**
|
|
61
91
|
* Universal options for all texture types
|
|
62
92
|
*
|
|
@@ -129,6 +159,15 @@ export interface TextureOptions {
|
|
|
129
159
|
* @defaultValue `false`
|
|
130
160
|
*/
|
|
131
161
|
flipY?: boolean;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* You can use resizeMode to determine the clipping automatically from the width
|
|
165
|
+
* and height of the source texture. This can be convenient if you are unsure about
|
|
166
|
+
* the exact image sizes but want the image to cover a specific area.
|
|
167
|
+
*
|
|
168
|
+
* The resize modes cover and contain are supported
|
|
169
|
+
*/
|
|
170
|
+
resizeMode?: ResizeModeOptions;
|
|
132
171
|
}
|
|
133
172
|
|
|
134
173
|
export class CoreTextureManager {
|
|
@@ -161,7 +200,7 @@ export class CoreTextureManager {
|
|
|
161
200
|
|
|
162
201
|
constructor(numImageWorkers: number) {
|
|
163
202
|
// Register default known texture types
|
|
164
|
-
if (this.hasCreateImageBitmap && this.hasWorker) {
|
|
203
|
+
if (this.hasCreateImageBitmap && this.hasWorker && numImageWorkers > 0) {
|
|
165
204
|
this.imageWorkerManager = new ImageWorkerManager(numImageWorkers);
|
|
166
205
|
}
|
|
167
206
|
|
|
@@ -36,6 +36,7 @@ export interface AnimationSettings {
|
|
|
36
36
|
export class CoreAnimation extends EventEmitter {
|
|
37
37
|
public propStartValues: Partial<INodeAnimatableProps> = {};
|
|
38
38
|
public restoreValues: Partial<INodeAnimatableProps> = {};
|
|
39
|
+
public settings: AnimationSettings;
|
|
39
40
|
private progress = 0;
|
|
40
41
|
private delayFor = 0;
|
|
41
42
|
private timingFunction: (t: number) => number | undefined;
|
|
@@ -44,7 +45,7 @@ export class CoreAnimation extends EventEmitter {
|
|
|
44
45
|
constructor(
|
|
45
46
|
private node: CoreNode,
|
|
46
47
|
private props: Partial<INodeAnimatableProps>,
|
|
47
|
-
|
|
48
|
+
settings: Partial<AnimationSettings>,
|
|
48
49
|
) {
|
|
49
50
|
super();
|
|
50
51
|
this.propStartValues = {};
|
|
@@ -53,12 +54,19 @@ export class CoreAnimation extends EventEmitter {
|
|
|
53
54
|
this.propStartValues[propName] = node[propName];
|
|
54
55
|
});
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
const easing = settings.easing || 'linear';
|
|
58
|
+
const delay = settings.delay ?? 0;
|
|
59
|
+
this.settings = {
|
|
60
|
+
duration: settings.duration ?? 0,
|
|
61
|
+
delay,
|
|
62
|
+
easing,
|
|
63
|
+
loop: settings.loop ?? false,
|
|
64
|
+
repeat: settings.repeat ?? 0,
|
|
65
|
+
repeatDelay: settings.repeatDelay ?? 0,
|
|
66
|
+
stopMethod: settings.stopMethod ?? false,
|
|
67
|
+
};
|
|
68
|
+
this.timingFunction = getTimingFunction(easing);
|
|
69
|
+
this.delayFor = delay;
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
reset() {
|
|
@@ -96,24 +104,40 @@ export class CoreAnimation extends EventEmitter {
|
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
106
|
|
|
99
|
-
applyEasing(p: number, s: number, e: number): number {
|
|
107
|
+
private applyEasing(p: number, s: number, e: number): number {
|
|
100
108
|
return (this.timingFunction(p) || p) * (e - s) + s;
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
update(dt: number) {
|
|
104
112
|
const { duration, loop, easing, stopMethod } = this.settings;
|
|
105
|
-
|
|
113
|
+
const { delayFor } = this;
|
|
114
|
+
if (duration === 0 && delayFor === 0) {
|
|
106
115
|
this.emit('finished', {});
|
|
107
116
|
return;
|
|
108
117
|
}
|
|
109
118
|
|
|
110
119
|
if (this.delayFor > 0) {
|
|
111
120
|
this.delayFor -= dt;
|
|
121
|
+
if (this.delayFor >= 0) {
|
|
122
|
+
// Either no or more delay left. Exit.
|
|
123
|
+
return;
|
|
124
|
+
} else {
|
|
125
|
+
// We went beyond the delay time, add it back to dt so we can continue
|
|
126
|
+
// with the animation.
|
|
127
|
+
dt = -this.delayFor;
|
|
128
|
+
this.delayFor = 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (duration === 0) {
|
|
133
|
+
// No duration, we are done.
|
|
134
|
+
this.emit('finished', {});
|
|
112
135
|
return;
|
|
113
136
|
}
|
|
114
137
|
|
|
115
|
-
if (this.
|
|
116
|
-
|
|
138
|
+
if (this.progress === 0) {
|
|
139
|
+
// Progress is 0, we are starting the post-delay part of the animation.
|
|
140
|
+
this.emit('animating', {});
|
|
117
141
|
}
|
|
118
142
|
|
|
119
143
|
this.progress += dt / duration;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
1
2
|
/*
|
|
2
3
|
* If not stated otherwise in this file or this component's LICENSE file the
|
|
3
4
|
* following copyright and licenses apply:
|
|
@@ -24,50 +25,48 @@ import type {
|
|
|
24
25
|
import type { AnimationManager } from './AnimationManager.js';
|
|
25
26
|
import type { CoreAnimation } from './CoreAnimation.js';
|
|
26
27
|
import { assertTruthy } from '../../utils.js';
|
|
28
|
+
import { EventEmitter } from '../../common/EventEmitter.js';
|
|
27
29
|
|
|
28
|
-
export class CoreAnimationController
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
startedResolve: ((scope?: any) => void) | null = null;
|
|
34
|
-
|
|
35
|
-
stoppedPromise: Promise<void> | null = null;
|
|
30
|
+
export class CoreAnimationController
|
|
31
|
+
extends EventEmitter
|
|
32
|
+
implements IAnimationController
|
|
33
|
+
{
|
|
34
|
+
stoppedPromise: Promise<void>;
|
|
36
35
|
/**
|
|
37
36
|
* If this is null, then the animation is in a finished / stopped state.
|
|
38
37
|
*/
|
|
39
38
|
stoppedResolve: (() => void) | null = null;
|
|
39
|
+
state: AnimationControllerState;
|
|
40
40
|
|
|
41
41
|
constructor(
|
|
42
42
|
private manager: AnimationManager,
|
|
43
43
|
private animation: CoreAnimation,
|
|
44
44
|
) {
|
|
45
|
+
super();
|
|
45
46
|
this.state = 'stopped';
|
|
46
|
-
|
|
47
|
+
// Initial stopped promise is resolved (since the animation is stopped)
|
|
48
|
+
this.stoppedPromise = Promise.resolve();
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
// Bind event handlers
|
|
51
|
+
this.onAnimating = this.onAnimating.bind(this);
|
|
52
|
+
this.onFinished = this.onFinished.bind(this);
|
|
53
|
+
}
|
|
49
54
|
|
|
50
55
|
start(): IAnimationController {
|
|
51
|
-
this.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.animation.once('finished', this.finished.bind(this));
|
|
56
|
-
|
|
57
|
-
// prevent registering the same animation twice
|
|
58
|
-
if (!this.manager.activeAnimations.has(this.animation)) {
|
|
59
|
-
this.manager.registerAnimation(this.animation);
|
|
56
|
+
if (this.state !== 'running') {
|
|
57
|
+
this.makeStoppedPromise();
|
|
58
|
+
this.registerAnimation();
|
|
59
|
+
this.state = 'running';
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
this.state = 'running';
|
|
63
61
|
return this;
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
stop(): IAnimationController {
|
|
67
|
-
this.
|
|
65
|
+
this.unregisterAnimation();
|
|
68
66
|
if (this.stoppedResolve !== null) {
|
|
69
67
|
this.stoppedResolve();
|
|
70
68
|
this.stoppedResolve = null;
|
|
69
|
+
this.emit('stopped', this);
|
|
71
70
|
}
|
|
72
71
|
this.animation.reset();
|
|
73
72
|
this.state = 'stopped';
|
|
@@ -75,7 +74,7 @@ export class CoreAnimationController implements IAnimationController {
|
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
pause(): IAnimationController {
|
|
78
|
-
this.
|
|
77
|
+
this.unregisterAnimation();
|
|
79
78
|
this.state = 'paused';
|
|
80
79
|
return this;
|
|
81
80
|
}
|
|
@@ -86,26 +85,24 @@ export class CoreAnimationController implements IAnimationController {
|
|
|
86
85
|
return this;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
this.
|
|
91
|
-
const promise = this.startedPromise;
|
|
92
|
-
assertTruthy(promise);
|
|
93
|
-
return promise;
|
|
88
|
+
waitUntilStopped(): Promise<void> {
|
|
89
|
+
return this.stoppedPromise;
|
|
94
90
|
}
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
private registerAnimation(): void {
|
|
93
|
+
// Hook up event listeners
|
|
94
|
+
this.animation.once('finished', this.onFinished);
|
|
95
|
+
this.animation.on('animating', this.onAnimating);
|
|
96
|
+
// Then register the animation
|
|
97
|
+
this.manager.registerAnimation(this.animation);
|
|
101
98
|
}
|
|
102
99
|
|
|
103
|
-
private
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
private unregisterAnimation(): void {
|
|
101
|
+
// First unregister the animation
|
|
102
|
+
this.manager.unregisterAnimation(this.animation);
|
|
103
|
+
// Then unhook event listeners
|
|
104
|
+
this.animation.off('finished', this.onFinished);
|
|
105
|
+
this.animation.off('animating', this.onAnimating);
|
|
109
106
|
}
|
|
110
107
|
|
|
111
108
|
private makeStoppedPromise(): void {
|
|
@@ -116,33 +113,31 @@ export class CoreAnimationController implements IAnimationController {
|
|
|
116
113
|
}
|
|
117
114
|
}
|
|
118
115
|
|
|
119
|
-
private
|
|
120
|
-
assertTruthy(this.startedResolve);
|
|
121
|
-
// resolve promise (and pass current this to continue to the chain)
|
|
122
|
-
this.startedResolve(this);
|
|
123
|
-
this.startedResolve = null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private finished(): void {
|
|
116
|
+
private onFinished(this: CoreAnimationController): void {
|
|
127
117
|
assertTruthy(this.stoppedResolve);
|
|
128
118
|
// If the animation is looping, then we need to restart it.
|
|
129
119
|
const { loop, stopMethod } = this.animation.settings;
|
|
130
120
|
|
|
131
121
|
if (stopMethod === 'reverse') {
|
|
132
122
|
this.animation.reverse();
|
|
133
|
-
this.start();
|
|
134
123
|
return;
|
|
135
124
|
}
|
|
136
125
|
|
|
137
|
-
// resolve promise
|
|
138
|
-
this.stoppedResolve();
|
|
139
|
-
this.stoppedResolve = null;
|
|
140
|
-
|
|
141
126
|
if (loop) {
|
|
142
127
|
return;
|
|
143
128
|
}
|
|
144
129
|
|
|
145
130
|
// unregister animation
|
|
146
|
-
this.
|
|
131
|
+
this.unregisterAnimation();
|
|
132
|
+
|
|
133
|
+
// resolve promise
|
|
134
|
+
this.stoppedResolve();
|
|
135
|
+
this.stoppedResolve = null;
|
|
136
|
+
this.emit('stopped', this);
|
|
137
|
+
this.state = 'stopped';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private onAnimating(this: CoreAnimationController): void {
|
|
141
|
+
this.emit('animating', this);
|
|
147
142
|
}
|
|
148
143
|
}
|
|
@@ -119,15 +119,10 @@ export class ImageWorkerManager {
|
|
|
119
119
|
return workers;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
private getNextWorker(): Worker {
|
|
122
|
+
private getNextWorker(): Worker | undefined {
|
|
123
123
|
const worker = this.workers[this.workerIndex];
|
|
124
124
|
this.workerIndex = (this.workerIndex + 1) % this.workers.length;
|
|
125
|
-
return worker
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private convertUrlToAbsolute(url: string): string {
|
|
129
|
-
const absoluteUrl = new URL(url, self.location.href);
|
|
130
|
-
return absoluteUrl.href;
|
|
125
|
+
return worker;
|
|
131
126
|
}
|
|
132
127
|
|
|
133
128
|
getImage(
|
|
@@ -137,14 +132,16 @@ export class ImageWorkerManager {
|
|
|
137
132
|
return new Promise((resolve, reject) => {
|
|
138
133
|
try {
|
|
139
134
|
if (this.workers) {
|
|
140
|
-
const absoluteSrcUrl = this.convertUrlToAbsolute(src);
|
|
141
135
|
const id = this.nextId++;
|
|
142
136
|
this.messageManager[id] = [resolve, reject];
|
|
143
|
-
this.getNextWorker()
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
137
|
+
const nextWorker = this.getNextWorker();
|
|
138
|
+
if (nextWorker) {
|
|
139
|
+
nextWorker.postMessage({
|
|
140
|
+
id,
|
|
141
|
+
src: src,
|
|
142
|
+
premultiplyAlpha,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
148
145
|
}
|
|
149
146
|
} catch (error) {
|
|
150
147
|
reject(error);
|
package/src/core/lib/utils.ts
CHANGED
|
@@ -248,3 +248,28 @@ export function isBoundPositive(bound: Bound): boolean {
|
|
|
248
248
|
export function isRectPositive(rect: Rect): boolean {
|
|
249
249
|
return rect.width > 0 && rect.height > 0;
|
|
250
250
|
}
|
|
251
|
+
|
|
252
|
+
export function convertUrlToAbsolute(url: string): string {
|
|
253
|
+
// handle local file imports
|
|
254
|
+
if (self.location.protocol === 'file:') {
|
|
255
|
+
const path = self.location.pathname.split('/');
|
|
256
|
+
path.pop();
|
|
257
|
+
const basePath = path.join('/');
|
|
258
|
+
const baseUrl = self.location.protocol + '//' + basePath;
|
|
259
|
+
|
|
260
|
+
// check if url has a leading dot
|
|
261
|
+
if (url.charAt(0) === '.') {
|
|
262
|
+
url = url.slice(1);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// check if url has a leading slash
|
|
266
|
+
if (url.charAt(0) === '/') {
|
|
267
|
+
url = url.slice(1);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return baseUrl + '/' + url;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const absoluteUrl = new URL(url, self.location.href);
|
|
274
|
+
return absoluteUrl.href;
|
|
275
|
+
}
|
|
@@ -50,6 +50,7 @@ import { WebGlContextWrapper } from '../../lib/WebGlContextWrapper.js';
|
|
|
50
50
|
import { RenderTexture } from '../../textures/RenderTexture.js';
|
|
51
51
|
import type { CoreNode } from '../../CoreNode.js';
|
|
52
52
|
import { WebGlCoreCtxRenderTexture } from './WebGlCoreCtxRenderTexture.js';
|
|
53
|
+
import { ImageTexture } from '../../textures/ImageTexture.js';
|
|
53
54
|
|
|
54
55
|
const WORDS_PER_QUAD = 24;
|
|
55
56
|
// const BYTES_PER_QUAD = WORDS_PER_QUAD * 4;
|
|
@@ -298,6 +299,32 @@ export class WebGlCoreRenderer extends CoreRenderer {
|
|
|
298
299
|
texture = texture.parentTexture;
|
|
299
300
|
}
|
|
300
301
|
|
|
302
|
+
const resizeMode = textureOptions?.resizeMode ?? false;
|
|
303
|
+
|
|
304
|
+
if (texture instanceof ImageTexture) {
|
|
305
|
+
if (resizeMode && texture.dimensions) {
|
|
306
|
+
const { width: tw, height: th } = texture.dimensions;
|
|
307
|
+
if (resizeMode.type === 'cover') {
|
|
308
|
+
const scaleX = width / tw;
|
|
309
|
+
const scaleY = height / th;
|
|
310
|
+
const scale = Math.max(scaleX, scaleY);
|
|
311
|
+
const precision = 1 / scale;
|
|
312
|
+
// Determine based on width
|
|
313
|
+
if (scale && scaleX && scaleX < scale) {
|
|
314
|
+
const desiredSize = precision * width;
|
|
315
|
+
texCoordX1 = (1 - desiredSize / tw) * (resizeMode.clipX ?? 0.5);
|
|
316
|
+
texCoordX2 = texCoordX1 + desiredSize / tw;
|
|
317
|
+
}
|
|
318
|
+
// Determine based on height
|
|
319
|
+
if (scale && scaleY && scaleY < scale) {
|
|
320
|
+
const desiredSize = precision * height;
|
|
321
|
+
texCoordY1 = (1 - desiredSize / th) * (resizeMode.clipY ?? 0.5);
|
|
322
|
+
texCoordY2 = texCoordY1 + desiredSize / th;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
301
328
|
// Flip texture coordinates if dictated by texture options
|
|
302
329
|
if (flipX) {
|
|
303
330
|
[texCoordX1, texCoordX2] = [texCoordX2, texCoordX1];
|
|
@@ -76,7 +76,8 @@ export class BorderEffect extends ShaderEffect {
|
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
static override onEffectMask = `
|
|
79
|
-
float
|
|
79
|
+
float intR = shaderMask + 1.0;
|
|
80
|
+
float mask = clamp(intR + width, 0.0, 1.0) - clamp(intR, 0.0, 1.0);
|
|
80
81
|
return mix(shaderColor, mix(shaderColor, maskColor, maskColor.a), mask);
|
|
81
82
|
`;
|
|
82
83
|
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
isCompressedTextureContainer,
|
|
24
24
|
loadCompressedTexture,
|
|
25
25
|
} from '../lib/textureCompression.js';
|
|
26
|
+
import { convertUrlToAbsolute } from '../lib/utils.js';
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Properties of the {@link ImageTexture}
|
|
@@ -37,7 +38,7 @@ export interface ImageTextureProps {
|
|
|
37
38
|
*
|
|
38
39
|
* @default ''
|
|
39
40
|
*/
|
|
40
|
-
src?: string | ImageData;
|
|
41
|
+
src?: string | ImageData | (() => ImageData);
|
|
41
42
|
/**
|
|
42
43
|
* Whether to premultiply the alpha channel into the color channels of the
|
|
43
44
|
* image.
|
|
@@ -50,6 +51,10 @@ export interface ImageTextureProps {
|
|
|
50
51
|
* @default true
|
|
51
52
|
*/
|
|
52
53
|
premultiplyAlpha?: boolean | null;
|
|
54
|
+
/**
|
|
55
|
+
* `ImageData` textures are not cached unless a `key` is provided
|
|
56
|
+
*/
|
|
57
|
+
key?: string | null;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
/**
|
|
@@ -85,9 +90,16 @@ export class ImageTexture extends Texture {
|
|
|
85
90
|
data: null,
|
|
86
91
|
};
|
|
87
92
|
}
|
|
88
|
-
|
|
93
|
+
|
|
94
|
+
if (typeof src !== 'string') {
|
|
95
|
+
if (src instanceof ImageData) {
|
|
96
|
+
return {
|
|
97
|
+
data: src,
|
|
98
|
+
premultiplyAlpha,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
89
101
|
return {
|
|
90
|
-
data: src,
|
|
102
|
+
data: src(),
|
|
91
103
|
premultiplyAlpha,
|
|
92
104
|
};
|
|
93
105
|
}
|
|
@@ -97,13 +109,16 @@ export class ImageTexture extends Texture {
|
|
|
97
109
|
return loadCompressedTexture(src);
|
|
98
110
|
}
|
|
99
111
|
|
|
112
|
+
// Convert relative URL to absolute URL
|
|
113
|
+
const absoluteSrc = convertUrlToAbsolute(src);
|
|
114
|
+
|
|
100
115
|
if (this.txManager.imageWorkerManager) {
|
|
101
116
|
return await this.txManager.imageWorkerManager.getImage(
|
|
102
|
-
|
|
117
|
+
absoluteSrc,
|
|
103
118
|
premultiplyAlpha,
|
|
104
119
|
);
|
|
105
120
|
} else if (this.txManager.hasCreateImageBitmap) {
|
|
106
|
-
const response = await fetch(
|
|
121
|
+
const response = await fetch(absoluteSrc);
|
|
107
122
|
const blob = await response.blob();
|
|
108
123
|
const hasAlphaChannel =
|
|
109
124
|
premultiplyAlpha ?? this.hasAlphaChannel(blob.type);
|
|
@@ -120,7 +135,7 @@ export class ImageTexture extends Texture {
|
|
|
120
135
|
if (!(src.substr(0, 5) === 'data:')) {
|
|
121
136
|
img.crossOrigin = 'Anonymous';
|
|
122
137
|
}
|
|
123
|
-
img.src =
|
|
138
|
+
img.src = absoluteSrc;
|
|
124
139
|
await new Promise<void>((resolve, reject) => {
|
|
125
140
|
img.onload = () => resolve();
|
|
126
141
|
img.onerror = () => reject(new Error(`Failed to load image`));
|
|
@@ -137,11 +152,12 @@ export class ImageTexture extends Texture {
|
|
|
137
152
|
|
|
138
153
|
static override makeCacheKey(props: ImageTextureProps): string | false {
|
|
139
154
|
const resolvedProps = ImageTexture.resolveDefaults(props);
|
|
140
|
-
//
|
|
141
|
-
|
|
155
|
+
// Only cache key-able textures; prioritise key
|
|
156
|
+
const key = resolvedProps.key || resolvedProps.src;
|
|
157
|
+
if (typeof key !== 'string') {
|
|
142
158
|
return false;
|
|
143
159
|
}
|
|
144
|
-
return `ImageTexture,${
|
|
160
|
+
return `ImageTexture,${key},${resolvedProps.premultiplyAlpha ?? 'true'}`;
|
|
145
161
|
}
|
|
146
162
|
|
|
147
163
|
static override resolveDefaults(
|
|
@@ -150,6 +166,7 @@ export class ImageTexture extends Texture {
|
|
|
150
166
|
return {
|
|
151
167
|
src: props.src ?? '',
|
|
152
168
|
premultiplyAlpha: props.premultiplyAlpha ?? true, // null,
|
|
169
|
+
key: props.key ?? null,
|
|
153
170
|
};
|
|
154
171
|
}
|
|
155
172
|
|