|
@@ -6,7 +6,7 @@
|
|
|
THREE.JSONLoader = function ( showStatus ) {
|
|
|
|
|
|
THREE.Loader.call( this, showStatus );
|
|
|
-
|
|
|
+
|
|
|
};
|
|
|
|
|
|
THREE.JSONLoader.prototype = new THREE.Loader();
|
|
@@ -14,342 +14,334 @@ THREE.JSONLoader.prototype.constructor = THREE.JSONLoader;
|
|
|
THREE.JSONLoader.prototype.supr = THREE.Loader.prototype;
|
|
|
|
|
|
|
|
|
-THREE.JSONLoader.prototype = {
|
|
|
+/**
|
|
|
+ * Load models generated by slim OBJ converter with ASCII option (converter_obj_three_slim.py -t ascii)
|
|
|
+ * - parameters
|
|
|
+ * - model (required)
|
|
|
+ * - callback (required)
|
|
|
+ * - texture_path (optional: if not specified, textures will be assumed to be in the same folder as JS model file)
|
|
|
+ */
|
|
|
|
|
|
- // Load models generated by slim OBJ converter with ASCII option (converter_obj_three_slim.py -t ascii)
|
|
|
- // - parameters
|
|
|
- // - model (required)
|
|
|
- // - callback (required)
|
|
|
- // - texture_path (optional: if not specified, textures will be assumed to be in the same folder as JS model file)
|
|
|
+THREE.JSONLoader.prototype.load = function ( parameters ) {
|
|
|
|
|
|
- load: function ( parameters ) {
|
|
|
-
|
|
|
- var url = parameters.model,
|
|
|
- callback = parameters.callback,
|
|
|
- texture_path = parameters.texture_path ? parameters.texture_path : THREE.Loader.prototype.extractUrlbase( url ),
|
|
|
-
|
|
|
- s = (new Date).getTime(),
|
|
|
- worker = new Worker( url );
|
|
|
-
|
|
|
- worker.onmessage = function( event ) {
|
|
|
-
|
|
|
- THREE.JSONLoader.prototype.createModel( event.data, callback, texture_path );
|
|
|
+ var scope = this,
|
|
|
+ url = parameters.model,
|
|
|
+ callback = parameters.callback,
|
|
|
+ texture_path = parameters.texture_path ? parameters.texture_path : this.extractUrlbase( url ),
|
|
|
+ worker = new Worker( url );
|
|
|
|
|
|
- };
|
|
|
+ worker.onmessage = function ( event ) {
|
|
|
|
|
|
- worker.postMessage( s );
|
|
|
+ scope.createModel( event.data, callback, texture_path );
|
|
|
+ scope.onLoadComplete();
|
|
|
|
|
|
- },
|
|
|
-
|
|
|
- createModel: function ( json, callback, texture_path ) {
|
|
|
+ };
|
|
|
|
|
|
- var Model = function ( texture_path ) {
|
|
|
+ this.onLoadStart();
|
|
|
+ worker.postMessage( new Date().getTime() );
|
|
|
|
|
|
- var scope = this;
|
|
|
+};
|
|
|
|
|
|
- THREE.Geometry.call( this );
|
|
|
+THREE.JSONLoader.prototype.createModel = function ( json, callback, texture_path ) {
|
|
|
|
|
|
- THREE.Loader.prototype.init_materials( scope, json.materials, texture_path );
|
|
|
-
|
|
|
- parse();
|
|
|
- init_skin();
|
|
|
- init_morphing();
|
|
|
+ var scope = this,
|
|
|
+ geometry = new THREE.Geometry();
|
|
|
|
|
|
- this.computeCentroids();
|
|
|
- this.computeFaceNormals();
|
|
|
-
|
|
|
- function parse() {
|
|
|
+ this.init_materials( geometry, json.materials, texture_path );
|
|
|
|
|
|
- if ( json.version === undefined || json.version != 2 ) {
|
|
|
+ parse();
|
|
|
|
|
|
- console.error( 'Deprecated file format.' );
|
|
|
- return;
|
|
|
+ init_skin();
|
|
|
+ init_morphing();
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- function isBitSet( value, position ) {
|
|
|
-
|
|
|
- return value & ( 1 << position );
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- var i, j, fi,
|
|
|
-
|
|
|
- offset, zLength,
|
|
|
-
|
|
|
- type,
|
|
|
- isQuad,
|
|
|
- hasMaterial,
|
|
|
- hasFaceUv, hasFaceVertexUv,
|
|
|
- hasFaceNormal, hasFaceVertexNormal,
|
|
|
- hasFaceColor, hasFaceVertexColor,
|
|
|
-
|
|
|
- vertex, face,
|
|
|
-
|
|
|
- faces = json.faces,
|
|
|
- vertices = json.vertices,
|
|
|
- normals = json.normals,
|
|
|
- colors = json.colors,
|
|
|
-
|
|
|
- nUvLayers = 0;
|
|
|
-
|
|
|
- // disregard empty arrays
|
|
|
-
|
|
|
- for ( i = 0; i < json.uvs.length; i++ ) {
|
|
|
-
|
|
|
- if ( json.uvs[ i ].length ) nUvLayers ++;
|
|
|
+ geometry.computeCentroids();
|
|
|
+ geometry.computeFaceNormals();
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- for ( i = 0; i < nUvLayers; i++ ) {
|
|
|
+ function parse() {
|
|
|
|
|
|
- scope.faceUvs[ i ] = [];
|
|
|
- scope.faceVertexUvs[ i ] = [];
|
|
|
+ if ( json.version === undefined || json.version != 2 ) {
|
|
|
|
|
|
- }
|
|
|
+ console.error( 'Deprecated file format.' );
|
|
|
+ return;
|
|
|
|
|
|
- offset = 0;
|
|
|
- zLength = vertices.length;
|
|
|
-
|
|
|
- while ( offset < zLength ) {
|
|
|
+ }
|
|
|
|
|
|
- vertex = new THREE.Vertex();
|
|
|
-
|
|
|
- vertex.position.x = vertices[ offset ++ ];
|
|
|
- vertex.position.y = vertices[ offset ++ ];
|
|
|
- vertex.position.z = vertices[ offset ++ ];
|
|
|
+ function isBitSet( value, position ) {
|
|
|
|
|
|
- scope.vertices.push( vertex );
|
|
|
+ return value & ( 1 << position );
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- offset = 0;
|
|
|
- zLength = faces.length;
|
|
|
+ };
|
|
|
|
|
|
- while ( offset < zLength ) {
|
|
|
+ var i, j, fi,
|
|
|
|
|
|
- type = faces[ offset ++ ];
|
|
|
+ offset, zLength,
|
|
|
|
|
|
-
|
|
|
- isQuad = isBitSet( type, 0 );
|
|
|
- hasMaterial = isBitSet( type, 1 );
|
|
|
- hasFaceUv = isBitSet( type, 2 );
|
|
|
- hasFaceVertexUv = isBitSet( type, 3 );
|
|
|
- hasFaceNormal = isBitSet( type, 4 );
|
|
|
- hasFaceVertexNormal = isBitSet( type, 5 );
|
|
|
- hasFaceColor = isBitSet( type, 6 );
|
|
|
- hasFaceVertexColor = isBitSet( type, 7 );
|
|
|
+ type,
|
|
|
+ isQuad,
|
|
|
+ hasMaterial,
|
|
|
+ hasFaceUv, hasFaceVertexUv,
|
|
|
+ hasFaceNormal, hasFaceVertexNormal,
|
|
|
+ hasFaceColor, hasFaceVertexColor,
|
|
|
|
|
|
- //console.log("type", type, "bits", isQuad, hasMaterial, hasFaceUv, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor);
|
|
|
+ vertex, face,
|
|
|
|
|
|
- if ( isQuad ) {
|
|
|
+ faces = json.faces,
|
|
|
+ vertices = json.vertices,
|
|
|
+ normals = json.normals,
|
|
|
+ colors = json.colors,
|
|
|
|
|
|
- face = new THREE.Face4();
|
|
|
-
|
|
|
- face.a = faces[ offset ++ ];
|
|
|
- face.b = faces[ offset ++ ];
|
|
|
- face.c = faces[ offset ++ ];
|
|
|
- face.d = faces[ offset ++ ];
|
|
|
+ nUvLayers = 0;
|
|
|
|
|
|
- nVertices = 4;
|
|
|
+ // disregard empty arrays
|
|
|
|
|
|
- } else {
|
|
|
+ for ( i = 0; i < json.uvs.length; i++ ) {
|
|
|
|
|
|
- face = new THREE.Face3();
|
|
|
-
|
|
|
- face.a = faces[ offset ++ ];
|
|
|
- face.b = faces[ offset ++ ];
|
|
|
- face.c = faces[ offset ++ ];
|
|
|
+ if ( json.uvs[ i ].length ) nUvLayers ++;
|
|
|
|
|
|
- nVertices = 3;
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- if ( hasMaterial ) {
|
|
|
+ for ( i = 0; i < nUvLayers; i++ ) {
|
|
|
|
|
|
- materialIndex = faces[ offset ++ ];
|
|
|
- face.materials = scope.materials[ materialIndex ];
|
|
|
+ geometry.faceUvs[ i ] = [];
|
|
|
+ geometry.faceVertexUvs[ i ] = [];
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- if ( hasFaceUv ) {
|
|
|
+ offset = 0;
|
|
|
+ zLength = vertices.length;
|
|
|
|
|
|
- for ( i = 0; i < nUvLayers; i++ ) {
|
|
|
+ while ( offset < zLength ) {
|
|
|
|
|
|
- uvLayer = json.uvs[ i ];
|
|
|
+ vertex = new THREE.Vertex();
|
|
|
|
|
|
- uvIndex = faces[ offset ++ ];
|
|
|
+ vertex.position.x = vertices[ offset ++ ];
|
|
|
+ vertex.position.y = vertices[ offset ++ ];
|
|
|
+ vertex.position.z = vertices[ offset ++ ];
|
|
|
|
|
|
- u = uvLayer[ uvIndex * 2 ];
|
|
|
- v = uvLayer[ uvIndex * 2 + 1 ];
|
|
|
+ geometry.vertices.push( vertex );
|
|
|
|
|
|
- scope.faceUvs[ i ].push( new THREE.UV( u, v ) );
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
+ offset = 0;
|
|
|
+ zLength = faces.length;
|
|
|
|
|
|
- }
|
|
|
+ while ( offset < zLength ) {
|
|
|
|
|
|
- if ( hasFaceVertexUv ) {
|
|
|
+ type = faces[ offset ++ ];
|
|
|
|
|
|
- for ( i = 0; i < nUvLayers; i++ ) {
|
|
|
-
|
|
|
- uvLayer = json.uvs[ i ];
|
|
|
|
|
|
- uvs = [];
|
|
|
+ isQuad = isBitSet( type, 0 );
|
|
|
+ hasMaterial = isBitSet( type, 1 );
|
|
|
+ hasFaceUv = isBitSet( type, 2 );
|
|
|
+ hasFaceVertexUv = isBitSet( type, 3 );
|
|
|
+ hasFaceNormal = isBitSet( type, 4 );
|
|
|
+ hasFaceVertexNormal = isBitSet( type, 5 );
|
|
|
+ hasFaceColor = isBitSet( type, 6 );
|
|
|
+ hasFaceVertexColor = isBitSet( type, 7 );
|
|
|
|
|
|
- for ( j = 0; j < nVertices; j ++ ) {
|
|
|
+ //console.log("type", type, "bits", isQuad, hasMaterial, hasFaceUv, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor);
|
|
|
|
|
|
- uvIndex = faces[ offset ++ ];
|
|
|
+ if ( isQuad ) {
|
|
|
|
|
|
- u = uvLayer[ uvIndex * 2 ];
|
|
|
- v = uvLayer[ uvIndex * 2 + 1 ];
|
|
|
+ face = new THREE.Face4();
|
|
|
|
|
|
- uvs[ j ] = new THREE.UV( u, v );
|
|
|
+ face.a = faces[ offset ++ ];
|
|
|
+ face.b = faces[ offset ++ ];
|
|
|
+ face.c = faces[ offset ++ ];
|
|
|
+ face.d = faces[ offset ++ ];
|
|
|
|
|
|
- }
|
|
|
+ nVertices = 4;
|
|
|
|
|
|
- // to get face <=> uv index correspondence
|
|
|
-
|
|
|
- fi = scope.faces.length;
|
|
|
- scope.faceVertexUvs[ i ][ fi ] = uvs;
|
|
|
+ } else {
|
|
|
|
|
|
- }
|
|
|
+ face = new THREE.Face3();
|
|
|
|
|
|
- }
|
|
|
+ face.a = faces[ offset ++ ];
|
|
|
+ face.b = faces[ offset ++ ];
|
|
|
+ face.c = faces[ offset ++ ];
|
|
|
|
|
|
- if ( hasFaceNormal ) {
|
|
|
+ nVertices = 3;
|
|
|
|
|
|
- normalIndex = faces[ offset ++ ] * 3;
|
|
|
+ }
|
|
|
|
|
|
- normal = new THREE.Vector3();
|
|
|
-
|
|
|
- normal.x = normals[ normalIndex ++ ];
|
|
|
- normal.y = normals[ normalIndex ++ ];
|
|
|
- normal.z = normals[ normalIndex ];
|
|
|
+ if ( hasMaterial ) {
|
|
|
|
|
|
- face.normal = normal;
|
|
|
+ materialIndex = faces[ offset ++ ];
|
|
|
+ face.materials = geometry.materials[ materialIndex ];
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- if ( hasFaceVertexNormal ) {
|
|
|
+ if ( hasFaceUv ) {
|
|
|
|
|
|
- for ( i = 0; i < nVertices; i++ ) {
|
|
|
+ for ( i = 0; i < nUvLayers; i++ ) {
|
|
|
|
|
|
- normalIndex = faces[ offset ++ ] * 3;
|
|
|
+ uvLayer = json.uvs[ i ];
|
|
|
|
|
|
- normal = new THREE.Vector3();
|
|
|
-
|
|
|
- normal.x = normals[ normalIndex ++ ];
|
|
|
- normal.y = normals[ normalIndex ++ ];
|
|
|
- normal.z = normals[ normalIndex ];
|
|
|
+ uvIndex = faces[ offset ++ ];
|
|
|
|
|
|
- face.vertexNormals.push( normal );
|
|
|
+ u = uvLayer[ uvIndex * 2 ];
|
|
|
+ v = uvLayer[ uvIndex * 2 + 1 ];
|
|
|
|
|
|
- }
|
|
|
+ geometry.faceUvs[ i ].push( new THREE.UV( u, v ) );
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
-
|
|
|
- if ( hasFaceColor ) {
|
|
|
+ }
|
|
|
|
|
|
- color = new THREE.Color( faces[ offset ++ ] );
|
|
|
- face.color = color;
|
|
|
+ if ( hasFaceVertexUv ) {
|
|
|
|
|
|
- }
|
|
|
+ for ( i = 0; i < nUvLayers; i++ ) {
|
|
|
+
|
|
|
+ uvLayer = json.uvs[ i ];
|
|
|
+
|
|
|
+ uvs = [];
|
|
|
+
|
|
|
+ for ( j = 0; j < nVertices; j ++ ) {
|
|
|
|
|
|
-
|
|
|
- if ( hasFaceVertexColor ) {
|
|
|
+ uvIndex = faces[ offset ++ ];
|
|
|
|
|
|
- for ( i = 0; i < nVertices; i++ ) {
|
|
|
+ u = uvLayer[ uvIndex * 2 ];
|
|
|
+ v = uvLayer[ uvIndex * 2 + 1 ];
|
|
|
|
|
|
- colorIndex = faces[ offset ++ ];
|
|
|
-
|
|
|
- color = new THREE.Color( colors[ colorIndex ] );
|
|
|
- face.vertexColors.push( color );
|
|
|
-
|
|
|
- }
|
|
|
+ uvs[ j ] = new THREE.UV( u, v );
|
|
|
|
|
|
}
|
|
|
|
|
|
- scope.faces.push( face );
|
|
|
+ // to get face <=> uv index correspondence
|
|
|
+
|
|
|
+ fi = geometry.faces.length;
|
|
|
+ geometry.faceVertexUvs[ i ][ fi ] = uvs;
|
|
|
|
|
|
}
|
|
|
|
|
|
- };
|
|
|
-
|
|
|
- function init_skin() {
|
|
|
-
|
|
|
- var i, l, x, y, z, w, a, b, c, d;
|
|
|
+ }
|
|
|
|
|
|
- if ( json.skinWeights ) {
|
|
|
-
|
|
|
- for( i = 0, l = json.skinWeights.length; i < l; i += 2 ) {
|
|
|
+ if ( hasFaceNormal ) {
|
|
|
|
|
|
- x = json.skinWeights[ i ];
|
|
|
- y = json.skinWeights[ i + 1 ];
|
|
|
- z = 0;
|
|
|
- w = 0;
|
|
|
-
|
|
|
- scope.skinWeights.push( new THREE.Vector4( x, y, z, w ) );
|
|
|
+ normalIndex = faces[ offset ++ ] * 3;
|
|
|
+
|
|
|
+ normal = new THREE.Vector3();
|
|
|
+
|
|
|
+ normal.x = normals[ normalIndex ++ ];
|
|
|
+ normal.y = normals[ normalIndex ++ ];
|
|
|
+ normal.z = normals[ normalIndex ];
|
|
|
+
|
|
|
+ face.normal = normal;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( hasFaceVertexNormal ) {
|
|
|
+
|
|
|
+ for ( i = 0; i < nVertices; i++ ) {
|
|
|
+
|
|
|
+ normalIndex = faces[ offset ++ ] * 3;
|
|
|
+
|
|
|
+ normal = new THREE.Vector3();
|
|
|
+
|
|
|
+ normal.x = normals[ normalIndex ++ ];
|
|
|
+ normal.y = normals[ normalIndex ++ ];
|
|
|
+ normal.z = normals[ normalIndex ];
|
|
|
+
|
|
|
+ face.vertexNormals.push( normal );
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
}
|
|
|
-
|
|
|
- if ( json.skinIndices ) {
|
|
|
-
|
|
|
- for( i = 0, l = json.skinIndices.length; i < l; i += 2 ) {
|
|
|
|
|
|
- a = json.skinIndices[ i ];
|
|
|
- b = json.skinIndices[ i + 1 ];
|
|
|
- c = 0;
|
|
|
- d = 0;
|
|
|
+ }
|
|
|
|
|
|
- scope.skinIndices.push( new THREE.Vector4( a, b, c, d ) );
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
+ if ( hasFaceColor ) {
|
|
|
+
|
|
|
+ color = new THREE.Color( faces[ offset ++ ] );
|
|
|
+ face.color = color;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if ( hasFaceVertexColor ) {
|
|
|
+
|
|
|
+ for ( i = 0; i < nVertices; i++ ) {
|
|
|
+
|
|
|
+ colorIndex = faces[ offset ++ ];
|
|
|
+
|
|
|
+ color = new THREE.Color( colors[ colorIndex ] );
|
|
|
+ face.vertexColors.push( color );
|
|
|
+
|
|
|
}
|
|
|
-
|
|
|
- scope.bones = json.bones;
|
|
|
- scope.animation = json.animation;
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- function init_morphing() {
|
|
|
-
|
|
|
- if( json.morphTargets !== undefined ) {
|
|
|
-
|
|
|
- var i, l, v, vl;
|
|
|
-
|
|
|
- for( i = 0, l = json.morphTargets.length; i < l; i++ ) {
|
|
|
-
|
|
|
- scope.morphTargets[ i ] = {};
|
|
|
- scope.morphTargets[ i ].name = json.morphTargets[ i ].name;
|
|
|
- scope.morphTargets[ i ].vertices = [];
|
|
|
-
|
|
|
- dstVertices = scope.morphTargets[ i ].vertices;
|
|
|
- srcVertices = json.morphTargets [ i ].vertices;
|
|
|
-
|
|
|
- for( v = 0, vl = srcVertices.length; v < vl; v += 3 ) {
|
|
|
-
|
|
|
- dstVertices.push( new THREE.Vertex( new THREE.Vector3( srcVertices[ v ], srcVertices[ v + 1 ], srcVertices[ v + 2 ] ) ) );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ geometry.faces.push( face );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ function init_skin() {
|
|
|
+
|
|
|
+ var i, l, x, y, z, w, a, b, c, d;
|
|
|
+
|
|
|
+ if ( json.skinWeights ) {
|
|
|
+
|
|
|
+ for( i = 0, l = json.skinWeights.length; i < l; i += 2 ) {
|
|
|
+
|
|
|
+ x = json.skinWeights[ i ];
|
|
|
+ y = json.skinWeights[ i + 1 ];
|
|
|
+ z = 0;
|
|
|
+ w = 0;
|
|
|
+
|
|
|
+ geometry.skinWeights.push( new THREE.Vector4( x, y, z, w ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( json.skinIndices ) {
|
|
|
+
|
|
|
+ for( i = 0, l = json.skinIndices.length; i < l; i += 2 ) {
|
|
|
+
|
|
|
+ a = json.skinIndices[ i ];
|
|
|
+ b = json.skinIndices[ i + 1 ];
|
|
|
+ c = 0;
|
|
|
+ d = 0;
|
|
|
+
|
|
|
+ geometry.skinIndices.push( new THREE.Vector4( a, b, c, d ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ geometry.bones = json.bones;
|
|
|
+ geometry.animation = json.animation;
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ function init_morphing() {
|
|
|
+
|
|
|
+ if( json.morphTargets !== undefined ) {
|
|
|
+
|
|
|
+ var i, l, v, vl;
|
|
|
+
|
|
|
+ for( i = 0, l = json.morphTargets.length; i < l; i++ ) {
|
|
|
+
|
|
|
+ geometry.morphTargets[ i ] = {};
|
|
|
+ geometry.morphTargets[ i ].name = json.morphTargets[ i ].name;
|
|
|
+ geometry.morphTargets[ i ].vertices = [];
|
|
|
+
|
|
|
+ dstVertices = geometry.morphTargets[ i ].vertices;
|
|
|
+ srcVertices = json.morphTargets [ i ].vertices;
|
|
|
+
|
|
|
+ for( v = 0, vl = srcVertices.length; v < vl; v += 3 ) {
|
|
|
+
|
|
|
+ dstVertices.push( new THREE.Vertex( new THREE.Vector3( srcVertices[ v ], srcVertices[ v + 1 ], srcVertices[ v + 2 ] ) ) );
|
|
|
+
|
|
|
}
|
|
|
|
|
|
- };
|
|
|
-
|
|
|
- };
|
|
|
+ }
|
|
|
|
|
|
- Model.prototype = new THREE.Geometry();
|
|
|
- Model.prototype.constructor = Model;
|
|
|
+ }
|
|
|
|
|
|
- callback( new Model( texture_path ) );
|
|
|
+ };
|
|
|
|
|
|
- }
|
|
|
+ callback( geometry );
|
|
|
|
|
|
-};
|
|
|
+}
|