|
@@ -61558,27 +61558,39 @@
|
|
|
if ((node.level % node.pcoGeometry.hierarchyStepSize) === 0) {
|
|
|
// let hurl = node.pcoGeometry.octreeDir + "/../hierarchy/" + node.name + ".hrc";
|
|
|
let hurl = node.pcoGeometry.octreeDir + '/' + node.getHierarchyPath() + '/' + node.name + '.hrc';
|
|
|
+ let startLoad = ()=>{
|
|
|
+ let xhr = XHRFactory.createXMLHttpRequest();
|
|
|
+ xhr.open('GET', hurl, true);
|
|
|
+ xhr.responseType = 'arraybuffer';
|
|
|
+ xhr.overrideMimeType('text/plain; charset=x-user-defined');
|
|
|
+ xhr.onreadystatechange = () => {
|
|
|
+ if (xhr.readyState === 4) {
|
|
|
+ if (xhr.status === 200 || xhr.status === 0) {
|
|
|
+ let hbuffer = xhr.response;
|
|
|
+ callback(node, hbuffer);
|
|
|
+ } else {
|
|
|
+ console.log('Failed to load file! HTTP status: ' + xhr.status + ', file: ' + hurl);
|
|
|
+ Potree.numNodesLoading--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ try {
|
|
|
+ xhr.send(null);
|
|
|
+ } catch (e) {
|
|
|
+ console.log('fehler beim laden der punktwolke: ' + e);
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
- let xhr = XHRFactory.createXMLHttpRequest();
|
|
|
- xhr.open('GET', hurl, true);
|
|
|
- xhr.responseType = 'arraybuffer';
|
|
|
- xhr.overrideMimeType('text/plain; charset=x-user-defined');
|
|
|
- xhr.onreadystatechange = () => {
|
|
|
- if (xhr.readyState === 4) {
|
|
|
- if (xhr.status === 200 || xhr.status === 0) {
|
|
|
- let hbuffer = xhr.response;
|
|
|
- callback(node, hbuffer);
|
|
|
- } else {
|
|
|
- console.log('Failed to load file! HTTP status: ' + xhr.status + ', file: ' + hurl);
|
|
|
- Potree.numNodesLoading--;
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
- try {
|
|
|
- xhr.send(null);
|
|
|
- } catch (e) {
|
|
|
- console.log('fehler beim laden der punktwolke: ' + e);
|
|
|
- }
|
|
|
+ if(Potree.getFileUrl){//更换url
|
|
|
+ getFileUrl(hurl).then(realUrl => {
|
|
|
+ hurl = realUrl;
|
|
|
+ startLoad();
|
|
|
+ });
|
|
|
+ }else {
|
|
|
+ startLoad();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -71238,27 +71250,42 @@ void main()
|
|
|
}
|
|
|
url += '?m='+node.pcoGeometry.timeStamp; //add
|
|
|
|
|
|
- let xhr = XHRFactory.createXMLHttpRequest();
|
|
|
- xhr.open('GET', url, true);
|
|
|
- xhr.responseType = 'arraybuffer';
|
|
|
- xhr.overrideMimeType('text/plain; charset=x-user-defined');
|
|
|
- xhr.onreadystatechange = () => {
|
|
|
- if (xhr.readyState === 4) {
|
|
|
- if((xhr.status === 200 || xhr.status === 0) && xhr.response !== null){
|
|
|
- let buffer = xhr.response;
|
|
|
- this.parse(node, buffer, callback);
|
|
|
- } else {
|
|
|
- //console.error(`Failed to load file! HTTP status: ${xhr.status}, file: ${url}`);
|
|
|
- throw new Error(`Failed to load file! HTTP status: ${xhr.status}, file: ${url}`);
|
|
|
+
|
|
|
+ let startLoad = ()=>{
|
|
|
+ let xhr = XHRFactory.createXMLHttpRequest();
|
|
|
+ xhr.open('GET', url, true);
|
|
|
+ xhr.responseType = 'arraybuffer';
|
|
|
+ xhr.overrideMimeType('text/plain; charset=x-user-defined');
|
|
|
+ xhr.onreadystatechange = () => {
|
|
|
+ if (xhr.readyState === 4) {
|
|
|
+ if((xhr.status === 200 || xhr.status === 0) && xhr.response !== null){
|
|
|
+ let buffer = xhr.response;
|
|
|
+ this.parse(node, buffer, callback);
|
|
|
+ } else {
|
|
|
+ //console.error(`Failed to load file! HTTP status: ${xhr.status}, file: ${url}`);
|
|
|
+ throw new Error(`Failed to load file! HTTP status: ${xhr.status}, file: ${url}`);
|
|
|
+ }
|
|
|
}
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ xhr.send(null);
|
|
|
+ } catch (e) {
|
|
|
+ console.log('fehler beim laden der punktwolke: ' + e);
|
|
|
}
|
|
|
- };
|
|
|
+ };
|
|
|
|
|
|
- try {
|
|
|
- xhr.send(null);
|
|
|
- } catch (e) {
|
|
|
- console.log('fehler beim laden der punktwolke: ' + e);
|
|
|
+ if(Potree.getFileUrl){//更换url
|
|
|
+ getFileUrl(hurl).then(realUrl => {
|
|
|
+ hurl = realUrl;
|
|
|
+ startLoad();
|
|
|
+ });
|
|
|
+ }else {
|
|
|
+ startLoad();
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
};
|
|
|
|
|
|
PointAttribute.RGBA_PACKED = new PointAttribute("rgba", PointAttributeTypes.DATA_TYPE_INT8, 4);
|
|
@@ -77254,117 +77281,132 @@ void main()
|
|
|
let pco = new PointCloudOctreeGeometry();
|
|
|
pco.timeStamp = timeStamp;
|
|
|
|
|
|
- pco.url = url;
|
|
|
- let xhr = XHRFactory.createXMLHttpRequest();
|
|
|
- xhr.open('GET', url+'?m='+timeStamp, true);
|
|
|
-
|
|
|
- xhr.onreadystatechange = function () {
|
|
|
- if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 0)) {
|
|
|
- let fMno = JSON.parse(xhr.responseText);
|
|
|
-
|
|
|
- let version = new Version(fMno.version);
|
|
|
-
|
|
|
- // assume octreeDir is absolute if it starts with http
|
|
|
- if (fMno.octreeDir.indexOf('http') === 0) {
|
|
|
- pco.octreeDir = fMno.octreeDir;
|
|
|
- } else {
|
|
|
- pco.octreeDir = url + '/../' + fMno.octreeDir;
|
|
|
- }
|
|
|
+
|
|
|
+ let startLoad = ()=>{
|
|
|
+ pco.url = url;
|
|
|
+ let xhr = XHRFactory.createXMLHttpRequest();
|
|
|
+ xhr.open('GET', url+'?m='+timeStamp, true);
|
|
|
+
|
|
|
+ xhr.onreadystatechange = function () {
|
|
|
+ if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 0)) {
|
|
|
+ let fMno = JSON.parse(xhr.responseText);
|
|
|
+
|
|
|
+ let version = new Version(fMno.version);
|
|
|
+
|
|
|
+ // assume octreeDir is absolute if it starts with http
|
|
|
+ if (fMno.octreeDir.indexOf('http') === 0) {
|
|
|
+ pco.octreeDir = fMno.octreeDir;
|
|
|
+ } else {
|
|
|
+ pco.octreeDir = url + '/../' + fMno.octreeDir;
|
|
|
+ }
|
|
|
|
|
|
- pco.spacing = fMno.spacing;
|
|
|
- pco.hierarchyStepSize = fMno.hierarchyStepSize;
|
|
|
+ pco.spacing = fMno.spacing;
|
|
|
+ pco.hierarchyStepSize = fMno.hierarchyStepSize;
|
|
|
|
|
|
- pco.pointAttributes = fMno.pointAttributes;
|
|
|
+ pco.pointAttributes = fMno.pointAttributes;
|
|
|
|
|
|
- let min = new Vector3(fMno.boundingBox.lx, fMno.boundingBox.ly, fMno.boundingBox.lz);
|
|
|
- let max = new Vector3(fMno.boundingBox.ux, fMno.boundingBox.uy, fMno.boundingBox.uz);
|
|
|
- let boundingBox = new Box3(min, max);
|
|
|
- let tightBoundingBox = boundingBox.clone();
|
|
|
+ let min = new Vector3(fMno.boundingBox.lx, fMno.boundingBox.ly, fMno.boundingBox.lz);
|
|
|
+ let max = new Vector3(fMno.boundingBox.ux, fMno.boundingBox.uy, fMno.boundingBox.uz);
|
|
|
+ let boundingBox = new Box3(min, max);
|
|
|
+ let tightBoundingBox = boundingBox.clone();
|
|
|
|
|
|
- if (fMno.tightBoundingBox) {//这个才是真实的bounding,前面那个bounding的size是个正方体,似乎取了最长边作为边长
|
|
|
- tightBoundingBox.min.copy(new Vector3(fMno.tightBoundingBox.lx, fMno.tightBoundingBox.ly, fMno.tightBoundingBox.lz));
|
|
|
- tightBoundingBox.max.copy(new Vector3(fMno.tightBoundingBox.ux, fMno.tightBoundingBox.uy, fMno.tightBoundingBox.uz));
|
|
|
- }
|
|
|
-
|
|
|
- let offset = min.clone(); //将成为点云的position,被我用作旋转中心(但在点云中不那么居中,navvis也是这样, 这样可能是为了让模型在这数据的bounding上)
|
|
|
+ if (fMno.tightBoundingBox) {//这个才是真实的bounding,前面那个bounding的size是个正方体,似乎取了最长边作为边长
|
|
|
+ tightBoundingBox.min.copy(new Vector3(fMno.tightBoundingBox.lx, fMno.tightBoundingBox.ly, fMno.tightBoundingBox.lz));
|
|
|
+ tightBoundingBox.max.copy(new Vector3(fMno.tightBoundingBox.ux, fMno.tightBoundingBox.uy, fMno.tightBoundingBox.uz));
|
|
|
+ }
|
|
|
+
|
|
|
+ let offset = min.clone(); //将成为点云的position,被我用作旋转中心(但在点云中不那么居中,navvis也是这样, 这样可能是为了让模型在这数据的bounding上)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- boundingBox.min.sub(offset); //点云的真实坐标的min都是0,0,0吗(我看案例是,因绕角落旋转,也就是原点)
|
|
|
-
|
|
|
- boundingBox.max.sub(offset);
|
|
|
+ boundingBox.min.sub(offset); //点云的真实坐标的min都是0,0,0吗(我看案例是,因绕角落旋转,也就是原点)
|
|
|
+
|
|
|
+ boundingBox.max.sub(offset);
|
|
|
|
|
|
- tightBoundingBox.min.sub(offset);
|
|
|
- tightBoundingBox.max.sub(offset);
|
|
|
+ tightBoundingBox.min.sub(offset);
|
|
|
+ tightBoundingBox.max.sub(offset);
|
|
|
|
|
|
- //改
|
|
|
- //pco.projection = fMno.projection || "+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs ",
|
|
|
- //"+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs" //给地图
|
|
|
-
|
|
|
- pco.boundingBox = boundingBox;
|
|
|
- pco.tightBoundingBox = tightBoundingBox;
|
|
|
- pco.boundingSphere = boundingBox.getBoundingSphere(new Sphere());
|
|
|
- pco.tightBoundingSphere = tightBoundingBox.getBoundingSphere(new Sphere());
|
|
|
- pco.offset = offset;
|
|
|
- if (fMno.pointAttributes === 'LAS') {
|
|
|
- pco.loader = new LasLazLoader(fMno.version, "las");
|
|
|
- pco.pointAttributes = lasLazAttributes(fMno);
|
|
|
- } else if (fMno.pointAttributes === 'LAZ') {
|
|
|
- pco.loader = new LasLazLoader(fMno.version, "laz");
|
|
|
- pco.pointAttributes = lasLazAttributes(fMno);
|
|
|
- } else {
|
|
|
- pco.loader = new BinaryLoader(fMno.version, boundingBox, fMno.scale);
|
|
|
- pco.pointAttributes = parseAttributes(fMno);
|
|
|
- }
|
|
|
+ //改
|
|
|
+ //pco.projection = fMno.projection || "+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs ",
|
|
|
+ //"+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs" //给地图
|
|
|
+
|
|
|
+ pco.boundingBox = boundingBox;
|
|
|
+ pco.tightBoundingBox = tightBoundingBox;
|
|
|
+ pco.boundingSphere = boundingBox.getBoundingSphere(new Sphere());
|
|
|
+ pco.tightBoundingSphere = tightBoundingBox.getBoundingSphere(new Sphere());
|
|
|
+ pco.offset = offset;
|
|
|
+ if (fMno.pointAttributes === 'LAS') {
|
|
|
+ pco.loader = new LasLazLoader(fMno.version, "las");
|
|
|
+ pco.pointAttributes = lasLazAttributes(fMno);
|
|
|
+ } else if (fMno.pointAttributes === 'LAZ') {
|
|
|
+ pco.loader = new LasLazLoader(fMno.version, "laz");
|
|
|
+ pco.pointAttributes = lasLazAttributes(fMno);
|
|
|
+ } else {
|
|
|
+ pco.loader = new BinaryLoader(fMno.version, boundingBox, fMno.scale);
|
|
|
+ pco.pointAttributes = parseAttributes(fMno);
|
|
|
+ }
|
|
|
|
|
|
- let nodes = {};
|
|
|
+ let nodes = {};
|
|
|
|
|
|
- { // load root
|
|
|
- let name = 'r';
|
|
|
+ { // load root
|
|
|
+ let name = 'r';
|
|
|
|
|
|
- let root = new PointCloudOctreeGeometryNode(name, pco, boundingBox);
|
|
|
- root.level = 0;
|
|
|
- root.hasChildren = true;
|
|
|
- root.spacing = pco.spacing;
|
|
|
- if (version.upTo('1.5')) {
|
|
|
- root.numPoints = fMno.hierarchy[0][1];
|
|
|
- } else {
|
|
|
- root.numPoints = 0;
|
|
|
- }
|
|
|
- pco.root = root;
|
|
|
- pco.root.load();
|
|
|
- nodes[name] = root;
|
|
|
- }
|
|
|
+ let root = new PointCloudOctreeGeometryNode(name, pco, boundingBox);
|
|
|
+ root.level = 0;
|
|
|
+ root.hasChildren = true;
|
|
|
+ root.spacing = pco.spacing;
|
|
|
+ if (version.upTo('1.5')) {
|
|
|
+ root.numPoints = fMno.hierarchy[0][1];
|
|
|
+ } else {
|
|
|
+ root.numPoints = 0;
|
|
|
+ }
|
|
|
+ pco.root = root;
|
|
|
+ pco.root.load();
|
|
|
+ nodes[name] = root;
|
|
|
+ }
|
|
|
|
|
|
- // load remaining hierarchy
|
|
|
- if (version.upTo('1.4')) {
|
|
|
- for (let i = 1; i < fMno.hierarchy.length; i++) {
|
|
|
- let name = fMno.hierarchy[i][0];
|
|
|
- let numPoints = fMno.hierarchy[i][1];
|
|
|
- let index = parseInt(name.charAt(name.length - 1));
|
|
|
- let parentName = name.substring(0, name.length - 1);
|
|
|
- let parentNode = nodes[parentName];
|
|
|
- let level = name.length - 1;
|
|
|
- //let boundingBox = POCLoader.createChildAABB(parentNode.boundingBox, index);
|
|
|
- let boundingBox = Utils.createChildAABB(parentNode.boundingBox, index);
|
|
|
-
|
|
|
- let node = new PointCloudOctreeGeometryNode(name, pco, boundingBox);
|
|
|
- node.level = level;
|
|
|
- node.numPoints = numPoints;
|
|
|
- node.spacing = pco.spacing / Math.pow(2, level);
|
|
|
- parentNode.addChild(node);
|
|
|
- nodes[name] = node;
|
|
|
- }
|
|
|
- }
|
|
|
+ // load remaining hierarchy
|
|
|
+ if (version.upTo('1.4')) {
|
|
|
+ for (let i = 1; i < fMno.hierarchy.length; i++) {
|
|
|
+ let name = fMno.hierarchy[i][0];
|
|
|
+ let numPoints = fMno.hierarchy[i][1];
|
|
|
+ let index = parseInt(name.charAt(name.length - 1));
|
|
|
+ let parentName = name.substring(0, name.length - 1);
|
|
|
+ let parentNode = nodes[parentName];
|
|
|
+ let level = name.length - 1;
|
|
|
+ //let boundingBox = POCLoader.createChildAABB(parentNode.boundingBox, index);
|
|
|
+ let boundingBox = Utils.createChildAABB(parentNode.boundingBox, index);
|
|
|
+
|
|
|
+ let node = new PointCloudOctreeGeometryNode(name, pco, boundingBox);
|
|
|
+ node.level = level;
|
|
|
+ node.numPoints = numPoints;
|
|
|
+ node.spacing = pco.spacing / Math.pow(2, level);
|
|
|
+ parentNode.addChild(node);
|
|
|
+ nodes[name] = node;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- pco.nodes = nodes;
|
|
|
+ pco.nodes = nodes;
|
|
|
|
|
|
- callback(pco);
|
|
|
- }
|
|
|
- };
|
|
|
+ callback(pco);
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
- xhr.send(null);
|
|
|
+ xhr.send(null);
|
|
|
+
|
|
|
+
|
|
|
+ if(Potree.getFileUrl){//更换url
|
|
|
+ getFileUrl(url).then(realUrl => {
|
|
|
+ url = realUrl;
|
|
|
+ startLoad();
|
|
|
+ });
|
|
|
+ }else {
|
|
|
+ startLoad();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
} catch (e) {
|
|
|
console.log("loading failed: '" + url + "'");
|
|
|
console.log(e);
|
|
@@ -95776,21 +95818,35 @@ ENDSEC
|
|
|
this.texLoading = true;
|
|
|
let src = `${Potree.settings.urls.prefix1}/images/${this.originID}.jpg`; //`server\test\SS-t-P1d6CwREny2\${this.id}.jpg` //`${Potree.settings.urls.prefix1}/${Potree.settings.webSite}/${this.pointcloud.sceneCode}/data/${this.pointcloud.sceneCode}/depthmap/${this.originID}.png`
|
|
|
//console.log('开始下载depthImg', this.id)
|
|
|
- let texture = texLoader$4.load( src, ()=>{
|
|
|
- this.skyboxTex = texture;
|
|
|
- this.dispatchEvent({type:'loadedTex', loaded:true});
|
|
|
- this.depthTexLoading = false;
|
|
|
- //viewer.dispatchEvent('content_changed')
|
|
|
- },null,(e)=>{//error
|
|
|
- console.error('loadTex失败, 数据集sceneCode'+ this.pointcloud.sceneCode, this.id );
|
|
|
-
|
|
|
- this.dispatchEvent({type:'loadedTex', });
|
|
|
- });
|
|
|
- texture.wrapS = RepeatWrapping;
|
|
|
- texture.flipY = false;
|
|
|
- texture.magFilter = LinearFilter;
|
|
|
- texture.minFilter = LinearFilter; //防止边缘竖线
|
|
|
- texture.generateMipmaps = false;
|
|
|
+
|
|
|
+ let startLoad = ()=>{
|
|
|
+ let texture = texLoader$4.load( src, ()=>{
|
|
|
+ this.skyboxTex = texture;
|
|
|
+ this.dispatchEvent({type:'loadedTex', loaded:true});
|
|
|
+ this.depthTexLoading = false;
|
|
|
+ //viewer.dispatchEvent('content_changed')
|
|
|
+ },null,(e)=>{//error
|
|
|
+ console.error('loadTex失败, 数据集sceneCode'+ this.pointcloud.sceneCode, this.id );
|
|
|
+
|
|
|
+ this.dispatchEvent({type:'loadedTex', });
|
|
|
+ });
|
|
|
+ texture.wrapS = RepeatWrapping;
|
|
|
+ texture.flipY = false;
|
|
|
+ texture.magFilter = LinearFilter;
|
|
|
+ texture.minFilter = LinearFilter; //防止边缘竖线
|
|
|
+ texture.generateMipmaps = false;
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ if(Potree.getFileUrl){//更换url
|
|
|
+ getFileUrl(src).then(realUrl => {
|
|
|
+ src = realUrl;
|
|
|
+ startLoad();
|
|
|
+ });
|
|
|
+ }else {
|
|
|
+ startLoad();
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
|
|
@@ -95803,22 +95859,36 @@ ENDSEC
|
|
|
`${Potree.settings.urls.prefix1}/depthmap/${this.originID}.png`;
|
|
|
|
|
|
//console.log('开始下载depthImg', this.id)
|
|
|
- let texture = texLoader$4.load( src, ()=>{
|
|
|
- this.depthTex = texture;
|
|
|
- this.dispatchEvent({type:'loadedDepthImg', pano:this, loaded:true});
|
|
|
- this.depthTexLoading = false;
|
|
|
- this.images360.updateDepthTex(this);
|
|
|
- //viewer.dispatchEvent('content_changed')
|
|
|
- },null,(e)=>{//error
|
|
|
- console.error('loadDepthImg失败, 数据集sceneCode'+ this.pointcloud.sceneCode, this.id );
|
|
|
- this.pointcloud.hasDepthTex = false;
|
|
|
- this.dispatchEvent({type:'loadedDepthImg', pano:this, });
|
|
|
- });
|
|
|
- texture.wrapS = RepeatWrapping;
|
|
|
- texture.flipY = false;
|
|
|
- texture.magFilter = LinearFilter;
|
|
|
- texture.minFilter = LinearFilter;
|
|
|
- texture.generateMipmaps = false;
|
|
|
+
|
|
|
+ let startLoad = ()=>{
|
|
|
+
|
|
|
+ let texture = texLoader$4.load( src, ()=>{
|
|
|
+ this.depthTex = texture;
|
|
|
+ this.dispatchEvent({type:'loadedDepthImg', pano:this, loaded:true});
|
|
|
+ this.depthTexLoading = false;
|
|
|
+ this.images360.updateDepthTex(this);
|
|
|
+ //viewer.dispatchEvent('content_changed')
|
|
|
+ },null,(e)=>{//error
|
|
|
+ console.error('loadDepthImg失败, 数据集sceneCode'+ this.pointcloud.sceneCode, this.id );
|
|
|
+ this.pointcloud.hasDepthTex = false;
|
|
|
+ this.dispatchEvent({type:'loadedDepthImg', pano:this, });
|
|
|
+ });
|
|
|
+ texture.wrapS = RepeatWrapping;
|
|
|
+ texture.flipY = false;
|
|
|
+ texture.magFilter = LinearFilter;
|
|
|
+ texture.minFilter = LinearFilter;
|
|
|
+ texture.generateMipmaps = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ if(Potree.getFileUrl){//更换url
|
|
|
+ getFileUrl(src).then(realUrl => {
|
|
|
+ src = realUrl;
|
|
|
+ startLoad();
|
|
|
+ });
|
|
|
+ }else {
|
|
|
+ startLoad();
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
|