原来叫做系统仿真现在都叫做数字孪生了,我也凑个热闹,这个是一个仓库提货的场景仿真。延续上一个项目《blender+Three.js 三维数据可视化23,有兴趣的可以去看。

环境:blender-2.93.6-windows-x64+Three.js 0.91.0+Echart2.0

这里是上一个场景的仓库部分,出货分为发货(直接从厂里发到客户,走内部流程)和自提(客户自己来取货),这里是自提部分,主要是这个自动装货机是我们项目的,所以借此机会客户加了一个这个需求。

废话说完,我们开始,首先分析需求和流程:

1、客户每天需要装车的订单是一个全集,这个全集会变化,我们5分钟更新一次;

2、拿到装货单排队等待的是一个子集,他们会入场等待;

3、有4个装卸口,4个自动装货机,每次装卸操作会记录装货的条码,可以通过条码得到品类、重量什么的信息;

需要模拟场景,拿到装货单的订单入场等待(模拟成一辆汽车,实际可能不止),在装卸口排队的或者装车的,模拟成装卸(车停在装卸口并且机械臂运动),装卸完成的,走人。

用blender创建仓库模型,用THREE.js加载进来。

function init() {

    var container = document.getElementById("three");

    camera = new THREE.PerspectiveCamera( 45, sceneWidth / sceneHeight, 1, 4000 );
    //camera.position.set(900, 400, 900);
    camera.position.set(1000, 500, 0);

    scene = new THREE.Scene();
    
    scene.background = new THREE.Color('#27313A');
    scene.fog = new THREE.Fog('#27313A', 1500, 2500 );
    
    var ambientLight=new THREE.AmbientLight('#FFFFFF');
    scene.add(ambientLight);
    
    var hemiLight = new THREE.HemisphereLight(0xFFFFFF,0x444444);
    hemiLight.position.set(100, 200, 100);
    scene.add(hemiLight);

    var dirLight = new THREE.DirectionalLight('#666666');
    dirLight.position.set( 200, 500, 200);
    dirLight.intensity=1;
    dirLight.castShadow = true;
    dirLight.shadow.camera.top = 1000;
    dirLight.shadow.camera.bottom = - 1000;
    dirLight.shadow.camera.left = - 1000;
    dirLight.shadow.camera.right = 1000;    
    dirLight.shadow.camera.far=2000;
    dirLight.shadow.mapSize.width = 2048;
    dirLight.shadow.mapSize.height = 2048;
    scene.add( dirLight );
    
    //scene.add(new THREE.CameraHelper( dirLight.shadow.camera ) );

    //地板    
    var mesh = new THREE.Mesh( new THREE.PlaneGeometry( 8000, 8000 ), new THREE.MeshBasicMaterial( {color: '#394855', depthWrite:false} ) );
    mesh.rotation.set(- Math.PI / 2,0,0);
    mesh.receiveShadow = true;
    scene.add( mesh );

    var grid = new THREE.GridHelper(8000, 200, '#2F3C46','#425462');
    grid.material.opacity = 0.5;
    grid.material.transparent = true;
    scene.add(grid);
    
    for (var val in whData) {
        whData[val].percent=Math.round(Math.random()*10000)/100;
        whData[val].goods=goodsArr[Math.round(Math.random()*100)%3];        
    }
    
    var material = new THREE.MeshPhongMaterial( {color:'#2F3C46'});
    var materialGround=new THREE.MeshStandardMaterial({color:'#2F3C46'});
    var materialMask = new THREE.MeshBasicMaterial({color: '#0099FF',opacity:0.1,transparent:true});
    var materialTruck = new THREE.MeshPhongMaterial({color: '#55711C'});
    var materialWorkArea = new THREE.MeshPhongMaterial({color: '#663300',opacity:0.5,transparent:true});
    var materialPark = new THREE.MeshPhongMaterial({color: '#2F3C46'});
    var materialWall = new THREE.MeshPhongMaterial({color: '#2F3C46',opacity:0.2,transparent:true,wireframe:false});
    var materialWallLine = new THREE.LineBasicMaterial({color: '#0099FF',opacity:0.5,transparent:true}) //仓库线框材质
    var materialEmpty=new THREE.MeshPhongMaterial({color:'#2F3C46'});    //仓库空位
    var materialCo = new THREE.MeshPhongMaterial({color:'#0099FF'});    //尿素                                                        
    var materialPo4 = new THREE.MeshPhongMaterial({color:'#99FF00'}); //磷肥
    var materialNh4 = new THREE.MeshPhongMaterial({color:'#FF0099'});    //二胺
    var infoMaterial=new THREE.LineBasicMaterial({color:'#0099FF',opacity:0.3,transparent:true});
    var alphaMap = new THREE.TextureLoader().load('images/alpha.png');
    
    var loader = new THREE.FBXLoader();
    loader.load('/object/warehouse.fbx', function ( object ) {
        object.traverse( function ( child ) {            
            if ( child.isMesh ) {                                    
                if(child.name=="ground") //地板
                {
                    child.material=materialGround;
                    child.castShadow = true;
                    child.receiveShadow = true;                                    
                }
                else if(child.name.indexOf("newOrder")==0)
                {                    
                    child.material =materialTruck;
                    child.castShadow = true;
                    child.receiveShadow = true;
                    newOrderTruck=child;
                }
                else if(child.name.indexOf("park")==0)
                {
                    var parkObject=new THREE.Group();
                    parkObject.position.set(child.position.x,child.position.y,child.position.z);
                    
                    var edges = new THREE.EdgesGeometry(child.geometry,1);                    
                    var lines =  new THREE.LineSegments(edges,materialWallLine);
                    edges.scale(child.scale.x,child.scale.y,child.scale.z*15);
                    lines.rotation.set(- Math.PI / 2,0,0);
                    lines.position.set(0,65,0);
                    parkObject.add(lines);
                                                            
                    child.material =materialPark;
                    child.castShadow = true;
                    child.receiveShadow = true;
                    child.position.set(0,0,0);
                    child.parent=null;
                    parkObject.add(child);                                                                                                    
                    
                    parks.push({id:Number(child.name.replace("park","")),object:parkObject,used:false});
                    scene.add(parkObject);                                                                                
                }
                else if(child.name.indexOf("point")==0)
                {
                    child.material =new THREE.MeshPhongMaterial( {color:'#666666'});
                    points[child.name]=child;
                }
                else if(child.name.indexOf("LotGround")==0) //停车场,用于显示订单信息
                {                    
                    child.material = materialPark;
                    child.material.opacity=0.2;
                    child.material.transparent=true;
                    child.castShadow = true;
                    child.receiveShadow = true;
                }
                else if(child.name.indexOf("Store")==0) //主仓库
                {                    
                    //加一个占位的线框        
                    var edges = new THREE.EdgesGeometry(child.geometry,1);                    
                    var lines =  new THREE.LineSegments(edges,materialWallLine);
                    edges.scale(child.scale.x,child.scale.y,child.scale.z);
                    lines.rotation.set(- Math.PI / 2,0,0);
                    lines.position.set(child.position.x,child.position.y,child.position.z);
                    scene.add(lines);                    
                    
                    child.material =materialWall;
                    child.castShadow = true;
                    child.receiveShadow = true;
                }
                else if(child.name.indexOf("W")==0)        
                {
                    //加一个占位的线框        
                    var edges = new THREE.EdgesGeometry(child.geometry,1);
                    var lines =  new THREE.LineSegments(edges,materialWallLine);
                    edges.scale(child.scale.x,child.scale.y,child.scale.z);
                    lines.rotation.set(- Math.PI / 2,0,0);
                    lines.position.set(child.position.x,child.position.y,child.position.z);
                    scene.add(lines);
                    
                    child.material =materialWall;
                    child.castShadow = true;
                    child.receiveShadow = true;
                }
                else if(child.name.indexOf("workArea")==0)
                {
                    child.material = materialWorkArea;
                    workAreaArr.push(child);
                    child.castShadow = true;
                    child.receiveShadow = true;
                }                
                else if(child.name.indexOf("HAND")==0)
                {
                    child.material =material;
                    child.position.x=-Math.random()*40-180;
                    handArr.push({object:child,isBusy:false,animate:false,step:0.3});
                }
                else if(child.name.indexOf("Shelf")==0) //货架
                {
                    //加一个占位的线框        
                    var edges = new THREE.EdgesGeometry(child.geometry,1);                                            
                    var edgesMaterial = new THREE.LineBasicMaterial({color: 0xffffff})
                    var lines =  new THREE.LineSegments(edges,edgesMaterial);
                    edges.scale(child.scale.x+2,child.scale.y+2,child.scale.z+2);
                    lines.position.set(child.position.x-1,child.position.y+10,child.position.z-12);
                    scene.add(lines);                    
                    
                    //处理容积
                    var childRealName=child.name.replace("Model",""); //IE11下莫名其妙的多了一个这个                    
                    
                    if(whData[childRealName]!=undefined && whData[childRealName]!=null)
                    {                        
                        var goods=whData[childRealName].goods;
                        if(goods!=null && goods!="")
                        {
                            if(goods.indexOf("CO")==0) //尿素
                            {
                                child.material=materialCo;
                                lines.material.color.set(materialCo.color);
                            }
                            else if(goods.indexOf("PO4")==0) //磷肥
                            {
                                child.material=materialPo4;
                                lines.material.color.set(materialPo4.color);
                            }
                            else if(goods.indexOf("NH4")==0) //二胺
                            {
                                child.material=materialNh4;
                                lines.material.color.set(materialNh4.color);
                            }
                        }
                        else
                        {
                            child.material = materialEmpty;
                            child.scale.set(child.scale.x,child.scale.y,2);
                        }
                        
                        //按比例高度
                        if(whData[child.name].percent!=null && whData[child.name].percent!="")
                        {
                            child.scale.set(child.scale.x,child.scale.y,whData[child.name].percent);
                        }
                        else
                        {
                            child.material = materialEmpty;
                            child.scale.set(child.scale.x,child.scale.y,2);
                        }
                    }
                    else
                    {
                        child.material = materialEmpty;
                        child.scale.set(child.scale.x,child.scale.y,2);
                    }                                        
                }
                else
                {
                    child.material = material;                            
                    child.castShadow = true;
                    child.receiveShadow = true;
                }
            }            
        } );        
        
        scene.add( object );
        
        //匹配一下机械手和装卸区
        for(var i=0;i<handArr.length;i++)
        {
            var handID=Number(handArr[i].object.name.replace("HAND",""));
            for(var j=0;j<workAreaArr.length;j++)
            {
                var workAreaID=Number(workAreaArr[j].name.replace("workArea",""));
                if(workAreaID==handID)    
                {
                    handArr[i].workArea=workAreaArr[j];
                    break;
                }
            }
        }
        
        //初始化系统数据
        initSystemData();
        
        //开始定时任务,刷新场景
        //interAnimate=setInterval("animate()",1000/30);        
        
        //开始定时任务,刷新数据
        interRefreshData=setInterval("refreshData()",5000);        
    } );                    

    renderer = new THREE.WebGLRenderer( { antialias: true,alpha:true } );
    renderer.setSize(sceneWidth, sceneHeight);
    renderer.autoClear=true;
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    container.appendChild(renderer.domElement);

    var controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.target.set( 0, 0, 0 );
    controls.update();

    window.addEventListener( 'resize', onWindowResize );    
    
    //animate_back();
}

 主要是把停车位、机械臂和装卸口用数组保存起来,数组定义如下:

var orderObjects=[]; //订单信息
var parks=[]; //车位信息
var currentParkOrder=null; //当前进行停车场动画的订单
var handArr=[]; //机械臂
var workAreaArr=[]; //装卸口信息
var currentLoadOrder=null; //当前进行装卸口停车的订单
var currentOverOrders=[]; //出库的订单列表

然后是处理停车、进入装卸口和离场的动画部分:

/取得一个新的订单对象
function getOrderObject(id)
{
	var orderObject={};
	var object=new THREE.Group();
	var truck=newOrderTruck.clone();
	truck.position.set(0,0,0);
	orderObject.truck=truck;
	object.add(truck);
	
	var tip=getTip(id);					
	tip.position.set(0,30,0);
	object.add(tip);
	
	orderObject.object=object;	
	orderObject.id=id;
	orderObject.state="wait";
	return orderObject;	
}
//************************************
//************************************
//        物体运动动画处理过程
//************************************
//************************************
function animateOrder()
{	
	//----------------------处理停车场动画--------------------------
	if(currentParkOrder!=null)
	{		
		if(currentParkOrder.progress<1)
		{
			currentParkOrder.progress += 0.008;
			var point = currentParkOrder.path.getPoint(currentParkOrder.progress);			
			if(currentParkOrder.object.position.z>point.z)
			{
				if(currentParkOrder.object.rotation.y>- Math.PI / 2)
				{
					currentParkOrder.object.rotation.set(0,currentParkOrder.object.rotation.y-0.08,0);
				}
			}
			else
			{
				if(currentParkOrder.object.rotation.y<0)
				{
					currentParkOrder.object.rotation.set(0,currentParkOrder.object.rotation.y+0.08,0);
				}
			}	
			currentParkOrder.object.position.set(point.x,point.y,point.z);
		}
		else
		{			
			currentParkOrder.pointIndex++;
			if(currentParkOrder.pointIndex>=currentParkOrder.points.length)
			{
				currentParkOrder.state="parked";
				currentParkOrder=null;
			}
			else
			{				
				var curvePath=[];
				var point=currentParkOrder.points[currentParkOrder.pointIndex-1];				
				curvePath.push(new THREE.Vector3(point.x,point.y,point.z));	
				point=currentParkOrder.points[currentParkOrder.pointIndex];
				curvePath.push(new THREE.Vector3(point.x,point.y,point.z));				
				var curve = new THREE.CatmullRomCurve3(curvePath);
				currentParkOrder.path=curve;
				currentParkOrder.progress=0;
			}
		}
	}
	//如果停车场不忙的话
	else
	{
		//处理新的动画
		for(var i=0;i<parks.length;i++)
		{
			//如果停车场有空位
			if(!parks[i].used)
			{
				//看看是否有等候的车辆,有的话就初始化一下它
				for(var j=0;j<orderObjects.length;j++)
				{
					if(orderObjects[j].state=="wait")
					{			
						parks[i].used=true;
						orderObjects[j].state="parking";						
						scene.add(orderObjects[j].object);
						initOrderParkAnimate(orderObjects[j],parks[i]);
						i=parks.length+1; //强制退出循环
						break;	
					}
				}
			}
		}
	}
	//----------------------停车场动画处理完毕--------------------------
	
	//----------------------处理装卸场动画--------------------------
	if(currentLoadOrder!=null)
	{
		if(currentLoadOrder.progress<1)
		{
			currentLoadOrder.progress += 0.008;
			var point = currentLoadOrder.path.getPoint(currentLoadOrder.progress);
			
			if(currentLoadOrder.pointIndex==2 && currentLoadOrder.object.rotation.y>- Math.PI / 2)
			{				
				currentLoadOrder.object.rotation.set(0,currentLoadOrder.object.rotation.y-0.02,0);				
			}
			else if(currentLoadOrder.pointIndex==4 && currentLoadOrder.object.rotation.y<0)
			{				
				currentLoadOrder.object.rotation.set(0,currentLoadOrder.object.rotation.y+0.08,0);			
			}
			else if(currentLoadOrder.pointIndex==5 && currentLoadOrder.object.rotation.y<Math.PI / 2)
			{				
				currentLoadOrder.object.rotation.set(0,currentLoadOrder.object.rotation.y+0.08,0);			
			}
			else if(currentLoadOrder.pointIndex==6 && currentLoadOrder.object.rotation.y<Math.PI)
			{				
				currentLoadOrder.object.rotation.set(0,currentLoadOrder.object.rotation.y+0.02,0);			
			}			
			currentLoadOrder.object.position.set(point.x,point.y,point.z);
		}
		else
		{			
			currentLoadOrder.pointIndex++;
			if(currentLoadOrder.pointIndex>=currentLoadOrder.points.length)
			{
				//计算装卸量
				currentLoadOrder.hand.quantity=currentLoadOrder.co+currentLoadOrder.po4+currentLoadOrder.nh4;
				currentLoadOrder.hand.over=0;
				currentLoadOrder.hand.animate=true;				
				currentLoadOrder.state="loading";
				currentLoadOrder=null;
			}
			else
			{				
				var curvePath=[];
				var point=currentLoadOrder.points[currentLoadOrder.pointIndex-1];				
				curvePath.push(new THREE.Vector3(point.x,point.y,point.z));	
				point=currentLoadOrder.points[currentLoadOrder.pointIndex];
				curvePath.push(new THREE.Vector3(point.x,point.y,point.z));				
				var curve = new THREE.CatmullRomCurve3(curvePath);
				currentLoadOrder.path=curve;
				currentLoadOrder.progress=0;
			}
		}
	}
	else
	{
		for(var i=0;i<handArr.length;i++)
		{
			//如果装卸臂不忙的话
			if(!handArr[i].isBusy)
			{
				//看看是否有等候的车辆,有的话就初始化一下它
				for(var j=0;j<orderObjects.length;j++)
				{
					if(orderObjects[j].state=="parked")
					{			
						handArr[i].isBusy=true;						
						orderObjects[j].park.used=false;
						initOrderLoadAnimate(orderObjects[j],handArr[i]);
						i=handArr.length+1; //强制退出循环
						break;	
					}
				}
			}
		}
	}
	//----------------------装卸场动画处理完毕--------------------------
	
	
	//----------------------处理离场动画,是个排队数组,每次发车一个,防止撞车--------------------------
	if(currentOverOrders.length>0)
	{
		var currentOverOrder=currentOverOrders[0];
		if(currentOverOrder.progress<1)
		{
			currentOverOrder.progress += 0.008;
			var point = currentOverOrder.path.getPoint(currentOverOrder.progress);
			if(currentOverOrder.pointIndex==2 && currentOverOrder.object.rotation.y>Math.PI/2)
			{				
				currentOverOrder.object.rotation.set(0,currentOverOrder.object.rotation.y-0.08,0);			
			}
			if(currentOverOrder.pointIndex==3 && currentOverOrder.object.rotation.y<Math.PI)
			{				
				currentOverOrder.object.rotation.set(0,currentOverOrder.object.rotation.y+0.08,0);			
			}
			currentOverOrder.object.position.set(point.x,point.y,point.z);
		}
		else
		{
			currentOverOrder.pointIndex++;
			if(currentOverOrder.pointIndex>=currentOverOrder.points.length)
			{				
				//删除数组元素
				currentOverOrders.splice(0,1);
				for(var i=0;i<orderObjects.length;i++)
				{
					if(orderObjects[i].id==currentOverOrder.id)
					{
						orderObjects.splice(i,1);
						break;
					}
				}
				
				//释放动画对象
				currentOverOrder.object.traverse(function(obj) {
					if (obj.type == 'Mesh') {
						obj.geometry.dispose();
						obj.material.dispose();						
					}
					
					if (obj.type == 'Sprite') {						
						obj.material.dispose();						
					}
				})
				// 删除场景对象scene的子对象group
				scene.remove(currentOverOrder.object);
				currentOverOrder=null;
			}
			else
			{				
				var curvePath=[];
				var point=currentOverOrder.points[currentOverOrder.pointIndex-1];				
				curvePath.push(new THREE.Vector3(point.x,point.y,point.z));	
				point=currentOverOrder.points[currentOverOrder.pointIndex];
				curvePath.push(new THREE.Vector3(point.x,point.y,point.z));				
				var curve = new THREE.CatmullRomCurve3(curvePath);
				currentOverOrder.path=curve;
				currentOverOrder.progress=0;
			}
		}		
	}
	//----------------------离场动画处理完毕--------------------------
	
	//----------------------处理机械臂动画--------------------------
	for(var i=0;i<handArr.length;i++)
	{		
		if(handArr[i].animate)
		{
			if(handArr[i].object.position.x<-215) 
			{
				handArr[i].step=0.3;			
			}
			else if(handArr[i].object.position.x>-180)
			{								
				handArr[i].step=-0.3;
				
				//减去装货量2吨
				handArr[i].over+=2;
				if(handArr[i].over>handArr[i].quantity) handArr[i].over=handArr[i].quantity;
				
				//更新显示
				var id=handArr[i].object.name.replace("HAND","");
				$('#dockOrder'+id).html("订单【"+handArr[i].orderObject.id+"】");
				$('#dockInfo'+id).html(handArr[i].over+"/"+handArr[i].quantity);
												
				var dockChart=echarts.getInstanceByDom($("#dockChart"+id)[0]);	
											
				//装完了,就走
				if(handArr[i].quantity<=handArr[i].over)
				{					
					//开始发车
					handArr[i].orderObject.state="loaded";
					initOrderOverAnimate(handArr[i].orderObject,handArr[i]);
					dockChart_option.series[0].data[0].value=0;
					finished+=handArr[i].quantity;
					$('#finished').html(finished);
					var finishedPercent=(finished>0)?Math.round(finished/Planned*1000)/10:0;
					$('#finishedPercent').html(finishedPercent+"%");
				}
				else
				{
					dockChart_option.series[0].data[0].value=Math.round(handArr[i].over/handArr[i].quantity*100);
				}
				dockChart.setOption(dockChart_option,true);
			}
			handArr[i].object.position.x+=handArr[i].step;
		}
	}
	//----------------------机械臂动画处理完毕--------------------------
}

//初始化离场动画
function initOrderOverAnimate(orderObject,handObject)
{
	var workArea=handObject.workArea;
	var pathPoints=[];
	
	//起点
	pathPoints.push({x:orderObject.object.position.x,y:orderObject.object.position.y,z:orderObject.object.position.z});	
	
	//出库
	pathPoints.push({x:points.pointPark5.position.x,y:points.pointPark5.position.y,z:workArea.position.z+10});
	
	//转弯
	pathPoints.push({x:points.pointPark6.position.x,y:points.pointPark6.position.y,z:points.pointPark6.position.z});
	
	//走人
	pathPoints.push({x:points.pointEnd.position.x,y:points.pointEnd.position.y,z:points.pointEnd.position.z});	
	
	orderObject.points=pathPoints;
	orderObject.pointIndex=0;
	orderObject.progress=1;
	
	handObject.animate=false;
	handObject.isBusy=false;
	
	currentOverOrders.push(orderObject);
}

//初始化装卸场动画
function initOrderLoadAnimate(orderObject,handObject)
{
	var workArea=handObject.workArea;
	var pathPoints=[];	
	
	//起点
	pathPoints.push({x:orderObject.object.position.x,y:orderObject.object.position.y,z:orderObject.object.position.z});	
	
	//出库和第一个拐弯
	if(orderObject.rowIndex==1)
	{
		//出库
		pathPoints.push({x:points.pointPark4.position.x,y:orderObject.object.position.y,z:orderObject.object.position.z});
		pathPoints.push({x:points.pointPark4.position.x,y:orderObject.object.position.y,z:orderObject.object.position.z+10});
		pathPoints.push({x:points.pointPark4.position.x,y:points.pointPark4.position.y,z:points.pointPark4.position.z});
	}
	else
	{
		pathPoints.push({x:points.pointPark3.position.x,y:orderObject.object.position.y,z:orderObject.object.position.z});
		pathPoints.push({x:points.pointPark3.position.x,y:orderObject.object.position.y,z:orderObject.object.position.z-20});
		pathPoints.push({x:points.pointPark3.position.x,y:points.pointPark3.position.y,z:points.pointPark3.position.z});
	}
	
	//第二个拐弯
	pathPoints.push({x:points.pointPark5.position.x,y:points.pointPark5.position.y,z:points.pointPark5.position.z});
	
	//倒车点
	pathPoints.push({x:points.pointPark5.position.x,y:points.pointPark5.position.y,z:workArea.position.z+10});
	
	//倒车和入库	
	pathPoints.push({x:workArea.position.x,y:workArea.position.y,z:workArea.position.z});
	
	orderObject.points=pathPoints;
	orderObject.pointIndex=0;
	orderObject.progress=1;	
	orderObject.hand=handObject;
	handObject.orderObject=orderObject;
	
	currentLoadOrder=orderObject;
}

//初始化停车动画
function initOrderParkAnimate(orderObject,park)
{
	var pathPoints=[];
			
	//起点
	pathPoints.push({x:points.pointStart.position.x,y:points.pointStart.position.y,z:points.pointStart.position.z});
	
	//转折
	if(park.id<=6)
	{
		pathPoints.push({x:points.pointPark2.position.x,y:points.pointPark2.position.y,z:points.pointPark2.position.z});
		
		//终点前一点
		pathPoints.push({x:points.pointPark2.position.x,y:points.pointPark2.position.y,z:park.object.position.z});
		
		orderObject.rowIndex=1;
	}
	else
	{
		pathPoints.push({x:points.pointPark1.position.x,y:points.pointPark1.position.y,z:points.pointPark1.position.z});
		
		//终点前一点
		pathPoints.push({x:points.pointPark1.position.x,y:points.pointPark1.position.y,z:park.object.position.z});	
		
		orderObject.rowIndex=2;	
	}
	
	//终点
	pathPoints.push({x:park.object.position.x,y:park.object.position.y,z:park.object.position.z});
	
	orderObject.points=pathPoints;			
	orderObject.object.position.set(points.pointStart.position.x, points.pointStart.position.y, points.pointStart.position.z);
	orderObject.pointIndex=0;
	orderObject.progress=1;
	orderObject.park=park;
	
	//开始动画
	currentParkOrder=orderObject;
}

在桢刷新时候调用(或者写一个定时器,这个比较稳定)

function animate_Frame() 
{

animateOrder();

renderer.render(scene, camera);
    
    requestAnimationFrame(animate_Frame);

}

主要的思路就是,在场景中放置标志点,然后将标志点连接成路径,均分为多少份,每次移动一个标志点的位置。

var curvePath=[];
				var point=currentLoadOrder.points[currentLoadOrder.pointIndex-1];				
				curvePath.push(new THREE.Vector3(point.x,point.y,point.z));	
				point=currentLoadOrder.points[currentLoadOrder.pointIndex];
				curvePath.push(new THREE.Vector3(point.x,point.y,point.z));				
				var curve = new THREE.CatmullRomCurve3(curvePath);
				currentLoadOrder.path=curve;
				currentLoadOrder.progress=0;

取两个点生成一个路径。

currentLoadOrder.progress += 0.008;
            var point = currentLoadOrder.path.getPoint(currentLoadOrder.progress);

...

currentLoadOrder.object.position.set(point.x,point.y,point.z);

根据点密度,调整这个progress,实现快或者慢的效果。

掉头主要是根据点的位置来计算,例如:

if(currentLoadOrder.pointIndex==2 && currentLoadOrder.object.rotation.y>- Math.PI / 2)
            {                
                currentLoadOrder.object.rotation.set(0,currentLoadOrder.object.rotation.y-0.02,0);                
            }

实现左转。

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐