feat: 3D model

This commit is contained in:
thinkinggis 2020-04-17 10:47:34 +08:00
parent e42149bdb2
commit 377ae5c036
29 changed files with 1006 additions and 1316 deletions

View File

@ -183,8 +183,5 @@
"tnpm": {
"mode": "yarn"
},
"version": "0.0.0",
"dependencies": {
"@turf/turf": "^5.1.6"
}
"version": "0.0.0"
}

View File

@ -60,5 +60,7 @@ export interface IRendererService {
getGLContext(): WebGLRenderingContext;
viewport(size: { x: number; y: number; width: number; height: number }): void;
readPixels(options: IReadPixelsOptions): Uint8Array;
setBaseState(): void;
setCustomLayerDefaults(): void;
destroy(): void;
}

View File

@ -79,6 +79,8 @@ export interface ITexture2DInitializationOptions {
}
export interface ITexture2D {
get(): unknown;
update(): void;
resize(options: { width: number; height: number }): void;
/**

View File

@ -5,6 +5,7 @@ import { IRenderConfig } from '../renderer/IRendererService';
export interface ISceneService {
destroyed: boolean;
loaded: boolean;
on(type: string, handle: (...args: any[]) => void): void;
off(type: string, handle: (...args: any[]) => void): void;
removeAllListeners(event?: string): this;

View File

@ -34,6 +34,8 @@ import { ISceneService } from './ISceneService';
export default class Scene extends EventEmitter implements ISceneService {
public destroyed: boolean = false;
public loaded: boolean = false;
@inject(TYPES.SceneID)
private readonly id: string;
/**
@ -228,6 +230,7 @@ export default class Scene extends EventEmitter implements ISceneService {
this.logger.info(' render inited');
this.layerService.initLayers();
this.controlService.addControls();
this.loaded = true;
this.emit('loaded');
this.inited = true;
}

View File

@ -36,9 +36,8 @@
},
"dependencies": {
"@antv/l7": "2.1.14",
"@antv/l7-component": "2.1.14",
"@babel/runtime": "^7.7.7",
"@turf/circle": "^6.0.1",
"@turf/turf": "^5.1.6",
"@turf/distance": "^6.0.1",
"@turf/helpers": "^6.1.4",
"@turf/midpoint": "^5.1.5",

View File

@ -20,6 +20,7 @@ export default class ImageModel extends BaseModel {
public getUninforms(): IModelUniform {
const { opacity } = this.layer.getLayerConfig() as IImageLayerStyleOptions;
this.texture.update();
return {
u_opacity: opacity || 1.0,
u_texture: this.texture,

View File

@ -4,17 +4,27 @@
* @see https://github.com/peterqliu/threebox/blob/master/examples/Object3D.html
*/
import { IMercator } from '@antv/l7-core';
import { Camera, Matrix4, Scene, WebGLRenderer } from 'three';
import {
AnimationMixer,
Camera,
Matrix4,
PCFSoftShadowMap,
PerspectiveCamera,
Scene,
WebGLRenderer,
} from 'three';
import BaseLayer from '../core/BaseLayer';
const DEG2RAD = Math.PI / 180;
export default class ThreeJSLayer extends BaseLayer<{
onAddMeshes: (threeScene: Scene, layer: ThreeJSLayer) => void;
}> {
public name: string = 'ThreeJSLayer';
public type: string = 'custom';
private scene: Scene;
private camera: Camera;
private renderer: WebGLRenderer;
private animateMixer: AnimationMixer[] = [];
// 地图中点墨卡托坐标
private center: IMercator;
@ -63,12 +73,12 @@ export default class ThreeJSLayer extends BaseLayer<{
// L7 负责 clear
this.renderer.autoClear = false;
// 是否需要 gamma correction?
this.renderer.gammaOutput = true;
this.renderer.gammaFactor = 2.2;
this.renderer.shadowMap.enabled = true;
// this.renderer.shadowMap.type = PCFSoftShadowMap;
this.scene = new Scene();
// 后续同步 L7 相机
this.camera = new Camera();
this.camera = new PerspectiveCamera(45, 1, 1, 2000000);
const config = this.getLayerConfig();
if (config && config.onAddMeshes) {
@ -76,10 +86,14 @@ export default class ThreeJSLayer extends BaseLayer<{
}
}
}
public renderModels() {
const { width, height } = this.rendererService.getViewportSize();
this.renderer.setSize(width, height, false);
return this.mapService.constructor.name === 'AMapService'
? this.renderAMapModels()
: this.renderMapboxModels();
}
public renderMapboxModels() {
// const { width, height } = this.rendererService.getViewportSize();
// this.renderer.setSize(width, height, false);
const gl = this.rendererService.getGLContext();
gl.frontFace(gl.CCW);
@ -97,9 +111,57 @@ export default class ThreeJSLayer extends BaseLayer<{
);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
this.rendererService.setBaseState();
this.animateMixer.forEach((mixer: AnimationMixer) => {
mixer.update(this.getTime());
});
return this;
}
public renderAMapModels() {
const gl = this.rendererService.getGLContext();
gl.frontFace(gl.CCW);
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
// @ts-ignore
const mapCamera = this.mapService.map.getCameraState();
const camera = this.camera;
let { pitch, rotation } = mapCamera;
const { fov, near, far, height, aspect } = mapCamera;
pitch *= DEG2RAD;
rotation *= DEG2RAD;
// @ts-ignore
camera.fov = (180 * fov) / Math.PI;
// @ts-ignore
camera.aspect = aspect;
// @ts-ignore
camera.near = near;
// @ts-ignore
camera.far = far;
// @ts-ignore
camera.updateProjectionMatrix();
camera.position.z = height * Math.cos(pitch);
camera.position.x = height * Math.sin(pitch) * Math.sin(rotation);
camera.position.y = -height * Math.sin(pitch) * Math.cos(rotation);
camera.up.x = -Math.cos(pitch) * Math.sin(rotation);
camera.up.y = Math.cos(pitch) * Math.cos(rotation);
camera.up.z = Math.sin(pitch);
camera.lookAt(0, 0, 0);
camera.position.x += mapCamera.position.x;
camera.position.y += -mapCamera.position.y;
this.renderer.state.reset();
this.renderer.autoClear = false;
this.renderer.render(this.scene, this.camera);
this.animateMixer.forEach((mixer: AnimationMixer) => {
mixer.update(this.getTime());
});
return this;
}
public addAnimateMixer(mixer: AnimationMixer) {
this.animateMixer.push(mixer);
}
protected getConfigSchema() {
return {
properties: {

View File

@ -258,10 +258,29 @@ export default class AMapService
lnglat: [number, number],
altitude: number,
rotate: [number, number, number],
scale: [number, number, number],
origin: IMercator,
scale: [number, number, number] = [1, 1, 1],
origin: IMercator = { x: 0, y: 0, z: 0 },
): number[] {
return (mat4.create() as unknown) as number[];
const flat = this.viewport.projectFlat(lnglat);
// @ts-ignore
const modelMatrix = mat4.create();
mat4.translate(
modelMatrix,
modelMatrix,
vec3.fromValues(flat[0], flat[1], altitude),
);
mat4.scale(
modelMatrix,
modelMatrix,
vec3.fromValues(scale[0], scale[1], scale[2]),
);
mat4.rotateX(modelMatrix, modelMatrix, rotate[0]);
mat4.rotateY(modelMatrix, modelMatrix, rotate[1]);
mat4.rotateZ(modelMatrix, modelMatrix, rotate[2]);
return (modelMatrix as unknown) as number[];
}
public async init(): Promise<void> {
const {

View File

@ -240,13 +240,12 @@ export default class MapboxService
lnglat: [number, number],
altitude: number,
): IMercator {
return {
x: 0,
y: 0,
z: 0,
};
const { x = 0, y = 0, z = 0 } = mapboxgl.MercatorCoordinate.fromLngLat(
lnglat,
altitude,
);
return { x, y, z };
}
public getModelMatrix(
lnglat: [number, number],
altitude: number,

View File

@ -15,6 +15,8 @@ import {
*/
export default class ReglTexture2D implements ITexture2D {
private texture: regl.Texture2D;
private width: number;
private height: number;
constructor(reGl: regl.Regl, options: ITexture2DInitializationOptions) {
const {
@ -34,6 +36,8 @@ export default class ReglTexture2D implements ITexture2D {
min = gl.NEAREST,
colorSpace = gl.BROWSER_DEFAULT_WEBGL,
} = options;
this.width = width;
this.height = height;
const textureOptions: regl.Texture2DOptions = {
width,
@ -69,9 +73,15 @@ export default class ReglTexture2D implements ITexture2D {
public get() {
return this.texture;
}
public update() {
// @ts-ignore
this.texture._texture.bind();
}
public resize({ width, height }: { width: number; height: number }): void {
this.texture.resize(width, height);
this.width = width;
this.height = height;
}
public destroy() {

View File

@ -36,6 +36,8 @@ import ReglTexture2D from './ReglTexture2D';
export default class ReglRendererService implements IRendererService {
private gl: regl.Regl;
private $container: HTMLDivElement | null;
private width: number;
private height: number;
public async init(
$container: HTMLDivElement,
@ -146,6 +148,8 @@ export default class ReglRendererService implements IRendererService {
renderCanvas.style.height = height / 2 + 'px';
}
this.gl._gl.viewport(x, y, width, height);
this.width = width;
this.height = height;
this.gl._refresh();
};
@ -182,6 +186,31 @@ export default class ReglRendererService implements IRendererService {
return this.gl._gl;
};
public setBaseState() {
this.gl({
cull: {
enable: false,
face: 'back',
},
viewport: {
x: 0,
y: 0,
height: this.width,
width: this.height,
},
blend: {
enable: false,
equation: 'add',
},
framebuffer: null,
});
this.gl._refresh();
}
public setCustomLayerDefaults() {
const gl = this.getGLContext();
gl.disable(gl.CULL_FACE);
}
public destroy = () => {
// @see https://github.com/regl-project/regl/blob/gh-pages/API.md#clean-up
this.gl.destroy();

View File

@ -103,6 +103,9 @@ class Scene
this.initControl();
}
public getServiceContainer(): Container {
return this.container;
}
public getSize(): [number, number] {
return this.mapService.getSize();
}
@ -133,6 +136,18 @@ class Scene
return this.sceneService.exportPng(type);
}
public registerRenderService(render: any) {
if (this.sceneService.loaded) {
const renderSerivce = new render(this);
renderSerivce.init();
} else {
this.on('loaded', () => {
const renderSerivce = new render(this);
renderSerivce.init();
});
}
}
public get map() {
return this.mapService.map;
}

11
packages/three/README.md Normal file
View File

@ -0,0 +1,11 @@
# `three`
> L7 ThreeJS
通过ThreeJS 扩展加载3D模型
## Usage
```
import three = require('l7-three');
```

View File

@ -0,0 +1,48 @@
{
"name": "@antv/l7-three",
"version": "2.1.14",
"description": "three for L7 ",
"keywords": ["3D", "L7", "three"],
"author": "thinkinggis <lzx199065@gmail.com>",
"license": "ISC",
"main": "lib/index.js",
"module": "es/index.js",
"unpkg": "dist/l7-tree.js",
"types": "es/index.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"dist",
"lib",
"es",
"README.md"
],
"repository": {
"type": "git",
"url": "git+https://github.com/antvis/L7.git"
},
"scripts": {
"tsc": "tsc --project tsconfig.build.json",
"clean": "rimraf dist; rimraf es; rimraf lib;",
"build": "run-p build:*",
"build:cjs": "BABEL_ENV=cjs babel src --root-mode upward --out-dir lib --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
"build:esm": "BABEL_ENV=esm babel src --root-mode upward --out-dir es --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
"watch": "BABEL_ENV=cjs babel src --watch --root-mode upward --out-dir lib --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
"build:cdn": "node_modules/.bin/rollup -c",
"lint:ts": "run-p -c lint:ts-*",
"test": "jest"
},
"bugs": {
"url": "https://github.com/antvis/L7/issues"
},
"homepage": "https://github.com/antvis/L7#readme",
"dependencies": {
"three": "^0.115.0",
"@antv/l7": "2.1.14",
"@babel/runtime": "^7.7.7",
"rollup": "^2.3.3",
"rollup-plugin-less": "^1.1.2"
}
}

View File

@ -0,0 +1,51 @@
import pkg from './package.json';
import typescript from 'rollup-plugin-typescript';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import buble from 'rollup-plugin-buble';
import postcss from 'rollup-plugin-postcss';
import { terser } from 'rollup-plugin-terser';
import url from 'postcss-url';
export default {
input: './src/index.ts',
plugins: [
// less(),
typescript({
exclude: 'node_modules/**',
typescript: require('typescript')
}),
resolve({
preferBuiltins: false
}),
postcss({
plugins: [
url({ url: 'inline' })
]
}),
commonjs({
namedExports: {
eventemitter3: [ 'EventEmitter' ],
lodash: [ 'merge' ]
}
}),
buble({
transforms: { generator: false }
}),
terser()
],
external: [
'@antv/l7'
],
output: [
{
format: 'umd',
name: 'L7-Three',
file: pkg.unpkg,
sourcemap: true,
globals: {
'@antv/l7': 'L7'
}
}
]
};

View File

@ -0,0 +1,108 @@
import { BaseLayer, ILayer, IMercator } from '@antv/l7';
import {
AnimationMixer,
Camera,
Matrix4,
PCFSoftShadowMap,
PerspectiveCamera,
Scene,
WebGLRenderer,
} from 'three';
import {
IThreeRenderService,
ThreeRenderServiceType,
} from './threeRenderService';
const DEG2RAD = Math.PI / 180;
interface IThreeJSLayer extends ILayer {
getModelMatrix(
lnglat: [number, number],
altitude: number,
rotation: [number, number, number],
scale: [number, number, number],
): Matrix4;
addAnimateMixer(mixer: AnimationMixer): void;
}
export default class ThreeJSLayer
extends BaseLayer<{
onAddMeshes: (threeScene: Scene, layer: ThreeJSLayer) => void;
}>
implements IThreeJSLayer {
public type: string = 'custom';
protected threeRenderService: IThreeRenderService;
private scene: Scene = new Scene();
private renderer: WebGLRenderer;
private animateMixer: AnimationMixer[] = [];
// 地图中点墨卡托坐标
private center: IMercator;
// 初始状态相机变换矩阵
/**
*
*/
public getModelMatrix(
lnglat: [number, number],
altitude: number = 0,
rotation: [number, number, number] = [0, 0, 0],
scale: [number, number, number] = [1, 1, 1],
): Matrix4 {
return new Matrix4().fromArray(
this.mapService.getModelMatrix(
lnglat,
altitude,
rotation,
scale,
this.threeRenderService.center,
),
);
}
public buildModels() {
this.threeRenderService = this.getContainer().get<IThreeRenderService>(
ThreeRenderServiceType,
);
const config = this.getLayerConfig();
if (config && config.onAddMeshes) {
config.onAddMeshes(this.scene, this);
}
}
public renderModels() {
const gl = this.rendererService.getGLContext();
this.rendererService.setCustomLayerDefaults();
const cullFace =
this.mapService.constructor.name === 'AMapService' ? gl.BACK : gl.FRONT;
gl.cullFace(cullFace);
const renderer = this.threeRenderService.renderer;
renderer.state.reset();
renderer.autoClear = false;
const camera = this.threeRenderService.getRenderCamera();
renderer.render(this.scene, camera);
this.rendererService.setBaseState();
this.animateMixer.forEach((mixer: AnimationMixer) => {
mixer.update(this.getTime());
});
this.rendererService.setBaseState();
return this;
}
public renderAMapModels() {
const gl = this.rendererService.getGLContext();
// gl.frontFace(gl.CCW);
// gl.enable(gl.CULL_FACE);
// gl.cullFace(gl.BACK);
this.rendererService.setCustomLayerDefaults();
const renderer = this.threeRenderService.renderer;
renderer.state.reset();
renderer.autoClear = false;
renderer.render(this.scene, this.threeRenderService.getRenderCamera());
this.animateMixer.forEach((mixer: AnimationMixer) => {
mixer.update(this.getTime());
});
this.rendererService.setBaseState();
return this;
}
public addAnimateMixer(mixer: AnimationMixer) {
this.animateMixer.push(mixer);
}
}

View File

@ -0,0 +1,33 @@
import { Scene } from '@antv/l7';
import {
AnimationMixer,
Camera,
Matrix4,
PCFSoftShadowMap,
PerspectiveCamera,
Scene as ThreeScene,
WebGLRenderer,
} from 'three';
import {
IThreeRenderService,
ThreeRenderService,
ThreeRenderServiceType,
} from './threeRenderService';
export default class ThreeRender {
private threeRenderService: IThreeRenderService;
constructor(scene: Scene) {
const sceneContainer = scene.getServiceContainer();
sceneContainer
.bind<IThreeRenderService>(ThreeRenderServiceType)
.to(ThreeRenderService)
.inSingletonScope();
this.threeRenderService = sceneContainer.get<IThreeRenderService>(
ThreeRenderServiceType,
);
}
public init() {
this.threeRenderService.init();
}
}

View File

@ -0,0 +1,110 @@
import { IMapService, IMercator, IRendererService, TYPES } from '@antv/l7';
import { inject, injectable } from 'inversify';
import {
AnimationMixer,
Camera,
Matrix4,
PCFSoftShadowMap,
PerspectiveCamera,
Scene as ThreeScene,
WebGLRenderer,
} from 'three';
const DEG2RAD = Math.PI / 180;
export interface IThreeRenderService {
renderer: WebGLRenderer;
camera: Camera;
center: IMercator;
init(): void;
getRenderCamera(): Camera;
}
export const ThreeRenderServiceType = Symbol.for('ThreeJSRenderService');
@injectable()
export class ThreeRenderService implements IThreeRenderService {
public renderer: WebGLRenderer;
public camera: Camera;
public center: IMercator;
private scene: ThreeScene;
// 初始状态相机变换矩阵
private cameraTransform: Matrix4;
@inject(TYPES.IRendererService)
private readonly rendererService: IRendererService;
@inject(TYPES.IMapService)
private readonly mapService: IMapService;
public init() {
const canvas = this.rendererService.getCanvas() as HTMLCanvasElement;
const gl = this.rendererService.getGLContext();
if (canvas && gl) {
const center = this.mapService.getCenter();
this.center = this.mapService.lngLatToMercator(
[center.lng, center.lat],
0,
);
}
const { x, y, z } = this.center;
this.cameraTransform = new Matrix4().makeTranslation(x, y, z);
this.renderer = new WebGLRenderer({
canvas,
context: gl,
antialias: true,
});
this.renderer.autoClear = false;
// 是否需要 gamma correction?
this.renderer.gammaFactor = 2.2;
this.renderer.shadowMap.enabled = true;
// this.renderer.shadowMap.type = PCFSoftShadowMap;
this.scene = new ThreeScene();
this.camera = new PerspectiveCamera(45, 1, 1, 2000000);
}
public getRenderCamera(): Camera {
return this.mapService.constructor.name === 'AMapService'
? this.AMapCamera()
: this.mapboxCamera();
}
private mapboxCamera(): Camera {
const mercatorMatrix = new Matrix4().fromArray(
// @ts-ignore
this.mapService.map.transform.customLayerMatrix(),
);
this.camera.projectionMatrix = mercatorMatrix.multiply(
this.cameraTransform,
);
return this.camera;
}
private AMapCamera(): Camera {
// @ts-ignore
const mapCamera = this.mapService.map.getCameraState();
const camera = this.camera;
let { pitch, rotation } = mapCamera;
const { fov, near, far, height, aspect } = mapCamera;
pitch *= DEG2RAD;
rotation *= DEG2RAD;
// @ts-ignore
camera.fov = (180 * fov) / Math.PI;
// @ts-ignore
camera.aspect = aspect;
// @ts-ignore
camera.near = near;
// @ts-ignore
camera.far = far;
// @ts-ignore
camera.updateProjectionMatrix();
camera.position.z = height * Math.cos(pitch);
camera.position.x = height * Math.sin(pitch) * Math.sin(rotation);
camera.position.y = -height * Math.sin(pitch) * Math.cos(rotation);
camera.up.x = -Math.cos(pitch) * Math.sin(rotation);
camera.up.y = Math.cos(pitch) * Math.cos(rotation);
camera.up.z = Math.sin(pitch);
camera.lookAt(0, 0, 0);
camera.position.x += mapCamera.position.x;
camera.position.y += -mapCamera.position.y;
return camera;
}
}

View File

@ -0,0 +1,3 @@
import ThreeLayer from './core/baseLayer';
import ThreeRender from './core/threeRender';
export { ThreeLayer, ThreeRender };

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"declarationDir": "./es",
"rootDir": "./src",
"baseUrl": "./"
},
"include": ["./src"]
}

View File

@ -0,0 +1,125 @@
import { Scene } from '@antv/l7';
import { GaodeMap, Mapbox } from '@antv/l7-maps';
import { ThreeLayer, ThreeRender } from '@antv/l7-three';
import * as React from 'react';
// import { DirectionalLight, Scene as ThreeScene } from 'three';
import * as THREE from 'three';
// tslint:disable-next-line:no-submodule-imports
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
export default class GlTFThreeJSDemo extends React.Component {
// @ts-ignore
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const response = await fetch(
'https://gw.alipayobjects.com/os/basement_prod/893d1d5f-11d9-45f3-8322-ee9140d288ae.json',
);
const pointsData = await response.json();
const scene = new Scene({
id: 'map',
map: new GaodeMap({
center: [111.4453125, 32.84267363195431],
pitch: 45,
rotation: 30,
zoom: 13,
}),
});
this.scene = scene;
scene.registerRenderService(ThreeRender);
scene.on('loaded', () => {
const threeJSLayer = new ThreeLayer({
enableMultiPassRenderer: false,
onAddMeshes: (threeScene: THREE.Scene, layer: ThreeLayer) => {
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);
// 使用 Three.js glTFLoader 加载模型
const loader = new GLTFLoader();
loader.load(
// 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf',
// 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/AnimatedCube/glTF/AnimatedCube.gltf',
// 'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/radar/34M_17.gltf',
// 'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/duck/Duck.gltf', // duck
// 'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/truck/CesiumMilkTruck.gltf', // Truck
// 'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/man/CesiumMan.gltf',
'https://gw.alipayobjects.com/os/bmw-prod/3ca0a546-92d8-4ba0-a89c-017c218d5bea.gltf',
(gltf) => {
// 根据 GeoJSON 数据放置模型
layer.getSource().data.dataArray.forEach(({ coordinates }) => {
const gltfScene = gltf.scene;
gltfScene.applyMatrix(
// 生成模型矩阵
layer.getModelMatrix(
[coordinates[0], coordinates[1]], // 经纬度坐标
0, // 高度,单位米/
[Math.PI / 2, -Math.PI, 0], // 沿 XYZ 轴旋转角度
[100, 100, 100], // 沿 XYZ 轴缩放比例
),
);
const animations = gltf.animations;
if (animations && animations.length) {
const mixer = new THREE.AnimationMixer(gltfScene);
// @ts-ignore
// for (let i = 0; i < 1; i++) {
const animation = animations[2];
// There's .3333 seconds junk at the tail of the Monster animation that
// keeps it from looping cleanly. Clip it at 3 seconds
const action = mixer.clipAction(animation);
action.play();
// }
layer.addAnimateMixer(mixer);
}
// 向场景中添加模型
threeScene.add(gltfScene);
});
// 重绘图层
layer.render();
},
);
},
})
.source({
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [111.4453125, 32.84267363195431],
},
},
],
})
.animate(true);
scene.addLayer(threeJSLayer);
});
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -0,0 +1,146 @@
import { Scene, PolygonLayer, ThreeJSLayer } from '@antv/l7';
import { GaodeMap, Mapbox } from '@antv/l7-maps';
import { ThreeLayer, ThreeRender } from '@antv/l7-three';
import * as React from 'react';
// import { DirectionalLight, Scene as ThreeScene } from 'three';
import * as THREE from 'three';
// tslint:disable-next-line:no-submodule-imports
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
export default class GlTFThreeJSDemo extends React.Component {
// @ts-ignore
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
center: [112, 35.39847],
pitch: 45,
rotation: 30,
zoom: 5,
}),
});
this.scene = scene;
scene.registerRenderService(ThreeRender);
scene.on('loaded', async () => {
const response = await fetch(
'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json',
);
const data = await response.json();
const polygonlayer = new PolygonLayer({
name: '01',
});
polygonlayer
.source(data)
.color('name', [
'#2E8AE6',
'#69D1AB',
'#DAF291',
'#FFD591',
'#FF7A45',
'#CF1D49',
])
.shape('fill')
.select(true)
.style({
opacity: 1.0,
});
scene.addLayer(polygonlayer);
const threeJSLayer = new ThreeLayer({
enableMultiPassRenderer: false,
onAddMeshes: (threeScene: THREE.Scene, layer: ThreeLayer) => {
// 添加光
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);
// 使用 Three.js glTFLoader 加载模型
const loader = new GLTFLoader();
loader.load(
// 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf',
// 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/AnimatedCube/glTF/AnimatedCube.gltf',
// 'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/radar/34M_17.gltf',
// 'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/duck/Duck.gltf', // duck
'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/truck/CesiumMilkTruck.gltf', // Truck
// 'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/man/CesiumMan.gltf',
// 'https://gw.alipayobjects.com/os/bmw-prod/3ca0a546-92d8-4ba0-a89c-017c218d5bea.gltf',
(gltf) => {
// 根据 GeoJSON 数据放置模型
layer.getSource().data.dataArray.forEach(({ coordinates }) => {
const gltfScene = gltf.scene.clone();
gltfScene.applyMatrix(
// 生成模型矩阵
layer.getModelMatrix(
[coordinates[0], coordinates[1]], // 经纬度坐标
0, // 高度,单位米
[Math.PI / 2, 0, 0], // 沿 XYZ 轴旋转角度
[100000, 100000, 100000], // 沿 XYZ 轴缩放比例
),
);
const animations = gltf.animations;
if (animations && animations.length) {
const mixer = new THREE.AnimationMixer(gltfScene);
// @ts-ignore
for (let i = 0; i < animations.length; i++) {
const animation = animations[i];
// There's .3333 seconds junk at the tail of the Monster animation that
// keeps it from looping cleanly. Clip it at 3 seconds
const action = mixer.clipAction(animation);
action.play();
}
layer.addAnimateMixer(mixer);
}
// 向场景中添加模型
threeScene.add(gltfScene);
});
// 重绘图层
layer.render();
},
);
},
})
.source({
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [112, 35.39847],
},
},
],
})
.animate(true);
scene.addLayer(threeJSLayer);
});
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -0,0 +1,157 @@
import { PointLayer, Scene } from '@antv/l7';
import { GaodeMap, Mapbox } from '@antv/l7-maps';
import { ThreeLayer, ThreeRender } from '@antv/l7-three';
import * as React from 'react';
// import { DirectionalLight, Scene as ThreeScene } from 'three';
import * as THREE from 'three';
// tslint:disable-next-line:no-submodule-imports
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
export default class GlTFThreeJSDemo extends React.Component {
// @ts-ignore
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const response = await fetch(
'https://gw.alipayobjects.com/os/basement_prod/893d1d5f-11d9-45f3-8322-ee9140d288ae.json',
);
const pointsData = await response.json();
const scene = new Scene({
id: 'map',
map: new Mapbox({
center: [121.4, 31.258134],
pitch: 45,
rotation: 30,
zoom: 15,
}),
});
scene.registerRenderService(ThreeRender);
this.scene = scene;
scene.on('loaded', async () => {
// scene.registerRenderService(ThreeRender);
const response = await fetch(
'https://gw.alipayobjects.com/os/basement_prod/893d1d5f-11d9-45f3-8322-ee9140d288ae.json',
);
scene.addImage(
'00',
'https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*Rq6tQ5b4_JMAAAAAAAAAAABkARQnAQ',
);
scene.addImage(
'01',
'https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*0D0SQ6AgkRMAAAAAAAAAAABkARQnAQ',
);
scene.addImage(
'02',
'https://gw.alipayobjects.com/zos/rmsportal/xZXhTxbglnuTmZEwqQrE.png',
);
const data = await response.json();
const imageLayer = new PointLayer()
.source(data, {
parser: {
type: 'json',
x: 'longitude',
y: 'latitude',
},
})
.shape('name', ['00', '01', '02'])
// .shape('triangle')
// .color('red')
.active(true)
.size(20);
scene.addLayer(imageLayer);
const threeJSLayer = new ThreeLayer({
enableMultiPassRenderer: false,
onAddMeshes: (threeScene: THREE.Scene, layer: ThreeLayer) => {
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);
// 使用 Three.js glTFLoader 加载模型
const loader = new GLTFLoader();
loader.load(
// 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf',
// 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/AnimatedCube/glTF/AnimatedCube.gltf',
// 'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/radar/34M_17.gltf',
// 'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/duck/Duck.gltf', // duck
// 'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/truck/CesiumMilkTruck.gltf', // Truck
// 'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/man/CesiumMan.gltf',
'https://gw.alipayobjects.com/os/bmw-prod/3ca0a546-92d8-4ba0-a89c-017c218d5bea.gltf',
(gltf) => {
// 根据 GeoJSON 数据放置模型
layer.getSource().data.dataArray.forEach(({ coordinates }) => {
const gltfScene = gltf.scene;
gltfScene.applyMatrix(
// 生成模型矩阵
layer.getModelMatrix(
[coordinates[0], coordinates[1]], // 经纬度坐标
0, // 高度,单位米/
[Math.PI / 2, -Math.PI, 0], // 沿 XYZ 轴旋转角度
[10, 10, 10], // 沿 XYZ 轴缩放比例
),
);
const animations = gltf.animations;
if (animations && animations.length) {
const mixer = new THREE.AnimationMixer(gltfScene);
// @ts-ignore
// for (let i = 0; i < 1; i++) {
const animation = animations[2];
// There's .3333 seconds junk at the tail of the Monster animation that
// keeps it from looping cleanly. Clip it at 3 seconds
const action = mixer.clipAction(animation);
action.play();
// }
layer.addAnimateMixer(mixer);
}
// 向场景中添加模型
threeScene.add(gltfScene);
});
// 重绘图层
layer.render();
},
);
},
})
.source({
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [121.4, 31.258134],
},
},
],
})
.animate(true);
scene.addLayer(threeJSLayer);
});
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -0,0 +1,10 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import AMapModel from './Components/amap_three';
import MapboxModel from './Components/mapbox_three';
import ThreeRender from './Components/threeRender';
storiesOf('3D 模型', module)
.add('ThreeJS Render', () => <ThreeRender />, {})
.add('高德模型', () => <AMapModel />, {})
.add('Mapbox模型', () => <MapboxModel />, {});

View File

@ -7,6 +7,7 @@ import {
DirectionalLight,
Mesh,
MeshLambertMaterial,
Matrix4,
Scene as ThreeScene,
} from 'three';
// @ts-ignore
@ -71,7 +72,11 @@ export default class ThreeJSLayerComponent extends React.Component {
});
const cube = new Mesh(geometry, redMaterial);
cube.applyMatrix(
layer.getModelMatrix([120.19382669582967, 30.258134], 10, [0, 0, 0]),
layer.getModelMatrix([120.19382669582967, 30.258134], 10, [
0,
0,
0,
]) as Matrix4,
);
cube.frustumCulled = false;
threeScene.add(cube);

View File

@ -1,7 +1,7 @@
import { Scene, ThreeJSLayer } from '@antv/l7';
import { Mapbox } from '@antv/l7-maps';
import * as React from 'react';
import { DirectionalLight, Scene as ThreeScene } from 'three';
import { DirectionalLight, Matrix4, Scene as ThreeScene } from 'three';
// tslint:disable-next-line:no-submodule-imports
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
@ -25,7 +25,7 @@ export default class GlTFThreeJSDemo extends React.Component {
center: [121.434765, 31.256735],
pitch: 45,
rotation: 30,
zoom: 16,
zoom: 18,
}),
});
this.scene = scene;
@ -46,7 +46,8 @@ export default class GlTFThreeJSDemo extends React.Component {
loader.load(
// 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf',
// 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/AnimatedCube/glTF/AnimatedCube.gltf',
'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
'https://gw.alipayobjects.com/os/antvdemo/assets/gltf/radar/34M_17.gltf',
// 'https://gw.alipayobjects.com/os/bmw-prod/3ca0a546-92d8-4ba0-a89c-017c218d5bea.gltf',
(gltf) => {
// 根据 GeoJSON 数据放置模型
layer.getSource().data.dataArray.forEach(({ coordinates }) => {

1312
yarn.lock

File diff suppressed because it is too large Load Diff