分享 HT 实用技巧:实现指南针和 3D 魔方导航
前言
三维场景时常需要一个导航标识,用来确定场景所处的方位。
一般有两种表现形式:指南针、小方盒(方位魔方)。
参考一下百度百科中的 maya 界面,可以看到右上角有一个标识方位的小盒子,说的就是它:
Hightopo 的 HT for Web 产品可以很方便地构造轻量化的 3D 可视化场景,在 web 端 我们可以利用 HT 2D 引擎 和 3D 渲染引擎 来实现这个功能,搭建一个简易的类 maya 操作界面。
预览地址: https://www.hightopo.com/demo/compass-and-directionbox/
界面简介及效果预览
在这个界面里面我们用到了一个二维场景和两个三维场景,具体效果如下:
功能实现
先来描述一下页面布局:
指南针 通过在 ht.graph.GraphView 中给一个图元设置一个事先绘制好的图标来实现,只需把它放在图纸的左上角(即下图中的位置 1)即可。
方位魔方 通过在一个小场景 (ht.graph3d.Graph3dView)中放置一个魔方 obj 模型来实现,然后把这个小场景放置在图纸的右上角(即下图中的位置 2) 即可。
主三维场景(ht.graph3d.Graph3dView)作为背景放置在整个二维页面的下方(即下图中的位置 3)。
代码示例:
1 const g3d = new ht.graph3d.Graph3dView();
2 g3d.setOriginAxisVisible(true);
3 g3d.setGridVisible(true);
4 g3d.addToDOM();
5 const g2d = new ht.graph.GraphView();
6 g2d.deserialize('displays/test.json', json => {
7 g2d.addToDOM(g3d.getView());
8 });
位置关系:
指南针同步
先约定一下方位,我们将 Z 轴的负半轴的方向作为北方,Z 轴正半轴作为南方,X 轴的正半轴作为东方,X 轴的负半轴作为西方。
由于 指南针 的目的是用于指示鸟瞰图中的方位,所以与 Y 轴并没有什么关系,我们可以将整个计算过程放在二维空间中进行。
代码示例:
1 const eye = this.g3d.getEye();
2 const center = this.g3d.getCenter();
3 const v = new ht.Math.Vector2(eye[0], eye[2]);
4 const v2 = new ht.Math.Vector2(center[0], center[2]);
5 const angle = v.sub(v2).angle() - Math.PI / 2;
6 compass.setRotation(-angle);
7 compass.a('angle', angle);
8 compass.a('angle2', angle);
在这段代码中,我们用 eye (相机) 和 center (观测点)来构建两个二维向量 (ht.Math.Vector2),舍弃掉 Y 轴上的分量。
利用向量减法,求得由 center 指向 eye 的向量并存入变量 v 中,利用 angle() 方法可以获取到当前向量与 x 正半轴 (即正东方向)的夹角(弧度制),为什么要减去 Math.PI / 2 呢,因为我们计算求得的是与 x 轴的夹角,而指南针的正方向(北方)是对应着 z 轴的负半轴。
求得了旋转角度后,通过 setRotation() 方法我们可以设置 指南针 图元的旋转角度,为什么要取一个负值(- angle)?因为当视线逆时针转动的时候,坐标轴 和 指南针 相对于人眼是沿反方向运动的,也就是顺时针旋转。
利用 HT 2D 引擎提供的 数据绑定 的功能,轮盘图标 和 角度图标 的旋转角度可以通过给 compass 这个节点设置属性值来实时动态改变。
每一次视线发生改变都需要进行如上的计算和设置,我们可以通过给三维场景组件增加一个属性监听器来实现:
1 graph3dView.addPropertyChangeListener(e=>{
2 if(e.property === 'eye' || e.property === 'center'){
3 changeCompass();
4 //...
5 }
6 });
图例参考:
方位魔方同步
先约定一下方位,X 正半轴为右,负半轴为左; Y 正半轴为顶,负半轴为底;Z 正半轴为前,负半轴为后。
方位魔方不同于指南针,它用于呈现三维空间中的视线方位。
与此同时,它也是一个可以交互的方位操纵杆,可以方便快捷的将当前视角变为顶视图、侧视图等。
视线改变触发魔方变换
代码示例:
1 graph3dView.addPropertyChangeListener(e => {
2 if (e.property === 'eye') {
3 const newValue = e.newValue;
4 const vEye = new ht.Math.Vector3(newValue[0], newValue[1], newValue[2]).normalize();
5 graph3dView2.setEye([300 * vEye.x, 300 * vEye.y, 300 * vEye.z]);
6 }
7 });
在上述代码中我们通过监听主三维场景(graph3dView) 中 eye 属性的变化来动态改变小场景(graph3dView2) 中的 eye 的位置, 来达到联动的效果。
其中,e.newValue 会获取到场景视点改变后的值,我们用这个值构建一个三维向量(ht.Math.Vector3)并调用 normalize() 方法进行归一化,这样可以使得任何角度、位置求得的距离都保持一致。
将求得的分量乘以 300 的原因在于这个距离观测小方块不大不小刚合适,当然也可以根据需要改成别的值。
效果示例:
点击魔方改变场景视角
要想实现点击魔方来改变主场景中的视线,需要一个非常关键的信息,那就是鼠标究竟点击了小魔方的哪一个面。
在这里我们需要用到一个求交点的方法: graph3dView.intersectObject(event, data),该方法会返回一个对象,该对象用于描述点击的位置信息, 其中 world 属性用来表示点击位置的世界坐标。
代码示例:
1 graph3dView2.addInteractorListener(event => {
2 if (event.kind === 'clickData') {
3 const obj = graph3dView2.intersectObject(event.event, event.data);
4 if(obj) {
5 const world = obj.world;
6 //...
7 }
8 }
9 });
拿到了这个描述点击位置的 world 属性我们就可以比较轻松地算出点击了哪个面,因为我们的小方块是放置在原点处,并且它是规则的六面体,这两个关键信息决定了无论点击它的哪一个面,所点击的那个面它所对应的轴的分量的值一定会大于它在另外两个轴的分量,因此我们可以简单的判断三分量中哪个值较大就能确定视线更靠近哪个轴,然后通过判断分量的正负号来判断是在正半轴还是负半轴。
判断了出了点击的哪个面之后,只需要在两个三维场景中分别设置各自视点(eye) 的位置即可。
代码示例:
1 const world = obj.world;
2 const x = world.x;
3 const y = world.y;
4 const z = world.z;
5 if (Math.abs(x) - Math.abs(y) > 0 && Math.abs(x) - Math.abs(z) > 0) {
6 if (x > 0) {
7 graph3dView2.setEye([300, 0, 0]);
8 graph3dView.setEye([this._distance, 0, 0]);
9 graph3dView2.setCenter([0, 0, 0]);
10 this._g3d.setCenter([0, 0, 0]);
11 } else {
12 graph3dView2.setEye([-300, 0, 0]);
13 graph3dView.setEye([-this._distance, 0, 0]);
14 graph3dView2.setCenter([0, 0, 0]);
15 graph3dView.setCenter([0, 0, 0]);
16 }
17 } else if (Math.abs(y) - Math.abs(x) > 0 && Math.abs(y) - Math.abs(z) > 0) {
18 //...
19 }
其中,this._distance 是用来描述主场景中视线与原点的距离,可根据需要来调整,300 与之前的描述一致,是小场景中一个比较合适的视角位置,也可以根据需要调整。
最后我们还需要处理一下小方块点击变色的问题(这也不见得是个问题,视需求而定),可以在点击事件监听器的最后做如下设置:
1 const sm = graph3dView2.dm().getSelectionModel();
2 sm.setSelection(null);
点击魔方各个面效果演示:
总结
直观的方位指示在室内定位、GIS、车站、机场等诸多场景中有着广泛的应用,利用 HT 提供的二三维引擎可以轻松地实现。
web 3D 有无限的想象空间,有着非常丰富的数据呈现方式,更有着诸多让人眼前一亮的可视化效果,等着我们去将这些数据呈现方式在各个行业中落地,HT 在这方面做了大量的探索和尝试,例如这个好玩儿的太阳系监控系统:https://www.hightopo.com/demo/solar-system/
2019 我们也更新了数百个工业互联网 2D/3D 可视化案例集,在这里你能发现许多新奇的实例,也能发掘出不一样的工业互联网:《分享数百个 HT 工业互联网 2D 3D 可视化应用案例之 2019 篇》,更多行业应用实例可以参考官网案例链接:
https://www.hightopo.com/demos/index.html
分享 HT 实用技巧:实现指南针和 3D 魔方导航的更多相关文章
- ★10 个实用技巧,让Finder带你飞~
10 个实用技巧,让 Finder 带你飞 Finder 是 Mac 电脑的系统程序,有的功能类似 Windows 的资源管理器.它是我们打开 Mac 首先见到的「笑脸」,有了它,我们可以组织和使用 ...
- ★10 个实用技巧,让Finder带你飞~
10 个实用技巧,让 Finder 带你飞 Finder 是 Mac 电脑的系统程序,有的功能类似 Windows 的资源管理器.它是我们打开 Mac 首先见到的「笑脸」,有了它,我们可以组织和使用 ...
- 这5个实用技巧,教你设计出更好的App
三年前,谷歌公司分享了一项研究:用户平均会安装36个app在手机上,但每天都使用的只有9个.据统计,只有4%的app会被使用一年以上. 所以,能运用基本用户体验设计原则来设计出更好的app,对公司大有 ...
- JavaScript 实用技巧和写法建议
1.前言 从大学到现在,接触前端已经有几年了,感想方面,就是对于程序员而言,想要提高自己的技术水平和编写易于阅读和维护的代码,我觉得不能每天都是平庸的写代码,更要去推敲,去摸索和优化代码,总结当中的技 ...
- 使用wireshark抓包分析-抓包实用技巧
目录 使用wireshark抓包分析-抓包实用技巧 前言 自定义捕获条件 输入配置 输出配置 命令行抓包 抓取多个接口 抓包分析 批量分析 合并包 结论 参考文献 使用wireshark抓包分析-抓包 ...
- VUE基础实用技巧
Vue以前听说过,有了解过一点.当时还在热衷于原生JavaScript去写一些方法的封装,不是为啥,就感觉这样很帅,后面多多少少接触了一些JQuery的用法,到现在为止,JavaScript原生封装的 ...
- 初学者学习JavaScript的实用技巧!
Javascript是一种高级编程语言,通过解释执行.它是一门动态类型,面向对象(基于原型)的直译语言.它已经由欧洲电脑制造商协会通过ECMAScript实现语言标准化,它被世界上的绝大多数网站所使用 ...
- Notepad++ 实用技巧
Notepad++是一款开源的文本编辑器,功能强大.很适合用于编辑.注释代码.它支持绝大部分主流的编程语言. 本文主要列举了本人在实际使用中遇到的一些技巧. 快捷键 自定义快捷键 首先,需要知道的是: ...
- javascript实用技巧、javascript高级技巧
字号+作者:H5之家 来源:H5之家 2016-10-31 11:00 我要评论( ) 三零网提供网络编程. JavaScript 的技术文章javascript实用技巧.javascript高级技巧 ...
随机推荐
- MySQL不香吗,清华架构师告诉你为什么还要有noSQL?
强烈推荐观看: 阿里P8架构师谈(数据库系列):NoSQL使用场景和选型比较,以及与SQL的区别_哔哩哔哩 (゜-゜)つロ 干杯~-bilibiliwww.bilibili.com noSQL的大概 ...
- Java实现 LeetCode 729 我的日程安排表 I(二叉树)
729. 我的日程安排表 I 实现一个 MyCalendar 类来存放你的日程安排.如果要添加的时间内没有其他安排,则可以存储这个新的日程安排. MyCalendar 有一个 book(int sta ...
- Java实现 蓝桥杯 算法提高 合并石子
算法提高 合并石子 时间限制:2.0s 内存限制:256.0MB 问题描述 在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数 ...
- Java实现 LeetCode 446 等差数列划分 II - 子序列
446. 等差数列划分 II - 子序列 如果一个数列至少有三个元素,并且任意两个相邻元素之差相同,则称该数列为等差数列. 例如,以下数列为等差数列: 1, 3, 5, 7, 9 7, 7, 7, 7 ...
- Java实现 蓝桥杯VIP 算法提高 质因数2
算法提高 质因数2 时间限制:1.0s 内存限制:256.0MB 将一个正整数N(1<N<32768)分解质因数,把质因数按从小到大的顺序输出.最后输出质因数的个数. 输入格式 一行,一个 ...
- Java实现 蓝桥杯 乘积最大
输入输出样例 输入样例#1: 4 2 1231 输出样例#1: 62 import java.util.Scanner; public class chengjizuida { public stat ...
- java实现股票的风险
股票的风险 股票风险 股票交易上的投机行为往往十分危险.假设某股票行为十分怪异,每天不是涨停(上涨10%)就是跌停(下跌10%).假设上涨和下跌的概率均等(都是50%).再假设交易过程没有任何手续费. ...
- MySQL数据库基本使用(DDL)
MySQL是一种开源的关系型数据库管理系统,并且因为其性能.可靠性和适应性而备受关注.下面是最近一个月MySQL.Oracle.SQL Server的百度指数搜索指数对比: 可以看到,在最近一个月,M ...
- 机器学习——十大数据挖掘之一的决策树CART算法
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是机器学习专题的第23篇文章,我们今天分享的内容是十大数据挖掘算法之一的CART算法. CART算法全称是Classification ...
- CentOS8.1中搭建Gitlab服务器
依旧是写在前面的话♠:很多IT人从业N年也许都还没有亲自搭过一次Gitlab服务器,是不是?有木有?!通常都是背着自己的笔记电脑到一家公司入职,或入职后领到公司分配的电脑,然后分配了Git账号,拿了将 ...