@shaderfrog/core 0.0.1

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.
@@ -0,0 +1,670 @@
1
+ import * as BABYLON from 'babylonjs';
2
+ import { Engine, EngineNodeType, EngineContext } from '../../core/engine';
3
+ import {
4
+ nodeName,
5
+ doesLinkThruShader,
6
+ NodeParser,
7
+ ShaderStage,
8
+ prepopulatePropertyInputs,
9
+ Graph,
10
+ mangleMainFn,
11
+ NodeType,
12
+ } from '../../core/graph';
13
+ import importers from './importers';
14
+
15
+ import {
16
+ returnGlPositionHardCoded,
17
+ returnGlPosition,
18
+ makeFnStatement,
19
+ returnGlPositionVec3Right,
20
+ } from '../../ast/manipulate';
21
+
22
+ import { Program } from '@shaderfrog/glsl-parser/ast';
23
+ import {
24
+ CodeNode,
25
+ NodeProperty,
26
+ property,
27
+ SourceNode,
28
+ } from '../../core/nodes/code-nodes';
29
+
30
+ import {
31
+ namedAttributeStrategy,
32
+ texture2DStrategy,
33
+ uniformStrategy,
34
+ } from '../../core/strategy';
35
+ import { NodeInput, NodePosition } from '../../core/nodes/core-node';
36
+ import { DataNode, UniformDataType } from '../../core/nodes/data-nodes';
37
+
38
+ // Setting these properties on the material have side effects, not just for the
39
+ // GLSL, but for the material itself in JS memory apparently, maybe the bound
40
+ // uniforms?. The material we create in babylengine must have the same initial
41
+ // properties as those in BabylonComponent or else there will be errors with
42
+ // uniforms
43
+ export const physicalDefaultProperties: Partial<
44
+ Record<keyof BABYLON.PBRMaterial, any>
45
+ > = {
46
+ forceIrradianceInFragment: true,
47
+ albedoColor: new BABYLON.Color3(1.0, 1.0, 1.0),
48
+ metallic: 0.0,
49
+ roughness: 1.0,
50
+ };
51
+
52
+ const log = (...args: any[]) =>
53
+ console.log.call(console, '\x1b[33m(babylengine)\x1b[0m', ...args);
54
+
55
+ const babylonHackCache: Record<string, { fragment: string; vertex: string }> =
56
+ {};
57
+
58
+ export const physicalNode = (
59
+ id: string,
60
+ name: string,
61
+ groupId: string | null | undefined,
62
+ position: NodePosition,
63
+ uniforms: UniformDataType[],
64
+ stage: ShaderStage | undefined,
65
+ nextStageNodeId?: string
66
+ ): CodeNode =>
67
+ prepopulatePropertyInputs({
68
+ id,
69
+ name,
70
+ groupId,
71
+ position,
72
+ type: EngineNodeType.physical,
73
+ config: {
74
+ uniforms,
75
+ version: 3,
76
+ mangle: false,
77
+ preprocess: true,
78
+ properties: [
79
+ property('Base Color', 'baseColor', 'rgb', '?????'),
80
+ property('Color', 'albedoColor', 'rgb', 'uniform_vAlbedoColor'),
81
+ property('Texture', 'albedoTexture', 'texture', 'filler_albedoSampler'),
82
+ property('Bump Map', 'bumpTexture', 'texture', 'filler_bumpSampler'),
83
+ property('Metalness', 'metallic', 'number'),
84
+ property('Roughness', 'roughness', 'number'),
85
+ property('Env Map', 'environmentTexture', 'samplerCube'),
86
+ property('Reflection Texture', 'reflectionTexture', 'samplerCube'),
87
+ property('Refraction Texture', 'refractionTexture', 'samplerCube'),
88
+ property('Index Of Refraction', 'indexOfRefraction', 'number'),
89
+ property('Alpha', 'alpha', 'number'),
90
+ property('Direct Intensity', 'directIntensity', 'number'),
91
+ property('Environment Intensity', 'environmentIntensity', 'number'),
92
+ property('Camera Exposure', 'cameraExposure', 'number'),
93
+ property('Camera Contrast', 'cameraContrast', 'number'),
94
+ property('Micro Surface', 'microSurface', 'number'),
95
+ property('Reflectivity Color', 'reflectivityColor', 'rgb'),
96
+ ],
97
+ hardCodedProperties: physicalDefaultProperties,
98
+ strategies: [
99
+ uniformStrategy(),
100
+ stage === 'fragment'
101
+ ? texture2DStrategy()
102
+ : namedAttributeStrategy('position'),
103
+ ],
104
+ },
105
+ inputs: [],
106
+ outputs: [
107
+ {
108
+ name: 'vector4',
109
+ category: 'data',
110
+ id: '1',
111
+ },
112
+ ],
113
+ source: '',
114
+ stage,
115
+ nextStageNodeId,
116
+ });
117
+
118
+ export type RuntimeContext = {
119
+ scene: BABYLON.Scene;
120
+ camera: BABYLON.Camera;
121
+ BABYLON: any;
122
+ sceneData: any;
123
+ // material: any;
124
+ // index: number;
125
+ // threeTone: any;
126
+ cache: {
127
+ data: {
128
+ [key: string]: any;
129
+ };
130
+ nodes: {
131
+ [id: string]: {
132
+ // fragmentRef: any;
133
+ // vertexRef: any;
134
+ fragment: string;
135
+ vertex: string;
136
+ };
137
+ };
138
+ };
139
+ };
140
+
141
+ export const toonNode = (
142
+ id: string,
143
+ name: string,
144
+ groupId: string | null | undefined,
145
+ position: NodePosition,
146
+ uniforms: UniformDataType[],
147
+ stage: ShaderStage | undefined,
148
+ nextStageNodeId?: string
149
+ ): CodeNode =>
150
+ prepopulatePropertyInputs({
151
+ id,
152
+ name,
153
+ groupId,
154
+ position,
155
+ type: EngineNodeType.toon,
156
+ config: {
157
+ uniforms,
158
+ version: 3,
159
+ preprocess: true,
160
+ mangle: false,
161
+ properties: [
162
+ property('Color', 'color', 'rgb', 'uniform_diffuse'),
163
+ property('Texture', 'map', 'texture', 'filler_map'),
164
+ property(
165
+ 'Gradient Map',
166
+ 'gradientMap',
167
+ 'texture',
168
+ 'filler_gradientMap'
169
+ ),
170
+ property('Normal Map', 'normalMap', 'texture', 'filler_normalMap'),
171
+ property('Normal Scale', 'normalScale', 'vector2'),
172
+ property('Displacement Map', 'displacementMap', 'texture'),
173
+ property('Env Map', 'envMap', 'samplerCube'),
174
+ ],
175
+ strategies: [
176
+ uniformStrategy(),
177
+ stage === 'fragment'
178
+ ? texture2DStrategy()
179
+ : namedAttributeStrategy('position'),
180
+ ],
181
+ },
182
+ inputs: [],
183
+ outputs: [
184
+ {
185
+ name: 'vector4',
186
+ category: 'data',
187
+ id: '1',
188
+ },
189
+ ],
190
+ source: '',
191
+ stage,
192
+ nextStageNodeId,
193
+ });
194
+
195
+ const babylonMaterialProperties = (
196
+ scene: BABYLON.Scene,
197
+ graph: Graph,
198
+ node: SourceNode,
199
+ sibling?: SourceNode
200
+ ): Record<string, any> => {
201
+ // Find inputs to this node that are dependent on a property of the material
202
+ const propertyInputs = node.inputs
203
+ .filter((i) => i.property)
204
+ .reduce<Record<string, NodeInput>>(
205
+ (acc, input) => ({ ...acc, [input.id]: input }),
206
+ {}
207
+ );
208
+
209
+ // Then look for any edges into those inputs and set the material property
210
+ const props = graph.edges
211
+ .filter((edge) => edge.to === node.id || edge.to === sibling?.id)
212
+ .reduce<Record<string, any>>((acc, edge) => {
213
+ // Check if we've plugged into an input for a property
214
+ const propertyInput = propertyInputs[edge.input];
215
+ if (propertyInput) {
216
+ // Find the property itself
217
+ const property = (node.config.properties || []).find(
218
+ (p) => p.property === propertyInput.property
219
+ ) as NodeProperty;
220
+
221
+ // Initialize the property on the material
222
+ if (property.type === 'texture') {
223
+ acc[property.property] = new BABYLON.Texture('', scene);
224
+ } else if (property.type === 'number') {
225
+ acc[property.property] = 0.5;
226
+ } else if (property.type === 'rgb') {
227
+ acc[property.property] = new BABYLON.Color3(1, 1, 1);
228
+ } else if (property.type === 'rgba') {
229
+ acc[property.property] = new BABYLON.Color4(1, 1, 1, 1);
230
+ }
231
+ }
232
+ return acc;
233
+ }, {});
234
+ return props;
235
+ };
236
+
237
+ export let mIdx = 0;
238
+ let id = () => mIdx++;
239
+
240
+ const nodeCacheKey = (graph: Graph, node: SourceNode) => {
241
+ return (
242
+ '[ID:' +
243
+ node.id +
244
+ 'Edges:' +
245
+ graph.edges
246
+ .filter((edge) => edge.to === node.id)
247
+ .map((edge) => `(${edge.to}->${edge.input})`)
248
+ .sort()
249
+ .join(',') +
250
+ ']'
251
+ // Currently excluding node inputs because these are calculated *after*
252
+ // the onbeforecompile, so the next compile, they'll all change!
253
+ // node.inputs.map((i) => `${i.id}${i.bakeable}`)
254
+ );
255
+ };
256
+
257
+ const programCacheKey = (
258
+ engineContext: EngineContext,
259
+ graph: Graph,
260
+ node: SourceNode,
261
+ sibling: SourceNode
262
+ ) => {
263
+ // The megashader source is dependent on scene information, like the number
264
+ // and type of lights in the scene. This kinda sucks - it's duplicating
265
+ // three's material cache key, and is coupled to how three builds shaders
266
+ const scene = engineContext.runtime.scene as BABYLON.Scene;
267
+ const lights = scene.getNodes().filter((n) => n instanceof BABYLON.Light);
268
+
269
+ return (
270
+ [node, sibling]
271
+ .sort((a, b) => a.id.localeCompare(b.id))
272
+ .map((n) => nodeCacheKey(graph, n))
273
+ .join('-') +
274
+ '|Lights:' +
275
+ lights.join(',') +
276
+ '|Envtex:' +
277
+ scene.environmentTexture
278
+ );
279
+ };
280
+
281
+ const cacher = async (
282
+ engineContext: EngineContext,
283
+ graph: Graph,
284
+ node: SourceNode,
285
+ sibling: SourceNode,
286
+ newValue: (...args: any[]) => Promise<any>
287
+ ) => {
288
+ const cacheKey = programCacheKey(engineContext, graph, node, sibling);
289
+
290
+ if (engineContext.runtime.cache.data[cacheKey]) {
291
+ log(`Cache hit "${cacheKey}"`);
292
+ } else {
293
+ log(`Cache miss "${cacheKey}"`);
294
+ }
295
+ const materialData = await (engineContext.runtime.cache.data[cacheKey] ||
296
+ newValue());
297
+ log(`Material cache "${cacheKey}" is now`, materialData);
298
+
299
+ engineContext.runtime.cache.data[cacheKey] = materialData;
300
+ engineContext.runtime.engineMaterial = materialData.material;
301
+
302
+ // TODO: We mutate the nodes here, can we avoid that later?
303
+ node.source =
304
+ node.stage === 'fragment' ? materialData.fragment : materialData.vertex;
305
+ sibling.source =
306
+ sibling.stage === 'fragment' ? materialData.fragment : materialData.vertex;
307
+ };
308
+
309
+ const onBeforeCompileMegaShader = async (
310
+ engineContext: EngineContext,
311
+ graph: Graph,
312
+ node: SourceNode,
313
+ sibling: SourceNode
314
+ ): Promise<{
315
+ material: BABYLON.Material;
316
+ fragment: string;
317
+ vertex: string;
318
+ }> => {
319
+ const { scene, sceneData } = engineContext.runtime;
320
+
321
+ const pbrName = `engine_pbr${id()}`;
322
+ const shaderMaterial = new BABYLON.PBRMaterial(pbrName, scene);
323
+
324
+ shaderMaterial.linkRefractionWithTransparency = true;
325
+ shaderMaterial.subSurface.isRefractionEnabled = true;
326
+ const newProperties = {
327
+ ...(node.config.hardCodedProperties ||
328
+ sibling.config.hardCodedProperties ||
329
+ {}),
330
+ ...babylonMaterialProperties(scene, graph, node, sibling),
331
+ };
332
+ Object.assign(shaderMaterial, newProperties);
333
+ log('Engine megashader initial properties', { newProperties });
334
+
335
+ let vertexSource: string;
336
+ let fragmentSource: string;
337
+
338
+ // This was a previous attempt to do what's done in submeshes below
339
+ // const nodeCache = engineContext.runtime.cache.nodes;
340
+ // fragmentSource =
341
+ // nodeCache[node.id]?.fragment ||
342
+ // nodeCache[node.nextStageNodeId || 'unknown']?.fragment;
343
+ // vertexSource =
344
+ // nodeCache[node.id]?.vertex ||
345
+ // nodeCache[node.nextStageNodeId || 'unknown']?.vertex;
346
+
347
+ const genHackCacheKey = (unknown: BABYLON.MaterialDefines | string[]) =>
348
+ unknown.toString();
349
+ let hackKey: string;
350
+
351
+ return new Promise((resolve) => {
352
+ shaderMaterial.customShaderNameResolve = (
353
+ shaderName,
354
+ uniforms,
355
+ uniformBuffers,
356
+ samplers,
357
+ defines,
358
+ attributes,
359
+ options
360
+ ) => {
361
+ hackKey = genHackCacheKey(defines);
362
+ log('Babylengine creating new shader', {
363
+ uniforms,
364
+ uniformBuffers,
365
+ samplers,
366
+ defines,
367
+ attributes,
368
+ options,
369
+ });
370
+ if (options) {
371
+ options.processFinalCode = (type, code) => {
372
+ // If babylon thinks it has cached code for a new shader,
373
+ // processFinalCode doesn't get called. This hack attempt is to try to
374
+ // recreate Babylon's internal cache, based on my understanding that
375
+ // babylon looks to the defines to determine shader uniqueness
376
+ babylonHackCache[hackKey] = babylonHackCache[hackKey] || {
377
+ fragment: '',
378
+ vertex: '',
379
+ };
380
+ if (type === 'vertex') {
381
+ log('captured vertex code', { code });
382
+ vertexSource = code;
383
+ babylonHackCache[hackKey].vertex = code;
384
+ return code;
385
+ } else if (type === 'fragment') {
386
+ log('captured fragment code', { code });
387
+ fragmentSource = code;
388
+ babylonHackCache[hackKey].fragment = code;
389
+ return code;
390
+ }
391
+ throw new Error(`Unknown type ${type}`);
392
+ };
393
+ } else {
394
+ console.warn('No options for', pbrName);
395
+ }
396
+ return shaderName;
397
+ };
398
+
399
+ if (!sceneData.mesh) {
400
+ log('🍃 EFF, no MESHREF RENDER()....');
401
+ }
402
+ shaderMaterial.forceCompilation(sceneData.mesh, (compiledMaterial) => {
403
+ log('Babylon shader compilation done!');
404
+ if (!fragmentSource || !vertexSource) {
405
+ log('Reusing previous mesh render...');
406
+ vertexSource = babylonHackCache[hackKey]?.vertex;
407
+ fragmentSource = babylonHackCache[hackKey]?.fragment;
408
+ }
409
+
410
+ if (!fragmentSource || !vertexSource) {
411
+ debugger;
412
+ }
413
+ log('captured', { fragmentSource, vertexSource });
414
+
415
+ if (node.stage === 'fragment') {
416
+ node.source = fragmentSource;
417
+ }
418
+ if (sibling.stage === 'fragment') {
419
+ sibling.source = fragmentSource;
420
+ }
421
+ if (node.stage === 'vertex') {
422
+ node.source = vertexSource;
423
+ }
424
+ if (sibling.stage === 'vertex') {
425
+ sibling.source = vertexSource;
426
+ }
427
+
428
+ engineContext.runtime.cache.nodes[node.id] = {
429
+ // fragmentRef,
430
+ // vertexRef,
431
+ fragment: fragmentSource,
432
+ vertex: vertexSource,
433
+ };
434
+
435
+ // This doesn't appear to do anything (see comment above submeshes)
436
+ compiledMaterial.dispose(true);
437
+
438
+ resolve({
439
+ material: compiledMaterial,
440
+ fragment: fragmentSource,
441
+ vertex: vertexSource,
442
+ });
443
+ });
444
+ });
445
+ };
446
+
447
+ // TODO: NEED TO DO SAME THREE MANGLIGN STEP HERE
448
+ const megaShaderMainpulateAst: NodeParser['manipulateAst'] = (
449
+ engineContext,
450
+ engine,
451
+ graph,
452
+ node,
453
+ ast,
454
+ inputEdges
455
+ ) => {
456
+ const programAst = ast as Program;
457
+ const mainName = 'main' || nodeName(node);
458
+
459
+ if (node.stage === 'vertex') {
460
+ if (doesLinkThruShader(graph, node)) {
461
+ returnGlPositionHardCoded(mainName, programAst, 'vec3', 'transformed');
462
+ } else {
463
+ returnGlPosition(mainName, programAst);
464
+ }
465
+ }
466
+
467
+ // We specify engine nodes are mangle: false, which is the graph step that
468
+ // handles renaming the main fn, so we have to do it ourselves
469
+ mangleMainFn(programAst, node);
470
+ return programAst;
471
+ };
472
+
473
+ const evaluateNode = (node: DataNode) => {
474
+ if (node.type === 'number') {
475
+ return parseFloat(node.value);
476
+ }
477
+
478
+ if (node.type === 'vector2') {
479
+ return new BABYLON.Vector2(
480
+ parseFloat(node.value[0]),
481
+ parseFloat(node.value[1])
482
+ );
483
+ } else if (node.type === 'vector3') {
484
+ return new BABYLON.Vector3(
485
+ parseFloat(node.value[0]),
486
+ parseFloat(node.value[1]),
487
+ parseFloat(node.value[2])
488
+ );
489
+ } else if (node.type === 'vector4') {
490
+ return new BABYLON.Vector4(
491
+ parseFloat(node.value[0]),
492
+ parseFloat(node.value[1]),
493
+ parseFloat(node.value[2]),
494
+ parseFloat(node.value[3])
495
+ );
496
+ } else if (node.type === 'rgb') {
497
+ return new BABYLON.Color3(
498
+ parseFloat(node.value[0]),
499
+ parseFloat(node.value[1]),
500
+ parseFloat(node.value[2])
501
+ );
502
+ } else if (node.type === 'rgba') {
503
+ return new BABYLON.Color4(
504
+ parseFloat(node.value[0]),
505
+ parseFloat(node.value[1]),
506
+ parseFloat(node.value[2]),
507
+ parseFloat(node.value[3])
508
+ );
509
+ } else {
510
+ return node.value;
511
+ }
512
+ };
513
+
514
+ export const babylengine: Engine = {
515
+ name: 'babylon',
516
+ importers,
517
+ mergeOptions: {
518
+ includePrecisions: true,
519
+ includeVersion: false,
520
+ },
521
+ evaluateNode,
522
+ constructors: {
523
+ [EngineNodeType.physical]: physicalNode,
524
+ [EngineNodeType.toon]: toonNode,
525
+ },
526
+ // TODO: Get from uniform lib?
527
+ preserve: new Set<string>([
528
+ 'viewProjection',
529
+ 'normalMatrix',
530
+ 'vAmbientInfos',
531
+ 'vOpacityInfos',
532
+ 'vEmissiveInfos',
533
+ 'vLightmapInfos',
534
+ 'vReflectivityInfos',
535
+ 'vMicroSurfaceSamplerInfos',
536
+ 'vReflectionInfos',
537
+ 'vReflectionFilteringInfo',
538
+ 'vReflectionPosition',
539
+ 'vReflectionSize',
540
+ 'vBumpInfos',
541
+ 'albedoMatrix',
542
+ 'ambientMatrix',
543
+ 'opacityMatrix',
544
+ 'emissiveMatrix',
545
+ 'lightmapMatrix',
546
+ 'reflectivityMatrix',
547
+ 'microSurfaceSamplerMatrix',
548
+ 'bumpMatrix',
549
+ 'bumpSampler',
550
+ 'vTangentSpaceParams',
551
+ 'reflectionMatrix',
552
+ 'vReflectionColor',
553
+ 'vAlbedoColor',
554
+ 'vLightingIntensity',
555
+ 'vReflectionMicrosurfaceInfos',
556
+ 'pointSize',
557
+ 'vReflectivityColor',
558
+ 'vEmissiveColor',
559
+ 'visibility',
560
+ 'vMetallicReflectanceFactors',
561
+ 'vMetallicReflectanceInfos',
562
+ 'metallicReflectanceMatrix',
563
+ 'vClearCoatParams',
564
+ 'vClearCoatRefractionParams',
565
+ 'vClearCoatInfos',
566
+ 'clearCoatMatrix',
567
+ 'clearCoatRoughnessMatrix',
568
+ 'vClearCoatBumpInfos',
569
+ 'vClearCoatTangentSpaceParams',
570
+ 'clearCoatBumpMatrix',
571
+ 'vClearCoatTintParams',
572
+ 'clearCoatColorAtDistance',
573
+ 'vClearCoatTintInfos',
574
+ 'clearCoatTintMatrix',
575
+ 'vAnisotropy',
576
+ 'vAnisotropyInfos',
577
+ 'anisotropyMatrix',
578
+ 'vSheenColor',
579
+ 'vSheenRoughness',
580
+ 'vSheenInfos',
581
+ 'sheenMatrix',
582
+ 'sheenRoughnessMatrix',
583
+ 'vRefractionMicrosurfaceInfos',
584
+ 'vRefractionFilteringInfo',
585
+ 'vRefractionInfos',
586
+ 'refractionMatrix',
587
+ 'vThicknessInfos',
588
+ 'thicknessMatrix',
589
+ 'vThicknessParam',
590
+ 'vDiffusionDistance',
591
+ 'vTintColor',
592
+ 'vSubSurfaceIntensity',
593
+ 'scatteringDiffusionProfile',
594
+ 'vDetailInfos',
595
+ 'detailMatrix',
596
+ 'Scene',
597
+ 'vEyePosition',
598
+ 'vAmbientColor',
599
+ 'vCameraInfos',
600
+ 'vPositionW',
601
+ 'vMainUV1',
602
+ 'vNormalW',
603
+ 'Light0',
604
+ 'albedoSampler',
605
+ 'environmentBrdfSampler',
606
+ 'position',
607
+ 'normal',
608
+ 'uv',
609
+ 'world',
610
+ 'time',
611
+ 'Light0',
612
+ 'Light1',
613
+ 'Light2',
614
+ 'Light3',
615
+ 'light0',
616
+ 'light1',
617
+ 'light2',
618
+ 'light3',
619
+ 'vLightData0',
620
+ 'vLightDiffuse0',
621
+ 'vLightSpecular0',
622
+ 'vLightFalloff0',
623
+ 'vSphericalL00',
624
+ 'vSphericalL1_1',
625
+ 'vSphericalL10',
626
+ 'vSphericalL11',
627
+ 'vSphericalL2_2',
628
+ 'vSphericalL2_1',
629
+ 'vSphericalL20',
630
+ 'vSphericalL21',
631
+ 'vSphericalL22',
632
+ 'vAlbedoInfos',
633
+ 'reflectionSampler',
634
+ ]),
635
+ parsers: {
636
+ [NodeType.SOURCE]: {
637
+ manipulateAst: (engineContext, engine, graph, node, ast, inputEdges) => {
638
+ const programAst = ast as Program;
639
+ const mainName = 'main' || nodeName(node);
640
+
641
+ // This hinges on the vertex shader calling vec3(p)
642
+ if (node.stage === 'vertex') {
643
+ if (doesLinkThruShader(graph, node)) {
644
+ returnGlPositionVec3Right(mainName, programAst);
645
+ } else {
646
+ returnGlPosition(mainName, programAst);
647
+ }
648
+ }
649
+ return ast;
650
+ },
651
+ },
652
+ [EngineNodeType.physical]: {
653
+ onBeforeCompile: (graph, engineContext, node, sibling) =>
654
+ cacher(engineContext, graph, node, sibling as SourceNode, () =>
655
+ onBeforeCompileMegaShader(
656
+ engineContext,
657
+ graph,
658
+ node,
659
+ sibling as SourceNode
660
+ )
661
+ ),
662
+ manipulateAst: megaShaderMainpulateAst,
663
+ },
664
+ },
665
+ };
666
+
667
+ babylengine.parsers[EngineNodeType.toon] =
668
+ babylengine.parsers[EngineNodeType.physical];
669
+ babylengine.parsers[EngineNodeType.phong] =
670
+ babylengine.parsers[EngineNodeType.physical];