【threejs教程4】threejs添加跳动标注

【图片完整效果代码位于文章末】

  物体的点击事件与外发光教程可见往期文章:

【threejs教程2】threejs物体点击交互事件

【threejs教程3】threejs物体轮廓发光

引言

        本文讲解如何使用Three.js库创建一个动态的3D标注系统,用于高亮和追踪场景中的特定物体。这段代码的核心功能是定义两个函数:showObjectLocation 和 locationAnimate,分别用于创建并显示标注物以及为其添加动画效果。

物体实现跳跃原理

        物体的上下浮动实际是使用了正弦函数y=sinx,通过不断增加x轴的值,让y在[-1,1]区间内不断变化,用y的值来改变物体在垂直方向上的位置即可模拟跳跃或者振动效果。通过改变函数的公式y=a*sinx中a的值,即可调整跳跃的幅度。

具体实现步骤如下所示:

1.初始化变量与声明函数

// 初始化一个变量,用于保存当前正在显示的标注物
let currentLocationObj = null;
// 定义一个函数,用于在三维场景中显示目标物体的位置
function showObjectLocation(obj) {
  // ...
}
// 定义一个函数,用于给当前标注物添加旋转和上下浮动的动画效果
function locationAnimate(obj) {
  // ...
}

2.创建与更新标注物(showObjectLocation函数详解)

2.1创建圆锥形标注物

使用THREE.ConeGeometry创建一个底部半径为0.25、高度为0.5、由4个径向片段构成的圆锥几何体。然后,将圆锥体沿X轴旋转180度,使其尖端向下。给圆锥体赋予一种基础材质,颜色设为浅蓝色(0x66ccff)。

const geometry = new THREE.ConeGeometry(0.25, 0.5, 4);
geometry.rotateX(Math.PI);
const locationMaterial = new THREE.MeshBasicMaterial({
  color: 0x66ccff,
});
const pyramid = new THREE.Mesh(geometry, locationMaterial);

2.2设置标注物的位置

pyramid.position.set(obj.position.x, 0, obj.position.z) // 设置x、z坐标,并让y坐标略高于地面

2.3.添加标注物到场景

将新的标注物添加到场景中,并更新currentLocationObj为当前标注物。

scene.add(pyramid);
currentLocationObj = pyramid;

2.4.启动动画

调用locationAnimate函数为标注物添加动态效果。

locationAnimate(pyramid);

3.实现标注物动画(locationAnimate函数详解)

locationAnimate函数实现了标注物的持续动画效果:

3.1.定义内部动画计时器

创建一个内部变量time用于跟踪动画的时间进度,防止多个动画之间互相干扰。

let time = 0;

3.2.定义递归动画函数

animate函数利用requestAnimationFrame不断调用自身,形成一个动画循环。

function animate() {
  const animationHandle = requestAnimationFrame(() => animate());
  // 更新旋转和位置属性...
  // ...
}

3.3旋转与浮动动画效果

在每次动画帧中,标注物绕Y轴旋转,并根据正弦函数在其垂直位置上产生上下浮动效果。

obj.rotation.y += 0.03; // 旋转动画
obj.position.y = Math.sin(time) * 0.2 + 1.5; // 浮动动画
time += 0.07; // 更新时间变量

3.4管理动画句柄

将当前动画请求的句柄存储在标注物对象上,以便后续停止动画。

obj.animationHandle = animationHandle;

3.5启动动画

调用animate()函数启动动画循环。

animate();

3.6可选地,提供停止动画的方法

返回一个包含stop方法的对象,调用该方法时会取消正在进行的动画。

return {
  stop: function () {
    cancelAnimationFrame(obj.animationHandle);
  },
};

4.完整实现效果代码如下所示

<template>
	<div
		style="
			font-size: 24px;
			color: #ffffff;
			text-align: center;
			position: absolute;
			top: 20%;
			left: 50%;
			transform: translate(-50%, -50%);
		"
	>
		{{ msg }}
	</div>
</template>
<script setup>
import * as THREE from 'three'
import { onMounted, ref } from 'vue'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
const msg = ref('')
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(
	75,
	window.innerWidth / window.innerHeight,
	0.1,
	1000
)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
const controls = new OrbitControls(camera, renderer.domElement)
onMounted(() => {
	init()
})
function init() {
	camera.position.set(0, 0, 5)
	renderer.setSize(window.innerWidth, window.innerHeight)
	document.body.appendChild(renderer.domElement)
	const geometry = new THREE.BoxGeometry(1, 1, 1)
	const material1 = new THREE.MeshBasicMaterial({
		color: 0xff00a2d7,
		transparent: true,
		opacity: 0.5,
	})
	const material2 = new THREE.MeshBasicMaterial({
		color: 0xffd3e3fd,
		transparent: true,
		opacity: 0.5,
	})
	const cube1 = new THREE.Mesh(geometry, material1)
	const cube2 = new THREE.Mesh(geometry, material2)
	scene.add(cube1, cube2)
	cube1.position.set(0, 0, 0)
	cube1.name = '方块1'
	cube2.position.set(2, 0, 0)
	cube2.name = '方块2'
	cube1.position.x = -2
	controls.update()
	function animate() {
		requestAnimationFrame(animate)
		controls.update()
		cube1.rotation.y += 0.01
		cube2.rotation.y -= 0.01
		renderer.render(scene, camera)
	}
	animate()
	outlineanimate()
}
// 创建射线投射器
const raycaster = new THREE.Raycaster()
// 鼠标位置
const mouse = new THREE.Vector2()
// 记录上一个被点击的对象
let lastSelectedObject = null
let outlineComposer = new EffectComposer(renderer) // 轮廓渲染器
//物体发光通道
let outlinePass = null
let renderPass = new RenderPass(scene, camera)
// 新建一个场景通道  为了覆盖到原理来的场景上
outlineComposer.addPass(renderPass)
// 鼠标点击事件监听
window.addEventListener('click', mouseClick, false)
function mouseClick(event) {
	// 将鼠标坐标归一化
	mouse.x = (event.clientX / window.innerWidth) * 2 - 1
	mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
	// 设置射线起点为鼠标位置,射线的方向为相机视角方向
	raycaster.setFromCamera(mouse, camera)
	// 计算射线相交
	const intersects = raycaster.intersectObjects(scene.children, true)
	if (intersects.length > 0) {
		// 如果之前有选中的物体,将其颜色恢复为初始状态
		if (lastSelectedObject) {
			lastSelectedObject.material.color.set(
				lastSelectedObject.initialColor
			)
		}
		// 选中物体
		const selectedObject = intersects[0].object
		console.log('点击事件', selectedObject.name)
		msg.value = `标注${selectedObject.name}`
		// 记录当前选中物体的状态
		selectedObject.initialColor = selectedObject.material.color.clone()
		lastSelectedObject = selectedObject
		selectedObject.material.color.set(0xff62e258)
		outlineObj([selectedObject])
		showObjectLocation(selectedObject)
	} else {
		// 如果没有新的物体被选中,恢复上一个选中物体的颜色(如果存在的话)
		if (lastSelectedObject) {
			lastSelectedObject.material.color.set(
				lastSelectedObject.initialColor
			)
			msg.value = ''
		}
		if (outlinePass) {
			outlineComposer.removePass(outlinePass)
			outlinePass = null
		}
		if (currentLocationObj) {
			// 如果之前有标注物,则先移除
			scene.remove(currentLocationObj)
		}
	}
}
// 绘制轮廓线
function outlineObj(selectedObjects) {
	if (outlinePass) {
		outlineComposer.removePass(outlinePass)
		outlinePass = null
	}
	// 物体边缘发光通道
	outlinePass = new OutlinePass(
		new THREE.Vector2(window.innerWidth, window.innerHeight),
		scene,
		camera
	)
	outlinePass.edgeStrength = 5 // 边框的亮度
	outlinePass.edgeGlow = 1 // 光晕[0,1]
	outlinePass.edgeThickness = 5 // 边框宽度
	outlinePass.pulsePeriod = 3 // 呼吸闪烁的速度
	outlinePass.visibleEdgeColor.set(0xff4ecfff)
	outlinePass.hiddenEdgeColor.set(0x00ffff)
	outlinePass.selectedObjects = selectedObjects
	outlineComposer.addPass(outlinePass)
	// 自定义的着色器通道 作为参数
}
// 渲染循环
function outlineanimate() {
	requestAnimationFrame(outlineanimate)
	outlineComposer.render()
}
let currentLocationObj = null // 当前的标注物
// 定义显示物体位置的函数
function showObjectLocation(obj) {
	if (currentLocationObj) {
		// 如果之前有标注物,则先移除
		scene.remove(currentLocationObj)
	}
	// 创建圆锥几何体作为标注物
	const geometry = new THREE.ConeGeometry(0.25, 0.5, 4) // 创建一个底部半径为0.25、高度为0.5、由4个径向片段组成的圆锥几何体
	geometry.rotateX(Math.PI) // 将圆锥体绕X轴旋转180度,使得尖端朝下
	const locationMaterial1 = new THREE.MeshBasicMaterial({
		color: 0x66ccff,
	}) // 创建材质
	const pyramid = new THREE.Mesh(geometry, locationMaterial1)
	pyramid.name = '坐标标注'
	// 设置标注物的位置与传入物体的位置一致
	pyramid.position.set(obj.position.x, 0, obj.position.z) // 设置x、z坐标,并让y坐标略高于地面
	// 将标注物加入到场景中
	scene.add(pyramid)
	currentLocationObj = pyramid // 记录当前标注物
	locationAnimate(pyramid) // 调用动画函数
}
// 坐标标注旋转动画
function locationAnimate(obj) {
	let time = 0 //使用各自内部的time 防止多个动画相互影响
	function animate() {
		const animationHandle = requestAnimationFrame(() => animate())
		obj.rotation.y += 0.03
		// 上下浮动
		obj.position.y = Math.sin(time) * 0.2 + 1.5 // 按照正弦函数进行浮动
		time += 0.07
		obj.animationHandle = animationHandle
	}
	animate()
	// 可选地,返回停止动画的方法
	return {
		stop: function () {
			cancelAnimationFrame(obj.animationHandle)
		},
	}
}
</script>
  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
three.js是一个用于创建和展示 3D 地图的开源 JavaScript 库。它提供了许多功能和工具,可以轻松地创建和标注 3D 地图。 首先,我们需要加载地图的基本模型。在 three.js 中,我们可以通过导入地形数据或使用现有的地形模型来创建一个基本的 3D 地图。这可以通过使用 three.js 中的几何体和纹理进行实现。例如,我们可以使用高程数据创建山脉和河流,使用贴图添加地面纹理。 接下来,我们可以使用标注工具在地图上加入标记。这些标记可以是地点、建筑物或其他感兴趣的地方等。我们可以使用 three.js 的几何体和纹理来创建不同类型的标记,如球体、立方体或自定义模型。然后,我们可以将这些标记放置在地图的特定位置,并添加相应的标记文字或图标。 除了标注工具,three.js 还提供了交互性的功能,例如可以通过鼠标或触摸屏进行地图的旋转、缩放和平移操作。这允许用户自由地探索地图,并更好地了解地理环境。 最后,我们可以根据需要对地图进行自定义和优化。我们可以添加光源,使地图的光照效果更加真实。我们还可以使用 three.js 的特效功能,如雾效和阴影效果,来提高地图的视觉质量。 总之,通过使用three.js,我们可以轻松创建和标注3D地图,并使其具有交互性和视觉效果。这个库提供了许多功能和工具,使我们能够将地理数据可视化,并为用户提供一个更加 immersive 和有趣的地图浏览体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有只老羊在发呆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值