Merge pull request #288 from antvis/dev-three.js

Dev three.js
This commit is contained in:
@thinkinggis 2020-04-22 23:28:58 +08:00 committed by GitHub
commit 58448178d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1047 additions and 8 deletions

View File

@ -103,6 +103,7 @@
"rollup-pluginutils": "^2.8.2",
"sass-loader": "^7.1.0",
"style-loader": "^1.0.0",
"three": "0.115.0",
"styled-components": "^3.4.6",
"stylelint": "^9.5.0",
"stylelint-config-recommended": "^2.1.0",

View File

@ -24,8 +24,6 @@ const bytesPerElementMap = {
[gl.UNSIGNED_SHORT]: 2,
};
let counter = 0;
/**
* Layer
*/
@ -41,11 +39,8 @@ export default class StyleAttributeService implements IStyleAttributeService {
private readonly rendererService: IRendererService;
private attributes: IStyleAttribute[] = [];
private triangulation: Triangulation;
private c = counter++;
private featureLayout: {
sizePerElement: number;
elements: Array<{
@ -186,7 +181,7 @@ export default class StyleAttributeService implements IStyleAttributeService {
public createAttributesAndIndices(
features: IEncodeFeature[],
triangulation?: Triangulation,
triangulation: Triangulation,
): {
attributes: {
[attributeName: string]: IAttribute;

View File

@ -10,7 +10,11 @@ export interface IPoint {
x: number;
y: number;
}
export interface IMercator {
x: number;
y: number;
z: number;
}
export interface IStatusOptions {
showIndoorMap: boolean;
resizeEnable: boolean;
@ -73,6 +77,14 @@ export interface IMapService<RawMap = {}> {
lngLatToPixel(lnglat: Point): IPoint;
containerToLngLat(pixel: Point): ILngLat;
lngLatToContainer(lnglat: Point): IPoint;
lngLatToMercator(lnglat: [number, number], altitude: number): IMercator;
getModelMatrix(
lnglat: [number, number],
altitude: number,
rotate: [number, number, number],
scale: [number, number, number],
origin: IMercator,
): number[];
exportMap(type: 'jpg' | 'png'): string;
}

View File

@ -56,7 +56,13 @@ export interface IRendererService {
): void;
getViewportSize(): { width: number; height: number };
getContainer(): HTMLElement | null;
getCanvas(): HTMLCanvasElement | null;
getGLContext(): WebGLRenderingContext;
viewport(size: { x: number; y: number; width: number; height: number }): void;
readPixels(options: IReadPixelsOptions): Uint8Array;
setBaseState(): void;
setCustomLayerDefaults(): void;
setDirty(flag: boolean): void;
getDirty(): boolean;
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;
/**
@ -219,6 +221,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

@ -37,6 +37,7 @@
"dependencies": {
"@antv/l7": "^2.2.0",
"@babel/runtime": "^7.7.7",
"@turf/turf": "^5.1.6",
"@turf/circle": "^6.0.1",
"@turf/distance": "^6.0.1",
"@turf/helpers": "^6.1.4",

View File

@ -39,6 +39,7 @@
"inversify": "^5.0.1",
"lodash": "^4.17.15",
"merge-json-schemas": "1.0.0",
"polyline-miter-util": "^1.0.1",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {

View File

@ -5,6 +5,8 @@ import {
gl,
IActiveOption,
IAnimateOption,
ICameraService,
ICoordinateSystemService,
IDataState,
IEncodeFeature,
IFontService,
@ -118,6 +120,10 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
@lazyInject(TYPES.IShaderModuleService)
protected readonly shaderModuleService: IShaderModuleService;
protected cameraService: ICameraService;
protected coordinateService: ICoordinateSystemService;
protected iconService: IIconService;
protected fontService: IFontService;
@ -252,6 +258,12 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
TYPES.IInteractionService,
);
this.mapService = this.container.get<IMapService>(TYPES.IMapService);
this.cameraService = this.container.get<ICameraService>(
TYPES.ICameraService,
);
this.coordinateService = this.container.get<ICoordinateSystemService>(
TYPES.ICoordinateSystemService,
);
this.postProcessingPassFactory = this.container.get(
TYPES.IFactoryPostProcessingPass,
);

View File

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

View File

@ -10,6 +10,7 @@ import {
ILogService,
IMapConfig,
IMapService,
IMercator,
IPoint,
IStatusOptions,
IViewport,
@ -18,6 +19,7 @@ import {
TYPES,
} from '@antv/l7-core';
import { DOM } from '@antv/l7-utils';
import { mat4, vec2, vec3 } from 'gl-matrix';
import { inject, injectable } from 'inversify';
import { IAMapEvent, IAMapInstance } from '../../typings/index';
import './logo.css';
@ -241,6 +243,45 @@ export default class AMapService
};
}
public lngLatToMercator(
lnglat: [number, number],
altitude: number,
): IMercator {
return {
x: 0,
y: 0,
z: 0,
};
}
public getModelMatrix(
lnglat: [number, number],
altitude: number,
rotate: [number, number, number],
scale: [number, number, number] = [1, 1, 1],
origin: IMercator = { x: 0, y: 0, z: 0 },
): 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 {
id,

View File

@ -10,6 +10,7 @@ import {
ILogService,
IMapConfig,
IMapService,
IMercator,
IPoint,
IStatusOptions,
IViewport,
@ -18,6 +19,7 @@ import {
TYPES,
} from '@antv/l7-core';
import { DOM } from '@antv/l7-utils';
import { mat4, vec2, vec3 } from 'gl-matrix';
import { inject, injectable } from 'inversify';
import mapboxgl, { IControl, Map } from 'mapbox-gl';
@ -234,6 +236,53 @@ export default class MapboxService
public lngLatToContainer(lnglat: [number, number]): IPoint {
return this.map.project(lnglat);
}
public lngLatToMercator(
lnglat: [number, number],
altitude: number,
): IMercator {
const { x = 0, y = 0, z = 0 } = mapboxgl.MercatorCoordinate.fromLngLat(
lnglat,
altitude,
);
return { x, y, z };
}
public getModelMatrix(
lnglat: [number, number],
altitude: number,
rotate: [number, number, number],
scale: [number, number, number] = [1, 1, 1],
origin: IMercator = { x: 0, y: 0, z: 0 },
): number[] {
const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
lnglat,
altitude,
);
// @ts-ignore
const meters = modelAsMercatorCoordinate.meterInMercatorCoordinateUnits();
const modelMatrix = mat4.create();
mat4.translate(
modelMatrix,
modelMatrix,
vec3.fromValues(
modelAsMercatorCoordinate.x - origin.x,
modelAsMercatorCoordinate.y - origin.y,
modelAsMercatorCoordinate.z || 0 - origin.z,
),
);
mat4.scale(
modelMatrix,
modelMatrix,
vec3.fromValues(meters * scale[0], -meters * scale[1], meters * 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

@ -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,9 @@ import ReglTexture2D from './ReglTexture2D';
export default class ReglRendererService implements IRendererService {
private gl: regl.Regl;
private $container: HTMLDivElement | null;
private width: number;
private height: number;
private isDirty: boolean;
public async init(
$container: HTMLDivElement,
@ -146,6 +149,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();
};
@ -174,6 +179,47 @@ export default class ReglRendererService implements IRendererService {
return this.$container;
};
public getCanvas = () => {
return this.$container?.getElementsByTagName('canvas')[0] || null;
};
public getGLContext = () => {
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 setDirty(flag: boolean): void {
this.isDirty = flag;
}
public getDirty(): boolean {
return this.isDirty;
}
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,50 @@
{
"name": "@antv/l7-three",
"version": "2.1.15",
"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": {
"@antv/l7": "2.2.0",
"@babel/runtime": "^7.7.7",
"rollup": "^2.3.3",
"rollup-plugin-less": "^1.1.2"
},
"devDependencies": {
"three": "^0.115.0"
}
}

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,110 @@
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();
this.rendererService.setDirty(true);
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();
this.rendererService.setDirty(true);
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.applyMatrix4(
// 生成模型矩阵
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 { PolygonLayer, 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 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.applyMatrix4(
// 生成模型矩阵
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.applyMatrix4(
// 生成模型矩阵
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

@ -2,6 +2,7 @@ import { RasterLayer, Scene } from '@antv/l7';
import { Mapbox } from '@antv/l7-maps';
import * as dat from 'dat.gui';
// @ts-ignore
// tslint:disable-next-line:no-submodule-imports
import * as GeoTIFF from 'geotiff';
import * as React from 'react';
import { colorScales } from '../lib/colorscales';

View File

@ -202,6 +202,19 @@
resolved "https://registry.npmjs.org/@antv/gl-matrix/-/gl-matrix-2.7.1.tgz#acb8e37f7ab3df01345aba4372d7942be42eba14"
integrity sha512-oOWcVNlpELIKi9x+Mm1Vwbz8pXfkbJKykoCIOJ/dNK79hSIANbpXJ5d3Rra9/wZqK6MC961B7sybFhPlLraT3Q==
"@antv/l7@2.1.15":
version "2.1.15"
resolved "https://registry.npmjs.org/@antv/l7/-/l7-2.1.15.tgz#27121b8f22838b5ad17114a1b2895389b6b0a800"
integrity sha512-goR3M1e2YXXEo5jp/QYd1VF9rKoAy/DlKrJ5KLE/jhzuqgw9Kp0iHSOaGE+M7tFIyL8hCoG8JXHmAK1tVyeaIw==
dependencies:
"@antv/l7-component" "^2.1.15"
"@antv/l7-core" "^2.1.15"
"@antv/l7-layers" "^2.1.15"
"@antv/l7-maps" "^2.1.15"
"@antv/l7-scene" "^2.1.15"
"@antv/l7-utils" "^2.1.15"
"@babel/runtime" "^7.7.7"
"@antv/scale@~0.1.1":
version "0.1.5"
resolved "https://registry.npmjs.org/@antv/scale/-/scale-0.1.5.tgz#243266e8b9047cf64b2fdfc40f9834cf0846496e"
@ -12647,7 +12660,7 @@ gl-matrix@^3.0.0, gl-matrix@^3.1.0:
resolved "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.3.0.tgz#232eef60b1c8b30a28cbbe75b2caf6c48fd6358b"
integrity sha512-COb7LDz+SXaHtl/h4LeaFcNdJdAQSDeVqjiIihSXNrkWObZLhDI4hIkZC11Aeqp7bcE72clzB0BnDXr2SmslRA==
gl-vec2@^1.3.0:
gl-vec2@^1.0.0, gl-vec2@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/gl-vec2/-/gl-vec2-1.3.0.tgz#83d472ed46034de8e09cbc857123fb6c81c51199"
integrity sha512-YiqaAuNsheWmUV0Sa8k94kBB0D6RWjwZztyO+trEYS8KzJ6OQB/4686gdrf59wld4hHFIvaxynO3nRxpk1Ij/A==
@ -19002,6 +19015,13 @@ polished@^3.3.1:
dependencies:
"@babel/runtime" "^7.8.7"
polyline-miter-util@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/polyline-miter-util/-/polyline-miter-util-1.0.1.tgz#b693f2389ea0ded36a6bcf5ecd2ece4b6917d957"
integrity sha1-tpPyOJ6g3tNqa89ezS7OS2kX2Vc=
dependencies:
gl-vec2 "^1.0.0"
popper.js@^1.14.4, popper.js@^1.14.7:
version "1.16.1"
resolved "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
@ -24042,6 +24062,11 @@ threads@^1.3.1:
optionalDependencies:
tiny-worker ">= 2"
three@0.115.0, three@^0.115.0:
version "0.115.0"
resolved "https://registry.npmjs.org/three/-/three-0.115.0.tgz#540d800c381b9da2334c024f0fbe4d23f84eb05e"
integrity sha512-mAV2Ky3RdcbdSbR9capI+tKLvRldWYxd4151PZTT/o7+U2jh9Is3a4KmnYwzyUAhB2ZA3pXSgCd2DOY4Tj5kow==
throat@^4.0.0:
version "4.1.0"
resolved "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"