mirror of https://gitee.com/antv-l7/antv-l7
feat: 3D model
This commit is contained in:
parent
e42149bdb2
commit
377ae5c036
|
@ -183,8 +183,5 @@
|
||||||
"tnpm": {
|
"tnpm": {
|
||||||
"mode": "yarn"
|
"mode": "yarn"
|
||||||
},
|
},
|
||||||
"version": "0.0.0",
|
"version": "0.0.0"
|
||||||
"dependencies": {
|
|
||||||
"@turf/turf": "^5.1.6"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,5 +60,7 @@ export interface IRendererService {
|
||||||
getGLContext(): WebGLRenderingContext;
|
getGLContext(): WebGLRenderingContext;
|
||||||
viewport(size: { x: number; y: number; width: number; height: number }): void;
|
viewport(size: { x: number; y: number; width: number; height: number }): void;
|
||||||
readPixels(options: IReadPixelsOptions): Uint8Array;
|
readPixels(options: IReadPixelsOptions): Uint8Array;
|
||||||
|
setBaseState(): void;
|
||||||
|
setCustomLayerDefaults(): void;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,8 @@ export interface ITexture2DInitializationOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITexture2D {
|
export interface ITexture2D {
|
||||||
|
get(): unknown;
|
||||||
|
update(): void;
|
||||||
resize(options: { width: number; height: number }): void;
|
resize(options: { width: number; height: number }): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { IRenderConfig } from '../renderer/IRendererService';
|
||||||
|
|
||||||
export interface ISceneService {
|
export interface ISceneService {
|
||||||
destroyed: boolean;
|
destroyed: boolean;
|
||||||
|
loaded: boolean;
|
||||||
on(type: string, handle: (...args: any[]) => void): void;
|
on(type: string, handle: (...args: any[]) => void): void;
|
||||||
off(type: string, handle: (...args: any[]) => void): void;
|
off(type: string, handle: (...args: any[]) => void): void;
|
||||||
removeAllListeners(event?: string): this;
|
removeAllListeners(event?: string): this;
|
||||||
|
|
|
@ -34,6 +34,8 @@ import { ISceneService } from './ISceneService';
|
||||||
export default class Scene extends EventEmitter implements ISceneService {
|
export default class Scene extends EventEmitter implements ISceneService {
|
||||||
public destroyed: boolean = false;
|
public destroyed: boolean = false;
|
||||||
|
|
||||||
|
public loaded: boolean = false;
|
||||||
|
|
||||||
@inject(TYPES.SceneID)
|
@inject(TYPES.SceneID)
|
||||||
private readonly id: string;
|
private readonly id: string;
|
||||||
/**
|
/**
|
||||||
|
@ -228,6 +230,7 @@ export default class Scene extends EventEmitter implements ISceneService {
|
||||||
this.logger.info(' render inited');
|
this.logger.info(' render inited');
|
||||||
this.layerService.initLayers();
|
this.layerService.initLayers();
|
||||||
this.controlService.addControls();
|
this.controlService.addControls();
|
||||||
|
this.loaded = true;
|
||||||
this.emit('loaded');
|
this.emit('loaded');
|
||||||
this.inited = true;
|
this.inited = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,9 +36,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@antv/l7": "2.1.14",
|
"@antv/l7": "2.1.14",
|
||||||
"@antv/l7-component": "2.1.14",
|
|
||||||
"@babel/runtime": "^7.7.7",
|
"@babel/runtime": "^7.7.7",
|
||||||
"@turf/circle": "^6.0.1",
|
"@turf/turf": "^5.1.6",
|
||||||
"@turf/distance": "^6.0.1",
|
"@turf/distance": "^6.0.1",
|
||||||
"@turf/helpers": "^6.1.4",
|
"@turf/helpers": "^6.1.4",
|
||||||
"@turf/midpoint": "^5.1.5",
|
"@turf/midpoint": "^5.1.5",
|
||||||
|
|
|
@ -20,6 +20,7 @@ export default class ImageModel extends BaseModel {
|
||||||
|
|
||||||
public getUninforms(): IModelUniform {
|
public getUninforms(): IModelUniform {
|
||||||
const { opacity } = this.layer.getLayerConfig() as IImageLayerStyleOptions;
|
const { opacity } = this.layer.getLayerConfig() as IImageLayerStyleOptions;
|
||||||
|
this.texture.update();
|
||||||
return {
|
return {
|
||||||
u_opacity: opacity || 1.0,
|
u_opacity: opacity || 1.0,
|
||||||
u_texture: this.texture,
|
u_texture: this.texture,
|
||||||
|
|
|
@ -4,17 +4,27 @@
|
||||||
* @see https://github.com/peterqliu/threebox/blob/master/examples/Object3D.html
|
* @see https://github.com/peterqliu/threebox/blob/master/examples/Object3D.html
|
||||||
*/
|
*/
|
||||||
import { IMercator } from '@antv/l7-core';
|
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';
|
import BaseLayer from '../core/BaseLayer';
|
||||||
|
const DEG2RAD = Math.PI / 180;
|
||||||
export default class ThreeJSLayer extends BaseLayer<{
|
export default class ThreeJSLayer extends BaseLayer<{
|
||||||
onAddMeshes: (threeScene: Scene, layer: ThreeJSLayer) => void;
|
onAddMeshes: (threeScene: Scene, layer: ThreeJSLayer) => void;
|
||||||
}> {
|
}> {
|
||||||
public name: string = 'ThreeJSLayer';
|
public name: string = 'ThreeJSLayer';
|
||||||
|
public type: string = 'custom';
|
||||||
|
|
||||||
private scene: Scene;
|
private scene: Scene;
|
||||||
private camera: Camera;
|
private camera: Camera;
|
||||||
private renderer: WebGLRenderer;
|
private renderer: WebGLRenderer;
|
||||||
|
private animateMixer: AnimationMixer[] = [];
|
||||||
|
|
||||||
// 地图中点墨卡托坐标
|
// 地图中点墨卡托坐标
|
||||||
private center: IMercator;
|
private center: IMercator;
|
||||||
|
@ -63,12 +73,12 @@ export default class ThreeJSLayer extends BaseLayer<{
|
||||||
// L7 负责 clear
|
// L7 负责 clear
|
||||||
this.renderer.autoClear = false;
|
this.renderer.autoClear = false;
|
||||||
// 是否需要 gamma correction?
|
// 是否需要 gamma correction?
|
||||||
this.renderer.gammaOutput = true;
|
|
||||||
this.renderer.gammaFactor = 2.2;
|
this.renderer.gammaFactor = 2.2;
|
||||||
|
this.renderer.shadowMap.enabled = true;
|
||||||
|
// this.renderer.shadowMap.type = PCFSoftShadowMap;
|
||||||
|
|
||||||
this.scene = new Scene();
|
this.scene = new Scene();
|
||||||
// 后续同步 L7 相机
|
this.camera = new PerspectiveCamera(45, 1, 1, 2000000);
|
||||||
this.camera = new Camera();
|
|
||||||
|
|
||||||
const config = this.getLayerConfig();
|
const config = this.getLayerConfig();
|
||||||
if (config && config.onAddMeshes) {
|
if (config && config.onAddMeshes) {
|
||||||
|
@ -76,10 +86,14 @@ export default class ThreeJSLayer extends BaseLayer<{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderModels() {
|
public renderModels() {
|
||||||
const { width, height } = this.rendererService.getViewportSize();
|
return this.mapService.constructor.name === 'AMapService'
|
||||||
this.renderer.setSize(width, height, false);
|
? this.renderAMapModels()
|
||||||
|
: this.renderMapboxModels();
|
||||||
|
}
|
||||||
|
public renderMapboxModels() {
|
||||||
|
// const { width, height } = this.rendererService.getViewportSize();
|
||||||
|
// this.renderer.setSize(width, height, false);
|
||||||
|
|
||||||
const gl = this.rendererService.getGLContext();
|
const gl = this.rendererService.getGLContext();
|
||||||
gl.frontFace(gl.CCW);
|
gl.frontFace(gl.CCW);
|
||||||
|
@ -97,9 +111,57 @@ export default class ThreeJSLayer extends BaseLayer<{
|
||||||
);
|
);
|
||||||
this.renderer.state.reset();
|
this.renderer.state.reset();
|
||||||
this.renderer.render(this.scene, this.camera);
|
this.renderer.render(this.scene, this.camera);
|
||||||
|
this.rendererService.setBaseState();
|
||||||
|
this.animateMixer.forEach((mixer: AnimationMixer) => {
|
||||||
|
mixer.update(this.getTime());
|
||||||
|
});
|
||||||
return this;
|
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() {
|
protected getConfigSchema() {
|
||||||
return {
|
return {
|
||||||
properties: {
|
properties: {
|
||||||
|
|
|
@ -258,10 +258,29 @@ export default class AMapService
|
||||||
lnglat: [number, number],
|
lnglat: [number, number],
|
||||||
altitude: number,
|
altitude: number,
|
||||||
rotate: [number, number, number],
|
rotate: [number, number, number],
|
||||||
scale: [number, number, number],
|
scale: [number, number, number] = [1, 1, 1],
|
||||||
origin: IMercator,
|
origin: IMercator = { x: 0, y: 0, z: 0 },
|
||||||
): number[] {
|
): 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> {
|
public async init(): Promise<void> {
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -240,13 +240,12 @@ export default class MapboxService
|
||||||
lnglat: [number, number],
|
lnglat: [number, number],
|
||||||
altitude: number,
|
altitude: number,
|
||||||
): IMercator {
|
): IMercator {
|
||||||
return {
|
const { x = 0, y = 0, z = 0 } = mapboxgl.MercatorCoordinate.fromLngLat(
|
||||||
x: 0,
|
lnglat,
|
||||||
y: 0,
|
altitude,
|
||||||
z: 0,
|
);
|
||||||
};
|
return { x, y, z };
|
||||||
}
|
}
|
||||||
|
|
||||||
public getModelMatrix(
|
public getModelMatrix(
|
||||||
lnglat: [number, number],
|
lnglat: [number, number],
|
||||||
altitude: number,
|
altitude: number,
|
||||||
|
|
|
@ -15,6 +15,8 @@ import {
|
||||||
*/
|
*/
|
||||||
export default class ReglTexture2D implements ITexture2D {
|
export default class ReglTexture2D implements ITexture2D {
|
||||||
private texture: regl.Texture2D;
|
private texture: regl.Texture2D;
|
||||||
|
private width: number;
|
||||||
|
private height: number;
|
||||||
|
|
||||||
constructor(reGl: regl.Regl, options: ITexture2DInitializationOptions) {
|
constructor(reGl: regl.Regl, options: ITexture2DInitializationOptions) {
|
||||||
const {
|
const {
|
||||||
|
@ -34,6 +36,8 @@ export default class ReglTexture2D implements ITexture2D {
|
||||||
min = gl.NEAREST,
|
min = gl.NEAREST,
|
||||||
colorSpace = gl.BROWSER_DEFAULT_WEBGL,
|
colorSpace = gl.BROWSER_DEFAULT_WEBGL,
|
||||||
} = options;
|
} = options;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
|
||||||
const textureOptions: regl.Texture2DOptions = {
|
const textureOptions: regl.Texture2DOptions = {
|
||||||
width,
|
width,
|
||||||
|
@ -69,9 +73,15 @@ export default class ReglTexture2D implements ITexture2D {
|
||||||
public get() {
|
public get() {
|
||||||
return this.texture;
|
return this.texture;
|
||||||
}
|
}
|
||||||
|
public update() {
|
||||||
|
// @ts-ignore
|
||||||
|
this.texture._texture.bind();
|
||||||
|
}
|
||||||
|
|
||||||
public resize({ width, height }: { width: number; height: number }): void {
|
public resize({ width, height }: { width: number; height: number }): void {
|
||||||
this.texture.resize(width, height);
|
this.texture.resize(width, height);
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
|
|
|
@ -36,6 +36,8 @@ import ReglTexture2D from './ReglTexture2D';
|
||||||
export default class ReglRendererService implements IRendererService {
|
export default class ReglRendererService implements IRendererService {
|
||||||
private gl: regl.Regl;
|
private gl: regl.Regl;
|
||||||
private $container: HTMLDivElement | null;
|
private $container: HTMLDivElement | null;
|
||||||
|
private width: number;
|
||||||
|
private height: number;
|
||||||
|
|
||||||
public async init(
|
public async init(
|
||||||
$container: HTMLDivElement,
|
$container: HTMLDivElement,
|
||||||
|
@ -146,6 +148,8 @@ export default class ReglRendererService implements IRendererService {
|
||||||
renderCanvas.style.height = height / 2 + 'px';
|
renderCanvas.style.height = height / 2 + 'px';
|
||||||
}
|
}
|
||||||
this.gl._gl.viewport(x, y, width, height);
|
this.gl._gl.viewport(x, y, width, height);
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
this.gl._refresh();
|
this.gl._refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -182,6 +186,31 @@ export default class ReglRendererService implements IRendererService {
|
||||||
return this.gl._gl;
|
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 = () => {
|
public destroy = () => {
|
||||||
// @see https://github.com/regl-project/regl/blob/gh-pages/API.md#clean-up
|
// @see https://github.com/regl-project/regl/blob/gh-pages/API.md#clean-up
|
||||||
this.gl.destroy();
|
this.gl.destroy();
|
||||||
|
|
|
@ -103,6 +103,9 @@ class Scene
|
||||||
|
|
||||||
this.initControl();
|
this.initControl();
|
||||||
}
|
}
|
||||||
|
public getServiceContainer(): Container {
|
||||||
|
return this.container;
|
||||||
|
}
|
||||||
public getSize(): [number, number] {
|
public getSize(): [number, number] {
|
||||||
return this.mapService.getSize();
|
return this.mapService.getSize();
|
||||||
}
|
}
|
||||||
|
@ -133,6 +136,18 @@ class Scene
|
||||||
return this.sceneService.exportPng(type);
|
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() {
|
public get map() {
|
||||||
return this.mapService.map;
|
return this.mapService.map;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# `three`
|
||||||
|
|
||||||
|
> L7 ThreeJS
|
||||||
|
通过ThreeJS 扩展加载3D模型
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
import three = require('l7-three');
|
||||||
|
|
||||||
|
```
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
import ThreeLayer from './core/baseLayer';
|
||||||
|
import ThreeRender from './core/threeRender';
|
||||||
|
export { ThreeLayer, ThreeRender };
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.build.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"declarationDir": "./es",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"baseUrl": "./"
|
||||||
|
},
|
||||||
|
"include": ["./src"]
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 />, {});
|
|
@ -7,6 +7,7 @@ import {
|
||||||
DirectionalLight,
|
DirectionalLight,
|
||||||
Mesh,
|
Mesh,
|
||||||
MeshLambertMaterial,
|
MeshLambertMaterial,
|
||||||
|
Matrix4,
|
||||||
Scene as ThreeScene,
|
Scene as ThreeScene,
|
||||||
} from 'three';
|
} from 'three';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -71,7 +72,11 @@ export default class ThreeJSLayerComponent extends React.Component {
|
||||||
});
|
});
|
||||||
const cube = new Mesh(geometry, redMaterial);
|
const cube = new Mesh(geometry, redMaterial);
|
||||||
cube.applyMatrix(
|
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;
|
cube.frustumCulled = false;
|
||||||
threeScene.add(cube);
|
threeScene.add(cube);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Scene, ThreeJSLayer } from '@antv/l7';
|
import { Scene, ThreeJSLayer } from '@antv/l7';
|
||||||
import { Mapbox } from '@antv/l7-maps';
|
import { Mapbox } from '@antv/l7-maps';
|
||||||
import * as React from 'react';
|
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
|
// tslint:disable-next-line:no-submodule-imports
|
||||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ export default class GlTFThreeJSDemo extends React.Component {
|
||||||
center: [121.434765, 31.256735],
|
center: [121.434765, 31.256735],
|
||||||
pitch: 45,
|
pitch: 45,
|
||||||
rotation: 30,
|
rotation: 30,
|
||||||
zoom: 16,
|
zoom: 18,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
|
@ -46,7 +46,8 @@ export default class GlTFThreeJSDemo extends React.Component {
|
||||||
loader.load(
|
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/DamagedHelmet/glTF/DamagedHelmet.gltf',
|
||||||
// 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/AnimatedCube/glTF/AnimatedCube.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) => {
|
(gltf) => {
|
||||||
// 根据 GeoJSON 数据放置模型
|
// 根据 GeoJSON 数据放置模型
|
||||||
layer.getSource().data.dataArray.forEach(({ coordinates }) => {
|
layer.getSource().data.dataArray.forEach(({ coordinates }) => {
|
||||||
|
|
Loading…
Reference in New Issue