super-three 0.157.0 → 0.157.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.
- package/build/three.cjs +474 -53
- package/build/three.js +474 -53
- package/build/three.min.js +1 -1
- package/build/three.module.js +474 -53
- package/build/three.module.min.js +1 -1
- package/package.json +1 -1
- package/src/renderers/WebGLMultiviewRenderTarget.js +35 -0
- package/src/renderers/WebGLRenderer.js +57 -20
- package/src/renderers/webgl/WebGLBackground.js +1 -1
- package/src/renderers/webgl/WebGLMultiview.js +100 -0
- package/src/renderers/webgl/WebGLProgram.js +50 -0
- package/src/renderers/webgl/WebGLPrograms.js +5 -0
- package/src/renderers/webgl/WebGLTextures.js +197 -20
- package/src/renderers/webxr/WebXRManager.js +36 -12
package/package.json
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author fernandojsg / http://fernandojsg.com
|
|
3
|
+
* @author Takahiro https://github.com/takahirox
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { WebGLRenderTarget } from './WebGLRenderTarget.js';
|
|
7
|
+
|
|
8
|
+
class WebGLMultiviewRenderTarget extends WebGLRenderTarget {
|
|
9
|
+
|
|
10
|
+
constructor( width, height, numViews, options = {} ) {
|
|
11
|
+
|
|
12
|
+
super( width, height, options );
|
|
13
|
+
|
|
14
|
+
this.depthBuffer = false;
|
|
15
|
+
this.stencilBuffer = false;
|
|
16
|
+
|
|
17
|
+
this.numViews = numViews;
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
copy( source ) {
|
|
22
|
+
|
|
23
|
+
super.copy( source );
|
|
24
|
+
|
|
25
|
+
this.numViews = source.numViews;
|
|
26
|
+
|
|
27
|
+
return this;
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
WebGLMultiviewRenderTarget.prototype.isWebGLMultiviewRenderTarget = true;
|
|
34
|
+
|
|
35
|
+
export { WebGLMultiviewRenderTarget };
|
|
@@ -46,6 +46,7 @@ import { WebGLGeometries } from './webgl/WebGLGeometries.js';
|
|
|
46
46
|
import { WebGLIndexedBufferRenderer } from './webgl/WebGLIndexedBufferRenderer.js';
|
|
47
47
|
import { WebGLInfo } from './webgl/WebGLInfo.js';
|
|
48
48
|
import { WebGLMorphtargets } from './webgl/WebGLMorphtargets.js';
|
|
49
|
+
import { WebGLMultiview } from './webgl/WebGLMultiview.js';
|
|
49
50
|
import { WebGLObjects } from './webgl/WebGLObjects.js';
|
|
50
51
|
import { WebGLPrograms } from './webgl/WebGLPrograms.js';
|
|
51
52
|
import { WebGLProperties } from './webgl/WebGLProperties.js';
|
|
@@ -79,6 +80,7 @@ class WebGLRenderer {
|
|
|
79
80
|
preserveDrawingBuffer = false,
|
|
80
81
|
powerPreference = 'default',
|
|
81
82
|
failIfMajorPerformanceCaveat = false,
|
|
83
|
+
multiviewStereo = false,
|
|
82
84
|
} = parameters;
|
|
83
85
|
|
|
84
86
|
this.isWebGLRenderer = true;
|
|
@@ -313,6 +315,7 @@ class WebGLRenderer {
|
|
|
313
315
|
let extensions, capabilities, state, info;
|
|
314
316
|
let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects;
|
|
315
317
|
let programCache, materials, renderLists, renderStates, clipping, shadowMap;
|
|
318
|
+
let multiview;
|
|
316
319
|
|
|
317
320
|
let background, morphtargets, bufferRenderer, indexedBufferRenderer;
|
|
318
321
|
|
|
@@ -346,6 +349,7 @@ class WebGLRenderer {
|
|
|
346
349
|
renderLists = new WebGLRenderLists();
|
|
347
350
|
renderStates = new WebGLRenderStates( extensions, capabilities );
|
|
348
351
|
background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha );
|
|
352
|
+
multiview = new WebGLMultiview( _this, extensions, _gl );
|
|
349
353
|
shadowMap = new WebGLShadowMap( _this, objects, capabilities );
|
|
350
354
|
uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state );
|
|
351
355
|
|
|
@@ -368,7 +372,7 @@ class WebGLRenderer {
|
|
|
368
372
|
|
|
369
373
|
// xr
|
|
370
374
|
|
|
371
|
-
const xr = ( typeof navigator !== 'undefined' && 'xr' in navigator ) ? new WebXRManager( _this, _gl ) : new WebVRManager( _this );
|
|
375
|
+
const xr = ( typeof navigator !== 'undefined' && 'xr' in navigator ) ? new WebXRManager( _this, _gl, extensions, multiviewStereo ) : new WebVRManager( _this );
|
|
372
376
|
|
|
373
377
|
this.xr = xr;
|
|
374
378
|
|
|
@@ -1115,13 +1119,23 @@ class WebGLRenderer {
|
|
|
1115
1119
|
|
|
1116
1120
|
if ( camera.isArrayCamera ) {
|
|
1117
1121
|
|
|
1118
|
-
|
|
1122
|
+
if ( xr.enabled && xr.isMultiview ) {
|
|
1119
1123
|
|
|
1120
|
-
|
|
1124
|
+
textures.setDeferTextureUploads( true );
|
|
1121
1125
|
|
|
1122
|
-
|
|
1126
|
+
renderScene( currentRenderList, scene, camera, camera.cameras[ 0 ].viewport );
|
|
1123
1127
|
|
|
1124
|
-
|
|
1128
|
+
} else {
|
|
1129
|
+
|
|
1130
|
+
const cameras = camera.cameras;
|
|
1131
|
+
|
|
1132
|
+
for ( let i = 0, l = cameras.length; i < l; i ++ ) {
|
|
1133
|
+
|
|
1134
|
+
const camera2 = cameras[ i ];
|
|
1135
|
+
|
|
1136
|
+
renderScene( currentRenderList, scene, camera2, camera2.viewport );
|
|
1137
|
+
|
|
1138
|
+
}
|
|
1125
1139
|
|
|
1126
1140
|
}
|
|
1127
1141
|
|
|
@@ -1149,6 +1163,8 @@ class WebGLRenderer {
|
|
|
1149
1163
|
|
|
1150
1164
|
if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera );
|
|
1151
1165
|
|
|
1166
|
+
textures.runDeferredUploads();
|
|
1167
|
+
|
|
1152
1168
|
if ( xr.enabled && xr.submitFrame ) {
|
|
1153
1169
|
|
|
1154
1170
|
xr.submitFrame();
|
|
@@ -1616,6 +1632,7 @@ class WebGLRenderer {
|
|
|
1616
1632
|
materialProperties.vertexAlphas = parameters.vertexAlphas;
|
|
1617
1633
|
materialProperties.vertexTangents = parameters.vertexTangents;
|
|
1618
1634
|
materialProperties.toneMapping = parameters.toneMapping;
|
|
1635
|
+
materialProperties.numMultiviewViews = parameters.numMultiviewViews;
|
|
1619
1636
|
|
|
1620
1637
|
}
|
|
1621
1638
|
|
|
@@ -1647,6 +1664,8 @@ class WebGLRenderer {
|
|
|
1647
1664
|
|
|
1648
1665
|
}
|
|
1649
1666
|
|
|
1667
|
+
const numMultiviewViews = _currentRenderTarget && _currentRenderTarget.isWebGLMultiviewRenderTarget ? _currentRenderTarget.numViews : 0;
|
|
1668
|
+
|
|
1650
1669
|
const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
|
|
1651
1670
|
const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;
|
|
1652
1671
|
|
|
@@ -1750,6 +1769,10 @@ class WebGLRenderer {
|
|
|
1750
1769
|
|
|
1751
1770
|
needsProgramChange = true;
|
|
1752
1771
|
|
|
1772
|
+
} else if ( materialProperties.numMultiviewViews !== numMultiviewViews ) {
|
|
1773
|
+
|
|
1774
|
+
needsProgramChange = true;
|
|
1775
|
+
|
|
1753
1776
|
}
|
|
1754
1777
|
|
|
1755
1778
|
} else {
|
|
@@ -1796,8 +1819,17 @@ class WebGLRenderer {
|
|
|
1796
1819
|
|
|
1797
1820
|
// common camera uniforms
|
|
1798
1821
|
|
|
1799
|
-
|
|
1800
|
-
|
|
1822
|
+
if ( program.numMultiviewViews > 0 ) {
|
|
1823
|
+
|
|
1824
|
+
multiview.updateCameraProjectionMatricesUniform( camera, p_uniforms );
|
|
1825
|
+
multiview.updateCameraViewMatricesUniform( camera, p_uniforms );
|
|
1826
|
+
|
|
1827
|
+
} else {
|
|
1828
|
+
|
|
1829
|
+
p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix );
|
|
1830
|
+
p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );
|
|
1831
|
+
|
|
1832
|
+
}
|
|
1801
1833
|
|
|
1802
1834
|
const uCamPos = p_uniforms.map.cameraPosition;
|
|
1803
1835
|
|
|
@@ -1945,8 +1977,17 @@ class WebGLRenderer {
|
|
|
1945
1977
|
|
|
1946
1978
|
// common matrices
|
|
1947
1979
|
|
|
1948
|
-
|
|
1949
|
-
|
|
1980
|
+
if ( program.numMultiviewViews > 0 ) {
|
|
1981
|
+
|
|
1982
|
+
multiview.updateObjectMatricesUniforms( object, camera, p_uniforms );
|
|
1983
|
+
|
|
1984
|
+
} else {
|
|
1985
|
+
|
|
1986
|
+
p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );
|
|
1987
|
+
p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );
|
|
1988
|
+
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1950
1991
|
p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );
|
|
1951
1992
|
|
|
1952
1993
|
// UBOs
|
|
@@ -2056,20 +2097,16 @@ class WebGLRenderer {
|
|
|
2056
2097
|
const renderTargetProperties = properties.get( renderTarget );
|
|
2057
2098
|
renderTargetProperties.__hasExternalTextures = true;
|
|
2058
2099
|
|
|
2059
|
-
|
|
2100
|
+
renderTargetProperties.__autoAllocateDepthBuffer = depthTexture === undefined;
|
|
2060
2101
|
|
|
2061
|
-
|
|
2102
|
+
if ( ! renderTargetProperties.__autoAllocateDepthBuffer && ! _currentRenderTarget.isWebGLMultiviewRenderTarget ) {
|
|
2062
2103
|
|
|
2063
|
-
|
|
2104
|
+
// The multisample_render_to_texture extension doesn't work properly if there
|
|
2105
|
+
// are midframe flushes and an external depth buffer. Disable use of the extension.
|
|
2106
|
+
if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) {
|
|
2064
2107
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) {
|
|
2068
|
-
|
|
2069
|
-
console.warn( 'THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided' );
|
|
2070
|
-
renderTargetProperties.__useRenderToTexture = false;
|
|
2071
|
-
|
|
2072
|
-
}
|
|
2108
|
+
console.warn( 'THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided' );
|
|
2109
|
+
renderTargetProperties.__useRenderToTexture = false;
|
|
2073
2110
|
|
|
2074
2111
|
}
|
|
2075
2112
|
|
|
@@ -68,7 +68,7 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha,
|
|
|
68
68
|
if ( boxMesh === undefined ) {
|
|
69
69
|
|
|
70
70
|
boxMesh = new Mesh(
|
|
71
|
-
new BoxGeometry(
|
|
71
|
+
new BoxGeometry( 10000, 10000, 10000 ),
|
|
72
72
|
new ShaderMaterial( {
|
|
73
73
|
name: 'BackgroundCubeMaterial',
|
|
74
74
|
uniforms: cloneUniforms( ShaderLib.backgroundCube.uniforms ),
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author fernandojsg / http://fernandojsg.com
|
|
3
|
+
* @author Takahiro https://github.com/takahirox
|
|
4
|
+
*/
|
|
5
|
+
import { Matrix3 } from '../../math/Matrix3.js';
|
|
6
|
+
import { Matrix4 } from '../../math/Matrix4.js';
|
|
7
|
+
|
|
8
|
+
class WebGLMultiview {
|
|
9
|
+
|
|
10
|
+
constructor( renderer, extensions, gl ) {
|
|
11
|
+
|
|
12
|
+
this.renderer = renderer;
|
|
13
|
+
|
|
14
|
+
this.DEFAULT_NUMVIEWS = 2;
|
|
15
|
+
this.maxNumViews = 0;
|
|
16
|
+
this.gl = gl;
|
|
17
|
+
|
|
18
|
+
this.extensions = extensions;
|
|
19
|
+
|
|
20
|
+
this.available = this.extensions.has( 'OCULUS_multiview' );
|
|
21
|
+
|
|
22
|
+
if ( this.available ) {
|
|
23
|
+
|
|
24
|
+
const extension = this.extensions.get( 'OCULUS_multiview' );
|
|
25
|
+
|
|
26
|
+
this.maxNumViews = this.gl.getParameter( extension.MAX_VIEWS_OVR );
|
|
27
|
+
|
|
28
|
+
this.mat4 = [];
|
|
29
|
+
this.mat3 = [];
|
|
30
|
+
this.cameraArray = [];
|
|
31
|
+
|
|
32
|
+
for ( var i = 0; i < this.maxNumViews; i ++ ) {
|
|
33
|
+
|
|
34
|
+
this.mat4[ i ] = new Matrix4();
|
|
35
|
+
this.mat3[ i ] = new Matrix3();
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//
|
|
44
|
+
getCameraArray( camera ) {
|
|
45
|
+
|
|
46
|
+
if ( camera.isArrayCamera ) return camera.cameras;
|
|
47
|
+
|
|
48
|
+
this.cameraArray[ 0 ] = camera;
|
|
49
|
+
|
|
50
|
+
return this.cameraArray;
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
updateCameraProjectionMatricesUniform( camera, uniforms ) {
|
|
55
|
+
|
|
56
|
+
var cameras = this.getCameraArray( camera );
|
|
57
|
+
|
|
58
|
+
for ( var i = 0; i < cameras.length; i ++ ) {
|
|
59
|
+
|
|
60
|
+
this.mat4[ i ].copy( cameras[ i ].projectionMatrix );
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
uniforms.setValue( this.gl, 'projectionMatrices', this.mat4 );
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
updateCameraViewMatricesUniform( camera, uniforms ) {
|
|
69
|
+
|
|
70
|
+
var cameras = this.getCameraArray( camera );
|
|
71
|
+
|
|
72
|
+
for ( var i = 0; i < cameras.length; i ++ ) {
|
|
73
|
+
|
|
74
|
+
this.mat4[ i ].copy( cameras[ i ].matrixWorldInverse );
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
uniforms.setValue( this.gl, 'viewMatrices', this.mat4 );
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
updateObjectMatricesUniforms( object, camera, uniforms ) {
|
|
83
|
+
|
|
84
|
+
var cameras = this.getCameraArray( camera );
|
|
85
|
+
|
|
86
|
+
for ( var i = 0; i < cameras.length; i ++ ) {
|
|
87
|
+
|
|
88
|
+
this.mat4[ i ].multiplyMatrices( cameras[ i ].matrixWorldInverse, object.matrixWorld );
|
|
89
|
+
this.mat3[ i ].getNormalMatrix( this.mat4[ i ] );
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
uniforms.setValue( this.gl, 'modelViewMatrices', this.mat4 );
|
|
94
|
+
uniforms.setValue( this.gl, 'normalMatrices', this.mat3 );
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export { WebGLMultiview };
|
|
@@ -455,6 +455,8 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
|
|
|
455
455
|
let prefixVertex, prefixFragment;
|
|
456
456
|
let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : '';
|
|
457
457
|
|
|
458
|
+
const numMultiviewViews = parameters.numMultiviewViews;
|
|
459
|
+
|
|
458
460
|
if ( parameters.isRawShaderMaterial ) {
|
|
459
461
|
|
|
460
462
|
prefixVertex = [
|
|
@@ -861,6 +863,53 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
|
|
|
861
863
|
'#define textureCubeGradEXT textureGrad'
|
|
862
864
|
].join( '\n' ) + '\n' + prefixFragment;
|
|
863
865
|
|
|
866
|
+
// Multiview
|
|
867
|
+
|
|
868
|
+
if ( numMultiviewViews > 0 ) {
|
|
869
|
+
|
|
870
|
+
// TODO: fix light transforms here?
|
|
871
|
+
|
|
872
|
+
prefixVertex = [
|
|
873
|
+
'#extension GL_OVR_multiview : require',
|
|
874
|
+
'layout(num_views = ' + numMultiviewViews + ') in;',
|
|
875
|
+
'#define VIEW_ID gl_ViewID_OVR'
|
|
876
|
+
].join( '\n' ) + '\n' + prefixVertex;
|
|
877
|
+
|
|
878
|
+
prefixVertex = prefixVertex.replace(
|
|
879
|
+
[
|
|
880
|
+
'uniform mat4 modelViewMatrix;',
|
|
881
|
+
'uniform mat4 projectionMatrix;',
|
|
882
|
+
'uniform mat4 viewMatrix;',
|
|
883
|
+
'uniform mat3 normalMatrix;'
|
|
884
|
+
].join( '\n' ),
|
|
885
|
+
[
|
|
886
|
+
'uniform mat4 modelViewMatrices[' + numMultiviewViews + '];',
|
|
887
|
+
'uniform mat4 projectionMatrices[' + numMultiviewViews + '];',
|
|
888
|
+
'uniform mat4 viewMatrices[' + numMultiviewViews + '];',
|
|
889
|
+
'uniform mat3 normalMatrices[' + numMultiviewViews + '];',
|
|
890
|
+
|
|
891
|
+
'#define modelViewMatrix modelViewMatrices[VIEW_ID]',
|
|
892
|
+
'#define projectionMatrix projectionMatrices[VIEW_ID]',
|
|
893
|
+
'#define viewMatrix viewMatrices[VIEW_ID]',
|
|
894
|
+
'#define normalMatrix normalMatrices[VIEW_ID]'
|
|
895
|
+
].join( '\n' )
|
|
896
|
+
);
|
|
897
|
+
|
|
898
|
+
prefixFragment = [
|
|
899
|
+
'#extension GL_OVR_multiview : require',
|
|
900
|
+
'#define VIEW_ID gl_ViewID_OVR'
|
|
901
|
+
].join( '\n' ) + '\n' + prefixFragment;
|
|
902
|
+
|
|
903
|
+
prefixFragment = prefixFragment.replace(
|
|
904
|
+
'uniform mat4 viewMatrix;',
|
|
905
|
+
[
|
|
906
|
+
'uniform mat4 viewMatrices[' + numMultiviewViews + '];',
|
|
907
|
+
'#define viewMatrix viewMatrices[VIEW_ID]'
|
|
908
|
+
].join( '\n' )
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
}
|
|
912
|
+
|
|
864
913
|
}
|
|
865
914
|
|
|
866
915
|
const vertexGlsl = versionString + prefixVertex + vertexShader;
|
|
@@ -1025,6 +1074,7 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
|
|
|
1025
1074
|
this.program = program;
|
|
1026
1075
|
this.vertexShader = glVertexShader;
|
|
1027
1076
|
this.fragmentShader = glFragmentShader;
|
|
1077
|
+
this.numMultiviewViews = numMultiviewViews;
|
|
1028
1078
|
|
|
1029
1079
|
return this;
|
|
1030
1080
|
|
|
@@ -107,6 +107,8 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
|
|
|
107
107
|
|
|
108
108
|
const currentRenderTarget = renderer.getRenderTarget();
|
|
109
109
|
|
|
110
|
+
const numMultiviewViews = currentRenderTarget && currentRenderTarget.isWebGLMultiviewRenderTarget ? currentRenderTarget.numViews : 0;
|
|
111
|
+
|
|
110
112
|
const IS_INSTANCEDMESH = object.isInstancedMesh === true;
|
|
111
113
|
|
|
112
114
|
const HAS_MAP = !! material.map;
|
|
@@ -197,6 +199,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
|
|
|
197
199
|
instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null,
|
|
198
200
|
|
|
199
201
|
supportsVertexTextures: SUPPORTS_VERTEX_TEXTURES,
|
|
202
|
+
numMultiviewViews: numMultiviewViews,
|
|
200
203
|
outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ),
|
|
201
204
|
|
|
202
205
|
map: HAS_MAP,
|
|
@@ -544,6 +547,8 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
|
|
|
544
547
|
_programLayers.enable( 18 );
|
|
545
548
|
if ( parameters.decodeVideoTexture )
|
|
546
549
|
_programLayers.enable( 19 );
|
|
550
|
+
if ( parameters.numMultiviewViews )
|
|
551
|
+
_programLayers.enable( 20 );
|
|
547
552
|
|
|
548
553
|
array.push( _programLayers.mask );
|
|
549
554
|
|