-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Expand file tree
/
Copy pathselectionOutlineLayer.ts
More file actions
434 lines (373 loc) · 15.9 KB
/
Copy pathselectionOutlineLayer.ts
File metadata and controls
434 lines (373 loc) · 15.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
import { serialize, serializeAsColor3 } from "../Misc/decorators";
import { type Nullable } from "../types";
import { Scene } from "../scene";
import { type SubMesh } from "../Meshes/subMesh";
import { type AbstractMesh } from "../Meshes/abstractMesh";
import { type Mesh } from "../Meshes/mesh";
import { type Effect } from "../Materials/effect";
import { type Material } from "../Materials/material";
import { EffectLayer } from "./effectLayer";
import { Constants } from "../Engines/constants";
import { RegisterClass } from "../Misc/typeStore";
import { SerializationHelper } from "../Misc/decorators.serialization";
import { type IThinSelectionOutlineLayerOptions, ThinSelectionOutlineLayer } from "./thinSelectionOutlineLayer";
import { type Color3 } from "../Maths/math.color";
import "../Rendering/depthRendererSceneComponent";
declare module "../scene" {
// eslint-disable-next-line @typescript-eslint/naming-convention
export interface Scene {
/**
* Return a the first selection outline layer of the scene with a given name.
* @param name The name of the selection outline layer to look for.
* @returns The selection outline layer if found otherwise null.
*/
getSelectionOutlineLayerByName(name: string): Nullable<SelectionOutlineLayer>;
}
}
Scene.prototype.getSelectionOutlineLayerByName = function (name: string): Nullable<SelectionOutlineLayer> {
for (let index = 0; index < this.effectLayers?.length; index++) {
if (this.effectLayers[index].name === name && this.effectLayers[index].getEffectName() === SelectionOutlineLayer.EffectName) {
return (<any>this.effectLayers[index]) as SelectionOutlineLayer;
}
}
return null;
};
/**
* Selection outline layer options. This helps customizing the behaviour
* of the selection outline layer.
*/
export interface ISelectionOutlineLayerOptions extends IThinSelectionOutlineLayerOptions {
/**
* Enable MSAA by choosing the number of samples. Default: 1
*/
mainTextureSamples?: number;
}
/**
* The selection outline layer Helps adding a outline effect around a mesh.
*
* Once instantiated in a scene, simply use the addMesh or removeMesh method to add or remove
* outlined meshes to your scene.
*/
export class SelectionOutlineLayer extends EffectLayer {
/**
* Effect Name of the selection outline layer.
*/
public static get EffectName() {
return ThinSelectionOutlineLayer.EffectName;
}
/**
* The outline color (default (1, 0.5, 0))
*/
@serializeAsColor3()
public get outlineColor(): Color3 {
return this._thinEffectLayer.outlineColor;
}
public set outlineColor(value: Color3) {
this._thinEffectLayer.outlineColor = value;
}
/**
* The thickness of the edges (default: 2.0)
*/
@serialize()
public get outlineThickness(): number {
return this._thinEffectLayer.outlineThickness;
}
public set outlineThickness(value: number) {
this._thinEffectLayer.outlineThickness = value;
}
/**
* The strength of the occlusion effect (default: 0.8)
*/
@serialize()
public get occlusionStrength(): number {
return this._thinEffectLayer.occlusionStrength;
}
public set occlusionStrength(value: number) {
this._thinEffectLayer.occlusionStrength = value;
}
/**
* The occlusion threshold (default: 0.01)
*/
@serialize()
public get occlusionThreshold(): number {
return this._thinEffectLayer.occlusionThreshold;
}
public set occlusionThreshold(value: number) {
this._thinEffectLayer.occlusionThreshold = value;
}
@serialize("options")
private _options: Required<ISelectionOutlineLayerOptions>;
declare protected readonly _thinEffectLayer: ThinSelectionOutlineLayer;
/**
* Instantiates a new selection outline Layer and references it to the scene..
* @param name The name of the layer
* @param scene The scene to use the layer in
* @param options Sets of none mandatory options to use with the layer (see ISelectionOutlineLayerOptions for more information)
*/
public constructor(name: string, scene?: Scene, options?: Partial<ISelectionOutlineLayerOptions>) {
super(name, scene, options !== undefined ? !!options.forceGLSL : false, new ThinSelectionOutlineLayer(name, scene, options));
// Adapt options
this._options = {
mainTextureRatio: 1.0,
mainTextureFixedSize: 0,
alphaBlendingMode: Constants.ALPHA_COMBINE,
camera: null,
mainTextureSamples: 1,
renderingGroupId: -1,
mainTextureType: Constants.TEXTURETYPE_FLOAT,
mainTextureFormat: Constants.TEXTUREFORMAT_RG,
forceGLSL: false,
storeCameraSpaceZ: false,
outlineMethod: Constants.OUTLINELAYER_SAMPLING_TRIDIRECTIONAL,
...options,
};
// Fall back to a supported mask texture type if the device doesn't support rendering to float framebuffers
// or linear filtering of float textures (e.g. OES_texture_float_linear missing on some iOS versions)
if (this._options.mainTextureType === Constants.TEXTURETYPE_FLOAT && !(this._engine.getCaps().textureFloatRender && this._engine.getCaps().textureFloatLinearFiltering)) {
this._options.mainTextureType = Constants.TEXTURETYPE_HALF_FLOAT;
}
if (this._options.mainTextureType === Constants.TEXTURETYPE_HALF_FLOAT && !this._engine.getCaps().textureHalfFloatRender && !this._options.storeCameraSpaceZ) {
this._options.mainTextureType = Constants.TEXTURETYPE_UNSIGNED_BYTE;
}
// Initialize the layer
this._init(this._options);
// Do not render as long as no meshes have been added
this._shouldRender = false;
}
/**
* Checks if the layer is ready to render.
* When selections are active, this also lazily creates the depth renderer
* and checks that its depth map is ready.
* @returns true if the layer is ready
*/
public override isLayerReady(): boolean {
if (!super.isLayerReady()) {
return false;
}
if (this.shouldRender()) {
const depthRenderer = this._scene.enableDepthRenderer();
if (!depthRenderer.getDepthMap().isReadyForRendering()) {
return false;
}
}
return true;
}
/**
* Get the effect name of the layer.
* @returns The effect name
*/
public getEffectName(): string {
return SelectionOutlineLayer.EffectName;
}
protected override _numInternalDraws(): number {
return 1; // draw depth mask on main pass and outline on merge pass
}
/**
* Create the merge effect. This is the shader use to blit the information back
* to the main canvas at the end of the scene rendering.
* @returns The effect created
*/
protected _createMergeEffect(): Effect {
return this._thinEffectLayer._createMergeEffect();
}
/**
* Creates the render target textures and post processes used in the selection outline layer.
*/
protected _createTextureAndPostProcesses(): void {
this._textures = [];
this._thinEffectLayer.bindTexturesForCompose = (effect: Effect): void => {
effect.setTexture("maskSampler", this._mainTexture);
const depthRenderer = this._scene.enableDepthRenderer();
effect.setTexture("depthSampler", depthRenderer.getDepthMap());
const mainTextureDesiredSize = this._mainTextureDesiredSize;
this._thinEffectLayer.textureWidth = mainTextureDesiredSize.width;
this._thinEffectLayer.textureHeight = mainTextureDesiredSize.height;
};
this._thinEffectLayer._createTextureAndPostProcesses();
this._postProcesses = [];
this._mainTexture.samples = this._options.mainTextureSamples;
this._mainTexture.onAfterUnbindObservable.add(() => {
// glow layer and highlight layer both call this._scene.postProcessManager.directRender
// when you call this._scene.postProcessManager.directRender, it has 4 side effects:
// 1. binds the framebuffer
// 2. setAlphaMode(ALPHA_DISABLE)
// 3. setDepthBuffer(true)
// 4. setDepthWrite(true)
// glow layer and highlight layer are restore framebuffer and depends on other side effects
// but for now 3 and 4 are not needed to resolve the state management issue, so we just restore alpha mode
this._scene.getEngine().setAlphaMode(Constants.ALPHA_DISABLE);
});
}
/**
* Creates the main texture for the effect layer.
*/
protected override _createMainTexture(): void {
super._createMainTexture();
// set the render list for selective rendering
this._mainTexture.renderList = this._thinEffectLayer._selection;
}
/**
* @returns whether or not the layer needs stencil enabled during the mesh rendering.
*/
public needStencil(): boolean {
return this._thinEffectLayer.needStencil();
}
/**
* Checks for the readiness of the element composing the layer.
* @param subMesh the mesh to check for
* @param useInstances specify whether or not to use instances to render the mesh
* @returns true if ready otherwise, false
*/
public isReady(subMesh: SubMesh, useInstances: boolean): boolean {
return this._thinEffectLayer.isReady(subMesh, useInstances);
}
/**
* Implementation specific of rendering the generating effect on the main canvas.
* @param effect The effect used to render through
* @param renderIndex
*/
protected _internalRender(effect: Effect, renderIndex: number): void {
this._thinEffectLayer._internalCompose(effect, renderIndex);
}
/**
* @returns true if the layer contains information to display, otherwise false.
*/
public override shouldRender(): boolean {
return this._thinEffectLayer.shouldRender();
}
/**
* Returns true if the mesh should render, otherwise false.
* @param mesh The mesh to render
* @returns true if it should render otherwise false
*/
protected override _shouldRenderMesh(mesh: Mesh): boolean {
return this._thinEffectLayer._shouldRenderMesh(mesh);
}
/**
* Returns true if the mesh can be rendered, otherwise false.
* @param mesh The mesh to render
* @param material The material used on the mesh
* @returns true if it can be rendered otherwise false
*/
protected override _canRenderMesh(mesh: AbstractMesh, material: Material): boolean {
return this._thinEffectLayer._canRenderMesh(mesh, material);
}
/**
* Adds specific effects defines.
* @param defines The defines to add specifics to.
*/
protected override _addCustomEffectDefines(defines: string[]): void {
this._thinEffectLayer._addCustomEffectDefines(defines);
}
/**
* Sets the required values for both the emissive texture and and the main color.
* @param mesh
* @param subMesh
* @param material
*/
protected _setEmissiveTextureAndColor(mesh: Mesh, subMesh: SubMesh, material: Material): void {
this._thinEffectLayer._setEmissiveTextureAndColor(mesh, subMesh, material);
}
/**
* Determine if a given mesh will be highlighted by the current SelectionOutlineLayer
* @param mesh mesh to test
* @returns true if the mesh will be highlighted by the current SelectionOutlineLayer
*/
public override hasMesh(mesh: AbstractMesh): boolean {
return this._thinEffectLayer.hasMesh(mesh);
}
/**
* Remove all the meshes currently referenced in the selection outline layer
*/
public clearSelection(): void {
this._thinEffectLayer.clearSelection();
this._mainTexture.renderList = this._thinEffectLayer._selection; // update render list
}
/**
* Adds mesh or group of mesh to the current selection
*
* If a group of meshes is provided, they will outline as a single unit
* @param meshOrGroup Meshes to add to the selection
*/
public addSelection(meshOrGroup: AbstractMesh | AbstractMesh[]): void {
this._thinEffectLayer.addSelection(meshOrGroup);
}
/**
* Free any resources and references associated to a mesh.
* Internal use
* @param mesh The mesh to free.
* @internal
*/
public _disposeMesh(mesh: Mesh): void {
this._thinEffectLayer._disposeMesh(mesh);
}
/**
* Gets the class name of the effect layer
* @returns the string with the class name of the effect layer
*/
public override getClassName(): string {
return "SelectionOutlineLayer";
}
/**
* Serializes this SelectionOutline layer
* @returns a serialized SelectionOutline layer object
*/
public serialize(): any {
const serializationObject = SerializationHelper.Serialize(this);
serializationObject.customType = "BABYLON.SelectionOutlineLayer";
// Selected meshes
serializationObject.selection = [];
const selection = this._thinEffectLayer._selection;
if (selection) {
const meshUniqueIdToSelectionId = this._thinEffectLayer._meshUniqueIdToSelectionId;
// selection can be sparse since _removeMesh can remove entries
const selectionMap: {
[uniqueId: number]: {
meshIds: string[];
};
} = {};
for (let i = 0; i < selection.length; ++i) {
const mesh = selection[i];
const selectionId = meshUniqueIdToSelectionId[mesh.uniqueId];
if (!selectionMap[selectionId]) {
selectionMap[selectionId] = {
meshIds: [],
};
}
selectionMap[selectionId].meshIds.push(mesh.id);
}
serializationObject.selection = selectionMap;
}
return serializationObject;
}
/**
* Creates a SelectionOutline layer from parsed SelectionOutline layer data
* @param parsedSelectionOutlineLayer defines the SelectionOutline layer data
* @param scene defines the current scene
* @param rootUrl defines the root URL containing the SelectionOutline layer information
* @returns a parsed SelectionOutline layer
*/
public static override Parse(parsedSelectionOutlineLayer: any, scene: Scene, rootUrl: string): SelectionOutlineLayer {
const selectionOutlineLayer = SerializationHelper.Parse(
() => new SelectionOutlineLayer(parsedSelectionOutlineLayer.name, scene, parsedSelectionOutlineLayer.options),
parsedSelectionOutlineLayer,
scene,
rootUrl
);
const selectionMap = parsedSelectionOutlineLayer.selection as { [uniqueId: number]: { meshIds: string[] } };
// Selected meshes
for (const outlinedMeshes of Object.values(selectionMap)) {
const meshes: AbstractMesh[] = [];
for (let meshIndex = 0; meshIndex < outlinedMeshes.meshIds.length; meshIndex++) {
const meshId = outlinedMeshes.meshIds[meshIndex];
const mesh = scene.getMeshById(meshId);
if (mesh) {
meshes.push(mesh);
}
}
selectionOutlineLayer.addSelection(meshes);
}
return selectionOutlineLayer;
}
}
RegisterClass("BABYLON.SelectionOutlineLayer", SelectionOutlineLayer);