xzw 1 месяц назад
Родитель
Сommit
3a00694bd2

+ 75 - 48
examples/splatter.html

@@ -68,21 +68,23 @@
 		</div>
 		<div id="potree_sidebar_container"> </div>
 	</div>
-    <div id='move_Btns'  >
+    <!-- <div id='move_Btns'  >
         <button name='forward2' > <span><<</span></button>
         <button name='forward' > <span><</span></button>
         <button name='backward' ><span>></span></button>
         <button name='backward2' ><span>>></span></button>
-    </div>
+    </div> -->
     <!-- <script src="https://webapi.amap.com/maps?v=2.0&key=4578048f6e03386759d5609401e738d3"></script> -->
 	<script type="module">
 
-	import * as THREE from "../libs/three.js/build/three.module.js";
+	//import * as THREE from "../libs/three.js/build/three.module.js";
     import browser from '../src/custom/utils/browser.js' //这里必须加.js
-    window.THREE = THREE 
-   
-    Potree.config.view.near = 0.001,  Potree.config.view.far = 1e3  //same as splatter  
-    if(browser.isMobile()){
+    //window.THREE = THREE 
+    
+    Potree.settings.showStats  = true
+    Potree.config.view.near = 0.001,  Potree.config.view.far = 1e3  //same as splatter 
+    let log =  browser.isMobile() 
+    if(log){//
         //window.addEventListener("load", ()=>{
             var textarea = document.createElement('textarea');
             textarea.id = "consoleLog";
@@ -107,7 +109,7 @@
              
         //}) 
         
-        {
+        /*{
             
             let btns = document.querySelector('#move_Btns')    
             Array.from(btns.children).forEach((e,i)=>{
@@ -135,29 +137,60 @@
                 e.addEventListener('pointerout', pointerup)
             })  
             
-            document.addEventListener('contextmenu', function(e) {
-                if (e.target.classList.contains('no-context-menu')) {
-                    e.preventDefault();
-                }
-            });
-        }
+            
+        }*/
         
     }
     
     var splatId = browser.urlHasValue('m',true)  
     var urlAtSplatter = !browser.urlHasValue('urlAtCurrent') ; 
+    
+    Potree.settings.firstCtrlRotInvSmooth = true 
+    Potree.settings.rotAroundPoint = false  
+    Potree.settings.noAA = true 
+    
     //Potree.start(document.getElementById("potree_render_area"),null, null,{noMap:true}); 
-    new Potree.Viewer(document.getElementById("potree_render_area") , null, {noMap:true});
+    new Potree.Viewer(document.getElementById("potree_render_area") , null, {noMap:true, enableJoy:true});
     viewer.background.set('#000')
     Potree.settings.orbitCtlMoveFree = true
-    
+    let dom = document.querySelector('#mapGaode')
+    if(dom){
+        //viewer.splatter.logArea = dom 
+        dom.style.height = '134px',         dom.style.width = '200px'
+        dom.style['font-size'] = '14px'
+        
+        window.logAreaTool = {
+            area: dom,
+            list:{},
+            change(name, text, level=0){
+                this.list[name] = {text, level}
+                this.update()
+            },
+            remove(name){
+                delete this.list[name]
+                this.update()
+            },
+            update(){
+                let text = ''
+                let objects = Object.values(this.list)
+                objects.sort((a,b)=>{return b.level - a.level})
+                for(let i=0, len=objects.length; i<len; i++){
+                    text += objects[i].text + '<br>'
+                }
+                this.area.innerHTML = text
+            }
+        }
+    } 
+ 
     if(splatId === ''){
         splatId = 'SG-t-jp-zSUztbgFpmt', urlAtSplatter = true
     }
    
     let localProfile = {
         'SG-t-WSs5eaQJLoc' :{
-            view : {"yaw":-1.6062499999999997,"pitch":0.13850996852046182,"position":{"x":-42.06086329177142,"y":3.3968851133538633,"z":-1.9854159672413823}}
+            //view : {"yaw":-1.6062499999999997,"pitch":0.13850996852046182,"position":{"x":-42.06086329177142,"y":3.3968851133538633,"z":-1.9854159672413823}}
+             view : {"yaw":-1.6034087578950877,"pitch":0.1454619081473329,"position":{"x":-41.71435321819845,"y":3.2051723875245823,"z":-1.808349204344115}}
+        
         }
     }[splatId]
     
@@ -165,16 +198,10 @@
     
     
     let applyConfig = (config)=>{
-        new Potree.Splatter(viewer,config); 
+        let splatter = new Potree.Splatter(viewer,config);
+        splatter.visible = true        
         config.view && viewer.mainViewport.view.applyJson(config.view) 
          
-        let dom = document.querySelector('#mapGaode')
-        if(dom){
-            viewer.splatter.logArea = dom 
-            dom.style.height = '43px',         dom.style.width = '200px'
-            dom.style['font-size'] = '14px'
-        } 
- 
     }
     
     if(splatId!=''){
@@ -205,48 +232,48 @@
             applyConfig(config)
             
             let btn1 = document.querySelector('#potree_render_area [value=">>全景"]')
-            btn1.value = '停lod'
+            btn1.style.right = '27%'
+            /*btn1.value = '停lod'
             btn1.onclick = ()=>{ 
                 btn1.value = btn1.value == '停lod' ? '开lod' : '停lod' 
                 viewer.splatter.renderer.lodder.pauseLod = btn1.value == '停lod' ? 0 : 1
+            }*/
+            btn1.value = '停碰撞'
+            btn1.onclick = ()=>{ 
+                btn1.value = btn1.value == '停碰撞' ? '开碰撞' : '停碰撞' 
+                window.stopColl = btn1.value == '停碰撞' ? 0 : 1 
             }
+             
+            
             let btn2 = document.querySelector('#potree_render_area [value="隐藏点云"]')
-            btn2.value = '停sort' 
+            /*btn2.value = '停sort' 
             btn2.onclick = ()=>{
                 btn2.value = btn2.value == '停sort' ? '开sort' : '停sort' 
                 viewer.splatter.renderer.lodder.pauseSort = btn2.value == '停sort' ? 0 : 1
+            }*/
+            btn2.value = 'switchColl' 
+            btn2.onclick = ()=>{ 
+                viewer.collider.toggleMesh()
             }
             
+            browser.urlHasValue('character') && viewer.setControls(viewer.characterCtrl)
+         
+            
+            
+            
             
         })
-    
+        
     
     } 
-     
-    Potree.settings.rotAroundPoint = false  
-     
+    Potree.settings.dblToFocusPoint = 1
     viewer.scene.axisArrow.visible = true  
+    viewer.scene.axisArrow.position.z = 20
     //example: https://192.168.0.59:1234/examples/splatter.html?m=rly-jb1  
     Potree.config.view.fov = 50  
     Potree.settings.keepMinFov = true 
     viewer.setFOV(50)//原本70,在手机端比splatter的点多,增大fov会减少点
-    Potree.settings.intersectWhenHover = false   //降cpu
-    //viewer.scene.axisArrow.visible = true // 会导致效果不对还警告?!!!!
-    
-     setTimeout(()=>{
-         //console.log('stop lod and sort!  ')
-        //viewer.splatter.renderer.lodder.pauseLod = 1
-        //viewer.splatter.renderer.lodder.pauseSort = 1
-        
-        //viewer.setControls(viewer.orbitControls)
-        
-        
-      //  setInterval(()=>{
-      //      viewer.controls.yawDelta -= 0.002
-      //  },8)
-        
-    },48200)  
-    
+    Potree.settings.intersectWhenHover = false   //降cpu 
 	</script>
 	
 	

+ 3 - 2
src/custom/materials/DepthBasicMaterial.js

@@ -122,8 +122,9 @@ export default class DepthBasicMaterial extends THREE.ShaderMaterial{
                 delete this.defines.useDepth 
             }
             this.realUseDepth = useDepth
-            if(this.autoDepthTest)this.depthWrite = this.depthTest = !useDepth  //如果useDepth = false,使用原始的depthTest
-            this.needsUpdate = true
+            if(this.autoDepthTest)/* this.depthWrite = */ this.depthTest = !useDepth  //如果useDepth = false,使用原始的depthTest
+            this.needsUpdate = true   //半透明物体depthWrite得是false?
+            
             if(!viewport)viewport = viewer.mainViewport //暂时这么设置
             useDepth && this.events.setSize({viewport})
         }

+ 8 - 4
src/custom/mergeStartTest.js

@@ -18,7 +18,11 @@ import cameraLight from './utils/cameraLight.js'
  
 
 var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
-
+    if(!Potree.settings.isOfficial){ 
+        if(/* Potree.settings.isTest && */ Potree.browser.isMobile()){
+            changeLog()
+        }
+    }
     Potree.settings.mergeTransCtlOnClick = true
 
 
@@ -321,7 +325,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
              
          
     let tilesetUrls = [    
-        
+        'https://4dkk.4dage.com/scene_view_data/SG-t-WSs5eaQJLoc/images/3dtiles/tileset.json?_=1760681463937',
         'https://4dkk.4dage.com/scene_view_data/SS-t-8bMZzAAcpul/images/3dtiles/tileset.json?_=1760681463937',
         'https://4dkk.4dage.com/fusion/test/b3dm/modelId_11947/tileset.json',//json有的包含多个materials,之前会花掉,现在解决了
         'https://4dkk.4dage.com/fusion/testb3dm/test001/tileset.json',  //体育中心
@@ -817,7 +821,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
  
  
  
-/* var changeLog = ()=>{ //如果移动端加了test反而出不来bug的话,用这个
+ var changeLog = ()=>{ //如果移动端加了test反而出不来bug的话,用这个
         
     
         var textarea = document.createElement('textarea');
@@ -861,7 +865,7 @@ var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m
     
 }
  
-changeLog() */
+//changeLog()  
  //可以直接用edlShader来渲染obj的outline,但不能渲染被遮挡的部分 
  
 

+ 18 - 3
src/custom/modules/Animation/AnimationEditor.js

@@ -36,9 +36,9 @@ export default class AnimationEditor extends THREE.EventDispatcher{
         
         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)  
+                this.updateCameraFollow() 
+                                                                                           
+                                                    
             })
         }
         
@@ -59,6 +59,9 @@ export default class AnimationEditor extends THREE.EventDispatcher{
         keys = [...keys.slice(0,index), key, ...keys.slice(index,keys.length)]
         this[keyType+'Keys'].set(model,keys)
         this.updateTimeRange()
+        
+        this.updateCameraFollow()
+        
     }
     
     removeKey(model, keyType, key  ){
@@ -331,6 +334,7 @@ export default class AnimationEditor extends THREE.EventDispatcher{
             if(obj.clipState != clipState){//动作是否改变
                 obj.traverse(e=>e.isSkinnedMesh && (e.boundingSphere = null)) //动画会导致bound改变,清空,raycast时重新计算bound,否则hover不到模型
                 obj.clipChanged = true
+                //但有的SkinnedMesh在transform后离近就消失,只有model.traverse(e=>e.isSkinnedMesh && (e.frustumCulled = false))才有用
             }
             obj.clipState = clipState
             	  
@@ -379,16 +383,21 @@ export default class AnimationEditor extends THREE.EventDispatcher{
         let curve = key.path.curve.clone()
         if(key.reverse) curve.points.reverse()  
         let position = curve.getPointAt(percent); 
+            position.add(key.path.edge.position)
+    
         let pathQua, quaternion
       
         if(percent2 <= 1){  
             let position2 = curve.getPointAt(percent2);
+            position2.add(key.path.edge.position)
             pathQua = math.getQuaFromPosAim(position2, position)  
         }else{
             percent2 = percent - delta
             let position2 = curve.getPointAt(percent2);
+            position2.add(key.path.edge.position)
             pathQua = math.getQuaFromPosAim(position, position2)  
         }
+         
         
         pathQua.multiplyQuaternions( pathQua, rot90Qua ); //这是当模型导进来就旋转正确时的quaternion
         key.curQua_ = pathQua.clone() //记录下
@@ -480,6 +489,12 @@ export default class AnimationEditor extends THREE.EventDispatcher{
         
     }
     
+    updateCameraFollow(){
+        let targetObject = viewer.modules.MergeEditor.selected
+            targetObject = this.ifContainsModel(targetObject) ? targetObject : null
+        this.setCameraFollow(targetObject)  
+    }
+    
     
     updateTimeRange(){
         let maxTime = 0

+ 3 - 2
src/custom/modules/panos/Images360.js

@@ -548,7 +548,7 @@ export class Images360 extends THREE.EventDispatcher{
                         camera.updateProjectionMatrix() 
                         
                         
-                        
+                         
                         
                         if(this.elDisplayModel){
                             this.elDisplayModel.val( mode == 'showPointCloud' ? ">>全景" : '>>点云')
@@ -557,7 +557,8 @@ export class Images360 extends THREE.EventDispatcher{
                         /* this.panos.forEach(e=>{
                             Potree.Utils.updateVisible(e, 'modeIsShowPanos', mode == 'showPanos', 1,  mode == 'showPanos' ? 'add':'cancel') // 
                         }) */
-                         
+                        
+                        viewer.joyStick?.updateEnable() 
                          
                         this.dispatchEvent({type:'endChangeMode',mode})  
                         console.log('setModeSuccess: ' + mode)       

+ 1 - 0
src/custom/modules/panos/tile/TileDownloader.js

@@ -255,6 +255,7 @@ class TileDownloader extends THREE.EventDispatcher{
         var t = this.getTileUrl(e/* e.pano.id, e.panoSize, e.tileSize, e.tileIndex, e.pano.alignmentType */);//xzw add alignmentType
         if(!t)return;
         this.activeDownloads.push(e);
+        //console.log('startload', t) 
         this.loadImage(t, TileDownloader.DOWNLOAD_RETRIES, this.downloadComplete.bind(this, e), this.downloadFailed.bind(this, e))
     }
 

Разница между файлами не показана из-за своего большого размера
+ 698 - 84
src/custom/objects/3dgs/splatter/SplatterThree.js


+ 6 - 2
src/custom/potree.shim.js

@@ -978,8 +978,12 @@ Utils.setObjectLayers = function(object, layerName){//add
         e.layers.set(layer)
     })
 }
-
- 
+Utils.isWhichLayer = function(object ){//add
+    let layer = object.layers.mask.toString(2).length - 1
+    for(let name in Potree.config.renderLayers){
+        if(Potree.config.renderLayers[name] == layer) return name
+    }
+} 
 
  
  

+ 3 - 2
src/custom/settings.js

@@ -461,7 +461,7 @@ function getPrefix(){
 }
 
 let isTest = browser.urlHasValue('test')
-let settings = {//设置   可修改
+let settings = {//设置   可修改 
     editType : '',
     number: '', //场景序号
     originDatasetId:'',//场景原本的数据集id,应该就是数据集第一个吧
@@ -555,7 +555,8 @@ let settings = {//设置   可修改
     
     //fastTran: isTest
     pathSmooth : true,// window.location.href.includes('192.168.0.59')  //true //smooth曲线, 非折线
-    maxClipFadeTime: 0.6
+    maxClipFadeTime: 0.6,
+    maxPixelRatio: Infinity,
 }
  
 

+ 37 - 0
src/custom/three.shim.js

@@ -470,6 +470,15 @@ THREE.Curve.prototype.getPointAt = function ( u, optionalTarget ) {
 
 } 
 
+THREE.Object3D.prototype.dealMaterial = function(fun){
+    if(!this.material)return
+    if(this.material instanceof Array){//obj
+        this.material.forEach(fun) 
+    }else{
+        fun(this.material)
+    }
+}
+
 
 const _inverseMatrix$3 = /*@__PURE__*/ new THREE.Matrix4();
 const _ray$3 = /*@__PURE__*/ new THREE.Ray();
@@ -952,6 +961,34 @@ THREE.Triangle.getInterpolatedAttribute = function( attr, i1, i2, i3, barycoord,
 
 }
 
+
+
+THREE.Triangle.getInterpolation = function( point, p1, p2, p3, v1, v2, v3, target ) {
+
+    if ( this.getBarycoord( point, p1, p2, p3, _v40 ) === null ) {
+
+        target.x = 0;
+        target.y = 0;
+        if ( 'z' in target ) target.z = 0;
+        if ( 'w' in target ) target.w = 0;
+        return null;
+
+    }
+
+    target.setScalar( 0 );
+    target.addScaledVector( v1, _v40.x );
+    target.addScaledVector( v2, _v40.y );
+    target.addScaledVector( v3, _v40.z );
+
+    return target;
+
+}
+
+
+
+
+
+
 let _vector$7 = new THREE.Vector3
 THREE.InterleavedBufferAttribute.prototype.applyNormalMatrix = function(m) {
 

+ 34 - 0
src/custom/utils/Queue.js

@@ -0,0 +1,34 @@
+
+ 
+
+
+
+export default class Queue { //队列:指针移动 (最优)。   数据量大的时候比用Map速度快
+    constructor() {
+        this.buffer = [];
+        this.head = 0;
+        this.tail = 0;
+    }
+    getHead(){
+       return this.buffer[this.head];
+    }
+    dequeue() {//从头部取出
+        if (this.head >= this.tail) return null;
+        const item = this.buffer[this.head];
+        this.buffer[this.head] = undefined; // 帮助GC
+        this.head++;
+        
+        // 定期清理前部空间
+        if (this.head > 10000 && this.head > this.buffer.length / 2) {
+            this.buffer = this.buffer.slice(this.head);
+            this.tail -= this.head;
+            this.head = 0;
+        }
+        return item;
+    }
+    
+    enqueue(item) {//从尾部追加
+        this.buffer[this.tail] = item;
+        this.tail++;
+    }
+}

+ 78 - 1
src/custom/utils/math.js

@@ -691,7 +691,84 @@ var math = {
     },
     getCelsiusFromKelvin(c){  
         return c - 273.1  
-    }
+    },
+    
+    getTwoPointsTran(points1, points2, up){//在两个系统上获得两个对应的点,得到从1到2的变换矩阵
+        let vec1 = new THREE.Vector3().subVectors(points1[1], points1[0]   )
+        let vec2 = new THREE.Vector3().subVectors(points2[1], points2[0]   )
+     
+        let matrix = new THREE.Matrix4
+        
+        matrix.makeTranslation(-points1[0].x, -points1[0].y, -points1[0].z)
+        let angle = vec1.angleTo(vec2)
+        let qua = this.getQuaBetween2Vector(vec1, vec2, up)
+        let rotMatrix = new THREE.Matrix4().makeRotationFromQuaternion(qua)
+         
+        matrix.premultiply(rotMatrix)
+        
+        if(ifScale){ //只能是等比缩放,无法拉伸,否则可能性是无数种
+            let len1 = vec1.length()
+            let len2 = vec2.length()
+            let scale = len2 / len1
+            let sclMatrix = new THREE.Matrix4().makeScale(scale,scale,scale)
+            matrix.premultiply(sclMatrix)
+        }
+        
+        
+        matrix.elements[12]+=points2[0].x 
+        matrix.elements[13]+=points2[0].y 
+        matrix.elements[14]+=points2[0].z 
+        
+        return matrix 
+    },
+    
+    
+        //如果把三个点通过旋转拉伸缩放平移变换到另外三个点,要怎么求出变换矩阵  
+        /**
+         * 使用矩阵求逆直接计算仿射变换矩阵
+         */
+        computeAffineTransformMatrix(srcPoints, dstPoints) {
+            const [p1, p2, p3] = srcPoints;
+            const [q1, q2, q3] = dstPoints;
+            
+            // 构建4x4齐次坐标矩阵(第四个点是原点)
+            // 为了确保矩阵可逆,我们需要4个线性独立的点
+            // 所以用前三个点,第四个点用它们的重心
+            const p4 = new THREE.Vector3()
+                .add(p1).add(p2).add(p3)
+                .multiplyScalar(1/3)
+                .add(new THREE.Vector3(1, 1, 1)); // 稍微偏移确保线性独立
+                
+            const q4 = new THREE.Vector3()
+                .add(q1).add(q2).add(q3)
+                .multiplyScalar(1/3)
+                .add(new THREE.Vector3(1, 1, 1));
+            
+            // 构建源点和目标点的4x4齐次坐标矩阵
+            const P = new THREE.Matrix4();
+            const Q = new THREE.Matrix4();
+            
+            // 设置矩阵列(齐次坐标,第4维为1)
+            P.set(
+                p1.x, p2.x, p3.x, p4.x,
+                p1.y, p2.y, p3.y, p4.y,
+                p1.z, p2.z, p3.z, p4.z,
+                1,    1,    1,    1
+            );
+            
+            Q.set(
+                q1.x, q2.x, q3.x, q4.x,
+                q1.y, q2.y, q3.y, q4.y,
+                q1.z, q2.z, q3.z, q4.z,
+                1,    1,    1,    1
+            );
+            
+            // 计算变换矩阵:T = Q * P^{-1}
+            const P_inv = new THREE.Matrix4().getInverse(P); 
+            const T = new THREE.Matrix4().multiplyMatrices(Q, P_inv);
+            
+            return T;
+        }
 };
 
 /* 

+ 132 - 48
src/custom/viewer/ViewerNew.js

@@ -1,6 +1,6 @@
  
 
-import * as GaussianSplats3D from "../../../libs/gaussian/gaussian-splats-3d.module.js";
+//import * as GaussianSplats3D from "../../../libs/gaussian/gaussian-splats-3d.module.js";
 
 import {Loader3DTiles} from '../../../libs/three.js/3dtiles/three-loader-3dtiles.esm.js'; 
 import {OBJLoader} from "../../../libs/three.js/loaders/OBJLoader.js";
@@ -93,12 +93,15 @@ import BasicMaterial from '../materials/BasicMaterial.js'
 import {FirstPersonControls} from "../../navigation/FirstPersonControlsNew.js";
 import {OrbitControls} from "../../navigation/OrbitControlsNew.js";
 //import {VRControls} from "../../navigation/VRControlsNew.js";
- 
+import { CharacterControl} from "../../navigation/CharacterControl.js";
 
 import { ClassificationScheme } from "../../materials/ClassificationScheme.js";
 import { VRButton } from '../../../libs/three.js/extra/VRButton.js';
 import DxfLoader from '../../loader/DxfLoader.js'
  
+ 
+import JoyStick from '../../navigation/JoyStick' 
+ 
 //import {Splat} from '../objects/3dgs/Splat.js'
  
 
@@ -128,9 +131,11 @@ THREE.Cache.enabled = true //这样不会重复网络请求相同的图(如热
 export class Viewer extends ViewerBase{
 	
 	constructor(domElement, mapArea_, args = {}){
-		super(domElement, $.extend(args,{name:'mainViewer', antialias: !browser.urlHasValue('noAA'), preserveDrawingBuffer:true}));
+		super(domElement, $.extend(args,{name:'mainViewer', antialias: !Potree.settings.noAA, preserveDrawingBuffer:true}));
         //注:viewer因为要分屏,尤其是四屏,preserveDrawingBuffer需要为true, 否则无法局部clear 
         
+        
+        
         window.viewer = this
         mapArea = mapArea_                 
         
@@ -149,7 +154,7 @@ export class Viewer extends ViewerBase{
         if(this.renderer.capabilities.isWebGL2){
             Potree.settings.isWebgl2 = true  //是否启用webgl2
         } 
-         
+        
          
         if(Potree.settings.editType == "pano" || Potree.settings.editType == "merge"  ){
             this.modules = { 
@@ -358,7 +363,11 @@ export class Viewer extends ViewerBase{
                         try{
                         if(e.event.ctrlKey){
                             if(e.event.key.toLowerCase() == 'c'){
-                                let info = JSON.stringify(  this.mainViewport.view.getJson() );
+                                let info = this.mainViewport.view.getJson()
+                                if(this.controls instanceof CharacterControl){
+                                    info.modelPos = this.controls.modelPos.toObject()
+                                }
+                                info = JSON.stringify( info );
                                 console.log(`Copy view params: ${info}`),
                                 navigator.clipboard.writeText(info)  //need https
                                   
@@ -367,6 +376,14 @@ export class Viewer extends ViewerBase{
                                     let info = JSON.parse(A);
                                     if(info.yaw != void 0){
                                         this.mainViewport.view.applyJson(info) 
+                                        if(this.controls instanceof CharacterControl){
+                                            if(info.modelPos){
+                                                this.controls.modelPos.copy(info.modelPos)
+                                            }else{
+                                                this.controls.modelPos.copy(this.mainViewport.view.position)
+                                            }
+                                        }
+                                        
                                         console.log(`pasteViewParams ${A}`) 
                                     }
                                 })
@@ -446,9 +463,10 @@ export class Viewer extends ViewerBase{
                 this.initDragAndDrop();
             }
 
-            if(Potree.settings.isTest){
+            if(Potree.settings.isTest || Potree.settings.showStats  ){
                 this.stats = new Stats();
-                this.stats.showPanel( 2 ); // 0: fps, 1: ms, 2: mb, 3+: custom
+                console.log('state created')
+                //this.stats.showPanel( 2 ); // 0: fps, 1: ms, 2: mb, 3+: custom
                 document.body.appendChild( this.stats.dom );
             }
 
@@ -600,7 +618,7 @@ export class Viewer extends ViewerBase{
                 
                  
                 
-                this.createControls();
+                this.createControls(args.enableJoy);
 
                 this.clippingTool.setScene(this.scene);
                 
@@ -785,8 +803,20 @@ export class Viewer extends ViewerBase{
             }) 
             
         }
-        
-        
+        {
+            let maxPixelRatio_ = Potree.settings.maxPixelRatio
+            Object.defineProperty(Potree.settings , "maxPixelRatio",{ 
+                get: function() {
+                    return maxPixelRatio_
+                },
+                set: (max)=>{
+                    if(maxPixelRatio_ != max){ 
+                        maxPixelRatio_ = max
+                        this.updateScreenSize({forceUpdateSize:true})
+                    }
+                }
+            }) 
+        }
         /* this.reticule.addEventListener('update',(e)=>{
             this.needRender = true 
             if(this.mapViewer && this.mapViewer.attachedToViewer)this.mapViewer.needRender = true  //分屏时mapViewer也有reticule
@@ -1085,7 +1115,11 @@ export class Viewer extends ViewerBase{
         object.traverse((mesh, o={})=>{
             //let visi = o.visible === false ? false : mesh.visible    //mesh.realVisible() //如果祖先不可见,此mesh一定不可见
             if(mesh.geometry){
-                posCount += mesh.geometry.attributes.position.count 
+                if(mesh.geometry instanceof THREE.BufferGeometry ){
+                    posCount += mesh.geometry.attributes.position.count 
+                }else{
+                    posCount += mesh.geometry.vertices.length 
+                }
                 //visi && (visiPosCount += mesh.geometry.attributes.position.count)
             }
             /* if(mesh.material && !mesh.material.map){
@@ -1768,13 +1802,14 @@ export class Viewer extends ViewerBase{
 			if (this.controls) {
                 this.controls.setEnable(false) 
 				//this.inputHandler.removeInputListener(this.controls);
-                this.controls.moveSpeed = this.moveSpeed;  //记录   (因为orbit的radius很大,转为firstPerson时要缩小)
+                //this.controls.moveSpeed = this.getMoveSpeed()//this.moveSpeed;  //记录????   (因为orbit的radius很大,转为firstPerson时要缩小)
 			} 
 			this.controls = controls;
-            controls.moveSpeed && this.setMoveSpeed(controls.moveSpeed) //add
+            controls.moveSpeed && this.setMoveSpeed(controls.moveSpeed) //add  
             
             this.controls.setEnable(true) 
 			 
+            this.dispatchEvent('setControls')
 			//this.inputHandler.addInputListener(this.controls);
 		}
 	}
@@ -2458,7 +2493,7 @@ export class Viewer extends ViewerBase{
 	// Viewer Internals
 	// ------------------------------------------------------------------------------------
 
-	createControls () {
+	createControls (enableJoy) {
 		{ // create FIRST PERSON CONTROLS
 			this.fpControls = new FirstPersonControls(this, this.mainViewport);
 			this.fpControls.enabled = false;
@@ -2473,6 +2508,14 @@ export class Viewer extends ViewerBase{
             }) */
         
 		}
+         
+        { 
+            this.characterCtrl =  new CharacterControl(this  );
+            
+            
+        }
+        
+        
 
 		// { // create GEO CONTROLS
 		//	this.geoControls = new GeoControls(this.scene.camera, this.renderer.domElement);
@@ -2490,6 +2533,13 @@ export class Viewer extends ViewerBase{
 			this.orbitControls.addEventListener('start', this.disableAnnotations.bind(this));
 			this.orbitControls.addEventListener('end', this.enableAnnotations.bind(this));
 		}
+        
+        
+        if(enableJoy){
+            this.joyStick = new JoyStick(this.renderArea.parentElement, [this.fpControls, this.characterCtrl])
+            
+        }
+
 
 		/* { // create EARTH CONTROLS
 			this.earthControls = new EarthControls(this);
@@ -2993,7 +3043,12 @@ export class Viewer extends ViewerBase{
 			this.scene.cameraO.position.copy(scene.view.position);
 		} else if (controls !== null) {
 			controls.setScene(scene);
-			controls.update(delta);
+            
+             
+            let count = viewer.collider && window.multiUpdate ? 6 : 1
+            for (let s = 0; s < count; ++s)
+                controls.update(delta/count);
+			
         
             //更新camera
             this.viewports.forEach(viewport=>{
@@ -3490,11 +3545,11 @@ export class Viewer extends ViewerBase{
                 }))) continue//return
             }else{ 
                 this.clear(params)  
-                pRenderer.clearTargets(params);
-                 
-                 
+                pRenderer.clearTargets(params); 
                 this.renderBG(viewport)
-                 
+                
+                
+                viewer.dispatchEvent({type: "render.begin3",  viewer: viewer, viewport, params }); 
                 if(this.splatMesh){
                     Potree.Utils.setCameraLayers(params.camera, ['model'])
                     this.renderer.render(this.splatMesh, params.camera) 
@@ -3705,6 +3760,7 @@ export class Viewer extends ViewerBase{
         let renderer = params.renderer || this.renderer
         let camera = params.camera ? params.camera : this.scene.getActiveCamera();
         //清除深度 !!!!
+        this.dispatchEvent({type: "render.beforeClearDepth",  viewer: this, viewport:params.viewport });
         renderer.clearDepth(); 
  
         if(!params.magnifier){ 
@@ -5164,16 +5220,19 @@ export class Viewer extends ViewerBase{
         {
             let hasAnimation
             window.pauseAni || this.objs.children.forEach(model=>{
-                if(model.mixer && model.mixer._nActiveActions){ 
+                //model.traverse(object=>object.isSkinnedMesh && object.computeBoundingSphere())
+                
+                if(model.visible && model.mixer && (model.clipChanged || model.actions.some(a=>a._mixer._isActiveAction( a ) && !a.paused ))){ //播放中或者动作状态改变
                     hasAnimation = true
+                    model.clipChanged = false
                     model.mixer.update(deltaTime)
+                    //console.log('mixer update', model.name)
                 }
             }) //以后有空的话用frust判断是否在画面内,不在的话即使有动画也不要 update 和 render, 如果paused的话是不是也可以不update
             hasAnimation && (this.dispatchEvent('content_changed'))
         }
         
         
-        
 		// let vrActive = viewer.renderer.xr.isPresenting;
 		// if(vrActive){
 		// 	this.update(this.clock.getDelta(), timestamp);
@@ -5654,10 +5713,11 @@ export class Viewer extends ViewerBase{
     getObjLoader(){
         let loader = loaders.objLoaders.find(e=>!e.inUse)
         if(!loader){
-            loader = new OBJLoader( this.fileManager ) 
-            loader.inUse = true 
+            loader = new OBJLoader( this.fileManager )   
+                                
             loaders.objLoaders.push(loader)
         }
+        loader.inUse = true 
         return loader 
     }
 
@@ -5782,6 +5842,9 @@ export class Viewer extends ViewerBase{
                     //获取在scale为1时,表现出的大小
                     boundingBox.union(boundingBox_.clone().applyMatrix4(child.matrixWorld)) //但感觉如果最外层object大小不为1,要还原下scale再乘
                  
+                    if(child instanceof THREE.SkinnedMesh){ 
+                        child.boundingBox = null    //delete 动画会导致bound改变, raycast干脆不用boundingBox了,否则之后要实时重计算 
+                    } 
                     
                     let changeMat = (oldMat)=>{
                         let mat = oldMat
@@ -5807,7 +5870,10 @@ export class Viewer extends ViewerBase{
                     }
                    
                    
+                    if(fileInfo_.prop?.is4dkkModel){
                     
+                        child.material.color.set(1,1,1); //看到有obj不是白色
+                    }
                     
                 } 
             } );
@@ -5942,25 +6008,9 @@ export class Viewer extends ViewerBase{
             loaders.glbLoader.unlitMat = true//!!fileInfo.unlit
             loaders.glbLoader.load(fileInfo.url,  ( gltf, total )=>{    
                 console.log('loadGLTF', gltf, 'aniCount:',gltf.animations.length)
-                let model = gltf.scene
-                if(gltf.animations.length){
-                    let skeleton = new THREE.SkeletonHelper( model );
-					//skeleton.visible = false; 
-                    viewer.scene.scene.add(skeleton)
-                    model.skeletonHelper = skeleton //注意:不能覆盖model.skeleton,因其另有 */
-                    skeleton.material.opacity = 0.1
-                    let mixer = new THREE.AnimationMixer( model);
-                    model.actions = []
-                    gltf.animations.forEach(ani=>{
-                        if(ani.tracks.filter(e=>e instanceof THREE.QuaternionKeyframeTrack).length > 1){ //>一帧的
-                            model.actions.push(mixer.clipAction( ani )); 
-                        } 
-                    })
-                    model.mixer = mixer
-                }
-                model.gltf = gltf
-                  
-                
+                let model = gltf.scene  
+                model.gltf = gltf 
+                this.gltfAddAnimation(model)
                 loadDone(model) 
             }, onProgress, onError)
             
@@ -5991,8 +6041,9 @@ export class Viewer extends ViewerBase{
                     //maximumMemoryUsage: 100, //缓存大小,见tiles3DMaxMemory。单位M(但实际结果是 2.5*maximumMemoryUsage + 750  。超过2G会崩, 所以应该小于540) 若太小,密集的tile反复加载很卡. (任务管理器刷新网页后若内存不掉就要结束进程否则虚高)
                     debug: browser.urlHasValue('tilesBox'),  //show box  
                     parent: this.scene.scene, 
-                    is4dkk: fileInfo.is4dkk,//是否是4dkk中的模型. 通常maximumScreenSpaceError需要10
+                    is4dkkModel: fileInfo.is4dkkModel, //是否是4dkk中的模型. 通常maximumScreenSpaceError需要10
                     updateTime: fileInfo.updateTime, //加后缀防止缓存
+                    //cesiumIONToken:  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5OTc4MTFiYS1hYzhlLTQ3ZjYtYWJmMi1hODMwMWMxZGRiYTQiLCJpZCI6ODU1NDksImlhdCI6MTY1Mzc5NDc5N30.ldTi8bF3XvSOgnZrMITokRW4kE3i8Mwbarhk5OQbPsI',
                 },  
             })  
             //console.log(result)
@@ -6013,7 +6064,7 @@ export class Viewer extends ViewerBase{
                 if(master.panos) viewer.images360.judgeModelMat(e.tileContent)
                 //set Layers ?
                 Potree.Utils.setObjectLayers(e.tileContent, 'model')  
-           
+                fileInfo.side && e.tileContent.traverse(e=>e.material && (e.material.side = fileInfo.side))//新软件导出的带坐标的box型模型要反面才看的到,干脆双面
             })
             
             { 
@@ -6029,10 +6080,10 @@ export class Viewer extends ViewerBase{
                 })  
             }
             let v = true
-            result.model.visiChangeCallback = ()=>{
+            result.model.visiChangeCallback = (force)=>{
                 let visi = result.model.realVisible()
                 tileset.visible = visi;  //同步,使不加载 
-                if(v != visi){
+                if(force || v != visi){
                     tileset.nextForceUpdate = true
                     v = visi
                 } 
@@ -6149,6 +6200,39 @@ export class Viewer extends ViewerBase{
         }
     }
     
+    
+    gltfAddAnimation(model){ 
+        
+        if(model.gltf?.animations.length){
+            let skeleton = new THREE.SkeletonHelper( model );
+            
+            viewer.scene.scene.add(skeleton)
+            model.skeletonHelper = skeleton //注意:不能覆盖model.skeleton,因其另有 */
+            //if(Potree.settings.isOfficial){
+                Potree.Utils.updateVisible(skeleton,'hide',false)  
+            /* }else{
+                skeleton.material.opacity = 0.1
+                skeleton.bones.forEach((bone,i)=>{
+                    let label = new Potree.TextSprite({
+                        text:`${i} : ${bone.name}`, sizeInfo:{width2d:130},  fontWeight:'',  fontsize:20,
+                        backgroundColor:{r:255,g:255,b:255,a:0}, textColor:{r:255,g:255,b:255,a:1},
+                    }) 
+                    label.sprite.material.depthTest = false 
+                    bone.add(label)
+                })
+            } */
+            let mixer = new THREE.AnimationMixer( model);
+            model.actions = []
+            model.gltf.animations.forEach(ani=>{
+                if(ani.tracks.filter(e=>e instanceof THREE.QuaternionKeyframeTrack).length > 1){ //>一帧的
+                    model.actions.push(mixer.clipAction( ani )); 
+                } 
+            })
+            model.mixer = mixer 
+           
+        }
+        
+    }
      
     
     setAllTilesets(){//让所有tileset执行fun。    objs里每个model最多两层tileset
@@ -6528,8 +6612,8 @@ export class Viewer extends ViewerBase{
             "pitch": -1,
             "videoType": 1
         }
-        let monitor = new Monitor(data)
-        model.add(monitor)
+        let monitor = new Monitor(data, model) 
+        
         this.scene.monitors.push(monitor)
         this.dispatchEvent('content_changed')
     }

+ 5 - 2
src/custom/viewer/viewerBase.js

@@ -115,7 +115,7 @@ console.log('antialias',args.antialias)
         gl.clear = (bits)=>{
              console.error('clear')
         }   
- */
+        */
         
     }
     
@@ -144,7 +144,10 @@ console.log('antialias',args.antialias)
                 render = true 
                 this.screenSizeInfo.pixelRatio = window.devicePixelRatio  //如果player放在小窗口了,也要监测devicePixelRatio,因为缩放时client宽高不会改变
                 //config.isMobile ? (ratio = Math.min(window.devicePixelRatio, 2)) : (ratio = window.devicePixelRatio)
-                ratio = window.devicePixelRatio 
+                ratio = Math.min(Potree.settings.maxPixelRatio, window.devicePixelRatio)
+                
+                
+                
             }     
         }
         if (render) { 

+ 453 - 0
src/navigation/CharacterControl.js

@@ -0,0 +1,453 @@
+
+
+import * as THREE from "../../libs/three.js/build/three.module.js"; 
+import {Box3Helper} from "../utils/Box3Helper.js";
+import Collider from '../custom/objects/3dgs/splatter/Collider.js' 
+
+const gravity = -10
+
+const fadeDur = 0.3  //s
+const rotTranSpeed = 1.2 
+const qua_ = new THREE.Quaternion
+const up = new THREE.Vector3(0,0,1), down = new THREE.Vector3(0,0,-1) 
+const raycaster = new THREE.Raycaster() 
+const vec3 = new THREE.Vector3()
+ 
+const miniDelta = 1 / 60  //假设每帧都是这个时长,主要作用于重力
+ 
+const modelInfo = [
+    {name : 'Xgrids_robot_1_v2', angle : Math.PI/2 },
+    {name : 'soldier', angle : -Math.PI/2 },
+]
+const modelIndex = Potree.browser.urlHasValue('character',true) || 0  
+
+
+export class CharacterControl extends THREE.EventDispatcher{
+    constructor(viewer, url){
+        super()
+        this.model = null
+        this.viewer = viewer
+        
+        this.url = Potree.resourcePath + '/models/glb/animation/'+modelInfo[modelIndex].name+'.glb'//  soldier    why qiyu only one action?
+        
+        this.standardRadius = 4;
+        this.targetShiftUp = 1.4
+        this.gravity = gravity
+        this.defaultCollRadius = this.collRadius = 0.25 
+        this.fallMaxHeight = this.getMaxFall() 
+        this.modelPos = new THREE.Vector3
+        this.modelQua = new THREE.Quaternion
+        
+        this.keys = {
+            FORWARD: ['W'.charCodeAt(0), 38],
+            BACKWARD: ['S'.charCodeAt(0), 40],
+            LEFT: ['A'.charCodeAt(0), 37],
+            RIGHT: ['D'.charCodeAt(0), 39],
+            X: ['X'.charCodeAt(0)],
+            SHIFT : [16],
+            ALT : [18],
+            SPACE:[32] 
+        }
+        
+        this.boundingBox = new THREE.Box3()
+        
+        
+        
+        this.keyDir = new THREE.Vector3()
+        this.yawDelta = 0
+        this.pitchDelta = 0
+        this.rotationSpeed = Potree.browser.isMobile() ? 4 : 2
+        
+        this.velocity = new THREE.Vector3() //速度。相当于自动行走方向,但仅限于垂直方向,当人物在半空中时才不为0
+        
+        let drag = (e) => {//只允许旋转   copy from orbitControl
+            if(!this.enabled)return
+            
+            let viewport = e.dragViewport;
+            if(!viewport )return
+           
+			if (e.drag.startHandled === undefined) {
+				e.drag.startHandled = true; 
+				this.dispatchEvent({type: 'start'});
+			}
+ 
+            let ndrag = e.drag.pointerDelta.clone()//.add(new THREE.Vector2(1,1)).multiplyScalar(0.5)
+            ndrag.y *= -1
+ 
+            this.yawDelta += ndrag.x * this.rotationSpeed;
+            this.pitchDelta += ndrag.y * this.rotationSpeed;
+  
+            this.dragStarted = true
+            
+		};
+        
+         
+		let drop = e => {
+            if(!this.enabled)return
+            this.dragStarted = false
+			this.dispatchEvent({type: 'end'});
+		};
+        this.viewer.addEventListener('global_drag', drag);
+		this.viewer.addEventListener('global_drop', drop);
+	 
+    }
+    
+    
+    getMaxFall(){//要保证降落到地面时最后一帧降落高度不能大于collRadius否则穿模
+        let c = miniDelta * miniDelta * this.gravity
+        let n = Math.round(this.collRadius / c)   //需要多少次update
+        return - (n + 1) * n / 2 * c
+        
+        //若不是微分,而是连续 -Math.pow(this.collRadius / miniDelta, 2 ) / gravity / 2 不过好像差不多,最后降落都到不了radius
+    }
+    updatetModelPose(){
+        if(!this.model)return
+        this.model.position.copy(this.modelPos)
+        let qua = qua_.multiplyQuaternions(this.modelQua,  this.modelInitialQua)
+        this.model.quaternion.copy(qua)
+    }
+    
+    loadModel(){
+        if(this.loadRequest)return
+        this.loadRequest = true
+        let callback = (model)=>{
+            this.model = model
+            this.modelInitialQua = this.model.quaternion.clone()
+            if(this.enabled){
+                this.updatetModelPose() 
+            }
+          
+          
+            //this.boxHelper = new Box3Helper(this.model.boundingBox);
+            //this.model.add(this.boxHelper)
+            //该模型的中心在脚底
+            
+            
+            Potree.Utils.updateVisible(this.model, 'enable', this.enabled)
+            
+            if(this.url.includes('Xgrids_robot_1_v2')){//拆分动作
+ 
+                let ani = model.gltf.animations[0];
+                const clip1 = THREE.AnimationUtils.subclip(ani, 'Idle', 120, 268, 30)
+                  , act1 = model.mixer.clipAction(clip1);
+                    act1.name = 'Idle';
+                const clip2 = THREE.AnimationUtils.subclip(ani, 'Walk', 86, 115, 30)
+                  , act2 = model.mixer.clipAction(clip2);
+                    act2.name = 'Walk';
+                const clip3 = THREE.AnimationUtils.subclip(ani, 'Run', 61, 81, 30)
+                  , act3 = model.mixer.clipAction(clip3);
+                    act3.name = 'Run';
+                const clip4 = THREE.AnimationUtils.subclip(ani, 'Jump', 2, 56, 30)
+                  , act4 = model.mixer.clipAction(clip4);
+                    act4.name = 'Jump'               
+                model.gltf.animations = [clip1,clip2,clip3,clip4]
+                model.actions = [act1,act2,act3,act4]
+                
+            }
+            
+            
+        }
+        viewer.loadModel({ 
+            fileType:'glb', 
+            unlit:true,
+            url: this.url,
+            transform : { 
+                rotation : [Math.PI/2,  modelInfo[modelIndex].angle,   0],
+                position : [0,0,0],
+                scale:  [1,1,1]
+            } 
+        },callback )
+    }
+    
+    
+    
+    setEnable(state){ 
+        this.loadModel() 
+        this.enabled = state
+        if(state){
+            Collider.loadCollMesh()
+            
+            let view = this.viewer.mainViewport.view
+            this.modelPos.copy(view.position)
+            
+            let dir = view.direction
+            const angle = new THREE.Vector2().copy(dir).angle() //- Math.PI/2  // unwrapRad( Math.atan2( ease.x, ease.z ) + azimuth );
+            qua_.setFromAxisAngle( up, angle ); 
+            this.modelQua.copy(qua_)
+            this.updatetModelPose() 
+        }
+        
+        this.model && Potree.Utils.updateVisible(this.model, 'enable', state)
+    }
+    setScene(){}
+    
+    update(delta=1){//delta:秒
+        let startTime = performance.now()
+        //为了防止移动过快而穿模,就不用这个delta来控制速度平衡了,缺点就是卡的时候会很慢
+        let oldPos = this.modelPos.clone()
+    
+        let ih = this.viewer.inputHandler; 
+        let moveForward = this.keys.FORWARD.some(e => ih.pressedKeys[e]);
+        let moveBackward = this.keys.BACKWARD.some(e => ih.pressedKeys[e]);
+        let moveLeft = this.keys.LEFT.some(e => ih.pressedKeys[e]);
+        let moveRight = this.keys.RIGHT.some(e => ih.pressedKeys[e]);
+        let fast = this.keys.SHIFT.some(e => ih.pressedKeys[e]); 
+        let ignoreColl = this.keys.X.some(e => ih.pressedKeys[e]); 
+        
+        
+        let deltaRatio = delta / 16 * 1000 //如果帧率为60,该值为1
+        let moveSpeed = 0.05 //* deltaRatio 
+            fast && (moveSpeed *= 3)
+        let speed = moveSpeed
+        let view = this.viewer.scene.view
+        
+        { // apply rotation
+			let progression = Math.min(0.95, 0.3 * deltaRatio)
+
+			let yaw = view.yaw;
+			let pitch = view.pitch; 
+
+			yaw -= progression * this.yawDelta;
+			pitch -= progression * this.pitchDelta;
+
+			view.yaw = yaw;
+			view.pitch = pitch; 
+            
+            this.yawDelta *= (1-progression)    
+            this.pitchDelta *= (1-progression) 
+		}          
+        
+        
+        //人物不允许垂直移动
+        this.keyDir.set(0,0)
+        let moveDir = new THREE.Vector3
+        if(moveLeft || moveRight){
+            moveDir.x = moveLeft && moveRight ? 0 : moveLeft ? -1 : 1
+        } 
+        if(moveForward || moveBackward){
+            moveDir.y = moveForward && moveBackward ? 0 : moveForward ? 1 : -1
+        } 
+        moveDir.normalize() 
+        
+        if(!moveDir.lengthSq()){
+            if(this.viewer.joyStick?.state){
+                let joyState = this.viewer.joyStick.state
+                moveDir.x = joyState.dir.x// * speed
+                moveDir.y = joyState.dir.y// * speed 
+                
+                
+                let attenuation =/*  Potree.browser.urlHasValue('c',true) ||  */0.8  
+                let joySpeed = joyState.speed
+                speed = joyState.lastSpeed * attenuation + joySpeed * (1-attenuation) //缓冲一下避免变化太突然  
+                joyState.lastSpeed = speed 
+                speed *= 0.17 * moveSpeed
+                speed = Math.min(this.collRadius,  speed)
+            } 
+        } 
+        
+        
+        moveDir.multiplyScalar(speed)
+         
+        let moveWorld = view.translate(moveDir.x, moveDir.y, moveDir.z, {forceHorizon:true, onlyGetVec:true} )  
+        let moveDis = moveWorld.length() 
+       
+        const actionState =  moveDis > 1e-4 ? speed > 0.14 ? 'Run' : 'Walk' : 'Idle';
+        if ( actionState !== 'Idle' ) { 
+            
+            const angle = new THREE.Vector2().copy(moveWorld).angle() //- Math.PI/2  
+            qua_.setFromAxisAngle( up, angle ); 
+            
+            this.modelQua.rotateTowards( qua_,  THREE.Math.clamp(0.1, rotTranSpeed * speed, 0.8) ); //渐变
+            
+            //angle > 0 && console.log('angle', angle, qua_.toArray())  
+        }
+           
+        if ( actionState != this.curActState && this.model) { 
+
+            let newAction = this.model.actions.find(e=>e._clip.name == actionState) 
+            let oldAction = this.currentAction
+           
+            if(!newAction){
+                console.error('connot find action', actionState)
+                newAction = this.model.actions[0]
+            }
+            newAction.reset();
+            newAction.weight = 1.0;
+            newAction.stopFading();
+            oldAction?.stopFading();
+            // synchro if not idle
+            if ( actionState !== 'Idle' && oldAction ) newAction.time = oldAction.time * ( newAction.getClip().duration / oldAction.getClip().duration );
+            oldAction?._scheduleFading( fadeDur, oldAction.getEffectiveWeight(), 0 );
+            newAction._scheduleFading( fadeDur, newAction.getEffectiveWeight(), 1 );
+            newAction.play(); 
+             
+            this.curActState = actionState
+            this.currentAction = newAction
+            this.lastAction = oldAction
+            //see example: https://threejs.org/examples/?q=walk#webgl_animation_walk
+        }  
+         
+        
+        if ( actionState !== 'Idle'){  
+            //console.log('s',speed, actionState) 
+            let s = speed * ( actionState == 'Walk' ? 20 : 5)
+            this.currentAction.setEffectiveTimeScale(s)  //add
+            this.lastAction?.setEffectiveTimeScale(s)  //add for衔接、同步 
+        }  
+         
+         
+         
+        let useCollider = viewer.collider && !ignoreColl && !window.stopColl
+        //向下坠落一点  
+  
+        if(moveDis > 0.25){//尽量不超过radius
+            console.log('moveDis', moveDis)
+        }
+        if(useCollider){ 
+            this.onGround && this.velocity.set(0, 0, 0)
+            this.velocity.z += miniDelta * this.gravity
+            let fall = this.velocity.clone().multiplyScalar(miniDelta)  
+            moveWorld.add(fall)  
+        
+            if(this.edgeDetect(moveWorld)){
+                if(moveDis > 0){
+                     this.stuckDur += delta
+                }
+                if(this.stuckDur > 3){//可能朝任何方向都走不动,超过一定时间解除重力
+                    moveWorld.setZ(0)
+                    console.log('你好像被卡在空中了?解除重力')
+                }else{
+                    moveWorld.set(0, 0, 0)
+                }
+                this.velocity.set(0, 0, 0)
+                
+            }else{
+                this.stuckDur = 0
+            } 
+        } 
+        this.modelPos.add(moveWorld)
+        
+        if(useCollider){ 
+        
+            let moveD = moveWorld.length()
+            //if(moveD > 0.000001)console.log('moveD', moveD)
+        
+            
+            let collCount = window.mulColl ? THREE.Math.clamp(Math.ceil(moveDis / this.collRadius * 12), 1, 6) : 1
+             
+            this.modelPos.sub(moveWorld)
+            
+            let move_ = moveWorld.multiplyScalar(1/collCount)
+            
+            collCount > 1 && console.log('collCount',collCount)
+            
+            
+            for(let i=0;i<collCount;i++){
+                this.modelPos.add(move_)
+                
+                
+                
+                let start = new THREE.Vector3().addVectors(this.modelPos, new THREE.Vector3(0,0, this.collRadius-0.05)) 
+                let end = start.clone()
+                    end.z += this.targetShiftUp
+                let coll = viewer.collider?.moveDeltaCorrecting({radius:this.collRadius, start, end})  
+                if(coll?.hit){  
+                    this.modelPos.add(coll.delta);
+                    
+                    this.onGround = coll.delta.z > Math.abs(miniDelta * this.velocity.z * 0.25 / collCount)  
+                    if(!this.onGround){
+                        //coll.delta.normalize()
+                        //this.velocity.addScaledVector(coll.delta, -coll.delta.dot(this.velocity)) ;
+                    } 
+                     
+                }else{
+                    this.onGround = false
+                } 
+         
+            } 
+            
+            
+        }
+        
+        
+       /*  let d = this.modelPos.clone().sub(oldPos)
+        let d1 = d.length()
+        d1>0.01 && console.log(d1)   */
+
+        
+        let target = new THREE.Vector3().copy(this.modelPos)
+            target.z += this.targetShiftUp //尽量移动焦点到头部
+        view.radius = this.standardRadius
+        let dir = view.direction
+        let lookVec = new THREE.Vector3().copy(view.direction).multiplyScalar(this.standardRadius)
+        let pos = new THREE.Vector3().subVectors(target, lookVec)
+        view.position.copy(pos) 
+            
+            
+            
+        if(useCollider)  this.zoomInCam(target ) //防视线遮挡
+        
+         
+        this.updatetModelPose() 
+        
+        if(window.logDur){
+            let dur = performance.now() - startTime
+            console.log('update dur', dur)
+        }
+        
+    }
+    
+    zoomInCam(target){//fixCamera  视线被遮挡时把相机拉近到能看到人物
+        let view = this.viewer.scene.view 
+        let dir = view.direction.negate()
+        raycaster.set(target, dir)
+        let intersect = viewer.collider.pickWithRay(raycaster, view.radius)
+        
+        if(intersect){ 
+            let dis = intersect.point.distanceTo(target)
+            dis = Math.max(dis - 0.2, 0.2); //缩进一点 避免遮挡物旁边的mesh在视线内
+            let pos = target.clone().addScaledVector(dir, dis);
+            view.position.copy(pos)
+        }
+        
+        
+    }
+    
+    
+    edgeDetect(moveDelta){ //_checkNoGoZone
+        let endPos = vec3.addVectors(moveDelta, this.modelPos)  
+        endPos.z += 1 
+        raycaster.set(endPos,  down) 
+        let intersect = viewer.collider.pickWithRay(raycaster, this.fallMaxHeight)//设置太高的话,掉到地面时速度太大会穿过去
+        let willFall = !intersect
+        if(willFall)console.log('willFall')
+        return willFall
+    }
+    
+    /* updateCollRadius(moveDis, lastHeightDelta){//上坡且速度快时增大,主要防止楼梯抖
+        this.collRadius
+    } */
+
+ 
+}
+
+//加了碰撞检测后: 当有阻挡视线时,radius要缩小.  不穿墙穿地面,能走斜坡。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 119 - 68
src/navigation/FirstPersonControlsNew.js

@@ -31,13 +31,12 @@ export class FirstPersonControls extends THREE.EventDispatcher {
 		 
 
 		this.rotationSpeed = 200;
-		this.moveSpeed = 0.2;
+		this.moveSpeed = 0.05;
 		 
        
         this.setCurrentViewport({hoverViewport:viewport, force:true}) //this.currentViewport = viewport
         
         
-        
 		this.keys = {
             FORWARD: ['W'.charCodeAt(0), 38],
             BACKWARD: ['S'.charCodeAt(0), 40],
@@ -45,8 +44,8 @@ export class FirstPersonControls extends THREE.EventDispatcher {
             RIGHT: ['D'.charCodeAt(0), 39],
             UP: ['Q'.charCodeAt(0)],
             DOWN: ['E'.charCodeAt(0)],
-            
-            //SHIFT : [16],
+            X: ['X'.charCodeAt(0)],
+            SHIFT : [16],
             ALT : [18],
             SPACE:[32],
 
@@ -74,7 +73,9 @@ export class FirstPersonControls extends THREE.EventDispatcher {
             } 
         })
         
-
+        
+         
+        
 		let drag = (e) => {  
             if(!this.enabled)return 
             let viewport = e.dragViewport;
@@ -104,15 +105,14 @@ export class FirstPersonControls extends THREE.EventDispatcher {
 
 				this.dispatchEvent({type: 'start'});
 			}
-             
+            
             if (mode.includes('rotate')) {//旋转 
 				
                 //来自panoramaControl updateRotation
                 if(!this.pointerDragStart){
                    return this.pointerDragStart = e.pointer.clone()
                 }
-                 
-                
+            
                 let view = this.scene.view;
                 
                  
@@ -149,37 +149,55 @@ export class FirstPersonControls extends THREE.EventDispatcher {
                     view.applyToCamera(camera)
                      
                 }else{
-                  
-                    
-                    let _matrixWorld = camera.matrixWorld
-                    camera.matrixWorld = new THREE.Matrix4;//unproject 前先把相机置于原点 
-                    
-                    var e1 = new THREE.Vector3(this.pointerDragStart.x,this.pointerDragStart.y,-1).unproject(camera)
-                      , t = new THREE.Vector3(e.pointer.x,e.pointer.y,-1).unproject(camera)
-                      , i = Math.sqrt(e1.x * e1.x + e1.z * e1.z)
-                      , n = Math.sqrt(t.x * t.x + t.z * t.z)
-                      , o = Math.atan2(e1.y, i)
-                      , a = Math.atan2(t.y, n);
-                       
-                    this.pitchDelta +=  o - a  //上下旋转
-                    e1.y = 0,
-                    t.y = 0; 
-                    
-                    var s = Math.acos(e1.dot(t) / e1.length() / t.length());
-                    
-                    if(!isNaN(s)){
-                        var yawDelta = s    //左右旋转 
-                        this.pointerDragStart.x > e.pointer.x && (yawDelta *= -1) 
-                        this.yawDelta += yawDelta
-                    } 
-                    
-                    
-                    //console.log('rotate:', this.pitchDelta, e.pointer.toArray(), this.pointerDragStart.toArray())
-                    
-                    
-                    this.pointerDragStart.copy(e.pointer)
-                     
-                    camera.matrixWorld = _matrixWorld ;
+                    if(Potree.settings.ctrlRotInvSmooth){//朝相反方向且更顺滑 like orbitControl
+                        let rotationSpeed = Potree.browser.isMobile() ? 0.006 : 0.002;   
+                        let old = this.yawDelta
+                        this.yawDelta -= e.drag.mouseDelta.x * rotationSpeed;
+                        this.pitchDelta -= e.drag.mouseDelta.y * rotationSpeed; 
+                          
+                      /*  let rotationSpeed = Potree.browser.isMobile() ? 4 : 2;   
+                        let old = this.yawDelta
+                        this.yawDelta -= e.drag.pointerDelta.x * rotationSpeed;
+                        this.pitchDelta -= e.drag.pointerDelta.y * rotationSpeed; 
+                           */
+                         
+                         
+                         //console.log('mouseDelta', e.drag.mouseDelta.toArray(),  e.drag.pointerDelta.toArray() )
+                    }else{
+                        
+                        let _matrixWorld = camera.matrixWorld
+                        camera.matrixWorld = new THREE.Matrix4;//unproject 前先把相机置于原点 
+                        
+                        var e1 = new THREE.Vector3(this.pointerDragStart.x,this.pointerDragStart.y,-1).unproject(camera)
+                          , t = new THREE.Vector3(e.pointer.x,e.pointer.y,-1).unproject(camera)
+                          , i = Math.sqrt(e1.x * e1.x + e1.z * e1.z)
+                          , n = Math.sqrt(t.x * t.x + t.z * t.z)
+                          , o = Math.atan2(e1.y, i)
+                          , a = Math.atan2(t.y, n);
+                           
+                        this.pitchDelta +=  o - a  //上下旋转
+                        e1.y = 0,
+                        t.y = 0; 
+                        
+                        var s = Math.acos(e1.dot(t) / e1.length() / t.length());
+                        
+                        if(!isNaN(s)){
+                            var yawDelta = s    //左右旋转 
+                            this.pointerDragStart.x > e.pointer.x && (yawDelta *= -1) 
+                            this.yawDelta += yawDelta
+                        } 
+                        
+                        
+                        //console.log('rotate:', this.pitchDelta, e.pointer.toArray(), this.pointerDragStart.toArray())
+                        
+                        
+                        this.pointerDragStart.copy(e.pointer)
+                         
+                        camera.matrixWorld = _matrixWorld ;
+                            
+                        
+                        
+                    }
                     
                       
                 
@@ -393,7 +411,7 @@ export class FirstPersonControls extends THREE.EventDispatcher {
                 this.translationWorldDelta.add(moveVec.negate()) 
                 this.useAttenuation = false
             }else{
-                let speed = this.currentViewport.getMoveSpeed() * 7, direction
+                let speed = this.currentViewport.getMoveSpeed() * 12, direction
                  
                 
                 if(e.delta != void 0){//滚轮缩放 
@@ -414,8 +432,8 @@ export class FirstPersonControls extends THREE.EventDispatcher {
                     }
                     
                     
-                    let slightly = this.keys.ALT.some(e => this.viewer.inputHandler.pressedKeys[e]); 
-                    slightly && (speed *= 0.2)
+                    let fast = this.keys.SHIFT.some(e => this.viewer.inputHandler.pressedKeys[e]); 
+                    fast && (speed *= 3)
                     
                     
                     
@@ -488,7 +506,7 @@ export class FirstPersonControls extends THREE.EventDispatcher {
         }); */
 		this.viewer.addEventListener('global_drop', drop);
 		this.viewer.addEventListener('global_mousewheel', scroll);
-		this.viewer.addEventListener('global_dblclick', dblclick);
+		//this.viewer.addEventListener('global_dblclick', dblclick);
         
         
         
@@ -733,12 +751,13 @@ export class FirstPersonControls extends THREE.EventDispatcher {
 		}
 	}
 
-	update (delta=1) {
-        if(!this.enabled)return
- 
+	update(delta=1) {
+        if(!this.enabled)return 
+        let deltaRatio = delta / 16 * 1000
+        let ih = this.viewer.inputHandler;
         //console.log('update')
 		let view = this.currentViewport.view  
-
+        let lastPos = view.position.clone()
 		{ // cancel move animations on user input
 			let changes = [ this.yawDelta,
 				this.pitchDelta,
@@ -752,7 +771,6 @@ export class FirstPersonControls extends THREE.EventDispatcher {
 		}
 
 		{ // accelerate while input is given
-			let ih = this.viewer.inputHandler;
 
 			let moveForward = this.keys.FORWARD.some(e => ih.pressedKeys[e]);
 			let moveBackward = this.keys.BACKWARD.some(e => ih.pressedKeys[e]);
@@ -768,8 +786,9 @@ export class FirstPersonControls extends THREE.EventDispatcher {
 
             
             this.lockElevation = this.lockElevationOri || this.keys.SPACE.some(e => ih.pressedKeys[e]);
-            
-            let slightly = this.keys.ALT.some(e => ih.pressedKeys[e]);
+            let fast = this.keys.SHIFT.some(e => ih.pressedKeys[e]);
+            let moveSpeed = this.currentViewport.getMoveSpeed() * deltaRatio * (fast?3:1)
+
             
             if(!this.lockRotation){
                 if(rotateLeft){
@@ -793,34 +812,34 @@ export class FirstPersonControls extends THREE.EventDispatcher {
                     if (moveForward && moveBackward) {
                         this.translationWorldDelta.set(0, 0, 0);
                     } else if (moveForward) {
-                        this.translationWorldDelta.copy(dir.multiplyScalar(this.currentViewport.getMoveSpeed()));
+                        this.translationWorldDelta.copy(dir.multiplyScalar(moveSpeed));
                     } else if (moveBackward) {
-                        this.translationWorldDelta.copy(dir.multiplyScalar(-this.currentViewport.getMoveSpeed()));
+                        this.translationWorldDelta.copy(dir.multiplyScalar(-moveSpeed));
                     }
                 }else{
                     if (moveForward && moveBackward) {
                         this.translationDelta.y = 0;
                     } else if (moveForward) {
-                        this.translationDelta.y = this.currentViewport.getMoveSpeed() * (slightly ? 0.2 : 1);
+                        this.translationDelta.y = moveSpeed;
                     } else if (moveBackward) {
-                        this.translationDelta.y = -this.currentViewport.getMoveSpeed() * (slightly ? 0.2 : 1);
+                        this.translationDelta.y = -moveSpeed;
                     }
                 }
 
                 if (moveLeft && moveRight) {
                     this.translationDelta.x = 0;
                 } else if (moveLeft) {
-                    this.translationDelta.x = -this.currentViewport.getMoveSpeed();
+                    this.translationDelta.x = -moveSpeed
                 } else if (moveRight) {
-                    this.translationDelta.x = this.currentViewport.getMoveSpeed();
+                    this.translationDelta.x = moveSpeed
                 }
 
                 if (moveUp && moveDown) {
                     this.translationWorldDelta.z = 0;
                 } else if (moveUp) {
-                    this.translationWorldDelta.z = this.currentViewport.getMoveSpeed();
+                    this.translationWorldDelta.z = moveSpeed
                 } else if (moveDown) {
-                    this.translationWorldDelta.z = -this.currentViewport.getMoveSpeed();
+                    this.translationWorldDelta.z = -moveSpeed
                 }
                 
                 
@@ -828,27 +847,46 @@ export class FirstPersonControls extends THREE.EventDispatcher {
                     this.useAttenuation = false
                 }
                 
+                 
+                if(this.viewer.joyStick?.state){
+                    let joyState = this.viewer.joyStick.state
+                    let attenuation = 0.8  
+                    let joySpeed = joyState.speed
+                    let speed = joyState.lastSpeed * attenuation + joySpeed * (1-attenuation) //缓冲一下避免变化太突然  
+                    joyState.lastSpeed = speed
+                    speed *= 0.3 * moveSpeed
+                    this.translationDelta.x = joyState.dir.x * speed
+                    this.translationDelta.y = joyState.dir.y * speed 
+                }
+                
             } 
 		}
 
 		{ // apply rotation
 			let yaw = view.yaw;
 			let pitch = view.pitch; 
-            
-			yaw += this.yawDelta /* * delta; */
-			pitch += this.pitchDelta/*  * delta; */
+            /* 
+			yaw += this.yawDelta 
+			pitch += this.pitchDelta  */
 
+            let change = Potree.settings.rotAroundPoint ? 1 : 0.15//Math.min(0.95, 0.3 * deltaRatio)  //限制min画面会跳跃
+             
+            yaw += this.yawDelta * change 
+            pitch += this.pitchDelta * change 
            //this.yawDelta > 0.001 && console.log('yaw', this.yawDelta )
-
+            //Math.abs(this.yawDelta * change ) > 0.0001 && console.log( 'change', change)
 			view.yaw = yaw;
 			view.pitch = pitch;
-            if(this.yawDelta || this.pitchDelta){
+            /* if(this.yawDelta || this.pitchDelta){
                 view.cancelFlying('rotate')
-            }
-
-            
-            this.yawDelta = 0
-            this.pitchDelta = 0 
+            } */
+            
+            //剩余的下次再转
+            this.yawDelta = this.yawDelta * (1-change)
+            this.pitchDelta = this.pitchDelta * (1-change) 
+           
+            /* this.yawDelta = 0
+            this.pitchDelta = 0  */
 		}
         /* if(this.translationWorldDelta.length()>0) {
            // console.log('translationDelta')
@@ -899,5 +937,18 @@ export class FirstPersonControls extends THREE.EventDispatcher {
             this.translationWorldDelta.set(0,0,0)
             
         }  
+        
+        
+        let ignoreColl = this.keys.X.some(e => ih.pressedKeys[e]);
+        let moveDelta = new THREE.Vector3().subVectors(view.position, lastPos)
+        if(!ignoreColl && moveDelta.lengthSq() > 0 ){
+            let coll = viewer.collider?.moveDeltaCorrecting({start: view.position,  radius: 0.3 } )  
+            if(coll?.hit){ 
+                let collDelta = coll.delta
+                view.translateWorld( collDelta.x, collDelta.y, collDelta.z   );
+            }
+        }
+        //滚轮缩放快的话还是会穿透,qiyu也这样,如果实在不允许要加射线检测
+
 	}
 };

+ 34 - 22
src/navigation/InputHandlerNew.js

@@ -63,8 +63,8 @@ export class InputHandler extends THREE.EventDispatcher {
         
         
         document.addEventListener('mouseup', function (event) {
-    event.preventDefault();
-}, { passive: false });
+            event.preventDefault();
+        }, { passive: false });
 		this.domElement.addEventListener('contextmenu', (event) => { event.preventDefault(); }, false);
 		this.domElement.addEventListener('click', this.onMouseClick.bind(this), false);
 		this.domElement.addEventListener('mousedown', this.onMouseDown.bind(this), {passive:false, useCapture:false});
@@ -77,7 +77,9 @@ export class InputHandler extends THREE.EventDispatcher {
         this.domElement.addEventListener('mouseout', ()=>{
             this.containsMouse = false
         }, false);
-        
+        document.addEventListener('contextmenu', function(e) {//防止弹菜单 
+            e.preventDefault(); 
+        });
         
 		this.domElement.addEventListener('mousemove', this.onMouseMove.bind(this), {passive:false, useCapture:false}); 
         //add
@@ -161,20 +163,19 @@ export class InputHandler extends THREE.EventDispatcher {
         this.touches = touches.map(touch=>{
             let touch_ = oldTouches.find(a=>a.touch.identifier == touch.identifier)
             let pointer = touch_ && touch_.pointer  //复制原先的值
+            let mouse = touch_ && touch_.mouse  //复制原先的值
+            
             //let isAtDomElement = touch.target == this.domElement 
             return { 
-                touch,   pointer,  //isAtDomElement
+                touch,   pointer,  mouse //isAtDomElement
             }
         }) 
+         
         
-        
-        
-        
-        if(touches.length > 0){ 
-            
+        if(touches.length > 0){  
             let newTouches = touches.filter(e=>!
                 oldTouches.some(a=>a.touch.identifier == e.identifier) && !changedTouches.some(a=>a.identifier == e.identifier)  
-            ) //从按钮处划过时e.touches中会出现this.touches和changedTouches中都没有的identifier
+            ) //从按钮处划过时e.touches中会出现this.touches和changedTouches中都没有的identifier (别的dom的touch在触发该dom的touch事件时也会保留)
             if(newTouches.length>0){
                 console.warn('has new',newTouches.map(e=>e.identifier))
             }
@@ -182,24 +183,25 @@ export class InputHandler extends THREE.EventDispatcher {
             newTouches.concat(changedTouches).forEach(touch=>{   //修改changedTouches的 
                 let touch_ = this.touches.find(a=>a.touch.identifier == touch.identifier)
                 if(touch_){
-                    let a = this.getPointerInViewport(touch.pageX, touch.pageY, this.dragViewport||viewport, new THREE.Vector2) 
+                    let a = this.getPointerInViewport(touch.pageX, touch.pageY, this.dragViewport||viewport, new THREE.Vector2, new THREE.Vector2) 
                     touch_.pointer = a.pointer.clone() 
+                    touch_.mouse = a.mouse.clone() 
                     viewport = a.viewport;  camera = a.camera
                 } 
             })
-            
-            
+             
             
             //使用当前touches的平均
             if(touches.length > 1){
                 let pageX = Common.average(touches, "pageX")
                 let pageY = Common.average(touches, "pageY")
-                let a = this.getPointerInViewport(pageX, pageY, viewport, new THREE.Vector2) 
-                this.pointer.copy(a.pointer)
-                
+                let a = this.getPointerInViewport(pageX, pageY, viewport, new THREE.Vector2, new THREE.Vector2) 
+                this.pointer.copy(a.pointer) 
+                this.mouse.copy(a.mouse) 
                 
             }else{
                 this.pointer = this.touches[0].pointer.clone() //更新,使用当前touches中的第一个 
+                this.mouse = this.touches[0].mouse.clone()
             }
             //console.log('touchesInfo', this.pointer.x.toPrecision(2), this.pointer.y.toPrecision(2) , this.touches.length)    
             
@@ -433,6 +435,7 @@ export class InputHandler extends THREE.EventDispatcher {
             if(this.drag){
                 //因为触屏在按下前缺少pointermove所以要更新下
                 this.drag.end = this.pointer.clone()
+                this.drag.lastMouse = this.mouse.clone()
             }
              
              
@@ -452,6 +455,8 @@ export class InputHandler extends THREE.EventDispatcher {
             let dontIntersect = false
             this.intersect = this.getIntersect({viewport,  dontIntersect, clientX:e.clientX, clientY:e.clientY}) //更新intersect,避免在没有mousemove但flyToPano后intersect未更新。
             //this.intersect = this.getWholeIntersect()  
+            this.pointerDownIntersect = this.intersect
+            
         }
         if(!viewport)return //why add this?
         if (!this.drag) {
@@ -569,7 +574,7 @@ export class InputHandler extends THREE.EventDispatcher {
         }
         
         this.drag.end.copy(this.pointer) 
-        
+        this.drag.lastMouse.copy(this.mouse) 
         if(isTouch && e.touches.length >= 1){ 
             return
         }
@@ -755,13 +760,15 @@ export class InputHandler extends THREE.EventDispatcher {
 
 
 
-    getPointerInViewport(clientX, clientY, viewForceAt, pointer ){
+    getPointerInViewport(clientX, clientY, viewForceAt, pointer, mouse ){
         let rect = this.domElement.getBoundingClientRect();
         let x = clientX - rect.left;
         let y = clientY - rect.top;
+         
         let camera 
         let viewport 
-        pointer = pointer ||this.pointer
+        mouse = mouse || this.mouse
+        pointer = pointer || this.pointer
         //if(this.viewer.viewports || viewForceAt){
             var getDimension = (view)=>{
                 var left = Math.ceil(this.domElement.clientWidth * view.left)
@@ -772,8 +779,9 @@ export class InputHandler extends THREE.EventDispatcher {
                 return {left, bottom, width, height, top}
             }
             var getView = (view, left, bottom, width, height, top)=>{
-                this.mouse.set(x-left, y - top )
-                Utils.convertScreenPositionToNDC(pointer, this.mouse, width, height);
+                /* this. */mouse.set(x-left, y - top )
+                //console.log('setmouse', this.mouse.x, this.mouse.y) 
+                Utils.convertScreenPositionToNDC(pointer, /* this. */mouse, width, height);
                 //console.log('更新pointer2',this.pointer.toArray())
                 camera = view.camera;
                 viewport = view 
@@ -806,7 +814,7 @@ export class InputHandler extends THREE.EventDispatcher {
             }  
        
         return { 
-            camera, viewport, pointer 
+            camera, viewport, pointer, mouse
         }
     } 
 
@@ -1116,7 +1124,9 @@ export class InputHandler extends THREE.EventDispatcher {
             //this.drag.pointer = this.pointer.clone();
             //this.drag.hoverViewport = this.hoverViewport
             this.drag.pointerDelta.subVectors(this.pointer,  this.drag.end)
+            this.drag.mouseDelta.subVectors(this.mouse,  this.drag.lastMouse)
             this.drag.end.copy(this.pointer)
+            this.drag.lastMouse.copy(this.mouse)
             let dragConsumed = false; 
             
             if (this.drag.object && (e.buttons == Buttons.NONE || !this.drag.notPressMouse )){//如果是本不需要按鼠标的拖拽,但按下了鼠标,就不执行这段(改为拖拽场景,如添加测量时突然拖拽画面)
@@ -1298,7 +1308,9 @@ export class InputHandler extends THREE.EventDispatcher {
 		this.drag = {
 			start: this.pointer.clone(),
 			end: this.pointer.clone(),
+            lastMouse: this.mouse.clone(),
 			pointerDelta: new THREE.Vector2(0, 0), 
+            mouseDelta: new THREE.Vector2(0, 0), 
 			object: object, 
             hoverViewport: this.hoverViewport,   //会变化
             dragViewport: this.hoverViewport, //不变

+ 173 - 0
src/navigation/JoyStick.js

@@ -0,0 +1,173 @@
+import * as THREE from "../../libs/three.js/build/three.module.js";
+ 
+ 
+const setting = {
+    speedMax:50 
+    
+} 
+ 
+export default class JoyStick extends THREE.EventDispatcher{
+    
+    constructor(container, bindControls){
+        super()
+        let domElement = document.createElement('div')
+        domElement.id = 'joystick'
+        domElement.innerHTML = `
+            <div class="joyCircle"  > 
+                <img src = "${Potree.resourcePath + '/images/joy_speed_low.svg'}" >
+            </div>     
+        ` 
+        container.appendChild(domElement)
+        
+        this.circle = $(domElement).find('.joyCircle')
+        this.circle.css({
+            position: 'absolute',
+            width: '120px',
+            height: '120px',  
+            '-webkit-user-select': 'none',
+            'user-select': 'none',
+            'pointer-events': 'none', 
+            transform: 'translate(-50%, -50%)',
+            //transition: 'opacity 0.3s',
+            opacity: 0
+        }) 
+        $(domElement).css({
+            width: '50%',
+            height: '100%',
+            'z-index': 1000,
+            position: 'absolute'
+        }) 
+        
+        
+        domElement.addEventListener('touchstart',this.onTouchStart.bind(this))
+        domElement.addEventListener('touchmove',this.onTouchMove.bind(this))
+        domElement.addEventListener('touchend',this.onTouchEnd.bind(this))
+        this.domElement = domElement
+        
+        
+        this.bindControls = bindControls
+        
+        this.speed = 1
+        this.dir = new THREE.Vector2(0,1) 
+        
+        this.updateEnable()
+        viewer.addEventListener('setControls', this.updateEnable.bind(this))
+    }
+    
+    onTouchStart(e){
+        if(this.pointerDownPos)return
+         
+        let touch = Array.from(e.touches).find(e=>e.target == this.domElement)
+        
+        //this.touchId = touch.identifier
+        this.pointerDownPos = new THREE.Vector2(touch.clientX, touch.clientY)
+        this.updateRotate(0)
+        this.updatePos()
+        this.updateSpeed() 
+        this.dispatchEvent('touchstart')
+        this.updateState('touchstart')
+        
+        this.circle.css({
+            transition: 'none',
+            opacity: 1
+        })
+        
+        e.preventDefault()
+        e.stopPropagation()
+    }
+    
+     
+    
+    onTouchMove(e){
+        if(!this.pointerDownPos )return
+         
+        let touch = Array.from(e.touches).find(e=>e.target == this.domElement)
+        if(!touch)return console.error('no touch?')
+        let coord = new THREE.Vector2(touch.clientX, touch.clientY)
+        
+        let vec = new THREE.Vector2().subVectors(coord, this.pointerDownPos) 
+        let dis = vec.lengthSq()
+        
+        let angle = vec.angle() + Math.PI/2    //Math.atan2( ease.x, ease.z )
+        let dir = vec.normalize()
+            dir.y *= -1
+        if(dis > 0) this.updateRotate(angle, dir) 
+        this.updateSpeed(dis) 
+        this.dispatchEvent('touchmove')
+        this.updateState('touchmove')
+        
+        e.preventDefault()
+        e.stopPropagation()
+    }
+    
+    
+    
+    onTouchEnd(e){ 
+        this.pointerDownPos = null
+        this.circle.css({
+            transition: 'opacity 0.3s',
+            opacity: 0
+        })
+        this.dispatchEvent('touchend')
+        this.updateState('touchend')
+    }
+    
+    
+    updateRotate(rad, dir){
+        
+        //console.log('rad',rad)
+        let Deg = THREE.Math.radToDeg(rad) 
+        this.circle.css({transform: ` translate(-50%, -50%) rotate(${Deg}deg)`})
+        this.rad = rad
+        this.dir = dir || new THREE.Vector2(0,1) //默认向前
+        //console.log('dir', dir)
+    }
+    
+     
+    updatePos(){ 
+        this.circle.css({ left: this.pointerDownPos.x+'px',  top:this.pointerDownPos.y+'px'})  
+    }
+    
+    updateSpeed(disSq){ 
+        if(!disSq){
+            this.speed = 1
+        }else{ 
+            let oldSpeed = this.speed 
+            let a = 0.62
+            let b = 0.025
+            this.speed = 1 + Math.pow(disSq, a) * b
+            /* let attenuation = Potree.browser.urlHasValue('c',true) || 0.5  
+            this.speed = oldSpeed * attenuation + speed * (1-attenuation) //缓冲一下避免变化太突然 */
+        } 
+        //console.log('speed', this.speed )
+    } 
+    
+    updateEnable(){
+        this.setEnable(
+            Potree.browser.isMobile() && 
+            (Potree.settings.displayMode != 'showPanos') && 
+            this.bindControls.includes(viewer.controls) 
+        )
+        
+    }
+    setEnable(enable){
+        this.enabled = enable
+        this.domElement.style.display = enable ? 'block' : 'none'
+        //Potree.settings.ctrlRotInvSmooth = enable
+        enable || (this.dispatchEvent('unable'), this.updateState())  
+         
+    }
+    
+    updateState(msg){
+        if(msg == 'touchstart' || msg == 'touchmove'){
+            this.state = {
+                speed:  this.speed,
+                dir: this.dir,
+                lastSpeed: msg == 'touchstart' ? 1 : this.state.lastSpeed
+            }
+        }else{
+            this.state = null
+        } 
+    }
+    
+}

+ 66 - 48
src/navigation/OrbitControlsNew.js

@@ -31,7 +31,7 @@ export class OrbitControls extends THREE.EventDispatcher{
 		this.scene = null;
 		this.sceneControls = new THREE.Scene();
 
-		this.rotationSpeed = 3;  //旋转速度
+		this.rotationSpeed = Potree.browser.isMobile() ? 0.006 : 0.002;   //旋转速度
          
         viewport = viewport || viewer.viewports[0]
         
@@ -67,17 +67,10 @@ export class OrbitControls extends THREE.EventDispatcher{
             
             let viewport = e.dragViewport;
             if(!viewport /* || viewport.camera.type == "OrthographicCamera"  */)return
-            //let camera = viewport.camera 
-          
-
-
-			/* if (e.drag.object !== null) {
-				return;
-			} */
+             
             let mode
-            
-            if(e.isTouch){
-               
+            let view = viewport.view//this.currentViewport.view
+            if(e.isTouch){ 
                 if(e.touches.length == 1){
                     mode = 'rotate'  
                 }else{  
@@ -85,11 +78,7 @@ export class OrbitControls extends THREE.EventDispatcher{
                 }  
             }else{
                 mode = e.buttons === Potree.defines.Buttons.LEFT ? 'rotate' : 'pan'
-            }
-
-
-
-
+            } 
             
 			if (e.drag.startHandled === undefined) {
 				e.drag.startHandled = true; 
@@ -97,27 +86,31 @@ export class OrbitControls extends THREE.EventDispatcher{
 			}
 
 			 
-            let ndrag = e.drag.pointerDelta.clone()//.add(new THREE.Vector2(1,1)).multiplyScalar(0.5)
-            ndrag.y *= -1
-
-			if (mode == 'rotate') { 
-                 
+            /* let ndrag = e.drag.mouseDelta.clone() //    e.drag.pointerDelta.clone()//.add(new THREE.Vector2(1,1)).multiplyScalar(0.5)
+            ndrag.y *= -1 */
+           
+			if (mode == 'rotate') {
+                let ndrag = e.drag.mouseDelta.clone()
+                if(Potree.settings.ctrlRotInvSmooth){
+                    ndrag.x *= -1
+                }         
+                if(ndrag.x || ndrag.y){
+                    view.cancelFlying('rotate')
+                }
 				this.yawDelta += ndrag.x * this.rotationSpeed;
-				this.pitchDelta += ndrag.y * this.rotationSpeed;
-
-				
+				this.pitchDelta -= ndrag.y * this.rotationSpeed;
+                
 			} else if(mode == 'pan'){
                 if(!this.dragStarted) this.updateRadius('startPan')
-				this.panDelta.x += ndrag.x;
-				this.panDelta.y += ndrag.y;
-                
-				 
+				this.panDelta.x += e.drag.pointerDelta.x;
+				this.panDelta.y -= e.drag.pointerDelta.y;
+                 
 			}else if(mode == 'scale-pan'){ //add
                 this.dollyEnd.subVectors(e.touches[0].pointer, e.touches[1].pointer); 
                 var scale = this.dollyEnd.length() / this.dollyStart.length() 
                   
                 this.dollyStart.copy(this.dollyEnd); 
-                this.radiusDelta = (1-scale) * this.currentViewport.view.radius 
+                this.radiusDelta = (1-scale) * view.radius 
 			  
                 //------------------------
                 //平移
@@ -128,10 +121,8 @@ export class OrbitControls extends THREE.EventDispatcher{
                 this.panDelta.add(delta)
                 
                 this.lastScalePointer = pointer.clone()
-                
-                
-                
-                //console.log('scale ',scale, this.radiusDelta)
+                  
+                //console.log('scale ',scale, this.radiusDelta  )
                 
             }
             
@@ -140,11 +131,8 @@ export class OrbitControls extends THREE.EventDispatcher{
             
 		};
         
-        
-        
          
         
-        
 		let drop = e => {
             if(!this.enabled)return
             this.dragStarted = false
@@ -228,12 +216,12 @@ export class OrbitControls extends THREE.EventDispatcher{
 		this.viewer.addEventListener('global_drop', drop);
 		this.viewer.addEventListener('global_mousewheel', scroll);
 		this.viewer.addEventListener('global_dblclick', dblclick);
-        this.viewer.addEventListener('global_touchmove', (e)=>{ 
+        /* this.viewer.addEventListener('global_touchmove', (e)=>{ 
             if(e.touches.length>1){//单指的就触发上一句 
                 //console.log('global_touchmove' )
                 drag(e)
             }
-        });
+        }); */
         let prepareScale = (e)=>{//触屏的scale
             this.dollyStart.subVectors(e.touches[0].pointer, e.touches[1].pointer);
             this.lastScalePointer = new THREE.Vector2().addVectors(e.touches[0].pointer, e.touches[1].pointer).multiplyScalar(0.5);//两个指头的中心点
@@ -255,14 +243,14 @@ export class OrbitControls extends THREE.EventDispatcher{
         }) */
         
         
-        this.viewer.addEventListener('focusOnObject',(o)=>{
+        /* this.viewer.addEventListener('focusOnObject',(o)=>{
             if(o.position && o.CamTarget){
                 let distance = o.position.distanceTo(o.CamTarget)
                 //if(distance < minRadius) minRadius = distance * 0.5 //融合页面当focus一个很小的物体时,需要将minRadius也调小
                 this.minRadius = Math.min(standartMinRadius, distance * 0.5)
                 //console.log('focus dis', distance) 
             }
-        })
+        }) */
          
 	}
 
@@ -284,14 +272,17 @@ export class OrbitControls extends THREE.EventDispatcher{
         this.enabled = enabled
     }
 	stop(){
-		this.yawDelta = 0;
-		this.pitchDelta = 0;
+        if(!this.progression){
+            this.yawDelta = 0;
+            this.pitchDelta = 0;
+        }
 		this.radiusDelta = 0;
 		this.panDelta.set(0, 0);
 	}
 	  
     zoomToLocation(mouse){
-        let I = viewer.inputHandler.intersect;
+        let I = viewer.inputHandler.intersect || viewer.inputHandler.pointerDownIntersect;
+         
         let object  
         if(I){ 
             object = I.object || I.pointcloud;
@@ -417,15 +408,15 @@ export class OrbitControls extends THREE.EventDispatcher{
             }else if(moveDown){
                 pz = -1 * moveSpeed 
             }
-            let forceHorizon = !Potree.settings.orbitCtlMoveFree  ;
-            (px!=0 || py!=0 || pz!=0) && view.translate(px, py, pz, forceHorizon); 
+            
+            (px!=0 || py!=0 || pz!=0) && view.translate(px, py, pz, {forceHorizon:!Potree.settings.orbitCtlMoveFree  }); 
              
         }
                 
  
 
 
-		{ // apply rotation
+		 /*  { // apply rotation
 			let progression = Math.min(1, this.fadeFactor * delta);
 
 			let yaw = view.yaw;
@@ -442,7 +433,34 @@ export class OrbitControls extends THREE.EventDispatcher{
 			let position = new THREE.Vector3().addVectors(pivot, V);
 
 			view.position.copy(position);
-		}
+		}   */
+        
+        
+       { // apply rotation
+			let yaw = view.yaw;
+			let pitch = view.pitch;  
+            let pivot = view.getPivot();
+            let change = this.progression ? 0.15 : 1 //Math.min(0.95, 0.3 * deltaRatio)  //限制min画面会跳跃
+            
+            yaw += this.yawDelta * change 
+            pitch += this.pitchDelta * change 
+           //this.yawDelta > 0.001 && console.log('yaw', this.yawDelta )
+            //Math.abs(this.yawDelta * change ) > 0.0001 && console.log( 'change', change)
+			view.yaw = yaw;
+			view.pitch = pitch;
+            /* if(this.yawDelta || this.pitchDelta){
+                view.cancelFlying('rotate')
+            }
+             */
+            //剩余的下次再转
+            this.yawDelta = this.yawDelta * (1-change)
+            this.pitchDelta = this.pitchDelta * (1-change) 
+            
+            let V = this.currentViewport.view.direction.multiplyScalar(-view.radius);
+			let position = new THREE.Vector3().addVectors(pivot, V);
+
+			view.position.copy(position);
+		} 
 
 		if(camera.type != 'OrthographicCamera'){ // apply pan 平移 
             let panDistance = view.radius * Math.tan(THREE.Math.degToRad(camera.fov / 2));//参照4dkk  只要radius设置正确就完全跟手(见updateRadius)  
@@ -491,7 +509,7 @@ export class OrbitControls extends THREE.EventDispatcher{
 			this.radiusDelta -= progression * this.radiusDelta; */
             
             //取消衰减,直接stop
-            this.stop()  
+             this.stop()  
 		}
 	}
 };

+ 11 - 3
src/utils/TransformationToolNew.js

@@ -176,6 +176,8 @@ export class TransformationTool extends THREE.EventDispatcher{
             this.addEventListener('stopDrag', (e)=>{ 
                 let object = viewer.transformationTool.selection[0]
                 object && this.history.afterChange({object, matrix:object.matrix.clone()} )
+                object?.dispatchEvent({type:'stopDrag'})//add
+                
             })
             /* viewer.inputHandler.addEventListener('keydown', (e)=>{
                 if(e.keyCode == 90 && e.event.ctrlKey){//Z
@@ -757,7 +759,7 @@ export class TransformationTool extends THREE.EventDispatcher{
                 let diffQua = new THREE.Quaternion().setFromAxisAngle( normal, angle )//变化量,参考 selection.rotateOnAxis(normal, angle);
                 
                 let moveToZero = new THREE.Matrix4().setPosition(center.clone().negate()) //先将boundingBox中心(整体)移动到原点
-                let rotM = new THREE.Matrix4().makeRotationFromQuaternion(quaternion.clone().inverse().premultiply(diffQua).premultiply(quaternion) );//再旋转。根据selection.rotateOnAxis,应该是旧的qua 右乘 diffQua,所以先用invert消掉旧的qua
+                let rotM = new THREE.Matrix4().makeRotationFromQuaternion(quaternion.clone().invert().premultiply(diffQua).premultiply(quaternion) );//再旋转。根据selection.rotateOnAxis,应该是旧的qua 右乘 diffQua,所以先用invert消掉旧的qua
                 let moveBack = new THREE.Matrix4().setPosition(center.clone())//移动回去,使boundingBox中心位置还原
                 
                 selection.matrix.premultiply(moveToZero).premultiply(rotM).premultiply(moveBack)
@@ -785,6 +787,7 @@ export class TransformationTool extends THREE.EventDispatcher{
 		this.dragging = false;
 		this.setActiveHandle(null);
         this.dispatchEvent({type:'stopDrag', handle:'rotation'})//add
+         
 	}
 
 	dragTranslationHandle(e){//---大改,参考transformControls,为了加上xyz xy yz xz 这四个方向的变换。 (但感觉好像plane上有丢丢延迟?是因为drag延迟还是worldmatrix没更新)
@@ -894,17 +897,19 @@ export class TransformationTool extends THREE.EventDispatcher{
         }
 	}
 
-
+    
 	dropTranslationHandle(e){
 		this.dragging = false;
 		this.setActiveHandle(null);
         this.dispatchEvent({type:'stopDrag', handle:'translation'})//add
+         
 	}
 
 	dropScaleHandle(e){
 		this.dragging = false;
 		this.setActiveHandle(null);
-        this.dispatchEvent({type:'stopDrag', handle:'scale'})//add
+        this.dispatchEvent({type:'stopDrag', handle:'scale', object: e.drag.object})//add
+        
 	}
 
 	dragScaleHandle(e){
@@ -1009,6 +1014,9 @@ export class TransformationTool extends THREE.EventDispatcher{
 				//Utils.debugSphere(viewer.scene.scene, drag.pivot, 0.05);
 			}
 		}
+        
+        this.dropHandle(e)
+        
 	}
  
 	setActiveHandle(handle){

+ 3 - 1
src/viewer/EDLRendererNew.js

@@ -323,7 +323,9 @@ export class EDLRenderer{//Eye-Dome Lighting 眼罩照明
                     viewer.objs.traverse(e=>{if(e.material)e._OlddepthWrite = e.material.depthWrite, e.material.depthWrite = true}) //否则半透明的mesh无法遮住测量线
                     renderer.render(viewer.scene.scene, camera);
                     viewer.objs.traverse(e=>{if(e.material)e.material.depthWrite = e._OlddepthWrite})
-                }  
+                }
+
+                viewer.dispatchEvent({type:'renderToRTDepth',viewport:params.viewport})
             } 
 		}
            

+ 7 - 6
src/viewer/ExtendView.js

@@ -125,24 +125,25 @@ class ExtendView extends View {
     pan (x, y) { //发现pan其实就是translate
 		this.translate(x, 0, y)
 	}
-	translate (x, y, z, forceHorizon ) {
+	translate (x, y, z, {forceHorizon, onlyGetVec}={}) {
         //相机方向
 		let dir = new THREE.Vector3(0, 1, 0);
 		dir.applyAxisAngle(new THREE.Vector3(1, 0, 0), forceHorizon ? 0 : this.pitch); //上下角度
-		dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw);//水平角度
-
+		dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw);//水平角度  
+        
+        
 		let side = new THREE.Vector3(1, 0, 0);
 		side.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw);  //垂直于相机当前水平朝向的 左右方向
 
 		let up = side.clone().cross(dir); //垂直于相机当前水平朝向的 向上方向
 
-		let t = side.multiplyScalar(x)   //x影响 左右分量
+		let shift = side.multiplyScalar(x)   //x影响 左右分量
 			.add(dir.multiplyScalar(y))  //y影响 前后分量
 			.add(up.multiplyScalar(z));  //z影响 上下分量
              
+        if(onlyGetVec)return shift
         
-        
-		this.position = this.position.add(t);
+		this.position = this.position.add(shift);
          
         if((!math.closeTo(x, 0, 1e-2) || !math.closeTo(y, 0, 1e-2) || !math.closeTo(z, 0, 1e-2)) && Potree.settings.displayMode != 'showPanos'){
             this.cancelFlying('pos')

+ 5 - 4
src/viewer/View.js

@@ -82,11 +82,12 @@ export class View{//base
 
 		return side;
 	}
+ 
 
 	pan (x, y) {
-		let dir = new THREE.Vector3(0, 1, 0);
+		let dir = this.direction/* new THREE.Vector3(0, 1, 0);
 		dir.applyAxisAngle(new THREE.Vector3(1, 0, 0), this.pitch);
-		dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw);
+		dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw); */
 
 		// let side = new THREE.Vector3(1, 0, 0);
 		// side.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw);
@@ -102,9 +103,9 @@ export class View{//base
 	}
 
 	translate (x, y, z) {
-		let dir = new THREE.Vector3(0, 1, 0);
+		let dir = this.direction/* new THREE.Vector3(0, 1, 0);
 		dir.applyAxisAngle(new THREE.Vector3(1, 0, 0), this.pitch);
-		dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw);
+		dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw); */
 
 		let side = new THREE.Vector3(1, 0, 0);
 		side.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.yaw);