@eva/plugin-renderer-scene-capture 2.1.0-beta.14
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 +30 -0
- package/dist/EVA.plugin.renderer.scene.capture.js +518 -0
- package/dist/EVA.plugin.renderer.scene.capture.min.js +1 -0
- package/dist/plugin-renderer-scene-capture.cjs.js +550 -0
- package/dist/plugin-renderer-scene-capture.cjs.prod.js +1 -0
- package/dist/plugin-renderer-scene-capture.d.ts +152 -0
- package/dist/plugin-renderer-scene-capture.esm.js +542 -0
- package/index.js +7 -0
- package/package.json +25 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Component } from '@eva/eva.js';
|
|
2
|
+
import type { ComponentChanged } from '@eva/eva.js';
|
|
3
|
+
import { ContainerManager } from '@eva/plugin-renderer';
|
|
4
|
+
import { Renderer } from '@eva/plugin-renderer';
|
|
5
|
+
import { RendererSystem } from '@eva/plugin-renderer';
|
|
6
|
+
import { Texture } from 'pixi.js';
|
|
7
|
+
import type { UpdateParams } from '@eva/eva.js';
|
|
8
|
+
|
|
9
|
+
/** Get a live Pixi Texture produced by a SceneCapture `output` key. */
|
|
10
|
+
export declare function getSceneCaptureTexture(output: string): Texture | undefined;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Captures named live Eva renderer containers into an offscreen Pixi RenderTexture.
|
|
14
|
+
*
|
|
15
|
+
* Requires SceneCaptureSystem plus RendererSystem from @eva/plugin-renderer. Source
|
|
16
|
+
* names are resolved at capture time, so late-created reflection proxy entities work
|
|
17
|
+
* without rebinding. The component only stores serializable configuration; the Pixi
|
|
18
|
+
* target and presentation lifecycle are owned by SceneCaptureSystem.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* reflectionRoot.addComponent(new SceneCapture({
|
|
23
|
+
* sourceNames: ['building-reflection', 'actor-reflection'],
|
|
24
|
+
* output: 'rain-reflection',
|
|
25
|
+
* width: 750,
|
|
26
|
+
* height: 420,
|
|
27
|
+
* resolutionScale: 0.5,
|
|
28
|
+
* updateMode: 'always',
|
|
29
|
+
* clearAlpha: 0,
|
|
30
|
+
* }));
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare class SceneCapture extends Component<SceneCaptureParams> {
|
|
34
|
+
static componentName: string;
|
|
35
|
+
sourceNames: string[];
|
|
36
|
+
output: string;
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
resolutionScale: number;
|
|
40
|
+
updateMode: SceneCaptureUpdateMode;
|
|
41
|
+
clearColor: number;
|
|
42
|
+
clearAlpha: number;
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
/** Monotonic runtime invalidation sequence consumed by SceneCaptureSystem. */
|
|
45
|
+
captureRevision: number;
|
|
46
|
+
init(params?: SceneCaptureParams): void;
|
|
47
|
+
/** Mark this target dirty; manual targets capture on the next presentation frame. */
|
|
48
|
+
requestCapture(): void;
|
|
49
|
+
private normalizeUpdateMode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Serializable configuration for a live SceneCapture target. */
|
|
53
|
+
export declare interface SceneCaptureParams {
|
|
54
|
+
/** Names of live GameObjects whose renderer containers are captured in order. */
|
|
55
|
+
sourceNames?: string[];
|
|
56
|
+
/** Public registry key used to obtain the resulting Pixi Texture. */
|
|
57
|
+
output?: string;
|
|
58
|
+
/** Logical target width in Eva design units. */
|
|
59
|
+
width?: number;
|
|
60
|
+
/** Logical target height in Eva design units. */
|
|
61
|
+
height?: number;
|
|
62
|
+
/** Pixi RenderTexture resolution multiplier. Defaults to 1. */
|
|
63
|
+
resolutionScale?: number;
|
|
64
|
+
/** Capture policy. `manual` runs only after requestCapture(). */
|
|
65
|
+
updateMode?: SceneCaptureUpdateMode;
|
|
66
|
+
/** RGB clear color expressed as 0xRRGGBB. */
|
|
67
|
+
clearColor?: number;
|
|
68
|
+
/** Clear alpha in the inclusive 0..1 range. */
|
|
69
|
+
clearAlpha?: number;
|
|
70
|
+
/** Stops writes while preserving the last captured texture. */
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Renders named live Eva containers into transparent offscreen Pixi textures.
|
|
76
|
+
*
|
|
77
|
+
* The system registers a presentation participant scoped to the root RendererSystem
|
|
78
|
+
* application. This makes each capture finish before the application performs its
|
|
79
|
+
* final main-stage render. It never reparents source containers and explicitly
|
|
80
|
+
* rejects the main Pixi stage as a capture source.
|
|
81
|
+
*/
|
|
82
|
+
export declare class SceneCaptureSystem extends Renderer {
|
|
83
|
+
static systemName: string;
|
|
84
|
+
name: string;
|
|
85
|
+
/** Let a newly-added target become available during a zero-simulation presentation frame. */
|
|
86
|
+
readonly presentationAddFlushEnabled = true;
|
|
87
|
+
private readonly records;
|
|
88
|
+
private readonly outputs;
|
|
89
|
+
private readonly emptyCaptureRoot;
|
|
90
|
+
private readonly participantId;
|
|
91
|
+
private presentationHandle?;
|
|
92
|
+
private destroyed;
|
|
93
|
+
renderSystem: RendererSystem;
|
|
94
|
+
containerManager: ContainerManager;
|
|
95
|
+
init(): void;
|
|
96
|
+
update(time?: UpdateParams): void;
|
|
97
|
+
/** Compatibility fallback for hosts that have not yet exposed presentation participants. */
|
|
98
|
+
lateUpdate(): void;
|
|
99
|
+
componentChanged(changed: ComponentChanged): void;
|
|
100
|
+
/** Return this system's texture for an output key without relying on global state. */
|
|
101
|
+
getTexture(output: string): Texture | undefined;
|
|
102
|
+
onDestroy(): void;
|
|
103
|
+
private addRecord;
|
|
104
|
+
private createTexture;
|
|
105
|
+
private widthOf;
|
|
106
|
+
private heightOf;
|
|
107
|
+
private resolutionOf;
|
|
108
|
+
private resizeRecord;
|
|
109
|
+
private updateOutput;
|
|
110
|
+
private bindOutput;
|
|
111
|
+
private unbindOutput;
|
|
112
|
+
private disposeRecord;
|
|
113
|
+
private ensurePresentationParticipant;
|
|
114
|
+
private hasLivePresentationParticipant;
|
|
115
|
+
/** Executes synchronously inside the renderer's presentation phase. */
|
|
116
|
+
private captureForApplication;
|
|
117
|
+
private shouldCapture;
|
|
118
|
+
private captureRecord;
|
|
119
|
+
private resolveSourceContainers;
|
|
120
|
+
private toClearColor;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A registry entry is valid until its owning SceneCapture record is disposed. */
|
|
124
|
+
export declare interface SceneCaptureTextureEntry {
|
|
125
|
+
readonly output: string;
|
|
126
|
+
readonly owner: object;
|
|
127
|
+
readonly texture: Texture;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Public, owner-aware lookup table for live SceneCapture outputs.
|
|
132
|
+
*
|
|
133
|
+
* A later capture may intentionally replace the same key. Disposal is guarded by
|
|
134
|
+
* owner identity so removing an older component cannot erase the newer texture.
|
|
135
|
+
*/
|
|
136
|
+
export declare class SceneCaptureTextureRegistry {
|
|
137
|
+
private readonly entries;
|
|
138
|
+
get(output: string): Texture | undefined;
|
|
139
|
+
getEntry(output: string): SceneCaptureTextureEntry | undefined;
|
|
140
|
+
has(output: string): boolean;
|
|
141
|
+
set(output: string, owner: object, texture: Texture): void;
|
|
142
|
+
delete(output: string, owner?: object): boolean;
|
|
143
|
+
private normalize;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Shared lookup table for shader/filter consumers that do not own the capture system. */
|
|
147
|
+
export declare const sceneCaptureTextureRegistry: SceneCaptureTextureRegistry;
|
|
148
|
+
|
|
149
|
+
/** Controls when a SceneCapture writes its output texture. */
|
|
150
|
+
export declare type SceneCaptureUpdateMode = 'always' | 'onDirty' | 'manual';
|
|
151
|
+
|
|
152
|
+
export { }
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { Component, OBSERVER_TYPE, decorators } from '@eva/eva.js';
|
|
2
|
+
import { type } from '@eva/inspector-decorator';
|
|
3
|
+
import { Renderer, RendererSystem } from '@eva/plugin-renderer';
|
|
4
|
+
import { Container, RenderTexture } from 'pixi.js';
|
|
5
|
+
|
|
6
|
+
/******************************************************************************
|
|
7
|
+
Copyright (c) Microsoft Corporation.
|
|
8
|
+
|
|
9
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
10
|
+
purpose with or without fee is hereby granted.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
13
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
14
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
15
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
16
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
17
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
18
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
19
|
+
***************************************************************************** */
|
|
20
|
+
|
|
21
|
+
function __decorate(decorators, target, key, desc) {
|
|
22
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
23
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
24
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
25
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function __metadata(metadataKey, metadataValue) {
|
|
29
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
33
|
+
var e = new Error(message);
|
|
34
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const finitePositive$1 = (value, fallback) => {
|
|
38
|
+
const numberValue = Number(value);
|
|
39
|
+
return Number.isFinite(numberValue) && numberValue > 0 ? numberValue : fallback;
|
|
40
|
+
};
|
|
41
|
+
const clamp = (value, min, max, fallback) => {
|
|
42
|
+
const numberValue = Number(value);
|
|
43
|
+
if (!Number.isFinite(numberValue))
|
|
44
|
+
return fallback;
|
|
45
|
+
return Math.min(max, Math.max(min, numberValue));
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Captures named live Eva renderer containers into an offscreen Pixi RenderTexture.
|
|
49
|
+
*
|
|
50
|
+
* Requires SceneCaptureSystem plus RendererSystem from @eva/plugin-renderer. Source
|
|
51
|
+
* names are resolved at capture time, so late-created reflection proxy entities work
|
|
52
|
+
* without rebinding. The component only stores serializable configuration; the Pixi
|
|
53
|
+
* target and presentation lifecycle are owned by SceneCaptureSystem.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* reflectionRoot.addComponent(new SceneCapture({
|
|
58
|
+
* sourceNames: ['building-reflection', 'actor-reflection'],
|
|
59
|
+
* output: 'rain-reflection',
|
|
60
|
+
* width: 750,
|
|
61
|
+
* height: 420,
|
|
62
|
+
* resolutionScale: 0.5,
|
|
63
|
+
* updateMode: 'always',
|
|
64
|
+
* clearAlpha: 0,
|
|
65
|
+
* }));
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
class SceneCapture extends Component {
|
|
69
|
+
constructor() {
|
|
70
|
+
super(...arguments);
|
|
71
|
+
this.sourceNames = [];
|
|
72
|
+
this.output = '';
|
|
73
|
+
this.width = 256;
|
|
74
|
+
this.height = 256;
|
|
75
|
+
this.resolutionScale = 1;
|
|
76
|
+
this.updateMode = 'always';
|
|
77
|
+
this.clearColor = 0x000000;
|
|
78
|
+
this.clearAlpha = 0;
|
|
79
|
+
this.enabled = true;
|
|
80
|
+
/** Monotonic runtime invalidation sequence consumed by SceneCaptureSystem. */
|
|
81
|
+
this.captureRevision = 0;
|
|
82
|
+
}
|
|
83
|
+
init(params) {
|
|
84
|
+
if (params)
|
|
85
|
+
Object.assign(this, params);
|
|
86
|
+
this.sourceNames = Array.isArray(this.sourceNames)
|
|
87
|
+
? this.sourceNames.filter(name => typeof name === 'string' && name.trim().length > 0)
|
|
88
|
+
: [];
|
|
89
|
+
this.output = typeof this.output === 'string' ? this.output.trim() : '';
|
|
90
|
+
this.width = finitePositive$1(this.width, 256);
|
|
91
|
+
this.height = finitePositive$1(this.height, 256);
|
|
92
|
+
this.resolutionScale = finitePositive$1(this.resolutionScale, 1);
|
|
93
|
+
this.updateMode = this.normalizeUpdateMode(this.updateMode);
|
|
94
|
+
this.clearColor = Math.max(0, Math.min(0xffffff, Number(this.clearColor) || 0)) >>> 0;
|
|
95
|
+
this.clearAlpha = clamp(this.clearAlpha, 0, 1, 0);
|
|
96
|
+
this.enabled = this.enabled !== false;
|
|
97
|
+
}
|
|
98
|
+
/** Mark this target dirty; manual targets capture on the next presentation frame. */
|
|
99
|
+
requestCapture() {
|
|
100
|
+
this.captureRevision++;
|
|
101
|
+
this.emit('captureRequested', { revision: this.captureRevision });
|
|
102
|
+
}
|
|
103
|
+
normalizeUpdateMode(value) {
|
|
104
|
+
return value === 'onDirty' || value === 'manual' ? value : 'always';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
SceneCapture.componentName = 'SceneCapture';
|
|
108
|
+
__decorate([
|
|
109
|
+
type('array'),
|
|
110
|
+
__metadata("design:type", Array)
|
|
111
|
+
], SceneCapture.prototype, "sourceNames", void 0);
|
|
112
|
+
__decorate([
|
|
113
|
+
type('string'),
|
|
114
|
+
__metadata("design:type", String)
|
|
115
|
+
], SceneCapture.prototype, "output", void 0);
|
|
116
|
+
__decorate([
|
|
117
|
+
type('number'),
|
|
118
|
+
__metadata("design:type", Number)
|
|
119
|
+
], SceneCapture.prototype, "width", void 0);
|
|
120
|
+
__decorate([
|
|
121
|
+
type('number'),
|
|
122
|
+
__metadata("design:type", Number)
|
|
123
|
+
], SceneCapture.prototype, "height", void 0);
|
|
124
|
+
__decorate([
|
|
125
|
+
type('number'),
|
|
126
|
+
__metadata("design:type", Number)
|
|
127
|
+
], SceneCapture.prototype, "resolutionScale", void 0);
|
|
128
|
+
__decorate([
|
|
129
|
+
type('string'),
|
|
130
|
+
__metadata("design:type", String)
|
|
131
|
+
], SceneCapture.prototype, "updateMode", void 0);
|
|
132
|
+
__decorate([
|
|
133
|
+
type('number'),
|
|
134
|
+
__metadata("design:type", Number)
|
|
135
|
+
], SceneCapture.prototype, "clearColor", void 0);
|
|
136
|
+
__decorate([
|
|
137
|
+
type('number'),
|
|
138
|
+
__metadata("design:type", Number)
|
|
139
|
+
], SceneCapture.prototype, "clearAlpha", void 0);
|
|
140
|
+
__decorate([
|
|
141
|
+
type('boolean'),
|
|
142
|
+
__metadata("design:type", Boolean)
|
|
143
|
+
], SceneCapture.prototype, "enabled", void 0);
|
|
144
|
+
__decorate([
|
|
145
|
+
type('number'),
|
|
146
|
+
__metadata("design:type", Number)
|
|
147
|
+
], SceneCapture.prototype, "captureRevision", void 0);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Public, owner-aware lookup table for live SceneCapture outputs.
|
|
151
|
+
*
|
|
152
|
+
* A later capture may intentionally replace the same key. Disposal is guarded by
|
|
153
|
+
* owner identity so removing an older component cannot erase the newer texture.
|
|
154
|
+
*/
|
|
155
|
+
class SceneCaptureTextureRegistry {
|
|
156
|
+
constructor() {
|
|
157
|
+
this.entries = new Map();
|
|
158
|
+
}
|
|
159
|
+
get(output) {
|
|
160
|
+
var _a;
|
|
161
|
+
return (_a = this.entries.get(this.normalize(output))) === null || _a === void 0 ? void 0 : _a.texture;
|
|
162
|
+
}
|
|
163
|
+
getEntry(output) {
|
|
164
|
+
return this.entries.get(this.normalize(output));
|
|
165
|
+
}
|
|
166
|
+
has(output) {
|
|
167
|
+
return this.entries.has(this.normalize(output));
|
|
168
|
+
}
|
|
169
|
+
set(output, owner, texture) {
|
|
170
|
+
const key = this.normalize(output);
|
|
171
|
+
if (!key || !owner || !texture)
|
|
172
|
+
return;
|
|
173
|
+
this.entries.set(key, Object.freeze({ output: key, owner, texture }));
|
|
174
|
+
}
|
|
175
|
+
delete(output, owner) {
|
|
176
|
+
const key = this.normalize(output);
|
|
177
|
+
const entry = this.entries.get(key);
|
|
178
|
+
if (!entry || (owner && entry.owner !== owner))
|
|
179
|
+
return false;
|
|
180
|
+
return this.entries.delete(key);
|
|
181
|
+
}
|
|
182
|
+
normalize(output) {
|
|
183
|
+
return typeof output === 'string' ? output.trim() : '';
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Shared lookup table for shader/filter consumers that do not own the capture system. */
|
|
187
|
+
const sceneCaptureTextureRegistry = new SceneCaptureTextureRegistry();
|
|
188
|
+
/** Get a live Pixi Texture produced by a SceneCapture `output` key. */
|
|
189
|
+
function getSceneCaptureTexture(output) {
|
|
190
|
+
return sceneCaptureTextureRegistry.get(output);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let nextSystemId = 0;
|
|
194
|
+
const finitePositive = (value, fallback) => {
|
|
195
|
+
const numberValue = Number(value);
|
|
196
|
+
return Number.isFinite(numberValue) && numberValue > 0 ? numberValue : fallback;
|
|
197
|
+
};
|
|
198
|
+
const clampAlpha = (value) => {
|
|
199
|
+
const numberValue = Number(value);
|
|
200
|
+
if (!Number.isFinite(numberValue))
|
|
201
|
+
return 0;
|
|
202
|
+
return Math.max(0, Math.min(1, numberValue));
|
|
203
|
+
};
|
|
204
|
+
const normalizedOutput = (value) => (typeof value === 'string' ? value.trim() : '');
|
|
205
|
+
/**
|
|
206
|
+
* Renders named live Eva containers into transparent offscreen Pixi textures.
|
|
207
|
+
*
|
|
208
|
+
* The system registers a presentation participant scoped to the root RendererSystem
|
|
209
|
+
* application. This makes each capture finish before the application performs its
|
|
210
|
+
* final main-stage render. It never reparents source containers and explicitly
|
|
211
|
+
* rejects the main Pixi stage as a capture source.
|
|
212
|
+
*/
|
|
213
|
+
let SceneCaptureSystem = class SceneCaptureSystem extends Renderer {
|
|
214
|
+
constructor() {
|
|
215
|
+
super(...arguments);
|
|
216
|
+
this.name = 'SceneCapture';
|
|
217
|
+
/** Let a newly-added target become available during a zero-simulation presentation frame. */
|
|
218
|
+
this.presentationAddFlushEnabled = true;
|
|
219
|
+
this.records = new Map();
|
|
220
|
+
this.outputs = new Map();
|
|
221
|
+
this.emptyCaptureRoot = new Container();
|
|
222
|
+
this.participantId = `scene-capture:${++nextSystemId}`;
|
|
223
|
+
this.destroyed = false;
|
|
224
|
+
}
|
|
225
|
+
init() {
|
|
226
|
+
this.renderSystem = this.game.getSystem(RendererSystem);
|
|
227
|
+
if (!this.renderSystem) {
|
|
228
|
+
throw new Error('[SceneCapture] RendererSystem must be registered before SceneCaptureSystem.');
|
|
229
|
+
}
|
|
230
|
+
this.renderSystem.rendererManager.register(this);
|
|
231
|
+
this.ensurePresentationParticipant();
|
|
232
|
+
}
|
|
233
|
+
update(time) {
|
|
234
|
+
super.update(time);
|
|
235
|
+
this.ensurePresentationParticipant();
|
|
236
|
+
}
|
|
237
|
+
/** Compatibility fallback for hosts that have not yet exposed presentation participants. */
|
|
238
|
+
lateUpdate() {
|
|
239
|
+
var _a;
|
|
240
|
+
if (this.hasLivePresentationParticipant())
|
|
241
|
+
return;
|
|
242
|
+
const application = (_a = this.renderSystem) === null || _a === void 0 ? void 0 : _a.application;
|
|
243
|
+
if (application)
|
|
244
|
+
this.captureForApplication(application);
|
|
245
|
+
}
|
|
246
|
+
componentChanged(changed) {
|
|
247
|
+
if (changed.componentName !== 'SceneCapture')
|
|
248
|
+
return;
|
|
249
|
+
const component = changed.component;
|
|
250
|
+
const gameObjectId = changed.gameObject.id;
|
|
251
|
+
if (changed.type === OBSERVER_TYPE.ADD) {
|
|
252
|
+
this.addRecord(gameObjectId, component);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (changed.type === OBSERVER_TYPE.REMOVE) {
|
|
256
|
+
const record = this.records.get(gameObjectId);
|
|
257
|
+
if (record)
|
|
258
|
+
this.disposeRecord(record);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const record = this.records.get(gameObjectId);
|
|
262
|
+
if (!record)
|
|
263
|
+
return;
|
|
264
|
+
record.component = component;
|
|
265
|
+
this.resizeRecord(record);
|
|
266
|
+
this.updateOutput(record);
|
|
267
|
+
// `manual` remains explicitly controlled by requestCapture(). `onDirty`
|
|
268
|
+
// invalidates for every declarative configuration/source change.
|
|
269
|
+
if (component.updateMode !== 'manual')
|
|
270
|
+
record.dirty = true;
|
|
271
|
+
if (component.captureRevision !== record.lastCaptureRevision)
|
|
272
|
+
record.dirty = true;
|
|
273
|
+
}
|
|
274
|
+
/** Return this system's texture for an output key without relying on global state. */
|
|
275
|
+
getTexture(output) {
|
|
276
|
+
var _a;
|
|
277
|
+
return (_a = this.outputs.get(normalizedOutput(output))) === null || _a === void 0 ? void 0 : _a.texture;
|
|
278
|
+
}
|
|
279
|
+
onDestroy() {
|
|
280
|
+
var _a;
|
|
281
|
+
this.destroyed = true;
|
|
282
|
+
(_a = this.presentationHandle) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
283
|
+
this.presentationHandle = undefined;
|
|
284
|
+
for (const record of Array.from(this.records.values()))
|
|
285
|
+
this.disposeRecord(record);
|
|
286
|
+
try {
|
|
287
|
+
this.emptyCaptureRoot.destroy({ children: false });
|
|
288
|
+
}
|
|
289
|
+
catch (_b) {
|
|
290
|
+
// Renderer-adapter mocks and already-destroyed Pixi roots can reject cleanup.
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
addRecord(gameObjectId, component) {
|
|
294
|
+
const existing = this.records.get(gameObjectId);
|
|
295
|
+
if (existing)
|
|
296
|
+
this.disposeRecord(existing);
|
|
297
|
+
const record = {};
|
|
298
|
+
const owner = record;
|
|
299
|
+
Object.assign(record, {
|
|
300
|
+
owner,
|
|
301
|
+
gameObjectId,
|
|
302
|
+
component,
|
|
303
|
+
texture: this.createTexture(component),
|
|
304
|
+
output: normalizedOutput(component.output),
|
|
305
|
+
width: this.widthOf(component),
|
|
306
|
+
height: this.heightOf(component),
|
|
307
|
+
resolutionScale: this.resolutionOf(component),
|
|
308
|
+
dirty: component.updateMode !== 'manual' || component.captureRevision > 0,
|
|
309
|
+
lastCaptureRevision: 0,
|
|
310
|
+
disposed: false,
|
|
311
|
+
onCaptureRequested: () => {
|
|
312
|
+
if (!record.disposed)
|
|
313
|
+
record.dirty = true;
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
component.on('captureRequested', record.onCaptureRequested);
|
|
317
|
+
this.records.set(gameObjectId, record);
|
|
318
|
+
this.bindOutput(record);
|
|
319
|
+
}
|
|
320
|
+
createTexture(component) {
|
|
321
|
+
return RenderTexture.create({
|
|
322
|
+
width: this.widthOf(component),
|
|
323
|
+
height: this.heightOf(component),
|
|
324
|
+
resolution: this.resolutionOf(component),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
widthOf(component) {
|
|
328
|
+
return finitePositive(component.width, 256);
|
|
329
|
+
}
|
|
330
|
+
heightOf(component) {
|
|
331
|
+
return finitePositive(component.height, 256);
|
|
332
|
+
}
|
|
333
|
+
resolutionOf(component) {
|
|
334
|
+
return finitePositive(component.resolutionScale, 1);
|
|
335
|
+
}
|
|
336
|
+
resizeRecord(record) {
|
|
337
|
+
const width = this.widthOf(record.component);
|
|
338
|
+
const height = this.heightOf(record.component);
|
|
339
|
+
const resolutionScale = this.resolutionOf(record.component);
|
|
340
|
+
if (record.width === width && record.height === height && record.resolutionScale === resolutionScale)
|
|
341
|
+
return;
|
|
342
|
+
record.width = width;
|
|
343
|
+
record.height = height;
|
|
344
|
+
record.resolutionScale = resolutionScale;
|
|
345
|
+
const texture = record.texture;
|
|
346
|
+
if (typeof texture.resize === 'function') {
|
|
347
|
+
texture.resize(width, height, resolutionScale);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const previous = record.texture;
|
|
351
|
+
record.texture = this.createTexture(record.component);
|
|
352
|
+
this.bindOutput(record);
|
|
353
|
+
try {
|
|
354
|
+
previous.destroy(true);
|
|
355
|
+
}
|
|
356
|
+
catch (_a) {
|
|
357
|
+
// The old output is already detached from registry ownership.
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
updateOutput(record) {
|
|
361
|
+
const nextOutput = normalizedOutput(record.component.output);
|
|
362
|
+
if (record.output === nextOutput)
|
|
363
|
+
return;
|
|
364
|
+
this.unbindOutput(record);
|
|
365
|
+
record.output = nextOutput;
|
|
366
|
+
this.bindOutput(record);
|
|
367
|
+
}
|
|
368
|
+
bindOutput(record) {
|
|
369
|
+
if (!record.output)
|
|
370
|
+
return;
|
|
371
|
+
this.outputs.set(record.output, record);
|
|
372
|
+
sceneCaptureTextureRegistry.set(record.output, record.owner, record.texture);
|
|
373
|
+
}
|
|
374
|
+
unbindOutput(record) {
|
|
375
|
+
if (!record.output)
|
|
376
|
+
return;
|
|
377
|
+
if (this.outputs.get(record.output) === record)
|
|
378
|
+
this.outputs.delete(record.output);
|
|
379
|
+
sceneCaptureTextureRegistry.delete(record.output, record.owner);
|
|
380
|
+
}
|
|
381
|
+
disposeRecord(record) {
|
|
382
|
+
if (record.disposed)
|
|
383
|
+
return;
|
|
384
|
+
record.disposed = true;
|
|
385
|
+
this.records.delete(record.gameObjectId);
|
|
386
|
+
this.unbindOutput(record);
|
|
387
|
+
try {
|
|
388
|
+
record.component.off('captureRequested', record.onCaptureRequested);
|
|
389
|
+
}
|
|
390
|
+
catch (_a) {
|
|
391
|
+
// Component could have been fully torn down by a synchronous game destroy.
|
|
392
|
+
}
|
|
393
|
+
try {
|
|
394
|
+
record.texture.destroy(true);
|
|
395
|
+
}
|
|
396
|
+
catch (_b) {
|
|
397
|
+
// A renderer teardown may have destroyed its GPU texture first.
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
ensurePresentationParticipant() {
|
|
401
|
+
var _a;
|
|
402
|
+
if (this.destroyed || this.hasLivePresentationParticipant())
|
|
403
|
+
return;
|
|
404
|
+
const renderer = this.renderSystem;
|
|
405
|
+
if (!(renderer === null || renderer === void 0 ? void 0 : renderer.application) || typeof renderer.registerPresentationParticipant !== 'function')
|
|
406
|
+
return;
|
|
407
|
+
const applicationId = (_a = renderer.getPresentationApplicationId) === null || _a === void 0 ? void 0 : _a.call(renderer, renderer.application);
|
|
408
|
+
let handle;
|
|
409
|
+
handle = renderer.registerPresentationParticipant({
|
|
410
|
+
id: this.participantId,
|
|
411
|
+
applicationId,
|
|
412
|
+
build: () => ({
|
|
413
|
+
writes: Array.from(this.outputs.keys(), output => `scene-capture:${output}`),
|
|
414
|
+
}),
|
|
415
|
+
submit: (context) => {
|
|
416
|
+
this.captureForApplication(context.application);
|
|
417
|
+
},
|
|
418
|
+
dispose: () => {
|
|
419
|
+
if (this.presentationHandle === handle)
|
|
420
|
+
this.presentationHandle = undefined;
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
this.presentationHandle = handle;
|
|
424
|
+
}
|
|
425
|
+
hasLivePresentationParticipant() {
|
|
426
|
+
return Boolean(this.presentationHandle && !this.presentationHandle.disposed);
|
|
427
|
+
}
|
|
428
|
+
/** Executes synchronously inside the renderer's presentation phase. */
|
|
429
|
+
captureForApplication(application) {
|
|
430
|
+
const renderer = application === null || application === void 0 ? void 0 : application.renderer;
|
|
431
|
+
if (!(renderer === null || renderer === void 0 ? void 0 : renderer.render))
|
|
432
|
+
return;
|
|
433
|
+
for (const record of this.records.values()) {
|
|
434
|
+
if (!this.shouldCapture(record))
|
|
435
|
+
continue;
|
|
436
|
+
if (!this.captureRecord(renderer, application, record))
|
|
437
|
+
continue;
|
|
438
|
+
record.dirty = false;
|
|
439
|
+
record.lastCaptureRevision = record.component.captureRevision;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
shouldCapture(record) {
|
|
443
|
+
const component = record.component;
|
|
444
|
+
if (record.disposed || component.enabled === false)
|
|
445
|
+
return false;
|
|
446
|
+
if (component.updateMode === 'always')
|
|
447
|
+
return true;
|
|
448
|
+
if (component.captureRevision !== record.lastCaptureRevision)
|
|
449
|
+
record.dirty = true;
|
|
450
|
+
return record.dirty;
|
|
451
|
+
}
|
|
452
|
+
captureRecord(renderer, application, record) {
|
|
453
|
+
const sources = this.resolveSourceContainers(record.component, application);
|
|
454
|
+
const clearColor = this.toClearColor(record.component);
|
|
455
|
+
try {
|
|
456
|
+
if (sources.length === 0) {
|
|
457
|
+
renderer.render({
|
|
458
|
+
container: this.emptyCaptureRoot,
|
|
459
|
+
target: record.texture,
|
|
460
|
+
clear: true,
|
|
461
|
+
clearColor,
|
|
462
|
+
});
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
for (let index = 0; index < sources.length; index++) {
|
|
466
|
+
const source = sources[index];
|
|
467
|
+
const options = {
|
|
468
|
+
container: source,
|
|
469
|
+
target: record.texture,
|
|
470
|
+
clear: index === 0,
|
|
471
|
+
};
|
|
472
|
+
if (index === 0)
|
|
473
|
+
options.clearColor = clearColor;
|
|
474
|
+
// Passing the current world transform preserves root/parent placement
|
|
475
|
+
// without reparenting a live container out of the main stage.
|
|
476
|
+
if (source.worldTransform)
|
|
477
|
+
options.transform = source.worldTransform;
|
|
478
|
+
renderer.render(options);
|
|
479
|
+
}
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
catch (_a) {
|
|
483
|
+
// Keep the record dirty so a transient WebGL/WebGPU target error retries
|
|
484
|
+
// during a later presentation frame instead of freezing a stale reflection.
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
resolveSourceContainers(component, application) {
|
|
489
|
+
var _a;
|
|
490
|
+
const sources = [];
|
|
491
|
+
const sourceNames = Array.isArray(component.sourceNames) ? component.sourceNames : [];
|
|
492
|
+
const game = this.game;
|
|
493
|
+
for (const sourceName of sourceNames) {
|
|
494
|
+
if (!sourceName || typeof (game === null || game === void 0 ? void 0 : game.findAllByName) !== 'function')
|
|
495
|
+
continue;
|
|
496
|
+
const gameObjects = game.findAllByName(sourceName);
|
|
497
|
+
for (const gameObject of gameObjects || []) {
|
|
498
|
+
if (!gameObject || gameObject.destroyed)
|
|
499
|
+
continue;
|
|
500
|
+
const container = (_a = this.containerManager) === null || _a === void 0 ? void 0 : _a.getContainer(gameObject.id);
|
|
501
|
+
if (!container ||
|
|
502
|
+
container.destroyed ||
|
|
503
|
+
container === application.stage ||
|
|
504
|
+
container === this.emptyCaptureRoot) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (!sources.includes(container))
|
|
508
|
+
sources.push(container);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return sources;
|
|
512
|
+
}
|
|
513
|
+
toClearColor(component) {
|
|
514
|
+
const color = (Number(component.clearColor) || 0) >>> 0;
|
|
515
|
+
return [
|
|
516
|
+
((color >>> 16) & 0xff) / 255,
|
|
517
|
+
((color >>> 8) & 0xff) / 255,
|
|
518
|
+
(color & 0xff) / 255,
|
|
519
|
+
clampAlpha(component.clearAlpha),
|
|
520
|
+
];
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
SceneCaptureSystem.systemName = 'SceneCapture';
|
|
524
|
+
SceneCaptureSystem = __decorate([
|
|
525
|
+
decorators.componentObserver({
|
|
526
|
+
SceneCapture: [
|
|
527
|
+
{ prop: ['sourceNames'], deep: true },
|
|
528
|
+
{ prop: ['output'], deep: false },
|
|
529
|
+
{ prop: ['width'], deep: false },
|
|
530
|
+
{ prop: ['height'], deep: false },
|
|
531
|
+
{ prop: ['resolutionScale'], deep: false },
|
|
532
|
+
{ prop: ['updateMode'], deep: false },
|
|
533
|
+
{ prop: ['clearColor'], deep: false },
|
|
534
|
+
{ prop: ['clearAlpha'], deep: false },
|
|
535
|
+
{ prop: ['enabled'], deep: false },
|
|
536
|
+
{ prop: ['captureRevision'], deep: false },
|
|
537
|
+
],
|
|
538
|
+
})
|
|
539
|
+
], SceneCaptureSystem);
|
|
540
|
+
var SceneCaptureSystem$1 = SceneCaptureSystem;
|
|
541
|
+
|
|
542
|
+
export { SceneCapture, SceneCaptureSystem$1 as SceneCaptureSystem, SceneCaptureTextureRegistry, getSceneCaptureTexture, sceneCaptureTextureRegistry };
|