import * as THREE from '../lib/three.module.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 = 0.5 * $('#player').width(), o = 0.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 -0.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 } }, convertScreenPositionToNDC(pointer, mouse, width, height) { return (pointer = pointer || new THREE.Vector2()), (pointer.x = (mouse.x / width) * 2 - 1), (pointer.y = 2 * -(mouse.y / height) + 1), pointer }, convertNDCToScreenPosition(pointer, mouse, width, height) { return (mouse = mouse || new THREE.Vector2()), (mouse.x = Math.round(((pointer.x + 1) / 2) * width)), (mouse.y = Math.round((-(pointer.y - 1) / 2) * height)), mouse }, averageVectors(e, t) { var i = new THREE.Vector3() if (0 === e.length) return i for (var r = 0, o = 0; o < e.length; o++) { var a = t ? e[o][t] : e[o] i.add(a), r++ } return i.divideScalar(r) } } export default math