import * as THREE from "../../../libs/three.js/build/three.module.js"; import math from './math.js'; import {Line2} from "../../../libs/three.js/lines/Line2.js"; import {LineGeometry} from "../../../libs/three.js/lines/LineGeometry.js"; import {LineMaterial} from "../../../libs/three.js/lines/LineMaterial.js"; //画线等函数--by 许钟文 import {Features} from "../../Features.js"; import Common from './Common.js' var defaultColor = new THREE.Color(1,1,1);//config.applicationName == "zhiHouse" ? Colors.zhiBlue : Colors.lightGreen; function dealPosArr(points){//识别是否每个点都不一样,把连续点变为不连续的片段连接 let add = (points)=>{ let points2 = [] , len = points.length for(let i=0;ipoints2.push(...add(ps))) return points2 }else if(points.length > 2 && !points[2].equals(points[1])){ return add(points) }else return points } let center = new THREE.Vector3 function extractPos(posArr){//尽量让所有点都靠近原点 //console.log('extractPos', posArr.map(e=>e.clone())) let bound = new THREE.Box3 posArr.forEach(e=>bound.expandByPoint(e)) bound.getCenter(center) posArr.forEach(e=>e.sub(center)) //console.log('extractPos2 ', posArr.map(e=>e.clone()), center.clone()) return center.clone() } var LineDraw = { createLine: function (posArr, o={}) { //多段普通线 (第二个点和第三个点之间是没有线段的, 所以不用在意线段顺序) var mat if(o.mat){ mat = o.mat }else{ let prop = Object.assign({ lineWidth: o.lineWidth || 1, //windows无效。 似乎mac/ios上粗细有效 ? color: o.color || defaultColor },o) if(o.deshed ){ prop.dashSize = o.dashSize || 0.1, prop.gapSize = o.gapSize || 0.1 } mat = new THREE[o.deshed ? "LineDashedMaterial" : "LineBasicMaterial"](prop) } var line = new THREE.LineSegments(new THREE.BufferGeometry, mat); line.renderOrder = o.renderOrder || Potree.config.renderOrders.line line.avoidBigNumber = o.avoidBigNumber this.moveLine(line, posArr) return line; }, moveLine: function (line, posArr) { posArr = Common.CloneObject(posArr) //if(posArr.length == 0)return if(!line.uncontinuous || posArr[0] && posArr[0] instanceof Array)posArr = dealPosArr(posArr) let position = [] posArr.forEach(e=>position.push(e.x,e.y,e.z)) if(line.avoidBigNumber){ let moveVec = extractPos(posArr) line.position.copy(moveVec)//无视原本的position! } line.geometry.setAttribute('position', new THREE.Float32BufferAttribute(/* new Float32Array( */position/* ) */, 3)); line.geometry.attributes.position.needsUpdate = true; line.geometry.computeBoundingSphere(); if(line.material instanceof THREE.LineDashedMaterial){ line.computeLineDistances() //line.geometry.attributes.lineDistance.needsUpdate = true; line.geometry.verticesNeedUpdate = true; //没用 } } , createFatLineMat : function(o){ var supportExtDepth = !!Features.EXT_DEPTH.isSupported() let params = $.extend({}, { //默认 lineWidth : 5, color:0xffffff, transparent : true, depthWrite:false, depthTest:false, dashSize : 0.1, gapSize:0.1, }, o, { //修正覆盖: dashed: o.dashWithDepth ? supportExtDepth && !!o.dashed : !!o.dashed , dashWithDepth:!!o.dashWithDepth,//只在被遮住的部分显示虚线 useDepth: !!o.useDepth, supportExtDepth, }) var mat = new LineMaterial(params) //if(o.dashed)(mat.defines.USE_DASH = "") return mat; }, /* 创建可以改变粗细的线。 */ createFatLine : function(posArr, o){ var geometry = new LineGeometry(); geometry.setColors( o.color || [1,1,1]); var matLine = o.mat || this.createFatLineMat(o); var line = new Line2( geometry, matLine ); //line.computeLineDistances(); line.uncontinuous = o.uncontinuous //线不连续,由线段组成 line.avoidBigNumber = o.avoidBigNumber line.scale.set( 1, 1, 1 ); line.renderOrder = Potree.config.renderOrders.line; this.moveFatLine(line, posArr) return line; }, moveFatLine: function(line, posArr ){ posArr = Common.CloneObject(posArr) var geometry = line.geometry; var positions = []; if(!line.uncontinuous || posArr[0] && posArr[0] instanceof Array) posArr = dealPosArr(posArr) if(line.avoidBigNumber){ let moveVec = extractPos(posArr) line.position.copy(moveVec)//无视原本的position! } posArr.forEach(e=>{positions.push(...e.toArray())}) if(!geometry){ geometry = line.geometry = new LineGeometry(); } if(geometry.attributes.instanceEnd && geometry.attributes.instanceEnd.data.array.length != positions.length){//positions个数改变会有部分显示不出来,所以重建 geometry.dispose(); geometry = new LineGeometry(); line.geometry = geometry } geometry.setPositions( positions ) if(line.material.defines.USE_DASH != void 0){ //line.geometry.verticesNeedUpdate = true; //没用 line.geometry.computeBoundingSphere(); //for raycaster line.computeLineDistances(); } }, updateLine: function(line, posArr){ if(line instanceof Line2){ LineDraw.moveFatLine(line,posArr) }else{ LineDraw.moveLine(line,posArr) } }, /* 为line创建用于检测鼠标的透明mesh,实际是个1-2段圆台。 由于近大远小的原因,假设没有透视畸变、创建的是等粗的圆柱的话, 所看到的线上每个位置的粗细应该和距离成反比。所以将圆柱改为根据距离线性渐变其截面半径的圆台,在最近点(相机到线的垂足)最细。如果最近点在线段上,则分成两段圆台,否则一段。 */ createBoldLine:function(points, o){ o = o || {} var cylinder = o && o.cylinder; var CD = points[1].clone().sub(points[0]); var rotate = function(){//根据端点旋转好模型 cylinder.lastVector = CD;//记录本次的端点向量 var AB = new THREE.Vector3(0,-1,0) var axisVec = AB.clone().cross(CD).normalize(); //得到垂直于它们的向量,也就是旋转轴 var rotationAngle = AB.angleTo(CD); cylinder.quaternion.setFromAxisAngle( axisVec, rotationAngle ) } if(o && o.type == "init"){ cylinder = new THREE.Mesh() cylinder.material = o.mat if(CD.length() == 0)return cylinder; rotate() } if(CD.length() == 0)return cylinder; if(o.type != "update"){ var CDcenter = points[0].clone().add(points[1]).multiplyScalar(.5); cylinder.position.copy(CDcenter); if(!cylinder.lastVector || o.type == "moveAndRotate")rotate() else if(cylinder.lastVector && CD.angleTo(cylinder.lastVector)>0) rotate()//线方向改了or线反向了 重新旋转一下模型 if(config.isEdit && !objects.mainDesign.editing )return cylinder;//节省初始加载时间? } //为了保证线段任何地方的可检测点击范围看起来一样大,更新圆台的结构(但是在镜头边缘会比中心看起来大) var height = points[0].distanceTo(points[1]); var standPos = o && o.standPos || objects.player.position; var k = config.isMobile ? 20 : 40; var dis1 = points[0].distanceTo(standPos); var dis2 = points[1].distanceTo(standPos); var foot = math.getFootPoint(standPos, points[0], points[1]);//垂足 if(o.constantBold || objects.player.mode != "panorama"){ var width = 0.1//0.08; var pts = [new THREE.Vector2(width ,height/2),new THREE.Vector2(width ,-height/2)] }else if(foot.clone().sub(points[0]).dot( foot.clone().sub(points[1]) ) > 0){//foot不在线段上 var pts = [new THREE.Vector2(dis1 / k,height/2),new THREE.Vector2(dis2 / k,-height/2)] }else{//在线段上的话,要在垂足这加一个节点,因它距离站位最近,而两端较远 var dis3 = foot.distanceTo(standPos); var len = foot.distanceTo(points[0]) var pts = [new THREE.Vector2(dis1 / k,height/2), new THREE.Vector2(dis3 / k,height/2-len), new THREE.Vector2(dis2 / k,-height/2)] } cylinder.geometry && cylinder.geometry.dispose();//若不删除会占用内存 cylinder.geometry = new THREE.LatheBufferGeometry( pts, 4/* Math.min(dis1,dis2)<10?4:3 */ ) cylinder.renderOrder = 2; return cylinder; }, updateBoldLine:function(cylinder, points, type, standPos, constantBold){ this.createBoldLine(points,{type:type, cylinder : cylinder, standPos:standPos, constantBold}) //type:move:平移 会改长短 , type:update根据距离和角度更新 不改长短 }, } var MeshDraw = { getShape: function(shapes, holes){ //不一定闭合 暂时所有shapes共享holes。如果要单独的话, shapes改为[{shape:[],holes:[]},{}]的形式 if(shapes[0] && !(shapes[0] instanceof Array) ){//仅是一个shape的点 shapes = [shapes] } let holesArr = [] if(holes){//挖空 holes.forEach((points)=>{ var holePath = new THREE.Path() holePath.moveTo( points[0].x, points[0].y ) for(var i=1,len=points.length; i{ var shape = new THREE.Shape(); shape.moveTo( points[0].x, points[0].y ); for(var i=1,len=points.length; i{ shape.moveTo( points[0].x, points[0].y ); for(var i=1,len=points.length; i{ var holePath = new THREE.Path() holePath.moveTo( points[0].x, points[0].y ) for(var i=1,len=points.length; ie>= n / (oldCount-1) ) ) } } */ //console.log('cost dur:', performance.now() - startTime ) let nextUtoTIndex = 1 for(let i=0;i1){// 和上一个加入点的vec之间的夹角如果过大就加入 let reachNextUToT //找出新点中对应原先控制点的index,这些点必须加入拐点,否则会出现控制点偏移path(当它所在部分接近直线时) while(UtoTMapArr[i] > nextUtoTIndex / (oldCount-1)){//可能多个控制点对应一个点,当控制点很近时 reachNextUToT = true nextUtoTIndex ++ } if(/* pointIndexs.includes(i) || */ reachNextUToT || curVec.angleTo(lastVec) > minRad){//最小角度 (注意原始点不能太稀疏) newPoints.push(point) UtoTMapArr && newUtoTMapArr.push(UtoTMapArr[i]) lastVec = curVec } } } } return {newUtoTMapArr, newPoints} }, getExtrudeGeo: function(shapes, holes, options={openEnded:false, shapeDontClose:false}){//获得挤出棱柱,可以选择传递height,或者extrudePath var shape = this.getShape(shapes, holes) //points是横截面 [vector2,...] if(options.extrudePath ){// 路径 :[vector3,...] var length = options.extrudePath.reduce((total, currentValue, currentIndex, arr)=>{ if(currentIndex == 0)return 0 return total + currentValue.distanceTo(arr[currentIndex-1]); },0) //options.extrudePath = new THREE.CatmullRomCurve3(options.extrudePath) if(options.extrudePath.length == 2){ options.tension = 0 ;//否则一端扭曲 options.steps = 1 } {//去掉重复的点 let path = [] const minDis = options.dontSmooth ? 0 : 0.2 //CatmullRomCurve3 经常扭曲,如果两个点靠得很近可能会扭曲,这里去除靠的太近的点。但去除后依旧会出现一定扭曲. options.extrudePath.forEach((p,i)=>{ if(i==0 || i== options.extrudePath.length-1)return path.push(p) //首尾直接加入 let last = path[path.length-1]//和上一个比 let dis = last.distanceTo(p) if(dis <= minDis){ console.log(`第${i}个点(${p.toArray()})因为和上一个数据(${last.toArray()})太接近(dis:${dis})所以删除`) }else if(i == options.extrudePath.length - 2){//因为最后一个必定加入,所以倒数第二个还也不能太靠近最后一个 last = options.extrudePath[options.extrudePath.length-1] //和下一个(最后一个比) if(dis <= minDis){ console.log(`第${i}个点(${p.toArray()})因为和下一个数据(${last.toArray()})太接近(dis:${dis})所以删除`) }else{ path.push(p) } }else{ path.push(p) } }) options.extrudePath = path } if(!options.dontSmooth){ //平滑连续的曲线(但经常会有扭曲的问题,tension:0能缓解, 另外shape和path都最好在原点附近,也就是点需减去bound.min ) options.extrudePath = new THREE.CatmullRomCurve3(options.extrudePath, options.closed , 'catmullrom' /* 'centripetal' */ , options.tension)//tension:张力, 越大弯曲越大。 随着长度增长,该值需要减小,否则会扭曲 if(options.lessPoints !== false ){//曲线但压缩直线部分点数量 options.extrudePath.UtoTMapArr = [] //用于存储 getSpacedPoints得到的点对应points的百分比对应 let count = Math.max(2, Math.round(length* (options.lessSpace || 200) )) //为了防止有大拐弯才设置这么高 let points = options.extrudePath.getSpacedPoints(count-1) //拆分为更密集的点 let result = this.lessCurvePoints(points, options.extrudePath.points.length, options.minRad, options.extrudePath.UtoTMapArr ) //传UtoTMapArr的话点太多了卡住了 //options.extrudePath = points options.extrudePath = result.newPoints options.dontSmooth = true } } if(options.dontSmooth){ let curvePath = new THREE.CurvePath()//通用的曲线路径对象,它可以包含直线段和曲线段。在这里只做折线 curvePath.points = options.extrudePath//add for (let i = 0; i < options.extrudePath.length - 1; i++){ let curve3 = new THREE.LineCurve3(options.extrudePath[i], options.extrudePath[i + 1]);//添加线段 curvePath.add(curve3); } options.steps = options.extrudePath.length - 1 options.extrudePath = curvePath options.tension = 0 //已修改过three,原本会平分细分,现在dontSmooth时会直接按照控制点来分段 } } var extrudeSettings = $.extend(options,{ steps: options.steps != void 0 ? options.steps : ( options.extrudePath ? Math.round(length/(options.spaceDis || 0.2)) : 1), //分成几段 spaceDis每段长度 bevelEnabled: false, //不加的话,height为0时会有圆弧高度 //openEnded默认false }) var geometry = new THREE.ExtrudeBufferGeometry( shape, extrudeSettings ); //修改了three.js文件, buildLidFaces处,创建顶底面加了选项,可以选择开口。 return geometry; /* tension = 0:曲线会变成一条直线,没有弯曲。 tension = 0.5:曲线会经过所有控制点,并保持自然的弯曲。 tension > 0.5:曲线会更平滑,远离控制点之间的路径。 tension < 0.5:曲线会更贴近控制点之间的路径,弯曲更明显。 */ }, getUnPosPlaneGeo : function(){//获取还没有赋值位置的plane geometry var e = new Uint16Array([0, 1, 2, 0, 2, 3]) // , t = new Float32Array([-.5, -.5, 0, .5, -.5, 0, .5, .5, 0, -.5, .5, 0]) , i = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]) , g = new THREE.BufferGeometry; g.setIndex(new THREE.BufferAttribute(e, 1)), //g.addAttribute("position", new n.BufferAttribute(t, 3)), g.setAttribute("uv", new THREE.BufferAttribute(i, 2)) return function(){ return g } }(), getPlaneGeo : function(A,B,C,D){ var geo = this.getUnPosPlaneGeo().clone(); var pos = [ A.x, A.y, A.z, B.x, B.y, B.z, C.x, C.y, C.z, D.x, D.y, D.z ] //geo.addAttribute("position", new THREE.BufferAttribute(pos, 3)) geo.setAttribute('position', new THREE.Float32BufferAttribute(pos, 3)); geo.computeVertexNormals() geo.computeBoundingSphere() //for raycaster return geo; }, drawPlane : function(A,B,C,D, material){ var wall = new THREE.Mesh(this.getPlaneGeo(A,B,C,D), material); return wall; }, movePlane: function(mesh, A,B,C,D){ var pos = new Float32Array([ A.x, A.y, A.z, B.x, B.y, B.z, C.x, C.y, C.z, D.x, D.y, D.z ]) mesh.geometry.addAttribute("position", new THREE.BufferAttribute(pos, 3)) mesh.geometry.computeBoundingSphere()//for checkIntersect } , createGeometry:function(posArr, faceArr, uvArr, normalArr ){//创建复杂mesh. faceArr:[[0,1,2],[0,2,3]] let geo = new THREE.BufferGeometry; let positions = []; posArr.forEach(p=>positions.push(p.x,p.y,p.z)); geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); if(faceArr){ let indice = [] faceArr.forEach(f=>indice.push(...f)); geo.setIndex(indice) // auto set Uint16BufferAttribute or Uint32BufferAttribute } if(uvArr){ let uvs = [] uvArr.forEach(uv=>uvs.push(uv.x,uv.y)); geo.setAttribute("uv", new THREE.Float32BufferAttribute(uvs, 2)) } if(normalArr){ let normals = [] normalArr.forEach(n=>normals.push(n.x,n.y,n.z)); geo.setAttribute("normal", new THREE.Float32BufferAttribute(normals, 3)) } /* geo.computeVertexNormals() geo.computeBoundingSphere() //for raycaster */ return geo }, updateGeometry:function(geo, posArr, faceArr, uvArr, normalArr ){//创建复杂mesh. faceArr:[[0,1,2],[0,2,3]] let positions = []; posArr.forEach(p=>positions.push(p.x,p.y,p.z)); geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); geo.attributes.position.needsUpdate = true; if(faceArr){ let indice = [] faceArr.forEach(f=>indice.push(...f)); geo.setIndex(indice) // auto set Uint16BufferAttribute or Uint32BufferAttribute } if(uvArr){ let uvs = [] uvArr.forEach(uv=>uvs.push(uv.x,uv.y)); geo.setAttribute("uv", new THREE.Float32BufferAttribute(uvs, 2)) } if(normalArr){ let normals = [] normalArr.forEach(n=>normals.push(n.x,n.y,n.z)); geo.setAttribute("normal", new THREE.Float32BufferAttribute(normals, 3)) } /* geo.computeVertexNormals() */ geo.computeBoundingSphere() //for raycaster and visi return geo } } export {LineDraw, MeshDraw} ;