文章目录

  • 选点绘线
  • 绘制标签
    • 1.使用 TextGeometry 创建标签文字
    • 2. 使用 CSS2DObject 创建标签
  • 动态绘制点、线和标签
  • 绘制辅助线
  • 撤销操作

  测距功能,也就是选择两点,计算它们的距离,实现效果大致如下:

  上图中主要涉及几个操作:

  • 点击鼠标左键选点,点击鼠标右键停止选点,若选择点数超过两点,则两点绘制一条线段,两点中心添加一个距离标签
  • 动态绘制点和线段
  • 动态绘制距离标签
  • 确定两点后添加 xyz 辅助线
  • 按下 ESC 键撤销上一步操作

选点绘线

  首先,我们需要通过鼠标在三维空间中选点,但是我们的屏幕是二维的,还有一维不知道,因此没办法直接凭空选点,因此目前的选点都是基于某个物体来的,即在物体上选点。

  那么要如何获取鼠标点击的位置呢,首先需要知道标准设备坐标转空间坐标,然后使用 Raycaster 根据这个点从相机位置发出一条射线,并检测与这条射线相交的物体,即可获得鼠标点击的位置:

const mouse = new THREE.Vector2();// 标准设备坐标转空间坐标
mouse.x = (e.clientX / width) * 2 - 1;
mouse.y = -(e.clientY / height) * 2 + 1;let raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);let intersects = raycaster.intersectObjects(scene.children);
if(intersects.length > 0) {return intersects[0].point;   // point 即点击坐标
}

  选了点后,需要在该点位置绘制一个圆点表示,这边使用了SphereGeometry 来绘制圆点:

createPoint = (pos, config={color:0x009bea, size:0.3}) => {let mat = new THREE.MeshBasicMaterial({color: config.color || 0x009bea});let sphereGeometry = new THREE.SphereGeometry(config.size || 0.3, 32, 32);let sphere = new THREE.Mesh(sphereGeometry, mat);sphere.position.set(pos.x, pos.y, pos.z);return sphere;
};

  两点之间绘制一条线段:

createLine = (p1, p2, config={color:0x009bea}) => {let lineGeometry = new THREE.Geometry();let lineMaterial = new THREE.LineBasicMaterial({ color: config.color });lineGeometry.vertices.push(new THREE.Vector3().copy(p1), new THREE.Vector3().copy(p2));let line = new THREE.Line(lineGeometry, lineMaterial);return line;
};

绘制标签

  绘制标签也就是在屏幕上绘制文字,有两种方法可以生成文字,一种是使用 TextBufferGeometry 来创建 3d 文字,另一种是使用 CSS2DRenderer 和 CSS2DObject 来自定义 2d 标签。
  作为标签,肯定是选择后者更为合适,前者就是单纯的 3d 文字,而后者因为是使用 div 元素创建的,因此可以更自由的定义标签的样式、且标签的大小和朝向会跟随相机,并且不会被模型遮挡。

  不过不同场景实现方案的选择也会不同,因此这边都记录一下。

1.使用 TextGeometry 创建标签文字

  label 的创建需要先使用 FontLoader 来制指定字体,再使用 TextBufferGeometry 来生成标签,下面是一些关键代码:

const loader = new THREE.FontLoader();
loader.load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/fonts/gentilis_regular.typeface.json', function(font) {this.font = font;
}.bind(this));createLabel = (name, location) => {const textGeo = new THREE.TextBufferGeometry(name, {font: this.font,size: 0.8,height: 0.1,curveSegments: 1});const textMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const textMesh = new THREE.Mesh(textGeo, textMaterial);textMesh.position.copy(location);// 根据自己的坐标系设置进行旋转将文字水平显示textMesh.rotation.x = 0.5 * Math.PI;textMesh.rotation.y = Math.PI;return textMesh;
};

2. 使用 CSS2DObject 创建标签

  首先,要让 CSS2DObject 创建的标签起效果,需要使用 CSS2DRenderer 来渲染,因此场景中的 renderer 会有两个,一个 WebGLRenderer 用来渲染模型,一个 CSS2DRenderer 用来渲染标签,需要注意的是要使用 css2drenderer 来创建 controls

import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';renderer = new WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( canvas.width, canvas.height );
canvas.appendChild(renderer.domElement);labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(canvas.width, canvas.height);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
canvas.appendChild(labelRenderer.domElement);controls = new OrbitControls( camera, labelRenderer.domElement );

  然后就可以使用 CSS2DObject 来生成标签了:

createLabel = (text, pos) => {const div = document.createElement('div');div.className = 'label';div.textContent = text;const divLabel = new CSS2DObject(div);divLabel.position.set(pos.x, pos.y, pos.z);return divLabel;
}

  可以根据设置的 className 来设置标签样式:

.label {margin-top: -1em;border: 10px;border-radius: 8px;width: 85px;text-align: center;cursor: pointer;color: rgb(0, 155, 234);line-height: 1.2;background-color: rgb(244, 244, 244);box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.25);
}.label:hover {box-shadow: 0px 0px 20px rgba(0, 155, 234, 0.8);
}

动态绘制点、线和标签

  在选点过程中(鼠标移动过程中),若鼠标在模型之上,鼠标位置处会显示一个圆点,表示此处可点选,在选完一个点后,鼠标移动过程中鼠标位置和选点之间会有连线,且线段的距离会动态变化。

  这些动态效果主要都是通过自定义的名称来查找修改的,即使用了 scene.getObejctByName() 来找到这些动态的物体,然后再通过修改其位置和文字信息来产生动态效果(原本是通过 scene.addscene.remove 来实现,后面做了优化,通过 geometry.verticesposition 来实现),以动态的线段为例:

let activeLine = scene.getObjectByName('active-line');
if (activeLine) {activeLine.geometry.vertices[1].set(obj.point.x, obj.point.y, obj.point.z);activeLine.geometry.verticesNeedUpdate = true;
} else {activeLine = this.createLine(p1.position, obj.point);activeLine.name = 'active-line';scene.add(activeLine);
}

  动态点:

let obj = this.getIntersects(mouse);let activePoint = scene.getObjectByName('active-point');// 若点击位置不为空且场景中不存在活动的点,那么创建活动的点
if (obj && !activePoint) {activePoint = this.createPoint(obj.point);activePoint.material.transparent = true;activePoint.material.opacity = 0.6;activePoint.name = 'active-point';scene.add(activePoint);
} else if (obj) {   // 否则若点击位置不为空且存在活动的点,更新这个点的位置activePoint.position.set(obj.point.x, obj.point.y, obj.point.z);
} else if (activePoint) {    // 否则点击位置为空且存在活动的点,清除活动的点scene.remove(activePoint);
}

  动态标签:

let dis = p1.position.distanceTo(obj.point).toFixed(2);
let pos = new THREE.Vector3().copy(p1.position);
pos.add(obj.point);
pos.multiplyScalar(0.5);let label = scene.getObjectByName('active-label');
if (label) {label.element.textContent = '~' + dis;label.position.set(pos.x, pos.y, pos.z);
} else {label = this.createLabel('~' + dis, pos);label.name = 'active-label';scene.add(label);
}

绘制辅助线

  有了前面绘制线段的方法,绘制辅助线就比较简单了,只要再确定两个中间点,加上线段起始点和终点四个点就能绘制三条线。

  设线段起始点是 start,终点是 p ,每两点间变化一个坐标轴的值就能达到我们的目的。我们从起始点开始,首先,绘制 y 轴的线段,那么就使用起始点 start 和新的点py : Vector3(start.x, p.y, start.z) 绘制线段,再使用 py 和新的中间点 pz: Vector3(start.x, p.y, p.z) 来绘制 z 轴线段,最后使用 pz 和终点 p 来绘制 x 轴线段。

let py = new THREE.Vector3(start.position.x, p.position.y, start.position.z);
let pz = new THREE.Vector3(start.position.x, p.position.y, p.position.z);let liney = this.createLine(start.position, py, {color: 0xff0000});
let linez = this.createLine(py, pz, {color: 0x00ff00});
let linex = this.createLine(pz, p.position, {color: 0x0000ff});

撤销操作

  使用 keydown 对键盘事件进行监听,当 event.key === Escape 时我们就撤销前面绘制的点和线和距离 label (如果有这些对象的话),也就是我们之前要使用一个数据结构,比如数组来保存我们添加到场景中的这些对象,然后根据添加顺序再一个个移除即可,因为主要就使用了 scene.remove 和顺序逻辑判断,这边就不贴代码了。

ThreeJS 测距功能相关推荐

  1. (jQuery,SVG)使用jQuery和svg仿QQ地图测距功能(抛砖引玉)

    不久前看到了QQ地图的测距功能,觉得挺好玩的,就思考模仿一下.本来想通过canvas来画图,可惜对canvas不是很熟悉,就准备用svg了,其实我对svg也不是很熟,纯粹是学习. 代码只是简单的生成图 ...

  2. 高德地图JSAPI测距功能优化

    文章目录 前言 测距实现思路 使用测距插件 开启测距 关闭测距 前言 高德提供了一个距离测量插件可直接使用,但是没有完全满足需求.在测距过程中只会显示新增节点到起始点的总长度,而不会在鼠标移动过程中显 ...

  3. 使用Google地图的测距功能

    使用google地图API的测距功能 首先需要引入对应的Google地图的库(drawing,geometry,spherical) 引入方法在 <script src="https: ...

  4. QML QtLocation地图应用学习-2:实现测距功能

    1.实现思路 参照百度or高德地图的测距功能,主要由两种元素组成,标记点和连线. 其中连线很好解决,Qt 提供了 MapPolyline 类型,可以用来绘制折线,并且提供了增删的便捷函数: 对于标记点 ...

  5. 百度地图 测距功能 DistanceTool 在不同浏览器下标注不一致的解决办法,打开新地图测距不生效的解决办法

    1. 在项目中用到百度地图的测距功能,在主页面的地图中用没问题,如果从主页的地图跳转到详情页的地图,在返回到主页,打开地图的测距功能,测距一直不显示.下面是解决办法 将测距功能的工具类函数下载到本地, ...

  6. 实现MapX的移屏测距功能(转)

    本文转载自hi.baidu.com/redpanda/blog/category/Mapx/index/1 实现MapX的移屏测距功能(转) 前一段时间想利用业余时间把MapX的一些功能写出来,特别是 ...

  7. 实现MapX的移屏测距功能

    前一段时间想利用业余时间把MapX的一些功能写出来,特别是移屏测距的功能. 刚开始想的是仿MapInfo的测距功能,打算使用符号工具.折线工具和平移功能实现. 后来经过验证那样的测距方案行不通,主要表 ...

  8. fmcw matlab仿真,基于SIMULINK的FMCW雷达测距功能仿真.pdf

    基于SIMULINK的FMCW雷达测距功能仿真.pdf 全国空气动力测控技术交流会论文集 基于SlMUUNK的FMCW雷达测距功能仿真 也esimulation function ofFMCW dis ...

  9. 多路测量实时同步工作原理_TOF测距功能的原理及使用方法

    摘要:该方法属于双向测距技术,利用数据信号在一对收发机之间往返的飞行时间来测量两点间的距离.将发射端发出数据信号和接收到接收端应答信号的时间间隔记为Tt,接收端收到发射端的数据信号和发出应答信号的时间 ...

最新文章

  1. UILayer的一些属性
  2. php 读取stdclass,json - 在php中读取stdclass的问题?
  3. 实战DeviceIoControl 之中的一个:通过API訪问设备驱动程序
  4. 信息系统项目管理师备考经验
  5. ZZULIOJ 1067:有问题的里程表
  6. 关于Java List 的remove(index)方法
  7. 一行代码能做什么? 看到最后一个我终于忍不住...
  8. windows 安装PyHook3
  9. 学校校车运营各项安全管理制度_学校校车安全管理制度.doc
  10. Leetcode刷题笔记——剑指offer II (六)【图】
  11. python的成语接龙程序代码_基于Python经典版成语接龙逻辑实现
  12. 【安卓开发】DrawerLayout+ToolBar报错:DrawerLayout must be measured with MeasureSpec.EXACTLY.
  13. 地震桌面演练计算机模拟,地震应急处置演练平台环境编辑器及导演组客户端的设计与实现...
  14. redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refu
  15. 解决ceph 100.000% pgs not active
  16. 2022-1-13 c++STL——算法、迭代器的分类
  17. Google Chrome 插件开发: 无法建立连接, 接收端不存在. Could not establish connection. Receiving end does not exist
  18. EMV密钥管理体系与商业银行卡
  19. Navicat for Oracle 11.2.11 64位 简体中文破解版
  20. 高等学校计算机类专业教学质量国家标准,计算机学院召开教学质量国家标准及专业建设研讨会...

热门文章

  1. C++ 对象的内存布局(上)
  2. 大数据时代的数据管理
  3. 1、为什么使用Long时,推荐多使用valueOf方法,少使用parseLong方法
  4. 怎样使一个Android应用不被杀死?
  5. 内存技术:入门和测试挑战
  6. 用了 Elasticsearch 后,查询起飞了!
  7. 系统架构设计:进程缓存和缓存服务,如何抉择?
  8. 某大厂程序员抱怨:“大厂镀金”是鬼话!从大厂裸辞后,面阿里、字节全都挂掉,连货拉拉都不要自己!...
  9. 疫情期间,为什么Snapchat比Pinterest更赚钱
  10. Dubbo 负载均衡的实现