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
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "three-gpu-pathtracer",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "Path tracing renderer and utilities for three.js built on top of three-mesh-bvh.",
5
5
  "module": "src/index.js",
6
6
  "main": "build/index.umd.cjs",
7
7
  "type": "module",
8
+ "types": "src/index.d.ts",
8
9
  "sideEffects": false,
9
10
  "files": [
10
11
  "src/*",
@@ -30,24 +31,29 @@
30
31
  ],
31
32
  "devDependencies": {
32
33
  "@lookingglass/webxr": "^0.3.1",
34
+ "@monogrid/gainmap-js": "^3.0.5",
35
+ "@types/node": "^20.12.7",
36
+ "@types/three": "^0.163.0",
37
+ "@typescript-eslint/parser": "^6.21.0",
33
38
  "canvas-capture": "^2.0.5",
34
39
  "eslint": "^7.32.0",
35
40
  "eslint-config-mdcs": "^5.0.0",
36
41
  "node-fetch": "^3.2.9",
37
- "parcel": "^2.4.0",
42
+ "parcel": "^2.12.0",
38
43
  "pixelmatch": "^5.3.0",
39
44
  "pngjs": "^6.0.0",
40
45
  "process": "^0.11.10",
41
46
  "puppeteer": "^15.4.0",
42
47
  "rollup": "^2.70.0",
43
48
  "simple-git": "^3.10.0",
44
- "three": "^0.160.0",
45
- "three-mesh-bvh": "^0.7.3",
49
+ "three": "^0.163.0",
50
+ "three-mesh-bvh": "^0.7.4",
51
+ "typescript": "5.3.3",
46
52
  "yargs": "^17.5.1"
47
53
  },
48
54
  "peerDependencies": {
49
55
  "three": ">=0.151.0",
50
- "three-mesh-bvh": ">=0.7.0",
56
+ "three-mesh-bvh": ">=0.7.4",
51
57
  "xatlas-web": "^0.1.0"
52
58
  },
53
59
  "scripts": {
@@ -56,7 +62,7 @@
56
62
  "update-screenshots": "node ./scripts/push-screenshots.js",
57
63
  "screenshot-diff": "node ./scripts/regression-test.js",
58
64
  "build": "rollup -c",
59
- "lint": "eslint \"./src/**/*.{js,ts}\" \"./example/*.js\"",
65
+ "lint": "eslint \"./src/**/*.{js,ts}\" \"./example/*.js\" && tsc --noEmit",
60
66
  "prepublishOnly": "npm run build"
61
67
  },
62
68
  "repository": {
@@ -1,7 +1,8 @@
1
- import { RGBAFormat, FloatType, HalfFloatType, Color, Vector2, WebGLRenderTarget, NoBlending, NormalBlending, Vector4 } from 'three';
1
+ import { RGBAFormat, FloatType, Color, Vector2, WebGLRenderTarget, NoBlending, NormalBlending, Vector4, NearestFilter } from 'three';
2
2
  import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
3
3
  import { BlendMaterial } from '../materials/fullscreen/BlendMaterial.js';
4
4
  import { SobolNumberMapGenerator } from '../utils/SobolNumberMapGenerator.js';
5
+ import { PhysicalPathTracingMaterial } from '../materials/pathtracing/PhysicalPathTracingMaterial.js';
5
6
 
6
7
  function* renderTask() {
7
8
 
@@ -14,7 +15,6 @@ function* renderTask() {
14
15
  _sobolTarget,
15
16
  _subframe,
16
17
  alpha,
17
- camera,
18
18
  material,
19
19
  } = this;
20
20
  const _ogScissor = new Vector4();
@@ -27,13 +27,13 @@ function* renderTask() {
27
27
 
28
28
  if ( alpha ) {
29
29
 
30
- blendMaterial.opacity = this._opacityFactor / ( this._samples + 1 );
30
+ blendMaterial.opacity = this._opacityFactor / ( this.samples + 1 );
31
31
  material.blending = NoBlending;
32
32
  material.opacity = 1;
33
33
 
34
34
  } else {
35
35
 
36
- material.opacity = this._opacityFactor / ( this._samples + 1 );
36
+ material.opacity = this._opacityFactor / ( this.samples + 1 );
37
37
  material.blending = NormalBlending;
38
38
 
39
39
  }
@@ -64,30 +64,6 @@ function* renderTask() {
64
64
 
65
65
  for ( let x = 0; x < tilesX; x ++ ) {
66
66
 
67
- material.cameraWorldMatrix.copy( camera.matrixWorld );
68
- material.invProjectionMatrix.copy( camera.projectionMatrixInverse );
69
-
70
- // Perspective camera (default)
71
- let cameraType = 0;
72
-
73
- // An orthographic projection matrix will always have the bottom right element == 1
74
- // And a perspective projection matrix will always have the bottom right element == 0
75
- if ( camera.projectionMatrix.elements[ 15 ] > 0 ) {
76
-
77
- // Orthographic
78
- cameraType = 1;
79
-
80
- }
81
-
82
- if ( camera.isEquirectCamera ) {
83
-
84
- // Equirectangular
85
- cameraType = 2;
86
-
87
- }
88
-
89
- material.setDefine( 'CAMERA_TYPE', cameraType );
90
-
91
67
  // store og state
92
68
  const ogRenderTarget = _renderer.getRenderTarget();
93
69
  const ogAutoClear = _renderer.autoClear;
@@ -151,12 +127,12 @@ function* renderTask() {
151
127
 
152
128
  }
153
129
 
154
- this._samples += ( 1 / totalTiles );
130
+ this.samples += ( 1 / totalTiles );
155
131
 
156
132
  // round the samples value if we've finished the tiles
157
133
  if ( x === tilesX - 1 && y === tilesY - 1 ) {
158
134
 
159
- this._samples = Math.round( this._samples );
135
+ this.samples = Math.round( this.samples );
160
136
 
161
137
  }
162
138
 
@@ -219,52 +195,82 @@ export class PathTracingRenderer {
219
195
 
220
196
  }
221
197
 
222
- get samples() {
223
-
224
- return this._samples;
225
-
226
- }
227
-
228
198
  constructor( renderer ) {
229
199
 
230
200
  this.camera = null;
231
- this.tiles = new Vector2( 1, 1 );
201
+ this.tiles = new Vector2( 3, 3 );
232
202
 
233
203
  this.stableNoise = false;
234
204
  this.stableTiles = true;
235
205
 
236
- this._samples = 0;
206
+ this.samples = 0;
237
207
  this._subframe = new Vector4( 0, 0, 1, 1 );
238
208
  this._opacityFactor = 1.0;
239
209
  this._renderer = renderer;
240
210
  this._alpha = false;
241
- this._fsQuad = new FullScreenQuad( null );
211
+ this._fsQuad = new FullScreenQuad( new PhysicalPathTracingMaterial() );
242
212
  this._blendQuad = new FullScreenQuad( new BlendMaterial() );
243
213
  this._task = null;
244
214
  this._currentTile = 0;
245
215
 
246
216
  this._sobolTarget = new SobolNumberMapGenerator().generate( renderer );
247
217
 
248
- // will be null if extension not supported
249
- const floatLinearExtensionSupported = renderer.extensions.get( 'OES_texture_float_linear' );
250
-
251
218
  this._primaryTarget = new WebGLRenderTarget( 1, 1, {
252
219
  format: RGBAFormat,
253
- type: floatLinearExtensionSupported ? FloatType : HalfFloatType,
220
+ type: FloatType,
221
+ magFilter: NearestFilter,
222
+ minFilter: NearestFilter,
254
223
  } );
255
224
  this._blendTargets = [
256
225
  new WebGLRenderTarget( 1, 1, {
257
226
  format: RGBAFormat,
258
- type: floatLinearExtensionSupported ? FloatType : HalfFloatType,
227
+ type: FloatType,
228
+ magFilter: NearestFilter,
229
+ minFilter: NearestFilter,
259
230
  } ),
260
231
  new WebGLRenderTarget( 1, 1, {
261
232
  format: RGBAFormat,
262
- type: floatLinearExtensionSupported ? FloatType : HalfFloatType,
233
+ type: FloatType,
234
+ magFilter: NearestFilter,
235
+ minFilter: NearestFilter,
263
236
  } ),
264
237
  ];
265
238
 
266
239
  }
267
240
 
241
+ setCamera( camera ) {
242
+
243
+ const { material } = this;
244
+ material.cameraWorldMatrix.copy( camera.matrixWorld );
245
+ material.invProjectionMatrix.copy( camera.projectionMatrixInverse );
246
+ material.physicalCamera.updateFrom( camera );
247
+
248
+ // Perspective camera (default)
249
+ let cameraType = 0;
250
+
251
+ // An orthographic projection matrix will always have the bottom right element == 1
252
+ // And a perspective projection matrix will always have the bottom right element == 0
253
+ if ( camera.projectionMatrix.elements[ 15 ] > 0 ) {
254
+
255
+ // Orthographic
256
+ cameraType = 1;
257
+
258
+ }
259
+
260
+ if ( camera.isEquirectCamera ) {
261
+
262
+ // Equirectangular
263
+ cameraType = 2;
264
+
265
+ }
266
+
267
+ material.setDefine( 'CAMERA_TYPE', cameraType );
268
+
269
+ this.camera = camera;
270
+ // this.reset();
271
+
272
+ }
273
+
268
274
  setSize( w, h ) {
269
275
 
270
276
  w = Math.ceil( w );
@@ -283,6 +289,13 @@ export class PathTracingRenderer {
283
289
 
284
290
  }
285
291
 
292
+ getSize( target ) {
293
+
294
+ target.x = this._primaryTarget.width;
295
+ target.y = this._primaryTarget.height;
296
+
297
+ }
298
+
286
299
  dispose() {
287
300
 
288
301
  this._primaryTarget.dispose();
@@ -318,7 +331,7 @@ export class PathTracingRenderer {
318
331
  _renderer.setClearColor( ogClearColor, ogClearAlpha );
319
332
  _renderer.setRenderTarget( ogRenderTarget );
320
333
 
321
- this._samples = 0;
334
+ this.samples = 0;
322
335
  this._task = null;
323
336
 
324
337
  if ( this.stableNoise ) {
@@ -1,24 +1,259 @@
1
- import { DynamicPathTracingSceneGenerator } from './DynamicPathTracingSceneGenerator.js';
1
+ import { BufferGeometry } from 'three';
2
+ import { MeshBVH, SAH } from 'three-mesh-bvh';
3
+ import { StaticGeometryGenerator, NO_CHANGE, GEOMETRY_ADJUSTED, GEOMETRY_REBUILT } from './utils/StaticGeometryGenerator.js';
4
+ import { updateMaterialIndexAttribute } from './utils/GeometryPreparationUtils.js';
5
+
6
+ // collect the textures from the materials
7
+ function getTextures( materials ) {
8
+
9
+ const textureSet = new Set();
10
+ for ( let i = 0, l = materials.length; i < l; i ++ ) {
11
+
12
+ const material = materials[ i ];
13
+ for ( const key in material ) {
14
+
15
+ const value = material[ key ];
16
+ if ( value && value.isTexture ) {
17
+
18
+ textureSet.add( value );
19
+
20
+ }
21
+
22
+ }
23
+
24
+ }
25
+
26
+ return Array.from( textureSet );
27
+
28
+ }
29
+
30
+ // collect the lights in the scene
31
+ function getLights( objects ) {
32
+
33
+ const lights = [];
34
+ const iesSet = new Set();
35
+ for ( let i = 0, l = objects.length; i < l; i ++ ) {
36
+
37
+ objects[ i ].traverse( c => {
38
+
39
+ if ( c.visible ) {
40
+
41
+ if (
42
+ c.isRectAreaLight ||
43
+ c.isSpotLight ||
44
+ c.isPointLight ||
45
+ c.isDirectionalLight
46
+ ) {
47
+
48
+ lights.push( c );
49
+
50
+ if ( c.iesMap ) {
51
+
52
+ iesSet.add( c.iesMap );
53
+
54
+ }
55
+
56
+ }
57
+
58
+ }
59
+
60
+ } );
61
+
62
+ }
63
+
64
+ const iesTextures = Array.from( iesSet ).sort( ( a, b ) => {
65
+
66
+ if ( a.uuid < b.uuid ) return 1;
67
+ if ( a.uuid > b.uuid ) return - 1;
68
+ return 0;
69
+
70
+ } );
71
+
72
+ return { lights, iesTextures };
73
+
74
+ }
2
75
 
3
76
  export class PathTracingSceneGenerator {
4
77
 
5
- generate( scene, options = {} ) {
78
+ get initialized() {
79
+
80
+ return Boolean( this.bvh );
81
+
82
+ }
83
+
84
+ constructor( objects ) {
85
+
86
+ // options
87
+ this.bvhOptions = {};
88
+ this.attributes = [ 'position', 'normal', 'tangent', 'color', 'uv', 'uv2' ];
89
+ this.generateBVH = true;
90
+
91
+ // state
92
+ this.bvh = null;
93
+ this.geometry = new BufferGeometry();
94
+ this.staticGeometryGenerator = new StaticGeometryGenerator( objects );
95
+ this._bvhWorker = null;
96
+ this._pendingGenerate = null;
97
+ this._buildAsync = false;
98
+
99
+ }
100
+
101
+ setObjects( objects ) {
102
+
103
+ this.staticGeometryGenerator.setObjects( objects );
104
+
105
+ }
106
+
107
+ setBVHWorker( bvhWorker ) {
108
+
109
+ this._bvhWorker = bvhWorker;
110
+
111
+ }
112
+
113
+ async generateAsync( onProgress = null ) {
114
+
115
+ if ( ! this._bvhWorker ) {
116
+
117
+ throw new Error( 'PathTracingSceneGenerator: "setBVHWorker" must be called before "generateAsync" can be called.' );
118
+
119
+ }
6
120
 
7
- // ensure scene transforms are up to date
8
- // TODO: remove this?
9
- if ( Array.isArray( scene ) ) {
121
+ if ( this.bvh instanceof Promise ) {
10
122
 
11
- scene.forEach( s => s.updateMatrixWorld( true ) );
123
+ // if a bvh is already being generated we can wait for that to finish
124
+ // and build another with the latest data while sharing the results.
125
+ if ( ! this._pendingGenerate ) {
126
+
127
+ this._pendingGenerate = new Promise( async () => {
128
+
129
+ await this.bvh;
130
+ this._pendingGenerate = null;
131
+
132
+ // TODO: support multiple callbacks queued?
133
+ return this.generateAsync( onProgress );
134
+
135
+ } );
136
+
137
+ }
138
+
139
+ return this._pendingGenerate;
12
140
 
13
141
  } else {
14
142
 
15
- scene.updateMatrixWorld( true );
143
+ this._buildAsync = true;
144
+ const result = this.generate( onProgress );
145
+ this._buildAsync = false;
146
+
147
+ result.bvh = this.bvh = await result.bvh;
148
+ return result;
16
149
 
17
150
  }
18
151
 
19
- const generator = new DynamicPathTracingSceneGenerator( scene );
20
- generator.bvhOptions = options;
21
- return generator.generate();
152
+ }
153
+
154
+ generate( onProgress = null ) {
155
+
156
+ const { staticGeometryGenerator, geometry, attributes } = this;
157
+ const objects = staticGeometryGenerator.objects;
158
+ staticGeometryGenerator.attributes = attributes;
159
+
160
+ // update the skeleton animations in case WebGLRenderer is not running
161
+ // to update it.
162
+ objects.forEach( o => {
163
+
164
+ o.traverse( c => {
165
+
166
+ if ( c.isSkinnedMesh && c.skeleton ) {
167
+
168
+ c.skeleton.update();
169
+
170
+ }
171
+
172
+ } );
173
+
174
+ } );
175
+
176
+ // generate the geometry
177
+ const result = staticGeometryGenerator.generate( geometry );
178
+ const materials = result.materials;
179
+ const textures = getTextures( materials );
180
+ const { lights, iesTextures } = getLights( objects );
181
+
182
+ if ( result.changeType !== NO_CHANGE ) {
183
+
184
+ updateMaterialIndexAttribute( geometry, materials, materials );
185
+
186
+ }
187
+
188
+ // only generate a new bvh if the objects used have changed
189
+ if ( this.generateBVH ) {
190
+
191
+ if ( this.bvh instanceof Promise ) {
192
+
193
+ throw new Error( 'PathTracingSceneGenerator: BVH is already building asynchronously.' );
194
+
195
+ }
196
+
197
+ if ( result.changeType === GEOMETRY_REBUILT ) {
198
+
199
+ const bvhOptions = {
200
+ strategy: SAH,
201
+ maxLeafTris: 1,
202
+ indirect: true,
203
+ onProgress,
204
+ ...this.bvhOptions,
205
+ };
206
+
207
+ if ( this._buildAsync ) {
208
+
209
+ this.bvh = this._bvhWorker.generate( geometry, bvhOptions );
210
+
211
+ } else {
212
+
213
+ this.bvh = new MeshBVH( geometry, bvhOptions );
214
+
215
+ }
216
+
217
+ } else if ( result.changeType === GEOMETRY_ADJUSTED ) {
218
+
219
+ this.bvh.refit();
220
+
221
+ }
222
+
223
+ }
224
+
225
+ return {
226
+ bvhChanged: result.changeType !== NO_CHANGE,
227
+ bvh: this.bvh,
228
+ lights,
229
+ iesTextures,
230
+ geometry,
231
+ materials,
232
+ textures,
233
+ objects,
234
+ };
235
+
236
+ }
237
+
238
+ }
239
+
240
+ export class DynamicPathTracingSceneGenerator extends PathTracingSceneGenerator {
241
+
242
+ constructor( ...args ) {
243
+
244
+ super( ...args );
245
+ console.warn( 'DynamicPathTracingSceneGenerator has been deprecated and renamed to "PathTracingSceneGenerator".' );
246
+
247
+ }
248
+
249
+ }
250
+
251
+ export class PathTracingSceneWorker extends PathTracingSceneGenerator {
252
+
253
+ constructor( ...args ) {
254
+
255
+ super( ...args );
256
+ console.warn( 'PathTracingSceneWorker has been deprecated and renamed to "PathTracingSceneGenerator".' );
22
257
 
23
258
  }
24
259