123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651 |
- import * as THREE from "../../../libs/three.js/build/three.module.js";
- import { Utils } from "../../utils.js";
- import math from "../../utils/math.js";
- import {LineDraw} from "../../utils/DrawUtil.js";
- import CurveCtrl from "../../objects/tool/CurveCtrl.js";
- import HandleSvg from "../../objects/tool/HandleSvg.js";
- import {/* transitions,*/ easing, lerp} from '../../utils/transitions.js'
- import { EventDispatcher } from "../../EventDispatcher.js";
- const colors = {
- position: 'red',
- target : 'blue'
- }
-
- let lineMats
- const getLineMat = function(name){
- if(!lineMats){
- lineMats = {
- position: LineDraw.createFatLineMat({
- color: colors.position,
- lineWidth: 3
- }),
- target : LineDraw.createFatLineMat({
- color: colors.target,
- lineWidth: 3
- }),
- frustum: LineDraw.createFatLineMat({
- color: colors.position,
- lineWidth: 2
- }),
- aimAtTarget: new THREE.LineBasicMaterial({color:colors.target})
- }
- }
- return lineMats[name]
- }
-
- export class CameraAnimation extends EventDispatcher{
- constructor(viewer){
- super();
-
- this.viewer = viewer;
- this.selectedElement = null;
- //this.controlPoints = [];
- this.uuid = THREE.Math.generateUUID();
- this.node = new THREE.Object3D();
- this.node.name = "camera animation";
- this.viewer.scene.scene.add(this.node);
- this.frustum = this.createFrustum();
- this.node.add(this.frustum);
-
- this.name = "Camera Animation";
-
- // "centripetal", "chordal", "catmullrom"
- this.curveType = "centripetal"
- this.visible = true;
- this.targets = [];
- this.createPath();
- this.duration = 5;
- this.percent = 0;
- this.currentIndex = 0
- this.durations = []
- this.quaternions = [];
-
- if(!Potree.settings.isTest){
- this.setVisible(false)
- }
-
-
-
- this.addEventListener('dispose', ()=>{
- this.dispose()
- })
-
-
- this.targetLines = new THREE.Object3D
- this.node.add(this.targetLines)
-
-
-
-
-
- }
- static defaultFromView(viewer){
- const animation = new CameraAnimation(viewer);
- const camera = viewer.scene.getActiveCamera();
- const target = viewer.scene.view.getPivot();
- const cpCenter = new THREE.Vector3(
- 0.3 * camera.position.x + 0.7 * target.x,
- 0.3 * camera.position.y + 0.7 * target.y,
- 0.3 * camera.position.z + 0.7 * target.z,
- );
- const targetCenter = new THREE.Vector3(
- 0.05 * camera.position.x + 0.95 * target.x,
- 0.05 * camera.position.y + 0.95 * target.y,
- 0.05 * camera.position.z + 0.95 * target.z,
- );
- const r = 2//camera.position.distanceTo(target) * 0.3;
- //const dir = target.clone().sub(camera.position).normalize();
- const angle = Utils.computeAzimuth(camera.position, target);
- const n = 5;
- for(let i = 0; i < n; i++){
- let u = 1.5 * Math.PI * (i / n) + angle;
- const dx = r * Math.cos(u);
- const dy = r * Math.sin(u);
- const cpPos = new THREE.Vector3(
- cpCenter.x + dx,
- cpCenter.y + dy,
- cpCenter.z,
- )
- const targetPos = new THREE.Vector3(
- targetCenter.x + dx * 0.1,
- targetCenter.y + dy * 0.1,
- targetCenter.z,
- )
- animation.createControlPoint(null,{position:cpPos, target:targetPos});
-
-
- }
- animation.changeCallback()
- return animation;
- }
-
- createControlPoint(index, posInfo ){
- const length = this.posCurve.points.length
- const position = new THREE.Vector3
- const target = new THREE.Vector3
-
- if(index == void 0 ){
- index = length;
- }
-
- if(!posInfo){
-
- if(length >= 2 && index === 0){
- const dir = new THREE.Vector3().subVectors(this.posCurve.points[0], this.posCurve.points[1] )
- position.copy(this.posCurve.points[0]).add(dir);
-
- const tDir = new THREE.Vector3().subVectors(this.targets[0].position, this.targets[1].position )
- target.copy(this.targets[0].position).add(dir);
-
- }else if(length >= 2 && index === length){
- const dir = new THREE.Vector3().subVectors(this.posCurve.points[length-1], this.posCurve.points[length-2] )
- position.copy(this.posCurve.points[length-2]).add(dir);
-
- const tDir = new THREE.Vector3().subVectors(this.targets[length-1].position, this.targets[length-2].position )
- target.copy(this.targets[length-2].position).add(dir);
-
- }else if(length >= 2){
- position.copy(this.posCurve.points[index-1].clone().add(this.posCurve.points[index]).multiplyScalar(0.5));
- target.copy(this.targets[length-1].position.clone().add(this.targets[length]).multiplyScalar(0.5));
- }
- }else{
- position.copy(posInfo.position)
- target.copy(posInfo.target)
-
- }
-
- this.posCurve.addPoint(position, index/* , true */)
- //this.targetCurve.addPoint(target, index/* , true */)
- let targetSvg = new HandleSvg(target, colors.target)
- targetSvg.visible = this.visible
- this.targets = [...this.targets.slice(0,index), targetSvg, ...this.targets.slice(index,length)]
- if(this.useDurSlice){//不使用全局的duration,而是分段的
- this.durations = [...this.durations.slice(0,index), posInfo.duration, ...this.durations.slice(index,length)]
- }
-
-
- this.dispatchEvent({
- type: "controlpoint_added",
- index
- });
-
- {
- let targetLine = LineDraw.createLine([position,target] ,{mat: getLineMat('aimAtTarget')})
- this.targetLines.children = [...this.targetLines.children.slice(0,index), targetLine, ...this.targetLines.children.slice(index,length)]
-
-
-
- this.targets[index].addEventListener('dragged', (e)=>{
- this.updatePathCallback()
- this.dragPointCallback(e)
- })
- }
- }
-
- dragPointCallback(e){
-
- let index = e.index
- if(e.index == void 0){
- index = this.targets.indexOf(e.target)
- }
- LineDraw.moveLine(this.targetLines.children[index], [this.posCurve.points[index], this.targets[index].position] )
-
- this.updateFrustum()
- }
-
- updatePathCallback(){
- {
- this.quaternions = [];
- let length = this.posCurve.points.length;
- for(let i=0; i<length; i++){
- let quaternion = math.getQuaFromPosAim(this.posCurve.points[i], this.targets[i].position)
- this.quaternions.push( quaternion)
- }
- }
- this.reMapCurvePercent()
- }
-
-
- removeControlPoint(index){
- this.posCurve.removePoint(index)
- //this.targetCurve.removePoint(index)
- this.targets[index].dispose();
- this.targets.splice(index, 1)
-
- this.dispatchEvent({
- type: "controlpoint_removed",
- index
- });
- this.targetLines.remove(this.targetLines.children[index])
- if(this.useDurSlice){
- this.durations.splice(index, 1)
- }
-
- }
- createPath(){
- this.posCurve = new CurveCtrl([],getLineMat('position'), colors.position, 'posCurve');
- //this.targetCurve = new CurveCtrl([], getLineMat('target'), colors.target, 'targetCurve', {noLine:true});
- this.posCurve.needsPercent = true
- this.node.add(this.posCurve)
- //this.node.add(this.targetCurve)
-
- this.posCurve.addEventListener('dragCurvePoint', this.dragPointCallback.bind(this))
- this.posCurve.addEventListener('updatePath', this.updatePathCallback.bind(this))
-
-
-
-
- }
-
- createFrustum(){
- const f = 0.3;
- const positions = [
- new THREE.Vector3( 0, 0, 0),
- new THREE.Vector3(-f, -f, +1),
- new THREE.Vector3( 0, 0, 0),
- new THREE.Vector3( f, -f, +1),
- new THREE.Vector3( 0, 0, 0),
- new THREE.Vector3( f, f, +1),
- new THREE.Vector3( 0, 0, 0),
- new THREE.Vector3(-f, f, +1),
- new THREE.Vector3(-f, -f, +1),
- new THREE.Vector3( f, -f, +1),
- new THREE.Vector3( f, -f, +1),
- new THREE.Vector3( f, f, +1),
- new THREE.Vector3( f, f, +1),
- new THREE.Vector3(-f, f, +1),
- new THREE.Vector3(-f, f, +1),
- new THREE.Vector3(-f, -f, +1),
- ]
-
- positions.forEach(e=>e.z *= -1) //因为得到的rotation是camera的,作用在物体上要反向,所以这里反向一下
-
- //geometry.computeBoundingSphere();//?
- const line = LineDraw.createFatLine( positions, {material:getLineMat('frustum')})
-
- //line.scale.set(20, 20, 20);
- line.visible = false
- return line;
- }
- reMapCurvePercent(){ //因在不同点在相同位置旋转,由于间隔仅和位置距离相关,导致时间间隔为0,采取重新调整间隔的策略。
- var length = this.posCurve.points.length
-
- if(length<2){
- return this.newPointsPercents = []
- }
- var newPercents = [0];
-
-
-
- if(this.useDurSlice){ //已经设定好了每一段的duration的话
- let sums = [0]
- let sum = 0, last
- for(let i=0;i<length-1;i++){ //去掉最后一个duration,因为已到终点
- let duration = this.durations[i];
- sum += duration;
- last = duration;
- sums.push(sum)
- }
- for(let i=1;i<length;i++){
- newPercents.push(sum == 0 ? i/length : sums[i] / sum)
- }
-
- }else{
-
- const maxSpaceDur = this.duration / length //每两点之间修改间隔时间后,最大时间
- const durPerRad = 0.8 //每弧度应该占用的时间
- const minSpaceDur = Math.min(0.8, maxSpaceDur)//每两点之间修改间隔时间后,最小时间
- const maxAngleSpaceDur = THREE.Math.clamp(durPerRad * Math.PI, minSpaceDur, maxSpaceDur )// 最大可能差距是180度
-
-
-
- var percents = this.posCurve.pointsPercent;
-
-
-
- for(let i=1;i<length;i++){
- let diff = (percents[i] - percents[i-1]) * this.duration //间隔时间
- let percent
- let curMin = minSpaceDur
- if(diff < maxAngleSpaceDur){ //若小于最大旋转时间
- let rad = this.quaternions[i].angleTo(this.quaternions[i-1])
- curMin = THREE.Math.clamp(rad * durPerRad, minSpaceDur, maxSpaceDur)
-
- }
- diff = Math.max(diff, curMin)
- percent = newPercents[i-1] + (diff / this.duration) //得到新的percent
-
-
- newPercents.push(percent)
- }
-
- let maxPercent = newPercents[length-1] //最后一个,若扩充过时间,就会>1
- if( !math.closeTo(maxPercent, 1)){
- let scale = 1 / maxPercent //需要压缩的比例 <1 这一步会让实际得到的间隔更小
- newPercents = newPercents.map(e=> e*=scale )
- }
-
-
- }
-
- this.newPointsPercents = newPercents;
- //console.log(newPercents)
- }
- at(originPercent, delta, transitionRatio){
-
- originPercent = THREE.Math.clamp(originPercent, 0, 1)
- //修改第一层:起始时间
- let percent = originPercent;
- /* const easePercent = 0.3; //缓动占比 //如果能在所有从静止到运动的中间加缓动就好了呀:lastPos * 0.9 + currentPos * 0.1 ?
- if(originPercent < easePercent){
- console.log('easeIn')
- percent = easing.easeInSine(originPercent, 0, easePercent, easePercent) //currentTime, startY, wholeY, duration 选了一个衔接时接近斜率1的缓动函数
- }else if(originPercent > 1-easePercent){
- console.log('easeOut')
- percent = easing.easeOutSine(originPercent-(1-easePercent), 1-easePercent, easePercent, easePercent)
- } */
-
-
-
-
- let quaternion
-
- if(percent < 1){
- //修改第二层:使用每个点的重定位的 newPointsPercents
-
- this.currentIndex = this.newPointsPercents.findIndex(e=> e>percent ) - 1
- //假设每个节点的百分比是精确的,那么:
- let curIndexPercent = this.newPointsPercents[this.currentIndex];
- let nextIndexPercent = this.newPointsPercents[this.currentIndex+1];
- let progress = (percent - curIndexPercent) / (nextIndexPercent - curIndexPercent)//在这两个节点间的百分比
-
- //投影到原本的 posCurve.pointsPercent上:
- let curIndexOriPercent = this.posCurve.pointsPercent[this.currentIndex]
- let nextIndexOriPercent = this.posCurve.pointsPercent[this.currentIndex+1]
- percent = curIndexOriPercent + (nextIndexOriPercent - curIndexOriPercent) * progress
-
- let endQuaternion = this.quaternions[this.currentIndex+1]
- let startQuaternion = this.quaternions[this.currentIndex]
- quaternion = (new THREE.Quaternion()).copy(startQuaternion)
- lerp.quaternion(quaternion, endQuaternion)(progress)
-
-
- }else{
-
-
- this.currentIndex = this.posCurve.points.length - 1;
-
- quaternion = math.getQuaFromPosAim(this.posCurve.points[this.currentIndex], this.targets[this.currentIndex].position)
- }
-
-
- const position = this.posCurve.getPointAt(percent); // 需要this.posCurve.points.length>1 否则报错
-
-
- //console.log(this.currentIndex, originPercent)
- //缓动:
- var aimQua, aimPos;
- if(delta != void 0 ){
- if(Potree.settings.tourTestCameraMove){
- aimQua = this.frustum.quaternion.clone();
- aimPos = this.frustum.position.clone();
- }else{
- var camera = viewer.scene.getActiveCamera();
- aimQua = camera.quaternion.clone();
- aimPos = camera.position.clone();
- }
-
- transitionRatio = transitionRatio || 1 / Potree.settings.cameraAniSmoothRatio//渐变系数,越小缓动程度越高,越平滑
- transitionRatio *= delta * 60 //假设标准帧率为60fps,当帧率低时(delta大时)要降低缓动
- //console.log(transitionRatio, delta) //画面ui变化会使delta变大
- transitionRatio = THREE.Math.clamp(transitionRatio, 0, 1);
- lerp.quaternion(aimQua, quaternion)(transitionRatio) //每次只改变一点点
- lerp.vector(aimPos, position)(transitionRatio)
-
- }else{
- aimQua = quaternion; aimPos = position
- }
-
-
-
- let rotation = new THREE.Euler().setFromQuaternion(aimQua )
-
-
- const frame = {
- position: aimPos,
- rotation
- };
- return frame;
- }
- set(percent){
- this.percent = percent;
- }
-
- setVisible(visible){
- this.node.visible = visible;
-
- this.posCurve.visible = visible
- this.targets.forEach(e=>e.visible = visible )
- this.visible = visible;
- }
- setDuration(duration){
- if(duration != this.duration){
- this.duration = duration;
- if(this.quaternions.length == this.posCurve.points.length)this.reMapCurvePercent()
-
- }
- }
- getDuration(duration){
- return this.duration;
- }
- play(startOptions={}){
- if(this.onUpdate){
- return console.error('已经开始播放')
- }
-
-
-
- let startPercent = 0, currentIndex = 0
- if(startOptions.percent != void 0 ){
- startPercent = startOptions.percent
- }else if(startOptions.index){
- currentIndex = index
- //startPercent = index/(this.posCurve.points.length-1)
-
- startPercent = this.posCurve.pointsPercent[index]
- }
-
-
- //const tStart = performance.now();
-
-
- const duration = this.duration;
- this.originalyVisible = this.visible;
- Potree.settings.tourTestCameraMove || this.setVisible(false);
-
-
- let tStart, startTransitionRatio = 0.2
- let startDelay = 1/startTransitionRatio / 20 ;//因为缓动所以延迟开始,前面前都是at(0),使过渡到开始点位(但是依旧不能准确停在起始点,因为缓动是乘百分比有残留。所以直接平滑衔接到开始后的位置)
- let hasPlayedTime = 0
-
- let finishDelay = Potree.settings.cameraAniSmoothRatio / 60 * 3//结束后还需要多久时间才能大致达到缓动的最终目标
- let hasStoppedTime = 0
- this.onUpdate = (e) => {
- if(this.posCurve.points.length<2){
- if(this.posCurve.points.length == 1){
- viewer.scene.view.position.copy(this.posCurve.points[0]);
- viewer.scene.view.rotation = new THREE.Euler().setFromQuaternion(this.quaternions[0])
- }
- this.pause()
- return
- }
-
- let percent, transitionRatio
-
- if(tStart){
- let tNow = performance.now();
- let elapsed = (tNow - tStart) / 1000;
- percent = elapsed / duration + startPercent;
- }else{//从当前位置过渡到开始位置
- percent = 0
- hasPlayedTime += e.delta
- transitionRatio = startTransitionRatio;
- //console.log('延迟开始')
- if(hasPlayedTime > startDelay){
- tStart = performance.now();
- }
-
- }
-
- this.set(percent);
-
- const frame = this.at(percent, e.delta, transitionRatio);
-
- if(currentIndex != this.currentIndex){
- currentIndex = this.currentIndex
- console.log('updateCurrentIndex', currentIndex)
- this.dispatchEvent({type:'updateCurrentIndex', currentIndex })
- }
-
-
-
- if(!Potree.settings.tourTestCameraMove){
- viewer.scene.view.position.copy(frame.position);
- //viewer.scene.view.lookAt(frame.target);
- viewer.scene.view.rotation = frame.rotation;
- }
- this.updateFrustum(frame)
-
-
-
- if(percent >= 1){
- if(hasStoppedTime > finishDelay){
- this.pause()
- }else{
- hasStoppedTime += e.delta
- //console.log('延迟结束')
- }
-
- }
-
-
-
- };
- this.viewer.addEventListener("update", this.onUpdate);
-
-
- }
- pause(){
- this.setVisible(this.originalyVisible);
- this.viewer.removeEventListener("update", this.onUpdate);
- this.dispatchEvent('playDone')
- this.onUpdate = null
- }
- updateFrustum(frame){
- const frustum = this.frustum;
- if(this.posCurve.points.length>1){
- frustum.visible = true
- }else{
- frustum.visible = false;
- return
- }
-
- frame = frame || this.at(this.percent);
- frustum.position.copy(frame.position);
- //frustum.lookAt(...frame.target.toArray());
- frustum.rotation.copy(frame.rotation)
- }
- changeCallback(){
- this.posCurve.update()
- //this.targetCurve.update()
- this.targets.forEach(e=>e.update())
- this.updateFrustum()
- }
- dispose(){//add
- this.posCurve.dispose()
- //this.targetCurve.dispatchEvent({type:'dispose'})
- this.targets.forEach(e=>e.dispose())
- this.durations = []
- this.node.parent.remove(this.node);
- }
- }
- //scene.removeCameraAnimation
- //修改:不使用targetCurve作为target曲线,因为播放时posCuve的节点和targetCurve并没有对应,且使用target的曲线会使角度变化大的情况过渡生硬。
- // 改完旋转了。但是位置也有问题。速度完全和路程相关,当在同一位置设置多点时,这段的总时长为0. (是否要设置最小时长?不过也做不到 - -)
|