mirror of https://gitee.com/antv-l7/antv-l7
fix(layer): fix merge conflict
This commit is contained in:
commit
453adb09d6
|
@ -65,3 +65,5 @@ jspm_packages/
|
|||
# End of https://www.gitignore.io/api/node
|
||||
|
||||
lib/
|
||||
|
||||
.DS_Store
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,150 @@
|
|||
# PixelPickingEngine 设计
|
||||
|
||||
在地图交互中,除了地图底图本身提供的平移、旋转、缩放、flyTo 等相机动作,最常用的就是信息要素的拾取以及后续的高亮了。
|
||||
|
||||
3D 引擎常用的拾取技术通常有两种:RayPicking 和 PixelPicking。前者从鼠标点击处沿着投影方向发射一根射线,通过包围盒碰撞检测获取到接触到的第一个对象,后续就可以进行选中对象的高亮甚至是跟随移动了,以上运算均在 CPU 侧完成。
|
||||
但是在 L7 的场景中,海量数据在同一个 Geometry 中,无法计算每个要素的包围盒,因此在 GPU 侧完成的 PixelPicking 更加适合。
|
||||
|
||||
作为拾取引擎 PixelPickingEngine,除了实现内置基本的拾取 Pass,最重要的是提供友好易用的 API,覆盖以下常见场景:
|
||||
* 基本的拾取场景,用户只需要开启 Layer 拾取功能并设置高亮颜色即可。
|
||||
* 拾取后展示特定 UI 组件的场景,用户需要监听事件,在回调中使用上述拾取对象完成组件展示。
|
||||
* 更灵活的联动场景,用户可以不依赖 L7 内置的事件监听机制,直接拾取并高亮指定点/区域包含的要素。
|
||||
|
||||
本文会依次介绍:
|
||||
* PixelPicking 原理
|
||||
* 使用方法
|
||||
* 拾取对象结构
|
||||
* 拾取 API 的使用方法
|
||||
* 开启/关闭拾取
|
||||
* 设置高亮颜色
|
||||
* 展示自定义 UI 组件
|
||||
* 在自定义 Layer 中使用
|
||||
|
||||
## PixelPicking 原理
|
||||
|
||||
在执行时机方面,基于 [MultiPassRenderer](./MultiPassRenderer.md) 的设计,拾取发生在实际渲染之前:
|
||||
```
|
||||
ClearPass -> PixelPickingPass -> RenderPass -> [ ...其他后处理 Pass ] -> CopyPass
|
||||
```
|
||||
|
||||
PixelPickingPass 分解步骤如下:
|
||||
1. 逐要素编码(idx -> color),传入 attributes 渲染 Layer 到纹理。
|
||||
2. 获取鼠标在视口中的位置。由于目前 L7 与地图结合的方案为双 Canvas 而非共享 WebGL Context,事件监听注册在地图底图上。
|
||||
3. 读取纹理在指定位置的颜色,进行解码(color -> idx),查找对应要素,作为 Layer `onHover/onClick` 回调参数传入。
|
||||
4. (可选)将待高亮要素对应的颜色传入 Vertex Shader 用于每个 Vertex 判断自身是否被选中,如果被选中,在 Fragment Shader 中将高亮颜色与计算颜色混合。
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 拾取对象结构定义
|
||||
|
||||
拾取对象结构定义如下:
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
| -------- | --- | ------------- |
|
||||
| x | `number` | 鼠标位置在视口空间 x 坐标,取值范围 `[0, viewportWidth]` |
|
||||
| y | `number` | 鼠标位置在视口空间 y 坐标,取值范围 `[0, viewportHeight]` |
|
||||
| lnglat | `{ lng: number; lat: number; }` | 鼠标位置经纬度坐标 |
|
||||
| feature | `object` | GeoJSON feature 属性 |
|
||||
|
||||
### API
|
||||
|
||||
对于基本的拾取场景,用户只需要开启 Layer 拾取功能并设置高亮颜色即可。
|
||||
而对于拾取后展示特定 UI 组件的场景,用户需要监听事件,在回调中使用上述拾取对象完成组件展示。
|
||||
最后,对于更灵活的联动场景,用户可以不依赖 L7 内置的事件监听机制,直接拾取并高亮指定点/区域包含的要素。
|
||||
|
||||
#### 禁用/开启拾取
|
||||
|
||||
并不是所有 Layer 都需要拾取(例如文本渲染 Layer),通过 `enablePicking` 关闭可以跳过该阶段,减少不必要的渲染开销:
|
||||
```typescript
|
||||
const layer = new PolygonLayer({
|
||||
enablePicking: false, // 关闭拾取
|
||||
});
|
||||
```
|
||||
|
||||
⚠️L7 默认开启拾取。
|
||||
|
||||
#### 设置高亮颜色
|
||||
|
||||
如果一个 Layer 开启了拾取,我们可以通过 `highlightColor` 设置高亮颜色:
|
||||
```typescript
|
||||
const layer = new PolygonLayer({
|
||||
enablePicking: true, // 开启拾取
|
||||
highlightColor: 'red', // 设置高亮颜色
|
||||
});
|
||||
```
|
||||
|
||||
#### 展示自定义 UI 组件
|
||||
|
||||
监听 Layer 上的 `hover/mousemove` 事件就可以得到拾取对象,然后通过对象中包含的位置以及原始数据信息,就可以使用 L7 内置或者自定义 UI 组件展示:
|
||||
```typescript
|
||||
layer.on('hover', ({ x, y, lnglat, feature }) => {
|
||||
// 展示 UI 组件
|
||||
});
|
||||
layer.on('mousemove', ({ x, y, lnglat, feature }) => {
|
||||
// 同上
|
||||
});
|
||||
```
|
||||
|
||||
除了基于事件监听,还可以通过 Layer 的构造函数传入 `onHover` 回调,在后续 Layer 对应的 react 组件中也可以以这种方式使用:
|
||||
```typescript
|
||||
const layer = new PolygonLayer({
|
||||
enablePicking: true,
|
||||
onHover: ({ x, y, lnglat, feature }) => {
|
||||
// 展示 UI 组件
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### 直接调用拾取引擎方法
|
||||
|
||||
除了默认在地图上交互完成拾取,在与其他系统进行联动时,脱离了地图交互,仍需要具备拾取指定点/区域内包含要素的能力。
|
||||
```typescript
|
||||
anotherSystem.on('hover', ({ x, y }) => {
|
||||
layer.pick({
|
||||
x,
|
||||
y,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
⚠️目前只支持拾取视口中一个点所在的要素,未来可以实现拾取指定区域内的全部要素。
|
||||
|
||||
### 自定义 Layer 中的拾取
|
||||
|
||||
用户实现自定义 Layer 时,必然需要实现 Vertex/Fragment Shader。如果也想使用拾取功能,就需要在 Shader 中引入拾取模块,方法如下。
|
||||
|
||||
在 Vertex Shader 中引入 `picking` 模块。关于 L7 Shader 的模块化设计,[详见]()。
|
||||
```glsl
|
||||
// mylayer.vert.glsl
|
||||
|
||||
#pragma include "picking"
|
||||
|
||||
void main() {
|
||||
setPickingColor(customPickingColors);
|
||||
}
|
||||
```
|
||||
|
||||
在 Fragment Shader 中
|
||||
```glsl
|
||||
// mylayer.frag.glsl
|
||||
|
||||
#pragma include "picking"
|
||||
|
||||
void main() {
|
||||
// 必须在末尾,保证后续不会再对 gl_FragColor 进行修改
|
||||
gl_FragColor = highlightPickingColor(gl_FragColor);
|
||||
}
|
||||
```
|
||||
|
||||
其中涉及 `picking` 模块方法说明如下:
|
||||
|
||||
| 方法名 | 应用 shader | 说明 |
|
||||
| -------- | --- | ------------- |
|
||||
| `setPickingColor` | `vertex` | 比较自身颜色编码与高亮颜色,判断是否被选中,传递结果给 fragment |
|
||||
| `highlightPickingColor` | `fragment` | 当前 fragment 被选中则使用高亮颜色混合,否则直接输出原始计算结果 |
|
||||
|
||||
## 参考资料
|
||||
|
||||
* [Deck.gl 交互文档](https://deck.gl/#/documentation/developer-guide/adding-interactivity)
|
||||
* [Deck.gl Picking 实现](https://deck.gl/#/documentation/developer-guide/writing-custom-layers/picking)
|
||||
* 「Interactive.Computer.Graphics.Top.Down.Approach - 3.9 Picking」
|
|
@ -22,6 +22,7 @@
|
|||
"@l7/source": "0.0.1",
|
||||
"eventemitter3": "^3.1.0",
|
||||
"gl-matrix": "^3.1.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"inversify": "^5.0.1",
|
||||
"inversify-inject-decorators": "^3.1.0",
|
||||
"lodash": "^4.17.15",
|
||||
|
@ -32,6 +33,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/gl-matrix": "^2.4.5",
|
||||
"@types/hammerjs": "^2.0.36",
|
||||
"@types/lodash": "^4.14.138",
|
||||
"@types/viewport-mercator-project": "^6.1.0"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import container, { lazyInject } from './inversify.config';
|
||||
import ClearPass from './services/renderer/passes/ClearPass';
|
||||
import MultiPassRenderer from './services/renderer/passes/MultiPassRenderer';
|
||||
import PixelPickingPass from './services/renderer/passes/PixelPickingPass';
|
||||
import BlurHPass from './services/renderer/passes/post-processing/BlurHPass';
|
||||
import BlurVPass from './services/renderer/passes/post-processing/BlurVPass';
|
||||
import CopyPass from './services/renderer/passes/post-processing/CopyPass';
|
||||
|
@ -27,8 +29,10 @@ export {
|
|||
SceneService,
|
||||
packCircleVertex,
|
||||
/** pass */
|
||||
MultiPassRenderer,
|
||||
ClearPass,
|
||||
RenderPass,
|
||||
PixelPickingPass,
|
||||
BlurHPass,
|
||||
BlurVPass,
|
||||
CopyPass,
|
||||
|
|
|
@ -10,6 +10,7 @@ import { IIconService} from './services/asset/IIconService';
|
|||
import { ICameraService } from './services/camera/ICameraService';
|
||||
import { IGlobalConfigService } from './services/config/IConfigService';
|
||||
import { ICoordinateSystemService } from './services/coordinate/ICoordinateSystemService';
|
||||
import { IInteractionService } from './services/interaction/IInteractionService';
|
||||
import { ILayerService } from './services/layer/ILayerService';
|
||||
import { ILogService } from './services/log/ILogService';
|
||||
import { IShaderModuleService } from './services/shader/IShaderModuleService';
|
||||
|
@ -19,6 +20,7 @@ import IconService from './services/asset/IconService';
|
|||
import CameraService from './services/camera/CameraService';
|
||||
import GlobalConfigService from './services/config/ConfigService';
|
||||
import CoordinateSystemService from './services/coordinate/CoordinateSystemService';
|
||||
import InteractionService from './services/interaction/InteractionService';
|
||||
import LayerService from './services/layer/LayerService';
|
||||
import LayerStyleService from './services/layer/LayerStyleService';
|
||||
import LogService from './services/log/LogService';
|
||||
|
@ -58,6 +60,10 @@ container
|
|||
.bind<ILogService>(TYPES.ILogService)
|
||||
.to(LogService)
|
||||
.inSingletonScope();
|
||||
container
|
||||
.bind<IInteractionService>(TYPES.IInteractionService)
|
||||
.to(InteractionService)
|
||||
.inSingletonScope();
|
||||
|
||||
// @see https://github.com/inversify/InversifyJS/blob/master/wiki/inheritance.md#what-can-i-do-when-my-base-class-is-provided-by-a-third-party-module
|
||||
decorate(injectable(), EventEmitter);
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export interface IInteractionService {
|
||||
init(): void;
|
||||
destroy(): void;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import Hammer from 'hammerjs';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { TYPES } from '../../types';
|
||||
import { ILogService } from '../log/ILogService';
|
||||
import { IRendererService } from '../renderer/IRendererService';
|
||||
import { IInteractionService } from './IInteractionService';
|
||||
|
||||
@injectable()
|
||||
export default class InteractionService implements IInteractionService {
|
||||
@inject(TYPES.IRendererService)
|
||||
private readonly rendererService: IRendererService;
|
||||
|
||||
@inject(TYPES.ILogService)
|
||||
private readonly logger: ILogService;
|
||||
|
||||
private hammertime: HammerManager;
|
||||
|
||||
public init() {
|
||||
const $containter = this.rendererService.getContainer();
|
||||
if ($containter) {
|
||||
const hammertime = new Hammer($containter);
|
||||
hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL });
|
||||
hammertime.get('pinch').set({ enable: true });
|
||||
|
||||
// hammertime.on('panstart', this.onPanstart);
|
||||
hammertime.on('panmove', this.onPanmove);
|
||||
// hammertime.on('panend', this.onPanend);
|
||||
// hammertime.on('pinch', this.onPinch);
|
||||
|
||||
// $containter.addEventListener('wheel', this.onMousewheel);
|
||||
this.hammertime = hammertime;
|
||||
}
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
if (this.hammertime) {
|
||||
this.hammertime.destroy();
|
||||
}
|
||||
const $containter = this.rendererService.getContainer();
|
||||
if ($containter) {
|
||||
// $containter.removeEventListener('wheel', this.onMousewheel);
|
||||
}
|
||||
}
|
||||
|
||||
private onPanmove = (e: HammerInput) => {
|
||||
// @ts-ignore
|
||||
// this.logger.info(e);
|
||||
// if (this.isMoving) {
|
||||
// this.deltaX = e.center.x - this.lastX;
|
||||
// this.deltaY = e.center.y - this.lastY;
|
||||
// this.lastX = e.center.x;
|
||||
// this.lastY = e.center.y;
|
||||
// this.emit(Mouse.MOVE_EVENT, {
|
||||
// deltaX: this.deltaX,
|
||||
// deltaY: this.deltaY,
|
||||
// deltaZ: this.deltaZ
|
||||
// });
|
||||
// }
|
||||
};
|
||||
}
|
|
@ -109,6 +109,7 @@ export interface ILayerPlugin {
|
|||
*/
|
||||
export interface ILayerInitializationOptions {
|
||||
enableMultiPassRenderer: boolean;
|
||||
enablePicking: boolean;
|
||||
passes: Array<string | [string, { [key: string]: unknown }]>;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,9 +31,6 @@ export interface IPostProcessingPass extends IPass {
|
|||
export interface IPostProcessor {
|
||||
getReadFBO(): IFramebuffer;
|
||||
getWriteFBO(): IFramebuffer;
|
||||
useScreenRenderTarget(renderCommand: () => void): void;
|
||||
useOffscreenRenderTarget(renderCommand: () => void): void;
|
||||
renderToPostProcessor(renderCommand: () => void): void;
|
||||
resize(viewportWidth: number, viewportHeight: number): void;
|
||||
add(pass: IPostProcessingPass, layer: ILayer): void;
|
||||
render(layer: ILayer): Promise<unknown>;
|
||||
|
|
|
@ -2,9 +2,13 @@ import { ILayer } from '../layer/ILayerService';
|
|||
import { IAttribute, IAttributeInitializationOptions } from './IAttribute';
|
||||
import { IBuffer, IBufferInitializationOptions } from './IBuffer';
|
||||
import { IElements, IElementsInitializationOptions } from './IElements';
|
||||
import { IFramebuffer } from './IFramebuffer';
|
||||
import {
|
||||
IFramebuffer,
|
||||
IFramebufferInitializationOptions,
|
||||
} from './IFramebuffer';
|
||||
import { IModel, IModelInitializationOptions } from './IModel';
|
||||
import { IMultiPassRenderer, IPass } from './IMultiPassRenderer';
|
||||
import { ITexture2D, ITexture2DInitializationOptions } from './ITexture2D';
|
||||
|
||||
export interface IRenderConfig {
|
||||
/**
|
||||
|
@ -32,7 +36,13 @@ export interface IRendererService {
|
|||
createAttribute(options: IAttributeInitializationOptions): IAttribute;
|
||||
createBuffer(options: IBufferInitializationOptions): IBuffer;
|
||||
createElements(options: IElementsInitializationOptions): IElements;
|
||||
createMultiPassRenderer(layer: ILayer): IMultiPassRenderer;
|
||||
createTexture2D(options: ITexture2DInitializationOptions): ITexture2D;
|
||||
createFramebuffer(options: IFramebufferInitializationOptions): IFramebuffer;
|
||||
renderToFramebuffer(
|
||||
framebuffer: IFramebuffer | null,
|
||||
drawCommands: () => void,
|
||||
): void;
|
||||
getViewportSize(): { width: number; height: number };
|
||||
getContainer(): HTMLElement | null;
|
||||
viewport(size: { x: number; y: number; width: number; height: number }): void;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export default class BasePostProcessingPass<InitializationOptions = {}>
|
|||
protected readonly shaderModule: IShaderModuleService;
|
||||
|
||||
@lazyInject(TYPES.IRendererService)
|
||||
protected readonly renderer: IRendererService;
|
||||
protected readonly rendererService: IRendererService;
|
||||
|
||||
protected config: Partial<InitializationOptions> | undefined;
|
||||
|
||||
|
@ -51,7 +51,7 @@ export default class BasePostProcessingPass<InitializationOptions = {}>
|
|||
}
|
||||
|
||||
public init() {
|
||||
const { createAttribute, createBuffer, createModel } = this.renderer;
|
||||
const { createAttribute, createBuffer, createModel } = this.rendererService;
|
||||
const { vs, fs, uniforms } = this.setupShaders();
|
||||
|
||||
this.model = createModel({
|
||||
|
@ -81,19 +81,31 @@ export default class BasePostProcessingPass<InitializationOptions = {}>
|
|||
|
||||
public render(layer: ILayer) {
|
||||
const postProcessor = layer.multiPassRenderer.getPostProcessor();
|
||||
const { renderToFramebuffer } = this.rendererService;
|
||||
|
||||
const useRenderTarget = (this.renderToScreen
|
||||
? postProcessor.useScreenRenderTarget
|
||||
: postProcessor.useOffscreenRenderTarget
|
||||
).bind(postProcessor);
|
||||
renderToFramebuffer(
|
||||
this.renderToScreen ? null : postProcessor.getWriteFBO(),
|
||||
() => {
|
||||
this.model.draw({
|
||||
uniforms: {
|
||||
u_Texture: postProcessor.getReadFBO(),
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
useRenderTarget(async () => {
|
||||
this.model.draw({
|
||||
uniforms: {
|
||||
u_Texture: postProcessor.getReadFBO(),
|
||||
},
|
||||
});
|
||||
});
|
||||
// const useRenderTarget = (this.renderToScreen
|
||||
// ? postProcessor.useScreenRenderTarget
|
||||
// : postProcessor.useOffscreenRenderTarget
|
||||
// ).bind(postProcessor);
|
||||
|
||||
// useRenderTarget(async () => {
|
||||
// this.model.draw({
|
||||
// uniforms: {
|
||||
// u_Texture: postProcessor.getReadFBO(),
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
}
|
||||
|
||||
public isEnabled() {
|
||||
|
|
|
@ -1,45 +1,44 @@
|
|||
import { injectable } from 'inversify';
|
||||
import { ILayer } from '../../layer/ILayerService';
|
||||
import {
|
||||
ILayer,
|
||||
IMultiPassRenderer,
|
||||
IPass,
|
||||
IPostProcessingPass,
|
||||
IPostProcessor,
|
||||
PassType,
|
||||
} from '@l7/core';
|
||||
import regl from 'regl';
|
||||
import ReglPostProcessor from './ReglPostProcessor';
|
||||
} from '../IMultiPassRenderer';
|
||||
import PostProcessor from './PostProcessor';
|
||||
|
||||
/**
|
||||
* ported from Three.js EffectComposer
|
||||
* @example
|
||||
* const renderer = new MultiPassRenderer(gl, [
|
||||
* new ClearPass(gl),
|
||||
* new RenderPass(gl, {
|
||||
* const renderer = new MultiPassRenderer([
|
||||
* new ClearPass(),
|
||||
* new RenderPass({
|
||||
* models: [
|
||||
* new Model(),
|
||||
* new Model(),
|
||||
* ],
|
||||
* }),
|
||||
* new CopyPass(gl, {
|
||||
* new CopyPass({
|
||||
* renderToScreen: true,
|
||||
* }),
|
||||
* new TAAPass(gl),
|
||||
* new TAAPass(),
|
||||
* ]);
|
||||
* renderer.render();
|
||||
* @see https://yuque.antfin-inc.com/yuqi.pyq/fgetpa/apuvbf#dRM8W
|
||||
*/
|
||||
export default class ReglMultiPassRenderer implements IMultiPassRenderer {
|
||||
@injectable()
|
||||
export default class MultiPassRenderer implements IMultiPassRenderer {
|
||||
private passes: IPass[] = [];
|
||||
private postProcessor: IPostProcessor;
|
||||
|
||||
private reGl: regl.Regl;
|
||||
private layer: ILayer;
|
||||
private renderFlag: boolean;
|
||||
|
||||
constructor(reGl: regl.Regl, layer: ILayer) {
|
||||
this.reGl = reGl;
|
||||
constructor(layer: ILayer) {
|
||||
this.layer = layer;
|
||||
this.postProcessor = new ReglPostProcessor(reGl);
|
||||
this.postProcessor = new PostProcessor();
|
||||
}
|
||||
|
||||
public setRenderFlag(renderFlag: boolean) {
|
|
@ -0,0 +1,46 @@
|
|||
import { inject, injectable } from 'inversify';
|
||||
import { lazyInject } from '../../../index';
|
||||
import { TYPES } from '../../../types';
|
||||
import { ILayer, ILayerService } from '../../layer/ILayerService';
|
||||
import { gl } from '../gl';
|
||||
import { IFramebuffer } from '../IFramebuffer';
|
||||
import { IPass, PassType } from '../IMultiPassRenderer';
|
||||
import { IRendererService } from '../IRendererService';
|
||||
|
||||
/**
|
||||
* PixelPickingPass based on
|
||||
*/
|
||||
@injectable()
|
||||
export default class PixelPickingPass implements IPass {
|
||||
@lazyInject(TYPES.IRendererService)
|
||||
protected readonly rendererService: IRendererService;
|
||||
|
||||
private pickingFBO: IFramebuffer;
|
||||
|
||||
public getType() {
|
||||
return PassType.Normal;
|
||||
}
|
||||
|
||||
public init(layer: ILayer) {
|
||||
const { createTexture2D, createFramebuffer } = this.rendererService;
|
||||
this.pickingFBO = createFramebuffer({
|
||||
color: createTexture2D({
|
||||
width: 1,
|
||||
height: 1,
|
||||
wrapS: gl.CLAMP_TO_EDGE,
|
||||
wrapT: gl.CLAMP_TO_EDGE,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
public render(layer: ILayer) {
|
||||
const { getViewportSize, renderToFramebuffer } = this.rendererService;
|
||||
this.pickingFBO.resize(getViewportSize());
|
||||
|
||||
renderToFramebuffer(this.pickingFBO, () => {
|
||||
layer.multiPassRenderer.setRenderFlag(false);
|
||||
layer.render();
|
||||
layer.multiPassRenderer.setRenderFlag(true);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,32 +1,29 @@
|
|||
import {
|
||||
gl,
|
||||
IFramebuffer,
|
||||
ILayer,
|
||||
IPostProcessingPass,
|
||||
IPostProcessor,
|
||||
} from '@l7/core';
|
||||
import regl from 'regl';
|
||||
import ReglFramebuffer from './ReglFramebuffer';
|
||||
import ReglTexture2D from './ReglTexture2D';
|
||||
import { injectable } from 'inversify';
|
||||
import { lazyInject } from '../../../index';
|
||||
import { TYPES } from '../../../types';
|
||||
import { ILayer } from '../../layer/ILayerService';
|
||||
import { gl } from '../gl';
|
||||
import { IFramebuffer } from '../IFramebuffer';
|
||||
import { IPostProcessingPass, IPostProcessor } from '../IMultiPassRenderer';
|
||||
import { IRendererService } from '../IRendererService';
|
||||
|
||||
/**
|
||||
* ported from Three.js EffectComposer
|
||||
* 后处理负责 pingpong read/write framebuffer,最后一个 pass 默认输出到屏幕
|
||||
*/
|
||||
export default class ReglPostProcessor implements IPostProcessor {
|
||||
@injectable()
|
||||
export default class PostProcessor implements IPostProcessor {
|
||||
@lazyInject(TYPES.IRendererService)
|
||||
protected readonly rendererService: IRendererService;
|
||||
|
||||
private passes: IPostProcessingPass[] = [];
|
||||
private readFBO: IFramebuffer;
|
||||
private writeFBO: IFramebuffer;
|
||||
|
||||
private screenRenderTarget: regl.DrawCommand;
|
||||
private offscreenRenderTarget: regl.DrawCommand;
|
||||
private inputRenderTarget: regl.DrawCommand;
|
||||
|
||||
private reGl: regl.Regl;
|
||||
|
||||
constructor(reGl: regl.Regl) {
|
||||
this.reGl = reGl;
|
||||
this.readFBO = new ReglFramebuffer(reGl, {
|
||||
color: new ReglTexture2D(reGl, {
|
||||
constructor() {
|
||||
const { createFramebuffer, createTexture2D } = this.rendererService;
|
||||
this.readFBO = createFramebuffer({
|
||||
color: createTexture2D({
|
||||
width: 1,
|
||||
height: 1,
|
||||
wrapS: gl.CLAMP_TO_EDGE,
|
||||
|
@ -34,27 +31,14 @@ export default class ReglPostProcessor implements IPostProcessor {
|
|||
}),
|
||||
});
|
||||
|
||||
this.writeFBO = new ReglFramebuffer(reGl, {
|
||||
color: new ReglTexture2D(reGl, {
|
||||
this.writeFBO = createFramebuffer({
|
||||
color: createTexture2D({
|
||||
width: 1,
|
||||
height: 1,
|
||||
wrapS: gl.CLAMP_TO_EDGE,
|
||||
wrapT: gl.CLAMP_TO_EDGE,
|
||||
}),
|
||||
});
|
||||
|
||||
this.screenRenderTarget = reGl({
|
||||
framebuffer: null,
|
||||
});
|
||||
|
||||
this.offscreenRenderTarget = reGl({
|
||||
// since post-processor will swap read/write fbos, we must retrieve it dynamically
|
||||
framebuffer: () => (this.writeFBO as ReglFramebuffer).get(),
|
||||
});
|
||||
|
||||
this.inputRenderTarget = reGl({
|
||||
framebuffer: () => (this.readFBO as ReglFramebuffer).get(),
|
||||
});
|
||||
}
|
||||
|
||||
public getReadFBO() {
|
||||
|
@ -65,26 +49,6 @@ export default class ReglPostProcessor implements IPostProcessor {
|
|||
return this.writeFBO;
|
||||
}
|
||||
|
||||
public renderToPostProcessor(renderCommand: () => void) {
|
||||
this.inputRenderTarget(() => {
|
||||
this.reGl.clear({
|
||||
color: [0, 0, 0, 0],
|
||||
depth: 1,
|
||||
stencil: 0,
|
||||
framebuffer: (this.getReadFBO() as ReglFramebuffer).get(),
|
||||
});
|
||||
renderCommand();
|
||||
});
|
||||
}
|
||||
|
||||
public useScreenRenderTarget(callback: () => void) {
|
||||
this.screenRenderTarget({}, callback);
|
||||
}
|
||||
|
||||
public useOffscreenRenderTarget(callback: () => void) {
|
||||
this.offscreenRenderTarget({}, callback);
|
||||
}
|
||||
|
||||
public async render(layer: ILayer) {
|
||||
for (let i = 0; i < this.passes.length; i++) {
|
||||
const pass = this.passes[i];
|
|
@ -1,12 +1,18 @@
|
|||
import { inject, injectable } from 'inversify';
|
||||
import { ILayer, ILayerService } from '../../layer/ILayerService';
|
||||
import { injectable } from 'inversify';
|
||||
import { lazyInject } from '../../../index';
|
||||
import { TYPES } from '../../../types';
|
||||
import { ILayer } from '../../layer/ILayerService';
|
||||
import { IPass, PassType } from '../IMultiPassRenderer';
|
||||
import { IRendererService } from '../IRendererService';
|
||||
|
||||
/**
|
||||
* RenderPass,负责输出到后续 PostProcessor 的 readFBO 中
|
||||
*/
|
||||
@injectable()
|
||||
export default class RenderPass implements IPass {
|
||||
@lazyInject(TYPES.IRendererService)
|
||||
protected readonly rendererService: IRendererService;
|
||||
|
||||
public getType() {
|
||||
return PassType.Normal;
|
||||
}
|
||||
|
@ -16,7 +22,16 @@ export default class RenderPass implements IPass {
|
|||
}
|
||||
|
||||
public render(layer: ILayer) {
|
||||
layer.multiPassRenderer.getPostProcessor().renderToPostProcessor(() => {
|
||||
const { renderToFramebuffer, clear } = this.rendererService;
|
||||
const readFBO = layer.multiPassRenderer.getPostProcessor().getReadFBO();
|
||||
renderToFramebuffer(readFBO, () => {
|
||||
clear({
|
||||
color: [0, 0, 0, 0],
|
||||
depth: 1,
|
||||
stencil: 0,
|
||||
framebuffer: readFBO,
|
||||
});
|
||||
|
||||
// render to post processor
|
||||
layer.multiPassRenderer.setRenderFlag(false);
|
||||
layer.render();
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { injectable } from 'inversify';
|
||||
import { lazyInject } from '../../../../index';
|
||||
import blur from '../../../../shaders/post-processing/blur.glsl';
|
||||
import quad from '../../../../shaders/post-processing/quad.glsl';
|
||||
import { TYPES } from '../../../../types';
|
||||
import { IRendererService } from '../../IRendererService';
|
||||
import BasePostProcessingPass from '../BasePostProcessingPass';
|
||||
|
||||
export interface IBlurHPassConfig {
|
||||
|
@ -15,6 +18,9 @@ const defaultConfig: IBlurHPassConfig = {
|
|||
export default class BlurHPass extends BasePostProcessingPass<
|
||||
IBlurHPassConfig
|
||||
> {
|
||||
@lazyInject(TYPES.IRendererService)
|
||||
protected readonly rendererService: IRendererService;
|
||||
|
||||
public setupShaders() {
|
||||
this.shaderModule.registerModule('blur-pass', {
|
||||
vs: quad,
|
||||
|
@ -22,7 +28,7 @@ export default class BlurHPass extends BasePostProcessingPass<
|
|||
});
|
||||
|
||||
const { vs, fs, uniforms } = this.shaderModule.getModule('blur-pass');
|
||||
const { width, height } = this.renderer.getViewportSize();
|
||||
const { width, height } = this.rendererService.getViewportSize();
|
||||
|
||||
const { blurRadius } = {
|
||||
...defaultConfig,
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { injectable } from 'inversify';
|
||||
import { lazyInject } from '../../../../index';
|
||||
import blur from '../../../../shaders/post-processing/blur.glsl';
|
||||
import quad from '../../../../shaders/post-processing/quad.glsl';
|
||||
import { TYPES } from '../../../../types';
|
||||
import { IRendererService } from '../../IRendererService';
|
||||
import BasePostProcessingPass from '../BasePostProcessingPass';
|
||||
|
||||
export interface IBlurVPassConfig {
|
||||
|
@ -15,6 +18,9 @@ const defaultConfig: IBlurVPassConfig = {
|
|||
export default class BlurVPass extends BasePostProcessingPass<
|
||||
IBlurVPassConfig
|
||||
> {
|
||||
@lazyInject(TYPES.IRendererService)
|
||||
protected readonly rendererService: IRendererService;
|
||||
|
||||
public setupShaders() {
|
||||
this.shaderModule.registerModule('blur-pass', {
|
||||
vs: quad,
|
||||
|
@ -22,7 +28,7 @@ export default class BlurVPass extends BasePostProcessingPass<
|
|||
});
|
||||
|
||||
const { vs, fs, uniforms } = this.shaderModule.getModule('blur-pass');
|
||||
const { width, height } = this.renderer.getViewportSize();
|
||||
const { width, height } = this.rendererService.getViewportSize();
|
||||
|
||||
const { blurRadius } = {
|
||||
...defaultConfig,
|
||||
|
|
|
@ -5,6 +5,7 @@ import { TYPES } from '../../types';
|
|||
import { createRendererContainer } from '../../utils/dom';
|
||||
import { ICameraService, IViewport } from '../camera/ICameraService';
|
||||
import { IGlobalConfig, IGlobalConfigService } from '../config/IConfigService';
|
||||
import { IInteractionService } from '../interaction/IInteractionService';
|
||||
import { ILayer, ILayerService } from '../layer/ILayerService';
|
||||
import { ILogService } from '../log/ILogService';
|
||||
import { IMapCamera, IMapService } from '../map/IMapService';
|
||||
|
@ -38,6 +39,9 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
@inject(TYPES.ICameraService)
|
||||
private readonly cameraService: ICameraService;
|
||||
|
||||
@inject(TYPES.IInteractionService)
|
||||
private readonly interactionService: IInteractionService;
|
||||
|
||||
@inject(TYPES.IShaderModuleService)
|
||||
private readonly shaderModule: IShaderModuleService;
|
||||
|
||||
|
@ -110,6 +114,9 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
// 初始化 ShaderModule
|
||||
this.shaderModule.registerBuiltinModules();
|
||||
|
||||
// 初始化 container 上的交互
|
||||
this.interactionService.init();
|
||||
|
||||
// TODO:init renderer
|
||||
this.logger.info('renderer loaded');
|
||||
});
|
||||
|
@ -141,6 +148,7 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
this.inited = false;
|
||||
this.layerService.clean();
|
||||
this.configService.reset();
|
||||
this.interactionService.destroy();
|
||||
window.removeEventListener('resize', this.handleWindowResized, false);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ const TYPES = {
|
|||
IRendererService: Symbol.for('IRendererService'),
|
||||
IShaderModuleService: Symbol.for('IShaderModuleService'),
|
||||
IIconService: Symbol.for('IIconService'),
|
||||
IInteractionService: Symbol.for('IInteractionService'),
|
||||
|
||||
/** multi-pass */
|
||||
ClearPass: Symbol.for('ClearPass'),
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
import {
|
||||
gl,
|
||||
IRendererService,
|
||||
IShaderModuleService,
|
||||
lazyInject,
|
||||
TYPES,
|
||||
} from '@l7/core';
|
||||
import BaseLayer from '../core/BaseLayer';
|
||||
import LineBuffer from './buffers/line';
|
||||
import line_frag from './shaders/line_frag.glsl';
|
||||
import line_vert from './shaders/line_vert.glsl';
|
||||
export default class LineLayer extends BaseLayer {
|
||||
public name: string = 'LineLayer';
|
||||
@lazyInject(TYPES.IShaderModuleService)
|
||||
private readonly shaderModule: IShaderModuleService;
|
||||
|
||||
@lazyInject(TYPES.IRendererService)
|
||||
private readonly renderer: IRendererService;
|
||||
|
||||
protected renderModels() {
|
||||
this.models.forEach((model) =>
|
||||
model.draw({
|
||||
uniforms: {
|
||||
u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
||||
},
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
protected buildModels(): void {
|
||||
this.shaderModule.registerModule('line', {
|
||||
vs: line_vert,
|
||||
fs: line_frag,
|
||||
});
|
||||
|
||||
this.models = [];
|
||||
const { vs, fs, uniforms } = this.shaderModule.getModule('line');
|
||||
const buffer = new LineBuffer({
|
||||
data: this.getEncodedData(),
|
||||
style: this.styleOption,
|
||||
});
|
||||
console.log(buffer);
|
||||
const {
|
||||
createAttribute,
|
||||
createBuffer,
|
||||
createElements,
|
||||
createModel,
|
||||
} = this.renderer;
|
||||
|
||||
this.models.push(
|
||||
createModel({
|
||||
attributes: {
|
||||
a_Position: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.positions,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 3,
|
||||
}),
|
||||
a_normal: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.normals,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 3,
|
||||
}),
|
||||
a_color: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.colors,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 4,
|
||||
}),
|
||||
a_size: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.sizes,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 1,
|
||||
}),
|
||||
a_miter: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.miters,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 1,
|
||||
}),
|
||||
},
|
||||
uniforms: {
|
||||
...uniforms,
|
||||
u_opacity: this.styleOption.opacity as number,
|
||||
},
|
||||
fs,
|
||||
vs,
|
||||
count: buffer.indexArray.length,
|
||||
elements: createElements({
|
||||
data: buffer.indexArray,
|
||||
type: gl.UNSIGNED_INT,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
uniform float u_blur : 0.9;
|
||||
varying vec4 v_color;
|
||||
varying vec3 v_normal;
|
||||
void main() {
|
||||
gl_FragColor = v_color;
|
||||
// anti-alias
|
||||
// float blur = 1. - smoothstep(u_blur, 1., length(v_normal));
|
||||
// gl_FragColor.a *= blur;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
attribute float a_miter;
|
||||
attribute vec4 a_color;
|
||||
attribute float a_size;
|
||||
attribute float a_distance;
|
||||
attribute float a_dash_array;
|
||||
attribute float a_total_distance;
|
||||
attribute vec3 a_normal;
|
||||
attribute vec3 a_Position;
|
||||
uniform mat4 u_ModelMatrix;
|
||||
|
||||
varying vec4 v_color;
|
||||
varying float v_dash_array;
|
||||
varying vec3 v_normal;
|
||||
#pragma include "projection"
|
||||
void main() {
|
||||
v_normal = a_normal;
|
||||
v_color = a_color;
|
||||
vec3 size = a_miter * a_size * v_normal;
|
||||
vec2 offset = project_pixel(size.xy);
|
||||
vec4 project_pos = project_position(vec4(a_Position.xy, 0, 1.0));
|
||||
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, 0, 1.0));
|
||||
}
|
|
@ -9,6 +9,8 @@ import {
|
|||
IPostProcessingPass,
|
||||
IRendererService,
|
||||
lazyInject,
|
||||
MultiPassRenderer,
|
||||
PixelPickingPass,
|
||||
RenderPass,
|
||||
TYPES,
|
||||
} from '@l7/core';
|
||||
|
@ -20,6 +22,20 @@ const builtinPostProcessingPassMap: {
|
|||
blurV: BlurVPass,
|
||||
};
|
||||
|
||||
/**
|
||||
* 'blurH' -> ['blurH', {}]
|
||||
*/
|
||||
function normalizePasses(
|
||||
passes: Array<string | [string, { [key: string]: unknown }]>,
|
||||
) {
|
||||
return passes.map((pass: string | [string, { [key: string]: unknown }]) => {
|
||||
if (typeof pass === 'string') {
|
||||
pass = [pass, {}];
|
||||
}
|
||||
return pass;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Layer 配置的 passes 创建 MultiPassRenderer 并渲染
|
||||
* @example
|
||||
|
@ -83,28 +99,23 @@ export default class MultiPassRendererPlugin implements ILayerPlugin {
|
|||
layer: ILayer,
|
||||
passes: Array<string | [string, { [key: string]: unknown }]>,
|
||||
) {
|
||||
const multiPassRenderer = this.rendererService.createMultiPassRenderer(
|
||||
layer,
|
||||
);
|
||||
// TODO: PickingPass
|
||||
multiPassRenderer.add(new ClearPass());
|
||||
multiPassRenderer.add(new RenderPass());
|
||||
const multiPassRenderer = new MultiPassRenderer(layer);
|
||||
|
||||
const normalizedPasses: Array<
|
||||
[string, { [key: string]: unknown }]
|
||||
> = passes.map((pass: string | [string, { [key: string]: unknown }]) => {
|
||||
if (typeof pass === 'string') {
|
||||
pass = [pass, {}];
|
||||
}
|
||||
return pass;
|
||||
});
|
||||
multiPassRenderer.add(new ClearPass());
|
||||
|
||||
if (layer.getInitializationOptions().enablePicking) {
|
||||
multiPassRenderer.add(new PixelPickingPass());
|
||||
}
|
||||
multiPassRenderer.add(new RenderPass());
|
||||
|
||||
// post processing
|
||||
// TODO: pass initialization params
|
||||
normalizedPasses.forEach((pass: [string, { [key: string]: unknown }]) => {
|
||||
const PostProcessingPassClazz = builtinPostProcessingPassMap[pass[0]];
|
||||
multiPassRenderer.add(new PostProcessingPassClazz(pass[1]));
|
||||
});
|
||||
normalizePasses(passes).forEach(
|
||||
(pass: [string, { [key: string]: unknown }]) => {
|
||||
const PostProcessingPassClazz = builtinPostProcessingPassMap[pass[0]];
|
||||
multiPassRenderer.add(new PostProcessingPassClazz(pass[1]));
|
||||
},
|
||||
);
|
||||
// 末尾为固定的 CopyPass
|
||||
multiPassRenderer.add(new CopyPass());
|
||||
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
/**
|
||||
* 对于 polyline-normal 的改进
|
||||
* 超过阈值,miter 转成 bevel 接头,
|
||||
* 要注意 Three.js 中默认 THREE.FrontFaceDirectionCCW
|
||||
* @see https://zhuanlan.zhihu.com/p/59541559
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { copy, create, dot } from 'gl-vec2';
|
||||
// @ts-ignore
|
||||
import { computeMiter, direction, normal } from 'polyline-miter-util';
|
||||
|
||||
// @ts-ignore
|
||||
function extrusions(positions, out, miters, point, normal1, scale) {
|
||||
addNext(out, miters, normal1, -scale);
|
||||
addNext(out, miters, normal1, scale);
|
||||
positions.push(...point, 0);
|
||||
positions.push(...point, 0);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
// tslint:disable-next-line:no-shadowed-variable
|
||||
function addNext(out, miters, normal, length) {
|
||||
out.push(normal[0], normal[1], 0);
|
||||
miters.push(length);
|
||||
}
|
||||
// @ts-ignore
|
||||
function lineSegmentDistance(end, start) {
|
||||
const dx = start[0] - end[0];
|
||||
const dy = start[1] - end[1];
|
||||
const dz = start[2] - end[2];
|
||||
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
// @ts-ignore
|
||||
function isPointEqual(a, b) {
|
||||
return a[0] === b[0] && a[1] === b[1];
|
||||
}
|
||||
// @ts-ignore
|
||||
export default function(points, closed, indexOffset) {
|
||||
const lineA = [0, 0];
|
||||
const lineB = [0, 0];
|
||||
const tangent = [0, 0];
|
||||
const miter = [0, 0];
|
||||
// tslint:disable-next-line:variable-name
|
||||
let _started = false;
|
||||
// tslint:disable-next-line:variable-name
|
||||
let _normal = null;
|
||||
const tmp = create();
|
||||
let count = indexOffset || 0;
|
||||
const miterLimit = 3;
|
||||
// @ts-ignore
|
||||
const out = [];
|
||||
const attrPos = [];
|
||||
const attrIndex = [];
|
||||
// @ts-ignore
|
||||
const miters = [];
|
||||
const attrDistance = [0, 0];
|
||||
if (closed) {
|
||||
points = points.slice();
|
||||
points.push(points[0]);
|
||||
}
|
||||
|
||||
const total = points.length;
|
||||
|
||||
for (let i = 1; i < total; i++) {
|
||||
const index = count;
|
||||
const last = points[i - 1];
|
||||
const cur = points[i];
|
||||
let next = i < points.length - 1 ? points[i + 1] : null;
|
||||
// 如果当前点和前一点相同,跳过
|
||||
if (isPointEqual(last, cur)) {
|
||||
continue;
|
||||
}
|
||||
if (next) {
|
||||
let nextIndex = i + 1;
|
||||
// 找到不相同的下一点
|
||||
while (next && isPointEqual(cur, next)) {
|
||||
next = nextIndex < points.length - 1 ? points[++nextIndex] : null;
|
||||
}
|
||||
}
|
||||
const lineDistance = lineSegmentDistance(cur, last);
|
||||
const d = lineDistance + attrDistance[attrDistance.length - 1];
|
||||
|
||||
direction(lineA, cur, last);
|
||||
|
||||
if (!_normal) {
|
||||
_normal = [0, 0];
|
||||
normal(_normal, lineA);
|
||||
}
|
||||
|
||||
if (!_started) {
|
||||
_started = true;
|
||||
// @ts-ignore
|
||||
extrusions(attrPos, out, miters, last, _normal, 1);
|
||||
}
|
||||
|
||||
attrIndex.push(index + 0, index + 2, index + 1);
|
||||
|
||||
// no miter, simple segment
|
||||
if (!next) {
|
||||
// reset normal
|
||||
normal(_normal, lineA);
|
||||
// @ts-ignore
|
||||
extrusions(attrPos, out, miters, cur, _normal, 1);
|
||||
attrDistance.push(d, d);
|
||||
attrIndex.push(index + 1, index + 2, index + 3);
|
||||
count += 2;
|
||||
} else {
|
||||
// get unit dir of next line
|
||||
direction(lineB, next, cur);
|
||||
|
||||
// stores tangent & miter
|
||||
let miterLen = computeMiter(tangent, miter, lineA, lineB, 1);
|
||||
|
||||
// get orientation
|
||||
const flip = dot(tangent, _normal) < 0 ? -1 : 1;
|
||||
const bevel = Math.abs(miterLen) > miterLimit;
|
||||
|
||||
// 处理前后两条线段重合的情况,这种情况不需要使用任何接头(miter/bevel)。
|
||||
// 理论上这种情况下 miterLen = Infinity,本应通过 isFinite(miterLen) 判断,
|
||||
// 但是 AMap 投影变换后丢失精度,只能通过一个阈值(1000)判断。
|
||||
if (Math.abs(miterLen) > 1000) {
|
||||
// @ts-ignore
|
||||
extrusions(attrPos, out, miters, cur, _normal, 1);
|
||||
attrIndex.push(index + 1, index + 2, index + 3);
|
||||
attrIndex.push(index + 2, index + 4, index + 3);
|
||||
normal(tmp, lineB);
|
||||
copy(_normal, tmp); // store normal for next round
|
||||
// @ts-ignore
|
||||
extrusions(attrPos, out, miters, cur, _normal, 1);
|
||||
attrDistance.push(d, d, d, d);
|
||||
|
||||
// the miter is now the normal for our next join
|
||||
count += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bevel) {
|
||||
miterLen = miterLimit;
|
||||
|
||||
// next two points in our first segment
|
||||
// @ts-ignore
|
||||
extrusions(attrPos, out, miters, cur, _normal, 1);
|
||||
|
||||
attrIndex.push(index + 1, index + 2, index + 3);
|
||||
|
||||
// now add the bevel triangle
|
||||
attrIndex.push(
|
||||
...(flip === 1
|
||||
? [index + 2, index + 4, index + 5]
|
||||
: [index + 4, index + 5, index + 3]),
|
||||
);
|
||||
|
||||
normal(tmp, lineB);
|
||||
copy(_normal, tmp); // store normal for next round
|
||||
// @ts-ignore
|
||||
extrusions(attrPos, out, miters, cur, _normal, 1);
|
||||
attrDistance.push(d, d, d, d);
|
||||
|
||||
// the miter is now the normal for our next join
|
||||
count += 4;
|
||||
} else {
|
||||
// next two points in our first segment
|
||||
// @ts-ignore
|
||||
extrusions(attrPos, out, miters, cur, _normal, 1);
|
||||
attrIndex.push(index + 1, index + 2, index + 3);
|
||||
|
||||
// now add the miter triangles
|
||||
// @ts-ignore
|
||||
addNext(out, miters, miter, miterLen * -flip);
|
||||
attrPos.push(...cur, 0);
|
||||
attrIndex.push(index + 2, index + 4, index + 3);
|
||||
attrIndex.push(index + 4, index + 5, index + 6);
|
||||
normal(tmp, lineB);
|
||||
copy(_normal, tmp); // store normal for next round
|
||||
// @ts-ignore
|
||||
extrusions(attrPos, out, miters, cur, _normal, 1);
|
||||
attrDistance.push(d, d, d, d, d);
|
||||
|
||||
// the miter is now the normal for our next join
|
||||
count += 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
return {
|
||||
// @ts-ignore
|
||||
normals: out,
|
||||
attrIndex,
|
||||
attrPos,
|
||||
attrDistance,
|
||||
// @ts-ignore
|
||||
miters,
|
||||
};
|
||||
}
|
|
@ -1,7 +1,4 @@
|
|||
import {
|
||||
IRenderbuffer,
|
||||
IRenderbufferInitializationOptions,
|
||||
} from '@l7/core';
|
||||
import { IRenderbuffer, IRenderbufferInitializationOptions } from '@l7/core';
|
||||
import regl from 'regl';
|
||||
import { formatMap } from './constants';
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* @see https://github.com/regl-project/regl/blob/gh-pages/API.md
|
||||
*/
|
||||
import {
|
||||
gl,
|
||||
IAttribute,
|
||||
IAttributeInitializationOptions,
|
||||
IBuffer,
|
||||
|
@ -11,20 +10,22 @@ import {
|
|||
IClearOptions,
|
||||
IElements,
|
||||
IElementsInitializationOptions,
|
||||
ILayer,
|
||||
IFramebuffer,
|
||||
IFramebufferInitializationOptions,
|
||||
IModel,
|
||||
IModelInitializationOptions,
|
||||
IMultiPassRenderer,
|
||||
IRendererService,
|
||||
ITexture2D,
|
||||
ITexture2DInitializationOptions,
|
||||
} from '@l7/core';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { injectable } from 'inversify';
|
||||
import regl from 'regl';
|
||||
import ReglAttribute from './ReglAttribute';
|
||||
import ReglBuffer from './ReglBuffer';
|
||||
import ReglElements from './ReglElements';
|
||||
import ReglFramebuffer from './ReglFramebuffer';
|
||||
import ReglModel from './ReglModel';
|
||||
import ReglMultiPassRenderer from './ReglMultiPassRenderer';
|
||||
import ReglTexture2D from './ReglTexture2D';
|
||||
|
||||
/**
|
||||
* regl renderer
|
||||
|
@ -32,8 +33,10 @@ import ReglMultiPassRenderer from './ReglMultiPassRenderer';
|
|||
@injectable()
|
||||
export default class ReglRendererService implements IRendererService {
|
||||
private gl: regl.Regl;
|
||||
private $container: HTMLDivElement | null;
|
||||
|
||||
public async init($container: HTMLDivElement): Promise<void> {
|
||||
this.$container = $container;
|
||||
// tslint:disable-next-line:typedef
|
||||
this.gl = await new Promise((resolve, reject) => {
|
||||
regl({
|
||||
|
@ -67,31 +70,43 @@ export default class ReglRendererService implements IRendererService {
|
|||
});
|
||||
}
|
||||
|
||||
public createModel = (options: IModelInitializationOptions): IModel => {
|
||||
return new ReglModel(this.gl, options);
|
||||
};
|
||||
public createModel = (options: IModelInitializationOptions): IModel =>
|
||||
new ReglModel(this.gl, options);
|
||||
|
||||
public createAttribute = (
|
||||
options: IAttributeInitializationOptions,
|
||||
): IAttribute => {
|
||||
return new ReglAttribute(this.gl, options);
|
||||
};
|
||||
): IAttribute => new ReglAttribute(this.gl, options);
|
||||
|
||||
public createBuffer = (options: IBufferInitializationOptions): IBuffer => {
|
||||
return new ReglBuffer(this.gl, options);
|
||||
};
|
||||
public createBuffer = (options: IBufferInitializationOptions): IBuffer =>
|
||||
new ReglBuffer(this.gl, options);
|
||||
|
||||
public createElements = (
|
||||
options: IElementsInitializationOptions,
|
||||
): IElements => {
|
||||
return new ReglElements(this.gl, options);
|
||||
): IElements => new ReglElements(this.gl, options);
|
||||
|
||||
public createTexture2D = (
|
||||
options: ITexture2DInitializationOptions,
|
||||
): ITexture2D => new ReglTexture2D(this.gl, options);
|
||||
|
||||
public createFramebuffer = (options: IFramebufferInitializationOptions) =>
|
||||
new ReglFramebuffer(this.gl, options);
|
||||
|
||||
public renderToFramebuffer = (
|
||||
framebuffer: IFramebuffer | null,
|
||||
drawCommands: () => void,
|
||||
) => {
|
||||
const useFramebuffer = this.gl({
|
||||
// since post-processor will swap read/write fbos, we must retrieve it dynamically
|
||||
framebuffer: framebuffer
|
||||
? () => (framebuffer as ReglFramebuffer).get()
|
||||
: null,
|
||||
});
|
||||
|
||||
// TODO: pass other options
|
||||
useFramebuffer({}, drawCommands);
|
||||
};
|
||||
|
||||
public createMultiPassRenderer = (layer: ILayer): IMultiPassRenderer => {
|
||||
return new ReglMultiPassRenderer(this.gl, layer);
|
||||
};
|
||||
|
||||
public clear(options: IClearOptions) {
|
||||
public clear = (options: IClearOptions) => {
|
||||
// @see https://github.com/regl-project/regl/blob/gh-pages/API.md#clear-the-draw-buffer
|
||||
const { color, depth, stencil, framebuffer = null } = options;
|
||||
const reglClearOptions: regl.ClearOptions = {
|
||||
|
@ -106,9 +121,9 @@ export default class ReglRendererService implements IRendererService {
|
|||
: (framebuffer as ReglFramebuffer).get();
|
||||
|
||||
this.gl.clear(reglClearOptions);
|
||||
}
|
||||
};
|
||||
|
||||
public viewport({
|
||||
public viewport = ({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
|
@ -118,17 +133,21 @@ export default class ReglRendererService implements IRendererService {
|
|||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}) {
|
||||
}) => {
|
||||
// use WebGL context directly
|
||||
// @see https://github.com/regl-project/regl/blob/gh-pages/API.md#unsafe-escape-hatch
|
||||
this.gl._gl.viewport(x, y, width, height);
|
||||
this.gl._refresh();
|
||||
}
|
||||
};
|
||||
|
||||
public getViewportSize() {
|
||||
public getViewportSize = () => {
|
||||
return {
|
||||
width: this.gl._gl.drawingBufferWidth,
|
||||
height: this.gl._gl.drawingBufferHeight,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
public getContainer = () => {
|
||||
return this.$container;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
import { Line } from '@l7/layers';
|
||||
import { Scene } from '@l7/scene';
|
||||
import * as React from 'react';
|
||||
|
||||
export default class Point3D extends React.Component {
|
||||
private scene: Scene;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.scene.destroy();
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const response = await fetch(
|
||||
'https://gw.alipayobjects.com/os/rmsportal/ZVfOvhVCzwBkISNsuKCc.json',
|
||||
);
|
||||
const testdata = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: [
|
||||
[91.58203125, 34.95799531086792],
|
||||
[96.767578125, 34.379712580462204],
|
||||
[99.228515625, 33.7243396617476],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const scene = new Scene({
|
||||
center: [102.602992, 23.107329],
|
||||
id: 'map',
|
||||
pitch: 0,
|
||||
type: 'mapbox',
|
||||
style: 'mapbox://styles/mapbox/dark-v9',
|
||||
zoom: 2,
|
||||
});
|
||||
const LineLayer = new Line({});
|
||||
|
||||
LineLayer.source(testdata)
|
||||
.size(5)
|
||||
.color('red')
|
||||
.shape('line')
|
||||
.size(10);
|
||||
scene.addLayer(LineLayer);
|
||||
// function run() {
|
||||
// scene.render();
|
||||
// requestAnimationFrame(run);
|
||||
// }
|
||||
// requestAnimationFrame(run);
|
||||
scene.render();
|
||||
this.scene = scene;
|
||||
console.log(LineLayer);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -83,6 +83,17 @@ export default class Mapbox extends React.Component {
|
|||
this.scene = scene;
|
||||
console.log(layer);
|
||||
/*** 运行时修改样式属性 ***/
|
||||
// const gui = new dat.GUI();
|
||||
// this.gui = gui;
|
||||
// const pointFolder = gui.addFolder('Polygon 样式属性');
|
||||
// pointFolder
|
||||
// .add(layer.styleOptions, 'opacity')
|
||||
// .onChange((opacity: number) => {
|
||||
// layer.style({
|
||||
// opacity,
|
||||
// });
|
||||
// scene.render();
|
||||
// });
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -2825,6 +2825,11 @@
|
|||
"@types/minimatch" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/hammerjs@^2.0.36":
|
||||
version "2.0.36"
|
||||
resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.36.tgz#17ce0a235e9ffbcdcdf5095646b374c2bf615a4c"
|
||||
integrity sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ==
|
||||
|
||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
|
||||
|
@ -7507,6 +7512,11 @@ gzip-size@5.1.1:
|
|||
duplexer "^0.1.1"
|
||||
pify "^4.0.1"
|
||||
|
||||
hammerjs@^2.0.8:
|
||||
version "2.0.8"
|
||||
resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1"
|
||||
integrity sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=
|
||||
|
||||
handle-thing@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754"
|
||||
|
|
Loading…
Reference in New Issue