Three.js PathControl源码解析

/**
 * @author alteredq / http://alteredqualia.com/
 */

THREE.PathControls = function ( object, domElement ) {

    this.object = object;
    this.domElement = ( domElement !== undefined ) ? domElement : document;

    this.id = "PathControls" + THREE.PathControlsIdCounter ++;

    // API

    this.duration = 10 * 1000; // milliseconds; // 运动时间
    this.waypoints = [];// 轨道上的关键点,一般都是轨道上的转折点,因为非转折点一般可以通过插值算出来

    this.useConstantSpeed = true;// 是否匀速
    this.resamplingCoef = 50;

    this.debugPath = new THREE.Object3D();// 调试线,有了它可以判断相机是否按照预定轨道来行进
    this.debugDummy = new THREE.Object3D();

    this.animationParent = new THREE.Object3D();

    this.lookSpeed = 0.005;// 转头速度
    // 下面两个表示相机是否可以上下、左右摆动
    this.lookVertical = true;
    this.lookHorizontal = true;
    
    this.verticalAngleMap   = { srcRange: [ 0, 2 * Math.PI ], dstRange: [ 0, 2 * Math.PI ] };
    this.horizontalAngleMap = { srcRange: [ 0, 2 * Math.PI ], dstRange: [ 0, 2 * Math.PI ] };

    // internals

    this.target = new THREE.Object3D();

    this.mouseX = 0;
    this.mouseY = 0;

    this.lat = 0;
    this.lon = 0;

    this.phi = 0;
    this.theta = 0;

    var PI2 = Math.PI * 2;
    // 在渲染时候,为了计算渲染的视图大小、鼠标所在位置等,我们需要提前知道视图大小;
    // 这里讲视图大小不能出在viewHalfX和viewHalfY变量中,这样当鼠标移动的时候,
    // 我们就可以知道鼠标在窗口中的相对位置了,这样可以根据鼠标来控制相机
    this.viewHalfX = 0;
    this.viewHalfY = 0;

    if ( this.domElement !== document ) {

        this.domElement.setAttribute( 'tabindex', -1 );

    }

    // methods

    this.handleResize = function () {

        if ( this.domElement === document ) {

            this.viewHalfX = window.innerWidth / 2;
            this.viewHalfY = window.innerHeight / 2;

        } else {

            this.viewHalfX = this.domElement.offsetWidth / 2;
            this.viewHalfY = this.domElement.offsetHeight / 2;

        }

    };

    this.update = function ( delta ) {

        var srcRange, dstRange;

        if( this.lookHorizontal ) this.lon += this.mouseX * this.lookSpeed * delta;
        if( this.lookVertical )   this.lat -= this.mouseY * this.lookSpeed * delta;

        this.lon = Math.max( 0, Math.min( 360, this.lon ) );
        this.lat = Math.max( - 85, Math.min( 85, this.lat ) );

        this.phi = THREE.Math.degToRad( 90 - this.lat );
        this.theta = THREE.Math.degToRad( this.lon );

        this.phi = normalize_angle_rad( this.phi );

        // constrain vertical look angle

        srcRange = this.verticalAngleMap.srcRange;
        dstRange = this.verticalAngleMap.dstRange;

        var tmpPhi = THREE.Math.mapLinear( this.phi, srcRange[ 0 ], srcRange[ 1 ], dstRange[ 0 ], dstRange[ 1 ] );
        var tmpPhiFullRange = dstRange[ 1 ] - dstRange[ 0 ];
        var tmpPhiNormalized = ( tmpPhi - dstRange[ 0 ] ) / tmpPhiFullRange;

        this.phi = QuadraticEaseInOut( tmpPhiNormalized ) * tmpPhiFullRange + dstRange[ 0 ];

        // constrain horizontal look angle

        srcRange = this.horizontalAngleMap.srcRange;
        dstRange = this.horizontalAngleMap.dstRange;

        var tmpTheta = THREE.Math.mapLinear( this.theta, srcRange[ 0 ], srcRange[ 1 ], dstRange[ 0 ], dstRange[ 1 ] );
        var tmpThetaFullRange = dstRange[ 1 ] - dstRange[ 0 ];
        var tmpThetaNormalized = ( tmpTheta - dstRange[ 0 ] ) / tmpThetaFullRange;

        this.theta = QuadraticEaseInOut( tmpThetaNormalized ) * tmpThetaFullRange + dstRange[ 0 ];

        var targetPosition = this.target.position,
            position = this.object.position;

        targetPosition.x = 100 * Math.sin( this.phi ) * Math.cos( this.theta );
        targetPosition.y = 100 * Math.cos( this.phi );
        targetPosition.z = 100 * Math.sin( this.phi ) * Math.sin( this.theta );

        this.object.lookAt( this.target.position );

    };

    this.onMouseMove = function ( event ) {

        if ( this.domElement === document ) {

            this.mouseX = event.pageX - this.viewHalfX;
            this.mouseY = event.pageY - this.viewHalfY;

        } else {

            this.mouseX = event.pageX - this.domElement.offsetLeft - this.viewHalfX;
            this.mouseY = event.pageY - this.domElement.offsetTop - this.viewHalfY;

        }

    };

    // utils

    function normalize_angle_rad( a ) {

        var b = a % PI2;
        return b >= 0 ? b : b + PI2;

    };

    function distance( a, b ) {

        var dx = a[ 0 ] - b[ 0 ],
            dy = a[ 1 ] - b[ 1 ],
            dz = a[ 2 ] - b[ 2 ];

        return Math.sqrt( dx * dx + dy * dy + dz * dz );

    };

    function QuadraticEaseInOut ( k ) {

        if ( ( k *= 2 ) < 1 ) return 0.5 * k * k;
        return - 0.5 * ( --k * ( k - 2 ) - 1 );

    };

    function bind( scope, fn ) {

        return function () {

            fn.apply( scope, arguments );

        };

    };

    function initAnimationPath( parent, spline, name, duration ) {

        var animationData = {

           name: name,
           fps: 0.6,
           length: duration,

           hierarchy: []

        };

        var i,
            parentAnimation, childAnimation,
            path = spline.getControlPointsArray(),
            sl = spline.getLength(),
            pl = path.length,
            t = 0,
            first = 0,
            last  = pl - 1;
        // 将path路径中的所有点,包括开始点和结束点都转换成为一个个关键帧,
        // 将这些关键帧存到自定义的parentAnimation变量中,关键帧包括相机的位置、缩放、旋转参数等,
        // 将这些关键帧组成的动作,通过THREE.AnimationHandler的add函数,加到动画引擎中;
        // 动画引擎负责在关键帧之间插值,来决定关键帧中间的相机的位置、大小、缩放等。
        parentAnimation = { parent: -1, keys: [] };
        parentAnimation.keys[ first ] = { time: 0,        pos: path[ first ], rot: [ 0, 0, 0, 1 ], scl: [ 1, 1, 1 ] };
        parentAnimation.keys[ last  ] = { time: duration, pos: path[ last ],  rot: [ 0, 0, 0, 1 ], scl: [ 1, 1, 1 ] };

        for ( i = 1; i < pl - 1; i++ ) {

            // real distance (approximation via linear segments)

            t = duration * sl.chunks[ i ] / sl.total;

            // equal distance

            //t = duration * ( i / pl );

            // linear distance

            //t += duration * distance( path[ i ], path[ i - 1 ] ) / sl.total;

            parentAnimation.keys[ i ] = { time: t, pos: path[ i ] };

        }

        animationData.hierarchy[ 0 ] = parentAnimation;
        // Animationhandler已经改名为AnimationMixer
        THREE.AnimationHandler.add( animationData );

        return new THREE.Animation( parent, name, THREE.AnimationHandler.CATMULLROM_FORWARD, false );

    };


    function createSplineGeometry( spline, n_sub ) {

        var i, index, position,
            geometry = new THREE.Geometry();

        for ( i = 0; i < spline.points.length * n_sub; i ++ ) {

            index = i / ( spline.points.length * n_sub );
            position = spline.getPoint( index );

            geometry.vertices[ i ] = new THREE.Vector3( position.x, position.y, position.z );

        }

        return geometry;

    };

    function createPath( parent, spline ) {

        var lineGeo = createSplineGeometry( spline, 10 ),
            particleGeo = createSplineGeometry( spline, 10 ),
            lineMat = new THREE.LineBasicMaterial( { color: 0xff0000, linewidth: 3 } ),
            lineObj = new THREE.Line( lineGeo, lineMat ),
            particleObj = new THREE.ParticleSystem( particleGeo, new THREE.ParticleBasicMaterial( { color: 0xffaa00, size: 3 } ) );

        lineObj.scale.set( 1, 1, 1 );
        parent.add( lineObj );

        particleObj.scale.set( 1, 1, 1 );
        parent.add( particleObj );

        var waypoint,
            geo = new THREE.SphereGeometry( 1, 16, 8 ),
            mat = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );

        for ( var i = 0; i < spline.points.length; i ++ ) {

            waypoint = new THREE.Mesh( geo, mat );
            waypoint.position.copy( spline.points[ i ] );
            parent.add( waypoint );

        }

    };

    this.init = function ( ) {

        // constructor

        this.spline = new THREE.Spline();// 定义了一条显示路径的线
        this.spline.initFromArray( this.waypoints );// 将关键点复制到线中去

        if ( this.useConstantSpeed ) {
            // 如果相机速度时恒定的,对spline进行重采样
            // 比如三个点生成一条直线,那么在转折角处,一定会出现速度不恒定情况,
            // 为解决这个问题,让相机在路径上基本都是匀速运行,我们可以根据三个点拟合出一条曲线,
            // 然后根据采样大小,将拟合后的点重新放入spline中,新采样点一定会比之前多
            // 通过参数开控制采样点多少,参数越小采样点越多
            this.spline.reparametrizeByArcLength( this.resamplingCoef );

        }

        if ( this.createDebugDummy ) {// 是否绘制一直物体,调试作用

            var dummyParentMaterial = new THREE.MeshLambertMaterial( { color: 0x0077ff } ),
            dummyChildMaterial  = new THREE.MeshLambertMaterial( { color: 0x00ff00 } ),
            dummyParentGeo = new THREE.CubeGeometry( 10, 10, 20 ),
            dummyChildGeo  = new THREE.CubeGeometry( 2, 2, 10 );

            this.animationParent = new THREE.Mesh( dummyParentGeo, dummyParentMaterial );

            var dummyChild = new THREE.Mesh( dummyChildGeo, dummyChildMaterial );
            dummyChild.position.set( 0, 10, 0 );

            this.animation = initAnimationPath( this.animationParent, this.spline, this.id, this.duration );

            this.animationParent.add( this.object );
            this.animationParent.add( this.target );
            this.animationParent.add( dummyChild );

        } else {

            this.animation = initAnimationPath( this.animationParent, this.spline, this.id, this.duration );
            this.animationParent.add( this.target );
            this.animationParent.add( this.object );

        }

        if ( this.createDebugPath ) {

            createPath( this.debugPath, this.spline );

        }

        this.domElement.addEventListener( 'mousemove', bind( this, this.onMouseMove ), false );

    };

    this.handleResize();

};

THREE.PathControlsIdCounter = 0;

 

posted @ 2021-06-19 10:24  木的树  阅读(355)  评论(0编辑  收藏  举报