|
@@ -122,10 +122,17 @@ export class Viewer extends ViewerBase{
|
|
|
CamAniEditor,
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
+ {
|
|
|
+ Potree.timeCollect = {
|
|
|
+ depthSampler : {minCount:20, median:0.5}, //median预置一个中等值,以防止cpu过低的设备首次卡顿
|
|
|
+ }
|
|
|
+ for(let i in Potree.timeCollect){
|
|
|
+ Potree.timeCollect[i].measures = [];
|
|
|
+ Potree.timeCollect[i].sum = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- //console.log('create viewer')
|
|
|
+
|
|
|
|
|
|
this.navigateMode = 'free' // 'panorama'; 'free'自由模式是只显示点云或者未进入到漫游点,
|
|
|
this.isEdit = true
|
|
@@ -716,7 +723,7 @@ export class Viewer extends ViewerBase{
|
|
|
}
|
|
|
|
|
|
|
|
|
- if(Potree.settings.editType != 'merge'){
|
|
|
+ {
|
|
|
let updated, zoomLevel
|
|
|
this.addEventListener('camera_changed', e => {
|
|
|
|
|
@@ -725,7 +732,7 @@ export class Viewer extends ViewerBase{
|
|
|
blockedByIntersectHistory.clear() //清空
|
|
|
this.updateDatasetAt() //更新所在数据集
|
|
|
|
|
|
- if(Potree.settings.ifShowMarker){
|
|
|
+ if(Potree.settings.ifShowMarker && Potree.settings.editType != 'merge'){
|
|
|
updated = true
|
|
|
|
|
|
Common.intervalTool.isWaiting('updateMarkerVisibles', ()=>{
|
|
@@ -2239,8 +2246,8 @@ export class Viewer extends ViewerBase{
|
|
|
}
|
|
|
|
|
|
update(delta, timestamp){
|
|
|
-
|
|
|
- if(Potree.measureTimings) performance.mark("update-start");
|
|
|
+
|
|
|
+ viewer.addTimeMark('update','start')
|
|
|
|
|
|
this.dispatchEvent({
|
|
|
type: 'update_start',
|
|
@@ -2257,9 +2264,9 @@ export class Viewer extends ViewerBase{
|
|
|
|
|
|
Potree.pointLoadLimit = Potree.pointBudget * 2;
|
|
|
|
|
|
- const lTarget = camera.position.clone().add(camera.getWorldDirection(new THREE.Vector3()).multiplyScalar(1000));
|
|
|
+ /* const lTarget = camera.position.clone().add(camera.getWorldDirection(new THREE.Vector3()).multiplyScalar(1000));
|
|
|
this.scene.directionalLight.position.copy(camera.position);
|
|
|
- this.scene.directionalLight.lookAt(lTarget);
|
|
|
+ this.scene.directionalLight.lookAt(lTarget); */
|
|
|
|
|
|
|
|
|
for (let pointcloud of visiblePointClouds) {
|
|
@@ -2280,6 +2287,8 @@ export class Viewer extends ViewerBase{
|
|
|
|
|
|
this.updateMaterialDefaults(pointcloud);
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
|
|
|
{
|
|
|
if(this.showBoundingBox){
|
|
@@ -2305,8 +2314,9 @@ export class Viewer extends ViewerBase{
|
|
|
|
|
|
|
|
|
if(this.boundNeedUpdate)this.updateModelBound() //add
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
this.scene.cameraP.fov = this.fov;
|
|
|
|
|
|
let controls = this.getControls();
|
|
@@ -2328,9 +2338,8 @@ export class Viewer extends ViewerBase{
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-
|
|
|
|
|
|
- this.cameraChanged()//判断camera画面是否改变
|
|
|
+ this.lastFrameChanged = this.cameraChanged()//判断camera画面是否改变
|
|
|
|
|
|
|
|
|
{ // update clip boxes
|
|
@@ -2383,7 +2392,8 @@ export class Viewer extends ViewerBase{
|
|
|
}
|
|
|
|
|
|
this.updateAnnotations();
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
if(this.mapView){
|
|
|
this.mapView.update(delta);
|
|
|
if(this.mapView.sceneProjection){
|
|
@@ -2396,25 +2406,19 @@ export class Viewer extends ViewerBase{
|
|
|
transitions.update(delta);
|
|
|
this.transformationTool.update();
|
|
|
|
|
|
+
|
|
|
if(Potree.settings.editType != 'pano' && Potree.settings.editType != 'merge'){
|
|
|
this.modules.ParticleEditor.update(delta)
|
|
|
- this.mapViewer.update(delta)
|
|
|
+ this.mapViewer.update(delta) //地图更新
|
|
|
}
|
|
|
-
|
|
|
- this.dispatchEvent({
|
|
|
- type: 'update',
|
|
|
- delta: delta,
|
|
|
- timestamp: timestamp});
|
|
|
-
|
|
|
- if(Potree.measureTimings) {
|
|
|
- performance.mark("update-end");
|
|
|
- performance.measure("update", "update-start", "update-end");
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+ this.dispatchEvent({ type: 'update', delta: delta, timestamp: timestamp}); //在有sidebar时耗高cpu,占本update函数80%
|
|
|
+
|
|
|
+
|
|
|
+ viewer.addTimeMark('update','end')
|
|
|
//add ------
|
|
|
this.reticule.updateVisible()
|
|
|
-
|
|
|
+
|
|
|
|
|
|
}
|
|
|
|
|
@@ -2728,6 +2732,8 @@ export class Viewer extends ViewerBase{
|
|
|
}
|
|
|
viewports = viewports.filter(v=>v.active)
|
|
|
if(viewports.length == 0)return
|
|
|
+
|
|
|
+ viewer.addTimeMark('renderDefault','start')
|
|
|
|
|
|
//console.log('render', viewports.map(e=>e.name).join(','))
|
|
|
|
|
@@ -2847,7 +2853,8 @@ export class Viewer extends ViewerBase{
|
|
|
|
|
|
this.renderer.setRenderTarget(null)
|
|
|
this.needRender = false
|
|
|
-
|
|
|
+
|
|
|
+ viewer.addTimeMark('renderDefault','end')
|
|
|
}
|
|
|
|
|
|
renderBG(view){
|
|
@@ -2931,9 +2938,12 @@ export class Viewer extends ViewerBase{
|
|
|
|
|
|
*/
|
|
|
|
|
|
- renderOverlay(params){
|
|
|
+ renderOverlay(params){
|
|
|
+ viewer.addTimeMark('renderOverlay','start')
|
|
|
this.renderOverlay1(params)
|
|
|
this.renderOverlay2(params)
|
|
|
+ viewer.addTimeMark('renderOverlay','end')
|
|
|
+
|
|
|
}
|
|
|
|
|
|
renderOverlay1(params){
|
|
@@ -3039,9 +3049,8 @@ export class Viewer extends ViewerBase{
|
|
|
/* 大规模WebGL应用引发浏览器崩溃的几种情况及解决办法
|
|
|
https://blog.csdn.net/weixin_30378311/article/details/94846947 */
|
|
|
|
|
|
- render(params={}){//add params
|
|
|
- if(Potree.measureTimings) performance.mark("render-start");
|
|
|
-
|
|
|
+ render(params={}){//add params
|
|
|
+ viewer.addTimeMark('render','start')
|
|
|
const vrActive = this.renderer.xr.isPresenting;
|
|
|
|
|
|
if(vrActive){
|
|
@@ -3054,30 +3063,30 @@ export class Viewer extends ViewerBase{
|
|
|
this.renderDefault(params);
|
|
|
}
|
|
|
}
|
|
|
- if(Potree.measureTimings){
|
|
|
- performance.mark("render-end");
|
|
|
- performance.measure("render", "render-start", "render-end");
|
|
|
- }
|
|
|
+ viewer.addTimeMark('render','end')
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- startScreenshot(info={}, width=800, height=400, compressRatio){//add
|
|
|
- let deferred = info.deferred || $.Deferred();
|
|
|
+ startScreenshot(info={}, width=800, height=400, compressRatio ){//add
|
|
|
+ //let deferred = info.deferred || $.Deferred();
|
|
|
+ let getImageDeferred = info.getImageDeferred || $.Deferred();
|
|
|
+ let finishDeferred = info.finishDeferred || $.Deferred();
|
|
|
+
|
|
|
let viewerMaster = info.map ? this.mapViewer : this; //截图主体
|
|
|
let useMap = info.type == 'measure' || info.map
|
|
|
|
|
|
|
|
|
if(this.images360.flying){//如果在飞,飞完再截图
|
|
|
- info.deferred = deferred
|
|
|
+ info.getImageDeferred = getImageDeferred , info.finishDeferred = finishDeferred
|
|
|
let f = ()=>{
|
|
|
this.startScreenshot(info, width, height, compressRatio)
|
|
|
this.images360.removeEventListener('cameraMoveDone', f)
|
|
|
}
|
|
|
this.images360.addEventListener('cameraMoveDone', f) //once
|
|
|
- return deferred.promise()
|
|
|
+ return {getImagePromise:getImageDeferred.promise(), finishPromise:finishDeferred.promise()}
|
|
|
}
|
|
|
|
|
|
var sid = Date.now()
|
|
@@ -3099,14 +3108,15 @@ export class Viewer extends ViewerBase{
|
|
|
}
|
|
|
|
|
|
let screenshot = ()=>{
|
|
|
+ let pose
|
|
|
|
|
|
useMap && (viewer.mapViewer.needRender = true)
|
|
|
|
|
|
this.needRender = true
|
|
|
|
|
|
let { dataUrl } = viewerMaster.makeScreenshot( new THREE.Vector2(width,height), null, compressRatio );
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
|
|
|
if(!Potree.settings.isOfficial){
|
|
|
Common.downloadFile(dataUrl, 'screenshot.jpg')
|
|
@@ -3136,11 +3146,9 @@ export class Viewer extends ViewerBase{
|
|
|
})
|
|
|
}) */
|
|
|
updateCamera()
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- deferred.resolve(dataUrl)
|
|
|
+
|
|
|
+ finishDeferred.resolve({dataUrl, pose})
|
|
|
+
|
|
|
console.log('screenshot done: '+sid)
|
|
|
}
|
|
|
|
|
@@ -3172,13 +3180,27 @@ export class Viewer extends ViewerBase{
|
|
|
mapViewport.camera.updateProjectionMatrix()
|
|
|
}
|
|
|
|
|
|
+
|
|
|
|
|
|
- if(Potree.settings.displayMode == 'showPanos') {
|
|
|
- viewer.images360.flyToPano({pano:oldStates.pano, duration:0, callback:()=>{
|
|
|
+
|
|
|
+ let recover = ()=>{
|
|
|
+ if(Potree.settings.displayMode == 'showPanos') {
|
|
|
+ viewer.images360.flyToPano({pano:oldStates.pano, duration:0, callback:()=>{
|
|
|
+ finish()
|
|
|
+ }})
|
|
|
+ }else{
|
|
|
finish()
|
|
|
- }})
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(info.ifGetPose){
|
|
|
+ Potree.sdk.scene.getPose().done(pose_ =>{
|
|
|
+ pose = pose_
|
|
|
+ getImageDeferred.resolve({dataUrl, pose})
|
|
|
+ recover()
|
|
|
+ })
|
|
|
}else{
|
|
|
- finish()
|
|
|
+ recover()
|
|
|
}
|
|
|
|
|
|
}
|
|
@@ -3280,8 +3302,8 @@ export class Viewer extends ViewerBase{
|
|
|
测量线的截图因为要调用分屏的,会改变画面
|
|
|
但是普通截图的话,不会改变画面
|
|
|
*/
|
|
|
-
|
|
|
- return deferred.promise()
|
|
|
+
|
|
|
+ return {getImagePromise:getImageDeferred.promise(), finishPromise:finishDeferred.promise()}
|
|
|
|
|
|
|
|
|
}
|
|
@@ -3704,108 +3726,135 @@ export class Viewer extends ViewerBase{
|
|
|
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ addTimeMark(name, type){
|
|
|
+ let record = Potree.timeCollect[name]
|
|
|
+ let needRecord = record && record.measures.length < record.minCount
|
|
|
+
|
|
|
+ if(needRecord || Potree.measureTimings){
|
|
|
+ performance.mark(name+"-"+type)
|
|
|
+ if(type == 'end'){
|
|
|
+ let measure = performance.measure(name,name+"-start",name+"-end");
|
|
|
+
|
|
|
+ if(needRecord){
|
|
|
+ record.measures.push( measure.duration )
|
|
|
+ record.sum += measure.duration;
|
|
|
+ record.mean = record.sum / record.measures.length;
|
|
|
+ record.measures.sort( (a, b) => a - b );
|
|
|
+ record.median = record.measures[parseInt(record.measures.length / 2)]
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
|
|
|
+ resolveTimings(timestamp,log){//打印用时。 注:performance手机的精度只到整数位 。 sidebar中监听update时有高cpu的函数所以不要用demo测
|
|
|
+
|
|
|
+ if(!this.toggle){
|
|
|
+ this.toggle = timestamp;
|
|
|
+ }
|
|
|
+ let duration = timestamp - this.toggle;
|
|
|
+ if(duration > 1000.0){
|
|
|
+ if(log){
|
|
|
+ let measures = performance.getEntriesByType("measure");
|
|
|
+
|
|
|
+ let names = new Set();
|
|
|
+ for(let measure of measures){
|
|
|
+ names.add(measure.name);
|
|
|
+ }
|
|
|
+
|
|
|
+ let groups = new Map();
|
|
|
+ for(let name of names){
|
|
|
+ groups.set(name, {
|
|
|
+ measures: [],
|
|
|
+ sum: 0,
|
|
|
+ n: 0,
|
|
|
+ min: Infinity,
|
|
|
+ max: -Infinity
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ for(let measure of measures){
|
|
|
+ let group = groups.get(measure.name);
|
|
|
+ group.measures.push(measure);
|
|
|
+ group.sum += measure.duration;
|
|
|
+ group.n++;
|
|
|
+ group.min = Math.min(group.min, measure.duration);
|
|
|
+ group.max = Math.max(group.max, measure.duration);
|
|
|
+ }
|
|
|
|
|
|
+ /* let glQueries = Potree.resolveQueries(this.renderer.getContext()); // resolveQueries 无
|
|
|
+ for(let [key, value] of glQueries){
|
|
|
|
|
|
- resolveTimings(timestamp){//打印用时。 注:performance手机的精度只到整数位
|
|
|
- if(Potree.measureTimings){
|
|
|
- if(!this.toggle){
|
|
|
- this.toggle = timestamp;
|
|
|
- }
|
|
|
- let duration = timestamp - this.toggle;
|
|
|
- if(duration > 1000.0){
|
|
|
-
|
|
|
- let measures = performance.getEntriesByType("measure");
|
|
|
-
|
|
|
- let names = new Set();
|
|
|
- for(let measure of measures){
|
|
|
- names.add(measure.name);
|
|
|
- }
|
|
|
-
|
|
|
- let groups = new Map();
|
|
|
- for(let name of names){
|
|
|
- groups.set(name, {
|
|
|
- measures: [],
|
|
|
- sum: 0,
|
|
|
- n: 0,
|
|
|
- min: Infinity,
|
|
|
- max: -Infinity
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- for(let measure of measures){
|
|
|
- let group = groups.get(measure.name);
|
|
|
- group.measures.push(measure);
|
|
|
- group.sum += measure.duration;
|
|
|
- group.n++;
|
|
|
- group.min = Math.min(group.min, measure.duration);
|
|
|
- group.max = Math.max(group.max, measure.duration);
|
|
|
- }
|
|
|
+ let group = {
|
|
|
+ measures: value.map(v => {return {duration: v}}),
|
|
|
+ sum: value.reduce( (a, i) => a + i, 0),
|
|
|
+ n: value.length,
|
|
|
+ min: Math.min(...value),
|
|
|
+ max: Math.max(...value)
|
|
|
+ };
|
|
|
|
|
|
- /* let glQueries = Potree.resolveQueries(this.renderer.getContext()); // resolveQueries 无
|
|
|
- for(let [key, value] of glQueries){
|
|
|
-
|
|
|
- let group = {
|
|
|
- measures: value.map(v => {return {duration: v}}),
|
|
|
- sum: value.reduce( (a, i) => a + i, 0),
|
|
|
- n: value.length,
|
|
|
- min: Math.min(...value),
|
|
|
- max: Math.max(...value)
|
|
|
- };
|
|
|
-
|
|
|
- let groupname = `[tq] ${key}`;
|
|
|
- groups.set(groupname, group);
|
|
|
- names.add(groupname);
|
|
|
- } */
|
|
|
-
|
|
|
- for(let [name, group] of groups){
|
|
|
- group.mean = group.sum / group.n;
|
|
|
- group.measures.sort( (a, b) => a.duration - b.duration );
|
|
|
-
|
|
|
- if(group.n === 1){
|
|
|
- group.median = group.measures[0].duration;
|
|
|
- }else if(group.n > 1){
|
|
|
- group.median = group.measures[parseInt(group.n / 2)].duration;
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- let cn = Array.from(names).reduce( (a, i) => Math.max(a, i.length), 0) + 5;
|
|
|
- let cmin = 10;
|
|
|
- let cmed = 10;
|
|
|
- let cmax = 10;
|
|
|
- let csam = 6;
|
|
|
-
|
|
|
- let message = ` ${"NAME".padEnd(cn)} |`
|
|
|
- + ` ${"MIN".padStart(cmin)} |`
|
|
|
- + ` ${"MEDIAN".padStart(cmed)} |`
|
|
|
- + ` ${"MAX".padStart(cmax)} |`
|
|
|
- + ` ${"SAMPLES".padStart(csam)} \n`;
|
|
|
- message += ` ${"-".repeat(message.length) }\n`;
|
|
|
-
|
|
|
- names = Array.from(names).sort();
|
|
|
- for(let name of names){
|
|
|
- let group = groups.get(name);
|
|
|
- let min = group.min.toFixed(3);
|
|
|
- let median = group.median.toFixed(3);
|
|
|
- let max = group.max.toFixed(3);
|
|
|
- let n = group.n;
|
|
|
-
|
|
|
- message += ` ${name.padEnd(cn)} |`
|
|
|
- + ` ${min.padStart(cmin)} |`
|
|
|
- + ` ${median.padStart(cmed)} |`
|
|
|
- + ` ${max.padStart(cmax)} |`
|
|
|
- + ` ${n.toString().padStart(csam)}\n`;
|
|
|
- }
|
|
|
- message += `\n`;
|
|
|
- console.log(message);
|
|
|
-
|
|
|
- performance.clearMarks();
|
|
|
- performance.clearMeasures();
|
|
|
- this.toggle = timestamp;
|
|
|
- }
|
|
|
- }
|
|
|
+ let groupname = `[tq] ${key}`;
|
|
|
+ groups.set(groupname, group);
|
|
|
+ names.add(groupname);
|
|
|
+ } */
|
|
|
+
|
|
|
+ for(let [name, group] of groups){
|
|
|
+ group.mean = group.sum / group.n;
|
|
|
+ group.measures.sort( (a, b) => a.duration - b.duration );
|
|
|
+
|
|
|
+ if(group.n === 1){
|
|
|
+ group.median = group.measures[0].duration;
|
|
|
+ }else if(group.n > 1){
|
|
|
+ group.median = group.measures[parseInt(group.n / 2)].duration;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ let cn = Array.from(names).reduce( (a, i) => Math.max(a, i.length), 0) + 5;
|
|
|
+ let cmin = 6;
|
|
|
+ let cmed = 6;
|
|
|
+ let cmax = 6;
|
|
|
+ let csam = 4;
|
|
|
+
|
|
|
+ let message = ` ${"NAME".padEnd(cn)} |`
|
|
|
+ + ` ${"MIN".padStart(cmin)} |`
|
|
|
+ + ` ${"MEDIAN".padStart(cmed)} |`
|
|
|
+ + ` ${"MAX".padStart(cmax)} |`
|
|
|
+ + ` ${"AVE".padStart(cmax)} |`
|
|
|
+ + ` ${"SAMPLES".padStart(csam)} \n`;
|
|
|
+ message += ` ${"-".repeat(message.length) }\n`;
|
|
|
+
|
|
|
+ names = Array.from(names).sort();
|
|
|
+ for(let name of names){
|
|
|
+ let group = groups.get(name);
|
|
|
+ let min = group.min.toFixed(3);
|
|
|
+ let median = group.median.toFixed(3);
|
|
|
+ let max = group.max.toFixed(3);
|
|
|
+ let ave = group.mean.toFixed(3); //add
|
|
|
+ let n = group.n;
|
|
|
+
|
|
|
+ message += ` ${name.padEnd(cn)} |`
|
|
|
+ + ` ${min.padStart(cmin)} |`
|
|
|
+ + ` ${median.padStart(cmed)} |`
|
|
|
+ + ` ${max.padStart(cmax)} |`
|
|
|
+ + ` ${ave.padStart(cmax)} |`
|
|
|
+ + ` ${n.toString().padStart(csam)}\n`;
|
|
|
+ }
|
|
|
+ message += `\n`;
|
|
|
+ console.log(message);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ performance.clearMarks();
|
|
|
+ performance.clearMeasures();
|
|
|
+ this.toggle = timestamp;
|
|
|
+ }
|
|
|
+
|
|
|
+ //注意,console.log本身用时挺高,降4倍时可能占用0.5毫秒,所以不能每帧都打印
|
|
|
}
|
|
|
|
|
|
loop(timestamp){
|
|
@@ -3817,10 +3866,8 @@ export class Viewer extends ViewerBase{
|
|
|
if(this.stats){
|
|
|
this.stats.begin();
|
|
|
}
|
|
|
-
|
|
|
- //if(Potree.measureTimings){
|
|
|
- performance.mark("loop-start");
|
|
|
- //}
|
|
|
+ viewer.addTimeMark('loop','start')
|
|
|
+
|
|
|
|
|
|
this.update(this.clock.getDelta(), timestamp);
|
|
|
this.magnifier.render();
|
|
@@ -3845,24 +3892,25 @@ export class Viewer extends ViewerBase{
|
|
|
// }
|
|
|
|
|
|
|
|
|
- if(Potree.measureTimings){
|
|
|
- performance.mark("loop-end");
|
|
|
- performance.measure("loop", "loop-start", "loop-end");
|
|
|
- }
|
|
|
|
|
|
- this.resolveTimings(timestamp);
|
|
|
-
|
|
|
Potree.framenumber++;
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
+ //-------------
|
|
|
+ this.images360.tileDownloader.update()
|
|
|
+ this.images360.panoRenderer.update()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ //-------------
|
|
|
if(this.stats){
|
|
|
this.stats.end();
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- //let endTime = performance.now()
|
|
|
- //console.log('费时:' ,parseInt((endTime-startTime)*100 )/100)
|
|
|
-
|
|
|
- //this.lastEndTime = endTime
|
|
|
+
|
|
|
+ viewer.addTimeMark('loop','end')
|
|
|
+ this.resolveTimings(timestamp, Potree.measureTimings);
|
|
|
+
|
|
|
}
|
|
|
|
|
|
postError(content, params = {}){
|