xzw 6 月之前
父節點
當前提交
169e1364a4
共有 47 個文件被更改,包括 14512 次插入12232 次删除
  1. 10698 10388
      libs/Cesium/Cesium.js
  2. 0 481
      libs/shapefile/shapefile.js
  3. 80 44
      libs/three.js/lines/LineMaterial.js
  4. 20 15
      libs/three.js/loaders/GLTFLoader.js
  5. 9 1
      rollup.config.js
  6. 48 25
      src/Potree.js
  7. 12 5
      src/PotreeRendererNew.js
  8. 51 21
      src/custom/materials/DepthBasicMaterial.js
  9. 7 15
      src/custom/mergeStartTest.js
  10. 10 6
      src/custom/modules/CameraAnimation/CamAniEditor.js
  11. 56 43
      src/custom/modules/CameraAnimation/CameraAnimation.js
  12. 52 0
      src/custom/modules/CameraAnimation/CameraAnimationCurve.js
  13. 2 2
      src/custom/modules/clipModel/Clip.js
  14. 43 28
      src/custom/modules/mergeModel/MergeEditor.js
  15. 1 1
      src/custom/modules/panoEdit/panoEditor.js
  16. 3 18
      src/custom/modules/panos/Images360.js
  17. 8 14
      src/custom/modules/panos/Panorama.js
  18. 21 13
      src/custom/objects/Sprite.js
  19. 186 98
      src/custom/objects/Tag.js
  20. 193 149
      src/custom/objects/TextSprite.js
  21. 2 2
      src/custom/objects/tool/CurveCtrl.js
  22. 30 65
      src/custom/objects/tool/Measure.js
  23. 79 46
      src/custom/objects/tool/MeasuringTool.js
  24. 1188 0
      src/custom/objects/tool/Path.js
  25. 26 30
      src/custom/objects/tool/TagTool.js
  26. 3 3
      src/custom/objects/tool/TransformControls.js
  27. 95 45
      src/custom/objects/tool/ctrlPolygon.js
  28. 48 10
      src/custom/potree.shim.js
  29. 46 10
      src/custom/settings.js
  30. 162 189
      src/custom/start.js
  31. 5 0
      src/custom/three.shim.js
  32. 40 1
      src/custom/utils/Common.js
  33. 17 3
      src/custom/utils/CursorDeal.js
  34. 2 3
      src/custom/utils/History.js
  35. 235 115
      src/custom/viewer/ViewerNew.js
  36. 5 5
      src/custom/viewer/map/MapViewer.js
  37. 20 10
      src/loader/BinaryLoader.js
  38. 435 21
      src/loader/ShapefileLoader.js
  39. 83 56
      src/materials/shaders/depthBasic.fs
  40. 24 4
      src/materials/shaders/depthBasic.vs
  41. 62 42
      src/navigation/InputHandlerNew.js
  42. 100 121
      src/navigation/OrbitControlsNew.js
  43. 57 43
      src/viewer/EDLRendererNew.js
  44. 20 27
      src/viewer/ExtendScene.js
  45. 2 5
      src/viewer/sidebar2.html
  46. 7 1
      src/viewer/sidebarNew.js
  47. 219 8
      src/workers/BinaryDecoderWorker.js

File diff suppressed because it is too large
+ 10698 - 10388
libs/Cesium/Cesium.js


+ 0 - 481
libs/shapefile/shapefile.js

@@ -1,481 +0,0 @@
-// https://github.com/mbostock/shapefile Version 0.6.2. Copyright 2017 Mike Bostock.
-(function (global, factory) {
-	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
-	typeof define === 'function' && define.amd ? define(['exports'], factory) :
-	(factory((global.shapefile = global.shapefile || {})));
-}(this, (function (exports) { 'use strict';
-
-var array_cancel = function() {
-  this._array = null;
-  return Promise.resolve();
-};
-
-var array_read = function() {
-  var array = this._array;
-  this._array = null;
-  return Promise.resolve(array ? {done: false, value: array} : {done: true, value: undefined});
-};
-
-function array(array) {
-  return new ArraySource(array instanceof Uint8Array ? array : new Uint8Array(array));
-}
-
-function ArraySource(array) {
-  this._array = array;
-}
-
-ArraySource.prototype.read = array_read;
-ArraySource.prototype.cancel = array_cancel;
-
-var fetchPath = function(url) {
-  return fetch(url).then(function(response) {
-    return response.body && response.body.getReader
-        ? response.body.getReader()
-        : response.arrayBuffer().then(array);
-  });
-};
-
-var requestPath = function(url) {
-  return new Promise(function(resolve, reject) {
-    var request = new XMLHttpRequest;
-    request.responseType = "arraybuffer";
-    request.onload = function() { resolve(array(request.response)); };
-    request.onerror = reject;
-    request.ontimeout = reject;
-    request.open("GET", url, true);
-    request.send();
-  });
-};
-
-function path(path) {
-  return (typeof fetch === "function" ? fetchPath : requestPath)(path);
-}
-
-function stream(source) {
-  return typeof source.read === "function" ? source : source.getReader();
-}
-
-var empty = new Uint8Array(0);
-
-var slice_cancel = function() {
-  return this._source.cancel();
-};
-
-function concat(a, b) {
-  if (!a.length) return b;
-  if (!b.length) return a;
-  var c = new Uint8Array(a.length + b.length);
-  c.set(a);
-  c.set(b, a.length);
-  return c;
-}
-
-var slice_read = function() {
-  var that = this, array = that._array.subarray(that._index);
-  return that._source.read().then(function(result) {
-    that._array = empty;
-    that._index = 0;
-    return result.done ? (array.length > 0
-        ? {done: false, value: array}
-        : {done: true, value: undefined})
-        : {done: false, value: concat(array, result.value)};
-  });
-};
-
-var slice_slice = function(length) {
-  if ((length |= 0) < 0) throw new Error("invalid length");
-  var that = this, index = this._array.length - this._index;
-
-  // If the request fits within the remaining buffer, resolve it immediately.
-  if (this._index + length <= this._array.length) {
-    return Promise.resolve(this._array.subarray(this._index, this._index += length));
-  }
-
-  // Otherwise, read chunks repeatedly until the request is fulfilled.
-  var array = new Uint8Array(length);
-  array.set(this._array.subarray(this._index));
-  return (function read() {
-    return that._source.read().then(function(result) {
-
-      // When done, it’s possible the request wasn’t fully fullfilled!
-      // If so, the pre-allocated array is too big and needs slicing.
-      if (result.done) {
-        that._array = empty;
-        that._index = 0;
-        return index > 0 ? array.subarray(0, index) : null;
-      }
-
-      // If this chunk fulfills the request, return the resulting array.
-      if (index + result.value.length >= length) {
-        that._array = result.value;
-        that._index = length - index;
-        array.set(result.value.subarray(0, length - index), index);
-        return array;
-      }
-
-      // Otherwise copy this chunk into the array, then read the next chunk.
-      array.set(result.value, index);
-      index += result.value.length;
-      return read();
-    });
-  })();
-};
-
-function slice(source) {
-  return typeof source.slice === "function" ? source :
-      new SliceSource(typeof source.read === "function" ? source
-          : source.getReader());
-}
-
-function SliceSource(source) {
-  this._source = source;
-  this._array = empty;
-  this._index = 0;
-}
-
-SliceSource.prototype.read = slice_read;
-SliceSource.prototype.slice = slice_slice;
-SliceSource.prototype.cancel = slice_cancel;
-
-var dbf_cancel = function() {
-  return this._source.cancel();
-};
-
-var readBoolean = function(value) {
-  return /^[nf]$/i.test(value) ? false
-      : /^[yt]$/i.test(value) ? true
-      : null;
-};
-
-var readDate = function(value) {
-  return new Date(+value.substring(0, 4), value.substring(4, 6) - 1, +value.substring(6, 8));
-};
-
-var readNumber = function(value) {
-  return !(value = value.trim()) || isNaN(value = +value) ? null : value;
-};
-
-var readString = function(value) {
-  return value.trim() || null;
-};
-
-var types = {
-  B: readNumber,
-  C: readString,
-  D: readDate,
-  F: readNumber,
-  L: readBoolean,
-  M: readNumber,
-  N: readNumber
-};
-
-var dbf_read = function() {
-  var that = this, i = 1;
-  return that._source.slice(that._recordLength).then(function(value) {
-    return value && (value[0] !== 0x1a) ? {done: false, value: that._fields.reduce(function(p, f) {
-      p[f.name] = types[f.type](that._decode(value.subarray(i, i += f.length)));
-      return p;
-    }, {})} : {done: true, value: undefined};
-  });
-};
-
-var view = function(array) {
-  return new DataView(array.buffer, array.byteOffset, array.byteLength);
-};
-
-var dbf = function(source, decoder) {
-  source = slice(source);
-  return source.slice(32).then(function(array) {
-    var head = view(array);
-    return source.slice(head.getUint16(8, true) - 32).then(function(array) {
-      return new Dbf(source, decoder, head, view(array));
-    });
-  });
-};
-
-function Dbf(source, decoder, head, body) {
-  this._source = source;
-  this._decode = decoder.decode.bind(decoder);
-  this._recordLength = head.getUint16(10, true);
-  this._fields = [];
-  for (var n = 0; body.getUint8(n) !== 0x0d; n += 32) {
-    for (var j = 0; j < 11; ++j) if (body.getUint8(n + j) === 0) break;
-    this._fields.push({
-      name: this._decode(new Uint8Array(body.buffer, body.byteOffset + n, j)),
-      type: String.fromCharCode(body.getUint8(n + 11)),
-      length: body.getUint8(n + 16)
-    });
-  }
-}
-
-var prototype = Dbf.prototype;
-prototype.read = dbf_read;
-prototype.cancel = dbf_cancel;
-
-function cancel() {
-  return this._source.cancel();
-}
-
-var readMultiPoint = function(record) {
-  var i = 40, j, n = record.getInt32(36, true), coordinates = new Array(n);
-  for (j = 0; j < n; ++j, i += 16) coordinates[j] = [record.getFloat64(i, true), record.getFloat64(i + 8, true)];
-  return {type: "MultiPoint", coordinates: coordinates};
-};
-
-var readNull = function() {
-  return null;
-};
-
-var readPoint = function(record) {
-  return {type: "Point", coordinates: [record.getFloat64(4, true), record.getFloat64(12, true)]};
-};
-
-var readPolygon = function(record) {
-  var i = 44, j, n = record.getInt32(36, true), m = record.getInt32(40, true), parts = new Array(n), points = new Array(m), polygons = [], holes = [];
-  for (j = 0; j < n; ++j, i += 4) parts[j] = record.getInt32(i, true);
-  for (j = 0; j < m; ++j, i += 16) points[j] = [record.getFloat64(i, true), record.getFloat64(i + 8, true)];
-
-  parts.forEach(function(i, j) {
-    var ring = points.slice(i, parts[j + 1]);
-    if (ringClockwise(ring)) polygons.push([ring]);
-    else holes.push(ring);
-  });
-
-  holes.forEach(function(hole) {
-    polygons.some(function(polygon) {
-      if (ringContainsSome(polygon[0], hole)) {
-        polygon.push(hole);
-        return true;
-      }
-    }) || polygons.push([hole]);
-  });
-
-  return polygons.length === 1
-      ? {type: "Polygon", coordinates: polygons[0]}
-      : {type: "MultiPolygon", coordinates: polygons};
-};
-
-function ringClockwise(ring) {
-  if ((n = ring.length) < 4) return false;
-  var i = 0, n, area = ring[n - 1][1] * ring[0][0] - ring[n - 1][0] * ring[0][1];
-  while (++i < n) area += ring[i - 1][1] * ring[i][0] - ring[i - 1][0] * ring[i][1];
-  return area >= 0;
-}
-
-function ringContainsSome(ring, hole) {
-  var i = -1, n = hole.length, c;
-  while (++i < n) {
-    if (c = ringContains(ring, hole[i])) {
-      return c > 0;
-    }
-  }
-  return false;
-}
-
-function ringContains(ring, point) {
-  var x = point[0], y = point[1], contains = -1;
-  for (var i = 0, n = ring.length, j = n - 1; i < n; j = i++) {
-    var pi = ring[i], xi = pi[0], yi = pi[1],
-        pj = ring[j], xj = pj[0], yj = pj[1];
-    if (segmentContains(pi, pj, point)) {
-      return 0;
-    }
-    if (((yi > y) !== (yj > y)) && ((x < (xj - xi) * (y - yi) / (yj - yi) + xi))) {
-      contains = -contains;
-    }
-  }
-  return contains;
-}
-
-function segmentContains(p0, p1, p2) {
-  var x20 = p2[0] - p0[0], y20 = p2[1] - p0[1];
-  if (x20 === 0 && y20 === 0) return true;
-  var x10 = p1[0] - p0[0], y10 = p1[1] - p0[1];
-  if (x10 === 0 && y10 === 0) return false;
-  var t = (x20 * x10 + y20 * y10) / (x10 * x10 + y10 * y10);
-  return t < 0 || t > 1 ? false : t === 0 || t === 1 ? true : t * x10 === x20 && t * y10 === y20;
-}
-
-var readPolyLine = function(record) {
-  var i = 44, j, n = record.getInt32(36, true), m = record.getInt32(40, true), parts = new Array(n), points = new Array(m);
-  for (j = 0; j < n; ++j, i += 4) parts[j] = record.getInt32(i, true);
-  for (j = 0; j < m; ++j, i += 16) points[j] = [record.getFloat64(i, true), record.getFloat64(i + 8, true)];
-  return n === 1
-      ? {type: "LineString", coordinates: points}
-      : {type: "MultiLineString", coordinates: parts.map(function(i, j) { return points.slice(i, parts[j + 1]); })};
-};
-
-var readPolyLineZ = function(record) {
-  var i = 44, j, n = record.getInt32(36, true), m = record.getInt32(40, true), parts = new Array(n), points = new Array(m);
-  for (j = 0; j < n; ++j, i += 4) parts[j] = record.getInt32(i, true);
-  for (j = 0; j < m; ++j, i += 16) points[j] = [record.getFloat64(i, true), record.getFloat64(i + 8, true)];
-  // Advance two doubles (z min, z max)
-  i += 16;
-  for (j = 0; j < m; ++j, i += 8) points[j].push(record.getFloat64(i, true));
-  return n === 1
-      ? {type: "LineStringZ", coordinates: points}
-      : {type: "MultiLineStringZ", coordinates: parts.map(function(i, j) { return points.slice(i, parts[j + 1]); })};
-};
-
-var shp_read = function() {
-  var that = this;
-  return that._source.slice(8).then(function(array) {
-    if (array == null) return {done: true, value: undefined};
-    var header = view(array);
-    return that._source.slice(header.getInt32(4, false) * 2).then(function(array) {
-      var record = view(array);
-      return {done: false, value: record.getInt32(0, true) ? that._type(record) : readNull()};
-    });
-  });
-};
-
-var types$1 = {
-  0: readNull,
-  1: readPoint,
-  3: readPolyLine,
-  5: readPolygon,
-  8: readMultiPoint,
-  11: readPoint,
-  13: readPolyLineZ,
-  15: readPolygon,
-  18: readMultiPoint
-};
-
-var shp = function(source) {
-  source = slice(source);
-  return source.slice(100).then(function(array) {
-    return new Shp(source, view(array));
-  });
-};
-
-function Shp(source, header) {
-  var type = header.getInt32(32, true);
-  if (!(type in types$1)) throw new Error("unsupported shape type: " + type);
-  this._source = source;
-  this._type = types$1[type];
-  this.bbox = [header.getFloat64(36, true), header.getFloat64(44, true), header.getFloat64(52, true), header.getFloat64(60, true)];
-}
-
-var prototype$2 = Shp.prototype;
-prototype$2.read = shp_read;
-prototype$2.cancel = cancel;
-
-function noop() {}
-
-var shapefile_cancel = function() {
-  return Promise.all([
-    this._dbf && this._dbf.cancel(),
-    this._shp.cancel()
-  ]).then(noop);
-};
-
-var shapefile_read = function() {
-  var that = this;
-  return Promise.all([
-    that._dbf ? that._dbf.read() : {value: {}},
-    that._shp.read()
-  ]).then(function(results) {
-    var dbf = results[0], shp = results[1];
-    return shp.done ? shp : {
-      done: false,
-      value: {
-        type: "Feature",
-        properties: dbf.value,
-        geometry: shp.value
-      }
-    };
-  });
-};
-
-var shapefile = function(shpSource, dbfSource, decoder) {
-  return Promise.all([
-    shp(shpSource),
-    dbfSource && dbf(dbfSource, decoder)
-  ]).then(function(sources) {
-    return new Shapefile(sources[0], sources[1]);
-  });
-};
-
-function Shapefile(shp$$1, dbf$$1) {
-  this._shp = shp$$1;
-  this._dbf = dbf$$1;
-  this.bbox = shp$$1.bbox;
-}
-
-var prototype$1 = Shapefile.prototype;
-prototype$1.read = shapefile_read;
-prototype$1.cancel = shapefile_cancel;
-
-function open(shp$$1, dbf$$1, options) {
-  if (typeof dbf$$1 === "string") {
-    if (!/\.dbf$/.test(dbf$$1)) dbf$$1 += ".dbf";
-    dbf$$1 = path(dbf$$1, options);
-  } else if (dbf$$1 instanceof ArrayBuffer || dbf$$1 instanceof Uint8Array) {
-    dbf$$1 = array(dbf$$1);
-  } else if (dbf$$1 != null) {
-    dbf$$1 = stream(dbf$$1);
-  }
-  if (typeof shp$$1 === "string") {
-    if (!/\.shp$/.test(shp$$1)) shp$$1 += ".shp";
-    if (dbf$$1 === undefined) dbf$$1 = path(shp$$1.substring(0, shp$$1.length - 4) + ".dbf", options).catch(function() {});
-    shp$$1 = path(shp$$1, options);
-  } else if (shp$$1 instanceof ArrayBuffer || shp$$1 instanceof Uint8Array) {
-    shp$$1 = array(shp$$1);
-  } else {
-    shp$$1 = stream(shp$$1);
-  }
-  return Promise.all([shp$$1, dbf$$1]).then(function(sources) {
-    var shp$$1 = sources[0], dbf$$1 = sources[1], encoding = "windows-1252";
-    if (options && options.encoding != null) encoding = options.encoding;
-    return shapefile(shp$$1, dbf$$1, dbf$$1 && new TextDecoder(encoding));
-  });
-}
-
-function openShp(source, options) {
-  if (typeof source === "string") {
-    if (!/\.shp$/.test(source)) source += ".shp";
-    source = path(source, options);
-  } else if (source instanceof ArrayBuffer || source instanceof Uint8Array) {
-    source = array(source);
-  } else {
-    source = stream(source);
-  }
-  return Promise.resolve(source).then(shp);
-}
-
-function openDbf(source, options) {
-  var encoding = "windows-1252";
-  if (options && options.encoding != null) encoding = options.encoding;
-  encoding = new TextDecoder(encoding);
-  if (typeof source === "string") {
-    if (!/\.dbf$/.test(source)) source += ".dbf";
-    source = path(source, options);
-  } else if (source instanceof ArrayBuffer || source instanceof Uint8Array) {
-    source = array(source);
-  } else {
-    source = stream(source);
-  }
-  return Promise.resolve(source).then(function(source) {
-    return dbf(source, encoding);
-  });
-}
-
-function read(shp$$1, dbf$$1, options) {
-  return open(shp$$1, dbf$$1, options).then(function(source) {
-    var features = [], collection = {type: "FeatureCollection", features: features, bbox: source.bbox};
-    return source.read().then(function read(result) {
-      if (result.done) return collection;
-      features.push(result.value);
-      return source.read().then(read);
-    });
-  });
-}
-
-exports.open = open;
-exports.openShp = openShp;
-exports.openDbf = openDbf;
-exports.read = read;
-
-Object.defineProperty(exports, '__esModule', { value: true });
-
-})));

+ 80 - 44
libs/three.js/lines/LineMaterial.js

@@ -54,6 +54,9 @@ UniformsLib.line = {
     farPlane:{value: 100000},
     //uUseOrthographicCamera:{ type: "b", value: false },
 
+
+    fadeFar:{value:10},//渐变消失
+
 };
 
 ShaderLib[ 'line' ] = {
@@ -313,18 +316,19 @@ ShaderLib[ 'line' ] = {
         //加
         #if defined(GL_EXT_frag_depth) && defined(useDepth)    
             uniform sampler2D depthTexture;
-            uniform float nearPlane;
-            uniform float farPlane; 
+             
             uniform vec2 resolution;
             uniform vec2 viewportOffset;
             uniform vec3 backColor;
             uniform float occlusionDistance;
             uniform float clipDistance;
             uniform float maxClipFactor;
-            uniform float maxOcclusionFactor;
+            uniform float maxOcclusionFactor; 
         #endif
-
-
+        #if defined(FadeFar)
+            uniform float fadeFar;
+        #endif
+        
 
 		varying float vLineDistance;
 
@@ -353,7 +357,9 @@ ShaderLib[ 'line' ] = {
 		#include <clipping_planes_pars_fragment>
 
 
-        #if defined(GL_EXT_frag_depth) && defined(useDepth)   
+        #if defined(GL_EXT_frag_depth) && defined(useDepth) || defined(FadeFar)  
+            uniform float nearPlane;
+            uniform float farPlane;
             float convertToLinear(float zValue)
             {
                 //if(uUseOrthographicCamera){
@@ -493,56 +499,67 @@ ShaderLib[ 'line' ] = {
 
 
             //加
-            #if defined(GL_EXT_frag_depth) && defined(useDepth)    
+            #if defined(GL_EXT_frag_depth) && defined(useDepth) || defined(FadeFar)  
                
-                float mixFactor = 0.0;
-                float clipFactor = 0.0;
-
                 float fragDepth = convertToLinear(gl_FragCoord.z);
-                //gl_FragCoord大小为 viewport client大小 
-                vec2 depthTxtCoords = vec2(gl_FragCoord.x - viewportOffset.x,  gl_FragCoord.y - viewportOffset.y) / resolution;
+                #if defined(FadeFar) 
+                    float fadeOutFar = fadeFar * 1.3; //完全消失距离
+                    if(fragDepth > fadeOutFar){
+                        discard;
+                    }else if(fragDepth > fadeFar){
+                        alpha *= (fadeOutFar - fragDepth) / (fadeOutFar - fadeFar);
+                        diffuseColor.a = alpha;
+                    }  
+                #endif
+                
+                #if defined(GL_EXT_frag_depth) && defined(useDepth)
+                
+                    float mixFactor = 0.0;
+                    float clipFactor = 0.0;
+                    //gl_FragCoord大小为 viewport client大小 
+                    vec2 depthTxtCoords = vec2(gl_FragCoord.x - viewportOffset.x,  gl_FragCoord.y - viewportOffset.y) / resolution;
 
-                float textureDepth = convertToLinear(texture2D(depthTexture, depthTxtCoords).r);
+                    float textureDepth = convertToLinear(texture2D(depthTexture, depthTxtCoords).r);
 
-                float delta = fragDepth - textureDepth;
+                    float delta = fragDepth - textureDepth;
 
-                if (delta > 0.0)
-                {
+                    if (delta > 0.0)
+                    {
+                        
+                        mixFactor = clamp(delta / occlusionDistance, 0.0, maxOcclusionFactor);
+                        clipFactor = clamp(delta / clipDistance, 0.0, maxClipFactor);
+                    }
+                     
+                    if (clipFactor == 1.0)
+                    {
+                        discard;
+                    }
                     
-                    mixFactor = clamp(delta / occlusionDistance, 0.0, maxOcclusionFactor);
-                    clipFactor = clamp(delta / clipDistance, 0.0, maxClipFactor);
-                }
-                 
-                if (clipFactor == 1.0)
-                {
-                    discard;
-                }
-                
-                vec4 backColor_ = vec4(backColor, opacity); //vec4(0.8,0.8,0.8, 0.8*opacity);
-                 
-                #ifdef DASH_with_depth  
-                    // 只在被遮住的部分显示虚线, 所以若同时是虚线不可见部分和被遮住时, a为0
-                    if(unvisible) backColor_.a = 0.0;
-                #endif 
-                
-                //vec4 diffuseColor = vec4(mix(diffuse, backColor_, mixFactor), opacity*(1.0 - clipFactor));
-               
-               
-               
-                diffuseColor = mix(diffuseColor, backColor_ , mixFactor);   
-               
-               
-                diffuseColor.a *= (1.0 - clipFactor);  
+                    vec4 backColor_ = vec4(backColor, alpha /* opacity */); //vec4(0.8,0.8,0.8, 0.8*opacity);
+                     
+                    #ifdef DASH_with_depth  
+                        // 只在被遮住的部分显示虚线, 所以若同时是虚线不可见部分和被遮住时, a为0
+                        if(unvisible) backColor_.a = 0.0;
+                    #endif 
+                    
+                    //vec4 diffuseColor = vec4(mix(diffuse, backColor_, mixFactor), opacity*(1.0 - clipFactor));
+                   
+                   
+                   
+                    diffuseColor = mix(diffuseColor, backColor_ , mixFactor);   
+                   
+                   
+                    diffuseColor.a *= (1.0 - clipFactor);  
                 
+                #endif
             #endif
 
 
-
 			#include <logdepthbuf_fragment>
 			#include <color_fragment>
 
 			//gl_FragColor = vec4( diffuseColor.rgb, alpha );
-			gl_FragColor = vec4( diffuseColor.rgb, diffuseColor.a );
+			gl_FragColor =  diffuseColor;
 			#include <tonemapping_fragment>
 			#include <encodings_fragment>
 			#include <fog_fragment>
@@ -930,7 +947,21 @@ class LineMaterial extends ShaderMaterial {
     }
     
     
+    set fadeFar(far){
+        let needsUpdate = (!this.fadeFar ) != (!far)  //null为全部范围可见
+        //console.log('fadeFar needsUpdate', needsUpdate)
+        if(far){
+            this.defines.FadeFar = true
+            this.uniforms.fadeFar.value = far 
+        }else{
+            delete this.defines.FadeFar 
+        }
+        needsUpdate && (this.needsUpdate = true)
+    }
     
+    get fadeFar(){
+        return 'FadeFar' in this.defines && this.uniforms.fadeFar.value
+    }
     
     updateDepthParams(e={}){
          
@@ -944,9 +975,14 @@ class LineMaterial extends ShaderMaterial {
         
         if(hasDepth){
             this.uniforms.depthTexture.value = viewer.getPRenderer().getRtEDL(viewport).depthTexture   //其实只赋值一次就行
-            this.uniforms.nearPlane.value = camera.near;
-            this.uniforms.farPlane.value = camera.far;
+             
         }
+        
+        //hasDepth  or  FadeFar:
+        this.uniforms.nearPlane.value = camera.near;
+        this.uniforms.farPlane.value = camera.far; 
+        
+        
         //this.uniforms.uUseOrthographicCamera.value = !camera.isPerspectiveCamera
     }
 

+ 20 - 15
libs/three.js/loaders/GLTFLoader.js

@@ -419,24 +419,29 @@ class GLTFLoader extends Loader {
 						extensions[ extensionName ] = new GLTFMeshQuantizationExtension();
 						break;
 
-					default:
-
+					default: 
 						if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) {
                             if(!unknownExtensions[extensionName]){
                                 console.warn( 'GLTFLoader: Unknown extension "' + extensionName + '".' ,'使用默认的KHR_materials_unlit材质' );
                                 unknownExtensions[extensionName] = 1
                             } 
-                            //xzw add:  默认   
+                            //xzw add:  默认   不加的话都是白色的
                             json.extensionsRequired = 'KHR_materials_unlit'
-                            json.materials = [{
-                                extensions: {KHR_materials_unlit: {}}, 
-                                pbrMetallicRoughness:{
-                                    baseColorFactor:[1, 1, 1, 1],
-                                    baseColorTexture: {index: 0, texCoord: 0},
-                                    metallicFactor: 0,
-                                    roughnessFactor: 0.5,
-                                }
-                            }]
+                            let materials = []
+                            let i=0
+                            while(i<json.materials.length){
+                                materials.push({
+                                    extensions: {KHR_materials_unlit: {}}, 
+                                    pbrMetallicRoughness:{
+                                        baseColorFactor:[1, 1, 1, 1],
+                                        baseColorTexture: {index: i, texCoord: 0},
+                                        metallicFactor: 0,
+                                        roughnessFactor: 0.5,
+                                    }
+                                })
+                                i++;
+                            }
+                            json.materials = materials
                             extensions[ json.extensionsRequired ] = new GLTFMaterialsUnlitExtension();
 						}
                         
@@ -3564,9 +3569,9 @@ class GLTFParser {
 		let materialDef = json.materials[ materialIndex ];
 
         if(!materialDef){
-            console.error('gltf材质数据有问题, 可能引起一些列错误')  //add
-            materialDef = json.materials[0] || {}
-        }
+            throw new Error( 'gltf材质json没有找到该index(已隐藏该tile),  materialIndex :' +  materialIndex)  //add
+            //materialDef = json.materials[0] || {} //这样写无济于事,显示出的纹理是乱的。
+        }  ///让报错,这样mesh不会显示  TypeError: Cannot read properties of undefined (reading 'extensions')
 
 		let materialType;
 		const materialParams = {};

+ 9 - 1
rollup.config.js

@@ -1,3 +1,8 @@
+
+//import { base64 } from "./util/import-base-64.js";   //add
+
+
+
 const fs = require('fs-extra')
 const path = require('path')
 const babel = require('rollup-plugin-babel');
@@ -22,7 +27,10 @@ for (const dir of buildPaths) {
 					format: 'umd',
 					name: 'Potree',
 					sourcemap: true,
-				}
+				},
+                //plugins: [//add
+                //    base64({ include: "**/*.wasm" })
+                //]
 			}, {
 				input: 'src/workers/BinaryDecoderWorker.js',
 				output: {

File diff suppressed because it is too large
+ 48 - 25
src/Potree.js


+ 12 - 5
src/PotreeRendererNew.js

@@ -820,11 +820,18 @@ export class Renderer {
 			gl.enable(gl.BLEND);
             
             if(params.notAdditiveBlending){
-                gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); //NormalBlending 
-                /* gl.enable(gl.DEPTH_TEST);
-                gl.depthMask(true); //如果不开启depthWrite, 深度会错乱。  */ //无解
-                 gl.disable(gl.DEPTH_TEST);
-                 gl.depthMask(true);  
+                 gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); //NormalBlending 
+               
+                 //gl.disable(gl.DEPTH_TEST); gl.depthMask(true); //old
+                  gl.enable(gl.DEPTH_TEST); gl.depthMask(false);
+                 //gl.enable(gl.DEPTH_TEST); gl.depthMask(true); 
+                 //gl.disable(gl.DEPTH_TEST);  
+                /* 几种选择:
+                 1 都开启: 当opacity很小时发黑且遮住其他透明物体 
+                 2 关闭depthTest开启depthWrite 自己深度错乱,前排挡不住后排, 自己将后排的晚渲染可以改善,见disSqToCamZ_。 但前排不透明遮不住它!之前没发现
+                 3 开启depthTest关闭depthWrite 除了2的缺点,还挡不住后排的半透明物体(因为后渲染),或被先渲染的前排透明物体完全遮住。(写入深度值就会遮住后方。总之透明的要后渲染而且按距离排序,但mesh和点云排序是不可能了) 采用这项
+                 4 关闭depthTest关闭depthWrite 连不透明物都遮不住它,后排透明物能遮住它
+                 */
             }else{
                 gl.blendFunc(gl.SRC_ALPHA, gl.ONE); //AdditiveBlending   原本
                 gl.disable(gl.DEPTH_TEST);

+ 51 - 21
src/custom/materials/DepthBasicMaterial.js

@@ -17,17 +17,21 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
 			depthTexture:   { type: 't', 	value: null }, 
 			opacity:        { type: 'f',	value: 1  },
 			map:             { type: 't', 	value: o.map }, 
+            mapColor:  {type:'v3',      value: o.mapColor ?  new THREE.Color(o.mapColor) :  new THREE.Color("#ffffff")},
             baseColor:     {type:'v3',      value: o.color ?  new THREE.Color(o.color) :  new THREE.Color("#ffffff")},
             backColor:     {type:'v3',      value: o.backColor ?  new THREE.Color(o.backColor) :  new THREE.Color("#ddd")},
             clipDistance :     { type: 'f', 	value: o.clipDistance || 4 }, //消失距离
             occlusionDistance :     { type: 'f', 	value:  o.occlusionDistance || 1  }, //变为backColor距离
             maxClipFactor :  { type: 'f', 	value: o.maxClipFactor || 1 },  //0-1 
             maxOcclusionFactor :  { type: 'f', 	value: o.maxOcclusionFactor || 1 },  //0-1
-            mapScale:  { type: 'f', 	value:  o.mapScale || 1 },  //0-1
+            //mapScale:  { type: 'f', 	value:  o.mapScale || 1 },  //0-1
             
             startClipDis:  { type: 'f', 	value:  o.startClipDis || 0 },  //开始逐渐消失的距离
             startOcclusDis:  { type: 'f', 	value:  o.startOcclusDis || 0 },  //开始逐渐褪色的距离
-		}  
+		
+            fadeFar:{value:10},//渐变消失
+            
+        }  
         
         
         let {vs,fs} = Common.changeShaderToWebgl2(Shaders['depthBasic.vs'], Shaders['depthBasic.fs'], 'ShaderMaterial')
@@ -39,7 +43,7 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
             depthWrite: false,
             depthTest: false,
             transparent: o.transparent == void 0 ?  true : o.transparent,
-            side: o.side || 0 /* THREE.DoubleSide */, 
+            
         })
         
         
@@ -70,14 +74,15 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
         
         //-----其他----
          
-        this.autoDepthTest = o.autoDepthTest
-        if(o.opacity != void 0){
+        /*this.autoDepthTest = o.autoDepthTest
+         if(o.opacity != void 0){
             this.opacity = o.opacity
         }
         this.useDepth = o.useDepth
-        this.map = o.map
+        this.fadeFar = o.fadeFar
+        this.map = o.map */
        
-   
+        this.setValues(o)
     }
     
      
@@ -130,20 +135,26 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
     get map(){
         return this.uniforms.map.value
     }
-    set map(map){
-        this.uniforms.map.value = map; 
-        if(map){
+    set map(map){  
+        let oldDefines = Common.CloneObject(this.defines)
+        this.uniforms.map.value = map;         
+        if(map){ 
             this.defines.use_map = '' 
-        }else{
+            if(map.repeat.x != 1 || map.repeat.y != 1){
+                this.setUV()
+            }
+        }else{ 
             delete this.defines.use_map 
+            delete this.defines.UV_Transform
         }
+        Common.ifSame(oldDefines,this.defines) || (this.needsUpdate = true)
     }
      
     get opacity(){
         return this.uniforms.opacity.value
     }
     set opacity(o){
-        this.uniforms && (this.uniforms.opacity.value = o)
+        this.uniforms && o != void 0 && (this.uniforms.opacity.value = o)
     }
     
     get color(){
@@ -154,22 +165,33 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
     }
     
     
-    /* dispose(){ 
-        super.dispose()
-        viewer.depthBasic
-    } */
+    set fadeFar(far){
+        let needsUpdate = (!this.fadeFar ) != (!far)  //null为全部范围可见
+        //console.log('fadeFar needsUpdate', needsUpdate)
+        if(far){
+            this.defines.FadeFar = true
+            this.uniforms.fadeFar.value = far 
+        }else{
+            delete this.defines.FadeFar 
+        }
+        needsUpdate && (this.needsUpdate = true)
+    }
+    
+    get fadeFar(){
+        return 'FadeFar' in this.defines && this.uniforms.fadeFar.value
+    }
+    
     
     copy(source){
         super.copy(source)
         
         this.useDepth = source.useDepth
         this.map = source.map  
+        this.fadeFar = source.fadeFar
+         
         return this
     }
     
-    
-    
-    
     updateDepthParams(e={}){//主要用于点云遮住mesh
         var viewport = e.viewport || viewer.mainViewport;
         var camera = viewport.camera;
@@ -181,11 +203,19 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
         
         if(hasDepth){
             this.uniforms.depthTexture.value = viewer.getPRenderer().getRtEDL(viewport).depthTexture   //其实只赋值一次就行
-            this.uniforms.nearPlane.value = camera.near;
-            this.uniforms.farPlane.value = camera.far; 
         }
+        
+        //hasDepth  or  FadeFar:
+        this.uniforms.nearPlane.value = camera.near;
+        this.uniforms.farPlane.value = camera.far; 
         //this.uniforms.uUseOrthographicCamera.value = !camera.isPerspectiveCamera
     }
 
+    setUV(){
+        if(!this.map)return
+        this.map.updateMatrix()
+        this.uniforms.uvTransform = { value: this.map.matrix.clone() } 
+        this.defines.UV_Transform = true 
+    }
     
 }

+ 7 - 15
src/custom/mergeStartTest.js

@@ -259,15 +259,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
 
     let moveModel = (e)=>{//根据鼠标移动的位置改变位置
           
-        let camera = viewer.mainViewport.camera
-        var origin = new THREE.Vector3(e.pointer.x, e.pointer.y, -1).unproject(camera),
-        end = new THREE.Vector3(e.pointer.x, e.pointer.y, 1).unproject(camera)
-        var dir = end.sub(origin)
-        let planeZ = 0;
-        let r = (planeZ - origin.z)/dir.z
-        let x = r * dir.x + origin.x
-        let y = r * dir.y + origin.y
-        
+        let {x,y} = Potree.Utils.getPointerPosAtHeight(0,e.pointer)
         
         
         /* if(modelType == 'laser'){ 
@@ -329,6 +321,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
              
          
     let tilesetUrls = [   
+        'https://4dkk.4dage.com/fusion/test/b3dm/modelId_11947/tileset.json',//json有的包含多个materials,之前会花掉,现在解决了
         'https://4dkk.4dage.com/fusion/testb3dm/test001/tileset.json',  //体育中心
       // `${Potree.resourcePath}/models/3dtiles/SG-CPwbgVaPvcY/tileset.json`,
       // 'https://4dkk.4dage.com/scene_view_data/SG-CPwbgVaPvcY/images/3dtiles/tileset.json?_=1730963063808',
@@ -382,32 +375,31 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
      
     
     
-    let objUrls = [
-        Potree.resourcePath+'/models/obj/monitor_12M/archmodels95_043',
+    let objUrls = [ 
         Potree.resourcePath+'/models/obj/28M/GW1H',
         //Potree.resourcePath+'/models/obj/496M/Model', 
         Potree.resourcePath+'/models/obj/75M/Tile_+070_+051', 
         Potree.resourcePath+'/models/obj/74M/Tile_+008_+004', 
-         
+        Potree.resourcePath+'/models/obj/53M/output_00',
     ], objIndex = 0
     
     
     
     
     
-    
+     
     
     let modelType,  modelEditing, MergeEditor = viewer.modules.MergeEditor
     Potree.addModel = function(name, done, url, fixPose){ 
       
         cancelMove()
         modelType = name
-        
+         
         let loadDone = (model)=>{ 
             MergeEditor.modelAdded(model)
             if(!fixPose){
                 modelEditing = model; 
-      //          MergeEditor.setModelBtmHeight(model, 0) //默认离地高度为0                
+                MergeEditor.setModelBtmHeight(model, 0) //默认离地高度为0                
                 /* if(name == '3dTiles'){
                     setTimeout(()=>{
                         moveModel({pointer:{x:0,y:0}}) //3dTiles的移动会错乱,先默认放在当前视图中间吧 

+ 10 - 6
src/custom/modules/CameraAnimation/CamAniEditor.js

@@ -1,6 +1,6 @@
 import * as THREE from "../../../../libs/three.js/build/three.module.js"; 
 import {CameraAnimation} from './CameraAnimation.js'
-
+import {CameraAnimationCurve} from './CameraAnimationCurve.js'
 import {  transitions } from '../../utils/transitions.js'
 
 let CamAniEditor = {
@@ -9,12 +9,12 @@ let CamAniEditor = {
      
     
     createAnimation(data){
-        let animation = new CameraAnimation(viewer);
+        let animation = new CameraAnimation(viewer, data.tension);
         if(data) { 
             animation.name = data.name;
             animation.duration = data.duration;
-            animation.useDurSlice = data.useDurSlice
-             
+            animation.useDurSlice = data.useDurSlice 
+              
             for(const cpdata of data.points){ 
                 /* const position = Potree.Utils.datasetPosTransform({ fromDataset: true, position: cpdata.position, datasetId: Potree.settings.originDatasetId })
                 const target = Potree.Utils.datasetPosTransform({ fromDataset: true, position: cpdata.target, datasetId: Potree.settings.originDatasetId })
@@ -206,7 +206,7 @@ let CamAniEditor = {
             
         }
         return result 
-    }
+    },
         
         
         
@@ -214,7 +214,11 @@ let CamAniEditor = {
    
     
     
-    
+    createCurveAni(curve, duration, tangentDt){
+        let ani = new CameraAnimationCurve(curve, duration, tangentDt )
+        
+        return ani
+    }
     
     
     

+ 56 - 43
src/custom/modules/CameraAnimation/CameraAnimation.js

@@ -12,7 +12,7 @@ const colors = {
     target : 'blue'
 }
  
-
+let lastQuaternion
 let lineMats 
 const getLineMat = function(name){
     if(!lineMats){
@@ -40,7 +40,7 @@ const getLineMat = function(name){
 
 export class CameraAnimation extends THREE.EventDispatcher{
 
-	constructor(viewer){
+	constructor(viewer,tension){
 		super();
 		
 		this.viewer = viewer;
@@ -66,7 +66,7 @@ export class CameraAnimation extends THREE.EventDispatcher{
 		this.visible = true;
 
         this.targets = [];  
-		this.createPath();
+		this.createPath(tension);
         this.duration = 5; 
 		this.percent = 0;
         this.currentIndex = 0
@@ -253,8 +253,8 @@ export class CameraAnimation extends THREE.EventDispatcher{
 
 
 
-	createPath(){
-        this.posCurve = new CurveCtrl([],getLineMat('position'), colors.position, 'posCurve'); 
+	createPath(tension){
+        this.posCurve = new CurveCtrl([],getLineMat('position'), colors.position, 'posCurve', {tension } ); 
         //this.targetCurve = new CurveCtrl([], getLineMat('target'), colors.target, 'targetCurve', {noLine:true}); 
         this.posCurve.needsPercent = true
         this.node.add(this.posCurve)
@@ -398,40 +398,51 @@ export class CameraAnimation extends THREE.EventDispatcher{
         
 		
 	 
-        let quaternion
-        
-        if(percent < 1){
-            //修改第二层:使用每个点的重定位的 newPointsPercents
-            
-            this.currentIndex = this.newPointsPercents.findIndex(e=> e>percent ) - 1   
-            //假设每个节点的百分比是精确的,那么:
-            let curIndexPercent = this.newPointsPercents[this.currentIndex];
-            let nextIndexPercent = this.newPointsPercents[this.currentIndex+1];
-            let progress = (percent - curIndexPercent) / (nextIndexPercent - curIndexPercent)//在这两个节点间的百分比
-            
-            //投影到原本的 posCurve.pointsPercent上:
-            let curIndexOriPercent = this.posCurve.pointsPercent[this.currentIndex] 
-            let nextIndexOriPercent = this.posCurve.pointsPercent[this.currentIndex+1] 
-            percent = curIndexOriPercent + (nextIndexOriPercent - curIndexOriPercent) * progress
-            
-            let endQuaternion = this.quaternions[this.currentIndex+1]
-            let startQuaternion = this.quaternions[this.currentIndex]       
-            quaternion = (new THREE.Quaternion()).copy(startQuaternion) 
-            lerp.quaternion(quaternion, endQuaternion)(progress) 
-            
-            
+         
+        let position = this.posCurve.getPointAt(percent); // 需要this.posCurve.points.length>1 否则报错
+        let quaternion  
+        if(this.quaFromCurveTan){//沿着curve行走,目视curve前方
+            let percent2 = percent + (this.tangentDt || 0.001)
+            if(percent2 <= 1){
+                let position2 = this.posCurve.getPointAt(percent2);
+                quaternion = math.getQuaFromPosAim( position,  position2) 
+            }else{
+                quaternion = lastQuaternion
+            } 
         }else{
-             
-            
-            this.currentIndex = this.posCurve.points.length - 1;
-            
-            quaternion = math.getQuaFromPosAim(this.posCurve.points[this.currentIndex], this.targets[this.currentIndex].position)
+            if(percent < 1){
+                //修改第二层:使用每个点的重定位的 newPointsPercents
+                
+                this.currentIndex = this.newPointsPercents.findIndex(e=> e>percent ) - 1   
+                //假设每个节点的百分比是精确的,那么:
+                let curIndexPercent = this.newPointsPercents[this.currentIndex];
+                let nextIndexPercent = this.newPointsPercents[this.currentIndex+1];
+                let progress = (percent - curIndexPercent) / (nextIndexPercent - curIndexPercent)//在这两个节点间的百分比
+                
+                //投影到原本的 posCurve.pointsPercent上:
+                let curIndexOriPercent = this.posCurve.pointsPercent[this.currentIndex] 
+                let nextIndexOriPercent = this.posCurve.pointsPercent[this.currentIndex+1] 
+                percent = curIndexOriPercent + (nextIndexOriPercent - curIndexOriPercent) * progress
+                
+                let endQuaternion = this.quaternions[this.currentIndex+1]
+                let startQuaternion = this.quaternions[this.currentIndex]       
+                quaternion = (new THREE.Quaternion()).copy(startQuaternion) 
+                lerp.quaternion(quaternion, endQuaternion)(progress) 
+                
+                /* if(this.posInterpolate){  
+                    let endPos = this.posCurve.points[this.currentIndex+1]
+                    let startPos = this.posCurve.points[this.currentIndex]       
+                    position = (new THREE.Vector3()).copy(startPos) 
+                    lerp.vector(position, endPos)(progress) 
+                }           */      
+            }else{ 
+                this.currentIndex = this.posCurve.points.length - 1;
+                quaternion = this.quaternions[this.currentIndex]
+                position = this.posCurve.points[this.currentIndex]    
+            }
         }
         
-        
-        const position = this.posCurve.getPointAt(percent); // 需要this.posCurve.points.length>1 否则报错
-        
-        
+        lastQuaternion = quaternion
         //console.log(this.currentIndex, originPercent)
         //缓动:
         var aimQua, aimPos;
@@ -445,8 +456,8 @@ export class CameraAnimation extends THREE.EventDispatcher{
                 aimPos = camera.position.clone();
             }
             
-            transitionRatio = transitionRatio || 1 / Potree.settings.cameraAniSmoothRatio//渐变系数,越小缓动程度越高,越平滑
-            transitionRatio *= delta * 60 //假设标准帧率为60fps,当帧率低时(delta大时)要降低缓动
+            transitionRatio = transitionRatio || 1 / (this.cameraAniSmoothRatio || Potree.settings.cameraAniSmoothRatio)//渐变系数,越小缓动程度越高,越平滑
+            transitionRatio *= delta * 60 //假设标准帧率为60fps,当帧率低时(delta大时) 降低缓动。速度快时缓动太高会偏移路径
             //console.log(transitionRatio, delta) //画面ui变化会使delta变大
             transitionRatio = THREE.Math.clamp(transitionRatio, 0, 1);
             lerp.quaternion(aimQua, quaternion)(transitionRatio) //每次只改变一点点  
@@ -527,12 +538,14 @@ export class CameraAnimation extends THREE.EventDispatcher{
         
         
         let tStart, startTransitionRatio = 0.2
-        let startDelay = 1/startTransitionRatio / 20 ;//因为缓动所以延迟开始,前面前都是at(0),使过渡到开始点位(但是依旧不能准确停在起始点,因为缓动是乘百分比有残留。所以直接平滑衔接到开始后的位置)
+        let startDelay = 1/startTransitionRatio / 20 ;//s   因为缓动所以延迟开始,前面前都是at(0),使过渡到开始点位(但是依旧不能准确停在起始点,因为缓动是乘百分比有残留。所以直接平滑衔接到开始后的位置)
         let hasPlayedTime = 0
         
         let finishDelay = Potree.settings.cameraAniSmoothRatio / 60 * 3//结束后还需要多久时间才能大致达到缓动的最终目标
         let hasStoppedTime = 0
 
+ 
+
 		this.onUpdate = (e) => {
             if(this.posCurve.points.length<2){
                 if(this.posCurve.points.length == 1){
@@ -549,11 +562,11 @@ export class CameraAnimation extends THREE.EventDispatcher{
                 let tNow = performance.now(); 
                 let elapsed = (tNow - tStart) / 1000;
                 percent = elapsed / duration + startPercent;
-            }else{//从当前位置过渡到开始位置 
-                percent = 0
+            }else{//从当前位置过渡到开始位置  
+                percent = 0 //因为at函数里有缓动措施,所以只需要设置位置在初始点即可。
                 hasPlayedTime += e.delta
                 transitionRatio = startTransitionRatio;
-                //console.log('延迟开始') 
+                // console.log('延迟开始') 
                 if(hasPlayedTime > startDelay){
                     tStart = performance.now(); 
                 }
@@ -566,7 +579,7 @@ export class CameraAnimation extends THREE.EventDispatcher{
             
             if(currentIndex != this.currentIndex){
                 currentIndex = this.currentIndex
-                console.log('updateCurrentIndex', currentIndex)
+                //console.log('updateCurrentIndex', currentIndex)
                 this.dispatchEvent({type:'updateCurrentIndex', currentIndex  })
             }
             

+ 52 - 0
src/custom/modules/CameraAnimation/CameraAnimationCurve.js

@@ -0,0 +1,52 @@
+
+import * as THREE from "../../../../libs/three.js/build/three.module.js"; 
+ 
+import math  from "../../utils/math.js";
+import {CameraAnimation} from './CameraAnimation.js' 
+
+
+
+export class CameraAnimationCurve extends THREE.EventDispatcher{ //沿着curve轨迹的相机动画,不指定target,因视线沿着路线
+    
+    constructor(curve, duration, tangentDt ){//直接传入curve得到的points和UtoTMapArr
+        //简单写
+        super()
+        this.quaFromCurveTan = true
+        this.viewer = viewer 
+        //this.curve = curve
+        this.duration = duration
+        this.cameraAniSmoothRatio = 5 //缓动系数设置小点,尽量贴合路径,尤其速度快时
+        //this.newPointsPercents = UtoTMapArr.slice()
+        //this.posInterpolate = true  //不直接在curve上取点,因为UtoTMapArr也是getUtoTmapping得来的, curve上getUtoTmapping后和这个不对应。否则要再算一遍pointsPercent
+       
+        /* this.posCurve = { points getPointAt(t){
+            return curve.getPointAt(t)
+        } } */
+        this.posCurve = curve
+        //每个点直接朝向前面那个点
+        /* this.quaternions = [] 
+        let quaternion, length = points.length
+        for(let i=0; i<length-1; i++){
+            quaternion = math.getQuaFromPosAim( points[i],  points[i+1]) 
+            this.quaternions.push(quaternion) 
+        }
+        this.quaternions.push(quaternion)//最后一个点的朝向和前一个相同即可 */
+        
+        
+    }
+    
+    setVisible(){}
+    updateFrustum(){}
+    
+}
+
+
+
+CameraAnimationCurve.prototype.play = CameraAnimation.prototype.play
+CameraAnimationCurve.prototype.at = CameraAnimation.prototype.at
+CameraAnimationCurve.prototype.set = CameraAnimation.prototype.set
+CameraAnimationCurve.prototype.pause = CameraAnimation.prototype.pause
+
+
+
+

+ 2 - 2
src/custom/modules/clipModel/Clip.js

@@ -181,7 +181,7 @@ var Clip = {
         
         Potree.settings.unableNavigate = true
         Potree.settings.ifShowMarker = false
-        Potree.Utils.updateVisible(viewer.measuringTool.scene, 'clipModel', false)   
+        Potree.Utils.updateVisible(viewer.scene.overlayScene/* viewer.measuringTool.scene */, 'clipModel', false)   
         //Potree.Utils.updateVisible(viewer.mapViewer.cursor, 'clipModel', false)//隐藏地图游标
         viewer.inputHandler.toggleSelection(this.box);
         viewer.inputHandler.fixSelection = true
@@ -262,7 +262,7 @@ var Clip = {
         this.switchView('mainView')
         Potree.settings.unableNavigate = false
         Potree.settings.ifShowMarker = this.previousView.ifShowMarker
-        Potree.Utils.updateVisible(viewer.measuringTool.scene, 'clipModel', true)  
+        Potree.Utils.updateVisible(viewer.scene.overlayScene/* viewer.measuringTool.scene */, 'clipModel', true)  
         //Potree.Utils.updateVisible(viewer.mapViewer.cursor, 'clipModel', true) 
         viewer.setView(this.previousView)
         viewer.setLimitFar(true)

+ 43 - 28
src/custom/modules/mergeModel/MergeEditor.js

@@ -449,6 +449,8 @@ let MergeEditor = {
     },
     
     changeModelPointCount(object, type){
+        if(object.fileType == '3dTiles' || object.isPointcloud) return
+            
         let  posCount , texArea   
         if(type == 'add'){
             let o = viewer.getObjectPointCount(object) 
@@ -471,10 +473,21 @@ let MergeEditor = {
                 this.updateMemoryUsage()
             },{once:true})  
         }) */
-        if(model.fileType != '3dTiles'){
-            this.changeModelPointCount(model,'add')
+        let weightUpdate = ()=>{
+            this.changeModelPointCount(model,'add') 
+            this.updateMemoryUsage()
         }
-        this.updateMemoryUsage()
+        
+        if(model.fileType == 'obj'){//要等待贴图都加载完
+            if(viewer.fileManager.loading){
+                viewer.addEventListener('managerOnLoad',(e)=>{
+                    weightUpdate()
+                },{once:true}) //如果onError了咋办,暂时无法定位manager加载的哪个模型的
+            } 
+        }else{
+            weightUpdate()
+        }
+        
     },
     removeModel(model){
         if(this.selected == model) this.selectModel(null)
@@ -507,9 +520,8 @@ let MergeEditor = {
             })
             
         }
-        if(model.fileType != '3dTiles'){
-            this.changeModelPointCount(model,'sub')
-        }
+       
+        this.changeModelPointCount(model,'sub') 
         this.updateMemoryUsage()
     },
     
@@ -579,7 +591,7 @@ let MergeEditor = {
      
     
     showModelOutline(model, state){ 
-        if(Potree.settings.mergeType2 || (model ? model.fileType == '3dgs' : this.boxHelper.visible)  ){//高斯很卡
+        if(Potree.settings.selectShowBox || (model ? model.fileType == '3dgs' : this.boxHelper.visible)  ){//高斯很卡
             if(state !== false ){
                 this.updateBoxHelper(model) 
                 Potree.Utils.updateVisible(this.boxHelper,'unselect',true)
@@ -787,28 +799,31 @@ let MergeEditor = {
         }else{
             //model.traverse(e=>e.material && setOp(e, opacity))
             model.traverse(mesh=>{ 
-                if(mesh.material){ 
-                    if(mesh.material.originOpacity == void 0 ){
-                        mesh.material.originOpacity = mesh.material.opacity
-                    }
-                    mesh.material.opacity = mesh.material.originOpacity * opacity
-                    
-                    if(mesh.material.opacity<1){
-                        mesh.material.transparent = true  
-                        /* if(model.isPointcloud){
-                            mesh.changePointOpacity(realOpacity)  
-                        }else{
-                            mesh.material.opacity = realOpacity
-                        } */
+                if(mesh.material){
+                    let mats = (mesh.material instanceof Array) ? mesh.material : [mesh.material]
+                    mats.forEach(mat=>{
+                        if(mat.originOpacity == void 0 ){
+                            mat.originOpacity = mesh.material.opacity
+                        }
+                        mat.opacity = mat.originOpacity * opacity
                         
-                        mesh.renderOrder = Potree.config.renderOrders.model+1 
-                        //mesh.material.depthWrite = false
-                    }else{
-                        mesh.material.transparent = false
-                        mesh.renderOrder = Potree.config.renderOrders.model
-                        //mesh.material.depthWrite = true
-                    }
-                    mesh.material.depthWrite = mesh.material.opacity>0.3
+                        if(mat.opacity<1){
+                            mat.transparent = true  
+                            /* if(model.isPointcloud){
+                                mesh.changePointOpacity(realOpacity)  
+                            }else{
+                                mesh.material.opacity = realOpacity
+                            } */
+                            
+                            mesh.renderOrder = Potree.config.renderOrders.model+1 //如果是一个mesh多个material咋整? obj的。 暂时默认全部opacity一样吧
+                            //mesh.material.depthWrite = false
+                        }else{
+                            mat.transparent = false
+                            mesh.renderOrder = Potree.config.renderOrders.model
+                            //mesh.material.depthWrite = true
+                        }
+                        mat.depthWrite =  mat.opacity > 0.5 //防止的mesh之间完全遮挡,去掉write。write为true会完全遮挡后排的物体。没有write之后需要对渲染排序(three会排序,但有的角度会错)
+                    }) 
                 }
             })
         }

+ 1 - 1
src/custom/modules/panoEdit/panoEditor.js

@@ -263,7 +263,7 @@ class PanoEditor extends THREE.EventDispatcher{
             
             
             viewer.addEventListener('global_click',(e)=>{
-                if(this.entered)return
+                if(!this.entered)return
                 if(e.button === THREE.MOUSE.RIGHT){//取消旋转和平移
                      //console.log('right click',e)
                      this.setLinkOperateState('addLink',false)

+ 3 - 18
src/custom/modules/panos/Images360.js

@@ -1985,7 +1985,7 @@ export class Images360 extends THREE.EventDispatcher{
                 
                 //注:热点最好加上法线信息,这样可以多加一个限制,尽量顺着热点像展示的方向。 
             },
-            (pano)=>{
+            /* (pano)=>{
                 let  score = 0
                 if(pano.depthTex && checkIntersect){    
                     let intersect = !!viewer.ifPointBlockedByIntersect(target, pano.id, true)       //viewer.inputHandler.ifBlockedByIntersect({point:target, margin:0.1, cameraPos:pano})
@@ -1998,7 +1998,7 @@ export class Images360 extends THREE.EventDispatcher{
                     score = base * 1.5  //没加载好的话,不管了 , 几乎当做无遮挡,否则容易到不了最近点 
                 }
                 return score
-            }
+            } */
         
         ) 
         
@@ -2006,22 +2006,7 @@ export class Images360 extends THREE.EventDispatcher{
 		var g = Common.sortByScore(panos,  require, rank);
         // console.log(g)
         
-		/* let result1 = g && g.slice(0, 10)
-        if(result1){ 
-            g = Common.sortByScore(result1,  [], [(e)=>{//避免遮挡
-                let pano = e.item;
-                let  score = 0, log = '' 
-                  
-                if(atFloor && atFloor.panos.includes(pano)){//如果不在任何一楼呢?
-                    score += 600,  log+='atFloor' 
-                } 
-                return  {score, log}
-            }]);
-            if(g){
-                g.forEach(e=>{e.item = e.item.item})
-            }   
-            console.log(g)            
-        } */
+		 
         let pano = g && g.length > 0 && g[0].item
         if(pano && checkIntersect){
             let intersect = !!viewer.ifPointBlockedByIntersect(target, pano.id, true)

+ 8 - 14
src/custom/modules/panos/Panorama.js

@@ -236,9 +236,12 @@ class Panorama extends THREE.EventDispatcher{
         if(!this.pointcloud.hasDepthTex || this.depthTex || this.depthTexLoading)return
         this.depthTexLoading = true
         let mapping = Potree.settings.isLocal2 ? '' : this.pointcloud.datasetData.mapping //非离线包的话加mapping
-        let src = `${Potree.settings.urls.prefix1}/${mapping?(mapping+'/'):''}${Potree.settings.webSite}/${this.pointcloud.sceneCode}/data/${this.pointcloud.sceneCode}/depthmap/${this.originID}.png` 
-               
-               //  `${Potree.settings.urls.prefix1}/${Potree.settings.webSite}/${this.pointcloud.sceneCode}/data/${this.pointcloud.sceneCode}/depthmap/${this.originID}.png`
+        let src 
+        if(Potree.settings.urls.templates.depthTex){
+            src = Potree.Common.replaceAll(Potree.settings.urls.templates.depthTex, '{sceneCode}',  this.pointcloud.sceneCode) + `/${this.originID}.png` 
+        }else{
+            src = `${Potree.settings.urls.prefix1}/${mapping?(mapping+'/'):''}${Potree.settings.webSite}/${this.pointcloud.sceneCode}/data/${this.pointcloud.sceneCode}/depthmap/${this.originID}.png` 
+        }
         //console.log('开始下载depthImg', this.id)
         let texture = texLoader.load( src, ()=>{
             this.depthTex = texture
@@ -334,18 +337,9 @@ class Panorama extends THREE.EventDispatcher{
                 this.label.position.copy(this.floorPosition)
             } 
             this.label.position.z+=0.14
-            this.label.update()
+            this.label.updatePose()
         }
-        
-        /* if(this.label2){
-            if(Potree.settings.editType == 'pano'){
-                this.label2.position.copy(this.position)
-            }else{
-                this.label2.position.copy(this.floorPosition)
-            }
-            this.label2.position.copy(this.marker.position) 
-            this.label2.update()
-        } */
+      
           
     }
     

+ 21 - 13
src/custom/objects/Sprite.js

@@ -13,9 +13,10 @@ export default class Sprite extends THREE.Mesh{
         this.pickOrder = options.pickOrder || 0
         this.sizeInfo = options.sizeInfo
         this.dontFixOrient = options.dontFixOrient
-        this.options = options 
-        this.position.y = options.disToLine || 0 //离线距离
-        
+        this.options = options  
+        //this.position.y = options.disToLine || 0 //离线距离
+        options.transform2D && (this.position.x = options.transform2D.x, this.position.y = options.transform2D.y)//偏移
+         
          
         this.matrixAutoUpdate = false;
         this.matrixMap = new Map() 
@@ -63,7 +64,8 @@ export default class Sprite extends THREE.Mesh{
          
     }
     
-    set visible(v){
+    /* set visible(v){//注释原因renderoverlay时会反复隐藏显示。没必要加,显示后因matrixmap是空的会自动update
+    
         let oldV = this.visible_
         this.visible_ = v  
         if(v && !oldV){ 
@@ -72,15 +74,15 @@ export default class Sprite extends THREE.Mesh{
     }
     get visible(){
         return this.visible_ 
-    }
-    
+    } */
+     
      
     realVisible(viewport, interactables/* , raycaster */){
         if(interactables){
             if(!interactables.some((object)=>{//interactables中是否能找到this
                 let finded
                 object.traverse((object)=>{
-                    if(object == this){
+                    if(object == this.root){
                         finded = true
                         return {stopContinue:true}
                     }
@@ -100,7 +102,7 @@ export default class Sprite extends THREE.Mesh{
             return false
         }
         let v = true 
-        let parent = this.parent 
+        let parent = this.root 
         let lastParent = this
         while(parent){
             if(parent.visible === false){
@@ -162,6 +164,9 @@ export default class Sprite extends THREE.Mesh{
                 let setVisi = (state)=>{
                     this.visiMap.set(e.viewport, state)
                     Potree.Utils.updateVisible(this, 'unableCompute', !!state)
+                    //赋值原先的,否则之后每次render都触发update:
+                    this.matrixMap.set(e.viewport, this.matrix.clone())
+                    this.matrixMapRoot.set(e.viewport, this.root.matrix.clone()) 
                 }
                 
                 let renderer = e.viewer ? e.viewer.renderer : e.renderer
@@ -189,8 +194,7 @@ export default class Sprite extends THREE.Mesh{
                         let dis = r2.pos.distanceTo(r1.pos)
                         if(math.closeTo(dis,0)){
                             //console.log('dis == 0') 
-                            setVisi(false)
-                            return
+                            return setVisi(false) 
                             break
                         } 
                         if(dis<10 && !p2StateHistory.includes('tooLong')){//和r1的屏幕距离太近,要加长,否则精度过低
@@ -286,7 +290,7 @@ export default class Sprite extends THREE.Mesh{
             this.root.updateMatrix(); //因this.position可能在两个viewport不同          
             this.matrixMapRoot.set(e.viewport, this.root.matrix.clone())
         }
-         
+        //this.parent.text && console.log('update',this.parent.text, this.matrix.elements,  this.root.matrix.elements)
         this.needsUpdate = false
         this.useViewport = e.viewport
         this.dispatchEvent('spriteUpdated')
@@ -309,8 +313,8 @@ export default class Sprite extends THREE.Mesh{
            
         if(!matrix){
             this.update(e)
-            matrix = this.matrixMap.get(e.viewport);
-            if(!matrix)return                
+            matrix = this.matrixMap.get(e.viewport); 
+            if(!matrix)return              //maybe unvisible  
         }
         
         if(e.viewport == this.useViewport){
@@ -330,6 +334,10 @@ export default class Sprite extends THREE.Mesh{
         //console.log(this.root.name + e.viewport.name + " : "+this.root.matrixWorld.elements)
     }
     
+    
+    
+    
+    
     setUniforms(name,value){
         this.material.setUniforms(name,value) 
     }

+ 186 - 98
src/custom/objects/Tag.js

@@ -22,20 +22,16 @@ const depthMatProp = {  //为了防止拉远后因放大而一半嵌入墙。
     
 }
 
-const renderOrders = {
-    line: 3 ,
-    spot: 15, //高过模型
-    label: 17
-}
-const planeGeo = new THREE.PlaneGeometry(1,1)
+ 
+const planeGeo = new THREE.PlaneBufferGeometry(1,1)
 let texLoader = new THREE.TextureLoader() 
 
 let lineMat  
  
-const defaultLineLength = 0.6
-const defaultSpotScale = 0.4
+const defaultLineLength = 1
+const defaultSpotScale = 0.35
 
-const titleHeight = {uponSpot:0.1, uponLine:0}
+const titleHeight = {uponSpot:0.1 }//title底部和spot顶端间隔
 
 
 const Vectors = {
@@ -51,14 +47,15 @@ class Tag extends THREE.Object3D{
         super()
         
         this.title = o.title 
+        this.fontsize = o.fontsize
         this.lineLength = o.lineLength != void 0 ? o.lineLength : defaultLineLength
         this.position.copy(o.position)
         this.normal = o.normal != void 0 ? o.normal : new THREE.Vector3(0,0, 1)
         this.root = o.root
         this.dragEnable = true 
         this.build(o)
-        
-        
+        this.bindEvent()
+         
         
     }
     
@@ -73,67 +70,125 @@ class Tag extends THREE.Object3D{
         })))
         let group = new THREE.Object3D()
         this.spot = new THREE.Mesh(planeGeo, new DepthBasicMaterial(Object.assign({},depthMatProp,{
-            transparent:true,
-            map: texLoader.load(Potree.resourcePath+'/textures/spot_default.png' ),  
+            transparent:true, 
         })))  
         this.spot.scale.set(defaultSpotScale,defaultSpotScale,defaultSpotScale) 
-        this.spot.renderOrder = renderOrders.spot;
-        
+        this.spot.renderOrder = this.spot.pickOrder = Potree.config.renderOrders.tag.spot; 
+        Potree.settings.isOfficial || this.changeMap(Potree.resourcePath+'/textures/spot_default.png')
+         
+         
         this.line = LineDraw.createFatLine([],  {mat:lineMat})
-        this.line.addEventListener('drag',(e)=>{ 
-            this.dragEnable && this.changePos(e)
-        })
-        this.spot.addEventListener('drag',(e)=>{
-            this.dragEnable && this.onMesh && this.changePos(e)
-        })            
-        //拖拽线来移动。虽然理想方式是拟真,拖拽时不改变在线上的位置,使之平移,但仔细想想似乎办不到。因为墙面normal是不固定的,尤其在交界处难以确定。不知鼠标在空中的位置,即使是平行镜头移动也无法满足所有情况。matterport是加了底座,移动也是改变底座中心。
-    
-        
+        this.line.name = 'tagLine'
+        this.line.renderOrder = this.line.pickOrder = Potree.config.renderOrders.tag.line;
     
         this.titleLabel = new TextSprite(Object.assign({},depthMatProp,{
-            root: group, text: this.title, sizeInfo:{width2d:200}, 
+            root: group, text:'', sizeInfo:{width2d:150}, 
             textColor:{r:255,g:255,b:255,a:1.0},
             backgroundColor:{r:0,g:0,b:0,a:0.7},
             borderRadius: 6,  
-            fontsize:13,  fontWeight:'',//thick
-            renderOrder : renderOrders.label, pickOrder:renderOrders.label,
+            fontsize: this.fontsize || 14,  fontWeight:'',//thick
+            renderOrder : Potree.config.renderOrders.tag.label, 
+            pickOrder: Potree.config.renderOrders.tag.label,
             useDepth : true ,
+            maxLineWidth: 300,
+            transform2Dpercent:{x:0,y:0.5}, //向上移动一半
+            textAlign: Potree.settings.isOfficial && 'left'
         })) //更新sprite时,实际更新的是root: spot的矩阵
-        
+        this.setTitle(this.title)
         this.updateTitlePos()  
-        group.add(this.titleLabel)
-        
-        let mouseover = (e)=>{
-            this.dispatchEvent('mouseover')
-        }
-        let mouseleave = (e)=>{
-            this.dispatchEvent('mouseleave')
-        }
-        let click = (e)=>{
-            this.dispatchEvent('click')
-        }
-        this.spot.addEventListener('mouseover',mouseover)
-        this.spot.addEventListener('mouseleave',mouseleave)
-        this.titleLabel.addEventListener('mouseover',mouseover)
-        this.titleLabel.addEventListener('mouseleave',mouseleave)
-        this.spot.addEventListener('click',click)
-        this.titleLabel.addEventListener('click',click)
-          
-     
+        group.add(this.titleLabel) 
         group.add(this.spot)
         this.add(group);
         this.add(this.line)
+        viewer.tags.add(this)
+        
         
         this.updatePose()
         
+    }
+    
+    
+    
+    bindEvent(){
         
-        viewer.scene.tags.add(this)
+        let hoverState = {
+            line:0,spot:0,label:0
+        }
+        
+        {
+            //因为只有有intersect时才能拖拽,所以写得比较麻烦
+            let cursor = {hoverGrab:0, grabbing:0}
+            let setCursor = (name, action)=>{
+                let state = action == 'add' ? 1 : 0
+                if(state != cursor[name]){ 
+                    cursor[name] = state
+                    viewer.dispatchEvent({
+                        type : "CursorChange", action,  name 
+                    })
+                }
+            }
+            [this.line, this.spot].forEach(e=>e.addEventListener('mousemove',(e)=>{ 
+                hoverState[e.target.name == 'tagLine' ? 'line' : 'spot'] = 1 
+                if(this.dragEnable && viewer.inputHandler.intersect){//能拖拽时
+                    setCursor('hoverGrab', 'add')
+                }else{
+                    setCursor('hoverGrab', 'remove')
+                }
+            }));
+            [this.line, this.spot].forEach(e=>e.addEventListener('mouseleave',(e)=>{
+                hoverState[e.target.name == 'tagLine' ? 'line' : 'spot'] = 0   
+                if(!hoverState.line && !hoverState.spot){//都没hover才取消    
+                    setCursor('hoverGrab', 'remove')
+                }
+                /* if(!hoverState.line && !hoverState.spot && !hoverState.label){
+                    this.dispatchEvent('mouseleave')
+                } */
+            }));
+            
+            
+                
+            [this.line, this.spot].forEach(e=>e.addEventListener('drag',(e)=>{ 
+                if(this.dragEnable && cursor.grabbing){ 
+                    let info = viewer.tagTool.getPoseByIntersect(e)
+                    info && this.changePos(info)
+                }
+            }));
+            [this.line, this.spot].forEach(e=>e.addEventListener('startDragging',(e)=>{ 
+                this.dragEnable && viewer.inputHandler.intersect && setCursor('grabbing', 'add')
+            }));
+            [this.line, this.spot].forEach(e=>e.addEventListener('drop',(e)=>{ 
+                this.dragEnable && setCursor('grabbing', 'remove')
+            }));
+            //拖拽线来移动。虽然理想方式是拟真,拖拽时不改变在线上的位置,使之平移,但仔细想想似乎办不到。因为墙面normal是不固定的,尤其在交界处难以确定。不知鼠标在空中的位置,即使是平行镜头移动也无法满足所有情况。matterport是加了底座,移动也是改变底座中心。
+        }
+        
+        {
+            let mouseover = (e)=>{  
+                this.dispatchEvent('mouseover')
+            }
+            let mouseleave = (e)=>{
+                //if(!hoverState.line && !hoverState.spot && !hoverState.label){
+                    this.dispatchEvent('mouseleave')
+                //} 
+            }
+            let click = (e)=>{
+                this.dispatchEvent('click')
+            }
+            this.spot.addEventListener('mouseover',mouseover)
+            this.spot.addEventListener('mouseleave',mouseleave)
+            this.titleLabel.addEventListener('mouseover',mouseover)
+            this.titleLabel.addEventListener('mouseleave',mouseleave)
+            this.spot.addEventListener('click',click)
+            this.titleLabel.addEventListener('click',click)
+        } 
         
         this.titleLabel.sprite.addEventListener('spriteUpdated',()=>{
             this.updateDepthParams()
         })
     }
     
+    
+    
     updateDepthParams(){//为了避免热点嵌入墙壁,实时根据其大小更新材质系数。 但是在倾斜的角度看由于遮挡距离很大肯定会嵌入的
         let s = this.titleLabel.parent.scale.x 
         let names = ['clipDistance', 'occlusionDistance', 'startClipDis', 'startOcclusDis']
@@ -163,6 +218,7 @@ class Tag extends THREE.Object3D{
         let endPos = this.normal.clone().multiplyScalar(this.lineLength) 
         LineDraw.updateLine(this.line, [new THREE.Vector3(0,0,0), endPos])
         this.titleLabel.parent.position.copy(endPos) 
+        this.titleLabel.updatePose()
         viewer.dispatchEvent('content_changed')
         
         
@@ -175,77 +231,62 @@ class Tag extends THREE.Object3D{
     
     
     
-    changePos(e){ 
-        if(!e.intersect?.location)return
-        this.root = e.intersect.pointcloud || e.intersect.object
-        let localPos = Potree.Utils.datasetPosTransform({ toDataset: true,  pointcloud:e.intersect.pointcloud,  object:e.intersect.object,  position:e.intersect.location })
-        this.position.copy(localPos)
-        this.normal.copy(e.intersect.localNormal)
+    changePos(info){//注:onMesh时在非平地上拖拽,热点旋转会一直变 
+        this.position.copy(info.position)
+        this.normal.copy(info.normal)
+        this.root = info.root
         this.setNorQua()
-        this.updatePose()
+        this.updatePose() 
         this.dispatchEvent('posChanged')
-    }
-    
-    
-    changeTitle(title){
-        this.titleLabel.changeText(title)
-        viewer.dispatchEvent('content_changed')
-    }
-    
-    changeMap(url){
-        let map = texLoader.load(url,()=>{
-            viewer.dispatchEvent('content_changed') 
-        })
-        this.spot.material.map = map
-        
-    }
-    
-    setFaceAngle(faceAngle = 0) {
-        //this.baseQuaternion = quaternion.clone()
-        
-        let delta = faceAngle - (this.faceAngle || 0)
-        //this.plane.quaternion.setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(-faceAngle))
-        this.spot.rotateOnAxis(new THREE.Vector3(0,0,1), THREE.Math.degToRad(delta) )
-        //this.updateLabelPose()
-        this.faceAngle = faceAngle
         viewer.dispatchEvent('content_changed')
     }
     
-  
-    
-    changeOnMesh(onMesh){
+    changeOnMesh(onMesh){//是否贴在mesh上
+        //if(this.title == 'single2') debugger
         this.onMesh = onMesh
-        if(onMesh){ 
+        if(onMesh){//贴mesh上时不是sprite,且可设置旋转值
             this.add(this.spot)
             this.titleLabel.position.y = 0
             this.spot.position.set(0,0,0.01)//在mesh之上偏移一点
             this.setNorQua() 
-            this.line.renderOrder = renderOrders.spot+1 //比spot高,但比label低
-        }else{
+            this.spot.renderOrder = Potree.config.renderOrders.tag.onMesh.spot // 防止遮住线
+            this.line.renderOrder = Potree.config.renderOrders.tag.onMesh.line
+        }else{ 
             this.titleLabel.parent.add(this.spot) 
-            this.updateTitlePos()
+            this.updateTitlePos() 
             this.spot.position.set(0,0,0)
-            this.spot.quaternion.set(0,0,0,1)//this.titleLabel.update()
-            this.line.renderOrder = renderOrders.line //还原
+            this.spot.quaternion.set(0,0,0,1)//this.titleLabel.waitUpdate() 
+            this.realFaceAngle = 0
+            this.spot.renderOrder = Potree.config.renderOrders.tag.spot //还原
+            this.line.renderOrder = Potree.config.renderOrders.tag.line
         }
+        Potree.Utils.updateVisible(this.line,'hideTitle', !this.titleLabel.visible && onMesh ? false : true)
         this.updateDepthParams()
         viewer.dispatchEvent('content_changed')
     }
     
-    changeSpotScale(s){
-        this.spot.scale.set(s,s,s)
-       
+    
+    setFaceAngle(faceAngle = 0) { 
+        //if(this.title == 'single2') debugger 
+        this.faceAngle = faceAngle //先记录,但非onMesh时不会用
+        if(!this.onMesh) return
+             
+        let delta = faceAngle - (this.realFaceAngle || 0)
+        //this.plane.quaternion.setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(-faceAngle))
+        this.spot.rotateOnAxis(new THREE.Vector3(0,0,1), THREE.Math.degToRad(delta) )
+        //this.updateLabelPose()
+        this.realFaceAngle = faceAngle
         viewer.dispatchEvent('content_changed')
     }
     
-    updateTitlePos(){
-        this.onMesh || (this.titleLabel.position.y = titleHeight.uponSpot + this.spot.scale.x)
-    }
+   
+    
     
     setNorQua() {
         if(!this.onMesh)return
-        this.spot.quaternion.setFromRotationMatrix(new THREE.Matrix4().lookAt(this.normal, Vectors.ZERO, Vectors.UP))
-        this.setFaceAngle(this.faceAngle) //quaternion被重置了,所以再设置一下faceAngle
+        this.spot.quaternion.setFromRotationMatrix(new THREE.Matrix4().lookAt(this.normal, Vectors.ZERO, Vectors.UP)) //重算quaternion
+        this.realFaceAngle = 0      //quaternion被重置了,所以再设置一下faceAngle
+        this.setFaceAngle(this.faceAngle) 
     }
     
     /* 
@@ -255,10 +296,56 @@ class Tag extends THREE.Object3D{
     
     
     
+    
+    
+    setTitle(title=''){ 
+        this.titleLabel.setText(title) 
+        this.setTitleVisi(title instanceof Array || title.trim() != '',   'noText')
+        viewer.dispatchEvent('content_changed')
+    }
+    
+    setTitleVisi(v, reason=''){
+        Potree.Utils.updateVisible(this.titleLabel, 'hideTitle-'+reason, v)
+        //tag.onMesh && Potree.Utils.updateVisible(tag.line, 'hideTitle-'+reason, v)
+        //line的可见性比较复杂,所以干脆跟随title的,reason不记录那么多
+        this.onMesh && Potree.Utils.updateVisible(this.line, 'hideTitle', this.titleLabel.visible )
+        viewer.dispatchEvent('content_changed')
+    }
+    
+    setFontSize(fontsize){ 
+        this.titleLabel.fontsize = this.fontsize = fontsize
+        this.titleLabel.updateTexture();
+        //this.updateTitlePos()
+        viewer.dispatchEvent('content_changed')
+    }
+    
+    changeSpotScale(s){
+        s *= defaultSpotScale
+        this.spot.scale.set(s,s,s)
+        this.updateTitlePos()
+        viewer.dispatchEvent('content_changed')
+    }
+    
+    updateTitlePos(){
+        this.onMesh || (this.titleLabel.position.y = titleHeight.uponSpot + this.spot.scale.x / 2)
+    }
+    
+    changeMap(url){
+        let map = texLoader.load(url,()=>{
+            viewer.dispatchEvent('content_changed') 
+        })
+        this.spot.material.map = map
+        
+    }
+    
+    
     updateMatrixWorld(force){ //重写,只为了将root当做parent
-         
+        
+        
+        this.scale.set(1/this.root.scale.x, 1/this.root.scale.y, 1/this.root.scale.z ) //中和模型缩放。无论模型缩放如何都不能改tag大小
+  
         this.updateMatrix() 
-        this.matrixWorld.multiplyMatrices( this.root.matrixWorld, this.matrix );
+        this.matrixWorld.multiplyMatrices( this.root.matrixWorld ,   this.matrix );
          
         const children = this.children;
         for ( let i = 0, l = children.length; i < l; i ++ ) {
@@ -289,6 +376,7 @@ class Tag extends THREE.Object3D{
     dispose(){
         this.parent.remove(this);
         this.titleLabel?.dispose()
+        viewer.dispatchEvent('content_changed')
         
     } 
     

+ 193 - 149
src/custom/objects/TextSprite.js

@@ -8,14 +8,14 @@ import * as THREE from "../../../libs/three.js/build/three.module.js";
 import Sprite from './Sprite.js' 
 import Common from '../utils/Common.js';
 
-//可能还是要用html写,因为要加按钮和图片
+ 
  
 export class TextSprite extends THREE.Object3D{ 
     //注:为了分两层控制scale,不直接extend Sprite
 	constructor( options={}){ 
         super()
 		let map = new THREE.Texture();
-		map.minFilter = THREE.LinearFilter;
+		map.minFilter = THREE.LinearFilter;//清晰一些?
 		map.magFilter = THREE.LinearFilter;
         
         this.sprite = new Sprite( Object.assign({
@@ -30,10 +30,11 @@ export class TextSprite extends THREE.Object3D{
         
         this.fontWeight = options.fontWeight == void 0 ? 'Bold' : options.fontWeight
 		this.rectBorderThick = options.rectBorderThick || 0
-		this.textBorderThick = options.textBorderThick || 0
+		this.textBorderThick = options.textBorderThick || 0 
 		this.fontface = 'Arial';
 		this.fontsize = options.fontsize ||  16; 
-        this.textBorderColor = options.textBorderColor ? Common.CloneObject(options.textBorderColor):{ r: 0, g: 0, b: 0, a: 0.0 };
+        this.lineSpace = options.lineSpace 
+        this.textBorderColor = options.textBorderColor ? Common.CloneObject(options.textBorderColor):{ r: 0, g: 0, b: 0, a: 1.0 };
 		this.backgroundColor = options.backgroundColor ? Common.CloneObject(options.backgroundColor):{ r: 255, g: 255, b: 255, a: 1.0 };
 		this.textColor = options.textColor ? Common.CloneObject(options.textColor):{r: 0, g: 0, b: 0, a: 1.0};
         this.borderColor = options.borderColor  ? Common.CloneObject(options.borderColor):{ r: 0, g: 0, b: 0, a: 0.0 };
@@ -41,29 +42,42 @@ export class TextSprite extends THREE.Object3D{
         this.margin = options.margin
         this.textAlign = options.textAlign || 'center'
         this.name = options.name
+        this.transform2Dpercent = options.transform2Dpercent
+        this.maxLineWidth = options.maxLineWidth
         this.setText(options.text)
-         
-        
-		//this.setText(text);
-        
-        
-         
+          
 	}
 
 	setText(text){  
         if(text == void 0)text = ''
         if (this.text !== text) {
             if (!(text instanceof Array)) {
-                this.text = [text + '']
+                this.text = text.split('\n')  //如果是input手动输入的\n这里会是\\n且不会被拆分, 绘制的依然是\n。
+                //this.text = [text + '']
             } else this.text = text
-            this.updateTexture()
-            this.sprite.waitUpdate() //重新计算各个viewport的matrix 
+            this.updateTexture() 
         }
 	}
-
+/* setText(text){  
+        if(text == void 0)text = ''
+        if (this.text !== text) {
+            if (!(text instanceof Array)) {
+                this.text = text.split('\n')  //如果是input手动输入的\n这里会是\\n且不会被拆分, 绘制的依然是\n。
+                if(this.maxRowWordsCount){//每行显示最大字数
+                    this.text.forEach(str=>{
+                        if(str.length > this.maxRowWordsCount){
+                            
+                        }
+                    })
+                }
+                
+                //this.text = [text + '']
+            } else this.text = text
+            this.updateTexture() 
+        }
+	} */
 	setTextColor(color){
-		this.textColor = Common.CloneObject(color);
-
+		this.textColor = Common.CloneObject(color); 
 		this.updateTexture();
 	}
 
@@ -82,160 +96,94 @@ export class TextSprite extends THREE.Object3D{
         this.position.copy(pos)
         this.sprite.waitUpdate() 
     }
-    update(){
+    
+    updatePose(){
         this.sprite.waitUpdate()
-    }
-    /* setVisible(v){ 
-        Potree.Utils.updateVisible(this, 'setVisible', v)
-    } */
+    }  
+    
     setUniforms(name,value){
         this.sprite.setUniforms(name,value)
     }
-	updateTexture1(){
+	 
+    
+    
+    updateTexture(){
+        //canvas原点在左上角
 		let canvas = document.createElement('canvas');
 		let context = canvas.getContext('2d');
-        const r = window.devicePixelRatio
-		context.font = this.fontWeight + ' ' + this.fontsize * r + 'px ' + this.fontface; 
-       
-        //context["font-weight"] = 100; //语法与 CSS font 属性相同。
-		 
-        //this.text = '啊啊啊啊啊啊fag'
-        
-		let metrics = context.measureText(this.text );
-		let textWidth = metrics.width;
-		let margin = (this.margin ? new THREE.Vector2().copy(this.margin) : new THREE.Vector2(this.fontsize, Math.max(  this.fontsize*0.4, 10)  )).clone().multiplyScalar(r); 
-		let spriteWidth = 2 * margin.x + textWidth + 2 * this.rectBorderThick * r ;
-		let spriteHeight = 2 * margin.y + this.fontsize * r + 2 * this.rectBorderThick * r; 
-		context.canvas.width = spriteWidth;
-		context.canvas.height = spriteHeight;
-		context.font = this.fontWeight + ' ' + this.fontsize * r + 'px ' + this.fontface; 
- 
-        /* let diff = 2//针对英文大部分在baseLine之上所以降低一点(metrics.fontBoundingBoxAscent - metrics.fontBoundingBoxDescent) / 2
-
-        context.textBaseline = "middle"
-         */
-        let expand = Math.max(1, Math.pow(this.fontsize / 16, 1.3)) * r  // 针对英文大部分在baseLine之上所以降低一点,或者可以识别当不包含jgqp时才加这个值  
          
-        //canvas原点在左上角
-        context.textBaseline = 'alphabetic' //  "middle"  //设置文字基线。当起点y设置为0时,只有该线以下的部分被绘制出来。middle时文字显示一半(但是对该字体所有字的一半,有的字是不一定显示一半的,尤其汉字),alphabetic时是英文字母的那条基线。
-        
-        //let actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; // 当前文本字符串在这个字体下用的实际高度
-        
-        //文字y向距离从textBaseline向上算
-        let actualBoundingBoxAscent = metrics.actualBoundingBoxAscent == void 0 ? this.fontsize * r * 0.8 : metrics.actualBoundingBoxAscent //有的流览器没有。只能大概给一个
-        let y = actualBoundingBoxAscent + margin.y + expand 
-        //console.log(this.text, 'y' , y, 'actualBoundingBoxAscent', metrics.actualBoundingBoxAscent,'expand',expand )
-                                  
-        // border color
-        context.strokeStyle = 'rgba(' + this.borderColor.r + ',' + this.borderColor.g + ',' +
-            this.borderColor.b + ',' + this.borderColor.a + ')';
-            
-        let rectBorderThick = this.rectBorderThick * r;
-        
-        context.lineWidth = rectBorderThick
-		// background color
-		context.fillStyle = 'rgba(' + this.backgroundColor.r + ',' + this.backgroundColor.g + ',' +
-			this.backgroundColor.b + ',' + this.backgroundColor.a + ')';
-        this.roundRect(context, rectBorderThick / 2 , rectBorderThick / 2,
-            spriteWidth - rectBorderThick, spriteHeight - rectBorderThick, this.borderRadius * r);
-        
-		// text color
-        if(this.textBorderThick){
-            context.strokeStyle = 'rgba(' + this.textBorderColor.r + ',' + this.textBorderColor.g + ',' +
-                this.textBorderColor.b + ',' + this.textBorderColor.a + ')';
-            context.lineWidth = this.textBorderThick * r;
-            context.strokeText(this.text , rectBorderThick + margin.x,  y /* spriteHeight/2  + diff */ );
-        }
+         
+        const r = window.devicePixelRatio //不乘会模糊
+		context.font = this.fontWeight + ' ' + this.fontsize * r + 'px ' + this.fontface;  //context["font-weight"] = 100; //语法与 CSS font 属性相同。
+	 
         
-		context.fillStyle = 'rgba(' + this.textColor.r + ',' + this.textColor.g + ',' +
-			this.textColor.b + ',' + this.textColor.a + ')';
-		context.fillText(this.text , rectBorderThick + margin.x,  y/* spriteHeight/2  + diff */ );//x,y
- 
+        let textMaxWidth = 0, infos = [] 
+        context.textBaseline = 'alphabetic'  //  "middle"  //设置文字基线。当起点y设置为0时,只有该线以下的部分被绘制出来。middle时文字显示一半(但是对该字体所有字的一半,有的字是不一定显示一半的,尤其汉字),alphabetic时是英文字母的那条基线。
+                     
+        let textHeightAll = 0   
 
-		let texture = new THREE.Texture(canvas);
-		texture.minFilter = THREE.LinearFilter;
-		texture.magFilter = THREE.LinearFilter;
-		texture.needsUpdate = true;
-		//this.material.needsUpdate = true; 
-        
-        if(this.sprite.material.map){
-            this.sprite.material.map.dispose()
+        let texts = []
+        if(this.maxLineWidth){
+            this.text.forEach((words)=>{
+                if(!words){texts.push("");return;}
+                texts = texts.concat( breakLinesForCanvas( words,  context, this.maxLineWidth ) )
+            }) 
+        }else{
+            texts = this.text
         }
-		this.sprite.material.map = texture;
-		  
-		this.sprite.scale.set(spriteWidth * 0.01 / r, spriteHeight * 0.01 / r, 1.0);
-        
+
         
-         
-	}
-    
-    
-    
-    updateTexture(){
-       
-		let canvas = document.createElement('canvas');
-		let context = canvas.getContext('2d');
-        const r = window.devicePixelRatio
-		context.font = this.fontWeight + ' ' + this.fontsize * r + 'px ' + this.fontface; 
-       
-        //context["font-weight"] = 100; //语法与 CSS font 属性相同。
-		 
-        //this.text = '啊啊啊啊啊啊fag'
-        let textMaxWidth = 0,
-            infos = []
-        for (let text of this.text) {
+        for (let text of texts) {
             let metrics = context.measureText(text)
             let textWidth = metrics.width
             infos.push(metrics)
             textMaxWidth = Math.max(textMaxWidth, textWidth)
+            textHeightAll += metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent //文字真实高度
         }
          
-		let margin = (this.margin ? new THREE.Vector2().copy(this.margin) : new THREE.Vector2(this.fontsize, Math.max(  this.fontsize*0.4, 10)  )).clone().multiplyScalar(r); 
-		const lineSpace = (this.fontsize + margin.y) * 0.5
-        let spriteWidth = 2 * margin.x + textMaxWidth + 2 * (this.rectBorderThick + this.textBorderThick)* r;   //还要考虑this.textshadowColor,太麻烦了不写了
-		let spriteHeight = 2 * margin.y + (this.fontsize + this.textBorderThick*2)* r * this.text.length + 2 * this.rectBorderThick * r + lineSpace * (this.text.length - 1); 
-		context.canvas.width = spriteWidth;
+		let margin = (this.margin ? new THREE.Vector2().copy(this.margin) : new THREE.Vector2(this.fontsize,  this.fontsize * 0.8 )).multiplyScalar(r); 
+        
+        const lineSpace = (this.lineSpace || this.fontsize * 0.5) * r
+        let rectBorderThick = this.rectBorderThick * r,   
+            textBorderThick = this.textBorderThick * r   
+            
+        let spriteWidth = 2 * (margin.x + rectBorderThick + textBorderThick ) + textMaxWidth //还要考虑this.textshadowColor,太麻烦了不写了
+		let spriteHeight = 2 * (margin.y + rectBorderThick + textBorderThick * texts.length) + lineSpace * (texts.length - 1)+ textHeightAll; 
+        
+        //canvas宽高只会向下取整数,所以为了防止拉伸模糊这里必须先取整
+        spriteWidth = Math.floor(spriteWidth)
+        spriteHeight = Math.floor(spriteHeight)
+         
+        
+        context.canvas.width = spriteWidth;
 		context.canvas.height = spriteHeight;
-		context.font = this.fontWeight + ' ' + this.fontsize * r + 'px ' + this.fontface; 
+		context.font = this.fontWeight + ' ' + this.fontsize * r + 'px ' + this.fontface; //为何要再写一遍??
         
         if(spriteWidth>4000){
-            console.error('spriteWidth',spriteWidth,'spriteHeight',spriteHeight,this.fontsize,r,this.text,margin)
+            console.error('spriteWidth',spriteWidth,'spriteHeight',spriteHeight,this.fontsize,r,texts,margin)
         }
  
-        /* let diff = 2//针对英文大部分在baseLine之上所以降低一点(metrics.fontBoundingBoxAscent - metrics.fontBoundingBoxDescent) / 2
-
-        context.textBaseline = "middle"
-         */
-        let expand = Math.max(1, Math.pow(this.fontsize / 16, 1.3)) * r  // 针对英文大部分在baseLine之上所以降低一点,或者可以识别当不包含jgqp时才加这个值  
+        
+        let expand = 0//Math.max(1, Math.pow(this.fontsize / 16, 1.1)) * r  // 针对英文大部分在baseLine之上所以降低一点,或者可以识别当不包含jgqp时才加这个值 . 但即使都是汉字也会不同,如"哈哈"和"粉色",前者居中后者不
          
-        //canvas原点在左上角
-        context.textBaseline = 'alphabetic' //  "middle"  //设置文字基线。当起点y设置为0时,只有该线以下的部分被绘制出来。middle时文字显示一半(但是对该字体所有字的一半,有的字是不一定显示一半的,尤其汉字),alphabetic时是英文字母的那条基线。
-              
-        // border color
+       
         context.strokeStyle = 'rgba(' + this.borderColor.r + ',' + this.borderColor.g + ',' + this.borderColor.b + ',' + this.borderColor.a + ')';
-            
-        let rectBorderThick = this.rectBorderThick * r; 
-        context.lineWidth = rectBorderThick
-		// background color
+      
+        context.lineWidth = rectBorderThick 
 		context.fillStyle = 'rgba(' + this.backgroundColor.r + ',' + this.backgroundColor.g + ',' + this.backgroundColor.b + ',' + this.backgroundColor.a + ')';
         this.roundRect(context, rectBorderThick / 2 , rectBorderThick / 2, spriteWidth - rectBorderThick, spriteHeight - rectBorderThick, this.borderRadius * r);
         
-        context.fillStyle = 'rgba(' + this.textColor.r + ',' + this.textColor.g + ',' + this.textColor.b + ',' + this.textColor.a + ')'
- 
-        
         
-        let y = margin.y + expand 
-        for (let i = 0; i < this.text.length; i++) {
-            //let actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent // 当前文本字符串在这个字体下用的实际高度
-
+        context.fillStyle = 'rgba(' + this.textColor.r + ',' + this.textColor.g + ',' + this.textColor.b + ',' + this.textColor.a + ')'
+  
+        let y = margin.y + rectBorderThick 
+        for (let i = 0; i < texts.length; i++) { 
             //文字y向距离从textBaseline向上算
-            let actualBoundingBoxAscent = infos[i].actualBoundingBoxAscent == void 0 ? this.fontsize * r * 0.8 : infos[i].actualBoundingBoxAscent //有的流览器没有。只能大概给一个
-            y += actualBoundingBoxAscent + this.textBorderThick
-            //console.log(actualBoundingBoxAscent)
-
-            //console.log(this.text, 'y' , y, 'actualBoundingBoxAscent', metrics.actualBoundingBoxAscent,'expand',expand )
+            let actualBoundingBoxAscent = infos[i].fontBoundingBoxAscent == void 0 ? this.fontsize * r * 0.8 : infos[i].fontBoundingBoxAscent //有的流览器没有。只能大概给一个
+            y += actualBoundingBoxAscent + textBorderThick
+        
             let textLeftSpace = this.textAlign == 'center' ? (textMaxWidth - infos[i].width) / 2 : this.textAlign == 'left' ? 0  :  textMaxWidth - infos[i].width
-            let x = this.rectBorderThick + margin.x + textLeftSpace
+            let x = rectBorderThick + textBorderThick + margin.x + textLeftSpace
             
             
             // text color
@@ -244,16 +192,19 @@ export class TextSprite extends THREE.Object3D{
                 context.lineWidth = this.textBorderThick * r
                 context.strokeText(this.text[i], x, y)
             }
+               
               
             if (this.textshadowColor) {
                 context.shadowOffsetX = 0
                 context.shadowOffsetY = 0
-                context.shadowColor = this.textshadowColor
-                context.shadowBlur = 12 * r
+                context.shadowColor = this.textshadowColor //'red'
+                context.shadowBlur = (this.textShadowBlur || this.fontSize/3) * r
             }
-            context.fillText(this.text[i], x, y)
-
-            y += lineSpace
+            context.fillText(texts[i], x, y)
+            
+             
+            let actualBoundingBoxDescent = infos[i].fontBoundingBoxDescent == void 0 ? this.fontsize * r * 0.2 : infos[i].fontBoundingBoxDescent  
+            y += actualBoundingBoxDescent + textBorderThick + lineSpace
         } 
         
 		let texture = new THREE.Texture(canvas);
@@ -266,12 +217,16 @@ export class TextSprite extends THREE.Object3D{
             this.sprite.material.map.dispose()
         }
 		this.sprite.material.map = texture;
-		  
+		 
+
+        let oldScale = this.sprite.scale.clone()  
 		this.sprite.scale.set(spriteWidth * 0.01 / r, spriteHeight * 0.01 / r, 1.0);
         
-        
-	 
-    
+        if(!oldScale.equals(this.sprite.scale)){
+            this.updateTransform2D()
+            this.sprite.waitUpdate() //重新计算各个viewport的matrix 
+            
+        }
     }
     
 	roundRect(ctx, x, y, w, h, r){
@@ -291,6 +246,15 @@ export class TextSprite extends THREE.Object3D{
 		ctx.stroke();
 	}
     
+    updateTransform2D(){
+        if(this.transform2Dpercent){
+            ['x','y'].forEach((axis)=>{
+                let percent = this.transform2Dpercent[axis]
+                this.sprite.position.y = this.sprite.scale.y * percent
+            }) 
+        } 
+    }
+    
     dispose(){
         this.sprite.material.uniforms.map.value.dispose()
         this.parent && this.parent.remove(this)
@@ -302,3 +266,83 @@ export class TextSprite extends THREE.Object3D{
 }
 
 
+function findBreakPoint(text, width, context) {
+    var min = 0;
+    var max = text.length - 1;
+
+    while (min <= max) {
+        var middle = Math.floor((min + max) / 2);
+        var middleWidth = context.measureText(text.substr(0, middle)).width;
+        var oneCharWiderThanMiddleWidth = context.measureText(text.substr(0, middle + 1)).width;
+        if (middleWidth <= width && oneCharWiderThanMiddleWidth > width) {
+            return middle;
+        }
+        if (middleWidth < width) {
+            min = middle + 1;
+        } else {
+            max = middle - 1;
+        }
+    }
+
+    return -1;
+}
+
+function breakLinesForCanvas(text, context, width, font) {  
+    var result = [];
+    var breakPoint = 0;
+
+    if (font) {
+        context.font = font;
+    }
+
+    while ((breakPoint = findBreakPoint(text, width, context)) !== -1) {
+        result.push(text.substr(0, breakPoint));
+        text = text.substr(breakPoint);
+    }
+
+    if (text) {
+        result.push(text);
+    }
+
+    return result;
+} //'使用很寻常的二分查找,如果某一个位置之前的文字宽度小于等于设定的宽度,并且它之后一个字之前的文字宽度大于设定的宽度,那么这个位置就是文本的换行点。上面只是找到一个换行点,对于输入的一段文本,需要循环查找,直到不存在这样的换行点为止, 完整的代码如下',
+
+/* 
+function wrapText(text, maxWidth) {  
+  // 创建一个 canvas 元素来测量文本宽度  
+  const canvas = document.createElement('canvas');  
+  const ctx = canvas.getContext('2d');  
+
+  // 设置字体样式  
+  ctx.font = '16px Arial';  
+
+  let currentLine = '';  
+  const lines = [];  
+
+  // 拆分文本为单词数组  
+  const words = text.split(' ');  
+
+  for (let i = 0; i < words.length; i++) {  
+    const word = words[i];  
+    const wordWidth = ctx.measureText(word).width;  
+    const currentLineWidth = ctx.measureText(currentLine).width;  
+
+    if (currentLineWidth + wordWidth < maxWidth) {  
+      // 如果当前行加上这个单词不会超出最大宽度,就将它添加到当前行  
+      currentLine += (currentLine ? ' ' : '') + word;  
+    } else {  
+      // 如果当前行加上这个单词会超出最大宽度,就将当前行添加到结果数组,并开始新的一行  
+      lines.push(currentLine.trim());  
+      currentLine = word;  
+    }  
+  }  
+
+  // 添加最后一行  
+  if (currentLine) {  
+    lines.push(currentLine.trim());  
+  }  
+
+  return lines;  
+}  
+
+ */

+ 2 - 2
src/custom/objects/tool/CurveCtrl.js

@@ -9,13 +9,13 @@ import HandleSprite from  "./HandleSprite.js";
 
 const sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(0.08,0.08,3,2), new THREE.MeshBasicMaterial({color:'#f88'}))
 
-
+ 
 
 export default class CurveCtrl extends THREE.Object3D {
     
     constructor(points, lineMat, color, name, options={}){
         super()
-        this.curve = new THREE.CatmullRomCurve3(points, false, "centripetal"    /* , tension */)
+        this.curve = new THREE.CatmullRomCurve3(points, false, options.tension == void 0 ? "centripetal" : 'catmullrom', options.tension   )
         this.name = name || 'curveNode'
         this.handleMat = options.handleMat
         this.lineMat = lineMat

+ 30 - 65
src/custom/objects/tool/Measure.js

@@ -50,7 +50,7 @@ const mainLabelProp = {
     borderRadius : 12, margin:{x:20,y:4},
     renderOrder : Potree.config.renderOrders.measureLabel, 
     pickOrder: Potree.config.renderOrders.measureLabel,  
-    disToLine:-0.15,
+    transform2D: {x:0, y:-0.15},
     
     useDepth : true , 
     // 2023.10 尽量不让数字被挡住
@@ -68,7 +68,7 @@ const subLabelProp = {
     fontsize: 14 * textSizeRatio,  
     renderOrder : Potree.config.renderOrders.measureLabelSub, 
     pickOrder: Potree.config.renderOrders.measureLabelSub,  
-    disToLine:-0.13,
+    transform2D: {x:0, y:-0.13},
 }
 
 
@@ -87,12 +87,11 @@ export class Measure extends ctrlPolygon{
 		this.constructor.counter = (this.constructor.counter === undefined) ? 0 : this.constructor.counter + 1;
         
         
-        this.name = this.measureType + this.constructor.counter  //'Measure_' + this.constructor.counter;
-           
+        this.name = this.name || this.measureType + this.constructor.counter  
+        this.selectStates = {}
 	  
-		this.markerLabels = [];
-		this.edgeLabels = [];
-		this.angleLabels = [];
+		
+		this.edgeLabels = []; 
 		this.coordinateLabels = [];
         this.area = {value:0,string:''}
          
@@ -112,10 +111,10 @@ export class Measure extends ctrlPolygon{
         }
         
         
-        this.selectStates = {}
+        
         
         this.setUnitSystem(prop.unit || viewer.unitConvert.UnitService.defaultSystem)
-        Potree.Utils.setObjectLayers(this, 'measure' )
+        //Potree.Utils.setObjectLayers(this, 'measure' ) //取消:scene单独渲染应该不需要设置layer
         
         
         if(this.measureType == 'MulDistance' || this.measureType == 'Hor MulDistance' || this.measureType == 'Ver MulDistance'){
@@ -583,8 +582,8 @@ export class Measure extends ctrlPolygon{
 	addMarker (o={}) {
         var index = o.index == void 0 ? this.points.length : o.index  //要当第几个
         
-        let marker = new Sprite({mat:this.getMarkerMaterial('default'), sizeInfo: markerSizeInfo, name:"measure_point"} )
-        Potree.Utils.setObjectLayers(marker, 'measure' )
+        let marker = o.marker || new Sprite({mat:this.getMarkerMaterial('default'), sizeInfo: markerSizeInfo, name:"measure_point"} )
+        //Potree.Utils.setObjectLayers(marker, 'measure' )
         marker.pickOrder = marker.renderOrder = Potree.config.renderOrders.measureMarker 
         marker.markerSelectStates = {} 
         marker.addEventListener('startDragging',(e)=>{
@@ -640,9 +639,9 @@ export class Measure extends ctrlPolygon{
         //marker.measure = this 
         let edge
 		{ // edges 
-            edge = LineDraw.createFatLine( [ ],{mat:this.getLineMat('edgeDefault')} ) 
+            edge = o.edge || LineDraw.createFatLine( [ ],{mat:this.getLineMat('edgeDefault')} ) 
             edge.pickOrder = 0
-            Potree.Utils.setObjectLayers(edge, 'measure' ) 
+            //Potree.Utils.setObjectLayers(edge, 'measure' ) 
 
 
                         
@@ -903,32 +902,7 @@ export class Measure extends ctrlPolygon{
         this.dispatchEvent('disposed')
     }
     
-     
-	getTotalDistance () {
-		if (this.points.length === 0) {
-			return 0;
-		}
-
-		let distance = 0;
-
-		for (let i = 1; i < this.points.length; i++) {
-			let prev = this.points[i - 1];
-			let curr = this.points[i];
-			let d = prev.distanceTo(curr);
-
-			distance += d;
-		}
-
-		if (this.closed && this.points.length > 1) {
-			let first = this.points[0];
-			let last = this.points[this.points.length - 1];
-			let d = last.distanceTo(first);
-
-			distance += d;
-		}
-
-		return distance;
-	}
+      
 
 	getAngleBetweenLines (cornerPoint, point1, point2) {
 		let v1 = new THREE.Vector3().subVectors(point1, cornerPoint);
@@ -955,19 +929,7 @@ export class Measure extends ctrlPolygon{
 		return this.getAngleBetweenLines(point, previous, next);
 	}
     
-    getCenter(type){  
-        if(this.center){
-            return this.center.clone()
-        }else{  
-            let center = this.points.reduce(function(total, currentValue ){
-                return total.add(currentValue)
-            }, new THREE.Vector3 ) 
-            
-            this.points.length && center.multiplyScalar(1/this.points.length)
-            return center //求不出重心呜呜
-        } 
-        
-    }
+    
     
 	// updateAzimuth(){
 	// 	// if(this.points.length !== 2){
@@ -1031,14 +993,14 @@ export class Measure extends ctrlPolygon{
             edgeLabel.addEventListener('mouseleave',()=>{
                 this.setSelected(false, 'edgeLabel')
             })  
-            edgeLabel.addEventListener('click',()=>{
-                this.isNew || viewer.measuringTool.isAdding || viewer.focusOnObject(this, 'measure')
+            edgeLabel.addEventListener('click',(e)=>{ 
+                this.isNew || viewer.measuringTool.isAdding || e.button == THREE.MOUSE.LEFT && viewer.focusOnObject(this, 'measure')
             })
         }
         edgeLabel.visible = false
         edgeLabel.measure = this
         edgeLabel.sprite.material.depthTestWhenPick = true
-        Potree.Utils.setObjectLayers(edgeLabel, 'measure' )
+        //Potree.Utils.setObjectLayers(edgeLabel, 'measure' )
         this.add(edgeLabel)
         
         if(this.measureType == 'MulDistance'){
@@ -1064,7 +1026,7 @@ export class Measure extends ctrlPolygon{
     
     createCenterLabel(name){
         const centerLabel = new TextSprite(
-            $.extend({},mainLabelProp,{sizeInfo: labelSizeInfo, name, disToLine:0, fontsize:16*textSizeRatio} )
+            $.extend({},mainLabelProp,{sizeInfo: labelSizeInfo, name, transform2D:null, fontsize:16*textSizeRatio} )
         )
         
         centerLabel.addEventListener('mouseover',()=>{
@@ -1076,7 +1038,7 @@ export class Measure extends ctrlPolygon{
         centerLabel.addEventListener('click',()=>{
             this.isNew || viewer.measuringTool.isAdding || viewer.focusOnObject(this, 'measure')
         })
-        Potree.Utils.setObjectLayers(centerLabel, 'measure' )
+        //Potree.Utils.setObjectLayers(centerLabel, 'measure' )
        
         Utils.updateVisible(centerLabel, 'setVisible', false)
         return centerLabel; 
@@ -1088,27 +1050,30 @@ export class Measure extends ctrlPolygon{
     
     getMarkerMaterial(type) { 
         if(!markerMats){
-            
+            let maps = [texLoader.load(Potree.resourcePath+'/textures/pic_point_s32.png' ),
+                       texLoader.load(Potree.resourcePath+'/textures/pic_point32.png' )]    
+            maps.forEach(map=>{
+                map.repeat.set(1/markerMapShrink,1/markerMapShrink) 
+                map.offset.set((markerMapShrink-1)/2/markerMapShrink,  (markerMapShrink-1)/2/markerMapShrink)
+            })
+                       
             markerMats = {  
                 default:    new DepthBasicMaterial($.extend({},lineDepthInfo,{ 
                     transparent: !0,
                     opacity: 1,
-                    map: texLoader.load(Potree.resourcePath+'/textures/pic_point_s32.png' ), 
+                    map: maps[0] , 
                     useDepth:true ,
-                    mapScale: markerMapShrink
+                    //mapScale: markerMapShrink
                 })),
                 select:    new THREE.MeshBasicMaterial({  
                     transparent: !0,
                     opacity: 1,
                     depthTest:false,
-                    map: texLoader.load(Potree.resourcePath+'/textures/pic_point32.png'/*   , null, null, { antialias: false } */), 
+                    map: maps[1], 
                 }),   
             }
             Measure.markerMats = markerMats
-             
-            markerMats.select.map.repeat.set(1/markerMapShrink,1/markerMapShrink) 
-            markerMats.select.map.offset.set((markerMapShrink-1)/2/markerMapShrink,  (markerMapShrink-1)/2/markerMapShrink)
-            //markerMats.select.map.offset.set( -1.1 , -1.1 )
+              
         }
         return markerMats[type]
         

+ 79 - 46
src/custom/objects/tool/MeasuringTool.js

@@ -7,7 +7,7 @@ import {CameraMode} from "../../../defines.js";
 import {TextSprite} from '../TextSprite.js'
  
 import {Prism} from "../../modules/volumeCompute/Prism.js";
- 
+import {Path} from './Path.js' 
   
 export class MeasuringTool extends THREE.EventDispatcher{
 	constructor (viewer) {
@@ -21,22 +21,27 @@ export class MeasuringTool extends THREE.EventDispatcher{
 				type: 'cancel_insertions'
 			});
 		});
+ 
 
 		this.showLabels = true;
-        this.scene = new THREE.Scene();
-		this.scene.name = 'scene_measurement';
+        //this.scene = new THREE.Scene();
+		//this.scene.name = 'scene_measurement';
 		//this.light = new THREE.PointLight(0xffffff, 1.0);
 		//this.scene.add(this.light);  
-		this.viewer.inputHandler.registerInteractiveScene(this.scene);
+		
 		  
         this.history = new History({ 
             applyData: (data)=>{ 
                 if(data.measure.parent && data.measure.visible){ 
-                 
+                    if(viewer.scene.measurements.indexOf(data.measure) != viewer.scene.measurements.length-1){//非最新加的
+                        if(/* data.points.length < data.measure.minMarkers ||  */data.isNew) return //不允许减少点数至minMarkers以下, 也不允许 
+                    }
                     data = Potree.Common.CloneObject(data) //避免使用后更改数据又被使用
                     data.measure.reDraw() 
                     data.measure.initData(data) 
                     data.measure.isNew = data.isNew
+                    
+                    //console.log('changeByHistory points', data.points.length)
                     data.measure.dispatchEvent('changeByHistory')
 
                     /* if(data.measure.isPrism){
@@ -47,7 +52,8 @@ export class MeasuringTool extends THREE.EventDispatcher{
                     return true
                 }  
             },
-            getData:(measure)=>{ 
+            getData:(measure)=>{
+                if(measure.points.length == 0)return//没有点的话changeByHistory报错, 得有点可以移动
                 let data = {
                     measure, 
                     points: measure.points.map(e=>e.clone()),
@@ -70,7 +76,7 @@ export class MeasuringTool extends THREE.EventDispatcher{
         
         
 		this.onRemove = (e) => { e.measurement.dispose()/* this.scene.remove(e.measurement); */};
-		this.onAdd = e => {this.scene.add(e.measurement);};
+		this.onAdd = e => {viewer.scene.overlayScene.add(e.measurement);};
 
 		for(let measurement of viewer.scene.measurements){
 			this.onAdd({measurement: measurement});
@@ -90,7 +96,7 @@ export class MeasuringTool extends THREE.EventDispatcher{
         }
         
 		//viewer.addEventListener("update", this.update.bind(this));
-		viewer.addEventListener("render.pass.perspective_overlay", this.render.bind(this));
+		//viewer.addEventListener("render.pass.perspective_overlay", this.render.bind(this));
 		viewer.addEventListener("scene_changed", this.onSceneChange.bind(this));
 
 		viewer.scene.addEventListener('measurement_added', this.onAdd);
@@ -98,6 +104,10 @@ export class MeasuringTool extends THREE.EventDispatcher{
         
         viewer.addEventListener('resize',this.setSize.bind(this))
         
+        
+        
+        
+        
 	}
 
 	onSceneChange(e){
@@ -117,12 +127,15 @@ export class MeasuringTool extends THREE.EventDispatcher{
     
     createMeasureFromData(data){//add 
     
-        const measure = data.measureType == 'MulDistance Ring' ? new Prism(data) : new Measure(data);
+        const measure = data.measureType == 'MulDistance Ring' ? new Prism(data) : 
+              data.type == 'Path' ? new Path(data) : new Measure(data);
+            
         if(measure.failBuilded){
             return 
         }
         viewer.scene.addMeasurement(measure);
         
+        data.type == 'Path' && measure.setEditEnable(false)
         if(measure.guideLine)measure.guideLine.visible = false
         return  measure       
     }
@@ -348,19 +361,29 @@ export class MeasuringTool extends THREE.EventDispatcher{
         
         //this.editing = 
     }
-    
-    
+     
+     
     
 	startInsertion (args = {}, callback, cancelFun) {
-        
+        let measure 
         
 		let domElement = this.viewer.renderer.domElement;
  
- 
-        let measure = args.measureType == 'MulDistance Ring' ? new Prism(args) : new Measure(args);
-        this.scene.add(measure);
+        
+        if(args.resume ){
+            measure = args.measure
+            args.minMarkers = measure.minMarkers
+        }else{
+            measure = args.measureType == 'MulDistance Ring' ? new Prism(args) :
+                      args.type == 'Path' ? new Path(args) : new Measure(args); 
+        
+            viewer.scene.overlayScene.add(measure); 
+        }
+         
         measure.isNew = true
         
+        
+        
 		this.viewer.dispatchEvent({
 			type: 'start_inserting_measurement',
 			measure: measure
@@ -377,6 +400,7 @@ export class MeasuringTool extends THREE.EventDispatcher{
             let length = measure.points.length
 			if (e.button == THREE.MOUSE.LEFT || e.isTouch) { 
 				if (length >= measure.maxMarkers) {
+                    
                     end({finish:true});
 				}else{
                     
@@ -387,13 +411,13 @@ export class MeasuringTool extends THREE.EventDispatcher{
                     if(args.isRect && measure.markers.length == 3){//marker全可见 
                         measure.cloneMarker(0, 3)
                     }else{ 
-                        measure.markers[length].visible = false
-                        measure.edges[length].visible = false 
+                        Potree.Utils.updateVisible(measure.markers[length],'adding',false) 
+                        measure instanceof Path || (measure.edges[length].visible = false )
                     }
-                    measure.edges[length-1].visible = true 
+                    measure instanceof Path ||( measure.edges[length-1].visible = true )
                     
-                    measure.markers[length-1].visible = true;
-                     
+                   
+                    Potree.Utils.updateVisible(measure.markers[length-1],'adding',true) 
                     marker.isDragging = true 
                     
                     this.history.afterChange(measure)
@@ -453,15 +477,23 @@ export class MeasuringTool extends THREE.EventDispatcher{
                         measure.removeMarker(measure.points.length - 1); 
                     }
                 }
+            }else{//仅两个点的
+                this.history.beforeChange(measure)
+                this.history.afterChange(measure)
             }
             measure.isNew = false
             let length = measure.points.length 
             if(length){
-                measure.markers[length-1].visible = true;  
-                measure.edges[length-1].visible = !!measure.closed  
+                Potree.Utils.updateVisible(measure.markers[length-1],'adding',true)  
+                if(!(measure instanceof Path)){
+                    measure.edges[length-1].visible = !!measure.closed  
+                    measure.edges.forEach(edge=>{edge.dispatchEvent('addHoverEvent') })
+                }else{
+                    measure.edge.dispatchEvent('addHoverEvent') 
+                }   
                 
                 measure.markers.forEach(marker=>{marker.dispatchEvent('addHoverEvent') })
-                measure.edges.forEach(edge=>{edge.dispatchEvent('addHoverEvent') })
+                
                 measure.update();//update last edgeLabel 
             }
             
@@ -505,9 +537,6 @@ export class MeasuringTool extends THREE.EventDispatcher{
             if(e.measure && e.measure != measure || !viewer.scene.measurements.includes(measure) || !measure.isNew){
                 return;//若指定了退出的measure但和该measure不一致,就返回
             } 
-            if(e.remove || e.type == 'cancel_insertions'){
-                viewer.scene.removeMeasurement(measure)  
-            }
             
              
             measure.editStateChange(false)
@@ -554,7 +583,7 @@ export class MeasuringTool extends THREE.EventDispatcher{
           
                  
         let click = (e)=>{//一旦点击就立刻增加两marker  
-         
+            
             if(ifAtWrongPlace(e))return  
             if(e.clickElement)return  //如点击label时focusOnObject
              
@@ -562,11 +591,19 @@ export class MeasuringTool extends THREE.EventDispatcher{
             if(e.button === THREE.MOUSE.RIGHT)return 
             
             //console.log('measure clicked33', !!e.intersectPoint)
-             
-            //var I = e.intersectPoint && (e.intersectPoint.orthoIntersect || e.intersectPoint.location)
+              
             var I = e.intersect && (e.intersect.orthoIntersect || e.intersect.location)
             if(!I){
-                return measure.dispatchEvent('intersectNoPointcloud') 
+                if(measure.zPlaneWhenNoIntersect != void 0){
+                    let {x,y} = Potree.Utils.getPointerPosAtHeight(measure.zPlaneWhenNoIntersect, e.pointer )
+                    
+                    I = new THREE.Vector3(x,y, measure.zPlaneWhenNoIntersect)
+                }else{
+                    return measure.dispatchEvent('intersectNoPointcloud') 
+                    
+                    
+                }
+                
             }
             var atMap = e.drag.dragViewport.name == 'mapViewport'
             //在地图上测量的首个点按楼层高度(暂时先只按mainViewport相机高度吧,但navvis是按楼层,画在楼层的地面上,可能因为平面图显示的是楼层近地面),
@@ -588,8 +625,8 @@ export class MeasuringTool extends THREE.EventDispatcher{
             measure.dropMarker(e)
             
             if(measure.maxMarkers > 1 ){
-                measure.markers[1].visible = false
-                measure.edges[1].visible = false
+                Potree.Utils.updateVisible(measure.markers[1],'adding',false) 
+                measure instanceof Path || (measure.edges[1].visible = false)
             }
             if(measure.closed  && !measure.isRect){ 
                 measure.markers[0].addEventListener('mouseover', mouseover);
@@ -643,38 +680,34 @@ export class MeasuringTool extends THREE.EventDispatcher{
         
         
         let changeByHistory = (e)=>{
-            if(!measure.isNew)return
+            if(!measure.isNew )return
             
             let marker = measure.markers[measure.points.length-1]
             this.viewer.inputHandler.startDragging(marker , {endDragFun, notPressMouse:true} );  
-             
+            Potree.Utils.updateVisible(marker,'adding',false) 
             
             var I = viewer.inputHandler.intersect && (viewer.inputHandler.intersect.orthoIntersect || viewer.inputHandler.intersect.location)
             if(I){ 
                 measure.dragChange(I.clone(), measure.points.length-1 )  //使最后一个点在鼠标处
             }
-            /* if(measure.markers.length == 1){
-                Common.updateVisible(marker,  ,false)
-            } */
-            args.isRect || ( measure.edges[measure.points.length-1].visible = false)
-            
-            
-            
+       
+            args.isRect || measure instanceof Path || ( measure.edges[measure.points.length-1].visible = false)
+              
             //measure.continueDrag(measure.markers[measure.points.length-1], o )  
         }
         measure.addEventListener('changeByHistory',changeByHistory)
         
         
-		this.viewer.scene.addMeasurement(measure);
+		args.resume ||  this.viewer.scene.addMeasurement(measure);
         
 		return measure;
 	}
 	
     
-	render(o={}){
+	render(o={}){//废弃
         if(this.scene.children.filter(e=>e.visible).length == 0)return
         let renderer = o.renderer || this.viewer.renderer
-        Potree.Utils.setCameraLayers(o.camera, ['measure'])
+        Potree.Utils.setCameraLayers(o.camera, ['sceneObjects'])
 		
         /* if(o.screenshot && this.viewer.fxaaPass.enabled){ //抗锯齿
             this.viewer.ssaaRenderPass.sampleLevel = 4
@@ -683,8 +716,8 @@ export class MeasuringTool extends THREE.EventDispatcher{
         
             viewer.dispatchEvent({type: "render.begin2" , name:'measure', viewport:o.viewport, renderer:o.renderer  })
             renderer.render(this.scene, o.camera );
-        //} 
-	}
+        //}
+ 	}
     
     
     

File diff suppressed because it is too large
+ 1188 - 0
src/custom/objects/tool/Path.js


+ 26 - 30
src/custom/objects/tool/TagTool.js

@@ -9,11 +9,11 @@ import Tag from '../Tag.js'
 export class TagTool extends THREE.EventDispatcher{
 	constructor (viewer) {
 		super();
-        
-        
-        
-        
         this.viewer = viewer
+  
+        viewer.tags = new THREE.Object3D;
+        viewer.scene.overlayScene.add(viewer.tags)
+        
         
         
         
@@ -28,15 +28,24 @@ export class TagTool extends THREE.EventDispatcher{
     
     
     createTagFromData(data){
-        let tag = new Tag({
-            title: data.title, position: data.position,  normal: data.normal,
-            root: data.root   //e.intersect.pointcloud || e.intersect.object
-        })
-        
+        let tag = new Tag(data)
         return tag
         
     }
      
+     
+    getPoseByIntersect(e){
+        if(!e.intersect?.location)return
+        let root = e.intersect.pointcloud || e.intersect.object
+        let position = Potree.Utils.datasetPosTransform({ toDataset: true,  pointcloud:e.intersect.pointcloud,  object:e.intersect.object,  position:e.intersect.location })
+        let normal = e.intersect.localNormal?.clone() 
+        if(!normal){
+            normal = e.intersect.normal.clone().applyMatrix4(root.rotateInvMatrix) 
+        }
+        
+        return {root,normal,position}
+    } 
+     
     
     startInsertion (args = {}, callback, cancelFun) {
         let deferred = $.Deferred();
@@ -56,30 +65,15 @@ export class TagTool extends THREE.EventDispatcher{
             this.viewer.removeEventListener('global_click', click)
         }
         let click = (e)=>{
-            
-            
-            var worldPos = e.intersect && (/* e.intersect.orthoIntersect ||  */e.intersect.location)
-            if(!worldPos){
-                return  
-            }
-            
-            let localPos = Potree.Utils.datasetPosTransform({ toDataset: true, pointcloud:e.intersect.pointcloud, object:e.intersect.object,  position:worldPos })
-
-            
-            let tag = new Tag({
-                title: '1', position: localPos,  normal:e.intersect.localNormal,
-                root: e.intersect.pointcloud || e.intersect.object
-            }) 
-            
-            //pointcloud里加一个normal 的非float32
-            
-            
-            
+             
+            let info = this.getPoseByIntersect(e)
+            if(!info)return
+            info.title = '1' 
+            let tag = new Tag(info)  
             end()
             e.consume && e.consume()
             deferred.resolve(tag)
-            
-            
+             
             return {stopContinue:true}
             
         }
@@ -90,5 +84,7 @@ export class TagTool extends THREE.EventDispatcher{
     
     
     
+    
+    
 }
 

+ 3 - 3
src/custom/objects/tool/TransformControls.js

@@ -306,7 +306,7 @@ var TransformControls = function ( camera, domElement, options ) {
             ray.set(origin,  direction);
             
             Potree.Utils.setCameraLayers(ray,   //设置能识别到的layers 
-                ['sceneObjects','mapObjects','measure',  'transformationTool', 'model'],
+                ['sceneObjects','mapObjects',/* 'measure',  */ 'transformationTool', 'model'],
                 viewer.inputHandler.hoverViewport && viewer.inputHandler.hoverViewport.extraEnableLayers
             )
 
@@ -342,7 +342,7 @@ var TransformControls = function ( camera, domElement, options ) {
             ray.set(origin,  direction);
             
             Potree.Utils.setCameraLayers(ray,   //设置能识别到的layers 
-                ['sceneObjects','mapObjects','measure',  'transformationTool', 'model'],
+                ['sceneObjects','mapObjects',/* 'measure',  */ 'transformationTool', 'model'],
                 viewer.inputHandler.hoverViewport && viewer.inputHandler.hoverViewport.extraEnableLayers
             )
             
@@ -427,7 +427,7 @@ var TransformControls = function ( camera, domElement, options ) {
 		ray.set(origin,  direction);
 
         Potree.Utils.setCameraLayers(ray,   //设置能识别到的layers 
-            ['sceneObjects','mapObjects','measure',  'transformationTool', 'model'],
+            ['sceneObjects','mapObjects',/* 'measure',   */'transformationTool', 'model'],
             viewer.inputHandler.hoverViewport && viewer.inputHandler.hoverViewport.extraEnableLayers
         )
 

+ 95 - 45
src/custom/objects/tool/ctrlPolygon.js

@@ -17,7 +17,7 @@ const verticalLine = new THREE.Line3()
 export class ctrlPolygon extends THREE.Object3D { 
     constructor (type, prop) {
         super()
-        this.type = type
+        this.Type = type
         
         this.maxMarkers = Number.MAX_SAFE_INTEGER;
        
@@ -57,8 +57,10 @@ export class ctrlPolygon extends THREE.Object3D {
                 this.dataset_points = this.dataset_points.map(e=>{
                     return e && new THREE.Vector3().copy(e) 
                 })
-                prop.points = this.dataset_points.map((p,i)=>{
-                    return Potree.Utils.datasetPosTransform({fromDataset:true, datasetId:this.points_datasets[i], position: p})
+                let oldPoints = prop.points
+                prop.points = this.dataset_points.map((p,i)=>{ 
+                    let point = Potree.Utils.datasetPosTransform({fromDataset:true, datasetId:this.points_datasets[i], position: p})
+                    return point || oldPoints && oldPoints[i] //path 不在模型上的点直接使用prop.points
                 })
                 if(prop.points.some(e=>e == void 0)){
                     return false
@@ -207,28 +209,32 @@ export class ctrlPolygon extends THREE.Object3D {
         
         I = e.intersect && (e.intersect.adsorption ? e.intersect.location : (e.intersect.orthoIntersect || e.intersect.location))
         
-          
-        if(viewer.inputHandler.pressedKeys[18] || Potree.settings.dragPolyBeyondPoint&&!I ){//alt    dragPolyBeyondPoint可以平移拖拽到无点的地方---测试用
-            let i = this.markers.indexOf(e.drag.object);
-            I = this.points[i].clone()
-               
-            const projected = I.clone().project(e.drag.dragViewport.camera);
-            projected.x = e.pointer.x
-            projected.y = e.pointer.y
-             
-            const unprojected = projected.clone().unproject(e.drag.dragViewport.camera);
-            I.copy(unprojected); 
-        } 
-         
-         
+        if(!I){
+            if(this.zPlaneWhenNoIntersect != void 0){
+                let {x,y} = Potree.Utils.getPointerPosAtHeight(this.zPlaneWhenNoIntersect, e.pointer ) 
+                I = new THREE.Vector3(x,y, this.zPlaneWhenNoIntersect)
+            }
+            
+              
+            if(viewer.inputHandler.pressedKeys[18] || Potree.settings.dragPolyBeyondPoint){//alt    dragPolyBeyondPoint可以平移拖拽到无点的地方---测试用
+                let i = this.markers.indexOf(e.drag.object);
+                I = this.points[i].clone()
+                   
+                const projected = I.clone().project(e.drag.dragViewport.camera);
+                projected.x = e.pointer.x
+                projected.y = e.pointer.y
+                 
+                const unprojected = projected.clone().unproject(e.drag.dragViewport.camera);
+                I.copy(unprojected); 
+            }  
+        }
          
          
         if (I) {  
             let i = this.markers.indexOf(e.drag.object);
             if (i !== -1) {  
                 this.dragChange(I.clone(), i, atMap) 
-                
-               
+                 
                 if(this.points_datasets){
                     if(e.intersect){
                         if(e.intersect.pointcloud) this.points_datasets[i] = e.intersect.pointcloud.dataset_id
@@ -239,8 +245,12 @@ export class ctrlPolygon extends THREE.Object3D {
                                 this.points_datasets[i] = pointcloud.dataset_id
                             }else this.points_datasets[i] = null
                         }
+                    }else if(this.zPlaneWhenNoIntersect != void 0){
+                        this.points_datasets[i] = this.dataset_points[i] = null
                     }
-                }
+                } 
+                     
+                
             }
             this.editStateChange(true)
             return true
@@ -323,7 +333,7 @@ export class ctrlPolygon extends THREE.Object3D {
                     if(!this.facePlane || this.cannotConfirmNormal){//三个点且为水平方向时,计算面
                           
                         var points_ = points.map(e=>new THREE.Vector2(e.x,e.y))
-                        var points2 = getDifferentPoint(points_, 2); 
+                        var points2 = this.getDifferentPoint(points_, 2); 
                         if(points2){
                             let normal = math.getNormal2d({p1:points2[0], p2:points2[1]})  
                             normal = new THREE.Vector3(normal.x, normal.y, 0)
@@ -338,7 +348,7 @@ export class ctrlPolygon extends THREE.Object3D {
                 if(!this.faceDirection && this.showArea){ 
                     if(len == 3 || this.isRect) this.cannotConfirmNormal = true //当第三个点固定后(有四个点时)才能固定面
                     if(!this.facePlane || this.cannotConfirmNormal){
-                        var points3 = getDifferentPoint(points, 3);//只有找到三个不同的点算拥有面和area
+                        var points3 = this.getDifferentPoint(points, 3);//只有找到三个不同的点算拥有面和area
                         if(points3){
                             this.facePlane = new THREE.Plane().setFromCoplanarPoints(...points3 )
                         }
@@ -490,7 +500,7 @@ export class ctrlPolygon extends THREE.Object3D {
                 || this.isAtWrongPlace && this.isNew
                 || !e.isAtDomElement && this.isNew//如果是刚添加时在其他dom点击, 不要响应
                 ||  e.hoverViewport != viewer.mainViewport && this.unableDragAtMap //垂直的测量线不允许在地图上放点
-                || this.isNew && !getDifferentPoint(this.points, this.points.length )   //不允许和之前的点相同, 但这句在点云稀疏时会导致难结束 
+                || (this.isNew || this.forbitRepeatPoint)&& !this.getDifferentPoint(this.points, this.points.length )   //不允许和之前的点相同, 但这句在点云稀疏时会导致难结束 
             ) 
         ){
             return this.continueDrag(null,e)    
@@ -621,11 +631,9 @@ export class ctrlPolygon extends THREE.Object3D {
         
 		this.points.splice(index, 1); 
         
-        const marker = this.markers[index]
-		//this.remove(marker); 
+        const marker = this.markers[index] 
         this.markers.splice(index, 1);   
-        marker.dispose()
-        
+        marker.dispose ? marker.dispose() : marker.parent.remove(marker)  
         
 		let edgeIndex = index           //(index === 0) ? 0 : (index - 1);
         const edge = this.edges[edgeIndex]
@@ -726,7 +734,19 @@ export class ctrlPolygon extends THREE.Object3D {
     }
 
 
-    
+    getCenter(type){  
+        if(this.center){
+            return this.center.clone()
+        }else{  
+            let center = this.points.reduce(function(total, currentValue ){
+                return total.add(currentValue)
+            }, new THREE.Vector3 ) 
+            
+            this.points.length && center.multiplyScalar(1/this.points.length)
+            return center //求不出重心呜呜
+        } 
+        
+    }
     
     getIndex(index, add){
         let lastIndex = this.points.length - 1
@@ -734,7 +754,10 @@ export class ctrlPolygon extends THREE.Object3D {
         else if(add == 1)  return (index + 1 > lastIndex) ? 0 : index + 1; 
     }
      
-
+    updateEdge(index, p1,p2){
+        this.edges[index] && LineDraw.updateLine( this.edges[index], p1,p2)
+    }
+    
     update(options={}){
         if(this.points.length === 0){
 			return;
@@ -748,8 +771,8 @@ export class ctrlPolygon extends THREE.Object3D {
             this.updateMarker(this.markers[options.index], this.points[options.index])
             let previousIndex = this.getIndex(options.index, -1)
             let nextIndex = this.getIndex(options.index, +1) 
-            if( this.closed || nextIndex != 0  ) LineDraw.updateLine( this.edges[options.index], [this.points[options.index], this.points[nextIndex]]) 
-            if( this.closed || previousIndex != lastIndex  ) LineDraw.updateLine( this.edges[previousIndex], [this.points[options.index], this.points[previousIndex]]) 
+            if( this.closed || nextIndex != 0  ) this.updateEdge(options.index, [this.points[options.index], this.points[nextIndex]]) //LineDraw.updateLine( this.edges[options.index], [this.points[options.index], this.points[nextIndex]]) 
+            if( this.closed || previousIndex != lastIndex  ) this.updateEdge(previousIndex, [this.points[options.index], this.points[previousIndex]]) //LineDraw.updateLine( this.edges[previousIndex], [this.points[options.index], this.points[previousIndex]]) 
      
         }else{
             
@@ -769,10 +792,11 @@ export class ctrlPolygon extends THREE.Object3D {
                 
                 if(!this.closed && nextIndex == 0  )break; //add
                 { 
-                    let edge = this.edges[index]; 
+                    this.updateEdge(index,[point, nextPoint])
+                    /* let edge = this.edges[index]; 
                     if(edge){
                         LineDraw.updateLine(edge, [point, nextPoint]) 
-                    } 
+                    }  */
                 }  
             }
         }
@@ -835,7 +859,7 @@ export class ctrlPolygon extends THREE.Object3D {
     
     dispose(){//add 
         this.parent.remove(this)
-        this.markers.forEach(e=>e.dispose())  
+        this.markers.forEach(e=>e.dispose && e.dispose())  
         this.edges.forEach(e=>e.geometry.dispose())
     }
     
@@ -912,24 +936,50 @@ export class ctrlPolygon extends THREE.Object3D {
         },1)
         return timer
     }
- 
+    getDifferentPoint(points, count){//for facePlane
+        var result = [];
+        for(let i=0;i<points.length;i++){
+            var p = points[i];
+            if(result.find(e=>e.equals(p)))continue;
+            else result.push(p)
+            if(result.length == count)break
+        }
+        if(result.length == count || count == void 0)return result
+    }
     
-}
+    
+    getTotalDistance () {
+		if (this.points.length === 0) {
+			return 0;
+		}
 
+		let distance = 0;
 
+		for (let i = 1; i < this.points.length; i++) {
+			let prev = this.points[i - 1];
+			let curr = this.points[i];
+			let d = prev.distanceTo(curr);
 
-function getDifferentPoint(points, count){//for facePlane
-    var result = [];
-    for(let i=0;i<points.length;i++){
-        var p = points[i];
-        if(result.find(e=>e.equals(p)))continue;
-        else result.push(p)
-        if(result.length == count)break
-    }
-    if(result.length == count)return result
+			distance += d;
+		}
+
+		if (this.closed && this.points.length > 1) {
+			let first = this.points[0];
+			let last = this.points[this.points.length - 1];
+			let d = last.distanceTo(first);
+
+			distance += d;
+		}
+        this.totalLength = distance
+		return distance;
+	}
+
+    
 }
 
 
+
+
  
 
 

+ 48 - 10
src/custom/potree.shim.js

@@ -56,6 +56,8 @@ var texLoader = new THREE.TextureLoader()
     Potree.browser = browser
 
     /////////// add ////////////////////////////////// 
+    
+    
     Potree.defines.GLCubeFaces = {
         GL_TEXTURE_CUBE_MAP_POSITIVE_X: 0,
         GL_TEXTURE_CUBE_MAP_NEGATIVE_X: 1,
@@ -103,12 +105,18 @@ var texLoader = new THREE.TextureLoader()
         FORWARD: new THREE.Vector3(0,0,-1),
         BACK: new THREE.Vector3(0,0,1)
     };
-    /* var Vectors2 = {}
-    for(var i in Vectors){
-        Vectors2[i] = math.convertVector.YupToZup(Vectors[i])  
+    
+    Potree.defines.gs3d = { 
+        DepthMapRange : 1 << 16,
+        MemoryPageSize : 65536,
+        BytesPerFloat : 4,
+        BytesPerInt : 4,
+        MaxScenes : 32,
+        ProgressiveLoadSectionSize : 262144,
+        ProgressiveLoadSectionDelayDuration : 15,
+        SphericalHarmonics8BitCompressionRange : 3 
     }
-     */
-
+ 
 
     Potree.defines.DownloadStatus = Object.freeze({
         None: 0,
@@ -540,7 +548,15 @@ Utils.getPos2d = function(point, viewport , dom, renderer  ){//获取一个三
     };
 } 
 
-
+Utils.getPointerPosAtHeight = function(planeZ=0, pointer, camera=viewer.mainViewport.camera){ 
+    var origin = new THREE.Vector3(pointer.x, pointer.y, -1).unproject(camera),
+    end = new THREE.Vector3(pointer.x, pointer.y, 1).unproject(camera)
+    var dir = end.sub(origin) 
+    let r = (planeZ - origin.z)/dir.z
+    let x = r * dir.x + origin.x
+    let y = r * dir.y + origin.y
+    return {x,y}
+}
  
 
 Utils.screenPass = new function () {
@@ -932,7 +948,7 @@ Utils.setCameraLayers = function(camera, enableLayers, extraEnableLayers=[]){//a
     enableLayers.concat(extraEnableLayers).forEach(e=>{
         let layer = Potree.config.renderLayers[e]
         if(layer == void 0){
-            console.error('setCameraLayer没找到layer!');
+            console.error('setCameraLayer没找到layer!', e);
             return 
         }
         camera.layers.enable(layer)
@@ -1464,6 +1480,14 @@ Potree.updateVisibility = function(pointclouds, camera, areaSize){
 
 			/* level >= minLevel && */visibleNodes.push(node);
 			/* level >= minLevel && */pointcloud.visibleNodes.push(node);
+            
+            //if(Potree.settings.sortNodesDis){//add
+            if(pointcloud.material.opacity < 1 && Potree.settings.notAdditiveBlending ){ 
+                let nodePos = node.getBoundingSphere().center;
+                let toCam = new THREE.Vector3().subVectors(nodePos, camObjPos)
+                    toCam.projectOnVector(camObjDir)
+                node.disSqToCamZ_ = toCam.lengthSq()
+            }
 
 			if(node._transformVersion === undefined){
 				node._transformVersion = -1;
@@ -1544,7 +1568,7 @@ Potree.updateVisibility = function(pointclouds, camera, areaSize){
 				if(distance - radius < 0){
 					weight = Number.MAX_VALUE;
 				}
-                
+               
                 //如果能得到每个方向上的密度,也就是node数量,密度大的远处少加载,因为被遮挡了显示也没有意义,就好了。
 			} else {
 				// TODO ortho visibility
@@ -1559,7 +1583,7 @@ Potree.updateVisibility = function(pointclouds, camera, areaSize){
                 let distance = sphere.center.distanceToSquared(camObjPos); //先加载中间然后四周 
                 weight = sphere.radius / distance   
                  
-                
+              
                /* let vec = new THREE.Vector3().subVectors(sphere.center, camObjPos)  
                 let disOnCamDir = vec.dot(camObjDir)
                 let vecOnCamDir = camObjDir.clone().multiplyScalar(disOnCamDir) 
@@ -1572,7 +1596,8 @@ Potree.updateVisibility = function(pointclouds, camera, areaSize){
                 
 				//weight = diagonal;
 			}
-
+            
+    
 			priorityQueue.push({pointcloud: element.pointcloud, node: child, parent: node, weight: weight}); //貌似好像二叉堆中子节点和父节点没什么关系,就只是为了方便排序层层遍历
 		}
         //手机上像素点更小,所以远处感觉会更稀疏
@@ -1622,6 +1647,19 @@ Potree.updateVisibility = function(pointclouds, camera, areaSize){
     
     //add:
     Potree.numVisiblePoints = numVisiblePoints
+    Potree.visibleNodes = visibleNodes  
+   
+   
+    //if(Potree.settings.sortNodesDis){ 
+        //let s = performance.now() 
+        for(let pointcloud of pointclouds){
+            if(pointcloud.material.opacity < 1 && Potree.settings.notAdditiveBlending ){//排序。如果能所有点云一起排序更好,这样遮挡更正确
+                pointcloud.visibleNodes.sort((a,b)=>{return b.disSqToCamZ_ - a.disSqToCamZ_}) 
+            }            
+        }            
+        //console.log(performance.now() - s)
+    //}
+    
    
 	return {
 		visibleNodes: visibleNodes,

+ 46 - 10
src/custom/settings.js

@@ -1,7 +1,7 @@
 //xzw add  
 import browser from './utils/browser.js'
 
-
+const labelorder = 5
  
 const config = {//配置参数   不可修改
     displayMode:{ 
@@ -74,7 +74,16 @@ const config = {//配置参数   不可修改
         prefix4: 'https://uat-laser.4dkankan.com', //测试服 线上当前网站接口域名  
         prefix5: 'https://laser.4dkankan.com/backend',//正式服 线上当前网站接口域名
         prefix6: 'https://mix3d.4dkankan.com/backend',  //融合
-        prefix7: 'https://xfhd.4dkankan.com/backend',   //融合      
+        prefix7: 'https://xfhd.4dkankan.com/backend',   //融合 
+        handlePrefix: (prefix)=>{//去掉最后一个/, 否则国外的一些服务器不能访问 //
+            if(prefix.slice(-1) == '/') {
+                prefix = prefix.slice(0,prefix.length-1)
+            }
+            return prefix
+        },
+        templates : {
+            
+        }       
     },
     
     transitionsTime:{
@@ -232,14 +241,15 @@ const config = {//配置参数   不可修改
         sceneObjects:0,//default
         model : 2,   
         light: 15, 
-        measure:4,  
-        magnifier:5, 
+        //measure:4, 
+        //tags:5,        
+        magnifier:6, 
         magnifierContent:16,
-        volume:6,
-        transformationTool:7,
+        volume:7,
+        transformationTool:8,
        
-        map:8,
-        mapObjects:9,//default
+        map:9,
+        mapObjects:10,//default
         
          
         bothMapAndScene: 3,
@@ -254,16 +264,39 @@ const config = {//配置参数   不可修改
     },
     
     renderOrders:{ //会影响到绘制、pick时的顺序。
-        model:10,
+        line: 3,
         reticule:5,
         measureMarker: 6,
         measureLabelSub: 7,
         measureLabel: 8,
         sorptionSign:10,
         model:10,
-        line: 3,
+        
+        
+        path: {
+            label: labelorder, 
+            edge: labelorder,  
+            line: labelorder ,  //会被edge遮住一些,不过无碍
+            marker: labelorder+1, //cover edge
+        }, 
+         
+        tag:{ 
+            label: labelorder,
+            spot: labelorder,  
+            line: labelorder-1,   
+            onMesh:{
+                line: labelorder,
+                spot: labelorder-1,  
+            }
+        },
         
         magnifier:50,
+        
+        /* 
+        renderOrder:
+            理想情况是大家渲染层级都一样,用真实的遮挡情况。(depthTest为false的话会three会自动根据深度调整渲染顺序, 只是随着角度变化常有错) 但有时候为了处理特殊情况,如希望热点的线永在热点之后不穿出,需要增加层级。但热点和热点之间、热点和其他物品是平等的,而降低的层级总被远处的高层级遮挡。
+            因此无论何时尽量减少层级数目。如有必要,可临时将容易被忽视穿帮的线条设置为低层级。
+        */ 
     }, 
     
     siteModel:{
@@ -342,6 +375,7 @@ const config = {//配置参数   不可修改
     maxLRUPoints:40e6,
     depthTexUVyLimit: 0.141, // 在这个范围内是没有深度的,从图片算的0.14003, 设置为稍大于这个数值
     
+    maxSplatCount: 3000000, //整个网页最大支持显示多少个
     
     //neighbourPath:'data/1111/extraNeighbours.js' //本地版手动额外配置,权重最高
 }
@@ -480,8 +514,10 @@ let settings = {//设置   可修改
     panoZoomByPointer: false,//全景图是否定点缩放
     areaAtNotPlane: false,
     showNeighSetGui: browser.urlHasValue('neighGui'),
+    selectShowBox: true,
     
     //fastTran: isTest
+    pathSmooth : true,// window.location.href.includes('192.168.0.59')  //true //smooth曲线, 非折线
 }
  
 

+ 162 - 189
src/custom/start.js

@@ -9,6 +9,29 @@ import './three.shim.js'
 import "./potree.shim.js"
  
 window.THREE = THREE
+
+let baseZ = 0  //所以数据集高度都要减去这个值。在laser场景里该值为初始数据集的高程
+
+var transformPointcloud = (pointcloud, dataset)=>{
+    
+    var locationLonLat = dataset.location.slice(0,3) // [lon,lat,高程海拔] 
+    //当只有一个dataset时,无论如何transform 点云和漫游点都能对应上。 
+    var location = viewer.transform.lonlatToLocal.forward(locationLonLat)  //transform.inverse()
+    //初始化位置 
+     
+    viewer.sidebar && viewer.sidebar.addAlignmentButton(pointcloud) 
+    
+    //dataset.orientation = 0
+    let Alignment = viewer.modules.Alignment
+    Alignment.rotate(pointcloud, null, dataset.orientation)   
+    Alignment.translate(pointcloud, new THREE.Vector3(location[0], location[1],  dataset.location[2]-baseZ)) //要使初始数据集的z为0,所以要减去初始数据集的z
+    
+    pointcloud.updateMatrixWorld()
+     
+    Potree.Log(`点云${pointcloud.dataset_id}(${pointcloud.name})旋转值:${pointcloud.orientationUser}, 位置${math.toPrecision(pointcloud.translateUser.toArray(),3)}, 经纬度 ${dataset.location}, spacing ${pointcloud.material.spacing}`,{font:{color:"#f49",fontSize:13}} )
+     
+}
+
 export function start(dom, mapDom, number ){ //t-Zvd3w0m
     /* {
         let obj = JSON.parse(localStorage.getItem('setting'))
@@ -256,31 +279,11 @@ export function start(dom, mapDom, number ){ //t-Zvd3w0m
             viewer.dispatchEvent('allLoaded')
         }
         
-        var transformPointcloud = (pointcloud, dataset)=>{
-            var locationLonLat = dataset.location.slice(0,3) // [lon,lat,高程海拔]
-             
-           
-            //当只有一个dataset时,无论如何transform 点云和漫游点都能对应上。
-             
-            var location = viewer.transform.lonlatToLocal.forward(locationLonLat)  //transform.inverse()
-            //初始化位置 
-             
-            viewer.sidebar && viewer.sidebar.addAlignmentButton(pointcloud) 
-            
-            //dataset.orientation = 0
-            
-            Alignment.rotate(pointcloud, null, dataset.orientation)   
-            Alignment.translate(pointcloud, new THREE.Vector3(location[0], location[1],  dataset.location[2]-originDataset.location[2])) //要使初始数据集的z为0,所以要减去初始数据集的z
-            
-            pointcloud.updateMatrixWorld()
-            
-            
-            Potree.Log(`点云${pointcloud.dataset_id}(${pointcloud.name})旋转值:${pointcloud.orientationUser}, 位置${math.toPrecision(pointcloud.translateUser.toArray(),3)}, 经纬度 ${dataset.location}, spacing ${pointcloud.material.spacing}`,{font:{color:"#f49",fontSize:13}} )
-             
-        }
+        
         
         if(!Potree.settings.originDatasetId)Potree.settings.originDatasetId = data[0].id
         var originDataset = data.find(e=>e.id == Potree.settings.originDatasetId)  
+        baseZ = originDataset.location[2]
         /* originDataset.location[0] = 113.60608878174709
         originDataset.location[1] = 22.381189423935155
           
@@ -292,65 +295,7 @@ export function start(dom, mapDom, number ){ //t-Zvd3w0m
         {//拿初始数据集作为基准。它的位置要放到000
             var locationLonLat = originDataset.location.slice(0,2) 
             Potree.setLonlat(locationLonLat[0], locationLonLat[1])
-            /* if(window.AMapWith84){//需要转换为高德的,但该函数不准确,转入后再转出,和原来的有偏差.    navvis的我看data中存的globalLocation直接输入到高德地图后的定位和其要展示的定位一致,而我们要转为高德后才一致,猜测是navvis后台转为了高德可用的经纬度。 若不转的话,其他看起来没问题,仅高德地图定位不准确,因其为被加密后的火星坐标系。
-                locationLonLat = AMapWith84.wgs84ToAMap({x:locationLonLat[0], y:locationLonLat[1]})
-                locationLonLat = [locationLonLat.x,locationLonLat.y] 
-            }  
-            
-            
-            proj4.defs("LOCAL", "+proj=tmerc +ellps=WGS84 +lon_0=" + locationLonLat[0].toPrecision(15) + " +lat_0=" + locationLonLat[1].toPrecision(15)); //高德坐标系
-            proj4.defs("LOCAL_MAP", "+proj=tmerc +ellps=WGS84 +lon_0=" + locationLonLat[0].toPrecision(15) + " +lat_0=" + locationLonLat[1].toPrecision(15)); //地图和本地一样
-            proj4.defs("WGS84", "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs");
              
-            let transform1 = proj4("WGS84", "LOCAL"); //这个ok 是展开的平面投影  LOCAL即NAVVIS:TMERC
-            let transform2 = proj4("+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs;");
-            //注:转入后再转出,和原来的有偏差。如果输入是local坐标,数字越大偏差越大,当百万时就明显了。如果是lonlat,很奇怪经度小于50时就乱了。
-            viewer.transform = {
-                lonlatToLocal : transform1,
-                lonlatTo4550 : transform2       // 转大地坐标EPSG:4550  
-            } 
-            
-            if(window.AMapWith84 && Potree.settings.mapCompany != 'google'){//需要转换, 因本地高德用的lonlat和数据里的84不一样. (google地图在国内也用的高德,国外84)
-                let change = (transform)=>{
-                    let forward = transform.forward
-                    let inverse = transform.inverse;
-                    
-                    transform.forward = function(e, not84){
-                         let needTran = e.x == void 0 
-                         if(needTran)var a1 = {x:e[0],y:e[1]}
-                         else var a1 = e
-                         var a = not84 ? a1 : AMapWith84.wgs84ToAMap(a1)
-                         if(needTran){
-                             a = [a.x, a.y];  e[2] != void 0 && (a[2] = e[2])
-                         }else{
-                             e.z != void 0 && (a.z = e.z)
-                         }       
-                         return  forward(a)
-                    }
-                    
-                    transform.inverse = function(e, not84){
-                        let needTran = e.x == void 0
-                        var a = inverse(e)
-                        needTran && (a = {x:a[0],y:a[1]})
-                        a = not84 ? a : AMapWith84.aMapToWgs84(a)  
-                        
-                        if(needTran){
-                            a = [a.x,a.y];  e[2] != void 0 && (a[2] = e[2])
-                        }else{
-                            e.z != void 0 && (a.z = e.z)
-                        } 
-                        return a
-                        
-                    } 
-                     
-                }
-                for(let f in viewer.transform){
-                    change(viewer.transform[f]) 
-                }  
-            }  */
-            
-            
-            
             viewer.mapViewer && viewer.mapViewer.mapLayer.maps[0].updateProjection()
             
             
@@ -647,10 +592,12 @@ export function panoEditStart(dom, number, EditCloudsArgs){
         let datasetData = Potree.datasetData.find(e=>e.datasetId = datasetId)  
        
         panoData.forEach((pano, index)=>{
-            //let cloudPath = `${Potree.scriptPath}/data/panoEdit/uuidcloud/${pano.uuid}/cloud.js` 
-            let cloudPath = `${Potree.settings.urls.prefix1}/${Potree.settings.webSite}/${Potree.settings.number}/data/bundle_${Potree.settings.number}/building/uuidcloud/${pano.uuid}/cloud.js`
-             
-            
+           
+            //let cloudPath = `${Potree.settings.urls.prefix1}/${Potree.settings.webSite}/${Potree.settings.number}/data/bundle_${Potree.settings.number}/building/uuidcloud/${pano.uuid}/cloud.js`
+            //2024.12.9: 加mapping
+            let mapping = (Potree.settings.isLocal && (datasetData ? datasetData.mapping : browser.urlHasValue('mapping',true))) || ''
+                mapping && (mapping += '/')
+            let cloudPath = `${Potree.settings.urls.prefix1}/${mapping}${Potree.settings.webSite}/${Potree.settings.number}/data/bundle_${Potree.settings.number}/building/uuidcloud/${pano.uuid}/cloud.js`
             /*  if(Potree.settings.isLocal && dataset.mapping){
                 var cloudPath = `${Potree.settings.urls.prefix1}/${dataset.mapping}/${dataset.webBin}`  //webBin添加原因:每次裁剪之类的操作会换路径,因为oss文件缓存太严重,更新慢
             }else{
@@ -769,7 +716,6 @@ export function mergeEditStart(dom, mapDom){
             
             Potree.loadPointCloud(cloudPath, sceneName , sceneCode, timeStamp, e => {
                 
-
                 
                 let scene = viewer.scene;
                 let pointcloud = e.pointcloud; 
@@ -778,17 +724,21 @@ export function mergeEditStart(dom, mapDom){
                 
                 pointcloud.datasetData = dataset
                 pointcloud.hasDepthTex = dataset && Potree.settings.useDepthTex && !!dataset.has_depth    
-
+                pointcloud.initialPosition = pointcloud.position.clone() 
+                pointcloud.pos1MatrixInvert = new THREE.Matrix4().setPosition(pointcloud.initialPosition).invert()
+                
                 
                 material.minSize =  config.minSize
                 material.maxSize =  config.maxSize   
                 material.pointSizeType = config.pointSizeType //Potree.PointSizeType[config.pointSizeType]//Potree.PointSizeType.ADAPTIVE;//FIXED
                 pointcloud.changePointSize(config.realPointSize)  //material.size =  config.pointSize;
                 pointcloud.changePointOpacity(1)
+                
+                dataset && (transformPointcloud(pointcloud,  dataset),  pointcloud.hasLonLat = true)
+                
                 material.shape = Potree.PointShape.SQUARE; 
                 color && (pointcloud.color = pointcloud.material.color = color)     
                 pointcloud.timeStamp = timeStamp 
-                //transformPointcloud(pointcloud, originDataset)
                 scene.addPointCloud(pointcloud);
                  
                 
@@ -864,17 +814,11 @@ export function mergeEditStart(dom, mapDom){
         viewer.updateModelBound()
     } 
 
+    const moveModelWhenLoad = false
+
     let moveModel = (e)=>{//根据鼠标移动的位置改变位置
           
-        let camera = viewer.mainViewport.camera
-        var origin = new THREE.Vector3(e.pointer.x, e.pointer.y, -1).unproject(camera),
-        end = new THREE.Vector3(e.pointer.x, e.pointer.y, 1).unproject(camera)
-        var dir = end.sub(origin)
-        let planeZ = 0;
-        let r = (planeZ - origin.z)/dir.z
-        let x = r * dir.x + origin.x
-        let y = r * dir.y + origin.y
-        
+        let {x,y} = Potree.Utils.getPointerPosAtHeight(0,e.pointer)
         //过后改为根据intersect的点来设置底部高度;这样的话,需要发送高度
         
         /*let pos = new THREE.Vector3(x,y,  planeZ  )
@@ -901,22 +845,97 @@ export function mergeEditStart(dom, mapDom){
     
     let modelType,  modelEditing, MergeEditor = viewer.modules.MergeEditor
     Potree.addModel = function(prop, done, onProgress, onError){ //加载模型
-         
-    
+       
         let loadDone = (model)=>{ 
             model.dataset_id = prop.id //唯一标识
-             
-            if(prop.position){
-                model.position.copy(prop.position)
+            
+            {//设置下默认经纬度位置,当点击恢复默认时要恢复到此位置
+                
+                if(!model.isPointcloud){ //有经纬度    3dtiles
+                    model.rotation.copy(prop.baseRotation) //有的需要翻转90度 
+                    let lonlat = prop.raw.wgs84 || prop.raw.rtkLocation  //前者为素材库的osgb的
+                    if(lonlat){ 
+                        var locationLonLat = lonlat.split(',').map(e=>parseFloat(e))
+                        var location = new THREE.Vector3().fromArray(viewer.transform.lonlatToLocal.forward(locationLonLat)) 
+                        
+                        model.hasLonLat = true 
+                       
+                        if( model.fileType == '3dTiles' && prop.is4dkkModel ){ //深时深光mesh
+                            model.position.fromArray(model.runtime.getTileset().tileset.root.boundingVolume.box.slice(0,3))//必须要平移一段才能重合 
+                            model.position.copy(Potree.math.convertVector.ZupToYup(model.position))
+                             
+                            //饶原点旋转  类似Alignment.setMatrix 和点云一样处理 
+                            if(prop.raw.orientation){
+                                model.updateMatrixWorld()//此时和点云没有旋转平移时一样。
+                                let rotMatrix = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1),  parseFloat(prop.raw.orientation)   )  
+                                let pos2Matrix = new THREE.Matrix4().setPosition(location);//最后是平移
+                                var matrix = new THREE.Matrix4().multiplyMatrices(pos2Matrix, rotMatrix);
+                                model.matrix.premultiply(matrix) 
+                                model.matrix.decompose(model.position,model.quaternion,model.scale)
+                            }else{
+                                model.position.add(location)
+                            }
+                        }else{
+                            MergeEditor.moveBoundCenterTo(model, location )   
+                            if(prop.raw.orientation){
+                                model.rotation.y = parseFloat(prop.raw.orientation)  
+                            }  
+                            if(prop.is4dkkModel){
+                                console.warn('遇到is4dkkModel的且有经纬度的mesh,但不是3dtiles! 位置估计不准', model)
+                                //看见的场景说是市场不会带rtk的。所以我们这边标品也没存rtk的坐标信息。和产品聊了,不处理。意思就是,只有激光场景开启了rtk的rtkLocation才有值
+                                /* if(model.panos?.length){//只能通过漫游点经纬度来校准 
+                                    //但这时候panos还没加载。。。。
+                                    let sceneCode = ...从url中解析
+                                    Potree.loadDatasets((data)=>{ //获取datasetId
+                                        let originDataset = data.find(e=>e.sceneCode == sceneCode);//只加载初始数据集  
+                                        Potree.loadPanos(originDataset.datasetId,()=>{
+                                            shouldPos = 获取坐标  data[0].location经纬度
+                                            model.position.add(new THREE.Vector3().subVectors(shouldPos, model.panos[0].position))
+                                        }) 
+                                  }, sceneCode, (e)=>{
+                                            console.log(e)
+                                  } , prop.prefix) 
+                                } */
+                            }
+                        }   
+                    }
+                    
+                }
+                
+                if(model.fileType == 'shp'){
+                    if(model.prjNotSure){
+                        MergeEditor.moveBoundCenterTo(model,new THREE.Vector3()  )//因为不确定坐标类型,而点坐标可能几万米,所以放原点好一些
+                    } 
+                    model.hasLonLat = true  
+                }
+                MergeEditor.setModelBtmHeight(model, 0)// 离地高度为0 (因为不想在地图下方所以高程不管了,都在地面上即可)
+                
+                
+                if(model.hasLonLat){
+                    model.lonLatPos = model.position.clone()
+                    model.lonLatRot = model.rotation.clone() 
+                }            
             }
-            if(prop.rotation){
+
+                 
+            if(prop.position ){
+                if( prop.position.x != 0 || prop.position.y != 0 || prop.position.z != 0  ){//移动过后使用移动后的坐标
+                    model.position.copy(prop.position)
+                }else{//用户没设置位置(首次添加后没按保存)
+                    
+                } 
+            }
+            if(prop.rotation && (prop.rotation.x != 0 || prop.rotation.y != 0 || prop.rotation.z != 0  )){
                 //model.rotation.setFromVector3(prop.rotation) 
                 model.rotation.copy(prop.rotation) 
             }
             if(prop.scale != void 0){
                 model.scale.set(prop.scale,prop.scale,prop.scale)
             }
-              
+            
+            
+            
+            
             if(model.isPointcloud){
                 model.renderOrder = Potree.config.renderOrders.model;  //same as glb
             }
@@ -957,9 +976,9 @@ export function mergeEditStart(dom, mapDom){
                     model.updateMatrixWorld()
                     viewer.updateModelBound() 
                 }  
-                let maintainCenter = ()=>{ 
+                let maintainCenter = (e)=>{ 
                     //MergeEditor.maintainBoundXY(model) 
-                    MergeEditor.maintainBoundCenter(model) 
+                    e.by2d || MergeEditor.maintainBoundCenter(model) 
                     updateBound()
                     model.dispatchEvent('transformChanged')  
                 }
@@ -991,22 +1010,24 @@ export function mergeEditStart(dom, mapDom){
             done(model) // 先发送成功,因为2d界面会随机执行changePosition等初始化,然后这边再将模型移到中心地面上
             
             
-            if(prop.isFirstLoad){
+            if(prop.isFirstLoad){  
                 
-                MergeEditor.moveBoundCenterTo(model, new THREE.Vector3(0,0,0))  
+                if(model.hasLonLat || !moveModelWhenLoad){
+                    setTimeout(()=>{MergeEditor.focusOn(model)} , 1)
+                }else{
+                    MergeEditor.moveBoundCenterTo(model, new THREE.Vector3(0,0,0)) 
+                }
                 MergeEditor.setModelBtmHeight(model, 0) //初始加载设置离地高度为0
                 
+                 
+                
                 if(prop.mode != 'single'){//如果不是模型展示页,模型会随着鼠标位置移动 
                     modelEditing = model;
-                    /* if(model.fileType == '3dTiles'){
-                        setTimeout(()=>{
-                            moveModel({pointer:{x:0,y:0}}) //3dTiles的移动会错乱,先默认放在当前视图中间吧 
-                            confirmPos()
-                        },1)
-                    }else{ */
+                 
+                    if(!model.hasLonLat && moveModelWhenLoad){   
                         viewer.addEventListener('global_mousemove', moveModel); 
                         viewer.addEventListener('global_click', confirmPos, {importance:3});
-                    //} 
+                    } 
                 }
                 model.dispatchEvent("position_changed") 
             }else{
@@ -1017,57 +1038,33 @@ export function mergeEditStart(dom, mapDom){
              
             MergeEditor.modelAdded(model)
             
-            
-            
+             
             
         }
          
-         
-        
-        
-        if(prop.type == 'glb'){ 
-        
-            let callback = (object)=>{
-                //focusOnSelect(object, 1000)  
-                object.isModel = true
-                //object.dataset_id = Date.now() //暂时
-                
+          
+        if(prop.type == 'obj' || prop.type == 'glb'){  
+            let callback = (object)=>{  
+                object.isModel = true 
                 object.traverse(e=>e.material && (e.material.transparent = true))
               
-                /* object.addEventListener('click',(e)=>{
-                    //只是为了能得到hoverElement识别才加这个侦听
-                }) */
-                 
                 loadDone(object)
             }
-            
-              
+               
             let info = { 
                 fileType: prop.type, 
                 id: prop.id,
                 unlit: prop.unlit,
                 url : prop.url,
-                name : prop.title,
-                /* transform : { 
-                    position : prop.position,
-                    rotation : new THREE.Euler().setFromVector3(prop.rotation), 
-                    scale: new THREE.Vector3(prop.scale,prop.scale,prop.scale),        
-                }  */               
+                name : prop.title, 
             }    
               
-            viewer.loadModel(info , callback, onProgress, onError)
-               
-            
-             
+            viewer.loadModel(info , callback, onProgress, onError) 
        
         }else if(prop.type == 'osgb' || prop.type == 'b3dm'){  //3d tiles  
         
-            let callback = (object)=>{
-                  
-                object.isModel = true 
-                //透明度怎么办
-                //object.traverse(e=>e.material && (e.material.transparent = true))
-                
+            let callback = (object)=>{ 
+                object.isModel = true  
                 loadDone(object)
             } 
         
@@ -1075,33 +1072,16 @@ export function mergeEditStart(dom, mapDom){
                 fileType: '3dTiles', 
                 id: prop.id,
                 name : prop.title,
-                maximumScreenSpaceError: prop.maximumScreenSpaceError, 
-                /* tilesUrl: 'https://4dkk.4dage.com/scene_view_data/SS-Ds19qsmuFA/images/3dtiles/tileset.json',
-                transform : { 
-                    rotation : [Math.PI/2,  0,   0],
-                    position : [0,0,0]  
-                }  
-
-                  tilesUrl: 'https://testgis.4dage.com/LVBADUI_qp/tileset.json',
-                transform : { 
-                    rotation : [0,  0,   0],
-                    position : [0,0,0]  
-                }  */
-                
-                url:prop.url,
-                
+                maximumScreenSpaceError: prop.maximumScreenSpaceError,  
+                url:prop.url, 
             },callback,onprogress)
         
         }else if(prop.type == 'shp'){
 
-            let callback = (object)=>{
-                  
+            let callback = (object)=>{ 
                 object.isModel = true  
                 loadDone(object)
-            } 
-
-
-            
+            }  
             viewer.loadModel({  
                 fileType: 'shp', 
                 id: prop.id,
@@ -1109,15 +1089,7 @@ export function mergeEditStart(dom, mapDom){
                 url:prop.url,
                 
             },callback,onprogress)
-  
-          
-            //shpModel.position.set(-330000, 900000,10)//尽量移动到原点。原位置在江门那
-            
-             
-         
-           
-            
- 
+   
         
         }else if(prop.type == '3dgs'){ 
         
@@ -1132,20 +1104,21 @@ export function mergeEditStart(dom, mapDom){
                 name : prop.title, 
                 url:prop.url,
                 
-            },callback,onprogress)
-        
-            
-             
-            
+            },callback,onprogress) 
              
         }else{  
-            
-             //else if(prop.type == 'las' || prop.type == 'ply' || prop.type == 'laz' ) 
+             
             prop.url instanceof Array && (prop.url = prop.url[0]) //deal bug
-            Potree.loadPointCloudScene(prop.url, prop.type, prop.modelId, prop.title, (pointcloud)=>{  
-                pointcloud.matrixAutoUpdate = true
-                pointcloud.initialPosition = pointcloud.position.clone() 
-                pointcloud.pos1MatrixInvert = new THREE.Matrix4().setPosition(pointcloud.initialPosition).invert()
+            Potree.loadPointCloudScene(prop.url, prop.type, prop.modelId, prop.title, (pointcloud)=>{   
+                { 
+                    pointcloud.matrixAutoUpdate = true
+                    if(pointcloud.hasLonLat){
+                        pointcloud.matrix.decompose(pointcloud.position, pointcloud.quaternion, pointcloud.scale) //将数据集的经纬度和旋转应用到rotation和position (注意position和translateUser并不一样)
+                         
+                    }else if(!prop.isFirstLoad){//点云一般加载后position都不是0, 但后台初始化为0所以先归零要不然撤销后容易错
+                        pointcloud.position.set(0,0,0)
+                    }                    
+                }
                 
                 if(Potree.settings.mergeType2 && pointcloud.datasetData){
                     Potree.loadPanos(pointcloud.datasetData.id, (data) => { 

+ 5 - 0
src/custom/three.shim.js

@@ -377,5 +377,10 @@ THREE.Object3D.prototype.realVisible = function(){
 
 
 
+THREE.Curve.prototype.getPointAt = function ( u, optionalTarget ) { 
+    const t = this.getUtoTmapping( u );
+    this.UtoTMapArr && this.UtoTMapArr.push(t) //add
+    return this.getPoint( t, optionalTarget );
 
+} 
 

+ 40 - 1
src/custom/utils/Common.js

@@ -253,7 +253,7 @@ var Common = {
         }
     }, 
       
-    dealURL(url){ 
+    dealURL(url=''){ 
         let urlNew = this.replaceAll(url, "+", "%2B"); //this.replaceAll(url, "\\+", "%2B");// 浏览器似乎不支持访问带+的地址
     
         urlNew = this.replaceAll(urlNew, "/.//", "/") //去除双斜杠(/.//)
@@ -308,6 +308,9 @@ var Common = {
             if(!item){  //如果没有该项, 则加入循环
                 let ifContinue = func() 
                 item = {name, func, delayTime} 
+                /* if(name == 'processPriorityQueue'){
+                    console.log('isWaiting', delayTime)
+                } */
                 this.list.push(item);
                     setTimeout(()=>{ 
                         var a = this.list.indexOf(item);
@@ -329,6 +332,42 @@ var Common = {
         },
     }
     ,
+    
+    
+    
+    waitTool:{//定时器,在等待的这段时间内如果又触发则重新计时
+        list:[],
+        
+        wait(name, func, time){
+            let item = this.list.find(e=>e.name == name) 
+            
+            let timer = setTimeout(()=>{
+                func()
+                let index = this.list.indexOf(item)
+                this.list.splice(index, 1)
+            }, time)
+            if(item){
+                clearTimeout(item.timer) 
+            }else{
+                item = {name}
+                this.list.push(item)
+            }
+            item.timer = timer
+        },
+        
+        cancel(name){
+            let index = this.list.findIndex(e=>e.name == name) 
+            index>-1 && this.list.splice(index, 1)
+        }
+        
+    }
+    
+    
+    
+    
+    
+    
+    ,
     pushToGroupAuto : function(items, groups, recognizeFunction, recognizeGroup){//自动分组。 items是将分到一起的组合。items.length = 1 or 2. 
     
         recognizeFunction = recognizeFunction || function(){}

+ 17 - 3
src/custom/utils/CursorDeal.js

@@ -7,16 +7,30 @@ import Common from './Common.js'
 
 var CursorDeal = {
     priorityEvent : [//在前面的优先级高
+        
+        { pen_delPoint: `url({Potree.resourcePath}/images/polygon_mark/pic_pen_sub.png),auto`},
+        { pen_addPoint: `url({Potree.resourcePath}/images/polygon_mark/pic_pen_add.png),auto`},
+        { pen: `url({Potree.resourcePath}/images/polygon_mark/pic_pen.png),auto`},
+         
+        {'grabbing':'grabbing'},//通用
+        {'hoverGrab':'grab'},//通用
+        {'pointer':'pointer'},//通用
+        
+        
+        
         {'zoomInCloud':'zoom-in'},
         {'hoverPano':'pointer'}, 
         {"notAllowed-default":'not-allowed'},   
         {'connectPano':`url({Potree.resourcePath}/images/connect.png),auto`},
         {'disconnectPano':`url({Potree.resourcePath}/images/connect-dis.png),auto`},
-         
-        {'hoverLine':'pointer'},
-        {'hoverTranHandle':'grab'},
         
         
+        
+        {'hoverLine':'pointer'},
+        {'hoverTranHandle':'grab'},
+         
+         
+         
         {"movePointcloud":'move'}, 
         {"polygon_isIntersectSelf":'not-allowed'},
         {"polygon_AtWrongPlace":'not-allowed'},

+ 2 - 3
src/custom/utils/History.js

@@ -76,9 +76,8 @@ class History extends THREE.EventDispatcher{
     
     writeIn(data ){ 
         this.redoList.length = 0; //一旦录入新的操作,就不允许undo了
-         
-        this.undoList.push(data );
-        //console.log('新增undo', data)    
+        //console.log('writeIn',  data)    
+        this.undoList.push(data );   
     }
     
     

+ 235 - 115
src/custom/viewer/ViewerNew.js

@@ -17,7 +17,7 @@ import {TagTool} from "../objects/tool/TagTool.js";
 import Compass from "../objects/tool/Compass.js";
 import AxisViewer from "../objects/tool/AxisViewer.js";
  
-   
+    
 
 import {ExtendScene} from '../../viewer/ExtendScene.js' 
 import {transitions, easing, lerp} from '../utils/transitions.js' 
@@ -37,7 +37,7 @@ import {Message} from "../../utils/Message.js";
 import {Sidebar} from "../../viewer/sidebarNew.js";
 
 import {AnnotationTool} from "../../utils/AnnotationTool.js";
-import {MeasuringTool} from "../objects/tool/MeasuringTool.js";
+import {MeasuringTool} from "../objects/tool/MeasuringTool.js"; 
 import CursorDeal from '../utils/CursorDeal.js'
 import Common from '../utils/Common.js' 
 import browser from '../utils/browser.js'
@@ -59,6 +59,8 @@ import MergeEditor from "../modules/mergeModel/MergeEditor.js";
 import {RouteGuider}  from '../modules/route/RouteGuider.js'
 import {Clipping}  from '../modules/clipping/Clipping.js'
 
+
+
 import ParticleEditor from '../modules/Particles/ParticleEditor.js'
 import CamAniEditor from '../modules/CameraAnimation/CamAniEditor.js'  
 import PanoEditor  from '../modules/panoEdit/panoEditor.js' 
@@ -95,9 +97,12 @@ import { ClassificationScheme } from "../../materials/ClassificationScheme.js";
 import { VRButton } from '../../../libs/three.js/extra/VRButton.js';
 import DxfLoader from '../../loader/DxfLoader.js'
  
+import {Splat} from '../objects/3dgs/Splat.js'
  
- 
-const manager = new THREE.LoadingManager(); 
+
+
+
+
 let loaders = {}
 
 let mapArea; 
@@ -115,6 +120,8 @@ Potree.isIframeChild = window.parent!=window  //子页面
 window.addEventListener('blur',()=>{
     console.log('blur',window.winIndex)
 }) */
+THREE.Cache.enabled = true //这样不会重复网络请求相同的图(如热点换图)。
+ 
 
 export class Viewer extends ViewerBase{
 	
@@ -125,10 +132,15 @@ export class Viewer extends ViewerBase{
         window.viewer = this
         mapArea = mapArea_                 
         
+        this.setLoaders()
+        
+          
+        
         if(this.renderer.capabilities.isWebGL2){
             Potree.settings.isWebgl2 = true  //是否启用webgl2
         } 
          
+         
         if(Potree.settings.editType == "pano" || Potree.settings.editType == "merge"){
             this.modules = { 
                 Alignment,  
@@ -202,11 +214,11 @@ export class Viewer extends ViewerBase{
                 //this.pageHiddenCollect = []
             } 
             
-            setTimeout(()=>{ 
+            /* setTimeout(()=>{ 
                 console.log('depthSamChangeImg',Potree.timeCollect.depthSamChangeImg.median,  'sortByScore',Potree.timeCollect.sortByScore.median)
                 console.log('fps',Potree.fps, Potree.fps2,Potree.fpsRendered )
                 
-            },25000)
+            },25000) */
         }
          
   
@@ -528,12 +540,13 @@ export class Viewer extends ViewerBase{
                 this.scene.scene.add(this.reticule)
                 
                 
-                if(Potree.settings.editType != "pano" && (Potree.settings.editType != 'merge' /* || Potree.settings.showObjectsOnMap */)){
+                if(Potree.settings.editType != "pano" && (Potree.settings.editType != 'merge' /* || Potree.settings.showObjectsOnMap */) && !args.noMap){
                     this.mapViewer = new MapViewer(mapArea/* $('#mapGaode')[0] */)
                 }
                 
                 this.inputHandler = new InputHandler(this, this.scene.scene);
                 this.inputHandler.containsMouse = true//初始化,使键盘事件在mainViewer有效
+                this.inputHandler.registerInteractiveScene(this.scene.overlayScene);
                 //this.inputHandler.setScene(this.scene);
                 //this.inputHandler.addInputListener(this);//add
                 
@@ -636,14 +649,7 @@ export class Viewer extends ViewerBase{
             
             this.scene.scene.add(this.objs);
              
-            loaders = {
-                objLoader : new OBJLoader( manager ),
-                mtlLoader : new MTLLoader( manager ),
-                glbLoader : new GLTFLoader(undefined, this.renderer, Potree.settings.libsUrl ),
-                plyLoader : new PLYLoader( manager ),
-                dxfLoader : new DxfLoader(),
-                shapeLoader: new Potree.ShapefileLoader()
-            } 
+             
             //add test
             /* const environment = new RoomEnvironment();
             const pmremGenerator = new THREE.PMREMGenerator( this.renderer ); 
@@ -765,8 +771,24 @@ export class Viewer extends ViewerBase{
             else this.lazyRenderViewports()
         })
         
+        if(!Potree.settings.pathSmooth && Potree.settings.editType == 'merge' ){
+            viewer.addEventListener('camera_changed',(e)=>{
+                if(e.viewport == viewer.mainViewport && (e.changeInfo.positionChanged)){ 
+                    Common.intervalTool.isWaiting('updatePathArrows', ()=>{  
+                        Potree.Path.updateArrows()     
+                    },1000)  
+                }
+            }) 
+        } 
         
         this.addEventListener('allLoaded', ()=>{
+            
+            this.splatMesh = new Splat()
+            this.splatMesh.addPointcloud(this.scene.pointclouds[0])
+            
+            
+            
+            
             setTimeout(this.testPointcloudsMaxLevel.bind(this), 1000) //延迟一丢丢,等画面出现
             
             //Potree.settings.renderAllViewports = browser.maybeQilin() && this.renderer.capabilities.maxCubemapSize <= 2048 && //麒麟chromium若获取过webgl2只渲染一个viewport的话其他的会变黑
@@ -798,6 +820,7 @@ export class Viewer extends ViewerBase{
              
              
             this.createHackMesh() 
+             
         },{once:true}) 
         
          
@@ -865,7 +888,7 @@ export class Viewer extends ViewerBase{
         
         if(Potree.settings.editType != 'pano' &&  Potree.settings.editType != 'merge'){
               
-            this.addEventListener('switchFloorplanSelect',(e)=>{//进入平面图设置后 切换选中的数据集
+            this.mapViewer && this.addEventListener('switchFloorplanSelect',(e)=>{//进入平面图设置后 切换选中的数据集
                 this.selectedFloorplan = e.pointcloud;  //绝对显示
                 this.updateFpVisiDatasets()
                 let pointclouds;
@@ -880,7 +903,7 @@ export class Viewer extends ViewerBase{
             })
              
             
-            this.mapViewer.mapLayer.addEventListener('floorplanLoaded',()=>{
+            this.mapViewer?.mapLayer.addEventListener('floorplanLoaded',()=>{
                  this.updateCadVisibles(this.fpVisiDatasets, true)   //加载完成后重新更新下
             })
                 
@@ -1021,8 +1044,13 @@ export class Viewer extends ViewerBase{
             if(mesh.material){
                 let mats = (mesh.material instanceof Array) ? mesh.material : [mesh.material]
                 mats.forEach(mat =>{
-                    if(mat.map){
-                        texArea += mat.map.image.width * mat.map.image.height  
+                    let map = mat.map  
+                    if(map ){
+                        if(!map.image){
+                            console.error('!mat.map.image  ??', mat.map.uuid)//obj可能会
+                            return
+                        }
+                        texArea += map.image.width * map.image.height  
                         //visi && (visiTexArea += a)
                     }
                 })
@@ -1093,6 +1121,7 @@ export class Viewer extends ViewerBase{
                     this.shelterCount.byTex ++ ;
                     //console.log('computeByTex direct', panoId, point, ifShelter)
                 }else{
+                    //soon的无tex不敢写……
                     //console.log('延迟tex',panoId, point )
                     history.waitCompute = {panoId,   forceGet:extraPanoId  }
                     return useLastResult() 
@@ -1150,21 +1179,23 @@ export class Viewer extends ViewerBase{
             let history = shelterHistory[i];
             
             if(history.waitCompute){
-                if(history.waitCompute.panoId != void 0){
-                     if(!history.waitCompute.forceGet && (history.waitCompute.panoId != this.images360.currentPano.id || !this.images360.isAtPano(0.1))){
+                let panoId = history.waitCompute.panoId
+                if(panoId != void 0){
+                     if(!history.waitCompute.forceGet && (panoId != this.images360.currentPano?.id || !this.images360.isAtPano(0.1))){
                          delete history.waitCompute //取消计算
                      }else{
-                         if(this.images360.currentPano.depthTex){ 
+                         let pano = this.images360.getPano(panoId)
+                         if(pano.depthTex){ 
                              if(byTex >= maxTexCount)break
                              
                              byTex ++
-                             let ifShelter = !!viewer.inputHandler.ifBlockedByIntersect({point:history.point, margin:Potree.config.shelterMargin, useDepthTex:true, viewport:this.mainViewport }  ) 
-                             history.panos[this.images360.currentPano.id] = ifShelter 
+                             let ifShelter = !!viewer.inputHandler.ifBlockedByIntersect({point:history.point, pano, margin:Potree.config.shelterMargin, useDepthTex:true, viewport:this.mainViewport }  ) 
+                             history.panos[panoId] = ifShelter 
                              history.ifShelter = ifShelter
                              delete history.waitCompute  
                              //console.log('补1', history.point.toArray())
                          }else{
-                             if(this.images360.currentPano.pointcloud.hasDepthTex){
+                             if(pano.pointcloud.hasDepthTex){
                                  //先等待加载完深度图
                              }else{
                                 waitCloud.push(history)
@@ -1207,13 +1238,14 @@ export class Viewer extends ViewerBase{
                 
                 result.list.forEach(e=>{
                     let history = waitCloud2.find(a=>a.point.equals(e))
-                     
-                    let ifShelter = !!viewer.inputHandler.ifBlockedByIntersect({point:history.point, margin: Potree.config.shelterMargin , pickWindowSize:3, viewport:this.mainViewport}  ) 
+                    let panoId = history.waitCompute.panoId
+                    let cameraPos = (panoId != void 0 && panoId != this.images360.currentPano?.id) && this.images360.getPano(panoId).position  //如果非当前点,需要改相机位置
+                    let ifShelter = !!viewer.inputHandler.ifBlockedByIntersect({point:history.point, cameraPos, margin: Potree.config.shelterMargin , pickWindowSize:3, viewport:this.mainViewport}  ) 
                     
                     if(history.waitCompute.cameraPos){
                         history.notAtPano = {cameraPos: history.waitCompute.cameraPos , ifShelter }
                     }else{
-                        history.panos[this.images360.currentPano.id] = ifShelter 
+                        history.panos[panoId] = ifShelter 
                     }
                     history.ifShelter = ifShelter
                     byCloud++
@@ -1355,7 +1387,7 @@ export class Viewer extends ViewerBase{
     
     
     updateFpVisiDatasets(){
-          
+        if(!this.mapViewer) return
         let Clip = this.modules.Clip
         let SiteModel = this.modules.SiteModel
         let Alignment = this.modules.Alignment
@@ -3309,7 +3341,6 @@ export class Viewer extends ViewerBase{
         
         
         ///let needsResize = viewports.length > 1 || params_.resize   //去掉原因:因为不需要渲染的viewport不在此中所以无法判断几个viewport
-        
         for(let i=0; i<viewports.length; i++){
             let viewport = viewports[i]
             
@@ -3320,7 +3351,10 @@ export class Viewer extends ViewerBase{
                 params.extraEnableLayers = viewport.extraEnableLayers
                 params.cameraLayers = viewport.cameraLayers
             //}
-            
+            params.useModelOnRT = Potree.settings.intersectOnObjs && pRenderer.canUseRTPoint()  
+                                && pRenderer.getIfRtEDL_(params) && !pRenderer.getIfuseEdl(params) //模型是否绘制在rtEDL上且能直接绘制到屏幕(感觉容易错)
+                        
+        
             
             var left,bottom,width,height
             { 
@@ -3377,6 +3411,11 @@ export class Viewer extends ViewerBase{
           
             viewport.beforeRender && viewport.beforeRender()  
             
+            
+            
+            
+            
+            
             if(viewport.render){ 
                 if(!viewport.render($.extend({}, params, {
                     renderer,   clear:params.clear || this.clear.bind(this), resize:null,
@@ -3389,9 +3428,11 @@ export class Viewer extends ViewerBase{
                  
                 this.renderBG(viewport)
                  
-                if(Potree.settings.notAdditiveBlending){
+                 
+   
+                if(Potree.settings.notAdditiveBlending ){
                     params.renderBeforeCloud = true
-                    this.renderOverlay1(params)   //先渲染不透明的model。  但drawedModelOnRT时这里提前多渲染了一遍  
+                    this.renderOverlay1(params)   //先渲染不透明的model。    
                 }   
             }
            
@@ -3419,6 +3460,13 @@ export class Viewer extends ViewerBase{
                this.renderOverlay(params)                 
             }
                
+            if(this.splatMesh){
+                Potree.Utils.setCameraLayers(params.camera, ['model'])
+                this.renderer.render(this.splatMesh, params.camera) 
+                
+            }
+               
+               
             viewport.afterRender && viewport.afterRender() 
 
             this.dispatchEvent({type: "render.end",  viewer: this, viewport  }); 
@@ -3542,13 +3590,12 @@ export class Viewer extends ViewerBase{
         
          //为什么要在点云之后渲染,否则透明失效 、 会被点云覆盖 
         let cameraLayers
-
         if(params.cameraLayers) cameraLayers = params.cameraLayers
         else{
             if(params.viewport.name == "mapViewport" )cameraLayers = ['bothMapAndScene', 'light']
             else {
                 cameraLayers = ['sceneObjects', 'light', 'bothMapAndScene' ];
-                if(!params.drawedModelOnRT){
+                if(!params.useModelOnRT){
                      cameraLayers.push('model')
                 } 
             }
@@ -3559,8 +3606,10 @@ export class Viewer extends ViewerBase{
         if(cameraLayers.length){
             Potree.Utils.setCameraLayers(camera,  cameraLayers, params.extraEnableLayers) //透明贴图层 skybox 、reticule marker 不能遮住测量线
             
+            
             if('renderBeforeCloud' in params){
                 this.scene.scene.traverse((object)=>{
+                    if(params.useModelOnRT && viewer.objs.children.includes(object)  )return {stopContinue:true} //already drawn on rt
                     if(object.material){  
                         let transparent = object.material.opacity<1 || object.material.mapTransparent || !object.material.depthTest || !object.material.depthWrite //不写入深度的往往最后来比较
                         Potree.Utils.updateVisible(object, 'renderOpa',  params.renderBeforeCloud != transparent)  
@@ -3569,11 +3618,12 @@ export class Viewer extends ViewerBase{
                 })//ground的材质中opacity为1,所以被当做不透明了
             }
             
-            viewer.dispatchEvent({type: "render.begin2" , name:'scene', viewport:params.viewport  })
+            viewer.dispatchEvent({type: "render.begin2" , name:'scene', viewport:params.viewport, viewer:this })
             renderer.render(this.scene.scene, camera);  
              
             if('renderBeforeCloud' in params){
                 this.scene.scene.traverse((object)=>{
+                    if(params.useModelOnRT && viewer.objs.children.includes(object)  )return {stopContinue:true} //already drawn on rt
                     if(object.material){  
                         Potree.Utils.updateVisible(object, 'renderOpa',  true) //恢复 
                     } 
@@ -3587,51 +3637,7 @@ export class Viewer extends ViewerBase{
     }
     
     
-    createHackMesh(){//为了防止无depthTex的全景在pick点云时画面中仅有一个材质时会黑屏,所以在镜头前再加一个mesh。具体bug表现见bug记录。 
-        if(this.scene.pointclouds.every(e=>e.hasDepthTex) || viewer.images360.panos.length == 0)return 
-        
-        
-        
-        let mesh = new THREE.Mesh(viewer.images360.panos[0].marker.geometry, new THREE.MeshBasicMaterial({color:"#F00",side:2/* ,depthTest:false */}))
-            Potree.Utils.updateVisible(mesh,'show',false)
-            
-        this.images360.node.add(mesh)
-        
-         
-        let updatePos = ()=>{
-            let dir = this.mainViewport.view.direction
-            let radius = this.images360.cube.scale.length()
-            mesh.position.copy(this.mainViewport.view.position).add(dir.multiplyScalar(radius))//放置skybox之外
-        }
-        
-        
-        this.addEventListener('camera_changed', e => {
-            if(e.viewport.name == 'MainView' && Potree.settings.displayMode == 'showPanos' && !this.images360.currentPano.depthTex
-             && (e.changeInfo?.positionChanged || e.changeInfo?.quaternionChanged)){
-                 
-                updatePos()
-                 
-            } 
-        })
-        
-        let judge = ()=>{
-            if(!this.images360.currentPano?.depthTex && Potree.settings.displayMode == 'showPanos'){
-                Potree.Utils.updateVisible(mesh,'show',true)
-            }else{ 
-                Potree.Utils.updateVisible(mesh,'show',false)
-            }
-            
-        }
-        
-        
-        this.images360.addEventListener( 'flyToPanoDone', judge)
-            
-        this.images360.addEventListener( 'endChangeMode', judge)
-        
-
-        //不知道如果用点云计算非当前视角下的block会不会黑闪,如热点遮挡计算
-
-    }
+    
     
     renderOverlay2(params){//渲染剩余部分
         let renderer = params.renderer || this.renderer
@@ -3698,8 +3704,8 @@ export class Viewer extends ViewerBase{
    
         let s = SiteModel.editing && SiteModel.selected && (SiteModel.selected.buildType == 'room' || SiteModel.selected.buildType == 'floor') //空间模型的房间选中材质是需要depth的,这时候需要绘制两次点云
          
-        Potree.settings.pointEnableRT = !this.screenshoting && (this.scene.measurements.filter(e=>e.visible).length > 0 || s || PanoEditor?.entered || this.scene.tags.children.some(e=>e.visible))
-        
+        Potree.settings.pointEnableRT = /* !this.screenshoting && */ (this.scene.measurements.filter(e=>e.visible).length > 0 || s || PanoEditor?.entered || this.tags.children.some(e=>e.visible))
+                                            //2024.12为什么截图时不遮挡?
          
         if(vrActive){
             this.renderVR();
@@ -4370,7 +4376,7 @@ export class Viewer extends ViewerBase{
                  
                 boundSize.x *= scale //稍微放大一些,不然会靠到屏幕边缘
                 boundSize.y *= scale  
-                let min = 0.0001
+                let min = 1
                 boundSize.x = Math.max(min, boundSize.x)
                 boundSize.y = Math.max(min, boundSize.y)
             }
@@ -4467,7 +4473,7 @@ export class Viewer extends ViewerBase{
                     let facePlane = object.getFacePlane(target) 
                     let normal = facePlane.normal.clone()                
                     let angle = this.mainViewport.view.direction.angleTo(normal)
-                    let minDiff = THREE.Math.degToRad(60) 
+                    let minDiff = THREE.Math.degToRad(30) 
                     //console.log('angle',angle)
                     if(angle>minDiff && angle<Math.PI-minDiff){//当几乎正对时就不执行
                         if(angle<Math.PI/2){ //在背面
@@ -4475,6 +4481,7 @@ export class Viewer extends ViewerBase{
                         }
                         let dir = new THREE.Vector3().subVectors(camera.position, target).normalize() 
                         let newDir = new THREE.Vector3().addVectors(dir,normal)//两个角度的中间
+                        //console.log('newDir',newDir)
                         cameraPos.copy(target.clone().add(newDir))
                     }   
                 }else if(object.points.length == 2){ //线段
@@ -4487,7 +4494,7 @@ export class Viewer extends ViewerBase{
                         } 
                         let dir = new THREE.Vector3().subVectors(camera.position, target).normalize() 
                         let mid = new THREE.Vector3().addVectors(lineDir, dir).normalize() //中间法向量(如果刚好dir和lineDir反向,那得到的为零向量,就不移动了,但一般不会酱紫吧)
-                        let newDir = new THREE.Vector3().addVectors(dir, mid)
+                        let newDir = new THREE.Vector3().addVectors(dir, mid) 
                         cameraPos.copy(target.clone().add(newDir))
                     } 
                 }else{
@@ -4566,7 +4573,7 @@ export class Viewer extends ViewerBase{
                 }      
             }else if(Potree.settings.displayMode == 'showPanos'){//全景 (比较难校准)
                 let target2, dir
-                if( object.measureType.includes('MulDistance')){//因为该线不闭合,可能看向target的方向会没有线,所以换一个target
+                if( object.measureType?.includes('MulDistance')){//因为该线不闭合,可能看向target的方向会没有线,所以换一个target
                     target2 = object.points[Math.round(object.points.length / 2) ]//直接看向中间点
                     dir = new THREE.Vector3().subVectors(target2, position).normalize()  
                 }
@@ -4624,11 +4631,17 @@ export class Viewer extends ViewerBase{
             }
             
            
-            if(Potree.settings.displayMode == 'showPointCloud'){ 
+            if(Potree.settings.displayMode == 'showPointCloud' && !o.requestShowPano){ 
                 if(o.dontChangePos){
                     position.copy(cameraPos)
-                }else{
-                    dis = bestDistance
+                }else{ 
+                    if(o.maxDis){
+                        let disNow = cameraPos.distanceTo(target)
+                        dis = THREE.Math.clamp(disNow, 1, o.maxDis)
+                    }else{
+                        dis = bestDistance
+                    }
+                    
                     let dir = o.direction ? o.direction.clone().negate() :  this.mainViewport.view.direction.negate()// */new THREE.Vector3().subVectors(camera.position, target).normalize() 
                     if(o.dontLookUp && dir.z<0)  dir.z *= -1
                     position.copy(target).add(dir.multiplyScalar(dis))
@@ -4668,7 +4681,7 @@ export class Viewer extends ViewerBase{
                      
                 }  
             
-            }else if(Potree.settings.displayMode == 'showPanos'){
+            }else{
                 let pano = viewer.images360.fitPanoTowardPoint({
                     point : target,    
                     dir :  this.mainViewport.view.direction, //尽量不改相机方向,避免镜头晃动
@@ -4685,6 +4698,8 @@ export class Viewer extends ViewerBase{
                 return result           
             }
         }else if(object.boundingBox && type == 'boundingBox'){//使屏幕刚好看全boundingBox
+            //object.boundingBox.min.clamp(new THREE.Vector3(-1e4, -1e4, -1e4), new THREE.Vector3(1e4, 1e4, 1e4))//防止过大,ces崩溃
+            //object.boundingBox.max.clamp(new THREE.Vector3(-1e4, -1e4, -1e4), new THREE.Vector3(1e4, 1e4, 1e4))//防止过大,ces崩溃
             target = object.boundingBox.getCenter(new THREE.Vector3)
             if(o.dir){ //指定方向
                 cameraPos.copy(target).sub(o.dir)
@@ -5450,7 +5465,51 @@ export class Viewer extends ViewerBase{
         return r && r.score > 1 ? result[0].item : null  
     }
     
-    
+    createHackMesh(){//为了防止无depthTex的全景在pick点云时画面中仅有一个材质时会黑屏,所以在镜头前再加一个mesh。具体bug表现见bug记录。 
+        if(this.scene.pointclouds.every(e=>e.hasDepthTex) || viewer.images360.panos.length == 0)return 
+        
+        
+        
+        let mesh = new THREE.Mesh(viewer.images360.panos[0].marker.geometry, new THREE.MeshBasicMaterial({color:"#F00",side:2/* ,depthTest:false */}))
+            Potree.Utils.updateVisible(mesh,'show',false)
+            
+        this.images360.node.add(mesh)
+        
+         
+        let updatePos = ()=>{
+            let dir = this.mainViewport.view.direction
+            let radius = this.images360.cube.scale.length()
+            mesh.position.copy(this.mainViewport.view.position).add(dir.multiplyScalar(radius))//放置skybox之外
+        }
+        
+        
+        this.addEventListener('camera_changed', e => {
+            if(e.viewport.name == 'MainView' && Potree.settings.displayMode == 'showPanos' && !this.images360.currentPano.depthTex
+             && (e.changeInfo?.positionChanged || e.changeInfo?.quaternionChanged)){
+                 
+                updatePos()
+                 
+            } 
+        })
+        
+        let judge = ()=>{
+            if(!this.images360.currentPano?.depthTex && Potree.settings.displayMode == 'showPanos'){
+                Potree.Utils.updateVisible(mesh,'show',true)
+            }else{ 
+                Potree.Utils.updateVisible(mesh,'show',false)
+            }
+            
+        }
+        
+        
+        this.images360.addEventListener( 'flyToPanoDone', judge)
+            
+        this.images360.addEventListener( 'endChangeMode', judge)
+        
+
+        //不知道如果用点云计算非当前视角下的block会不会黑闪,如热点遮挡计算
+
+    }
     /*  createRoomEv(){ 
         
         const environment = new RoomEnvironment();
@@ -5458,6 +5517,36 @@ export class Viewer extends ViewerBase{
     } 
      */
 
+    setLoaders(){
+        this.fileManager = new THREE.LoadingManager(); //整体的load manager
+        this.fileManager.onLoad = () => {
+            console.log('All resources have been loaded');
+            this.fileManager.loading = false
+            this.dispatchEvent('managerOnLoad')
+            // 在这里可以执行模型渲染、动画等操作
+        };
+
+        // 设置加载进度的回调函数(可选)
+        this.fileManager.onProgress = (item, loaded, total) => {
+            if(loaded < total) this.fileManager.loading = true
+            console.log(`Loading ${item}: ${loaded} of ${total}`);
+        };
+
+        // 设置加载失败的回调函数(可选)
+        this.fileManager.onError = (url) => {
+            console.error(`Failed to load resource: ${url}`);
+        };
+        
+        loaders = {
+            objLoader : new OBJLoader( this.fileManager ),
+            mtlLoader : new MTLLoader( this.fileManager ),
+            glbLoader : new GLTFLoader(undefined, this.renderer, Potree.settings.libsUrl ),
+            plyLoader : new PLYLoader( this.fileManager ),
+            dxfLoader : new DxfLoader(),
+            shapeLoader: new Potree.ShapefileLoader()
+        }
+    }
+
     modelLoaded(object, fileInfo_={}, done){//普通模型加载完以后
         object.isModel = true
         let boundingBox = new THREE.Box3()
@@ -5575,21 +5664,31 @@ export class Viewer extends ViewerBase{
                     //Potree.Utils.makeTexDontResize(child.material.map) 
                     //console.log(child.name, 'roughness',child.material.roughness,'metalness',child.material.metalness)
                        
-
-                     
-                    if(fileInfo_.unlit && (!(child.material instanceof BasicMaterial) /* || object.fileType == 'glb' */)){ //注释掉是因为已经写入到loader文件里了
-                        //let material = new THREE.MeshBasicMaterial({map:child.material.map})
-                        let material = new BasicMaterial({map : child.material.map, opacity:child.material.opacity, color:child.material.color})  //很奇怪glb的图会使原本的MeshBasicMaterial 会偏暗,所以自己重新写
-                        //child.material.dispose()
-                        child.material = material 
-                    } 
-                    if(fileInfo_.useStandandMat && !(child.material instanceof THREE.MeshStandardMaterial)){
-                        child.material = new THREE.MeshStandardMaterial()
-                        child.material.roughness = 0.7
-                        child.material.metalness = 0.5
-                    } 
-                     
-                    //纯色的还是不能用BasicMaterial
+                   
+                    let changeMat = (oldMat)=>{
+                        let mat = oldMat
+                        if(fileInfo_.unlit && (!(oldMat instanceof BasicMaterial) /* || object.fileType == 'glb' */)){ //注释掉是因为已经写入到loader文件里了
+                            //mat = new THREE.MeshBasicMaterial({map:oldMat.map})
+                            mat = new BasicMaterial({map : oldMat.map, opacity: oldMat.opacity, color: oldMat.color})  //很奇怪glb的图会使原本的MeshBasicMaterial 会偏暗,所以自己重新写
+                            //oldMat.dispose() 
+                        } 
+                        if(fileInfo_.useStandandMat && !(oldMat instanceof THREE.MeshStandardMaterial)){
+                            mat = new THREE.MeshStandardMaterial()
+                            mat.roughness = 0.7
+                            mat.metalness = 0.5
+                        }  
+                        //纯色的还是不能用BasicMaterial
+                        return mat
+                    }
+                    if(child.material instanceof Array){//obj
+                        child.material = child.material.map(m=>changeMat(m))
+                    }else{
+                        child.material = changeMat(child.material)
+                    }
+                   
+                   
+                    
+                    
                 } 
             } );
         }
@@ -5637,12 +5736,12 @@ export class Viewer extends ViewerBase{
         console.log('开始加载', fileInfo.name, Common.getNameFromURL(fileInfo.url) )
     
         let boundingBox = new THREE.Box3()
-        /* if(!Potree.settings.boundAddObjs){
-           boundingBox.min.set(-0.5,-0.5,-0.5); boundingBox.max.set(0.5,0.5,0.5) 
-        } */ 
+        
         if(fileInfo.objurl){ 
-            fileInfo.url = fileInfo.objurl,   fileInfo.fileType = 'obj'   //兼容最早的 
-        }
+            /* fileInfo.url = fileInfo.objurl,    */fileInfo.fileType = 'obj'   //兼容最早的 
+        } 
+        
+        
         if(fileInfo.url instanceof Array){
             if(fileInfo.url.length == 1){
                 fileInfo.url =  fileInfo.url[0]
@@ -5677,6 +5776,22 @@ export class Viewer extends ViewerBase{
             }  
         };
     
+    
+    
+        if(fileInfo.fileType == 'obj'){
+            let a = fileInfo.url.split('/') 
+            let tails = a.pop().split('.') 
+            let head = a.join('/') + '/'
+            let name = tails[0],  fileType = tails[1]
+            if(fileType == 'obj'){
+                fileInfo.objurl || (fileInfo.objurl = fileInfo.url)
+                fileInfo.mtlurl || (fileInfo.mtlurl = head + name + '.mtl')
+            }else{
+                fileInfo.fileType = 'glb'
+            }
+        }
+    
+    
         if(fileInfo.fileType == 'obj'){ //暂时不支持数组
             if(fileInfo.mtlurl){ 
                 loaders.mtlLoader.load( fileInfo.mtlurl , (materials)=>{ 
@@ -5778,9 +5893,14 @@ export class Viewer extends ViewerBase{
                 loadDone(object)
             },fileInfo) 
         }else if(fileInfo.fileType == 'shp'){
-            if(viewer.transform){
-                loaders.shapeLoader.transform = viewer.transform.lonlatToLocal;
-            }
+            /* if(viewer.transform){
+                loaders.shapeLoader.transform = (v)=>{ 
+                    let a = viewer.transform.lonlatTo4550.inverse(v)
+                    return viewer.transform.lonlatToLocal.forward(a)
+                }  
+
+                //loaders.shapeLoader.transform = viewer.transform.lonlatToLocal.forward;
+            } */
             const shp = await loaders.shapeLoader.load(fileInfo.url, fileInfo.color);
             const shpModel = shp.node
             

+ 5 - 5
src/custom/viewer/map/MapViewer.js

@@ -475,7 +475,7 @@ export class MapViewer extends ViewerBase{
     }
     
     moveTo(endPosition, boundSize, duration=0, margin, easeName, callback   ){//前两个参数有xy即可
-        let z = Math.max(Potree.config.map.cameraHeight, endPosition.z +  (boundSize?.z ? boundSize.z/2 : 0) ) 
+        let z = Math.max(Potree.config.map.cameraHeight,  (endPosition.z || 0) + (boundSize?.z || 0)/2 + 1   ) 
         endPosition = new THREE.Vector3(endPosition.x, endPosition.y, z)
         this.view.moveOrthoCamera(this.viewports[0],  {endPosition, boundSize, margin, callback},   duration,  easeName)
         
@@ -587,7 +587,7 @@ export class MapViewer extends ViewerBase{
             }
               
             
-            if(desc == 'measure') this.inputHandler.registerInteractiveScene(viewer.measuringTool.scene);//虽然用的是viewer的inputHandler,但借用了this.inputHandler的interactiveScenes
+            if(desc == 'measure') this.inputHandler.registerInteractiveScene(viewer.scene.overlayScene/* viewer.measuringTool.scene */);//虽然用的是viewer的inputHandler,但借用了this.inputHandler的interactiveScenes
             else if(desc == 'split4Screens') {
                 this.inputHandler.registerInteractiveScene(viewer.scene.scene);
             }
@@ -605,7 +605,7 @@ export class MapViewer extends ViewerBase{
             this.viewports[0].height = 1;
             this.viewports[0].left = 0;
             
-            this.renderMeasure || this.inputHandler.unregisterInteractiveScene(viewer.measuringTool.scene);
+            this.renderMeasure || this.inputHandler.unregisterInteractiveScene(viewer.scene.overlayScene/* viewer.measuringTool.scene */);
             this.inputHandler.unregisterInteractiveScene(viewer.scene.scene);
             viewer.viewports = [viewer.mainViewport]
             this.updateScreenSize({forceUpdateSize:true}) //更新相机projectionMatrix
@@ -634,9 +634,9 @@ export class MapViewer extends ViewerBase{
     setDrawMeasure(draw){ 
         this.renderMeasure = !!draw
         if(draw){
-            this.inputHandler.registerInteractiveScene(viewer.measuringTool.scene);
+            this.inputHandler.registerInteractiveScene(viewer.scene.overlayScene/* viewer.measuringTool.scene */);
         }else{
-            this.inputHandler.unregisterInteractiveScene(viewer.measuringTool.scene);
+            this.inputHandler.unregisterInteractiveScene(viewer.scene.overlayScene/* viewer.measuringTool.scene */);
         }
     }
     

+ 20 - 10
src/loader/BinaryLoader.js

@@ -54,7 +54,7 @@ export class BinaryLoader{
 	};
 
 	parse(node, buffer, callback){  
-		let pointAttributes = node.pcoGeometry.pointAttributes;
+		let pointAttributes = node.pcoGeometry.pointAttributes; //定义自cloud.js
 		let numPoints = buffer.byteLength / node.pcoGeometry.pointAttributes.byteSize;
 
 		if (this.version.upTo('1.5')) {
@@ -80,12 +80,16 @@ export class BinaryLoader{
 			for(let property in buffers){
 				let buffer = buffers[property].buffer;
 				let batchAttribute = buffers[property].attribute;
-
+ 
 				if (property === "POSITION_CARTESIAN") {
-					geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(buffer), 3));
+					geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(buffer), 3)); 
+                } else if (property === "centersFloat") {//add
+					geometry.setAttribute("centersFloat", new THREE.BufferAttribute(new Float32Array(buffer), 4 ));
+				} else if (property === "centersInt") {//add
+					geometry.setAttribute("centersInt", new THREE.BufferAttribute(new Int32Array(buffer), 4 ));
 				} else if (property === "rgba") {
 					geometry.setAttribute("rgba", new THREE.BufferAttribute(new Uint8Array(buffer), 4, true));
-				} else if (property === "NORMAL_SPHEREMAPPED") {
+				}  else if (property === "NORMAL_SPHEREMAPPED") {
 					geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(buffer), 3));
 				} else if (property === "NORMAL_OCT16") {
 					geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(buffer), 3));
@@ -98,16 +102,22 @@ export class BinaryLoader{
 				} else if (property === "SPACING") {
 					let bufferAttribute = new THREE.BufferAttribute(new Float32Array(buffer), 1);
 					geometry.setAttribute('spacing', bufferAttribute);
-				} else {
+                  
+				} else if (property === "covs") {//add
+					let bufferAttribute = new THREE.BufferAttribute(new Float32Array(buffer), 6);
+					geometry.setAttribute('covs', bufferAttribute);
+                  
+				} else if(property === 'GS3D'){//改
+                    //geometry.setAttribute("rgba", new THREE.BufferAttribute(new Uint8Array(buffer), 4, true));
 					const bufferAttribute = new THREE.BufferAttribute(new Float32Array(buffer), 1);
-
+ 
 					bufferAttribute.potree = {
 						offset: buffers[property].offset,
 						scale: buffers[property].scale,
 						preciseBuffer: buffers[property].preciseBuffer,
 						range: batchAttribute.range,
 					};
-
+ 
 					geometry.setAttribute(property, bufferAttribute);
 
 					const attribute = pointAttributes.attributes.find(a => a.name === batchAttribute.name);
@@ -116,10 +126,10 @@ export class BinaryLoader{
 
 					if(node.getLevel() === 0){
 						attribute.initialRange = batchAttribute.range;
-					}
+					}  
 
-				}
-			}
+				} 
+			}  
 
 			tightBoundingBox.max.sub(tightBoundingBox.min);
 			tightBoundingBox.min.set(0, 0, 0);

+ 435 - 21
src/loader/ShapefileLoader.js

@@ -6,10 +6,21 @@ import {LineMaterial} from "../../libs/three.js/lines/LineMaterial.js";
 
 import {LineDraw} from "../custom/utils/DrawUtil.js"; 
 
+ 
+/*  proj4.defs("CGCS2000","+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=WGS84 +units=m +no_defs")
+ 
+function transformCGCS2000ToWGS84([lng, lat]) {  
+  // Define the CGCS2000 and WGS84 coordinate systems   
+    const transformed = proj4("CGCS2000", "LOCAL", [lng, lat]);  
+    return  transformed 
+}  
+ */
+
+
 export class ShapefileLoader{
 
 	constructor(){
-		this.transform = null;
+		//this.transform = null;
 	}
 
 	async load(path, color){
@@ -24,13 +35,23 @@ export class ShapefileLoader{
  
 
 		const features = await this.loadShapefileFeatures(path);
-		const node = new THREE.Object3D();
-		
+        if(!features){
+            console.error('no features', path)
+            return  
+        }
+        const node = new THREE.Object3D();
+        
+         
+        let transform = await this.loadProj(path, node); 
+        if(!transform){ 
+            transform = this.analyseTransform(features, path, node ) //自己根据数字来猜测,经纬度一般能准,但数字很大的话就不对了。用户自己移动吧
+        } 
+         
         
         let jump = 0 
 		for(const feature of features){//5是碎的
             //if(feature.geometry.type!= 'MultiLineString' || ++jump != 5) continue
-			const fnode = this.featureToSceneNode(feature, matLine);
+			const fnode = this.featureToSceneNode(feature, matLine, transform);
 			fnode && node.add(fnode);
 		} 
 
@@ -47,16 +68,23 @@ export class ShapefileLoader{
 		return result;
 	}
 
-	featureToSceneNode(feature, matLine){
+
+
+
+
+        
+
+
+	featureToSceneNode(feature, matLine, transform){
         //console.log(feature)
         
 		let geometry = feature.geometry;
 		
 		let color = new THREE.Color(1, 1, 1);
 
-		let transform = this.transform;
-		if(transform === null){
-			transform = {forward: (v) => v};
+	 
+		if(!transform){
+			transform = (v)=> v //{forward: (v) => v};
 		}
 		
 		if(geometry.type === "Point"){
@@ -65,7 +93,7 @@ export class ShapefileLoader{
 			let s = new THREE.Mesh(sg, sm);
 			
 			let [long, lat] = geometry.coordinates;
-			let pos = transform.forward([long, lat]);
+			let pos = transform/* .forward */([long, lat]);
 			
 			s.position.set(...pos, 20);
 			
@@ -78,7 +106,7 @@ export class ShapefileLoader{
 			let min = new THREE.Vector3(Infinity, Infinity, Infinity);
 			for(let i = 0; i < geometry.coordinates.length; i++){
 				let [long, lat] = geometry.coordinates[i];
-				let pos = transform.forward([long, lat]);
+				let pos = transform/* .forward */([long, lat]);
 				
 				min.x = Math.min(min.x, pos[0]);
 				min.y = Math.min(min.y, pos[1]);
@@ -114,7 +142,7 @@ export class ShapefileLoader{
                 let coordinateSlice = []
                 points.forEach(point=>{
                     let [long, lat] = point;
-                    let pos = transform.forward([long, lat]);
+                    let pos = transform/* .forward */([long, lat]);
                     min.x = Math.min(min.x, pos[0]);
                     min.y = Math.min(min.y, pos[1]);
                     min.z = Math.min(min.z, 20);
@@ -143,7 +171,7 @@ export class ShapefileLoader{
 				let min = new THREE.Vector3(Infinity, Infinity, Infinity);
 				for(let i = 0; i < pc.length; i++){
 					let [long, lat] = pc[i];
-					let pos = transform.forward([long, lat]);
+					let pos = transform/* .forward */([long, lat]);
 					
 					min.x = Math.min(min.x, pos[0]);
 					min.y = Math.min(min.y, pos[1]);
@@ -182,7 +210,7 @@ export class ShapefileLoader{
         function getLine(coordinates){
             
         }
-	}
+	} 
 
 	async loadShapefileFeatures(file){
 		let features = [];
@@ -195,26 +223,412 @@ export class ShapefileLoader{
                 if (result.done) {
                     break;
                 }
-
+                //value中的properties来自dbf,但该属性没有被用到,是否可以不加载
                 if (result.value && result.value.type === 'Feature' && result.value.geometry !== undefined) {
                     features.push(result.value);
                 }
-            }
+            } 
             return features;
         }else if(file.split('.').pop() == 'json'){
             return new Promise((resolve,reject)=>{
-                Potree.loadFile(file, {/* returnText:true */} , (data)=>{
-                    
-                     
+                Potree.loadFile(file, { } , (data)=>{ 
                     console.log(data);
-                    resolve(data.features)
-                    
-                 });
+                    resolve(data.features) 
+                });
             }) 
             
         }
 		
 	}
+    
+    
+    
+    
+    analyseTransform(features, path, node){//xzw只能自己暂时判断下 ,最好能根据.prj判断
+         
+        if(viewer.transform){
+            let count = 0, maxCount = 100
+            let notLonLat, transform
+            let prjJson = node.prjJson
+            
+            
+            if(prjJson && prjJson.unit ){
+                if(prjJson.unit.name.toLowerCase() == 'meter'   ){//'degree'
+                    notLonLat = true  
+                }
+            }else{
+                let judge = (coord)=>{
+                    if(!(coord[0] >= -180 && coord[0] <= 180 && coord[1] >= -90 && coord[1] <=90 )){//如果超出经纬度范围,那就不是经纬度
+                        notLonLat = true;   node.prjNotSure = true //不确定,不设定默认位置
+                        
+                    }
+                    count++
+                }//数字很小的话一般都是经纬度吧,就当作确定了
+                
+                for(let feature of features){ 
+                    if(count > maxCount)break
+                    for(let arr of feature.geometry.coordinates){
+                        
+                        if(arr[0] instanceof Array){
+                            for(let coord of arr){
+                                judge(coord)
+                                if(count > maxCount || notLonLat)break
+                            } 
+                        }else{
+                            judge(arr)
+                            if(count > maxCount || notLonLat)break
+                        }
+                        
+                        
+                    } 
+                } 
+                  
+            }
+            if(notLonLat){
+                /* transform = (v)=>{ 
+                    let a = viewer.transform.lonlatTo4550.inverse(v)   //这种类型和不使用transform很接近
+                    return viewer.transform.lonlatToLocal.forward(a)   
+                } */   
+                console.log('该shape不使用transform', path.split('/').pop())
+            }else{
+                transform = viewer.transform.lonlatToLocal.forward
+                console.log('根据坐标数值猜测,该shape使用经纬度', path.split('/').pop())
+            }
+            return transform
+            //loaders.shapeLoader.transform = viewer.transform.lonlatToLocal.forward;
+        }else{
+            node.prjNotSure = true 
+            console.log('该shape没用任何transform 因为场景没有设置经纬度', path.split('/').pop() )
+        }
+    }
 
+    
+    
+    
+    
+    async loadProj(path, model){
+        let a = path.split('/') 
+        let name = a.pop().split('.')[0]
+        path = a.join('/') + '/' + name + '.prj';
+        
+          
+         
+        return new Promise((resolve,reject)=>{ 
+            Potree.loadFile(path,{returnText:true},(e)=>{
+                
+                name += '_proj'
+                let projDef = parsePrjToProj4(e, model) 
+                proj4.defs(name, projDef)
+                console.log('loadedProj:', name, '解析得def:  ',  projDef, '原:', e, 'json:', model.prjJson)
+                
+                try{
+                    let proj = proj4(name, "LOCAL")
+                    
+                    let transform = ([lng, lat])=>{ 
+                        return proj.forward([lng, lat]);  
+                    }
+                    resolve(transform)
+                }catch(e){
+                    console.warn(e,  '建立proj4 transform 失败', name ) 
+                    resolve() 
+                } 
+                
+            },()=>{
+                resolve()  
+            })
+             
+        })
+        
+        return  transform 
+        
+    }  
 };
 
+
+
+
+function parsePrjToProj4(prjString, model) {//将prj的内容变为proj4的transform
+    
+    let def = ''
+    let json = model.prjJson = prjToJson(prjString)
+     
+    let projectionType
+    let datum = '';
+    let ellipsoid = '';
+    let centralLon = '0', centralLat = '0';
+    let scaleFactor = '1';
+    let falseEasting = '0', falseNorthing = '0';
+    
+   
+    
+    
+    
+    if(json.projection == 'Transverse_Mercator'){
+        projectionType = 'tmerc'
+    }else if(!json.projection){
+        projectionType = 'longlat'
+    }else{
+        projectionType = projection
+        console.log('unknown projection:', json.projection)
+    }
+     
+      
+     
+    if(json.parameters){
+        falseEasting = json.parameters.false_easting
+        falseNorthing = json.parameters.false_northing
+        centralLon = json.parameters.central_meridian
+        centralLat = json.parameters.latitude_of_origin
+        scaleFactor = json.parameters.scale_factor 
+    }
+    
+    if(json.geogcs?.spheroid){
+        let spheroid = json.geogcs.spheroid.name.toLowerCase().replace('_', '')    
+        if(spheroid.includes('wgs84') || spheroid.includes('wgs1984') )ellipsoid = 'WGS84'
+    }
+     
+     
+    def += `+proj=${projectionType} `
+    def += `+lat_0=${centralLat} `;
+    def += `+lon_0=${centralLon} `;
+    def += `+k=${scaleFactor} `;
+    def += `+x_0=${falseEasting} `;
+    def += `+y_0=${falseNorthing} `;
+    ellipsoid && (def += `+ellps=${ellipsoid} `);
+    def += '+units=m +no_defs';
+    return def
+    
+}
+
+
+
+
+
+
+function prjToJson(prjString) {//将prj的内容变为json
+    const result = {};
+
+    // 提取 PROJCS 和 GEOGCS 信息
+    const projcsMatch = prjString.match(/PROJCS\["([^"]+)",(.*)\]/);
+    if (projcsMatch) {
+        result.projcsName = projcsMatch[1];
+        const geoPart = projcsMatch[2];
+        
+        // 解析 GEOGCS 部分
+        const geoMatch = geoPart.match(/GEOGCS\["([^"]+)",DATUM\["([^"]+)",SPHEROID\["([^"]+)",([0-9.]+),([0-9.]+)\]\],PRIMEM\["([^"]+)",([0-9.]+)\],UNIT\["([^"]+)",([0-9.]+)\]/);
+        if (geoMatch) {
+            result.geogcs = {
+                name: geoMatch[1],
+                datum: geoMatch[2],
+                spheroid: {
+                    name: geoMatch[3],
+                    semiMajorAxis: parseFloat(geoMatch[4]),
+                    inverseFlattening: parseFloat(geoMatch[5])
+                },
+                primeMeridian: {
+                    name: geoMatch[6],
+                    value: parseFloat(geoMatch[7])
+                },
+                unit: {
+                    name: geoMatch[8],
+                    conversionFactor: parseFloat(geoMatch[9])
+                }
+            };
+        }
+
+        // 解析 PROJECTION 部分
+        const projMatch = geoPart.match(/PROJECTION\["([^"]+)"\]/);
+        if (projMatch) {
+            result.projection = projMatch[1];
+        }
+
+        // 解析 PARAMETER 部分
+        const parameters = {};
+        const paramMatches = geoPart.matchAll(/PARAMETER\["([^"]+)",([-\d.]+)\]/g);
+        for (const match of paramMatches) {
+            parameters[match[1]] = parseFloat(match[2]);
+        }
+        result.parameters = parameters;
+
+        // 解析 UNIT 部分
+        const unitMatch = geoPart.match(/UNIT\["([^"]+)",([-\d.]+)\]/);
+        if (unitMatch) {
+            result.unit = {
+                name: unitMatch[1],
+                conversionFactor: parseFloat(unitMatch[2])
+            };
+        }
+    } else {
+        // 如果没有 PROJCS,则尝试解析 GEOGCS
+        const geoMatch = prjString.match(/GEOGCS\["([^"]+)",DATUM\["([^"]+)",SPHEROID\["([^"]+)",([0-9.]+),([0-9.]+)\]\],PRIMEM\["([^"]+)",([0-9.]+)\],UNIT\["([^"]+)",([0-9.]+)\]/);
+        if (geoMatch) {
+            result.geogcs = {
+                name: geoMatch[1],
+                datum: geoMatch[2],
+                spheroid: {
+                    name: geoMatch[3],
+                    semiMajorAxis: parseFloat(geoMatch[4]),
+                    inverseFlattening: parseFloat(geoMatch[5])
+                },
+                primeMeridian: {
+                    name: geoMatch[6],
+                    value: parseFloat(geoMatch[7])
+                },
+                unit: {
+                    name: geoMatch[8],
+                    conversionFactor: parseFloat(geoMatch[9])
+                }
+            };
+        }
+    }
+
+    return result;
+}
+
+
+
+
+
+/* 
+  
+function parsePrjToProj4(prjString) {
+    // 初始化 Proj4 字符串的部分
+    let proj4Def = ''
+
+    // 提取关键信息
+    const prjParts = prjString.match(/(\w+)\[(.*?)\]/g);
+     
+    let projectionType = '';
+    let datum = '';
+    let ellipsoid = '';
+    let centralMeridian = '';
+    let scaleFactor = '1';
+    let falseEasting = '0';
+    let falseNorthing = '0';
+    let latitudeOfOrigin = '0';
+
+    prjParts.forEach(part => {
+        if (part.includes('PROJECTION')) {
+            projectionType = part.match(/"([^"]+)"/)[1].toLowerCase().replace('_', ''); 
+            //xzw:
+            if(projectionType == 'transversemercator') projectionType = 'tmerc'
+             
+        }else if (part.includes('DATUM')) {
+            datum = part.match(/"([^"]+)"/)[1];
+        } else if (part.includes('SPHEROID')) {
+            ellipsoid = part.match(/"([^"]+)"/)[1].toLowerCase();
+        } else if (part.includes('PARAMETER')) {
+            let paramName = part.match(/"([^"]+)"/)[1].toLowerCase().replace(/ /g, '_');
+            let paramValue = part.match(/(-?\d+\.?\d*)/)[1];
+            switch (paramName) {
+                case 'central_meridian':
+                    centralMeridian = paramValue;
+                    break;
+                case 'scale_factor':
+                    scaleFactor = paramValue;
+                    break;
+                case 'false_easting':
+                    falseEasting = paramValue;
+                    break;
+                case 'false_northing':
+                    falseNorthing = paramValue;
+                    break;
+                case 'latitude_of_origin':
+                    latitudeOfOrigin = paramValue;
+                    break;
+            }
+        }
+    });
+
+    // 构建完整的 Proj4 定义字符串
+    if(!projectionType && prjString.includes('GCS_WGS_1984')) projectionType = 'longlat'
+    
+    
+    projectionType && (proj4Def += `+proj=${projectionType} `) 
+    proj4Def += `+lat_0=${latitudeOfOrigin} `;
+    proj4Def += `+lon_0=${centralMeridian} `;
+    proj4Def += `+k=${scaleFactor} `;
+    proj4Def += `+x_0=${falseEasting} `;
+    proj4Def += `+y_0=${falseNorthing} `;
+    ellipsoid && (proj4Def += `+ellps=${ellipsoid} `);
+    proj4Def += '+units=m +no_defs';
+
+    return proj4Def;
+}
+ */
+
+
+
+
+ /* 
+function parsePrjToProj4(prjString) {
+    // 初始化 Proj4 参数对象
+    let proj4Params = {};
+    
+    // 提取 PROJCS 和 GEOGCS 部分
+    const projcsMatch = prjString.match(/PROJCS\["([^"]+)",(.*)\]/);
+    if (projcsMatch){ 
+    
+    const projcsName = projcsMatch[1];
+    const geoPart = projcsMatch[2];
+
+    // 解析 GEOGCS 部分
+    const datumMatch = geoPart.match(/DATUM\["([^"]+)",SPHEROID\["([^"]+)",([0-9.]+),([0-9.]+)\]\]/);
+    if (datumMatch) {
+        proj4Params.datum = datumMatch[1].toLowerCase();
+        proj4Params.ellps = datumMatch[2].toLowerCase();
+        proj4Params.a = parseFloat(datumMatch[3]); // Semi-major axis
+        proj4Params.rf = parseFloat(datumMatch[4]); // Inverse flattening
+    }
+
+    // 解析 PROJECTION 部分
+    const projMatch = geoPart.match(/PROJECTION\["([^"]+)"\]/);
+    if (projMatch) {
+        proj4Params.proj = projMatch[1].toLowerCase().replace(/_/g, '');
+    }
+
+    // 解析 PARAMETER 部分
+    const paramMatches = geoPart.matchAll(/PARAMETER\["([^"]+)",([-\d.]+)\]/g);
+    for (const match of paramMatches) {
+        const paramName = match[1].toLowerCase().replace(/ /g, '_');
+        const paramValue = parseFloat(match[2]);
+        switch (paramName) {
+            case 'false_easting':
+                proj4Params.x_0 = paramValue;
+                break;
+            case 'false_northing':
+                proj4Params.y_0 = paramValue;
+                break;
+            case 'central_meridian':
+                proj4Params.lon_0 = paramValue;
+                break;
+            case 'scale_factor':
+                proj4Params.k = paramValue;
+                break;
+            case 'latitude_of_origin':
+                proj4Params.lat_0 = paramValue;
+                break;
+        }
+    }
+
+    // 设置单位
+    proj4Params.units = "m"; // 默认米
+    proj4Params.no_defs = true;
+
+    // 构建 Proj4 字符串
+    let proj4Def = '+proj=' + proj4Params.proj;
+    proj4Def += ' +lat_0=' + proj4Params.lat_0;
+    proj4Def += ' +lon_0=' + proj4Params.lon_0;
+    proj4Def += ' +k=' + proj4Params.k;
+    proj4Def += ' +x_0=' + proj4Params.x_0;
+    proj4Def += ' +y_0=' + proj4Params.y_0;
+    proj4Def += ' +ellps=' + proj4Params.ellps;
+    proj4Def += ' +units=' + proj4Params.units;
+    proj4Def += ' +no_defs';
+
+    return proj4Def;
+}
+ */
+  
+//shp必须,dbf目前没用,prj最好有 
+ 

+ 83 - 56
src/materials/shaders/depthBasic.fs

@@ -1,35 +1,47 @@
-varying vec2 vUv;
+
 uniform float opacity;
-uniform float mapScale;
+//uniform float mapScale;
 uniform vec3 baseColor;
 
+
+
+
+
 #if defined use_map
+    varying vec2 vUv;
     uniform sampler2D map; 
+    uniform vec3 mapColor;
     
-    vec4 getMapColor(vec4 color){ 
-        if(mapScale == 1.0){
-            color = texture2D(map, vUv) * color; 
-            return color;
-        }else{ 
-            vec2 uv = (vUv - 0.5) / mapScale + 0.5; 
+    vec4 applyMapColor(vec4 color){ 
+        vec2 uv = vUv;
+        /*if(mapScale != 1.0){ 
+            uv = (vUv - 0.5) / mapScale + 0.5; 
             if(uv.x > 1.0 || uv.y > 1.0 || uv.x < 0.0 || uv.y < 0.0){
                 //color = vec4(0.0,0.0,0.0,0.0);
                 discard;
-            }else{
-                color = texture2D(map, uv) * color; 
-            }
-            return color; 
-        }
+            } 
+        }*/
+        vec4 colorFromMap = texture2D(map, uv);
+         
+        #if defined mapOverlay
+            //贴图叠加在基础色上,而非相乘
+            colorFromMap.rgb *= mapColor;
+            color = color * (1.0-colorFromMap.a) + colorFromMap;
+        #else
+            color *= colorFromMap;
+             
+        #endif
+        
+        return color;
     }
 #endif
  
 
-  #if defined(GL_EXT_frag_depth) && defined(useDepth)
+#if defined(GL_EXT_frag_depth) && defined(useDepth)
     //似乎通过gl.getExtension('EXT_frag_depth')得到的GL_EXT_frag_depth
      
     uniform sampler2D depthTexture;
-    uniform float nearPlane;
-    uniform float farPlane; 
+    
     uniform vec2 resolution;
     uniform vec2 viewportOffset; //  viewportOffset 范围从0-整个画布的像素
     uniform vec3 backColor;
@@ -41,63 +53,76 @@ uniform vec3 baseColor;
     uniform float maxOcclusionFactor;
     //uniform bool uUseOrthographicCamera;                                    
 
-    float convertToLinear(float zValue)
-    {
+    
+#endif 
+
+#if defined(GL_EXT_frag_depth) && defined(useDepth) || defined(FadeFar) 
+    uniform float nearPlane;
+    uniform float farPlane; 
+    float convertToLinear(float zValue){
         //if(uUseOrthographicCamera){
         //   return zValue*(farPlane-nearPlane)+nearPlane;
         //}else{ 
             float z = zValue * 2.0 - 1.0;
             return (2.0 * nearPlane * farPlane) / (farPlane + nearPlane - z * (farPlane - nearPlane));
         //}      
-     }
+    } 
 #endif
-  
- 
+
+#if defined(FadeFar)
+    uniform float fadeFar;
+#endif
+//#include <clipping_planes_pars_fragment>
   
 void main() {
-  
+    //#include <clipping_planes_fragment>
     
     vec4 color = vec4(baseColor, opacity);
     
     
     
-    #if defined(GL_EXT_frag_depth) && defined(useDepth)    
-        // mixFactor and clipFactor define the color mixing proportion between the states of
-        // full visibility and occluded visibility
-        // and
-        // full visibility and total invisibility
+    #if defined(GL_EXT_frag_depth) && defined(useDepth) || defined(FadeFar)  
+       
+        float fragDepth = convertToLinear(gl_FragCoord.z);
+        
+        #if defined(FadeFar) 
+            float fadeOutFar = fadeFar * 1.3; //完全消失距离
+            if(fragDepth > fadeOutFar){
+                discard;
+            }else if(fragDepth > fadeFar){
+                color.a *= (fadeOutFar - fragDepth) / (fadeOutFar - fadeFar);
+                 
+            }  
+        #endif
+        
         
         float mixFactor = 0.0;
         float clipFactor = 0.0;
         
+        #if defined(GL_EXT_frag_depth) && defined(useDepth)
         
-        // The linear depth value of the current fragment
-        float fragDepth = convertToLinear(gl_FragCoord.z);
-
-        // The coordinates of the current fragment in the depth texture
-        vec2 depthTxtCoords = vec2(gl_FragCoord.x-viewportOffset.x,  gl_FragCoord.y - viewportOffset.y) / resolution;
-        //gl_FragCoord大小为 viewport client大小 
-        
-        // The linear depth value of the pixel occupied by this fragment in the depth buffer
-        float textureDepth = convertToLinear(texture2D(depthTexture, depthTxtCoords).r);
+            vec2 depthTxtCoords = vec2(gl_FragCoord.x-viewportOffset.x,  gl_FragCoord.y - viewportOffset.y) / resolution;
+            //gl_FragCoord大小为 viewport client大小 
+            
+            float textureDepth = convertToLinear(texture2D(depthTexture, depthTxtCoords).r);
 
-        // The difference between the two depths
-        float delta =  fragDepth - textureDepth;
+           
+            float delta =  fragDepth - textureDepth;
 
-        if (delta > 0.0)//差距
-        {
-            // occlusionDistance and clipDistance define the width of the respective zones and
-            // mixFactor and clipFactor express the interpolation between the two colors depending on the position
-            // of the current fragment withing those zones.
-            
-            
-            mixFactor = clamp((delta - startOcclusDis) / (occlusionDistance - startOcclusDis), 0.0, maxOcclusionFactor);
-            clipFactor = clamp((delta - startClipDis) / (clipDistance - startClipDis), 0.0, maxClipFactor);
-            
-            
-            
-        }
-        
+            if (delta > 0.0)//差距
+            {
+                // occlusionDistance and clipDistance define the width of the respective zones and
+                // mixFactor and clipFactor express the interpolation between the two colors depending on the position
+                // of the current fragment withing those zones.
+                
+                mixFactor = clamp((delta - startOcclusDis) / (occlusionDistance - startOcclusDis), 0.0, maxOcclusionFactor);
+                clipFactor = clamp((delta - startClipDis) / (clipDistance - startClipDis), 0.0, maxClipFactor);
+                
+                
+            } 
+         #endif
+         
+         
         // If the fragment is totally transparent, don't bother drawing it
         if (clipFactor == 1.0)
         {
@@ -105,16 +130,18 @@ void main() {
         }else{
             
             #if defined use_map
-                color = getMapColor(color); 
+                color = applyMapColor(color);  
             #endif
-            
              
-            color = vec4(mix(color.rgb, backColor, mixFactor), color.a * (1.0 - clipFactor));
+            #if defined(GL_EXT_frag_depth) && defined(useDepth)
+                color = vec4(mix(color.rgb, backColor, mixFactor), color.a * (1.0 - clipFactor));
+            #endif    
         }
          
+         
     #else
         #if defined use_map
-            color = getMapColor(color); 
+            color = applyMapColor(color); 
         #endif 
     #endif
   

+ 24 - 4
src/materials/shaders/depthBasic.vs

@@ -1,10 +1,30 @@
 
  
+#if defined use_map 
+    varying vec2 vUv;
+    
+    #ifdef UV_Transform  
+        uniform mat3 uvTransform;
+    #endif
+#endif 
 
-varying vec2 vUv;
 void main() {
-    
-  vUv = uv;
-  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+    #if defined use_map 
+        #ifdef UV_Transform           
+            vUv = ( uvTransform * vec3( uv, 1 ) ).xy;               //include <uv_vertex> 
+        #else
+            vUv = uv;
+        #endif
+    #endif
+  
+  
+  vec4 mvPosition = vec4(position, 1.0);
+  
+  //add instanceMesh
+  #ifdef USE_INSTANCING 
+    mvPosition = instanceMatrix * mvPosition;
+  #endif
+  
+  gl_Position = projectionMatrix * modelViewMatrix * mvPosition;
 } 
  

+ 62 - 42
src/navigation/InputHandlerNew.js

@@ -62,11 +62,13 @@ export class InputHandler extends THREE.EventDispatcher {
         this.hoverViewport = viewer.viewports[0]
         
         
-        
+        document.addEventListener('mouseup', function (event) {
+    event.preventDefault();
+}, { passive: false });
 		this.domElement.addEventListener('contextmenu', (event) => { event.preventDefault(); }, false);
 		this.domElement.addEventListener('click', this.onMouseClick.bind(this), false);
-		this.domElement.addEventListener('mousedown', this.onMouseDown.bind(this), false);
-		window.addEventListener('mouseup', this.onMouseUp.bind(this), false);
+		this.domElement.addEventListener('mousedown', this.onMouseDown.bind(this), {passive:false, useCapture:false});
+		window.addEventListener('mouseup', this.onMouseUp.bind(this),  {passive:false, useCapture:false});
         if(Potree.isIframeChild){//子页面的话在父页面也要加侦听(应该不会有多层吧?否则要一直加到最外层)
             //window.parent.addEventListener('mouseup', this.onMouseUp.bind(this), false); //可能跨域
             //window.parent.postMessage('listenMouseup', '*');
@@ -77,7 +79,7 @@ export class InputHandler extends THREE.EventDispatcher {
         }, false);
         
         
-		this.domElement.addEventListener('mousemove', this.onMouseMove.bind(this), false);
+		this.domElement.addEventListener('mousemove', this.onMouseMove.bind(this), {passive:false, useCapture:false}); 
         //add
        /*  this.domElement.addEventListener("pointerout", this.onMouseUp.bind(this)),
         this.domElement.addEventListener("pointercancel", this.onMouseUp.bind(this)),
@@ -580,22 +582,18 @@ export class InputHandler extends THREE.EventDispatcher {
         
 		let consumed = false;
 		let consume = () => { return consumed = true; };
-		//if (this.hoveredElements.length === 0) {
-			/* for (let inputListener of this.getSortedListeners()) {
-				inputListener */this.viewer.dispatchEvent($.extend(  
-					this.getEventDesc(e,isTouch),
-                    {
-                        type: 'global_mouseup',
-                        pressDistance,
-                        consume,
-                    }
-                ));
+        this.viewer.dispatchEvent($.extend(  
+            this.getEventDesc(e,isTouch),
+            {
+                type: 'global_mouseup',
+                pressDistance,
+                consume,
+            }
+        ));
 
-				/* if(consumed){//??
-					break;
-				} */
-			//}
-		//}
+				 
+        
+        
         if (this.hoveredElements.length > 0) {        
 			let hovered = this.hoveredElements
 				.map(e => e.object)
@@ -639,9 +637,9 @@ export class InputHandler extends THREE.EventDispatcher {
 			} 
             
             // check for a click  
-            
-            if(pressDistance < Potree.config.clickMaxDragDis && pressTime<Potree.config.clickMaxPressTime && !e.unableClick){
-                let clickElement, consumed = false;
+           
+            if(!Potree.settings.disableClick && pressDistance < Potree.config.clickMaxDragDis && pressTime<Potree.config.clickMaxPressTime && !e.unableClick){
+                let clickElement 
                 if(this.hoveredElements){ 
                     clickElement = this.hoveredElements.find(e=>e.object._listeners['click']) 
                     if(clickElement){
@@ -654,7 +652,8 @@ export class InputHandler extends THREE.EventDispatcher {
                             {
                                 type: 'click',
                                 pressDistance ,
-                                cancel                                
+                                cancel ,
+                                consume                                
                             }
                         )); 
                         if(canceled){//比如只需要右键的话,可以忽视左键的点击
@@ -692,7 +691,7 @@ export class InputHandler extends THREE.EventDispatcher {
                 }
                  
                  
-                let consume = () => { return consumed = true; };
+                
                 let desc = this.getEventDesc(e,isTouch)
                  
                 if(!consumed){ 
@@ -729,11 +728,11 @@ export class InputHandler extends THREE.EventDispatcher {
                 
                 //自行执行双击:
                 
-                if(now - this.lastClickTime < Potree.config.doubleClickTime){
+                if(!consumed && now - this.lastClickTime < Potree.config.doubleClickTime){
                     this.onDoubleClick(e)
                 }
                 
-                this.lastClickTime = now
+                consumed || (this.lastClickTime = now)
             }
         
             this.drag = null; 
@@ -989,7 +988,14 @@ export class InputHandler extends THREE.EventDispatcher {
             }else{
                 intersect = intersectOnModel || intersectPoint
             } 
-                       
+            
+
+            if(intersect?.normal){ //尤其是点云屋顶上的normal都指向屋内了,需要根据站位反向一下
+                if (viewport.view.direction.angleTo(intersect.normal) < Math.PI / 2) {
+                    intersect.normal.negate()
+                    intersect.localNormal?.negate()
+                }
+            }
         }
          
                        
@@ -1056,12 +1062,11 @@ export class InputHandler extends THREE.EventDispatcher {
     
     
     
-    onMouseMove (e) {  
+    onMouseMove (e) {   
         return this.dealPointerMove( e )
     }
 
-    dealPointerMove(e, isTouch){ 
-    
+    dealPointerMove(e, isTouch){  
         if(this.interactHistory.move) return  //一帧只触发一次
         this.interactHistory.move = 1
          
@@ -1169,19 +1174,32 @@ export class InputHandler extends THREE.EventDispatcher {
                     this.lastMouseoverElement = curr
                     viewer.dispatchEvent('content_changed') 
                 }
-
-                if(hoveredElements.length > 0){
+                if(curr?._listeners?.mousemove){
+                    if(curr){ //xzw改为只取第一个
+                        curr.dispatchEvent($.extend(  
+                            this.getEventDesc(e),
+                            {
+                                type: 'mousemove',
+                                hoveredElement: cur
+                            }
+                        ));
+                    }
+                }
+                /* if(hoveredElements.length > 0){
                     let object = hoveredElements
                         .map(e => e.object)
                         .find(e => (e._listeners && e._listeners['mousemove']));
                     
-                    if(object){
-                        object.dispatchEvent({
-                            type: 'mousemove',
-                            object: object
-                        });
-                    } 
-                }
+                    if(object){ 
+                        object.dispatchEvent($.extend(  
+                            this.getEventDesc(e),
+                            {
+                                type: 'mousemove',
+                                hoveredElement: hoveredElements.find(e=>e.object == object)
+                            }
+                        ));
+                    }  
+                } */
                 this.hoveredElements = hoveredElements
             }
             
@@ -1444,7 +1462,7 @@ export class InputHandler extends THREE.EventDispatcher {
 
         
         //raycaster.layers.enableAll()//add
-        let layers = ['sceneObjects','mapObjects','measure',  'transformationTool', 'model', 'bothMapAndScene'] //设置能识别到的layers(如空间模型里只有mapViewer能识别到marker)
+        let layers = ['sceneObjects','mapObjects',/* 'measure', */  'transformationTool', 'model', 'bothMapAndScene'] //设置能识别到的layers(如空间模型里只有mapViewer能识别到marker)
         if(Potree.settings.mergeType2 && Potree.settings.modelSkybox && Potree.settings.displayMode == 'showPanos' && !viewer.images360.currentPano.pointcloud.hasDepthTex) layers.push('skybox')//model变成skybox了
         Potree.Utils.setCameraLayers(raycaster, layers,  this.hoverViewport && this.hoverViewport.extraEnableLayers)
              
@@ -1463,8 +1481,10 @@ export class InputHandler extends THREE.EventDispatcher {
                 
                 let material = e.object.material
                 
-                return e.object.pickDontCheckDis || ( material.depthTest == false || material.depthWrite == false) && !material.realUseDepth  //!material.depthTestWhenPick
-                 || ( material.useDepth ? e.distance <= this.intersect.distance + material.uniforms.clipDistance.value : e.distance <= this.intersect.distance )
+                return e.object.pickDontCheckDis || 
+                ( material.defines?.FadeFar == void 0 || e.distance < material.uniforms.fadeFar.value * 1.2  ) && (//在几乎完全消失的距离内
+                    ( material.depthTest == false || material.depthWrite == false) && !material.realUseDepth //!material.depthTestWhenPick
+                    || ( material.useDepth ? e.distance <= this.intersect.distance + material.uniforms.clipDistance.value : e.distance <= this.intersect.distance ))
                 //maxClipFactor是否需要考虑?
             }) 
         }

+ 100 - 121
src/navigation/OrbitControlsNew.js

@@ -16,8 +16,8 @@
 import * as THREE from "../../libs/three.js/build/three.module.js"; 
 import {Utils} from "../utils.js"; 
  
-
-let minRadius = 2
+const standartMinRadius = 2
+window.camRatio = 1850
 
  
 export class OrbitControls extends THREE.EventDispatcher{
@@ -49,7 +49,8 @@ export class OrbitControls extends THREE.EventDispatcher{
 		this.tweens = [];
         this.dollyStart = new THREE.Vector2
         this.dollyEnd = new THREE.Vector2
-        
+        this.minRadius = standartMinRadius
+        this.maxRadius = 300
         
         this.keys = {
             FORWARD: ['W'.charCodeAt(0), 38],
@@ -63,16 +64,16 @@ export class OrbitControls extends THREE.EventDispatcher{
         
 		let drag = (e) => {
             if(!this.enabled)return
- 
+            
             let viewport = e.dragViewport;
             if(!viewport /* || viewport.camera.type == "OrthographicCamera"  */)return
             //let camera = viewport.camera 
           
 
 
-			if (e.drag.object !== null) {
+			/* if (e.drag.object !== null) {
 				return;
-			}
+			} */
             let mode
             
             if(e.isTouch){
@@ -106,10 +107,10 @@ export class OrbitControls extends THREE.EventDispatcher{
 
 				
 			} else if(mode == 'pan'){
-               
+                if(!this.dragStarted) this.updateRadius('startPan')
 				this.panDelta.x += ndrag.x;
 				this.panDelta.y += ndrag.y;
-
+                
 				 
 			}else if(mode == 'scale-pan'){ //add
                 this.dollyEnd.subVectors(e.touches[0].pointer, e.touches[1].pointer); 
@@ -135,7 +136,7 @@ export class OrbitControls extends THREE.EventDispatcher{
             }
             
             this.stopTweens();
-            
+            this.dragStarted = true
             
 		};
         
@@ -146,27 +147,18 @@ export class OrbitControls extends THREE.EventDispatcher{
         
 		let drop = e => {
             if(!this.enabled)return
+            this.dragStarted = false
 			this.dispatchEvent({type: 'end'});
 		};
 
 		let scroll = (e) => {
             if(!this.enabled)return
 			let resolvedRadius = this.currentViewport.view.radius + this.radiusDelta;
-            
-            /* let model = viewer.inputHandler.intersect?.model || viewer.inputHandler.intersect?.pointcloud || viewer.modules.MergeEditor?.selected;  
-            let min = 0.1
-            if(model){
-                min *= model.scale.x            //有的模型太小。注意:如果没有选中模型且没有intersect会无法前进是会有点怪
-            }
-            if(resolvedRadius < min && e.delta>0)return; //防止缩放太小,导致很慢 */
-			this.radiusDelta += -e.delta * resolvedRadius * 0.1;
-            
-            
-            let model = viewer.inputHandler.intersect?.object || viewer.inputHandler.intersect?.pointcloud 
-            model && this.updateRadiusByModel(model, viewer.inputHandler.intersect.location.distanceTo(this.currentViewport.view.position))
-            
-            
+             
+			this.radiusDelta += -e.delta * resolvedRadius * 0.1; 
+             
 			this.stopTweens();
+            this.updateRadius('scroll')
 		};
 
 		let dblclick = (e) => {
@@ -266,12 +258,16 @@ export class OrbitControls extends THREE.EventDispatcher{
         this.viewer.addEventListener('focusOnObject',(o)=>{
             if(o.position && o.CamTarget){
                 let distance = o.position.distanceTo(o.CamTarget)
-                if(distance < minRadius) minRadius = distance * 0.5 //融合页面当focus一个很小的物体时,需要将minRadius也调小
+                //if(distance < minRadius) minRadius = distance * 0.5 //融合页面当focus一个很小的物体时,需要将minRadius也调小
+                this.minRadius = Math.min(standartMinRadius, distance * 0.5)
+                //console.log('focus dis', distance) 
             }
         })
-        
+         
 	}
 
+    
+
 	setScene (scene) {
 		this.scene = scene;
 	}
@@ -293,91 +289,34 @@ export class OrbitControls extends THREE.EventDispatcher{
 		this.radiusDelta = 0;
 		this.panDelta.set(0, 0);
 	}
-	
-	/* zoomToLocation(mouse){
-        if(!this.enabled)return
-		let camera = this.scene.getActiveCamera();
-		
-		let I = Utils.getMousePointCloudIntersection(
-            null, mouse, this.viewer.inputHandler.pointer,   
-			camera,
-			this.viewer,
-			this.scene.pointclouds,
-			{pickClipped: true});
-         
-		if (I === null) {
-			return;
-		} 
-
-		let targetRadius = 0;
-		{
-			let minimumJumpDistance = 0.2;
-
-			let domElement = this.renderer.domElement;
-			let ray = Utils.mouseToRay(this.viewer.inputHandler.pointer , camera, domElement.clientWidth, domElement.clientHeight);
-
-			let nodes = I.pointcloud.nodesOnRay(I.pointcloud.visibleNodes, ray);
-			let lastNode = nodes[nodes.length - 1];
-			let radius = lastNode.getBoundingSphere(new THREE.Sphere()).radius;
-			targetRadius = Math.min(this.currentViewport.view.radius, radius);
-			targetRadius = Math.max(minimumJumpDistance, targetRadius);
-		}
-
-		let d = this.currentViewport.view.direction.multiplyScalar(-1);
-		let cameraTargetPosition = new THREE.Vector3().addVectors(I.location, d.multiplyScalar(targetRadius));
-		// TODO Unused: let controlsTargetPosition = I.location;
-
-		let animationDuration = 600;
-		let easing = TWEEN.Easing.Quartic.Out;
-
-		{ // animate
-			let value = {x: 0};
-			let tween = new TWEEN.Tween(value).to({x: 1}, animationDuration);
-			tween.easing(easing);
-			this.tweens.push(tween);
-
-			let startPos = this.currentViewport.view.position.clone();
-			let targetPos = cameraTargetPosition.clone();
-			let startRadius = this.currentViewport.view.radius;
-			let targetRadius = cameraTargetPosition.distanceTo(I.location);
-
-			tween.onUpdate(() => {
-				let t = value.x;
-				this.currentViewport.view.position.x = (1 - t) * startPos.x + t * targetPos.x;
-				this.currentViewport.view.position.y = (1 - t) * startPos.y + t * targetPos.y;
-				this.currentViewport.view.position.z = (1 - t) * startPos.z + t * targetPos.z;
-
-				this.currentViewport.view.radius = (1 - t) * startRadius + t * targetRadius;
-				this.viewer.setMoveSpeed(this.currentViewport.view.radius);
-			});
-
-			tween.onComplete(() => {
-				this.tweens = this.tweens.filter(e => e !== tween);
-			});
-
-			tween.start();
-		}
-	} */
-
-
-
-
-    
+	  
     zoomToLocation(mouse){
         let I = viewer.inputHandler.intersect;
-        
-        if(!I)return
-        
-        let object = I.object || I.pointcloud;
-        I = I.location
-        
-        
-        if(!I || !object)return;
+        let object  
+        if(I){ 
+            object = I.object || I.pointcloud;
+            I = I.location
+        }else{//点击空白处(地面)
+            let {x,y} = Potree.Utils.getPointerPosAtHeight(0, viewer.inputHandler.pointer ) 
+            I = new THREE.Vector3(x,y, 0)
+        }
+         
         
         let dis = this.currentViewport.view.position.distanceTo(I); 
-        let distance = this.updateRadiusByModel(object, dis)
-        
+        let distance  
          
+        let object_ = object || viewer.modules.MergeEditor?.selected
+        if(object_){
+            distance = this.updateRadiusByModel(object_, dis)
+        } 
+        
+        if(!object){
+            if(!distance || distance < 50){ //方便从高空回到地面
+                distance = Math.min(dis,50)
+            } 
+            this.updateRadius('focus')
+        }
+        
         
         viewer.focusOnObject({ position:I }, 'point', null, {distance})
         
@@ -389,11 +328,53 @@ export class OrbitControls extends THREE.EventDispatcher{
         let size = bound.getSize(new THREE.Vector3); 
         let len = size.length() 
         let distance = THREE.Math.clamp(dis, 0.8 * object.scale.x , Math.max(len * 0.1 , 3 * object.scale.x) );
-        minRadius = distance
-        //console.log('updateRadiusByModel',distance)
+        this.minRadius = Math.min(distance, standartMinRadius) 
+        this.maxRadius = dis * 2 
+        //console.log('maxRadius hasIntersect',   this.maxRadius)
         return distance
     }
-
+    
+    updateRadius(type){
+        if(type == 'startPan'){//以drag的point为target, 使drag跟手
+            let I = viewer.inputHandler.intersect?.location
+            if(!I){
+                let {x,y} = Potree.Utils.getPointerPosAtHeight(0, viewer.inputHandler.pointer ) 
+                I = new THREE.Vector3(x,y, 0) //地面交点(如果没有显示地面网格很可能模型跑到地面之下怎么办,ground不再是0的高度,怎么办。主要是测试时乱放模型。)
+            } 
+            let vec = new THREE.Vector3().subVectors(I, this.currentViewport.view.position)
+            let dis = vec.projectOnVector(this.currentViewport.view.direction).length() //要拖拽的点到镜头平面距离
+              
+            this.currentViewport.view.radius = dis
+            //console.log('radius',dis)
+        }
+            
+         
+        
+        let model = viewer.inputHandler.intersect?.object || viewer.inputHandler.intersect?.pointcloud 
+        if(model){
+            this.updateRadiusByModel(model, viewer.inputHandler.intersect.location.distanceTo(this.currentViewport.view.position))
+             
+        }else { 
+            if(!viewer.bound || viewer.bound.boundSize.x == 0){
+                this.maxRadius = viewer.mainViewport.view.position.length() * 2
+            }else{
+                let boundFloor = viewer.bound.boundingBox.clone();
+                boundFloor.max.z = boundFloor.min.z
+                let dis2 = boundFloor.distanceToPoint(viewer.mainViewport.view.position)  //靠近模型底部时变慢 
+                this.maxRadius = Math.max(4, dis2 * 5)
+                //console.log('maxRadius noIntersect', this.maxRadius)
+            }
+        }  
+    }
+    /*  增加maxRadius限制是因为2024.12有人提单说在离模型区域bound很远的地方scroll和drag多次之后回来,radius会过大。
+    所以只能稍微加一个影响radius的因素。但是当没有intersect时,效果可能欠佳。
+    没有完美的方法,想象下,在bound外也有物体需要focusPoint, 如path, 此时虽然离bound很远但也需要小radius。
+    而当转向朝bound飞去却很慢,虽然可以根据intersect提高minRadius来加速,但如果是focus一个四周都有模型的target,就会被intersect干扰,无法focus。
+    
+    现在存在的现象就是容易出现radius过小,游不到终点的情况,所以我让双击可以focus地面,通过这个办法攀到终点。另外drag时修改了radius,使跟手。但若不显示地面和地图,就很奇怪。
+    *没办法直接更改radius,而是通过minRadius和maxRadius, 是因为情况太多了,直接更改不能满足所有情况,还会造成突变。
+    *minRadius一般不超过standardMinRadius
+    */
 
 	stopTweens () {
 		this.tweens.forEach(e => e.stop());
@@ -463,15 +444,12 @@ export class OrbitControls extends THREE.EventDispatcher{
 			view.position.copy(position);
 		}
 
-		if(camera.type != 'OrthographicCamera'){ // apply pan
-			/* let progression = Math.min(1, this.fadeFactor * delta);
-			let panDistance = progression * view.radius * 3; */
+		if(camera.type != 'OrthographicCamera'){ // apply pan 平移 
+            let panDistance = view.radius * Math.tan(THREE.Math.degToRad(camera.fov / 2));//参照4dkk  只要radius设置正确就完全跟手(见updateRadius)  
+            //计算了下确实是这么算的。 平移pivot。  
             
-            let panDistance = 2 * view.radius * Math.tan(THREE.Math.degToRad(camera.fov / 2));//参照4dkk。 平移target(也就是平移镜头位置),但还是难以保证跟手(navvis也不一定跟手,但是很奇怪在居中时中心点居然是跟手的,可能计算方式不同)
-            //计算了下确实是这么算的。 平移pivot。 
-            
-			let px = -this.panDelta.x * panDistance;
-			let py = this.panDelta.y * panDistance;
+			let px = -this.panDelta.x * panDistance * camera.aspect 
+			let py = this.panDelta.y * panDistance 
 
 			view.pan(px, py);
 		}
@@ -486,12 +464,13 @@ export class OrbitControls extends THREE.EventDispatcher{
                       
 			let V = view.direction.multiplyScalar(-radius);
 			let position = new THREE.Vector3().addVectors(view.getPivot(), V);
-			
-            if(this.constantlyForward) {// 到达中心点后还能继续向前移动,也就是能推进中心点
-                if(radius < minRadius){  
-                    radius = minRadius
-                } 
+            
+			radius = Math.min(radius, this.maxRadius)
+            if(this.constantlyForward) {// 到达中心点后还能继续向前移动,也就是能推进中心点 
+                radius = Math.max(radius, this.minRadius)
             }
+            
+            
             view.radius = radius;            
 			view.position.copy(position);
 		}

+ 57 - 43
src/viewer/EDLRendererNew.js

@@ -190,19 +190,31 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
 
 
 
+    getIfRtEDL_(params={}){
+       return (Potree.settings.pointEnableRT && Potree.settings.displayMode == 'showPointCloud' 
+                    || Potree.settings.displayMode == 'showPanos'/*  && viewer.images360.currentPano.pointcloud.hasDepthTex  */
+                    || viewer.useEDL) &&  
+                    Features.EXT_DEPTH.isSupported() && params.camera.type != "OrthographicCamera" && !params.dontRenderRtEDL && (params.rtEDL || this.getRtEDL(params.viewport))  // 平面相机不用depthTex直接打开depthTest?且不使用edl
+        
+    }
 
-
-
+    getIfuseEdl(params={}  ){
+        return viewer.useEDL  && Potree.settings.displayMode != 'showPanos'
+    }
+    
+    
+    
+    canUseRTPoint(){
+        return Potree.settings.useRTPoint && !viewer.scene.pointclouds.some(e=>e.visible && e.material.opacity < 1) && !viewer.objs.children.some(e=>e.opacity < 1)  //透明mesh发黑,透明无效
+    }
+    
+    
 	render(params={}){ 
          
         const viewer = this.viewer; 
 		let camera = params.camera ? params.camera : viewer.scene.getActiveCamera();
-        let rtEDL = (Potree.settings.pointEnableRT && Potree.settings.displayMode == 'showPointCloud' 
-                    || Potree.settings.displayMode == 'showPanos'/*  && viewer.images360.currentPano.pointcloud.hasDepthTex  */
-                    || viewer.useEDL) &&  
-                    Features.EXT_DEPTH.isSupported() && camera.type != "OrthographicCamera" && !params.dontRenderRtEDL && (params.rtEDL || this.getRtEDL(params.viewport))  // 平面相机不用depthTex直接打开depthTest?且不使用edl
-        let useEDL = viewer.useEDL && rtEDL && Potree.settings.displayMode != 'showPanos'
-        
+        let rtEDL = this.getIfRtEDL_(params) 
+        let useEDL = rtEDL && this.getIfuseEdl(params)
         let target = params.target || null
         
         const resolution = (rtEDL && Potree.settings.useRTPoint) ? new THREE.Vector2(rtEDL.width,rtEDL.height) : params.target ? new THREE.Vector2(params.target.width, params.target.height ) : params.viewport ? params.viewport.resolution2 : this.viewer.renderer.getSize(new THREE.Vector2());//截图时需要用target的大小
@@ -247,7 +259,7 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
                     if(Potree.settings.fastTran && viewer.images360.fastTranMaskPass.enabled){
                         viewer.images360.fastTranMaskPass.render()
                     } 
-                    return
+                    if(viewer.objs.children.length == 0 || !params.useModelOnRT) return 
                 }
                  
                  
@@ -294,21 +306,21 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
              
             if(rtEDL ){ //借用rtEDL存储深度信息  
                 renderer.setRenderTarget( rtEDL );
-                
+                 
                 if(visiblePointClouds2.length>0){  //渲染scenePointCloud到rtEDL
                     pRenderer.render(viewer.scene.scenePointCloud, camera,  rtEDL, {
                         shadowMaps:  lights.length > 0 ? [this.shadowMap] : null,
                         clipSpheres: viewer.scene.volumes.filter(v => (v instanceof SphereVolume)),
-                        transparent: true,   //如果点云透明需要透明
+                        transparent: false,   // 因透明时不写深度,所以不透明。 这也导致透明时不能直接使用rtEDL绘制到屏幕
                         notAdditiveBlending: Potree.settings.notAdditiveBlending
                     });
                 } 
-                if(Potree.settings.intersectOnObjs){// model也要渲染到rtEDL
+                
+                if(Potree.settings.intersectOnObjs){
                     Potree.Utils.setCameraLayers(camera, ['model','light'])  
                     viewer.objs.traverse(e=>{if(e.material)e._OlddepthWrite = e.material.depthWrite, e.material.depthWrite = true}) //否则半透明的mesh无法遮住测量线
                     renderer.render(viewer.scene.scene, camera);
                     viewer.objs.traverse(e=>{if(e.material)e.material.depthWrite = e._OlddepthWrite})
-                    //缺点:半透明的model 就算完全透明, 也会遮住测量线
                 }  
             } 
 		}
@@ -323,47 +335,49 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
         })
          
         
-        if(showPointClouds){ //绘制点云到画布
+        
+        {//绘制点云到画布
             if(useEDL){  //设置edlMaterial  //Features.EXT_DEPTH不支持的话不会到这一块
+                if(showPointClouds){ 
+                    const uniforms = this.edlMaterial.uniforms; 
+                    uniforms.resolution.value.copy(resolution) 
+                    uniforms.edlStrength.value = viewer.edlStrength;
+                    uniforms.radius.value = viewer.edlRadius;
+                    uniforms.useEDL.value = 1;//add
+                     
+                    let proj = camera.projectionMatrix;
+                    let projArray = new Float32Array(16);
+                    projArray.set(proj.elements);
+                    uniforms.uProj.value = projArray;
                  
-                const uniforms = this.edlMaterial.uniforms; 
-                uniforms.resolution.value.copy(resolution) 
-                uniforms.edlStrength.value = viewer.edlStrength;
-                uniforms.radius.value = viewer.edlRadius;
-                uniforms.useEDL.value = 1;//add
-                 
-                let proj = camera.projectionMatrix;
-                let projArray = new Float32Array(16);
-                projArray.set(proj.elements);
-                uniforms.uProj.value = projArray;
-             
-                uniforms.uEDLColor.value = rtEDL.texture; 
-                uniforms.opacity.value = viewer.edlOpacity; // HACK 
-                 
-                Utils.screenPass.render(renderer, this.edlMaterial, target);  //相当于一个描边后期特效。 缺点: 因为target上的没有抗锯齿,所以点云在晃动镜头时会不稳定地闪烁1px位置。优点:比不打开edl少绘制一次点云,更流畅了?!
-            }else if(Potree.settings.useRTPoint && rtEDL && visiblePointClouds2.every(e=>e.material.opacity>=1)){ 
-                //半透明点云在clearAlpha为非1的状态下截图 或 在useRTPoints时绘制到屏幕上透明度不对,亮度变低 。 只能在半透明时关闭一下useRTPoint
+                    uniforms.uEDLColor.value = rtEDL.texture; 
+                    uniforms.opacity.value = viewer.edlOpacity; // HACK 
+                     
+                    Utils.screenPass.render(renderer, this.edlMaterial, target);  //相当于一个描边后期特效。 缺点: 因为target上的没有抗锯齿,所以点云在晃动镜头时会不稳定地闪烁1px位置。优点:比不打开edl少绘制一次点云,更流畅了?!
+                }
+            }else if(this.canUseRTPoint() && rtEDL){ 
+                //半透明点云在clearAlpha为非1的状态下截图 或 在useRTPoints时绘制到屏幕上透明度不对,亮度变低 。深度值遮挡也不对。 只能在半透明时关闭一下useRTPoint
                 this.recoverToScreenMat.uniforms.tDiffuse.value = rtEDL.texture; 
                 if(this.recoverToScreenMat.defines.useDepth){
                     this.recoverToScreenMat.uniforms.depthTex.value = rtEDL.depthTexture; 
                 }
-                  
+              
                 Utils.screenPass.render(renderer, this.recoverToScreenMat, target/* , Potree.settings.useFxaa && viewer.composer2 */);
+                //这时候 params_.useModelOnRT 应该和 Potree.settings.intersectOnObjs 相等 否则出错
                 
-                params.drawedModelOnRT = Potree.settings.intersectOnObjs
             }else{
                 //渲染点云 (直接用rtEDL上的会失去抗锯齿, 导致频闪、密集时出现条纹,  自己写抗锯齿也要渲染好几次。另外透明度也要处理下) 
-                   
-                let prop = {
-                    shadowMaps:  lights.length > 0 ? [this.shadowMap] : null,
-                    clipSpheres: viewer.scene.volumes.filter(v => (v instanceof SphereVolume)) ,
-                    notAdditiveBlending: Potree.settings.notAdditiveBlending//add 否则透明的点云会挡住后面的模型。 加上这句后竟然透明不会叠加了!
-                }
-                 
-                pRenderer.render(viewer.scene.scenePointCloud, camera, null , prop);  
-                
+                if(showPointClouds){
+                    let prop = {
+                        shadowMaps:  lights.length > 0 ? [this.shadowMap] : null,
+                        clipSpheres: viewer.scene.volumes.filter(v => (v instanceof SphereVolume)) ,
+                        notAdditiveBlending: Potree.settings.notAdditiveBlending//add 否则透明的点云会挡住后面的模型。 加上这句后竟然透明不会叠加了!
+                    }
+                     
+                    pRenderer.render(viewer.scene.scenePointCloud, camera, null , prop);  
+                }  
             }
-        }        
+        }   
            
         visiblePointClouds2.forEach(e=>{
             e.visible = e.oldVisi

+ 20 - 27
src/viewer/ExtendScene.js

@@ -11,7 +11,11 @@ class ExtendScene extends Scene{
 		super();
 
 		delete this.sceneBG;
+		this.overlayScene = new THREE.Scene();
+        viewer.addEventListener("render.pass.perspective_overlay", this.renderOverlay.bind(this)); 
 		
+        
+        
          
 		this.cameraP = new THREE.PerspectiveCamera(this.fov, 1, Potree.config.view.near, Potree.config.view.cameraFar);
 		this.cameraO = new THREE.OrthographicCamera(-1, 1, 1, -1, Potree.config.view.near, Potree.settings.cameraFar);
@@ -25,8 +29,8 @@ class ExtendScene extends Scene{
         Potree.Utils.setObjectLayers(this.axisArrow,  'bothMapAndScene' )
         
         
-        this.tags = new THREE.Object3D;
-        this.scene.add(this.tags)
+        
+        
 
 	}
 
@@ -204,37 +208,26 @@ class ExtendScene extends Scene{
             this.bg2 = bg2
 		}
 
-
-		// { // lights
-		// 	{
-		// 		let light = new THREE.DirectionalLight(0xffffff);
-		// 		light.position.set(10, 10, 1);
-		// 		light.target.position.set(0, 0, 0);
-		// 		this.scene.add(light);
-		// 	}
-
-		// 	{
-		// 		let light = new THREE.DirectionalLight(0xffffff);
-		// 		light.position.set(-10, 10, 1);
-		// 		light.target.position.set(0, 0, 0);
-		// 		this.scene.add(light);
-		// 	}
-
-		// 	{
-		// 		let light = new THREE.DirectionalLight(0xffffff);
-		// 		light.position.set(0, -10, 20);
-		// 		light.target.position.set(0, 0, 0);
-		// 		this.scene.add(light);
-		// 	}
-		// }
 	} 
-     
+    
+
+    renderOverlay(o={}){//  measure  tag
+        if(this.overlayScene.children.filter(e=>e.visible).length == 0)return
+        let renderer = o.renderer || this.viewer.renderer
+        Potree.Utils.setCameraLayers(o.camera, ['sceneObjects'])
+		 
+        viewer.dispatchEvent({type: "render.begin2" , name:'overlays', viewport:o.viewport, renderer:o.renderer  })
+        renderer.render(this.overlayScene, o.camera );
+       
+	}
+    
+    
 };
 
 
 
 //Object.assign( ExtendScene.prototype, THREE.EventDispatcher.prototype );
 
-
+        
 
 export {ExtendScene}

+ 2 - 5
src/viewer/sidebar2.html

@@ -9,10 +9,7 @@
                 <ul name='model'>
                     <li name="laser">
                         laser : <button name='operation'>添加</button>  <button name='select'> select </button>
-                    </li>
-                    <li name="4dkk">
-                        4dkk : <button name='operation'>添加</button>  <button name='select'> select </button>
-                    </li>
+                    </li> 
                     <li name="obj">
                         obj : <button name='operation'>添加</button>  <button name='select'> select </button>
                     </li>
@@ -35,7 +32,7 @@
                 
                 <li> <button name='splitScreen'>分屏</button> </li>
                 <li> <button name='tag'>加热点</button> </li>
-                
+                <li> <button name='path'>加路径</button> </li>
                 
             </div>
             

+ 7 - 1
src/viewer/sidebarNew.js

@@ -191,7 +191,13 @@ export class Sidebar{
              
             viewer.tagTool.startInsertion()
         })
-        
+        pannel.find('li button[name="path"]').on('click',(e)=>{ 
+            let info = {
+                type : 'Path' , minMarkers : 2
+            }
+            viewer.measuringTool.startInsertion( info,() => {})
+
+        })
     }
     
     

+ 219 - 8
src/workers/BinaryDecoderWorker.js

@@ -1,8 +1,10 @@
 
-
+import * as THREE from "../../libs/three.js/build/three.module.js";
 import {Version} from "../Version.js";
 import {PointAttributes, PointAttribute, PointAttributeTypes} from "../loader/PointAttributes.js";
 
+import {Splat} from '../custom/objects/3dgs/Splat.js'
+
 const typedArrayMapping = {
 	"int8":   Int8Array,
 	"int16":  Int16Array,
@@ -16,6 +18,74 @@ const typedArrayMapping = {
 	"double": Float64Array,
 };
 
+
+
+
+const gs3dProplist = [
+'f_dc_0',  //color
+'f_dc_1',
+'f_dc_2',
+'f_rest_0',  // has sh if not null  at origin inriav1parser.js
+'f_rest_1',
+'f_rest_2',
+'f_rest_3',
+'f_rest_4',
+'f_rest_5',
+'f_rest_6',
+'f_rest_7',
+'f_rest_8',
+'f_rest_9',
+'f_rest_10',
+'f_rest_11',
+'f_rest_12',
+'f_rest_13',
+'f_rest_14',
+'f_rest_15',
+'f_rest_16',
+'f_rest_17',
+'f_rest_18',
+'f_rest_19',
+'f_rest_20',
+'f_rest_21',
+'f_rest_22',
+'f_rest_23',
+'f_rest_24',
+'f_rest_25',
+'f_rest_26',
+'f_rest_27',
+'f_rest_28',
+'f_rest_29',
+'f_rest_30',
+'f_rest_31',
+'f_rest_32',
+'f_rest_33',
+'f_rest_34',
+'f_rest_35',
+'f_rest_36',
+'f_rest_37',
+'f_rest_38',
+'f_rest_39',
+'f_rest_40',
+'f_rest_41',
+'f_rest_42',
+'f_rest_43',
+'f_rest_44',
+'opacity',
+'scale_0',
+'scale_1',
+'scale_2',
+'rot_0',
+'rot_1',
+'rot_2',
+'rot_3'
+]
+
+
+
+
+
+
+
 Potree = {};
 
 onmessage = function (event) {
@@ -40,12 +110,17 @@ onmessage = function (event) {
 
 	let attributeBuffers = {};
 	let inOffset = 0;
+    
+    let hasGS3D = pointAttributes.attributes.some(e=>e.name == 'GS3D')
+    
 	for (let pointAttribute of pointAttributes.attributes) {
 		
 		if (pointAttribute.name === "POSITION_CARTESIAN") {
 			let buff = new ArrayBuffer(numPoints * 4 * 3);
 			let positions = new Float32Array(buff);
-		
+            
+            
+            
 			for (let j = 0; j < numPoints; j++) {
 				let x, y, z;
 
@@ -77,6 +152,27 @@ onmessage = function (event) {
 			}
 
 			attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
+             
+            
+            if(hasGS3D){//add 
+                let buff2 = new ArrayBuffer(numPoints * 4 * 4);
+                let buff3 = new ArrayBuffer(numPoints * 4 * 4);
+                let centersInt = new Int32Array(buff2);
+                let centersFloat = new Float32Array(buff3);
+                for(let i=0;i<numPoints;i++){
+                    centersFloat[4 * i + 0] = positions[3 * i + 0]
+                    centersFloat[4 * i + 1] = positions[3 * i + 1]
+                    centersFloat[4 * i + 2] = positions[3 * i + 2]
+                    centersFloat[4 * i + 3] = 1;
+                    centersInt[4 * i + 0] = positions[3 * i + 0] * 1000
+                    centersInt[4 * i + 1] = positions[3 * i + 1] * 1000
+                    centersInt[4 * i + 2] = positions[3 * i + 2] * 1000
+                    centersInt[4 * i + 3] = 1000
+                }
+                attributeBuffers['centersInt'] = { buffer: buff2, attribute: pointAttribute };
+                attributeBuffers['centersFloat'] = { buffer: buff3, attribute: pointAttribute }; //暂时必须4位,以后再改cpp改为3位
+            }
+            
 		} else if (pointAttribute.name === "rgba") {
 			let buff = new ArrayBuffer(numPoints * 4);
 			let colors = new Uint8Array(buff);
@@ -116,10 +212,10 @@ onmessage = function (event) {
 				normals[3 * j + 0] = nx;
 				normals[3 * j + 1] = ny;
 				normals[3 * j + 2] = nz;
-			}
-
+			} 
+   
 			attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
-		} else if (pointAttribute.name === "NORMAL_OCT16") {
+		} else if (pointAttribute.name === "NORMAL_OCT16") {//只需要2 byte! 原本需要12个byte
 			let buff = new ArrayBuffer(numPoints * 4 * 3);
 			let normals = new Float32Array(buff);
 
@@ -142,8 +238,8 @@ onmessage = function (event) {
 					y = -(u / Math.sign(u) - 1) / Math.sign(v);
 				}
 
-				let length = Math.sqrt(x * x + y * y + z * z);
-				x = x / length;
+				let length = Math.sqrt(x * x + y * y + z * z);  //因法线长度固定为1,所以只需要xy就能算出z(不过这里似乎是约定相加为1?)
+				x = x / length;                                 //x和y都只有一个byte,所以精度很低,只有256个分段            
 				y = y / length;
 				z = z / length;
 				
@@ -168,7 +264,122 @@ onmessage = function (event) {
 			}
 
 			attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
-		} else {
+		} else if (pointAttribute.name === "GS3D") {//add  见inriav1plyparser.js
+            //////////////////////////////////////////////////////////
+            let buff = new ArrayBuffer(numPoints * pointAttribute.byteSize);
+			let f32 = new Float32Array(buff);
+               
+            for (let j = 0; j < numPoints; j++) {//仿照position的写法 填入数据
+                for (let i = 0; i < pointAttribute.numElements; i++) {
+                    //f32[pointAttribute.numElements * j + i] = view.getUint32( inOffset + j * pointAttributes.byteSize + 4*i, true) * scale   ;
+                    f32[pointAttribute.numElements * j + i] = view.getFloat32(inOffset + j * pointAttributes.byteSize + 4*i, true) //+ nodeOffset[0];
+                    //是Uint32还是Float32? PlyParserUtils.js中写的是getFloat32 (    rawVertex[fieldId] = vertexData.getFloat32(offset + fieldOffsets[fieldId], true);)
+                     
+                } 
+			}
+            attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
+            
+            
+            //得到颜色
+            
+            let buff2 = new ArrayBuffer(numPoints * 4);
+			let colors = new Uint8Array(buff2);
+            const SH_C0 = 0.28209479177387814; 
+            const offset_opa = gs3dProplist.indexOf('opacity')   
+            const offset_col = gs3dProplist.indexOf('f_dc_0')   
+            let getColor = (index)=>{
+                let value = (0.5 + SH_C0 * f32[index+offset_col]) * 255;  
+                return THREE.Math.clamp(Math.floor(value), 0, 255);
+            }
+            let getOpacity = (index)=>{
+                let value = (1 / (1 + Math.exp(-f32[index+offset_opa]))) * 255;   
+                return THREE.Math.clamp(Math.floor(value), 0, 255);
+            }
+			for (let j = 0; j < numPoints; j++) {
+				colors[4 * j + 0] = getColor(j * pointAttribute.numElements + 0)   
+				colors[4 * j + 1] = getColor(j * pointAttribute.numElements + 1) 
+				colors[4 * j + 2] = getColor(j * pointAttribute.numElements + 2) 
+                colors[4 * j + 3] = getOpacity(j * pointAttribute.numElements) 
+			}
+
+			attributeBuffers['rgba'] = { buffer: buff2, attribute: pointAttribute };
+            
+            
+          
+            
+            
+            //scale 
+            /* let buff4 = new ArrayBuffer(numPoints * 12);
+			let scale = new Float32Array(buff4);
+            const offset_scale = gs3dProplist.indexOf('scale_0') //第49个数开始是      
+            let getScale = (index)=>{
+                return Math.exp(f32[index+offset_scale]); 
+            }
+			for (let j = 0; j < numPoints; j++) {
+				scale[3 * j + 0] = getScale(j * pointAttribute.numElements + 0)   
+				scale[3 * j + 1] = getScale(j * pointAttribute.numElements + 1) 
+				scale[3 * j + 2] = getScale(j * pointAttribute.numElements + 2) 
+			}
+
+			attributeBuffers['scale'] = { buffer: buff4, attribute: pointAttribute };
+            
+            
+            //rotation
+            let buff5 = new ArrayBuffer(numPoints * 16);
+			let qua = new Float32Array(buff5);
+            const offset_rot = gs3dProplist.indexOf('rot_0')        
+            let getRot = (index)=>{
+                return f32[index+offset_rot] ; 
+            }
+			for (let j = 0; j < numPoints; j++) {
+				qua[4 * j + 0] = getRot(j * pointAttribute.numElements + 0)   
+				qua[4 * j + 1] = getRot(j * pointAttribute.numElements + 1) 
+				qua[4 * j + 2] = getRot(j * pointAttribute.numElements + 2) 
+                qua[4 * j + 2] = getRot(j * pointAttribute.numElements + 3) 
+			}
+
+			attributeBuffers['qua'] = { buffer: buff5, attribute: pointAttribute }; */
+            
+            
+            
+            //compute cov:
+            let buff3 = new ArrayBuffer(numPoints * 24); 
+            let covs = new Float32Array(buff3);
+            const offset_scale = gs3dProplist.indexOf('scale_0') //第49个数开始是      
+            const offset_rot = gs3dProplist.indexOf('rot_0')    
+            let scale = new THREE.Vector3()
+            let quaternion = new THREE.Quaternion()
+            
+            let getScale = (index)=>{ 
+                let get = (offset)=>{
+                    return Math.exp(f32[index * pointAttribute.numElements + offset + offset_scale]);
+                } 
+                scale.set(get(0), get(1), get(2)) 
+            }
+            let getQuatenion = (index)=>{
+                let get = (offset)=>{
+                    return f32[index * pointAttribute.numElements + offset + offset_rot]
+                } 
+                //quaternion.set( get(0), get(1), get(2), get(3)) 
+                quaternion.set(get(1), get(2), get(3), get(0)) //w放到最后 另外如果compressionLevel不是0的话还要再加一步,见this.fbf(sectionFloatArray[rotationBase + 1]
+                //quaternion.normalize();
+                
+            }
+            
+            for (let j = 0; j < numPoints; j++) {
+                getScale(j); 
+                getQuatenion(j);  
+                Splat.computeCovariance(scale, quaternion, null, covs, 6 * j ); 
+            }
+            
+            attributeBuffers['covs'] = { buffer: buff3, attribute: pointAttribute };
+            
+            
+             
+            
+            
+        ////////////////////////////////////////////////////////////////////////////////    
+        }else{
 			let buff = new ArrayBuffer(numPoints * 4);
 			let f32 = new Float32Array(buff);