diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index eb77dd9d28..0000000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 79052bd45d..a5542c8ad0 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,5 @@ jspm_packages/ # End of https://www.gitignore.io/api/node lib/ + +.DS_Store diff --git a/dev-docs/.DS_Store b/dev-docs/.DS_Store deleted file mode 100644 index eaea5babef..0000000000 Binary files a/dev-docs/.DS_Store and /dev/null differ diff --git a/dev-docs/PixelPickingEngine.md b/dev-docs/PixelPickingEngine.md new file mode 100644 index 0000000000..f4d8504c36 --- /dev/null +++ b/dev-docs/PixelPickingEngine.md @@ -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」 \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index 2813d705f4..ca6fb3029d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -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" } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4c765cb654..02422c05d4 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -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, diff --git a/packages/core/src/inversify.config.ts b/packages/core/src/inversify.config.ts index f76e15dc7e..c6b2509d12 100644 --- a/packages/core/src/inversify.config.ts +++ b/packages/core/src/inversify.config.ts @@ -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(TYPES.ILogService) .to(LogService) .inSingletonScope(); +container + .bind(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); diff --git a/packages/core/src/services/interaction/IInteractionService.ts b/packages/core/src/services/interaction/IInteractionService.ts new file mode 100644 index 0000000000..d1b20a01e9 --- /dev/null +++ b/packages/core/src/services/interaction/IInteractionService.ts @@ -0,0 +1,4 @@ +export interface IInteractionService { + init(): void; + destroy(): void; +} diff --git a/packages/core/src/services/interaction/InteractionService.ts b/packages/core/src/services/interaction/InteractionService.ts new file mode 100644 index 0000000000..b8fd4435f2 --- /dev/null +++ b/packages/core/src/services/interaction/InteractionService.ts @@ -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 + // }); + // } + }; +} diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index 20d690ea17..3c6cec27c1 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -109,6 +109,7 @@ export interface ILayerPlugin { */ export interface ILayerInitializationOptions { enableMultiPassRenderer: boolean; + enablePicking: boolean; passes: Array; } diff --git a/packages/core/src/services/renderer/IMultiPassRenderer.ts b/packages/core/src/services/renderer/IMultiPassRenderer.ts index e8d7d8a166..7481ed3820 100644 --- a/packages/core/src/services/renderer/IMultiPassRenderer.ts +++ b/packages/core/src/services/renderer/IMultiPassRenderer.ts @@ -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; diff --git a/packages/core/src/services/renderer/IRendererService.ts b/packages/core/src/services/renderer/IRendererService.ts index 9f2a7b3118..b0b15786f3 100644 --- a/packages/core/src/services/renderer/IRendererService.ts +++ b/packages/core/src/services/renderer/IRendererService.ts @@ -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; } diff --git a/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts b/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts index 40c8f1896f..952400bfa1 100644 --- a/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts +++ b/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts @@ -23,7 +23,7 @@ export default class BasePostProcessingPass protected readonly shaderModule: IShaderModuleService; @lazyInject(TYPES.IRendererService) - protected readonly renderer: IRendererService; + protected readonly rendererService: IRendererService; protected config: Partial | undefined; @@ -51,7 +51,7 @@ export default class BasePostProcessingPass } 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 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() { diff --git a/packages/renderer/src/regl/ReglMultiPassRenderer.ts b/packages/core/src/services/renderer/passes/MultiPassRenderer.ts similarity index 73% rename from packages/renderer/src/regl/ReglMultiPassRenderer.ts rename to packages/core/src/services/renderer/passes/MultiPassRenderer.ts index d3b55dfbc8..e28b046f64 100644 --- a/packages/renderer/src/regl/ReglMultiPassRenderer.ts +++ b/packages/core/src/services/renderer/passes/MultiPassRenderer.ts @@ -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) { diff --git a/packages/core/src/services/renderer/passes/PickingPass.ts b/packages/core/src/services/renderer/passes/PickingPass.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/src/services/renderer/passes/PixelPickingPass.ts b/packages/core/src/services/renderer/passes/PixelPickingPass.ts new file mode 100644 index 0000000000..bbf7f7286e --- /dev/null +++ b/packages/core/src/services/renderer/passes/PixelPickingPass.ts @@ -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); + }); + } +} diff --git a/packages/renderer/src/regl/ReglPostProcessor.ts b/packages/core/src/services/renderer/passes/PostProcessor.ts similarity index 50% rename from packages/renderer/src/regl/ReglPostProcessor.ts rename to packages/core/src/services/renderer/passes/PostProcessor.ts index 84068685fa..f93e071175 100644 --- a/packages/renderer/src/regl/ReglPostProcessor.ts +++ b/packages/core/src/services/renderer/passes/PostProcessor.ts @@ -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]; diff --git a/packages/core/src/services/renderer/passes/RenderPass.ts b/packages/core/src/services/renderer/passes/RenderPass.ts index 1ca8e43613..4a4b770cb7 100644 --- a/packages/core/src/services/renderer/passes/RenderPass.ts +++ b/packages/core/src/services/renderer/passes/RenderPass.ts @@ -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(); diff --git a/packages/core/src/services/renderer/passes/post-processing/BlurHPass.ts b/packages/core/src/services/renderer/passes/post-processing/BlurHPass.ts index 52856499b7..d999a9927e 100644 --- a/packages/core/src/services/renderer/passes/post-processing/BlurHPass.ts +++ b/packages/core/src/services/renderer/passes/post-processing/BlurHPass.ts @@ -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, diff --git a/packages/core/src/services/renderer/passes/post-processing/BlurVPass.ts b/packages/core/src/services/renderer/passes/post-processing/BlurVPass.ts index 584d4b50e7..b707fbed39 100644 --- a/packages/core/src/services/renderer/passes/post-processing/BlurVPass.ts +++ b/packages/core/src/services/renderer/passes/post-processing/BlurVPass.ts @@ -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, diff --git a/packages/core/src/services/scene/SceneService.ts b/packages/core/src/services/scene/SceneService.ts index 0ae413816a..4a7206f067 100644 --- a/packages/core/src/services/scene/SceneService.ts +++ b/packages/core/src/services/scene/SceneService.ts @@ -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); } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 6934825731..b6af81e25e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -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'), diff --git a/packages/layers/src/line/index.ts b/packages/layers/src/line/index.ts new file mode 100644 index 0000000000..817200e646 --- /dev/null +++ b/packages/layers/src/line/index.ts @@ -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, + }), + }), + ); + } +} diff --git a/packages/layers/src/line/shaders/line_frag.glsl b/packages/layers/src/line/shaders/line_frag.glsl new file mode 100644 index 0000000000..d2b43d6f18 --- /dev/null +++ b/packages/layers/src/line/shaders/line_frag.glsl @@ -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; +} \ No newline at end of file diff --git a/packages/layers/src/line/shaders/line_vert.glsl b/packages/layers/src/line/shaders/line_vert.glsl new file mode 100644 index 0000000000..fd3bc6d43e --- /dev/null +++ b/packages/layers/src/line/shaders/line_vert.glsl @@ -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)); +} diff --git a/packages/layers/src/plugins/MultiPassRendererPlugin.ts b/packages/layers/src/plugins/MultiPassRendererPlugin.ts index b83333aee4..fac1623412 100644 --- a/packages/layers/src/plugins/MultiPassRendererPlugin.ts +++ b/packages/layers/src/plugins/MultiPassRendererPlugin.ts @@ -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, +) { + 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, ) { - 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()); diff --git a/packages/layers/src/utils/normal2.ts b/packages/layers/src/utils/normal2.ts new file mode 100644 index 0000000000..8689a286ba --- /dev/null +++ b/packages/layers/src/utils/normal2.ts @@ -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, + }; +} diff --git a/packages/renderer/src/regl/ReglRenderbuffer.ts b/packages/renderer/src/regl/ReglRenderbuffer.ts index 7e68fc5d1b..0040cd9bd9 100644 --- a/packages/renderer/src/regl/ReglRenderbuffer.ts +++ b/packages/renderer/src/regl/ReglRenderbuffer.ts @@ -1,7 +1,4 @@ -import { - IRenderbuffer, - IRenderbufferInitializationOptions, -} from '@l7/core'; +import { IRenderbuffer, IRenderbufferInitializationOptions } from '@l7/core'; import regl from 'regl'; import { formatMap } from './constants'; diff --git a/packages/renderer/src/regl/index.ts b/packages/renderer/src/regl/index.ts index a22b73c608..a03f46baf4 100644 --- a/packages/renderer/src/regl/index.ts +++ b/packages/renderer/src/regl/index.ts @@ -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 { + 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; + }; } diff --git a/stories/MapAdaptor/components/Line.tsx b/stories/MapAdaptor/components/Line.tsx new file mode 100644 index 0000000000..44ca992fed --- /dev/null +++ b/stories/MapAdaptor/components/Line.tsx @@ -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 ( +
+ ); + } +} diff --git a/stories/MapAdaptor/components/Polygon.tsx b/stories/MapAdaptor/components/Polygon.tsx index 9078d45975..d77e2ee585 100644 --- a/stories/MapAdaptor/components/Polygon.tsx +++ b/stories/MapAdaptor/components/Polygon.tsx @@ -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() { diff --git a/yarn.lock b/yarn.lock index 9ccbb3e960..58dc8f9f57 100644 --- a/yarn.lock +++ b/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"