|
@@ -0,0 +1,735 @@
|
|
|
|
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
|
|
|
|
+
|
|
|
|
+import math from '../../utils/math.js'
|
|
|
|
+import Tween from '../../utils/Tween.js'
|
|
|
|
+import {easing, lerp} from '../../utils/transitions.js'
|
|
|
|
+
|
|
|
|
+//有的动画,如小狗,进来如果不play停在第一帧,mesh会错,爪子在前面;但如果都停在第一帧,动作有可能很奇怪
|
|
|
|
+
|
|
|
|
+const tweens = {}
|
|
|
|
+const maxClipFadeTime = Potree.settings.maxClipFadeTime//渐变时间 s
|
|
|
|
+/* const pathStates = new Map */
|
|
|
|
+//actions中可能包含没有动作的 如TPose
|
|
|
|
+
|
|
|
|
+//包括无动画的模型在内的各项属性的过渡
|
|
|
|
+
|
|
|
|
+const rot90Qua = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0),-Math.PI/2)
|
|
|
|
+
|
|
|
|
+export default class AnimationEditor extends THREE.EventDispatcher{
|
|
|
|
+ constructor(){
|
|
|
|
+ super()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ this.poseKeys = new Map //transform
|
|
|
|
+ this.descKeys = new Map //字幕
|
|
|
|
+ this.pathKeys = new Map //之前设置好的Path , 优先级高于pose
|
|
|
|
+ this.clipKeys = new Map //glb animation actions
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ this.duration = 0 //动画时长
|
|
|
|
+ this.time = 0 //当前播放时间
|
|
|
|
+ this.cursorTime = 0 //时间轴指针时间
|
|
|
|
+
|
|
|
|
+ this.keepDistance = true //focus的物体和相机保持不变的距离
|
|
|
|
+ this.poseTransition = false //pose缓动
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if(Potree.settings.isOfficial){
|
|
|
|
+ viewer.modules.MergeEditor.bus.addEventListener('changeSelect',()=>{
|
|
|
|
+ let targetObject = viewer.modules.MergeEditor.selected
|
|
|
|
+ targetObject = this.ifContainsModel(targetObject) ? targetObject : null
|
|
|
|
+ this.setCameraFollow(targetObject)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ addKey(model, keyType, key ){
|
|
|
|
+ let keys = this[keyType+'Keys'].get(model)
|
|
|
|
+ if(!keys){
|
|
|
|
+ keys = []
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let index = keys.findIndex(e=>e.time>key.time)
|
|
|
|
+ if(index == -1){
|
|
|
|
+ index = keys.length
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ keys = [...keys.slice(0,index), key, ...keys.slice(index,keys.length)]
|
|
|
|
+ this[keyType+'Keys'].set(model,keys)
|
|
|
|
+ this.updateTimeRange()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ removeKey(model, keyType, key ){
|
|
|
|
+ let keys = this[keyType+'Keys'].get(model)
|
|
|
|
+ if(!keys)return console.warn('removeKey没找到key')
|
|
|
|
+
|
|
|
|
+ let index = keys.indexOf(key)
|
|
|
|
+ if(index > -1){
|
|
|
|
+ if(keyType == 'clip'){
|
|
|
|
+ key.action.stop()
|
|
|
|
+ }
|
|
|
|
+ keys.splice(index,1)
|
|
|
|
+ this.updateTimeRange()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ reOrderKey(model, keyType, key ){
|
|
|
|
+ this.removeKey(model, keyType, key )
|
|
|
|
+ this.addKey(model, keyType, key )
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ at(time, delta, force){
|
|
|
|
+ this.dispatchEvent({type:'atTime', time}) //该时间可以大于本动画持续时间
|
|
|
|
+ this.cursorTime = time
|
|
|
|
+ /* if(time > this.duration + maxClipFadeTime/2){
|
|
|
|
+ for(let [model, keys] of this.clipKeys){
|
|
|
|
+ model.actions.forEach(a=>a.stop())
|
|
|
|
+ }
|
|
|
|
+ } */
|
|
|
|
+
|
|
|
|
+ let maxTime = this.duration+maxClipFadeTime/2
|
|
|
|
+ if(time >= maxTime) time = maxTime
|
|
|
|
+ if(this.time == time && !force)return
|
|
|
|
+ this.time = time //真实值
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ let oldDisToCam = this.camFollowObject?.length == 1 && this.keepDistance && this.camFollowObject[0].boundCenter.distanceTo(viewer.mainViewport.view.position)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ let transitionRatio = 0.05 * delta * 60 //渐变系数,越小缓动程度越高,越平滑 //假设标准帧率为60fps,当帧率低时(delta大时) 降低缓动。速度快时缓动太高会偏移路径
|
|
|
|
+ let transitionRatio2 = 0.8 * delta * 60
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ let posePathModels = [];
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ [this.poseKeys, this.pathKeys/* , this.clipKeys */].forEach((map)=>{
|
|
|
|
+ Array.from(map.keys()).forEach(model=>{
|
|
|
|
+ posePathModels.includes(model) || posePathModels.push(model)
|
|
|
|
+ })
|
|
|
|
+ })
|
|
|
|
+ /*
|
|
|
|
+ 路径>关键帧。但是如果每条路径开头和结尾以及过渡时没有关键帧,保持路径开头和结尾的姿态
|
|
|
|
+
|
|
|
|
+ */
|
|
|
|
+ posePathModels.forEach(model=>{
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ let pathKeys = this.pathKeys.get(model) || []
|
|
|
|
+ let poseKeys = this.poseKeys.get(model) || []
|
|
|
|
+
|
|
|
|
+ let atPath //是否在path中 至多只有一个
|
|
|
|
+ let lastPath
|
|
|
|
+ let nextPath
|
|
|
|
+
|
|
|
|
+ if(pathKeys.length){
|
|
|
|
+ pathKeys.find(key=>{
|
|
|
|
+ if(key.path.points.length < 2) return
|
|
|
|
+ let startToFade = key.time - maxClipFadeTime/2
|
|
|
|
+ let endFade = key.time + key.dur + maxClipFadeTime/2
|
|
|
|
+ atPath = time >= key.time && time <= key.time + key.dur
|
|
|
|
+ if(atPath){
|
|
|
|
+ atPath = key //找到一个就退出
|
|
|
|
+ return true
|
|
|
|
+ }
|
|
|
|
+ if(key.time + key.dur < time) lastPath = key
|
|
|
|
+ else if(key.time > time && !nextPath) nextPath = key
|
|
|
|
+
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if(poseKeys.length){
|
|
|
|
+ tweens.scale = new Tween(poseKeys.map(e=>e.time), poseKeys.map(e=>e.scale))
|
|
|
|
+ model.scale.copy(tweens.scale.lerp(time))
|
|
|
|
+
|
|
|
|
+ }else{
|
|
|
|
+ /* if(pathKeys.length){
|
|
|
|
+ model.quaternion.copy(model.defaultAniPose?.quaternion || new THREE.Quaternion()) //设置路径朝向前要先还原
|
|
|
|
+ } */
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if(atPath){//沿着curve行走,目视curve前方 (参照CameraAnimationCurve,搜quaFromCurveTan)
|
|
|
|
+ let percent = THREE.Math.clamp((time - atPath.time) / atPath.dur, 0, 1)
|
|
|
|
+ let {position , quaternion} = this.getPoseAtPathKey(atPath, percent, model) //模型文件先保证其center在脚底,如果要我手动将bound底部对齐路径高度再说
|
|
|
|
+ model.position.copy(position);
|
|
|
|
+ model.quaternion.copy(quaternion)
|
|
|
|
+
|
|
|
|
+ model.atPath = atPath
|
|
|
|
+
|
|
|
|
+ }else{
|
|
|
|
+ model.atPath = null
|
|
|
|
+ poseKeys = poseKeys.slice()
|
|
|
|
+ let addPathToPoseKey = (pathKey, percent)=>{ //把当前前后的path姿态加入帧
|
|
|
|
+ let {position , quaternion} = this.getPoseAtPathKey(pathKey, percent, model)
|
|
|
|
+ let fakeKey = {
|
|
|
|
+ isPath : true,
|
|
|
|
+ time: pathKey.time + pathKey.dur * percent,
|
|
|
|
+ pos:position , qua:quaternion
|
|
|
|
+ }
|
|
|
|
+ let index = poseKeys.findIndex(e=>e.time > fakeKey.time)
|
|
|
|
+ if(index == -1){
|
|
|
|
+ index = poseKeys.length
|
|
|
|
+ }
|
|
|
|
+ poseKeys = [...poseKeys.slice(0,index), fakeKey, ...poseKeys.slice(index,poseKeys.length)]
|
|
|
|
+ }
|
|
|
|
+ lastPath && addPathToPoseKey(lastPath, 1)
|
|
|
|
+ nextPath && addPathToPoseKey(nextPath, 0)
|
|
|
|
+
|
|
|
|
+ if(poseKeys.length){
|
|
|
|
+ tweens.pos = new Tween(poseKeys.map(e=>e.time), poseKeys.map(e=>e.pos))
|
|
|
|
+ model.position.copy(tweens.pos.lerp(time))
|
|
|
|
+ tweens.qua = new Tween(poseKeys.map(e=>e.time), poseKeys.map(e=>e.qua))
|
|
|
|
+ model.quaternion.copy(tweens.qua.lerp(time))
|
|
|
|
+ /* let poseKeys2 = poseKeys.filter(e=>e.isPath)//妈呀为什么这么写我忘了
|
|
|
|
+ if(poseKeys2.length ){
|
|
|
|
+ tweens.qua = new Tween(poseKeys2.map(e=>e.time), poseKeys2.map(e=>e.qua))
|
|
|
|
+ model.quaternion.copy(tweens.qua.lerp(time))
|
|
|
|
+ } */
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if(poseKeys.length || pathKeys.length){
|
|
|
|
+ model.dispatchEvent('position_changed')
|
|
|
|
+ model.dispatchEvent('rotation_changed')
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ for(let [model, keys] of this.clipKeys){
|
|
|
|
+ if(keys.length == 0) continue
|
|
|
|
+
|
|
|
|
+ let weights = keys.map((key,i)=>{ //计算每个动作权重(幅度)。
|
|
|
|
+
|
|
|
|
+ /* if(delta == void 0){//无缓动 但会造成和缓动时动作time不同
|
|
|
|
+ return time >= key.time && time <= key.time + key.dur ? 1 : 0
|
|
|
|
+ } */
|
|
|
|
+ key.index_ = i
|
|
|
|
+
|
|
|
|
+ let fadeTimeStart = Math.min(maxClipFadeTime, key.dur, (keys[i-1]?.dur || maxClipFadeTime )) / 2 //过渡时间不超过当前和前一个的 half of dur
|
|
|
|
+ let fadeTimeEnd = Math.min(maxClipFadeTime, key.dur, (keys[i+1]?.dur || maxClipFadeTime )) / 2 //过渡时间不超过当前和后一个的 half of dur
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ let startTime1 = key.time - fadeTimeStart
|
|
|
|
+ let endTime1 = key.time + key.dur + fadeTimeEnd
|
|
|
|
+ let startTime2 = key.time + fadeTimeStart
|
|
|
|
+ let endTime2 = key.time + key.dur - fadeTimeEnd
|
|
|
|
+
|
|
|
|
+ key.action.tempSW_ = {scale:0,weight:0,time: null, sameLinks:[]},
|
|
|
|
+ key.tempTime_ = THREE.Math.clamp(time - key.time, 0, key.dur) //time - startTime1 //当前动作时间
|
|
|
|
+ key.startTime1 = startTime1
|
|
|
|
+ key.endTime1 = endTime1
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if(i==0 && time<startTime2){//开始前维持第一个动作
|
|
|
|
+ return 1
|
|
|
|
+ }else if(i == keys.length-1 && time > endTime2){//所有动作播完后维持最后一个动作
|
|
|
|
+ return 1
|
|
|
|
+ }else{
|
|
|
|
+ if(time < startTime1 || time > endTime1)return 0 //out bound
|
|
|
|
+
|
|
|
|
+ if(time >= startTime2 && time <= endTime2 ) return 1
|
|
|
|
+
|
|
|
|
+ if(time < startTime2 ){
|
|
|
|
+ return Potree.math.linearClamp(time, [startTime1,startTime2],[0,1])
|
|
|
|
+ }else{
|
|
|
|
+ return Potree.math.linearClamp(time, [endTime2,endTime1],[1,0])
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })//最多有两个>0的,在过渡
|
|
|
|
+
|
|
|
|
+ let animateActions = [] //在播的动作
|
|
|
|
+ keys.forEach((key,i)=>{ weights[i]>0 && !animateActions.includes(key.action) && (key.action.tempSW_ = {scale:0,weight:0,time: null, sameLinks:[]}, animateActions.push(key.action) )})
|
|
|
|
+ //万一前后是一个动作…… 所以用tempSW_计算总值
|
|
|
|
+
|
|
|
|
+ keys.forEach((key,i)=>{ //要找到当前action之前所有不间断的所有key,他们之间要连续播放
|
|
|
|
+ if(animateActions.includes(key.action)){
|
|
|
|
+ if(key.startTime1 < time){//已播部分的key
|
|
|
|
+ let last = key.action.tempSW_.sameLinks[key.action.tempSW_.sameLinks.length - 1]
|
|
|
|
+ if(last){
|
|
|
|
+ if( key.index_ == last.index_ + 1 && key.startTime1 <= last.endTime1 ){ //相连
|
|
|
|
+ key.action.tempSW_.sameLinks.push(key)
|
|
|
|
+ }else{
|
|
|
|
+ key.action.tempSW_.sameLinks = [key] //clear
|
|
|
|
+ }
|
|
|
|
+ }else{
|
|
|
|
+ key.action.tempSW_.sameLinks = [key]
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ keys.forEach((key,i)=>{
|
|
|
|
+ if(animateActions.includes(key.action)){
|
|
|
|
+ let weight = weights[i] * key.weight //权重乘以自身幅度
|
|
|
|
+ if(weight>0){//最多两个
|
|
|
|
+ key.action.play()
|
|
|
|
+ key.action.paused = true //停在某帧 //如果没有点击该动作块的话 不停
|
|
|
|
+
|
|
|
|
+ key.action.tempSW_.weight += weight
|
|
|
|
+ key.tempTime_ >= 0 && (key.action.tempSW_.scale = key.speed) //相同动作不允许叠加速度
|
|
|
|
+
|
|
|
|
+ if(key.action.tempSW_.time == null){//如果两个动作相同 只需在第一个计算出总和
|
|
|
|
+ let timeSum = 0
|
|
|
|
+ key.action.tempSW_.sameLinks.forEach(key_ =>{
|
|
|
|
+ timeSum += (key_.tempTime_ % (key_.action._clip.duration / key_.speed)) * key_.speed //相同动作可能速度不同,算出每个clip的时间
|
|
|
|
+ })
|
|
|
|
+ key.action.tempSW_.time = timeSum % key.action._clip.duration
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //(老版本,过渡时播放时间会延长一点,有交集):
|
|
|
|
+ //key.action.tempSW_.time == null && (key.action.tempSW_.time = key.tempTime_) //相同动作优先用前一个的时间
|
|
|
|
+ //key.action.tempSW_.scale += key.speed // * weights[i] //乘以weight在开始和结束作为缓动效果好,但是不好计算实时time
|
|
|
|
+ //speed time都没有交集,只有weight有,为了过渡
|
|
|
|
+ }
|
|
|
|
+ }else{
|
|
|
|
+ key.action.stop() //不启动动画
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ animateActions.forEach(action=>{
|
|
|
|
+ action.setEffectiveTimeScale(action.tempSW_.scale) //speed 只有没paused时有效 这里都paused的所以没用
|
|
|
|
+ action.setEffectiveWeight(action.tempSW_.weight );
|
|
|
|
+ action.time = action.tempSW_.time //(action.tempSW_.time % (action._clip.duration / action.tempSW_.scale)) * action.tempSW_.scale //只有paused时有效
|
|
|
|
+ //console.log('action', action._clip.name, action.time, action.weight, action.tempSW_.scale )
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ //model.mixer.timeScale = 1 ;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ viewer.objs.children.forEach(obj=>{
|
|
|
|
+ if(!obj.actions?.length)return
|
|
|
|
+ let clipState = obj.actions.map(action=>{
|
|
|
|
+ let played = action._mixer._isActiveAction( action );
|
|
|
|
+ let paused = action.paused
|
|
|
|
+ let time = action.time
|
|
|
|
+ let weight = action.weight
|
|
|
|
+ return {played,paused,time,weight}
|
|
|
|
+ })
|
|
|
|
+ clipState = JSON.stringify(clipState)
|
|
|
|
+ if(obj.clipState != clipState){//动作是否改变
|
|
|
|
+ obj.traverse(e=>e.isSkinnedMesh && (e.boundingSphere = null)) //动画会导致bound改变,清空,raycast时重新计算bound,否则hover不到模型
|
|
|
|
+ obj.clipChanged = true
|
|
|
|
+ }
|
|
|
|
+ obj.clipState = clipState
|
|
|
|
+
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ {
|
|
|
|
+
|
|
|
|
+ if(this.camFollowObject && !viewer.scene.monitors.some(e=>e.isWatching)){//in front of model
|
|
|
|
+ if(this.camFollowObject.length == 1){
|
|
|
|
+ let model = this.camFollowObject[0]
|
|
|
|
+ if(viewer.images360.latestRequestMode == 'showPointCloud'){
|
|
|
|
+ if(this.camFaceToObject){
|
|
|
|
+ let dis = 4;
|
|
|
|
+ let dir = new THREE.Vector3(0,0.1,1).normalize()//稍微朝上
|
|
|
|
+ dir.multiplyScalar(dis).applyQuaternion(model.quaternion)
|
|
|
|
+ let pos = new THREE.Vector3().addVectors(model.boundCenter, dir)
|
|
|
|
+ viewer.mainViewport.view.position.copy(pos)
|
|
|
|
+ viewer.mainViewport.view.lookAt(model.boundCenter)
|
|
|
|
+ }else if(this.keepDistance){ //不改镜头方向 保持一定角度。如果要改镜头方向,把lookAt提前
|
|
|
|
+ viewer.mainViewport.view.position.subVectors(model.boundCenter, viewer.mainViewport.view.direction.clone().multiplyScalar(oldDisToCam))
|
|
|
|
+ viewer.mainViewport.view.radius = oldDisToCam
|
|
|
|
+ }else{
|
|
|
|
+ viewer.mainViewport.view.lookAt(model.boundCenter)
|
|
|
|
+ }
|
|
|
|
+ }else{
|
|
|
|
+ viewer.mainViewport.view.lookAt(model.boundCenter)
|
|
|
|
+ }
|
|
|
|
+ }else{
|
|
|
|
+ viewer.modules.MergeEditor.focusOn(this.camFollowObject, 0, true/* ,false,dirAve */)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ viewer.dispatchEvent('content_changed')
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ getPoseAtPathKey(key, percent, model){
|
|
|
|
+ let delta = 0.001
|
|
|
|
+ let percent2 = percent + delta
|
|
|
|
+ let curve = key.path.curve.clone()
|
|
|
|
+ if(key.reverse) curve.points.reverse()
|
|
|
|
+ let position = curve.getPointAt(percent);
|
|
|
|
+ let pathQua, quaternion
|
|
|
|
+
|
|
|
|
+ if(percent2 <= 1){
|
|
|
|
+ let position2 = curve.getPointAt(percent2);
|
|
|
|
+ pathQua = math.getQuaFromPosAim(position2, position)
|
|
|
|
+ }else{
|
|
|
|
+ percent2 = percent - delta
|
|
|
|
+ let position2 = curve.getPointAt(percent2);
|
|
|
|
+ pathQua = math.getQuaFromPosAim(position, position2)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pathQua.multiplyQuaternions( pathQua, rot90Qua ); //这是当模型导进来就旋转正确时的quaternion
|
|
|
|
+ key.curQua_ = pathQua.clone() //记录下
|
|
|
|
+ if(model.quaAtPath){
|
|
|
|
+ quaternion = new THREE.Quaternion().multiplyQuaternions(pathQua, model.quaAtPath)
|
|
|
|
+ }else{
|
|
|
|
+ quaternion = pathQua.clone()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ //model && quaternion.multiplyQuaternions( quaternion, model.quaternion ); //应用当前已有的quaternion
|
|
|
|
+ //如果要将模型底部中心对准路径,需要先修改好模型scale ,然后boundingBox中心应用scale和qua, 加到position里
|
|
|
|
+ //目前两个人物模型刚好模型pivot在脚底,如果是其他物体甚至直接用curve的朝向不太对,没有明确朝向。除非所有模型都保持上路径前的朝向
|
|
|
|
+ //或者pos的z还用之前的
|
|
|
|
+ //产品说位置偏移不管它,因为路径可以隐藏和修改。只要记录相对旋转即可。
|
|
|
|
+ return {position, quaternion}
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ getModelQuaAtPath(model){ //当前时间在路径上时,旋转模型后立即执行该函数,获取相对旋转值
|
|
|
|
+ if(!model.atPath)return
|
|
|
|
+ let qua = new THREE.Quaternion().multiplyQuaternions(model.atPath.curQua_.clone().invert(), model.quaternion)
|
|
|
|
+
|
|
|
|
+ //console.log('getModelQuaAtPath',qua)
|
|
|
|
+ model.quaAtPath = qua //相对旋转
|
|
|
|
+ return qua
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ play({ time = -maxClipFadeTime/2}={}){//动画时长比duration多一个maxClipFadeTime,为了给开始和结束动画过渡
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ this.updateTimeRange()
|
|
|
|
+ this.playing && this.pause()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ let maxTime = this.duration+maxClipFadeTime/2
|
|
|
|
+ this.playing = true
|
|
|
|
+
|
|
|
|
+ this.cursorTime = time
|
|
|
|
+
|
|
|
|
+ this.onUpdate = (e)=>{
|
|
|
|
+ this.cursorTime += e.delta
|
|
|
|
+ if(!Potree.settings.isOfficial && time > maxTime) this.cursorTime = maxTime
|
|
|
|
+ this.at(this.cursorTime, e.delta)
|
|
|
|
+ if(!Potree.settings.isOfficial && time > maxTime) {
|
|
|
|
+ this.dispatchEvent('stop')
|
|
|
|
+ for(let [model, keys] of this.clipKeys){
|
|
|
|
+ model.actions.forEach(a=>a.stop())
|
|
|
|
+ }
|
|
|
|
+ this.pause()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ viewer.addEventListener("update_start", this.onUpdate);
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pause(){
|
|
|
|
+ this.playing = false
|
|
|
|
+ viewer.removeEventListener("update_start", this.onUpdate);
|
|
|
|
+ /* for(let [model, keys] of this.clipKeys){
|
|
|
|
+ model.actions.forEach(a=>a.stop())
|
|
|
|
+ } */
|
|
|
|
+ viewer.dispatchEvent('content_changed')
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ setCameraFollow(camFollowObject){//for test
|
|
|
|
+ this.camFollowObject = camFollowObject
|
|
|
|
+ if(!camFollowObject)return
|
|
|
|
+
|
|
|
|
+ //Potree.settings.displayMode = 'showPointCloud'
|
|
|
|
+
|
|
|
|
+ if(!(this.camFollowObject instanceof Array)){//支持相机跟随多个物体,对着bound的中心
|
|
|
|
+ this.camFollowObject = [this.camFollowObject]
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.camFollowObject = this.camFollowObject.map(object=>{
|
|
|
|
+ if(typeof object == 'string'){
|
|
|
|
+ return viewer.objs.children.find(e=>e.name == object)
|
|
|
|
+ }else{
|
|
|
|
+ return object
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ updateTimeRange(){
|
|
|
|
+ let maxTime = 0
|
|
|
|
+ for(let [model, keys] of this.poseKeys){
|
|
|
|
+ keys.length>0 && (maxTime = Math.max(maxTime, keys[keys.length - 1].time))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for(let [model, keys] of this.clipKeys){
|
|
|
|
+ keys.length>0 && (maxTime = Math.max(maxTime, keys[keys.length - 1].time + keys[keys.length - 1].dur))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for(let [model, keys] of this.pathKeys){
|
|
|
|
+ keys.length>0 && (maxTime = Math.max(maxTime, keys[keys.length - 1].time + keys[keys.length - 1].dur))
|
|
|
|
+ }
|
|
|
|
+ this.duration = maxTime //不算开始和结束动画的过渡时间的话
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /* for(let [model, keys] of this.clipKeys){
|
|
|
|
+ max = Math.max(maxTime, keys[keys.length - 1].time + keys[keys.length - 1].dur)
|
|
|
|
+ } */
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /* removeModelCallback(model){
|
|
|
|
+ this.poseKeys.get
|
|
|
|
+ } */
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /////////////////////////////////
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ addPoseKey({model,time,index }={}){
|
|
|
|
+ /* if(replace){
|
|
|
|
+ this.removeKey(model,'pose', index)
|
|
|
|
+ } */
|
|
|
|
+ let keys = this.poseKeys.get(model)
|
|
|
|
+ if(!keys){
|
|
|
|
+ keys = []
|
|
|
|
+ }
|
|
|
|
+ let key = {
|
|
|
|
+ time,
|
|
|
|
+ qua: model.quaternion.clone(),
|
|
|
|
+ scale: model.scale.clone(),
|
|
|
|
+ pos: model.position.clone()
|
|
|
|
+ }
|
|
|
|
+ if(index == void 0)index = keys.length
|
|
|
|
+ keys = [...keys.slice(0,index), key, ...keys.slice(index,keys.length)]
|
|
|
|
+ this.poseKeys.set(model,keys)
|
|
|
|
+ return key
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ addClipKey({model, time, index, dur, actionIndex, weight=1, speed=1/* , replace */}={}){
|
|
|
|
+
|
|
|
|
+ /* if(replace){
|
|
|
|
+ this.removeKey(model,'clip',index)
|
|
|
|
+ } */
|
|
|
|
+ let keys = this.clipKeys.get(model)
|
|
|
|
+ if(!keys){
|
|
|
|
+ keys = []
|
|
|
|
+ }
|
|
|
|
+ let key = {
|
|
|
|
+ time, //startTime
|
|
|
|
+ dur,
|
|
|
|
+ action: model.actions[actionIndex],
|
|
|
|
+ speed, weight,
|
|
|
|
+ }
|
|
|
|
+ if(index == void 0)index = keys.length
|
|
|
|
+ keys = [...keys.slice(0,index), key, ...keys.slice(index,keys.length)]
|
|
|
|
+ this.clipKeys.set(model,keys)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ addPathKey({model, time, index, dur, path/* , replace */ }={}){//what if path is deleted ?
|
|
|
|
+ /* if(replace){
|
|
|
|
+ this.removeKey(model,'path',index)
|
|
|
|
+ } */
|
|
|
|
+ let keys = this.pathKeys.get(model)
|
|
|
|
+ if(!keys){
|
|
|
|
+ keys = []
|
|
|
|
+ }
|
|
|
|
+ let key = {
|
|
|
|
+ time, //startTime
|
|
|
|
+ dur,
|
|
|
|
+ path,
|
|
|
|
+ }
|
|
|
|
+ if(index == void 0)index = keys.length
|
|
|
|
+ keys = [...keys.slice(0,index), key, ...keys.slice(index,keys.length)]
|
|
|
|
+ this.pathKeys.set(model,keys)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ addDescKey({model, time, index, dur, desc/* , replace */ }={}){
|
|
|
|
+ /* if(replace){
|
|
|
|
+ this.removeKey(model,'desc',index)
|
|
|
|
+ } */
|
|
|
|
+ let keys = this.descKeys.get(model)
|
|
|
|
+ if(!keys){
|
|
|
|
+ keys = []
|
|
|
|
+ }
|
|
|
|
+ let key = {
|
|
|
|
+ time, //startTime
|
|
|
|
+ dur,
|
|
|
|
+ desc,
|
|
|
|
+ }
|
|
|
|
+ if(index == void 0)index = keys.length
|
|
|
|
+ keys = [...keys.slice(0,index), key, ...keys.slice(index,keys.length)]
|
|
|
|
+ this.descKeys.set(model,keys)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ save(){//for test, 注意保证每个模型名字不同
|
|
|
|
+ let data = {poseKeys:{}, clipKeys:{}}
|
|
|
|
+ for(let [model, keys] of this.clipKeys){
|
|
|
|
+ data.clipKeys[model.name] = keys.map(key=>{
|
|
|
|
+ return {
|
|
|
|
+ actionIndex: model.actions.indexOf(key.action),
|
|
|
|
+ time: key.time, dur:key.dur, weight:key.weight, speed:key.speed
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for(let [model, keys] of this.poseKeys){
|
|
|
|
+ data.poseKeys[model.name] = keys.map(key=>{
|
|
|
|
+ return {
|
|
|
|
+ qua: key.qua.toArray(), pos: key.pos.toArray(), scale:key.scale.toArray(),
|
|
|
|
+ time: key.time,
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ console.log(JSON.stringify(data))
|
|
|
|
+ return data
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ buildFromData(data){
|
|
|
|
+ if(typeof data == 'string'){
|
|
|
|
+ data = JSON.parse(data)
|
|
|
|
+ }
|
|
|
|
+ for(let name in data.poseKeys){
|
|
|
|
+ let model = viewer.objs.children.find(e=>e.name == name)
|
|
|
|
+ if(!model){
|
|
|
|
+ console.warn('没找到pose模型',name)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ let keys = data.poseKeys[name].map(e=>{
|
|
|
|
+ return {
|
|
|
|
+ qua: new THREE.Quaternion().fromArray(e.qua),
|
|
|
|
+ pos: new THREE.Vector3().fromArray(e.pos),
|
|
|
|
+ scale: new THREE.Vector3().fromArray(e.scale),
|
|
|
|
+ time: e.time
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ this.poseKeys.set(model,keys)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for(let name in data.clipKeys){
|
|
|
|
+ let model = viewer.objs.children.find(e=>e.name == name)
|
|
|
|
+ if(!model){
|
|
|
|
+ console.warn('没找到clip模型',name)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ let keys = data.clipKeys[name].map(e=>{
|
|
|
|
+ return {
|
|
|
|
+ action: model.actions[e.actionIndex],
|
|
|
|
+ time: e.time, dur: e.dur, weight:e.weight, speed:e.speed
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ this.clipKeys.set(model,keys)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ ifContainsModel(model){//动画帧里是否包含它
|
|
|
|
+ return [this.poseKeys, this.pathKeys, this.clipKeys].some((e)=>{
|
|
|
|
+ return e.has(model)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+function executeCrossFade( startAction, endAction, duration ) {
|
|
|
|
+ // Not only the start action, but also the end action must get a weight of 1 before fading
|
|
|
|
+ // (concerning the start action this is already guaranteed in this place)
|
|
|
|
+ if ( endAction ) {
|
|
|
|
+
|
|
|
|
+ setWeight( endAction, 1 );
|
|
|
|
+ endAction.time = 0;
|
|
|
|
+
|
|
|
|
+ if ( startAction ) {
|
|
|
|
+ // Crossfade with warping
|
|
|
|
+ startAction.crossFadeTo( endAction, duration, true );
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ // Fade in
|
|
|
|
+ endAction.fadeIn( duration );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ // Fade out
|
|
|
|
+ startAction.fadeOut( duration );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+function setWeight( action, weight ) {
|
|
|
|
+
|
|
|
|
+ action.enabled = true;
|
|
|
|
+ window.ani1 || action.setEffectiveTimeScale( 1 );
|
|
|
|
+ action.setEffectiveWeight( weight );
|
|
|
|
+
|
|
|
|
+} */
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* autoActionSpeed(action){ //要获取这段时间走过的路程很难,还是延期吧. 而且一段动作要对应多个速度不同的位移,是不可能的。
|
|
|
|
+ return dis / dur
|
|
|
|
+} */
|
|
|
|
+/*
|
|
|
|
+
|
|
|
|
+ 动作自动计算步伐 幅度(weight)或 速度
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ timeScale * modelStepSizeRatio * weight = dis / dur
|
|
|
|
+ 速度 每个模型的步长系数 幅度
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+如果人的bone attach 物品,物品就被add到人身上,需要click出物品
|
|
|
|
+//测试:为人加物体, 需要先选中物品
|
|
|
|
+let obj = viewer.modules.MergeEditor.selected
|
|
|
|
+viewer.objs.children.find(e=>e.name == 'Man.glb').skeletonHelper.bones[34].attach(obj); //左手骨和物品绑定
|
|
|
|
+//viewer.objs.children.find(e=>e.name == 'Soldier.glb').skeletonHelper.bones[9].attach(obj); //右手骨和物品绑定
|
|
|
|
+obj.updateMatrixWorld()
|
|
|
|
+obj.dispatchEvent({type:'position_changed',byControl:true })
|
|
|
|
+obj.dispatchEvent({type:'rotation_changed',byControl:true })
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+物体带动骨骼自动做动作 setAniIK
|
|
|
|
+ */
|