three-gpu-pathtracer 0.0.20 → 0.0.21

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.
Files changed (75) hide show
  1. package/README.md +111 -464
  2. package/build/index.module.js +5691 -5312
  3. package/build/index.module.js.map +1 -1
  4. package/build/index.umd.cjs +5369 -5003
  5. package/build/index.umd.cjs.map +1 -1
  6. package/package.json +12 -6
  7. package/src/core/PathTracingRenderer.js +59 -46
  8. package/src/core/PathTracingSceneGenerator.js +245 -10
  9. package/src/core/WebGLPathTracer.js +472 -0
  10. package/src/core/utils/BakedGeometry.js +35 -0
  11. package/src/core/utils/BufferAttributeUtils.js +64 -0
  12. package/src/{utils → core/utils}/GeometryPreparationUtils.js +35 -35
  13. package/src/core/utils/MeshDiff.js +102 -0
  14. package/src/core/utils/StaticGeometryGenerator.js +285 -0
  15. package/src/core/utils/convertToStaticGeometry.js +344 -0
  16. package/src/core/utils/mergeGeometries.js +218 -0
  17. package/src/core/utils/sceneUpdateUtils.js +96 -0
  18. package/src/index.d.ts +274 -0
  19. package/src/index.js +4 -20
  20. package/src/materials/MaterialBase.js +4 -0
  21. package/src/materials/fullscreen/ClampedInterpolationMaterial.js +112 -0
  22. package/src/materials/fullscreen/DenoiseMaterial.js +4 -0
  23. package/src/materials/pathtracing/PhysicalPathTracingMaterial.js +73 -76
  24. package/src/materials/pathtracing/glsl/{attenuateHit.glsl.js → attenuate_hit_function.glsl.js} +1 -1
  25. package/src/materials/pathtracing/glsl/{cameraUtils.glsl.js → camera_util_functions.glsl.js} +1 -1
  26. package/src/materials/pathtracing/glsl/{directLightContribution.glsl.js → direct_light_contribution_function.glsl.js} +1 -1
  27. package/src/materials/pathtracing/glsl/{getSurfaceRecord.glsl.js → get_surface_record_function.glsl.js} +1 -1
  28. package/src/materials/pathtracing/glsl/index.js +6 -0
  29. package/src/materials/pathtracing/glsl/{renderStructs.glsl.js → render_structs.glsl.js} +1 -1
  30. package/src/materials/pathtracing/glsl/{traceScene.glsl.js → trace_scene_function.glsl.js} +1 -3
  31. package/src/materials/surface/AmbientOcclusionMaterial.js +8 -8
  32. package/src/objects/PhysicalSpotLight.js +2 -2
  33. package/src/shader/bsdf/{bsdfSampling.glsl.js → bsdf_functions.glsl.js} +19 -72
  34. package/src/shader/bsdf/{fog.glsl.js → fog_functions.glsl.js} +1 -1
  35. package/src/shader/bsdf/{ggx.glsl.js → ggx_functions.glsl.js} +1 -1
  36. package/src/shader/bsdf/index.js +5 -0
  37. package/src/shader/bsdf/{iridescence.glsl.js → iridescence_functions.glsl.js} +1 -1
  38. package/src/shader/bsdf/{sheen.glsl.js → sheen_functions.glsl.js} +1 -1
  39. package/src/shader/bvh/index.js +2 -0
  40. package/src/shader/{structs/fogMaterialBvh.glsl.js → bvh/inside_fog_volume_function.glsl.js} +1 -1
  41. package/src/shader/{common/bvhAnyHit.glsl.js → bvh/ray_any_hit_function.glsl.js} +1 -1
  42. package/src/shader/common/{fresnel.glsl.js → fresnel_functions.glsl.js} +1 -1
  43. package/src/shader/common/index.js +5 -0
  44. package/src/shader/common/{math.glsl.js → math_functions.glsl.js} +1 -1
  45. package/src/shader/common/{intersectShapes.glsl.js → shape_intersection_functions.glsl.js} +1 -1
  46. package/src/shader/common/{arraySamplerTexelFetch.glsl.js → texture_sample_functions.glsl.js} +1 -1
  47. package/src/shader/common/{utils.glsl.js → util_functions.glsl.js} +1 -1
  48. package/src/shader/rand/index.js +3 -0
  49. package/src/shader/rand/pcg.glsl.js +1 -1
  50. package/src/shader/rand/sobol.glsl.js +4 -4
  51. package/src/shader/rand/{stratifiedTexture.glsl.js → stratified.glsl.js} +7 -2
  52. package/src/shader/sampling/{equirectSampling.glsl.js → equirect_sampling_functions.glsl.js} +1 -2
  53. package/src/shader/sampling/index.js +3 -0
  54. package/src/shader/sampling/{lightSampling.glsl.js → light_sampling_functions.glsl.js} +3 -3
  55. package/src/shader/sampling/{shapeSampling.glsl.js → shape_sampling_functions.glsl.js} +1 -1
  56. package/src/shader/structs/{cameraStruct.glsl.js → camera_struct.glsl.js} +1 -1
  57. package/src/shader/structs/{equirectStruct.glsl.js → equirect_struct.glsl.js} +1 -1
  58. package/src/shader/structs/index.js +5 -0
  59. package/src/shader/structs/{lightsStruct.glsl.js → lights_struct.glsl.js} +1 -1
  60. package/src/shader/structs/{materialStruct.glsl.js → material_struct.glsl.js} +2 -2
  61. package/src/shader/structs/surface_record_struct.glsl.js +63 -0
  62. package/src/uniforms/EquirectHdrInfoUniform.js +16 -11
  63. package/src/uniforms/LightsInfoUniformStruct.js +21 -10
  64. package/src/uniforms/MaterialsTexture.js +27 -86
  65. package/src/uniforms/RenderTarget2DArray.js +60 -20
  66. package/src/utils/BlurredEnvMapGenerator.js +12 -5
  67. package/src/utils/SobolNumberMapGenerator.js +3 -3
  68. package/src/utils/bufferToHash.js +22 -0
  69. package/src/core/DynamicPathTracingSceneGenerator.js +0 -164
  70. package/src/core/MaterialReducer.js +0 -256
  71. package/src/materials/pathtracing/LambertPathTracingMaterial.js +0 -297
  72. package/src/uniforms/IESProfilesTexture.js +0 -100
  73. package/src/uniforms/utils.js +0 -30
  74. package/src/utils/IESLoader.js +0 -327
  75. package/src/workers/PathTracingSceneWorker.js +0 -52
@@ -9,37 +9,63 @@ import {
9
9
  ShaderMaterial,
10
10
  } from 'three';
11
11
  import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
12
- import { reduceTexturesToUniqueSources } from './utils.js';
13
12
 
14
13
  const prevColor = new Color();
14
+ function getTextureHash( texture ) {
15
+
16
+ return texture ? `${ texture.uuid }:${ texture.version }` : null;
17
+
18
+ }
19
+
20
+ function assignOptions( target, options ) {
21
+
22
+ for ( const key in options ) {
23
+
24
+ if ( key in target ) {
25
+
26
+ target[ key ] = options[ key ];
27
+
28
+ }
29
+
30
+ }
31
+
32
+ }
33
+
15
34
  export class RenderTarget2DArray extends WebGLArrayRenderTarget {
16
35
 
17
- constructor( ...args ) {
36
+ constructor( width, height, options ) {
37
+
38
+ const textureOptions = {
39
+ format: RGBAFormat,
40
+ type: UnsignedByteType,
41
+ minFilter: LinearFilter,
42
+ magFilter: LinearFilter,
43
+ wrapS: RepeatWrapping,
44
+ wrapT: RepeatWrapping,
45
+ generateMipmaps: false,
46
+ ...options,
47
+ };
48
+
49
+ super( width, height, 1, textureOptions );
18
50
 
19
- super( ...args );
51
+ // manually assign the options because passing options into the
52
+ // constructor does not work
53
+ assignOptions( this.texture, textureOptions );
20
54
 
21
- const tex = this.texture;
22
- tex.format = RGBAFormat;
23
- tex.type = UnsignedByteType;
24
- tex.minFilter = LinearFilter;
25
- tex.magFilter = LinearFilter;
26
- tex.wrapS = RepeatWrapping;
27
- tex.wrapT = RepeatWrapping;
28
- tex.setTextures = ( ...args ) => {
55
+ this.texture.setTextures = ( ...args ) => {
29
56
 
30
57
  this.setTextures( ...args );
31
58
 
32
59
  };
33
60
 
61
+ this.hashes = [ null ];
62
+
34
63
  const fsQuad = new FullScreenQuad( new CopyMaterial() );
35
64
  this.fsQuad = fsQuad;
36
65
 
37
66
  }
38
67
 
39
- setTextures( renderer, width, height, textures ) {
40
-
41
- // get the list of textures with unique sources
42
- const uniqueTextures = reduceTexturesToUniqueSources( textures );
68
+ setTextures( renderer, textures, width = this.width, height = this.height ) {
43
69
 
44
70
  // save previous renderer state
45
71
  const prevRenderTarget = renderer.getRenderTarget();
@@ -49,17 +75,26 @@ export class RenderTarget2DArray extends WebGLArrayRenderTarget {
49
75
 
50
76
  // resize the render target and ensure we don't have an empty texture
51
77
  // render target depth must be >= 1 to avoid unbound texture error on android devices
52
- const depth = uniqueTextures.length || 1;
53
- this.setSize( width, height, depth );
78
+ const depth = textures.length || 1;
79
+ if ( width !== this.width || height !== this.height || this.depth !== depth ) {
80
+
81
+ this.setSize( width, height, depth );
82
+ this.hashes = new Array( depth ).fill( null );
83
+
84
+ }
85
+
54
86
  renderer.setClearColor( 0, 0 );
55
87
  renderer.toneMapping = NoToneMapping;
56
88
 
57
89
  // render each texture into each layer of the target
58
90
  const fsQuad = this.fsQuad;
91
+ const hashes = this.hashes;
92
+ let updated = false;
59
93
  for ( let i = 0, l = depth; i < l; i ++ ) {
60
94
 
61
- const texture = uniqueTextures[ i ];
62
- if ( texture ) {
95
+ const texture = textures[ i ];
96
+ const hash = getTextureHash( texture );
97
+ if ( texture && ( hashes[ i ] !== hash || texture.isWebGLRenderTarget ) ) {
63
98
 
64
99
  // revert to default texture transform before rendering
65
100
  texture.matrixAutoUpdate = false;
@@ -74,6 +109,10 @@ export class RenderTarget2DArray extends WebGLArrayRenderTarget {
74
109
  texture.updateMatrix();
75
110
  texture.matrixAutoUpdate = true;
76
111
 
112
+ // ensure textures are not updated unnecessarily
113
+ hashes[ i ] = hash;
114
+ updated = true;
115
+
77
116
  }
78
117
 
79
118
  }
@@ -84,6 +123,8 @@ export class RenderTarget2DArray extends WebGLArrayRenderTarget {
84
123
  renderer.setRenderTarget( prevRenderTarget );
85
124
  renderer.toneMapping = prevToneMapping;
86
125
 
126
+ return updated;
127
+
87
128
  }
88
129
 
89
130
  dispose() {
@@ -140,5 +181,4 @@ class CopyMaterial extends ShaderMaterial {
140
181
 
141
182
  }
142
183
 
143
-
144
184
  }
@@ -1,7 +1,7 @@
1
- import { WebGLRenderTarget, RGBAFormat, HalfFloatType, PMREMGenerator, DataTexture, EquirectangularReflectionMapping } from 'three';
1
+ import { WebGLRenderTarget, RGBAFormat, HalfFloatType, PMREMGenerator, DataTexture, EquirectangularReflectionMapping, FloatType, DataUtils } from 'three';
2
2
  import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
3
3
  import { MaterialBase } from '../materials/MaterialBase.js';
4
- import { utilsGLSL } from '../shader/common/utils.glsl.js';
4
+ import * as CommonGLSL from '../shader/common/index.js';
5
5
 
6
6
  class PMREMCopyMaterial extends MaterialBase {
7
7
 
@@ -31,7 +31,7 @@ class PMREMCopyMaterial extends MaterialBase {
31
31
  #include <common>
32
32
  #include <cube_uv_reflection_fragment>
33
33
 
34
- ${ utilsGLSL }
34
+ ${ CommonGLSL.util_functions }
35
35
 
36
36
  uniform sampler2D envMap;
37
37
  uniform float blur;
@@ -58,7 +58,7 @@ export class BlurredEnvMapGenerator {
58
58
  this.renderer = renderer;
59
59
  this.pmremGenerator = new PMREMGenerator( renderer );
60
60
  this.copyQuad = new FullScreenQuad( new PMREMCopyMaterial() );
61
- this.renderTarget = new WebGLRenderTarget( 1, 1, { type: HalfFloatType, format: RGBAFormat } );
61
+ this.renderTarget = new WebGLRenderTarget( 1, 1, { type: FloatType, format: RGBAFormat } );
62
62
 
63
63
  }
64
64
 
@@ -96,7 +96,14 @@ export class BlurredEnvMapGenerator {
96
96
 
97
97
  // read the data back
98
98
  const buffer = new Uint16Array( width * height * 4 );
99
- renderer.readRenderTargetPixels( renderTarget, 0, 0, width, height, buffer );
99
+ const readBuffer = new Float32Array( width * height * 4 );
100
+ renderer.readRenderTargetPixels( renderTarget, 0, 0, width, height, readBuffer );
101
+
102
+ for ( let i = 0, l = readBuffer.length; i < l; i ++ ) {
103
+
104
+ buffer[ i ] = DataUtils.toHalfFloat( readBuffer[ i ] );
105
+
106
+ }
100
107
 
101
108
  const result = new DataTexture( buffer, width, height, RGBAFormat, HalfFloatType );
102
109
  result.minFilter = texture.minFilter;
@@ -1,7 +1,7 @@
1
1
  import { FloatType, NearestFilter, NoBlending, RGBAFormat, Vector2, WebGLRenderTarget } from 'three';
2
2
  import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
3
3
  import { MaterialBase } from '../materials/MaterialBase.js';
4
- import { sobolCommonGLSL, sobolGenerationGLSL } from '../shader/rand/sobol.glsl.js';
4
+ import { sobol_common, sobol_point_generation } from '../shader/rand/sobol.glsl.js';
5
5
 
6
6
  class SobolNumbersMaterial extends MaterialBase {
7
7
 
@@ -30,8 +30,8 @@ class SobolNumbersMaterial extends MaterialBase {
30
30
 
31
31
  fragmentShader: /* glsl */`
32
32
 
33
- ${ sobolCommonGLSL }
34
- ${ sobolGenerationGLSL }
33
+ ${ sobol_common }
34
+ ${ sobol_point_generation }
35
35
 
36
36
  varying vec2 vUv;
37
37
  uniform vec2 resolution;
@@ -0,0 +1,22 @@
1
+ // https://www.geeksforgeeks.org/how-to-create-hash-from-string-in-javascript/
2
+ // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
3
+ export function bufferToHash( buffer ) {
4
+
5
+ let hash = 0;
6
+
7
+ if ( buffer.byteLength !== 0 ) {
8
+
9
+ const uintArray = new Uint8Array( buffer );
10
+ for ( let i = 0; i < buffer.byteLength; i ++ ) {
11
+
12
+ const byte = uintArray[ i ];
13
+ hash = ( ( hash << 5 ) - hash ) + byte;
14
+ hash |= 0;
15
+
16
+ }
17
+
18
+ }
19
+
20
+ return hash;
21
+
22
+ }
@@ -1,164 +0,0 @@
1
- import { BufferGeometry, MeshBasicMaterial, BufferAttribute, Mesh } from 'three';
2
- import { StaticGeometryGenerator, MeshBVH, SAH } from 'three-mesh-bvh';
3
- import { setCommonAttributes, getGroupMaterialIndicesAttribute } from '../utils/GeometryPreparationUtils.js';
4
-
5
- const dummyMaterial = new MeshBasicMaterial();
6
- export function getDummyMesh() {
7
-
8
- const emptyGeometry = new BufferGeometry();
9
- emptyGeometry.setAttribute( 'position', new BufferAttribute( new Float32Array( 9 ), 3 ) );
10
- return new Mesh( emptyGeometry, dummyMaterial );
11
-
12
- }
13
-
14
- export class DynamicPathTracingSceneGenerator {
15
-
16
- get initialized() {
17
-
18
- return Boolean( this.bvh );
19
-
20
- }
21
-
22
- constructor( objects ) {
23
-
24
- // ensure the objects is an array
25
- if ( ! Array.isArray( objects ) ) {
26
-
27
- objects = [ objects ];
28
-
29
- }
30
-
31
- // use a dummy object for a fallback
32
- const finalObjects = [ ...objects ];
33
- if ( finalObjects.length === 0 ) {
34
-
35
- finalObjects.push( getDummyMesh() );
36
-
37
- }
38
-
39
- // options
40
- this.bvhOptions = {};
41
- this.attributes = [ 'position', 'normal', 'tangent', 'color', 'uv', 'uv2' ];
42
-
43
- // state
44
- this.objects = finalObjects;
45
- this.bvh = null;
46
- this.geometry = new BufferGeometry();
47
- this.materials = null;
48
- this.textures = null;
49
- this.lights = [];
50
- this.staticGeometryGenerator = new StaticGeometryGenerator( this.objects );
51
-
52
- }
53
-
54
- reset() {
55
-
56
- this.bvh = null;
57
- this.geometry.dispose();
58
- this.geometry = new BufferGeometry();
59
- this.materials = null;
60
- this.textures = null;
61
- this.lights = [];
62
- this.staticGeometryGenerator = new StaticGeometryGenerator( this.objects );
63
-
64
- }
65
-
66
- dispose() {}
67
-
68
- prepScene() {
69
-
70
- if ( this.bvh !== null ) {
71
-
72
- return;
73
-
74
- }
75
-
76
- const { objects, staticGeometryGenerator, geometry, lights, attributes } = this;
77
- for ( let i = 0, l = objects.length; i < l; i ++ ) {
78
-
79
- objects[ i ].traverse( c => {
80
-
81
- if ( c.isMesh ) {
82
-
83
- const normalMapRequired = ! ! c.material.normalMap;
84
- setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
85
-
86
- } else if (
87
- c.isRectAreaLight ||
88
- c.isSpotLight ||
89
- c.isPointLight ||
90
- c.isDirectionalLight
91
- ) {
92
-
93
- lights.push( c );
94
-
95
- }
96
-
97
- } );
98
-
99
- }
100
-
101
- const textureSet = new Set();
102
- const materials = staticGeometryGenerator.getMaterials();
103
- materials.forEach( material => {
104
-
105
- for ( const key in material ) {
106
-
107
- const value = material[ key ];
108
- if ( value && value.isTexture ) {
109
-
110
- textureSet.add( value );
111
-
112
- }
113
-
114
- }
115
-
116
- } );
117
-
118
- staticGeometryGenerator.attributes = attributes;
119
- staticGeometryGenerator.generate( geometry );
120
-
121
- const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, materials, materials );
122
- geometry.setAttribute( 'materialIndex', materialIndexAttribute );
123
- geometry.clearGroups();
124
-
125
- this.materials = materials;
126
- this.textures = Array.from( textureSet );
127
-
128
- }
129
-
130
- generate() {
131
-
132
- const { objects, staticGeometryGenerator, geometry, bvhOptions } = this;
133
- if ( this.bvh === null ) {
134
-
135
- this.prepScene();
136
- this.bvh = new MeshBVH( geometry, { strategy: SAH, maxLeafTris: 1, ...bvhOptions } );
137
-
138
- return {
139
- lights: this.lights,
140
- bvh: this.bvh,
141
- materials: this.materials,
142
- textures: this.textures,
143
- objects,
144
- };
145
-
146
- } else {
147
-
148
- const { bvh } = this;
149
- staticGeometryGenerator.generate( geometry );
150
- bvh.refit();
151
- return {
152
- lights: this.lights,
153
- bvh: this.bvh,
154
- materials: this.materials,
155
- textures: this.textures,
156
- objects,
157
- };
158
-
159
- }
160
-
161
- }
162
-
163
-
164
- }
@@ -1,256 +0,0 @@
1
- // https://github.com/gkjohnson/webxr-sandbox/blob/main/skinned-mesh-batching/src/MaterialReducer.js
2
-
3
- function isTypedArray( arr ) {
4
-
5
- return arr.buffer instanceof ArrayBuffer && 'BYTES_PER_ELEMENT' in arr;
6
-
7
- }
8
-
9
- export class MaterialReducer {
10
-
11
- constructor() {
12
-
13
- const ignoreKeys = new Set();
14
- ignoreKeys.add( 'uuid' );
15
-
16
- this.ignoreKeys = ignoreKeys;
17
- this.shareTextures = true;
18
- this.textures = [];
19
- this.materials = [];
20
-
21
- }
22
-
23
- areEqual( objectA, objectB ) {
24
-
25
- const keySet = new Set();
26
- const traverseSet = new Set();
27
- const ignoreKeys = this.ignoreKeys;
28
-
29
- const traverse = ( a, b ) => {
30
-
31
- if ( a === b ) {
32
-
33
- return true;
34
-
35
- }
36
-
37
- if ( a && b && a instanceof Object && b instanceof Object ) {
38
-
39
- if ( traverseSet.has( a ) || traverseSet.has( b ) ) {
40
-
41
- throw new Error( 'MaterialReducer: Material is recursive.' );
42
-
43
- }
44
-
45
- const aIsElement = a instanceof Element;
46
- const bIsElement = b instanceof Element;
47
- if ( aIsElement || bIsElement ) {
48
-
49
- if ( aIsElement !== bIsElement || ! ( a instanceof Image ) || ! ( b instanceof Image ) ) {
50
-
51
- return false;
52
-
53
- }
54
-
55
- return a.src === b.src;
56
-
57
- }
58
-
59
- const aIsImageBitmap = a instanceof ImageBitmap;
60
- const bIsImageBitmap = b instanceof ImageBitmap;
61
- if ( aIsImageBitmap || bIsImageBitmap ) {
62
-
63
- return false;
64
-
65
- }
66
-
67
- if ( a.equals ) {
68
-
69
- return a.equals( b );
70
-
71
- }
72
-
73
- const aIsTypedArray = isTypedArray( a );
74
- const bIsTypedArray = isTypedArray( b );
75
- if ( aIsTypedArray || bIsTypedArray ) {
76
-
77
- if ( aIsTypedArray !== bIsTypedArray || a.constructor !== b.constructor || a.length !== b.length ) {
78
-
79
- return false;
80
-
81
- }
82
-
83
- for ( let i = 0, l = a.length; i < l; i ++ ) {
84
-
85
- if ( a[ i ] !== b[ i ] ) return false;
86
-
87
- }
88
-
89
- return true;
90
-
91
- }
92
-
93
- traverseSet.add( a );
94
- traverseSet.add( b );
95
-
96
- keySet.clear();
97
- for ( const key in a ) {
98
-
99
- if ( ! a.hasOwnProperty( key ) || a[ key ] instanceof Function || ignoreKeys.has( key ) ) {
100
-
101
- continue;
102
-
103
- }
104
-
105
- keySet.add( key );
106
-
107
- }
108
-
109
- for ( const key in b ) {
110
-
111
- if ( ! b.hasOwnProperty( key ) || b[ key ] instanceof Function || ignoreKeys.has( key ) ) {
112
-
113
- continue;
114
-
115
- }
116
-
117
- keySet.add( key );
118
-
119
- }
120
-
121
- const keys = Array.from( keySet.values() );
122
- let result = true;
123
- for ( const i in keys ) {
124
-
125
- const key = keys[ i ];
126
- if ( ignoreKeys.has( key ) ) {
127
-
128
- continue;
129
-
130
- }
131
-
132
- result = traverse( a[ key ], b[ key ] );
133
- if ( ! result ) {
134
-
135
- break;
136
-
137
- }
138
-
139
- }
140
-
141
- traverseSet.delete( a );
142
- traverseSet.delete( b );
143
- return result;
144
-
145
- }
146
-
147
- return false;
148
-
149
- };
150
-
151
- return traverse( objectA, objectB );
152
-
153
- }
154
-
155
- process( object ) {
156
-
157
- const { textures, materials } = this;
158
- let replaced = 0;
159
-
160
- const processMaterial = material => {
161
-
162
- // Check if another material matches this one
163
- let foundMaterial = null;
164
- for ( const i in materials ) {
165
-
166
- const otherMaterial = materials[ i ];
167
- if ( this.areEqual( material, otherMaterial ) ) {
168
-
169
- foundMaterial = otherMaterial;
170
-
171
- }
172
-
173
- }
174
-
175
- if ( foundMaterial ) {
176
-
177
- replaced ++;
178
- return foundMaterial;
179
-
180
- } else {
181
-
182
- materials.push( material );
183
-
184
- if ( this.shareTextures ) {
185
-
186
- // See if there's another texture that matches the ones on this material
187
- for ( const key in material ) {
188
-
189
- if ( ! material.hasOwnProperty( key ) ) continue;
190
-
191
- const value = material[ key ];
192
- if ( value && value.isTexture && value.image instanceof Image ) {
193
-
194
- let foundTexture = null;
195
- for ( const i in textures ) {
196
-
197
- const texture = textures[ i ];
198
- if ( this.areEqual( texture, value ) ) {
199
-
200
- foundTexture = texture;
201
- break;
202
-
203
- }
204
-
205
- }
206
-
207
- if ( foundTexture ) {
208
-
209
- material[ key ] = foundTexture;
210
-
211
- } else {
212
-
213
- textures.push( value );
214
-
215
- }
216
-
217
- }
218
-
219
- }
220
-
221
- }
222
-
223
- return material;
224
-
225
- }
226
-
227
- };
228
-
229
- object.traverse( c => {
230
-
231
- if ( c.isMesh && c.material ) {
232
-
233
- const material = c.material;
234
- if ( Array.isArray( material ) ) {
235
-
236
- for ( let i = 0; i < material.length; i ++ ) {
237
-
238
- material[ i ] = processMaterial( material[ i ] );
239
-
240
- }
241
-
242
- } else {
243
-
244
- c.material = processMaterial( material );
245
-
246
- }
247
-
248
- }
249
-
250
- } );
251
-
252
- return { replaced, retained: materials.length };
253
-
254
- }
255
-
256
- }