فهرست منبع

Merge branch 'master' of http://192.168.0.115:3000/4dkankan/4dkankan_bim

bill 2 سال پیش
والد
کامیت
409d6e5b9c
3فایلهای تغییر یافته به همراه664 افزوده شده و 1 حذف شده
  1. 11 1
      public/test-bim.html
  2. 52 0
      src/utils/common.js
  3. 601 0
      src/utils/math.js

+ 11 - 1
public/test-bim.html

@@ -57,7 +57,7 @@
     <!-- 引用BIMFACE的JavaScript显示组件库 -->
     <script src="https://static.bimface.com/api/BimfaceSDKLoader/BimfaceSDKLoader@latest-release.js"></script>
     <script>
-      let viewToken = "60a1020943254a2ca0a19b21293a1370";
+      let viewToken = "eeb852a19797490985bb3a85ba7af546";
       // 声明Viewer及App
       let viewer3D;
       let app;
@@ -119,6 +119,14 @@
             viewer3D.render();
           }
         );
+        
+        viewer3D.addEventListener(Glodon.Bimface.Viewer.ViewerDrawingEvent.MouseClicked,function (objectdata) {
+          // 调用viewerDrawing对象的Method,可以继续扩展功能
+          alert("objectId : " + JSON.stringify(objectdata.objectId) + "\n" + "worldPosition : " + JSON.stringify(objectdata.worldPosition));
+        });
+        
+        
+        
       }
 
       // 加载失败回调函数
@@ -141,6 +149,8 @@
           viewer3D.render();
         }
       }
+      
+      
     </script>
   </body>
 </html>

+ 52 - 0
src/utils/common.js

@@ -105,5 +105,57 @@ export default {
             str += arr[pos]
         }
         return str
+    },
+    
+    
+    
+    ifSame : function(object1, object2){
+        if(object1 == object2  )return true // 0 != undefined  , 0 == ''
+        else if(!object1 || !object2) return false
+        else if(object1.constructor != object2.constructor){
+            return false
+        }else if(object1 instanceof Array ) {
+            if(object1.length != object2.length)return false;
+            var _object2 = object2.slice(0);
+            
+            for(let i=0;i<object1.length;i++){ 
+                var u = _object2.find(e=>ifSame(object1[i], e));
+                if(u == void 0 && !_object2.includes(u) && !object1.includes(u))return false;
+                else{
+                    let index = _object2.indexOf(u);
+                    _object2.splice(index,1);
+                }
+            }
+            
+            return true
+        }else if(object1.equals instanceof Function ){//复杂数据仅支持这种,其他的可能卡住?
+            
+            return object1.equals(object2)
+              
+        }else if(typeof object1 == 'number' ||  typeof object1 == 'string'){
+            if(isNaN(object1) && isNaN(object2))return true
+            else return object1 == object2
+            
+        }else if(typeof object1 == "object"){
+            var keys1 = Object.keys(object1)
+            var keys2 = Object.keys(object2)
+            if(!ifSame(keys1,keys2))return false;
+            
+            for(let i in object1){
+                var same = ifSame(object1[i], object2[i]);
+                if(!same)return false
+            }
+            return true
+        }else{
+            console.log('isSame出现例外')
+        } 
+        
     }
+    ,
+    replaceAll : function (str, f, e) {
+        //f全部替换成e
+        var reg = new RegExp(f, "g"); //创建正则RegExp对象  
+        return str.replace(reg, e);
+    } 
+    ,
 }

+ 601 - 0
src/utils/math.js

@@ -0,0 +1,601 @@
+
+import * as THREE from "../../libs/three.js/build/three.module.js";
+import searchRings from "./searchRings.js";
+
+
+
+var math = {
+    getBaseLog(x, y) {//返回以 x 为底 y 的对数(即 logx y) .  Math.log 返回一个数的自然对数
+        return Math.log(y) / Math.log(x);
+    }
+    ,
+    convertVector : {
+        ZupToYup: function(e){//navvis -> 4dkk
+            return new THREE.Vector3(e.x,e.z,-e.y)
+        },
+        YupToZup: function(e){//4dkk -> navvis
+            return new THREE.Vector3(e.x,-e.z,e.y)
+        },
+        
+        
+    },
+    convertQuaternion: {
+        ZupToYup: function(e){//navvis -> 4dkk  //不同于convertVisionQuaternion
+            let rotation = new THREE.Euler(-Math.PI/2,0,0)   
+            let quaternion = new THREE.Quaternion().setFromEuler(rotation)
+            return e.clone().premultiply(quaternion)
+            //return new THREE.Quaternion(e.x,e.z,-e.y,e.w).multiply((new THREE.Quaternion).setFromAxisAngle(new THREE.Vector3(1,0,0), THREE.Math.degToRad(90)))
+        },
+        YupToZup: function(e){//4dkk -> navvis
+            let rotation = new THREE.Euler(Math.PI/2,0,0)
+            let quaternion = new THREE.Quaternion().setFromEuler(rotation)
+            return e.clone().premultiply(quaternion)
+        },
+        
+        
+    },
+	 
+    convertVisionQuaternion: function(e) {
+        return new THREE.Quaternion(e.x,e.z,-e.y,e.w).multiply((new THREE.Quaternion).setFromAxisAngle(new THREE.Vector3(0,1,0), THREE.Math.degToRad(90)))
+    },
+    invertVisionQuaternion : function(e) {//反转给算法部
+        var a = e.clone().multiply((new THREE.Quaternion).setFromAxisAngle(new THREE.Vector3(0,1,0), THREE.Math.degToRad(-90)))
+        return new THREE.Quaternion(a.x,-a.z,a.y,a.w)
+    },
+    //------------
+    
+    getVec2Angle : function(dir1,dir2){ 
+        return Math.acos( THREE.Math.clamp(this.getVec2Cos(dir1,dir2), -1,1) )  
+    },
+    getVec2Cos :  function(dir1,dir2){ 
+        return dir1.dot(dir2) / dir1.length() / dir2.length()    
+    },
+    getAngle:function(vec1, vec2, axis){//带方向的角度 vector3
+        var angle = vec1.angleTo(vec2)
+        var axis_ = vec1.clone().cross(vec2);
+        if(axis_[axis] < 0){
+            angle *= -1
+        }
+        return angle
+    }, 
+    
+    closeTo : function(a,b, precision=1e-6){ 
+        let f = (a,b)=>{
+            return Math.abs(a-b) < precision;
+        } 
+          
+        if(typeof (a) == 'number'){
+            return f(a, b);
+        }else{
+            let judge = (name)=>{
+                if(a[name] == void 0)return true //有值就判断,没值就不判断
+                else return f(a[name],b[name])
+            }
+            return judge('x') && judge('y') && judge('z') && judge('w')  
+        } 
+        
+    }, 
+     
+
+    
+	toPrecision: function (e, t) {//xzw change 保留小数
+		var f = function (e, t) {
+			var i = Math.pow(10, t);
+			return Math.round(e * i) / i
+		}
+		if (e instanceof Array) {
+			for (var s = 0; s < e.length; s++) {
+				e[s] = f(e[s], t);
+			}
+			return e;
+		} else if (e instanceof Object) {
+			for (var s in e) {
+				e[s] = f(e[s], t);
+			}
+			return e;
+		} else return f(e, t)
+	},
+    isEmptyQuaternion: function(e) {
+        return 0 === Math.abs(e.x) && 0 === Math.abs(e.y) && 0 === Math.abs(e.z) && 0 === Math.abs(e.w)
+    },
+    projectPositionToCanvas: function(e, t, i) {
+        i = i || new THREE.Vector3,
+        i.copy(e);
+		var r = .5 * $('#player').width()
+			, o = .5 * $('#player').height();
+        return i.project(t),
+        i.x = i.x * r + r,
+        i.y = -(i.y * o) + o,
+        i
+    },
+     
+	
+	handelPadResize:false,
+	/* handelPadding : function () { //去除player左边和上面的宽高,因为pc的player左上有其他element  许钟文
+		
+		var pads = [];//记录下来避免反复计算
+		var index = [];
+		var resetPad = function(){
+			pads = []; 
+			index = [];
+			math.handelPadResize = false; //switchview时resized为true
+		}
+		
+		if(config.isEdit && !config.isMobile){
+			window.addEventListener('resize',resetPad);
+		}
+		return function(x, y, domE){
+			if(!config.isEdit || config.isMobile) {
+				return {
+					x: x,
+					y: y
+				}  
+			}
+			
+			if(this.handelPadResize)resetPad(); 
+			domE = domE || $('#player')[0];
+			var pad;
+			var i = index.indexOf(domE);
+			if (i == -1){ 
+				index.push(domE);
+				pad = {
+					x: this.getOffset("left", domE),
+					y: this.getOffset("top", domE)
+				}
+				pads.push(pad)  
+			}					
+			else pad = pads[i];
+			return {
+				x: x - pad.x,
+				y: y - pad.y
+			}
+		}
+		
+	}(),  */
+	
+	getOffset: function (type, element, parent) {//获取元素的边距 许钟文
+		var offset = (type == "left") ? element.offsetLeft : element.offsetTop;
+		if (!parent) parent = $("body")[0];
+		while (element = element.offsetParent) {
+			if (element == parent) break;
+			offset += (type == "left") ? element.offsetLeft : element.offsetTop;
+		}
+		return offset;
+	}
+
+	,
+    constrainedTurn: function(e) {
+        var t = e % (2 * Math.PI);
+        return t = t > Math.PI ? t -= 2 * Math.PI : t < -Math.PI ? t += 2 * Math.PI : t
+    },
+    getFOVDotThreshold: function(e) {
+        return Math.cos(THREE.Math.degToRad(e / 2))
+    },
+    transform2DForwardVectorByCubeFace: function(e, t, i, n) {
+        switch (e) {
+        case GLCubeFaces.GL_TEXTURE_CUBE_MAP_POSITIVE_X:
+            i.set(1, t.y, t.x);
+            break;
+        case GLCubeFaces.GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
+            i.set(-1, t.y, -t.x);
+            break;
+        case GLCubeFaces.GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
+            i.set(-t.x, 1, -t.y);
+            break;
+        case GLCubeFaces.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
+            i.set(-t.x, -1, t.y);
+            break;
+        case GLCubeFaces.GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
+            i.set(-t.x, t.y, 1);
+            break;
+        case GLCubeFaces.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
+            i.set(t.x, t.y, -1)
+        }
+        n && i.normalize()
+    },
+	
+	
+	
+	getFootPoint : function(oldPos, p1, p2, restricInline){ //找oldPos在线段p1, p2上的垂足
+		/* if(isWorld){//输出全局坐标 需要考虑meshGroup.position
+			p1 = p1.clone();
+			p2 = p2.clone();
+			p1.y += mainDesign.meshGroup.position.y;
+			p2.y += mainDesign.meshGroup.position.y;
+		} */
+        if(p1.equals(p2))return p1.clone()
+		var op1 = oldPos.clone().sub(p1); 
+		var p1p2 = p1.clone().sub(p2)
+		var p1p2Len = p1p2.length()
+		var leftLen = op1.dot(p1p2) / p1p2Len;
+		var pos = p1.clone().add(p1p2.multiplyScalar( leftLen/p1p2Len )); 
+         
+        if(restricInline && pos.clone().sub(p1).dot( pos.clone().sub(p2)   ) > 0){//foot不在线段上
+            if(pos.distanceTo(p1) < pos.distanceTo(p2)) pos = p1.clone();
+            else pos = p2.clone();
+        }
+        
+		return pos;
+	},
+
+	
+	
+	
+	
+	
+	/**
+   * 计算多边形的重心
+   * @param {*} points
+   */
+	getCenterOfGravityPoint : function(mPoints){
+		var area = 0.0;//多边形面积
+		var Gx = 0.0, Gy = 0.0;// 重心的x、y
+
+		for (var i = 1; i <= mPoints.length; i++) {
+			var ix = mPoints[i % mPoints.length].x;
+			var iy = mPoints[i % mPoints.length].y;
+			var nx = mPoints[i - 1].x;
+			var ny = mPoints[i - 1].y;
+			var temp = (ix * ny - iy * nx) / 2.0;
+			area += temp;
+			Gx += temp * (ix + nx) / 3.0;
+			Gy += temp * (iy + ny) / 3.0;
+		}
+		Gx = Gx / area;
+		Gy = Gy / area;
+		return { x: Gx, y: Gy };
+	},
+	
+	getBound : function(ring){
+		var bound = new THREE.Box2();
+		for(var j=0,len = ring.length; j<len; j++){
+			bound.expandByPoint(ring[j])
+		}
+		return bound;
+	},
+
+	isPointInArea : function(ring, holes, point, ifAtLine){//判断点是否在某个环内, 若传递了holes代表还要不能在内环内
+		var bound = this.getBound(ring);
+		if(point.x < bound.min.x || point.x > bound.max.x || point.y < bound.min.y || point.y > bound.max.y)return false;
+		
+		 
+		var inside = false;
+		var x = point.x,
+		  y = point.y;
+
+		for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) {
+		  var xi = ring[i].x,
+			yi = ring[i].y;
+		  var xj = ring[j].x,
+			yj = ring[j].y;
+ 
+		  if((xi - x)*(yj - y) == (xi - x)*(yi - y) && x>=Math.min(xi,xj) && x<=Math.max(xi,xj)//xzw add
+			  && y>=Math.min(yi,yj) && y<=Math.max(yi,yj)
+		  ){
+			  //return !!ifAtLine;//在线段上,则判断为…… (默认在外)
+              return {atLine:true}
+		  }
+		  
+		   if (((yi > y) != (yj > y)) &&
+			(x < (xj - xi) * (y - yi) / (yj - yi) + xi) 
+			) {
+			inside = !inside;
+		  } 
+		  
+		}
+        
+        if(inside && holes){
+            return !holes.some(ring=>this.isPointInArea(ring, null, point, ifAtLine) )   //不能存在于任何一个二级内环内
+        }else{
+            return inside;
+        }
+        
+
+		
+	},
+	
+	getArea : function (ring) { //求面积  顺时针为正  来自three shape
+		for (var t = ring.length, i = 0, n = t - 1, r = 0; r < t; n = r++)
+			i += ring[n].x * ring[r].y - ring[r].x * ring[n].y;
+		return -.5 * i
+	},
+	isInBetween : function(a, b, c, precision) {
+        // 如果b几乎等于a或c,返回false.为了避免浮点运行时两值几乎相等,但存在相差0.00000...0001的这种情况出现使用下面方式进行避免
+
+        /* if (Math.abs(a - b) < 0.000001 || Math.abs(b - c) < 0.000001) {
+            return false;
+        } 
+ 
+        return (a <= b && b <= c) || (c <= b && b <= a);*/
+        
+        
+        //更改:如果b和a或c中一个接近 就算在a和c之间
+        return (a <= b && b <= c) || (c <= b && b <= a) || this.closeTo(a,b,precision) || this.closeTo(b,c,precision); 
+        
+    },
+	 
+    
+    ifPointAtLineBound:function(point, linePoints, precision){
+        //待验证  横线和竖线比较特殊
+        return  math.isInBetween(linePoints[0].x, point.x, linePoints[1].x, precision) && math.isInBetween(linePoints[0].y, point.y, linePoints[1].y, precision) 
+    }
+    
+    , 
+	
+	isLineIntersect: function (line1, line2, notSegment, precision) {//线段和线段是否有交点.  notSegment代表是直线而不是线段
+		var a1 = line1[1].y - line1[0].y;
+		var b1 = line1[0].x - line1[1].x;
+		var c1 = a1 * line1[0].x + b1 * line1[0].y;
+		//转换成一般式: Ax+By = C
+		var a2 = line2[1].y - line2[0].y;
+		var b2 = line2[0].x - line2[1].x;
+		var c2 = a2 * line2[0].x + b2 * line2[0].y;
+		// 计算交点		
+		var d = a1 * b2 - a2 * b1;
+
+		// 当d==0时,两线平行
+		if (d == 0) {
+		  return false;
+		} else {
+		  var x = (b2 * c1 - b1 * c2) / d;
+		  var y = (a1 * c2 - a2 * c1) / d;
+
+		  // 检测交点是否在两条线段上
+		  /* if (notSegment || (isInBetween(line1[0].x, x, line1[1].x) || isInBetween(line1[0].y, y, line1[1].y)) &&
+			(isInBetween(line2[0].x, x, line2[1].x) || isInBetween(line2[0].y, y, line2[1].y))) {
+			return {x,y};
+		  } */
+          if (notSegment ||  math.ifPointAtLineBound({x,y}, line1, precision) && math.ifPointAtLineBound({x,y}, line2, precision)){ 
+                return {x,y};
+		  }
+		}  
+    },
+     
+	getNormal2d : function(o={} ){//获取二维法向量 方向向内
+		var x,y, x1,y1; 
+		//line2d的向量
+        if(o.vec){
+            x1 = o.vec.x;  y1 = o.vec.y
+        }else{
+            x1 = o.p1.x - o.p2.x;
+            y1 = o.p1.y - o.p2.y;
+        }
+		
+		//假设法向量的x或y固定为1或-1
+		if(y1 != 0){
+			x = 1;
+			y = - (x1 * x) / y1;
+		}else if(x1 != 0){//y如果为0,正常情况x不会是0
+			y = 1;
+			x = - (y1 * y) / x1;
+		}else{
+			console.log("两个点一样");
+			return null;
+		}
+		
+		//判断方向里或者外:
+		var vNormal = new THREE.Vector3(x, 0, y);
+		var vLine = new THREE.Vector3(x1, 0, y1);
+		var vDir = vNormal.cross(vLine);
+		if(vDir.y>0){
+			x *= -1;
+			y *= -1;
+		} 
+		return new THREE.Vector2(x, y).normalize();
+ 	},
+     
+    getQuaBetween2Vector:function(oriVec, newVec, upVec){ //获取从oriVec旋转到newVec可以应用的quaternion
+        var angle = oriVec.angleTo(newVec);
+        var axis = oriVec.clone().cross( newVec).normalize();//两个up之间
+        if(axis.length() == 0){//当夹角为180 或 0 度时,得到的axis为(0,0,0),故使用备用的指定upVec
+            return new THREE.Quaternion().setFromAxisAngle( upVec, angle );
+        }
+        return new THREE.Quaternion().setFromAxisAngle( axis, angle );
+    }
+    /* ,
+    getQuaBetween2Vector2 : function(oriVec, newVec   ){//not camera
+        var _ = (new THREE.Matrix4).lookAt( oriVec, new THREE.Vector3, new THREE.Vector3(0,1,0))
+        var aimQua = (new THREE.Quaternion).setFromRotationMatrix(_)
+        var _2 = (new THREE.Matrix4).lookAt( newVec, new THREE.Vector3, new THREE.Vector3(0,1,0))
+        var aimQua2 = (new THREE.Quaternion).setFromRotationMatrix(_2)
+        
+        return aimQua2.multiply(aimQua.clone().inverse()) 
+        
+    } */
+    
+        
+    ,
+    
+    getScaleForConstantSize : function(){ //获得规定二维大小的mesh的scale值。可以避免因camera的projection造成的mesh视觉大小改变。  来源:tag.updateDisc
+        var w;  
+        var i = new THREE.Vector3, o = new THREE.Vector3, l = new THREE.Vector3, c = new THREE.Vector3, h = new THREE.Vector3
+        return function(op={}){
+            if(op.width2d) w = op.width2d //如果恒定二维宽度
+            else{//否则考虑上距离,加一丢丢近大远小的效果
+                var currentDis, nearBound, farBound
+                if(op.camera.type == "OrthographicCamera"){
+                    currentDis = 200 / op.camera.zoom  //(op.camera.right - op.camera.left) / op.camera.zoom
+                }else{
+                    currentDis = op.position.distanceTo(op.camera.position);
+                } 
+                w = op.maxSize - ( op.maxSize -  op.minSize) * THREE.Math.smoothstep(currentDis,  op.nearBound,  op.farBound);
+                //maxSize : mesh要表现的最大像素宽度;   nearBound: 最近距离,若比nearBound近,则使用maxSize
+            }
+            i.copy(op.position).project(op.camera),  //tag中心在屏幕上的二维坐标
+            o.set(op.resolution.x / 2, op.resolution.y / 2, 1).multiply(i), //转化成px   -w/2 到 w/2的范围
+            l.set(w / 2, 0, 0).add(o),  //加上tag宽度的一半
+            c.set(2 / op.resolution.x, 2 / op.resolution.y, 1).multiply(l), //再转回  -1 到 1的范围
+            h.copy(c).unproject(op.camera);//再转成三维坐标,求得tag边缘的位置
+            var g = h.distanceTo(op.position)//就能得到tag的三维半径
+            //这里使用的都是resolution2, 好处是手机端不会太小, 坏处是pc更改网页显示百分比时显示的大小会变(或许可以自己算出设备真实的deviceRatio, 因window.screen是不会改变的),但考虑到用户可以自行调节字大小也许是好的
+            return g  //可能NAN  当相机和position重叠时
+        }
+    }()
+    ,
+    
+    //W , H, left, top分别是rect的宽、高、左、上
+    getCrossPointAtRect : function(p1, aim, W , H, left, top){//求射线p1-aim在rect边界上的交点,其中aim在rect范围内,p1则不一定(交点在aim这边的延长线上)
+        
+        var x,y, borderX;
+        var r = (aim.x - p1.x) / (aim.y - p1.y);//根据相似三角形原理先求出这个比值	
+        var getX = function(y){
+            return  r * (y-p1.y) + p1.x;
+        }
+        var getY = function(x){
+            return  1/r * (x-p1.x) + p1.y;
+        }
+        if(aim.x >= p1.x){ 
+            borderX = W+left;
+        }else{
+            borderX = left; 
+        }
+        x = borderX;
+        y = getY(x);
+        if(y < top || y > top+H){
+            if(y < top){
+                y = top; 
+            }else{
+                y = top+H; 
+            }
+            x = getX(y)			
+        }
+        return new THREE.Vector2(x, y);  
+    },
+    getDirFromUV : function(uv){ //获取dir   反向计算 - -  二维转三维比较麻烦 
+        var dirB; //所求 单位向量
+        
+        
+        
+        var y = Math.cos(uv.y * Math.PI);              //uv中纵向可以直接确定y,  根据上面getUVfromDir的反向计算 
+                            // 故 uv.y * Math.PI  就是到垂直线(向上)的夹角
+        var angle = 2 * Math.PI * uv.x - Math.PI       //x/z代表的是角度
+
+        var axisX, axisZ; //axis为1代表是正,-1是负数
+        if (-Math.PI <= angle && angle < 0) {
+            axisX = -1 //下半圆
+        } else {
+            axisX = 1 //上半圆
+        }
+        if (-Math.PI / 2 <= angle && angle < Math.PI / 2) {
+            axisZ = 1 //右半圆
+        } else {
+            axisZ = -1 //左半圆
+        }
+
+
+
+        var XDivideZ = Math.tan(angle);
+        var z = Math.sqrt((1 - y * y) / (1 + XDivideZ * XDivideZ));
+        var x = XDivideZ * z
+
+
+        if (z * axisZ < 0) { //异号
+            z *= -1;
+            x *= -1;
+            if (x * axisX < 0) {
+          //      console.log("wrong!!!!!??????????")
+            }
+        }
+
+        x *= -1 //计算完成后这里不能漏掉 *= -1	
+        dirB = this.convertVector.YupToZup(new THREE.Vector3(x, y, z))   
+
+        //理想状态下x和z和anotherDir相同
+        return dirB
+
+
+    },
+    
+    getUVfromDir : function(dir) { //获取UV  同shader里的计算 
+        var dir = this.convertVector.ZupToYup(dir)
+        dir.x *= -1; //计算前这里不能漏掉 *= -1  见shader
+        var tx = Math.atan2(dir.x, dir.z) / (Math.PI * 2.0) + 0.5; //atan2(y,x) 返回从 X 轴正向逆时针旋转到点 (x,y) 时经过的角度。区间是-PI 到 PI 之间的值
+        var ty = Math.acos(dir.y) / Math.PI;
+        return new THREE.Vector2(tx,  ty)  
+
+        //理想状态下tx相同
+    },
+     
+    getDirByLonLat : function(lon,lat){
+        var dir = new THREE.Vector3
+        var phi = THREE.Math.degToRad(90 - lat);
+        var theta = THREE.Math.degToRad(lon);
+        dir.x = Math.sin(phi) * Math.cos(theta);
+        dir.y = Math.cos(phi);
+        dir.z = Math.sin(phi) * Math.sin(theta);   
+        return dir
+    } //0,0 => (1,0,0)     270=>(0,0,-1)
+    ,    
+    projectPointAtPlane:function(o={}){//获取一个点在一个面上的投影 {facePoints:[a,b,c], point:}
+        var plane = new THREE.Plane().setFromCoplanarPoints(...o.facePoints)
+        return plane.projectPoint(o.point, new THREE.Vector3() )
+    }
+    ,
+    
+    getPolygonsMixedRings:function( polygons,  onlyGetOutRing){//{points:[vector2,...],holes:[[],[]]} 
+         
+         
+        let points = [] 
+        let lines = []
+        let i = 0 
+        
+        polygons.forEach(e=> points.push(...e.map(a=>new THREE.Vector2().copy(a) )) )   
+        polygons.forEach((ps,j)=>{
+            let length = ps.length;
+            let index = 0;
+            while(index<length){
+                lines.push({p1:index+i,p2:(index+1)%length+i});
+                index ++;
+            }
+            i+=length
+        })
+    
+             
+        points.forEach((p,j)=>{p.id = j}) 
+            
+        let rings = searchRings({
+            points,
+            lines,
+            onlyGetOutRing    
+        })
+        //console.log(rings)
+        
+        rings = rings.filter(e=>e.closetParent == void 0)// 子环不加,被外环包含了
+        
+        return rings 
+        
+    },
+
+    getQuaFromPosAim( position, target ){ 
+        let matrix = (new THREE.Matrix4).lookAt(position, target, new THREE.Vector3(0,0,1))
+        return (new THREE.Quaternion).setFromRotationMatrix(matrix)
+        
+    },
+    
+    
+    getBoundByPoints(points, minSize){ 
+        var bound = new THREE.Box3
+        points.forEach(point=>{
+            bound.expandByPoint(point)
+        }) 
+        let center = bound.getCenter(new THREE.Vector3)
+        if(minSize){
+            let minBound = (new THREE.Box3()).setFromCenterAndSize(center, minSize)
+            bound.union(minBound)
+        }
+        return {
+            bounding:bound,
+            size: bound.getSize(new THREE.Vector3),
+            center,
+        }
+    },
+
+
+
+};
+
+ 
+Potree.math = math
+
+
+export default math