书接上文,初衷是想表现好西湖这个大场景,之前也加入了水和山的效果,但是感觉缺了些什么。得要加上西湖游船这个特色,一般散客可以乘坐的游船有六种,包括自划船、自开船、手划船、摇橹船、休闲船和画舫(豪华休闲船),另外特定时间还有夜游船以及西湖交通船。这六种船又有三种范围的限制,范围最大的是手划船和摇橹船,基本上可以到达西湖边的所有景点;次之的是休闲船和画舫,可以环湖游或是登岛游;范围最小的则是自开船,只能在各个自开船码头所在的水域内行驶。自划船比较特别一点,原则上是也可以环湖或是登岛的,但是不能穿过桥洞或是靠近机动船的航道,并且得考虑自身体力,所以一般游客也不会划离自划船码头太远。
开始之前先放一波效果图。
视频加载中...
实现西湖游船效果,我们先理一下思路,首先我们需要有船模型,有了模型,需要处理成我们想要的格式,然后怎么把模型加载到场景中,最后设置游船按照特定的轨迹运动,over。
1、获取模型并处理
我们demo中拿到的模型是在3D溜溜网中拿到的,具体的网址在我们拿到的数据是max格式的,使用3dmax打开模型,并导出为obj格式。
最终的效果应该是这样。
模型文件应该包括obj文件和mtl的材质文件以及贴图。
2、模型加载
模型加载的话我们要用到three的两个插件,MTLLoader和OBJLoader,使用mtl加载材质之后,再用obj加载模型。
mtlLoader.load(mtl, function( materials ) { materials.preload(); objLoader.setMaterials( materials ); objLoader.load(obj, function ( object ) { object.traverse( function ( child ) { if ( child instanceof THREE.Mesh ) { child.scale.set(scale,scale,scale); child.rotation.set(Math.PI * 1 / 2, -Math.PI * 1 / 2, 0); } }); const model = threeLayer.toModel(object,{ coordinate:position, altitude:altitude }); threeLayer.addMesh(model); })})
3、运动轨迹实现
import * as THREE from 'three'import * as maptalks from 'maptalks'//单个模型轨迹export class ModelRoute { constructor(options) { this.layer = options.layer this.model = options.model //速度倍率,控制速度 this.speed = typeof options.speed === 'number' ? options.speed : 1 //路径总时间,控制速度,speed与totalTime二选一,speed存在时totalTime无效 this.totalTime = typeof options.totalTime === 'number' ? options.totalTime : 0 this.isLoop = typeof options.isLoop === 'boolean' ? options.isLoop : true //模型数组 this._modelList = [] //gltf数组 this._gltfList = [] //每个模型路径组成的数组 this._coordsList = [] //每个模型实时位置函数组成的数组 this._animations = [] this._clock = new THREE.Clock() //混合器数组 this._mixerList = [] //存储经纬度数据 this._allDistance = [] this._distancesFromFist = [] this._dataListCoor = [] //时间起点,所有模型以第一个模型为播放时间起点 this._startTime //是否暂停 this.isPause = false //是否到达了终点 this.isEnd = false //当前位置中路径百分比 this._currentPercentage = 0 //当前时间位置到起点的距离,反复循环 this._currentDisFromFirst = 0 //是否是新的设置的路径 this._isNewcoordinates = false this.coordinates = options.coordinates //不循环,结束时回调 this.endCallback = () => {} //实时回调 this.callback this._prepareData() this._setPositionAndAngle() } _setPositionAndAngle() { //实时位置函数 return () => { if (this.isPause === true) return var po = this._getPositionAndAngle() if (!po) return var po_vec = this.layer.coordinateToVector3(new maptalks.Coordinate(po.x, po.y)) if (po.z) { po_vec.z = this.layer.distanceToVector3(po.z, po.z).x } if (this.callback) { po_vec.angle = po.angle this.callback(po, po_vec) } if (this.model.getObject3d) { this.model.getObject3d().position.set(po_vec.x, po_vec.y, po_vec.z) this.model.getObject3d().rotation.z = Math.PI - po.angle } else { this.model.position.set(po_vec.x, po_vec.y, po_vec.z) this.model.rotation.z = Math.PI - pa.angle } } } /** * @ignore * 准备数据 * @memberof ModelRoute */ _prepareData() { if (!this.coordinates) { return } var dis = 0, data = [], dataToDraw = [] for (var i = 0, l = this.coordinates.length; i < l - 1; i++) { dis += this.layer.map.computeLength(this.coordinates[i], this.coordinates[i + 1]) dataToDraw.push(dis) data.push(this.coordinates[i + 1]) } //路径总距离,经纬度计算的 this._allDistance = dis //路径每点与第一点的距离,经纬度计算的 this._distancesFromFist = dataToDraw //路径矢量坐标系坐标点数组,经纬度坐标的 this._dataListCoor = this.coordinates } /** * 修改播放速度 * @param {*} options * @return {*} options.speed 速度值 * @memberof ModelRoute */ config(options) { if (options.speed === 0) { return (this.isPause = true) } else { this.isPause = false } if (typeof options.speed !== 'undefined') { let oldSpeed = this.speed //重新计算当前时间,保证改变速度时,模型位置不变,即旧速度时当前路径所占百分比===新速度时当前路径所占百分比。((Date.now() - this._startTimeOLd)* oldSpeed * 0.001) % this._allDistance=((Date.now() - this._startTimeNew)* newSpeed * 0.001) % this._allDistance this._startTime = Date.now() - ((Date.now() - this._startTime) * oldSpeed) / options.speed this.speed = options.speed } if (typeof options.totalTime !== 'undefined') { //重新计算当前时间,保证改变总时间时,模型位置不变。 let t = Date.now() if (t - this._startTime < options.totalTime * 1000) { //路径第一次走 this._startTime = t - this._currentPercentage * (options.totalTime * 1000) } else { //路径循环走 this._startTime = t - this._currentPercentage * (options.totalTime * 1000) - options.totalTime * 1000 } this.totalTime = options.totalTime } if (options.coordinates) { this._startTime = Date.now() this.isEnd = false this._isNewcoordinates = true this.coordinates = options.coordinates this._prepareData() } } /** * @ignore * 获得实时位置和角度 * @param {*} t * @returns * @memberof ModelRoute */ _getPositionAndAngle() { if (!this._allDistance || !this._dataListCoor || !this._distancesFromFist || this.isEnd === true) { //若到达了终点,返回 return } let t = Date.now() let oldCurrentDisFromFirst = this._currentDisFromFirstCoor //播放时间起点 if (!this._startTime) { this._startTime = Date.now() } if (this.totalTime) { //1、总时间控制速度 //起点时间到当前时间的时间差值占总时间的百分比 this._currentPercentage = ((t - this._startTime) % (this.totalTime * 1000)) / (this.totalTime * 1000) this._currentDisFromFirstCoor = this._currentPercentage * this._allDistance } else if (this.speed) { //2、speed参数控制速度 this._currentDisFromFirstCoor = ((t - this._startTime) * this.speed * 0.1) % this._allDistance } else { //默认speed=1 this._currentDisFromFirstCoor = ((t - this._startTime) * 0.1) % this._allDistance } if (this._isNewcoordinates === true && this.isEnd == false) { // 通过config方法,重新配置新路径后,_currentDisFromFirstCoor会在新路径从头开始,但oldCurrentDisFromFirst还是上一个路径值,故需使旧位置在现在位置后面。 // 否则,旧位置值比新位置大,到下一个if语句,直接停止,不会执行新路径 this._isNewcoordinates = false oldCurrentDisFromFirst = this._currentDisFromFirstCoor - 1 } if (oldCurrentDisFromFirst && this._currentDisFromFirstCoor - oldCurrentDisFromFirst < 0) { //到达了终点 if (this.isLoop === false) { //不再循环 this.isEnd = true this.endCallback() return } } var posCoor = {} for (var i = 0; i < this._distancesFromFist.length; i++) { if (this._distancesFromFist[i] > this._currentDisFromFirstCoor) { if (i == 0) { //计算当前经纬度坐标 posCoor.x = this._dataListCoor[0][0] + ((this._dataListCoor[1][0] - this._dataListCoor[0][0]) * this._currentDisFromFirstCoor) / this._distancesFromFist[0] posCoor.y = this._dataListCoor[0][1] + ((this._dataListCoor[1][1] - this._dataListCoor[0][1]) * this._currentDisFromFirstCoor) / this._distancesFromFist[0] posCoor.z = 0 if (this._dataListCoor[0][2]) { posCoor.z = this._dataListCoor[0][2] + ((this._dataListCoor[1][2] - this._dataListCoor[0][2]) * this._currentDisFromFirstCoor) / this._distancesFromFist[0] } posCoor.angle = -Math.atan((this._dataListCoor[1][1] - this._dataListCoor[0][1]) / (this._dataListCoor[1][0] - this._dataListCoor[0][0])) } else { //计算当前经纬度坐标 var tmp1Coor = this._currentDisFromFirstCoor - this._distancesFromFist[i - 1] var tmp2Coor = this._distancesFromFist[i] - this._distancesFromFist[i - 1] posCoor.x = this._dataListCoor[i][0] + ((this._dataListCoor[i + 1][0] - this._dataListCoor[i][0]) * tmp1Coor) / tmp2Coor posCoor.y = this._dataListCoor[i][1] + ((this._dataListCoor[i + 1][1] - this._dataListCoor[i][1]) * tmp1Coor) / tmp2Coor posCoor.z = 0 if (this._dataListCoor[0][2]) { posCoor.z = this._dataListCoor[i][2] + ((this._dataListCoor[i + 1][2] - this._dataListCoor[i][2]) * tmp1Coor) / tmp2Coor } posCoor.angle = -Math.atan((this._dataListCoor[i + 1][1] - this._dataListCoor[i][1]) / (this._dataListCoor[i + 1][0] - this._dataListCoor[i][0])) } break } } if (this._dataListCoor[i + 1].x - this._dataListCoor[i].x < 0) { posCoor.angle += Math.PI } return posCoor }}4、最终效果呈现
视频加载中...
标签: dataListCoor
②文章观点仅代表原作者本人不代表本站立场,并不完全代表本站赞同其观点和对其真实性负责。
③文章版权归原作者所有,部分转载文章仅为传播更多信息、受益服务用户之目的,如信息标记有误,请联系站长修正。
④本站一律禁止以任何方式发布或转载任何违法违规的相关信息,如发现本站上有涉嫌侵权/违规及任何不妥的内容,请第一时间反馈。发送邮件到 88667178@qq.com,经核实立即修正或删除。