maptalks 开发GIS地图(9)maptalks.three.02 animation
1. 说明: 本demo主要是加载了一个具有动画效果的三维机器人,然后通过鼠标选择菜单控制机器人的动作和表情。
其实这个机器人,本身就已经具有动画和动作表情效果了,只不过是使用threejs的接口,把它加载到地图上,
然后再调用API操控机器人。
2. 先来看一下 ../threelayer/demo/data/RobotExpressive.glb 这个文件。通过Windows10下面自带的 3D查看器软件打开。
这是一个glb文件,通过3dmax软件导出后,已经具有了动画效果。而且还包括了多种动画效果。
3. 新建一个 maptalks 地图
关于maptalks 地图的功能,这里就不多讲了,可以参考前面的文章。
1 var map = new maptalks.Map("map", { 2 center: [19.06325670775459, 42.16842479475318], 3 zoom: 9, 4 pitch: 60, 5 // bearing: 180, 6 7 centerCross: true, 8 doubleClickZoom: false, 9 baseLayer: new maptalks.TileLayer('tile', { 10 urlTemplate: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', 11 subdomains: ['a', 'b', 'c', 'd'], 12 attribution: '© <a href="http://osm.org">OpenStreetMap</a> contributors, © <a href="https://carto.com/">CARTO</a>' 13 }) 14 });
4. 新建一个 ThreeLayer 图层,并设置三维三要素,光源、场景、镜头。
1 // the ThreeLayer to draw buildings 2 var threeLayer = new maptalks.ThreeLayer('t', { 3 forceRenderOnMoving: true, 4 forceRenderOnRotating: true 5 // animation: true 6 }); 7 threeLayer.prepareToDraw = function (gl, scene, camera) { 8 var light = new THREE.DirectionalLight(0xffffff); 9 light.position.set(0, -10, 10).normalize(); 10 scene.add(light); 11 camera.add(new THREE.PointLight('#fff', 4)); 12 13 addGltf(); 14 15 }; 16 threeLayer.addTo(map);
5. 添加三维模型对象
这里使用了 THREE.GLTFLoader 对象,此函数包含在 GLTFLoader.js 中,需要调用前添加引用。
其中 model 对象是获取了三维模型中的场景,然后将三维模型进行旋转45° model.rotation , 模型比例设为 1:1 , scale.set , 模型的位置设在地图中心点。
使用 addMesh 添加三维模型到对应的threeLayer 图层,完成了三维模型对象在地图上的添加。
1 function addGltf() { 2 clock = new THREE.Clock(); 3 stats = new Stats(); 4 map.getContainer().appendChild(stats.dom); 5 var loader = new THREE.GLTFLoader(); 6 loader.load('./data/RobotExpressive.glb', function (gltf) { 7 8 model = gltf.scene; 9 model.rotation.x = Math.PI / 2; 10 model.scale.set(100, 100, 100); 11 model.position.copy(threeLayer.coordinateToVector3(map.getCenter())); 12 threeLayer.addMesh(model); 13 14 createGUI(model, gltf.animations); 15 animate(); 16 17 }, undefined, function (e) { 18 19 console.error(e); 20 21 }); 22 }
6. 创建GUI用以控制机器人
这就是前面那篇文章说的使用了 dat.gui.min.js 中相关的内容。
1 function createGUI(model, animations) { 2 3 var states = ['Idle', 'Walking', 'Running', 'Dance', 'Death', 'Sitting', 'Standing']; 4 var emotes = ['Jump', 'Yes', 'No', 'Wave', 'Punch', 'ThumbsUp']; 5 6 gui = new dat.GUI(); 7 8 mixer = new THREE.AnimationMixer(model); 9 10 actions = {}; 11 12 for (var i = 0; i < animations.length; i++) { 13 14 var clip = animations[i]; 15 var action = mixer.clipAction(clip); 16 actions[clip.name] = action; 17 18 if (emotes.indexOf(clip.name) >= 0 || states.indexOf(clip.name) >= 4) { 19 20 action.clampWhenFinished = true; 21 action.loop = THREE.LoopOnce; 22 23 } 24 25 } 26 27 // states 28 29 var statesFolder = gui.addFolder('States'); 30 31 var clipCtrl = statesFolder.add(api, 'state').options(states); 32 33 clipCtrl.onChange(function () { 34 35 fadeToAction(api.state, 0.5); 36 37 }); 38 39 statesFolder.open(); 40 41 // emotes 42 43 var emoteFolder = gui.addFolder('Emotes'); 44 45 function createEmoteCallback(name) { 46 47 api[name] = function () { 48 49 fadeToAction(name, 0.2); 50 51 mixer.addEventListener('finished', restoreState); 52 53 }; 54 55 emoteFolder.add(api, name); 56 57 } 58 59 function restoreState() { 60 61 mixer.removeEventListener('finished', restoreState); 62 63 fadeToAction(api.state, 0.2); 64 65 } 66 67 for (var i = 0; i < emotes.length; i++) { 68 69 createEmoteCallback(emotes[i]); 70 71 } 72 73 emoteFolder.open(); 74 75 // expressions 76 77 face = model.getObjectByName('Head_2'); 78 79 var expressions = Object.keys(face.morphTargetDictionary); 80 var expressionFolder = gui.addFolder('Expressions'); 81 82 for (var i = 0; i < expressions.length; i++) { 83 84 expressionFolder.add(face.morphTargetInfluences, i, 0, 1, 0.01).name(expressions[i]); 85 86 } 87 88 activeAction = actions['Walking']; 89 activeAction.play(); 90 91 expressionFolder.open(); 92 93 }
7. 重新绘制webgl的状态,满足动画更新需要。
1 function animate() { 2 var dt = clock.getDelta(); 3 if (mixer) mixer.update(dt); 4 requestAnimationFrame(animate); 5 stats.update(); 6 // threeLayer._needsUpdate = !threeLayer._needsUpdate; 7 if (threeLayer._needsUpdate) { 8 threeLayer.renderScene(); 9 } 10 11 }
8. 在 maptalks 地图中的效果。
9. 源码地址
https://github.com/WhatGIS/maptalkMap/tree/main/threelayer/demo