飞机从武汉飞往背景,根据起点和终点,需要绘制飞机航线,网上搜来的通用代码运行后一直找不到copy属性。
坑1:
ray的at方法参数变更:
仔细排查发现,是ray的at方法修改了,现在必须要两个参数了,只需要增加一个临时变量来充当at方法的target 参数,只需要修改一点点就可以了。
坑2:
并非所有的起点和终点都可以绘制贝塞尔曲线:
代码跑通后,我随便设置了如下图的一组起点和终点,结果绘制的是一条直线。
v0和v3设置成下图,甚至还报NAN的错!
直到我把v0和v3设置得更加随机一些,才终于出现了曲线!
总结一下两点绘制贝塞尔曲线的方法:
创建一条平滑的三维 三次贝塞尔曲线, 由起点、终点和两个控制点所定义。
但是基于我们日常的需求,比如飞机航线,我们只知道起点和终点,也就是v0和v3,所以我们需要通过一系列算法,得到中间的两个控制点,也就是v1和v2,但是v0和v3需要符合一定条件,目前我通过经验,只能找到两个反例:
1、 v0和v3不可以是原点,也就是坐标不能为(0,0,0),否则绘制出来的将是一条直线。
2、v0和v3组成的直线,不可以贴在一条轴线上,比如(-5,0,0)和(5,0,0),就是一条贴在x轴的直线,这样我们的算法会报NaN的错。
既然如此,那就让v0和v3随机一点吧。
修改后的js代码如下:
import * as THREE from 'three'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js';
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
import { Line2 } from 'three/examples/jsm/lines/Line2.js';
export function createFlyLine( v0, v3 ) {
// 夹角
var angle = ( v0.angleTo( v3 ) * 1.8 ) / Math.PI / 0.1; // 0 ~ Math.PI
var aLen = angle * 0.4, hLen = angle * angle * 12;
var p0 = new THREE.Vector3( 0, 0, 0 );
// 法线向量
var rayLine = new THREE.Ray( p0, getVCenter( v0.clone(), v3.clone() ) );
// ray的at方法现在必须要两个参数才能执行,所以需要加入临时变量temp
var temp = new THREE.Vector3();
// 顶点坐标
var vtop = rayLine.at( hLen / rayLine.at( 1,temp).distanceTo( p0 ),temp);
// 控制点坐标
var v1 = getLenVcetor( v0.clone(), vtop, aLen );
var v2 = getLenVcetor( v3.clone(), vtop, aLen );
// 绘制三维三次贝赛尔曲线
var curve = new THREE.CubicBezierCurve3( v0, v1, v2, v3 );
var geometry = new LineGeometry();
var points = curve.getSpacedPoints( 5000 );
var positions = [];
var colors = [];
var color = new THREE.Color();
/**
* HSL中使用渐变
* h — hue value between 0.0 and 1.0
* s — 饱和度 between 0.0 and 1.0
* l — 亮度 between 0.0 and 1.0
*/
for (var j = 0; j < points.length; j ++) {
// color.setHSL( .31666+j*0.005,0.7, 0.7); //绿色
color.setHSL( .81666+j,0.88, 0.715+j*0.0025); //粉色
colors.push( color.r, color.g, color.b );
positions.push( points[j].x, points[j].y, points[j].z );
}
geometry.setPositions( positions );
geometry.setColors( colors );
var matLine = new LineMaterial( {
linewidth: 0.0006,
vertexColors: true,
dashed: false
} );
var lineMesh = new Line2( geometry, matLine );
return lineMesh;
}
// 计算v1,v2 的中点
function getVCenter( v1, v2 ) {
const v = v1.add( v2 );
return v.divideScalar( 2 );
}
// 计算V1,V2向量固定长度的点
function getLenVcetor( v1, v2, len ) {
const v1v2Len = v1.distanceTo( v2 );
return v1.lerp( v2, len / v1v2Len );
}
vue代码如下:
<template>
<div>
<!-- 本案例演示两点绘制贝塞尔曲线-->
<div id="container"></div>
</div>
</template>
<script>
import * as THREE from 'three'
// 注意OrbitControls要加{},注意路径是jsm
import {
OrbitControls
} from 'three/examples/jsm/controls/OrbitControls.js';
import {
createFlyLine,timerFlyLine
} from './flyLine.js';
export default {
name: "hello",
props: {
},
components: {
},
data() {
return {
scene: null,
renderer: null,
camera: null,
orbitControls: null
}
},
created() {},
mounted() {
this.init();
this.animate();
},
//后续还要在beforeDestory中进行销毁
beforeDestroy() {
this.scene = null;
this.renderer = null;
this.camera = null;
this.orbitControls = null;
clearInterval(timerFlyLine);
},
methods: {
// 场景初始化
init() {
let container = document.getElementById('container');
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// 特别注意,相机的位置要大于几何体的尺寸
this.camera.position.x = 10;
this.camera.position.y = 10;
this.camera.position.z = 10;
this.scene = new THREE.Scene();
this.renderer = new THREE.WebGLRenderer({
// alpha: true, // canvas是否包含alpha (透明度) 默认为 false
antialias: true,
// precision: 'highp',
})
// this.renderer.setClearAlpha(0.0); // 设置alpha,合法参数是一个 0.0 到 1.0 之间的浮点数
// 设置背景色
this.renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(this.renderer.domElement);
// 添加三维坐标轴
// 红色代表x轴,绿色代表y轴,蓝色代表z轴
// let axesHelper = new THREE.AxesHelper(30);
// this.scene.add(axesHelper);
// 环境光不能用来投射阴影,因为它没有方向。
var ambienLight = new THREE.AmbientLight(0xcccccc);
this.scene.add(ambienLight);
// 初始化轨道控制器
// 还需要配合animate,不断循环渲染,达到用鼠标旋转物体的作用。
this.orbitControls = new OrbitControls(this.camera, this.renderer.domElement);
// 窗口大小自适应
window.addEventListener('resize', this.onWindowResize, false);
this.addObjects();
},
// 添加物体
addObjects(){
// var v0 = new THREE.Vector3(-3, 4, 0);
// var v3 = new THREE.Vector3(3, 4, 0);
var v0 = new THREE.Vector3(- 1.7049594735603837, 3.208354470512221, - 3.4350509144786985);
var v3 = new THREE.Vector3(0.5738958419746141, - 0.44114968930852216, 4.9473255920938985);
var sphere = createFlyLine(v0,v3);
this.scene.add(sphere);
},
animate() {
requestAnimationFrame(this.animate);
this.renderer.render(this.scene, this.camera);
},
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
}
}
</script>
<style scoped>
#container {
width: 100%;
height: 600px;
outline: none;
/* background-image: linear-gradient(rgb(255, 255, 255), rgb(119, 119, 237)); */
}
</style>