HT for Web (Hightopo) 使用心得(5)- 动画的实现

其实,在 HT for Web 中,有多种手段可以用来实现动画。我们这里仍然用直升机为例,只是更换了场景。增加了巡游过程。

使用 HT 开发的一个简单网页直升机巡逻动画(Hightopo 使用心得(5))

这里主要用到的动画实现方式有三种:

  • setInterval
  • ht.Default.startAnim()
  • DataModel.addScheduleTask(task)

场景搭建

这里的主要工作分为:3D 场景配置以及模型加载。其中 3D 场景部分的设置代码如下:

this.g3d = new ht.graph3d.Graph3dView();
this.g3d.setGridVisible(true);
this.g3d.setGridSize(5000);
this.g3d.setGridGap(2000);
this.g3d.setNear(10)
this.g3d.setFar(10000000)
this.g3d.addToDOM();
this.dataModel = this.dm = this.g3d.dm();

为了给直升机搭建一个逼真的环境。这里我们增加了一个山体模型。另外,由于直升机机体与螺旋桨模型是分开的,因此需要分别加载并调整其位置让二者合并成一个模型。

// 加载山体模型
this.mountains = await this.createObj(MODELS.MOUNTAINS.name, MODELS.MOUNTAINS.obj, MODELS.MOUNTAINS.mtl);
this.mountains.s('3d.selectable',false);
this.mountains.s('shape3d.scaleable',true);
this.mountains.setScale3d([0.01, 0.1, 0.01]);
this.mountains.setElevation(1800); // 让山体在地面以上
// 分别加载直升机及螺旋桨模型
this.helicopterNode = await this.createObj(MODELS.HELICOPTER.name, MODELS.HELICOPTER.obj, MODELS.HELICOPTER.mtl);
this.propellerNode = await this.createObj(MODELS.PROPELLER.name, MODELS.PROPELLER.obj, MODELS.PROPELLER.mtl);
// 由于默认创建 Node 的时候,其锚点是在 [0.5, 0.5, 0.5],位置是在 [0, 0, 0]。导致模型并不在水平面以上。
let size3d = this.helicopterNode.getSize3d(); // 获取直升机模型的 [长,宽,高]
let height = size3d[1]; // 获取模型高度
this.helicopterNode.setPosition3d([0, height/2, 0]); // 将直升机放到地面上
this.propellerNode.setRotation3d([0.10506443461595279, 4.550746858974086, -0.007825951889059535]); // 让螺旋桨水平
this.propellerNode.setPosition3d([0, 215, -99.00152946490829]); // 将螺旋桨放到直升机上
this.propellerNode.setHost(this.helicopterNode); // 螺旋桨吸附到直升机上
this.helicopterNode.p3(0,2000,0); // 直升机

螺旋桨动画 - setInterval

螺旋桨动画比较简单,其本质是通过不断地修改螺旋桨节点在竖直方向(Y 轴)的角度。

/**
* 螺旋桨旋转动画
*
*/
startPropellerAnim(node) {
setInterval(() => {
const r3 = node.getRotation3d();
node.setRotation3d([r3[0], r3[1] + 0.4, r3[2]]); // 绕 Y 轴旋转
}, 20);
}

创建直升机巡游路径

有了直升机及环境,我们需要让直升机动起来。例如在这里,我们计划让直升机围绕山体巡逻。这里该如何实现呢?

在 HT for Web 官方手册中,其提供了一种实现方式,我们这里稍微加以改造便可让直升机围绕山体巡逻。

在代码层面,我们创建了一条三维线段(Polyline)。该线段实现的是一个圆环,悬浮在山体上面。有了这条路径,直升机便可沿着该路径前进实现巡游动画。

polyline的形状主要由points和segments这两个属性描述。二者都是数组。其中 points 可以理解成组成 polyline 所要用到的点集合,而 segments 数组主要用来定义如何使用前面的点来组成 polyline。

points 中的每一项为 {x,y,e} 格式,需要注意的是,这里代表高度的是 e(elevation),而不是 y。

segments 数组里面有5种值。分别为:

  • 1: moveTo,占用1个点信息,代表一个新路径的起点
  • 2: lineTo,占用1个点信息,代表从上次最后点连接到该点
  • 3: quadraticCurveTo,占用2个点信息,第一个点作为曲线控制点,第二个点作为曲线结束点
  • 4: bezierCurveTo,占用3个点信息,第一和第二个点作为曲线控制点,第三个点作为曲线结束点
  • 5: closePath,不占用点信息,代表本次路径绘制结束,并闭合到路径的起始点
/**
* 创建直升机巡游路径
*
* @memberof Index3d
*/
createPath() {
this.g3d.setDashDisabled(false); // 显示虚线
let height = 2000; // 线段离地高度
let dataModel = this.dataModel;
let polyline = this.polyline = new ht.Polyline();
polyline.setThickness(5); // 线段粗细
polyline.s({
'shape3d.image': 'assets/flow.png', // 贴图
"shape3d": "cylinder", // polyline类型,这里是圆柱。也可以是
'repeat.uv.length': 400, // 贴图宽度
'shape3d.resolution': 1600, // 管线分辨率,分辨率越高越平滑
});
dataModel.add(polyline);
// 起始点
const points = [{
x: -15000,
y: 0,
e: height,
}];
const segments = [1];
// 二次曲线,占用两个点。生成一条弧线。下同。
points.push({
x: -15000,
y: -15000,
e: height
});
points.push({
x: 0,
y: -15000,
e: height
});
segments.push(3);
points.push({
x: 15000,
y: -15000,
e: height
});
points.push({
x: 15000,
y: 0,
e: height
});
segments.push(3);
points.push({
x: 15000,
y: 15000,
e: height
});
points.push({
x: 0,
y: 15000,
e: height
});
segments.push(3);
points.push({
x: -15000,
y: 15000,
e: height
});
points.push({
x: -15000,
y: 0,
e: height,
});
segments.push(3);
polyline.setPoints(points);
polyline.setSegments(segments);
polyline.setAnchorElevation(0)
}

直升机巡游动画 - ht.Default.startAnim

接下来,我们需要让直升机沿着巡游路径前进。在实现的时候,我们使用了 ht.Default.startAnim() 方法。该方法我们在前几篇文章中都用过,这里就不再详细介绍。

ht.Default.startAnim() 会执行 duration 毫秒,在执行过程中,其会自动计算所需要的帧数并在每一帧都调用一次action 方法。也就是说,如果我们想让直升机 40 秒围绕路径飞行一圈,我们只需要将 duration 设置成40*1000 毫秒,并且在每一帧拿到当前时刻 polyline 上的点的坐标及方向。同时,使用该坐标与方向设置直升机位置及朝向就可以实现巡游动画。

这里面比较关键的一个方法是 g3d.getLineOffset(polyline, length * v) 。该方法会返回一个对象:{point: p.M…h.Vector3, tangent: p.M…h.Vector3}。其分别代表当前时刻 polyline 上的点的坐标及放向。根据这两个值,我们可以进一步配置直升机的位置和朝向。

/**
* 直升机沿着巡游路径飞行
*
* @param {number} [duration=40 * 1000]
* @memberof Index3d
*/
startFly(duration = 40 * 1000) {
const {
g3d,
polyline
} = this;
/** 获取巡游路径总长度 */
let length = g3d.getLineLength(polyline);
const params = {
delay: 0,
duration,
easing: (t) => {
return t;
},
action: (v, t) => {
let offset = g3d.getLineOffset(polyline, length * v),
point = offset.point,
px = point.x,
py = point.y + 200, // 让直升机高于polyline
pz = point.z,
tangent = offset.tangent,
tx = tangent.x,
ty = tangent.y,
tz = tangent.z;
this.helicopterNode.p3(px, py, pz);
this.helicopterNode.lookAt([px + tx, py + ty, pz + tz], 'back'); // 一个模型有6个面,这里需要确定机头处于哪个面
// 视角盯住直升机
if (this._cameraType == 1) {
g3d.setCenter(px, py, pz);
} else if (this._cameraType == 2) { // Camera跟随直升机运动
g3d.setEye(px - tx * 1800 + 1000, py - ty * 1800 + 1000, pz - tz * 1800); // 让镜头高于直升机并在尾部进行观察
g3d.setCenter(px, py, pz);
}
this.helicopterNode.a('angle', v * Math.PI * 120);
},
finishFunc: () => {
ht.Default.startAnim(params);
}
};
ht.Default.startAnim(params);
}

管道流动动画 - DataModel.addScheduleTask()

实现管道流动的动画有多种方式,其本质是定期改变管道的贴图偏移。

这里我们采用DataModel#addScheduleTask(task)实现流动动画。DataModel#addScheduleTask(task)实际上是添加了一个调度任务。由于该方法是在 DataModel 上执行,因此在每次执行的时候,DataModel 里面的每个 Data 都会被调用。我们可以在 action 参数里面对 Data 进行过滤。DataModel#addScheduleTask(task)方法的参数task为json对象,可指定如下属性:

  • interval:间隔毫秒数,默认值为10
  • enabled:是否启用开关,默认为true
  • beforeAction:调度开始之前的动作函数
  • action:间隔动作函数,对DataModel上的每个data节点都会执行一次action操作
  • afterAction:调度结束之后的调度函数

另外,可以用DataModel#removeScheduleTask(task)删除调度任务,其中task为以前添加过的调度任务对象。

/**
* 通过DataModel的addScheduleTask实现流动效果
*
* @memberof Index3d
*/
addScheduleTasks() {
const task = {
interval: 50, // 间隔毫秒数,默认值为10
enabled: true, // 是否启用开关,默认为true
beforeAction: () => {}, // 调度开始之前的动作函数
afterAction: () => {}, // 调度结束之后的调度函数
action: (data) => { // 间隔动作函数,对DataModel上的每个data节点都会执行一次action操作
if (data.getClassName() == 'ht.Polyline') {
const offset = (data.s('shape3d.uv.offset') || [0,0]);
data.s('shape3d.uv.offset', [offset[0] + 0.1, offset[1]]);
}
}
};
this.dataModel.addScheduleTask(task);
// this.dataModel.removeScheduleTask(task); // 删除调度任务
}

这里我们只是举例介绍一下DataModel#addScheduleTask(task)的用法。对于一个 DataModel 中大部分 Data 都需要动画的时候,可以考虑使用该方法。

在代码执行的时候,我们可以选择把巡游路径隐藏。这样看起来直升机就是沿着一个圆形持续巡游。

hidePath() {
this.polyline.s('3d.visible', false);
}

总结

本文介绍了如何通过代码实现一个直升机绕山巡游的动画,包括创建路径和实现直升机的飞行动画。另外,还介绍了如何通过DataModel#addScheduleTask(task)实现流动效果的动画。读完本文,你将了解到如何使用 HT for Web 实现各种动画效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/184022.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

a-modal拖拽弹框

drag.js export default {install(Vue) {// v-dialogDrag: 弹窗拖拽Vue.directive(dragModal, (el, bindings, vnode) > {Vue.nextTick(() > {const { visible, destroyOnClose } vnode.componentInstance// 防止未定义 destroyOnClose 关闭弹窗时dom未被销毁&#xff…

UWB高精度定位系统项目源码

在现代社会中,精准定位技术对于各行各业都至关重要。为了满足对高精度定位的需求,超宽带(Ultra-Wideband, UWB)技术应运而生。UWB高精度定位系统以其出色的定位精度和多样化的应用领域而备受关注。本文将深入探讨UWB高精度定位系统…

2024年计算机毕业设计选题(私聊获取源码或代做)

1、基于SpringBoot的养老院管理系统的设计与实现 2、基于SpringBoot的网上购物商城的设计与实现 3、基于SpringBoot的旅游网站的设计与实现 4、基于SpringBoot的网上点餐系统的设计与实现 5、基于SpringBoot的阿博图书馆管理系统的设计与实现 6、基于SpringBoot的足球青训…

向量场中的几个恒等式

向量场中的几个恒等式 1. ∇ 2 A ∇ ∇ ⋅ A − ∇ ∇ A \nabla ^2 A \nabla \nabla\cdot A-\nabla \times\nabla\times A ∇2A∇∇⋅A−∇∇A 2. ∇ ⋅ ∇ A 0 \nabla \cdot \nabla \times A 0 ∇⋅∇A0 3. ∇ ∇ ϕ 0 \nabla \times \nabla \phi0 ∇∇ϕ0

SCAU:2023年ACM校赛网上预赛(23级组别)

报数 Time Limit:1000MS Memory Limit:65535K 题型: 编程题 语言: 不限定 描述: 给定两个正整数a和b,用英语从a到b计数。根据a和b的大小关系,可能是正着数,也可能是倒着数。 输入格式: 第一行包含t&#xff0…

学习记录684@vue 统计用户页面停留时间

需求 想要统计用户在页面停留的真实时间,进入页面开始计时,切出本页面后完毕,另外需要注意关闭浏览器网页窗口或者关闭整个浏览器也要停止计时。 代码实现 data() {return {browseTime: 0, // 浏览时长初始值为 0clearTimeSet: null}},moun…

数据库服务器的配置要求

随着信息化的不断发展,数据库已经成为企业级应用的重要组成部分。安装数据库不仅是构建企业级应用的基础,也是保障数据安全和数据完整性的关键环节。数据库服务器的配置选择需要根据需求、使用情况多方面考虑。 以下是一些常见的配置要求: 1…

Kubernetes之kubeadm集群监控篇—prometheus 部署

配置文件编辑 # cat prometheus-config.yamlapiVersion: v1 kind: Namespace metadata:name: kube-prom --- apiVersion: v1 kind: ConfigMap metadata:name: prometheus-confignamespace: kube-prom data:prometheus.yml: |global:scrape_interval: 10sevaluation_interv…

算法基础之字符串哈希

字符串哈希 核心思想&#xff1a;用p(131或者13331)进制数储存字符串每一位数的hash值 L—R的哈希值 h[R]-h[L-1]*PR-L1 哈希值很大—>modQ(264)变小 用unsigned long long 存 (出界) #include<iostream>using namespace std;typedef unsigned long long ULL;co…

SpringMVC文件下载

<!--解决找不到“jquery-3.4.1.min.js”&#xff08;静态资源访问&#xff09;的问题 --> <mvc:default-servlet-handler/>方式1&#xff1a;如果去掉download就是查看图片 <a href"${pageContext.request.contextPath}/uploadfiles/${requestScope.filena…

C++输出100以内的素数

以下是一个简单的C程序&#xff0c;用于输出100以内的所有素数&#xff1a; #include <iostream>using namespace std;int main() { int num, i, flag 0; for(num 2; num < 100; num) { flag 0; for(i 2; i < num/2; i) { if(…

(C)一些题6

1.正确定义符号常量PI的宏定义为 A.define PI 3.14 B.define PI 3.14: C。#define PI 3.14 D #define PI 3.14&#xff1b; 2。关于字符数组的描述中错误的是() A.字符数组可以存放字符串 B.字符数组中的字符串可以整体输入和输出 C。可以在赋值语句中通过运算符“”对…

力扣日记11.30-【二叉树篇】平衡二叉树

力扣日记&#xff1a;【二叉树篇】平衡二叉树 日期&#xff1a;2023.11.30 参考&#xff1a;代码随想录、力扣 110. 平衡二叉树 题目描述 难度&#xff1a;简单 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#…

量子模拟技术突破!科学家将化学过程减慢 1000 亿倍

悉尼纳米科学中心的 Pablo Fernandez Peas 教授&#xff08;左&#xff09;、Ivan Kassal 副教授和 Tingrei Tan 博士。 &#xff08;图片来源&#xff1a;网络&#xff09; 在澳大利亚悉尼纳米科学中心&#xff0c;由悉尼医学院、物理和化学系组成的跨学科团队正在利用量子技…

【开发实践】使用jstree实现文件结构目录树

一、需求分析 因开发系统的需要&#xff0c;维护服务端导出文件的目录结构。因此&#xff0c;需要利用jstree&#xff0c;实现前端对文件结构目录的展示。 【预期效果】&#xff1a; 二、需求实现 【项目准备】&#xff1a; jstree在线文档&#xff1a;jstree在线文档地址 …

高频双计数器测量方法

如测量具有高频成分的数字频率或信号周期&#xff0c;请使用高频测量方法。如要在NI-DAQmx中使用该方法测量&#xff0c;成对计数器生成一个脉冲序列&#xff0c;脉冲序列的周期由测量时间属性指定。测量时间通常比待测的输入信号的周期长得多&#xff0c;以减少量化误差。但测…

常见的网络攻击方法

欢迎大家到我的博客浏览。网络攻击 | YinKais Blog 网络攻击<!--more--> 1、DNS 劫持 原理 当我们访问网站的时候&#xff0c;实际上会先进行域名解析得到网站服务器的 IP 地址&#xff0c;然后才进行后续的访问。DNS 劫持利用了这个过程追踪的漏洞&#xff0c;攻击者…

C++设计模式——Bridge模式(上)

一、什么是桥接模式 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;它将抽象部分和实现部分解耦&#xff0c;使得它们可以独立地变化。该模式使用了组合关系来替代继承&#xff0c;从而达到降低系统复杂度的目的。C作为一门面向对象的编程语…

Java核心知识点整理大全23-笔记

目录 21. JAVA 算法 21.1.1. 二分查找 21.1.2.冒泡排序算法 21.1.3. 插入排序算法 21.1.4. 快速排序算法 21.1.1. 希尔排序算法 21.1.2. 归并排序算法 21.1.3. 桶排序算法 21.1.4. 基数排序算法 21.1.5. 剪枝算法 21.1.6. 回溯算法 21.1.7. 最短路径算法 21.1.8. 最…