import * as THREE from "../../../libs/three.js/build/three.module.js"; import searchRings from "./searchRings.js"; import {ExtendView} from '../../viewer/ExtendView.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='z'){//带方向的角度 vector3 if(!vec1.isVector3){ vec1 = new THREE.Vector3(vec1.x, vec1.y, 0) vec2 = new THREE.Vector3(vec2.x, vec2.y, 0) } var angle = vec1.angleTo(vec2) var axis_ = vec1.clone().cross(vec2); if(typeof axis == 'string'){ if(axis_[axis] < 0){ angle *= -1 } }else{//vector3 if(axis_.dot(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 if(typeof e == 'number'){ return f(e, t) }else{ return e } }, 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 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 }, getVolume:function(faceArr){//求三角多面体的体积。和求面积同理都用鞋带计算法 https://blog.csdn.net/weixin_43414513/article/details/123758897 //问题:怎么确定方向 //每个三角形的顺序必须是右手螺旋法则指向物体外(外向里看为逆时针),分别为ABC,则每个三角形和原点O构成的三棱锥体积为 (OA cross OB) dot(OC) / 6 . 结果一般朝向原点的为负,反之为正 }, 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()) } */ getQuaByAim: function (aim, center=new THREE.Vector3) { let forward = new THREE.Vector3(0, 1, 0) let qua1 = new THREE.Quaternion().setFromUnitVectors(forward, aim.clone().sub(center).normalize()) /* var _ = (new THREE.Matrix4).lookAt(center,aim, new THREE.Vector3(0,1,0)); let qua2 = (new THREE.Quaternion).setFromRotationMatrix(_); let rot1 = new THREE.Euler().setFromQuaternion(qua1) let rot2 = new THREE.Euler().setFromQuaternion(qua2) //奇怪,qua2怎么都不对 console.log(rot1,rot2) */ return qua1 }, getAimByQua: function (quaternion, center=new THREE.Vector3) { return new THREE.Vector3(0, 0, -1).applyQuaternion(quaternion).add(center) }, 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{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) */ let view = new ExtendView() view.direction = new THREE.Vector3().subVectors(target,position) return view.quaternion }, 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, } }, /* linearClamp(value, x1,x2, y1, y2){//x为bound.min, bound.max value = THREE.Math.clamp(value, x1,x2) return y1 + ( y2 - y1) * (value - x1) / (x2 - x1) } */ linearClamp(value, xArr , yArr){ //xArr需要按顺序从小到大,yArr对应xArr中的值 let len = xArr.length if(value <= xArr[0]) return yArr[0] if(value >= xArr[len - 1]) return yArr[len - 1] let i = 0 while(++i < len ){ if(value < xArr[i]){ let x1 = xArr[i-1], x2 = xArr[i], y1 = yArr[i-1], y2 = yArr[i] value = y1 + ( y2 - y1) * (value - x1) / (x2 - x1) break } } return value } }; /* 如何将若干个点拟合出线段 // 假设你有一个点数组,每个点表示为一个包含x和y坐标的对象 const points = [ { x: 1, y: 2 }, { x: 2, y: 3 }, { x: 3, y: 4 }, { x: 4, y: 5 }, // ... 更多点 ]; // 定义用于计算最小二乘法参数的函数 function leastSquares( ) { let sumX = 0; let sumY = 0; let sumXY = 0; let sumX2 = 0; let n = points.length; for (const point of points) { sumX += point.x; sumY += point.y; sumXY += point.x * point.y; sumX2 += point.x * point.x; } const k = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); //不知道为何这样算,自己解不出来(使点到直线距离之和平方最小),但测了下似乎是准的 const b = (sumY - k * sumX) / n; return { k, b }; } // 使用上述函数计算拟合直线的参数 const { k, b } = leastSquares(points); // 现在你可以使用这些参数来表示拟合出的线段 console.log(`拟合线段的方程为: y = ${k}x + ${b}`); */ Potree.math = math export default math