feat: 增加点击建筑 demo

This commit is contained in:
shihui 2021-10-25 18:09:08 +08:00
parent b86055f322
commit 64bfbb4592
4 changed files with 433 additions and 0 deletions

View File

@ -136,6 +136,10 @@ L7 将 threejs 的引用封装成一个特殊的图层对象,在使用上与
用户通过该方法管理加载模型的动画
### getRenderCamera(): THREE.Camera
返回根据当前地图场景参数下对应的 THREEJS 相机
## 加载模型
用户可以使用 threejs 提供的能力加载其支持的任意模型

View File

@ -18,6 +18,11 @@
"filename": "mapbox_ant.js",
"title": "mapbox 地图",
"screenshot":"https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*kflKRJvm3hYAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "space_click.js",
"title": "点击建筑",
"screenshot":"https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*AY5vSIMeLy8AAAAAAAAAAAAAARQnAQ"
}
]
}

View File

@ -0,0 +1,423 @@
import { Scene } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
import { ThreeLayer, ThreeRender } from '@antv/l7-three';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader';
import { animate, easeInOut } from 'popmotion';
function changeValue(
startValue,
endValue,
duration = 500,
callback,
complete
) {
if (typeof startValue === 'number') {
animate({
from: {
v: startValue
},
to: {
v: endValue
},
ease: easeInOut,
duration,
onUpdate: o => {
callback(o.v);
return '';
},
onComplete: () => {
complete && complete();
return '';
}
});
} else {
animate({
from: {
lng: startValue.lng,
lat: startValue.lat,
pitch: startValue.pitch,
rotation: startValue.rotation,
zoom: startValue.zoom
},
to: {
lng: (endValue).lng,
lat: (endValue).lat,
pitch: (endValue).pitch,
rotation: (endValue).rotation,
zoom: (endValue).zoom
},
ease: easeInOut,
duration,
onUpdate: o => {
callback(o);
return '';
},
onComplete: () => {
complete && complete();
return '';
}
});
}
return '';
}
const raycaster = new THREE.Raycaster();
const lng = 120.1;
const lat = 30.265;
const scene = new Scene({
id: 'map',
map: new GaodeMap({
center: [ lng, lat ],
pitch: 70,
rotation: 220,
zoom: 16
})
});
scene.on('loaded', () => {
const mouse = new THREE.Vector2();
let zspace,
aspace,
ASpaceTextMesh,
ZSpaceTextMesh;
scene.registerRenderService(ThreeRender);
const center = scene.getCenter();
const threeJSLayer = new ThreeLayer({
enableMultiPassRenderer: false,
// @ts-ignore
onAddMeshes: (threeScene, layer) => {
threeScene.add(new THREE.AmbientLight(0xffffff));
const sunlight = new THREE.DirectionalLight(0xffffff, 0.25);
sunlight.position.set(0, 80000000, 100000000);
sunlight.matrixWorldNeedsUpdate = true;
threeScene.add(sunlight);
// map
// https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*gA0NRbuOF5cAAAAAAAAAAAAAARQnAQ
// height
// https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*eYFaRYlnnOUAAAAAAAAAAAAAARQnAQ
const image = new Image();
image.crossOrigin = '';
image.src =
'https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*eYFaRYlnnOUAAAAAAAAAAAAAARQnAQ';
image.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0, image.width, image.height);
const heightData = ctx.getImageData(0, 0, image.width, image.height)
.data;
const s = 53000;
const geometry = new THREE.PlaneGeometry(s, s, 255, 255);
geometry.vertices.map((v, i) => {
const r = heightData[i * 4];
const g = heightData[i * 4 + 1];
const b = heightData[i * 4 + 2];
let h =
-10000.0 +
(r * 255.0 * 256.0 * 256.0 + g * 255.0 * 256.0 + b * 255.0) *
0.1;
h = h / 20 - 127600;
h = Math.max(0, h);
v.z = h;
return '';
});
const material = new THREE.MeshPhongMaterial({
transparent: true,
// opacity: 0.6,
map: new THREE.TextureLoader().load(
'https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*gA0NRbuOF5cAAAAAAAAAAAAAARQnAQ'
)
});
const plane = new THREE.Mesh(geometry, material);
layer.setObjectLngLat(plane, [ 120.1008, 30.2573 ], 0);
plane.position.z = 10;
threeScene.add(plane);
return '';
};
// 使用 Three.js glTFLoader 加载模型
const loader = new GLTFLoader();
loader.load(
'https://gw.alipayobjects.com/os/bmw-prod/3ca0a546-92d8-4ba0-a89c-017c218d5bea.gltf',
gltf => {
const antModel = gltf.scene;
layer.adjustMeshToMap(antModel);
layer.setMeshScale(antModel, 20, 20, 20);
layer.setObjectLngLat(
antModel,
[ center.lng - 0.002, center.lat ],
0
);
const animations = gltf.animations;
if (animations && animations.length) {
const mixer = new THREE.AnimationMixer(antModel);
const animation = animations[1];
const action = mixer.clipAction(animation);
action.play();
layer.addAnimateMixer(mixer);
}
antModel.rotation.y = Math.PI;
// 向场景中添加模型
threeScene.add(antModel);
// 重绘图层
layer.render();
return '';
}
);
const v = `
varying vec2 vUv;
varying vec4 worldPosition;
void main() {
vUv = uv;
worldPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}`;
const f = `
varying vec2 vUv;
varying vec4 worldPosition;
uniform vec3 color;
void main() {
gl_FragColor = vec4(color, fract(worldPosition.z / 50.0));
}`;
const shadermaterial = new THREE.ShaderMaterial({
uniforms: {
color: {
value: new THREE.Vector3(0.21372549, 0.34705882, 0.56470588)
}
},
vertexShader: v,
fragmentShader: f
});
const fbxLoaded = new FBXLoader();
// load ZSpace
fbxLoaded.load(
'https://gw.alipayobjects.com/os/bmw-prod/af1652c9-3c4f-4e73-ac4c-1f78fefbaf6a.fbx',
gltf => {
zspace = gltf;
layer.adjustMeshToMap(zspace);
// @ts-ignore
zspace.children[0].material = shadermaterial;
layer.setMeshScale(zspace, 10, 10, 10);
layer.setObjectLngLat(zspace, [ 120.1015, 30.2661 ], 0);
zspace.rotation.x = Math.PI * 2;
zspace.rotation.z = -Math.PI * (-2 / 15);
threeScene.add(zspace);
return '';
}
);
fbxLoaded.load(
'https://gw.alipayobjects.com/os/bmw-prod/11d6e4c1-bd5b-4dc1-bae5-ac51c14e9056.fbx',
model => {
aspace = model;
layer.adjustMeshToMap(aspace);
// @ts-ignore
aspace.children[0].material = shadermaterial;
layer.setMeshScale(aspace, 8, 8, 8);
layer.setObjectLngLat(aspace, [ 120.099, 30.261 ], 0);
aspace.rotation.x = Math.PI * 2;
aspace.rotation.z = -Math.PI * (3 / 15);
threeScene.add(aspace);
return '';
}
);
const textLoader = new THREE.FontLoader();
textLoader.load(
'https://gw.alipayobjects.com/os/bmw-prod/0a3f46eb-294e-4d95-87f2-052c26ad4bf1.json',
font => {
const fontOptions = {
size: 360, // 字号大小,一般为大写字母的高度
height: 50, // 文字的厚度
font, // 字体,默认是'helvetiker',需对应引用的字体文件
bevelThickness: 10, // 倒角厚度
bevelSize: 10, // 倒角宽度
curveSegments: 30, // 弧线分段数,使得文字的曲线更加光滑
bevelEnabled: true // 布尔值,是否使用倒角,意为在边缘处斜切
};
const aspaceGeo = new THREE.TextGeometry('ASpace', fontOptions);
aspaceGeo.center();
const zspaceGeo = new THREE.TextGeometry('ZSpace', fontOptions);
zspaceGeo.center();
const fontMat = new THREE.MeshPhongMaterial({
color: 0xcccccc,
shininess: 60,
specular: 0xcccccc
});
const testHeight = 900;
ASpaceTextMesh = new THREE.Mesh(aspaceGeo, fontMat);
ASpaceTextMesh.rotation.x = Math.PI / 2;
ASpaceTextMesh.rotation.y = (-Math.PI * 3) / 4;
layer.setObjectLngLat(
ASpaceTextMesh,
[ 120.099, 30.261 ],
testHeight
);
threeScene.add(ASpaceTextMesh);
ZSpaceTextMesh = new THREE.Mesh(zspaceGeo, fontMat);
ZSpaceTextMesh.rotation.x = Math.PI / 2;
ZSpaceTextMesh.rotation.y = (-Math.PI * 3) / 4;
layer.setObjectLngLat(
ZSpaceTextMesh,
[ 120.103, 30.2661 ],
testHeight
);
threeScene.add(ZSpaceTextMesh);
getH(0, 200);
function getH(h1, h2) {
changeValue(
h1,
h2,
1000,
h => {
ASpaceTextMesh.position.z = testHeight + h;
ZSpaceTextMesh.position.z = testHeight + h;
return '';
},
() => {
setTimeout(() => getH(h2, h1), 10);
return '';
}
);
}
return '';
}
);
}
})
.source({
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [ 111.4453125, 32.84267363195431 ]
}
}
]
})
.animate(true);
scene.addLayer(threeJSLayer);
// @ts-ignore
let currentCamera = threeJSLayer.threeRenderService.getRenderCamera();
const currentView = {
lng: center.lng,
lat: center.lat,
pitch: 70,
rotation: 220,
zoom: 16
};
scene.on('zoom', () => {
const cen = scene.getCenter();
currentView.lng = cen.lng;
currentView.lat = cen.lat;
currentView.pitch = scene.getPitch();
currentView.zoom = scene.getZoom();
return '';
});
scene.getMapService().on('mapchange', () => {
// @ts-ignore
currentCamera = threeJSLayer.getRenderCamera();
currentView.pitch = scene.getPitch();
return '';
});
const ASpaceView = {
lng: 120.109509,
lat: 30.251529,
pitch: 83,
rotation: 225,
zoom: 16
};
const ZSpaceView = {
lng: 120.112026,
lat: 30.256881,
pitch: 80,
rotation: 220,
zoom: 16
};
scene.on('click', ev => {
// @ts-ignore
const size = scene?.map?.getSize();
mouse.x = (ev.pixel.x / size.width) * 2 - 1;
mouse.y = -(ev.pixel.y / size.height) * 2 + 1;
raycaster.setFromCamera(mouse, currentCamera);
const intersects = raycaster.intersectObjects([ zspace, aspace ], true);
if (intersects.length > 0) {
const object = intersects[0].object;
if (object.name === 'Z空间') {
selectSpace(currentView, ZSpaceView, ZSpaceTextMesh);
} else {
selectSpace(currentView, ASpaceView, ASpaceTextMesh);
}
}
return '';
});
function selectSpace(
currentView,
targetView,
spaceText
) {
if (spaceText) {
changeValue(
spaceText.rotation.y,
spaceText.rotation.y + Math.PI * 2,
500,
r => {
spaceText.rotation.y = r;
return '';
}
);
}
changeValue(currentView, targetView, 500, view => {
scene.setCenter([ view.lng, view.lat ]);
scene.setPitch(view.pitch);
scene.setRotation(view.rotation);
scene.setZoom(view.zoom);
currentView.lng = view.lng;
currentView.lat = view.lat;
currentView.pitch = view.pitch;
currentView.rotation = view.rotation;
currentView.zoom = view.zoom;
return '';
});
}
return '';
});

View File

@ -12,6 +12,7 @@ window.l7District = require('@antv/l7-district');
window.l7Three = require('@antv/l7-three');
window.three = require('three');
window.GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader');
window.FBXLoader = require('three/examples/jsm/loaders/FBXLoader');
window.react = require('react');
window.popmotion = require('popmotion');
window.reactDom = require('react-dom');