CameraAnimation.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. import * as THREE from "../../../libs/three.js/build/three.module.js";
  2. import { Utils } from "../../utils.js";
  3. import math from "../../utils/math.js";
  4. import {LineDraw} from "../../utils/DrawUtil.js";
  5. import CurveCtrl from "../../objects/tool/CurveCtrl.js";
  6. import HandleSvg from "../../objects/tool/HandleSvg.js";
  7. import {/* transitions,*/ easing, lerp} from '../../utils/transitions.js'
  8. import { EventDispatcher } from "../../EventDispatcher.js";
  9. const colors = {
  10. position: 'red',
  11. target : 'blue'
  12. }
  13. let lineMats
  14. const getLineMat = function(name){
  15. if(!lineMats){
  16. lineMats = {
  17. position: LineDraw.createFatLineMat({
  18. color: colors.position,
  19. lineWidth: 3
  20. }),
  21. target : LineDraw.createFatLineMat({
  22. color: colors.target,
  23. lineWidth: 3
  24. }),
  25. frustum: LineDraw.createFatLineMat({
  26. color: colors.position,
  27. lineWidth: 2
  28. }),
  29. aimAtTarget: new THREE.LineBasicMaterial({color:colors.target})
  30. }
  31. }
  32. return lineMats[name]
  33. }
  34. export class CameraAnimation extends EventDispatcher{
  35. constructor(viewer){
  36. super();
  37. this.viewer = viewer;
  38. this.selectedElement = null;
  39. //this.controlPoints = [];
  40. this.uuid = THREE.Math.generateUUID();
  41. this.node = new THREE.Object3D();
  42. this.node.name = "camera animation";
  43. this.viewer.scene.scene.add(this.node);
  44. this.frustum = this.createFrustum();
  45. this.node.add(this.frustum);
  46. this.name = "Camera Animation";
  47. // "centripetal", "chordal", "catmullrom"
  48. this.curveType = "centripetal"
  49. this.visible = true;
  50. this.targets = [];
  51. this.createPath();
  52. this.duration = 5;
  53. this.percent = 0;
  54. this.currentIndex = 0
  55. this.durations = []
  56. this.quaternions = [];
  57. if(!Potree.settings.isTest){
  58. this.setVisible(false)
  59. }
  60. this.addEventListener('dispose', ()=>{
  61. this.dispose()
  62. })
  63. this.targetLines = new THREE.Object3D
  64. this.node.add(this.targetLines)
  65. }
  66. static defaultFromView(viewer){
  67. const animation = new CameraAnimation(viewer);
  68. const camera = viewer.scene.getActiveCamera();
  69. const target = viewer.scene.view.getPivot();
  70. const cpCenter = new THREE.Vector3(
  71. 0.3 * camera.position.x + 0.7 * target.x,
  72. 0.3 * camera.position.y + 0.7 * target.y,
  73. 0.3 * camera.position.z + 0.7 * target.z,
  74. );
  75. const targetCenter = new THREE.Vector3(
  76. 0.05 * camera.position.x + 0.95 * target.x,
  77. 0.05 * camera.position.y + 0.95 * target.y,
  78. 0.05 * camera.position.z + 0.95 * target.z,
  79. );
  80. const r = 2//camera.position.distanceTo(target) * 0.3;
  81. //const dir = target.clone().sub(camera.position).normalize();
  82. const angle = Utils.computeAzimuth(camera.position, target);
  83. const n = 5;
  84. for(let i = 0; i < n; i++){
  85. let u = 1.5 * Math.PI * (i / n) + angle;
  86. const dx = r * Math.cos(u);
  87. const dy = r * Math.sin(u);
  88. const cpPos = new THREE.Vector3(
  89. cpCenter.x + dx,
  90. cpCenter.y + dy,
  91. cpCenter.z,
  92. )
  93. const targetPos = new THREE.Vector3(
  94. targetCenter.x + dx * 0.1,
  95. targetCenter.y + dy * 0.1,
  96. targetCenter.z,
  97. )
  98. animation.createControlPoint(null,{position:cpPos, target:targetPos});
  99. }
  100. animation.changeCallback()
  101. return animation;
  102. }
  103. createControlPoint(index, posInfo ){
  104. const length = this.posCurve.points.length
  105. const position = new THREE.Vector3
  106. const target = new THREE.Vector3
  107. if(index == void 0 ){
  108. index = length;
  109. }
  110. if(!posInfo){
  111. if(length >= 2 && index === 0){
  112. const dir = new THREE.Vector3().subVectors(this.posCurve.points[0], this.posCurve.points[1] )
  113. position.copy(this.posCurve.points[0]).add(dir);
  114. const tDir = new THREE.Vector3().subVectors(this.targets[0].position, this.targets[1].position )
  115. target.copy(this.targets[0].position).add(dir);
  116. }else if(length >= 2 && index === length){
  117. const dir = new THREE.Vector3().subVectors(this.posCurve.points[length-1], this.posCurve.points[length-2] )
  118. position.copy(this.posCurve.points[length-2]).add(dir);
  119. const tDir = new THREE.Vector3().subVectors(this.targets[length-1].position, this.targets[length-2].position )
  120. target.copy(this.targets[length-2].position).add(dir);
  121. }else if(length >= 2){
  122. position.copy(this.posCurve.points[index-1].clone().add(this.posCurve.points[index]).multiplyScalar(0.5));
  123. target.copy(this.targets[length-1].position.clone().add(this.targets[length]).multiplyScalar(0.5));
  124. }
  125. }else{
  126. position.copy(posInfo.position)
  127. target.copy(posInfo.target)
  128. }
  129. this.posCurve.addPoint(position, index/* , true */)
  130. //this.targetCurve.addPoint(target, index/* , true */)
  131. let targetSvg = new HandleSvg(target, colors.target)
  132. targetSvg.visible = this.visible
  133. this.targets = [...this.targets.slice(0,index), targetSvg, ...this.targets.slice(index,length)]
  134. if(this.useDurSlice){//不使用全局的duration,而是分段的
  135. this.durations = [...this.durations.slice(0,index), posInfo.duration, ...this.durations.slice(index,length)]
  136. }
  137. this.dispatchEvent({
  138. type: "controlpoint_added",
  139. index
  140. });
  141. {
  142. let targetLine = LineDraw.createLine([position,target] ,{mat: getLineMat('aimAtTarget')})
  143. this.targetLines.children = [...this.targetLines.children.slice(0,index), targetLine, ...this.targetLines.children.slice(index,length)]
  144. this.targets[index].addEventListener('dragged', (e)=>{
  145. this.updatePathCallback()
  146. this.dragPointCallback(e)
  147. })
  148. }
  149. }
  150. dragPointCallback(e){
  151. let index = e.index
  152. if(e.index == void 0){
  153. index = this.targets.indexOf(e.target)
  154. }
  155. LineDraw.moveLine(this.targetLines.children[index], [this.posCurve.points[index], this.targets[index].position] )
  156. this.updateFrustum()
  157. }
  158. updatePathCallback(){
  159. {
  160. this.quaternions = [];
  161. let length = this.posCurve.points.length;
  162. for(let i=0; i<length; i++){
  163. let quaternion = math.getQuaFromPosAim(this.posCurve.points[i], this.targets[i].position)
  164. this.quaternions.push( quaternion)
  165. }
  166. }
  167. this.reMapCurvePercent()
  168. }
  169. removeControlPoint(index){
  170. this.posCurve.removePoint(index)
  171. //this.targetCurve.removePoint(index)
  172. this.targets[index].dispose();
  173. this.targets.splice(index, 1)
  174. this.dispatchEvent({
  175. type: "controlpoint_removed",
  176. index
  177. });
  178. this.targetLines.remove(this.targetLines.children[index])
  179. if(this.useDurSlice){
  180. this.durations.splice(index, 1)
  181. }
  182. }
  183. createPath(){
  184. this.posCurve = new CurveCtrl([],getLineMat('position'), colors.position, 'posCurve');
  185. //this.targetCurve = new CurveCtrl([], getLineMat('target'), colors.target, 'targetCurve', {noLine:true});
  186. this.posCurve.needsPercent = true
  187. this.node.add(this.posCurve)
  188. //this.node.add(this.targetCurve)
  189. this.posCurve.addEventListener('dragCurvePoint', this.dragPointCallback.bind(this))
  190. this.posCurve.addEventListener('updatePath', this.updatePathCallback.bind(this))
  191. }
  192. createFrustum(){
  193. const f = 0.3;
  194. const positions = [
  195. new THREE.Vector3( 0, 0, 0),
  196. new THREE.Vector3(-f, -f, +1),
  197. new THREE.Vector3( 0, 0, 0),
  198. new THREE.Vector3( f, -f, +1),
  199. new THREE.Vector3( 0, 0, 0),
  200. new THREE.Vector3( f, f, +1),
  201. new THREE.Vector3( 0, 0, 0),
  202. new THREE.Vector3(-f, f, +1),
  203. new THREE.Vector3(-f, -f, +1),
  204. new THREE.Vector3( f, -f, +1),
  205. new THREE.Vector3( f, -f, +1),
  206. new THREE.Vector3( f, f, +1),
  207. new THREE.Vector3( f, f, +1),
  208. new THREE.Vector3(-f, f, +1),
  209. new THREE.Vector3(-f, f, +1),
  210. new THREE.Vector3(-f, -f, +1),
  211. ]
  212. positions.forEach(e=>e.z *= -1) //因为得到的rotation是camera的,作用在物体上要反向,所以这里反向一下
  213. //geometry.computeBoundingSphere();//?
  214. const line = LineDraw.createFatLine( positions, {material:getLineMat('frustum')})
  215. //line.scale.set(20, 20, 20);
  216. line.visible = false
  217. return line;
  218. }
  219. reMapCurvePercent(){ //因在不同点在相同位置旋转,由于间隔仅和位置距离相关,导致时间间隔为0,采取重新调整间隔的策略。
  220. var length = this.posCurve.points.length
  221. if(length<2){
  222. return this.newPointsPercents = []
  223. }
  224. var newPercents = [0];
  225. if(this.useDurSlice){ //已经设定好了每一段的duration的话
  226. let sums = [0]
  227. let sum = 0, last
  228. for(let i=0;i<length-1;i++){ //去掉最后一个duration,因为已到终点
  229. let duration = this.durations[i];
  230. sum += duration;
  231. last = duration;
  232. sums.push(sum)
  233. }
  234. for(let i=1;i<length;i++){
  235. newPercents.push(sum == 0 ? i/length : sums[i] / sum)
  236. }
  237. }else{
  238. const maxSpaceDur = this.duration / length //每两点之间修改间隔时间后,最大时间
  239. const durPerRad = 0.8 //每弧度应该占用的时间
  240. const minSpaceDur = Math.min(0.8, maxSpaceDur)//每两点之间修改间隔时间后,最小时间
  241. const maxAngleSpaceDur = THREE.Math.clamp(durPerRad * Math.PI, minSpaceDur, maxSpaceDur )// 最大可能差距是180度
  242. var percents = this.posCurve.pointsPercent;
  243. for(let i=1;i<length;i++){
  244. let diff = (percents[i] - percents[i-1]) * this.duration //间隔时间
  245. let percent
  246. let curMin = minSpaceDur
  247. if(diff < maxAngleSpaceDur){ //若小于最大旋转时间
  248. let rad = this.quaternions[i].angleTo(this.quaternions[i-1])
  249. curMin = THREE.Math.clamp(rad * durPerRad, minSpaceDur, maxSpaceDur)
  250. }
  251. diff = Math.max(diff, curMin)
  252. percent = newPercents[i-1] + (diff / this.duration) //得到新的percent
  253. newPercents.push(percent)
  254. }
  255. let maxPercent = newPercents[length-1] //最后一个,若扩充过时间,就会>1
  256. if( !math.closeTo(maxPercent, 1)){
  257. let scale = 1 / maxPercent //需要压缩的比例 <1 这一步会让实际得到的间隔更小
  258. newPercents = newPercents.map(e=> e*=scale )
  259. }
  260. }
  261. this.newPointsPercents = newPercents;
  262. //console.log(newPercents)
  263. }
  264. at(originPercent, delta, transitionRatio){
  265. originPercent = THREE.Math.clamp(originPercent, 0, 1)
  266. //修改第一层:起始时间
  267. let percent = originPercent;
  268. /* const easePercent = 0.3; //缓动占比 //如果能在所有从静止到运动的中间加缓动就好了呀:lastPos * 0.9 + currentPos * 0.1 ?
  269. if(originPercent < easePercent){
  270. console.log('easeIn')
  271. percent = easing.easeInSine(originPercent, 0, easePercent, easePercent) //currentTime, startY, wholeY, duration 选了一个衔接时接近斜率1的缓动函数
  272. }else if(originPercent > 1-easePercent){
  273. console.log('easeOut')
  274. percent = easing.easeOutSine(originPercent-(1-easePercent), 1-easePercent, easePercent, easePercent)
  275. } */
  276. let quaternion
  277. if(percent < 1){
  278. //修改第二层:使用每个点的重定位的 newPointsPercents
  279. this.currentIndex = this.newPointsPercents.findIndex(e=> e>percent ) - 1
  280. //假设每个节点的百分比是精确的,那么:
  281. let curIndexPercent = this.newPointsPercents[this.currentIndex];
  282. let nextIndexPercent = this.newPointsPercents[this.currentIndex+1];
  283. let progress = (percent - curIndexPercent) / (nextIndexPercent - curIndexPercent)//在这两个节点间的百分比
  284. //投影到原本的 posCurve.pointsPercent上:
  285. let curIndexOriPercent = this.posCurve.pointsPercent[this.currentIndex]
  286. let nextIndexOriPercent = this.posCurve.pointsPercent[this.currentIndex+1]
  287. percent = curIndexOriPercent + (nextIndexOriPercent - curIndexOriPercent) * progress
  288. let endQuaternion = this.quaternions[this.currentIndex+1]
  289. let startQuaternion = this.quaternions[this.currentIndex]
  290. quaternion = (new THREE.Quaternion()).copy(startQuaternion)
  291. lerp.quaternion(quaternion, endQuaternion)(progress)
  292. }else{
  293. this.currentIndex = this.posCurve.points.length - 1;
  294. quaternion = math.getQuaFromPosAim(this.posCurve.points[this.currentIndex], this.targets[this.currentIndex].position)
  295. }
  296. const position = this.posCurve.getPointAt(percent); // 需要this.posCurve.points.length>1 否则报错
  297. //console.log(this.currentIndex, originPercent)
  298. //缓动:
  299. var aimQua, aimPos;
  300. if(delta != void 0 ){
  301. if(Potree.settings.tourTestCameraMove){
  302. aimQua = this.frustum.quaternion.clone();
  303. aimPos = this.frustum.position.clone();
  304. }else{
  305. var camera = viewer.scene.getActiveCamera();
  306. aimQua = camera.quaternion.clone();
  307. aimPos = camera.position.clone();
  308. }
  309. transitionRatio = transitionRatio || 1 / Potree.settings.cameraAniSmoothRatio//渐变系数,越小缓动程度越高,越平滑
  310. transitionRatio *= delta * 60 //假设标准帧率为60fps,当帧率低时(delta大时)要降低缓动
  311. //console.log(transitionRatio, delta) //画面ui变化会使delta变大
  312. transitionRatio = THREE.Math.clamp(transitionRatio, 0, 1);
  313. lerp.quaternion(aimQua, quaternion)(transitionRatio) //每次只改变一点点
  314. lerp.vector(aimPos, position)(transitionRatio)
  315. }else{
  316. aimQua = quaternion; aimPos = position
  317. }
  318. let rotation = new THREE.Euler().setFromQuaternion(aimQua )
  319. const frame = {
  320. position: aimPos,
  321. rotation
  322. };
  323. return frame;
  324. }
  325. set(percent){
  326. this.percent = percent;
  327. }
  328. setVisible(visible){
  329. this.node.visible = visible;
  330. this.posCurve.visible = visible
  331. this.targets.forEach(e=>e.visible = visible )
  332. this.visible = visible;
  333. }
  334. setDuration(duration){
  335. if(duration != this.duration){
  336. this.duration = duration;
  337. if(this.quaternions.length == this.posCurve.points.length)this.reMapCurvePercent()
  338. }
  339. }
  340. getDuration(duration){
  341. return this.duration;
  342. }
  343. play(startOptions={}){
  344. if(this.onUpdate){
  345. return console.error('已经开始播放')
  346. }
  347. let startPercent = 0, currentIndex = 0
  348. if(startOptions.percent != void 0 ){
  349. startPercent = startOptions.percent
  350. }else if(startOptions.index){
  351. currentIndex = index
  352. //startPercent = index/(this.posCurve.points.length-1)
  353. startPercent = this.posCurve.pointsPercent[index]
  354. }
  355. //const tStart = performance.now();
  356. const duration = this.duration;
  357. this.originalyVisible = this.visible;
  358. Potree.settings.tourTestCameraMove || this.setVisible(false);
  359. let tStart, startTransitionRatio = 0.2
  360. let startDelay = 1/startTransitionRatio / 20 ;//因为缓动所以延迟开始,前面前都是at(0),使过渡到开始点位(但是依旧不能准确停在起始点,因为缓动是乘百分比有残留。所以直接平滑衔接到开始后的位置)
  361. let hasPlayedTime = 0
  362. let finishDelay = Potree.settings.cameraAniSmoothRatio / 60 * 3//结束后还需要多久时间才能大致达到缓动的最终目标
  363. let hasStoppedTime = 0
  364. this.onUpdate = (e) => {
  365. if(this.posCurve.points.length<2){
  366. if(this.posCurve.points.length == 1){
  367. viewer.scene.view.position.copy(this.posCurve.points[0]);
  368. viewer.scene.view.rotation = new THREE.Euler().setFromQuaternion(this.quaternions[0])
  369. }
  370. this.pause()
  371. return
  372. }
  373. let percent, transitionRatio
  374. if(tStart){
  375. let tNow = performance.now();
  376. let elapsed = (tNow - tStart) / 1000;
  377. percent = elapsed / duration + startPercent;
  378. }else{//从当前位置过渡到开始位置
  379. percent = 0
  380. hasPlayedTime += e.delta
  381. transitionRatio = startTransitionRatio;
  382. //console.log('延迟开始')
  383. if(hasPlayedTime > startDelay){
  384. tStart = performance.now();
  385. }
  386. }
  387. this.set(percent);
  388. const frame = this.at(percent, e.delta, transitionRatio);
  389. if(currentIndex != this.currentIndex){
  390. currentIndex = this.currentIndex
  391. console.log('updateCurrentIndex', currentIndex)
  392. this.dispatchEvent({type:'updateCurrentIndex', currentIndex })
  393. }
  394. if(!Potree.settings.tourTestCameraMove){
  395. viewer.scene.view.position.copy(frame.position);
  396. //viewer.scene.view.lookAt(frame.target);
  397. viewer.scene.view.rotation = frame.rotation;
  398. }
  399. this.updateFrustum(frame)
  400. if(percent >= 1){
  401. if(hasStoppedTime > finishDelay){
  402. this.pause()
  403. }else{
  404. hasStoppedTime += e.delta
  405. //console.log('延迟结束')
  406. }
  407. }
  408. };
  409. this.viewer.addEventListener("update", this.onUpdate);
  410. }
  411. pause(){
  412. this.setVisible(this.originalyVisible);
  413. this.viewer.removeEventListener("update", this.onUpdate);
  414. this.dispatchEvent('playDone')
  415. this.onUpdate = null
  416. }
  417. updateFrustum(frame){
  418. const frustum = this.frustum;
  419. if(this.posCurve.points.length>1){
  420. frustum.visible = true
  421. }else{
  422. frustum.visible = false;
  423. return
  424. }
  425. frame = frame || this.at(this.percent);
  426. frustum.position.copy(frame.position);
  427. //frustum.lookAt(...frame.target.toArray());
  428. frustum.rotation.copy(frame.rotation)
  429. }
  430. changeCallback(){
  431. this.posCurve.update()
  432. //this.targetCurve.update()
  433. this.targets.forEach(e=>e.update())
  434. this.updateFrustum()
  435. }
  436. dispose(){//add
  437. this.posCurve.dispose()
  438. //this.targetCurve.dispatchEvent({type:'dispose'})
  439. this.targets.forEach(e=>e.dispose())
  440. this.durations = []
  441. this.node.parent.remove(this.node);
  442. }
  443. }
  444. //scene.removeCameraAnimation
  445. //修改:不使用targetCurve作为target曲线,因为播放时posCuve的节点和targetCurve并没有对应,且使用target的曲线会使角度变化大的情况过渡生硬。
  446. // 改完旋转了。但是位置也有问题。速度完全和路程相关,当在同一位置设置多点时,这段的总时长为0. (是否要设置最小时长?不过也做不到 - -)