From 23595672c195f30020ffa46dc877cc84b3d89a0f Mon Sep 17 00:00:00 2001 From: YiQianYao <42212176+2912401452@users.noreply.github.com> Date: Fri, 25 Mar 2022 17:43:16 +0800 Subject: [PATCH] Shihui (#1019) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: planeGeometry 支持地形(地形贴图偏移) * style: lint style * feat: 增加 terrain clip * style: lint sytle * style: lint style --- packages/layers/src/Geometry/models/plane.ts | 230 +++++++++++++----- .../src/Geometry/shaders/plane_frag.glsl | 3 +- .../src/Geometry/shaders/plane_vert.glsl | 15 +- packages/layers/src/core/interface.ts | 4 + .../layers/src/plugins/DataMappingPlugin.ts | 4 +- stories/Object/components/plane.tsx | 25 +- stories/Object/components/planeTerrain.tsx | 81 ++++++ stories/Object/map.stories.tsx | 4 +- 8 files changed, 279 insertions(+), 87 deletions(-) create mode 100644 stories/Object/components/planeTerrain.tsx diff --git a/packages/layers/src/Geometry/models/plane.ts b/packages/layers/src/Geometry/models/plane.ts index 88cd2f6846..1afb6578b4 100644 --- a/packages/layers/src/Geometry/models/plane.ts +++ b/packages/layers/src/Geometry/models/plane.ts @@ -5,6 +5,7 @@ import { IModelUniform, ITexture2D, } from '@antv/l7-core'; +import { Version } from '@antv/l7-maps'; import { getMask, isMini } from '@antv/l7-utils'; // import { mat4, vec3 } from 'gl-matrix'; import BaseModel from '../../core/BaseModel'; @@ -12,61 +13,72 @@ import { IGeometryLayerStyleOptions } from '../../core/interface'; import planeFrag from '../shaders/plane_frag.glsl'; import planeVert from '../shaders/plane_vert.glsl'; -function initPlane( - width = 1, - height = 1, - widthSegments = 1, - heightSegments = 1, - lng = 120, - lat = 30, -) { - // https://github.com/mrdoob/three.js/blob/dev/src/geometries/PlaneGeometry.js - const widthHalf = width / 2; - const heightHalf = height / 2; - - const gridX = Math.floor(widthSegments); - const gridY = Math.floor(heightSegments); - - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; - - const segmentWidth = width / gridX; - const segmentHeight = height / gridY; - - const indices = []; - const positions = []; - - for (let iy = 0; iy < gridY1; iy++) { - const y = iy * segmentHeight - heightHalf; - - for (let ix = 0; ix < gridX1; ix++) { - const x = ix * segmentWidth - widthHalf; - - positions.push(x + lng, -y + lat, 0); - - positions.push(ix / gridX); - positions.push(1 - iy / gridY); - } - } - - for (let iy = 0; iy < gridY; iy++) { - for (let ix = 0; ix < gridX; ix++) { - const a = ix + gridX1 * iy; - const b = ix + gridX1 * (iy + 1); - const c = ix + 1 + gridX1 * (iy + 1); - const d = ix + 1 + gridX1 * iy; - - indices.push(a, b, d); - indices.push(b, c, d); - } - } - - return { indices, positions }; -} - export default class PlaneModel extends BaseModel { protected texture: ITexture2D; protected mapTexture: string | undefined; + protected positions: number[]; + protected indices: number[]; + + public initPlane( + width = 1, + height = 1, + widthSegments = 1, + heightSegments = 1, + lng = 120, + lat = 30, + ) { + // https://github.com/mrdoob/three.js/blob/dev/src/geometries/PlaneGeometry.js + const widthHalf = width / 2; + const heightHalf = height / 2; + + const gridX = Math.floor(widthSegments); + const gridY = Math.floor(heightSegments); + + const gridX1 = gridX + 1; + const gridY1 = gridY + 1; + + const segmentWidth = width / gridX; + const segmentHeight = height / gridY; + + const indices = []; + const positions = []; + + for (let iy = 0; iy < gridY1; iy++) { + const y = iy * segmentHeight - heightHalf; + + for (let ix = 0; ix < gridX1; ix++) { + const x = ix * segmentWidth - widthHalf; + if (this.mapService.version === Version['GAODE2.x']) { + // @ts-ignore + const [a, b] = this.mapService.lngLatToCoord([x + lng, -y + lat]) as [ + number, + number, + ]; + positions.push(a, b, 0); + } else { + positions.push(x + lng, -y + lat, 0); + } + + positions.push(ix / gridX); + positions.push(1 - iy / gridY); + } + } + + for (let iy = 0; iy < gridY; iy++) { + for (let ix = 0; ix < gridX; ix++) { + const a = ix + gridX1 * iy; + const b = ix + gridX1 * (iy + 1); + const c = ix + 1 + gridX1 * (iy + 1); + const d = ix + 1 + gridX1 * iy; + + indices.push(a, b, d); + indices.push(b, c, d); + } + } + + return { indices, positions }; + } + public planeGeometryTriangulation = () => { const { width = 1, @@ -74,25 +86,44 @@ export default class PlaneModel extends BaseModel { widthSegments = 1, heightSegments = 1, center = [120, 30], + terrainTexture, } = this.layer.getLayerConfig() as IGeometryLayerStyleOptions; - const { indices, positions } = initPlane( + + const { indices, positions } = this.initPlane( width, height, widthSegments, heightSegments, ...center, ); + this.positions = positions; + this.indices = indices; + + if (terrainTexture) { + // 存在地形贴图的时候会根据地形贴图对顶点进行偏移 + this.loadTerrainTexture(); + } + return { vertices: positions, indices, size: 5, }; }; + public planeGeometryUpdateTriangulation = () => { + return { + vertices: this.positions, + indices: this.indices, + size: 5, + }; + }; public getUninforms(): IModelUniform { const { opacity, mapTexture, + terrainClipHeight = 0, + terrainTexture, } = this.layer.getLayerConfig() as IGeometryLayerStyleOptions; if (this.mapTexture !== mapTexture) { this.mapTexture = mapTexture; @@ -102,7 +133,7 @@ export default class PlaneModel extends BaseModel { return { u_opacity: opacity || 1, u_mapFlag: mapTexture ? 1 : 0, - + u_terrainClipHeight: terrainTexture ? terrainClipHeight : -1, u_texture: this.texture, // u_ModelMatrix: mat4.translate(mat4.create(), mat4.create(), [1, 0, 0]) // u_ModelMatrix: mat4.rotateZ(mat4.create(), mat4.create(), 10) @@ -111,17 +142,6 @@ export default class PlaneModel extends BaseModel { }; } - // public rotateZ(): mat4 { - // const res = mat4.create() - // const roZero = mat4.translate(mat4.create(), mat4.create(), [-120, 0, -30]) - // const rotate = mat4.rotateZ(mat4.create(), mat4.create(), 10) - // const roOrigin = mat4.translate(mat4.create(), mat4.create(), [120, 0, 30]) - // mat4.multiply(res, res, roZero) - // mat4.multiply(res, res, rotate) - // mat4.multiply(res, res, roOrigin) - // return res - // } - public clearModels(): void { this.texture.destroy(); } @@ -149,13 +169,89 @@ export default class PlaneModel extends BaseModel { fragmentShader: planeFrag, triangulation: this.planeGeometryTriangulation, primitive: gl.TRIANGLES, - // primitive: gl.POINTS, - depth: { enable: false }, + depth: { enable: true }, blend: this.getBlend(), stencil: getMask(mask, maskInside), }), ]; } + + public getImageData(img: HTMLImageElement) { + const canvas: HTMLCanvasElement = document.createElement('canvas'); + const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; + const { width, height } = img; + canvas.width = width; + canvas.height = height; + + ctx.drawImage(img, 0, 0, width, height); + const imageData = ctx.getImageData(0, 0, width, height); + + return imageData; + } + + /** + * load terrain texture & offset attribute z + */ + public loadTerrainTexture(): void { + const { + mask = false, + maskInside = true, + widthSegments = 1, + heightSegments = 1, + terrainTexture, + rgb2height = (r: number, g: number, b: number) => r + g + b, + } = this.layer.getLayerConfig() as IGeometryLayerStyleOptions; + const terrainImage = new Image(); + terrainImage.crossOrigin = 'anonymous'; + terrainImage.onload = () => { + const imgWidth = terrainImage.width; + const imgHeight = terrainImage.height; + + const imageData = this.getImageData(terrainImage).data; + + const gridX = Math.floor(widthSegments); + const gridY = Math.floor(heightSegments); + + const gridX1 = gridX + 1; + const gridY1 = gridY + 1; + + const widthStep = imgWidth / gridX; + const heihgtStep = imgHeight / gridY; + + for (let iy = 0; iy < gridY1; iy++) { + const imgIndexY = Math.floor(iy * heihgtStep); + const imgLen = imgIndexY * imgWidth; + + for (let ix = 0; ix < gridX1; ix++) { + const imgIndexX = Math.floor(ix * widthStep); + const imgDataIndex = (imgLen + imgIndexX) * 4; + + const r = imageData[imgDataIndex]; + const g = imageData[imgDataIndex + 1]; + const b = imageData[imgDataIndex + 2]; + + const z = (iy * gridX1 + ix) * 5 + 2; + this.positions[z] = rgb2height(r, g, b); + } + } + + this.layer.models = [ + this.layer.buildLayerModel({ + moduleName: 'geometry_plane', + vertexShader: planeVert, + fragmentShader: planeFrag, + triangulation: this.planeGeometryUpdateTriangulation, + primitive: gl.TRIANGLES, + depth: { enable: true }, + blend: this.getBlend(), + stencil: getMask(mask, maskInside), + }), + ]; + this.layerService.renderLayers(); + }; + terrainImage.src = terrainTexture as string; + } + public buildModels() { return this.initModels(); } diff --git a/packages/layers/src/Geometry/shaders/plane_frag.glsl b/packages/layers/src/Geometry/shaders/plane_frag.glsl index 4b89fa46bf..756c3bd3aa 100644 --- a/packages/layers/src/Geometry/shaders/plane_frag.glsl +++ b/packages/layers/src/Geometry/shaders/plane_frag.glsl @@ -5,6 +5,7 @@ uniform float u_opacity; varying vec3 v_Color; varying vec2 v_uv; +varying float v_clip; #pragma include "picking" void main() { @@ -12,10 +13,10 @@ void main() { if(u_mapFlag > 0.0) { gl_FragColor = texture2D(u_texture, vec2(v_uv.x, 1.0 - v_uv.y)); gl_FragColor.a *= u_opacity; - } else { // gl_FragColor = vec4(v_uv, 0.0, u_opacity); gl_FragColor = vec4(v_Color, u_opacity); } + gl_FragColor.a *= v_clip; gl_FragColor = filterColor(gl_FragColor); } diff --git a/packages/layers/src/Geometry/shaders/plane_vert.glsl b/packages/layers/src/Geometry/shaders/plane_vert.glsl index 9b94e8939b..07803993d2 100644 --- a/packages/layers/src/Geometry/shaders/plane_vert.glsl +++ b/packages/layers/src/Geometry/shaders/plane_vert.glsl @@ -2,6 +2,8 @@ precision highp float; uniform mat4 u_ModelMatrix; uniform mat4 u_Mvp; +uniform float u_opacity; +uniform float u_terrainClipHeight; attribute vec3 a_Position; attribute vec2 a_Uv; @@ -9,6 +11,7 @@ attribute vec3 a_Color; varying vec3 v_Color; varying vec2 v_uv; +varying float v_clip; #pragma include "projection" #pragma include "picking" @@ -17,6 +20,12 @@ void main() { v_uv = a_Uv; vec4 project_pos = project_position(vec4(a_Position, 1.0)); + + v_clip = 1.0; + if(a_Position.z < u_terrainClipHeight) { + v_clip = 0.0; + } + // gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy,0., 1.0)); // float x = 1.0; @@ -35,12 +44,10 @@ void main() { // ); if(u_CoordinateSystem == COORDINATE_SYSTEM_P20_2) { // gaode2.x - gl_Position = u_Mvp * (vec4(project_pos.xy, 0., 1.0)); + gl_Position = u_Mvp * (vec4(project_pos.xy, a_Position.z, 1.0)); } else { - gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy, 0., 1.0)); + gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy, a_Position.z, 1.0)); } setPickingColor(a_PickingColor); - - gl_PointSize = 10.0; } diff --git a/packages/layers/src/core/interface.ts b/packages/layers/src/core/interface.ts index 8b822c9615..6528e2900f 100644 --- a/packages/layers/src/core/interface.ts +++ b/packages/layers/src/core/interface.ts @@ -106,6 +106,7 @@ export interface IGeometryLayerStyleOptions { maskInside?: boolean; mapTexture?: string; + terrainTexture?: string; // planeGeometry center?: [number, number]; @@ -114,6 +115,9 @@ export interface IGeometryLayerStyleOptions { widthSegments?: number; heightSegments?: number; + + terrainClipHeight?: number; + rgb2height?: (r: number, g: number, b: number) => number; } export enum CanvasUpdateType { diff --git a/packages/layers/src/plugins/DataMappingPlugin.ts b/packages/layers/src/plugins/DataMappingPlugin.ts index 5c0377c652..4157458a51 100644 --- a/packages/layers/src/plugins/DataMappingPlugin.ts +++ b/packages/layers/src/plugins/DataMappingPlugin.ts @@ -180,7 +180,7 @@ export default class DataMappingPlugin implements ILayerPlugin { // TODO: 避免经纬度被重复计算导致坐标位置偏移 .filter((d) => !d.originCoordinates) .map((d) => { - d.version = 'GAODE2.x'; + d.version = Version['GAODE2.x']; // @ts-ignore d.originCoordinates = cloneDeep(d.coordinates); // 为了兼容高德1.x 需要保存一份原始的经纬度坐标数据(许多上层逻辑依赖经纬度数据) // @ts-ignore @@ -193,7 +193,7 @@ export default class DataMappingPlugin implements ILayerPlugin { // TODO: 避免经纬度被重复计算导致坐标位置偏移 .filter((d) => !d.originCoordinates) .map((d) => { - d.version = 'GAODE2.x'; + d.version = Version['GAODE2.x']; // @ts-ignore d.originCoordinates = cloneDeep(d.coordinates); // 为了兼容高德1.x 需要保存一份原始的经纬度坐标数据(许多上层逻辑依赖经纬度数据) // @ts-ignore diff --git a/stories/Object/components/plane.tsx b/stories/Object/components/plane.tsx index 267ed4de20..8765d77f04 100644 --- a/stories/Object/components/plane.tsx +++ b/stories/Object/components/plane.tsx @@ -1,5 +1,5 @@ import { GeometryLayer, Scene, IMapService } from '@antv/l7'; -import { GaodeMap, Mapbox } from '@antv/l7-maps'; +import { GaodeMap, GaodeMapV2, Mapbox } from '@antv/l7-maps'; import * as React from 'react'; export default class Demo extends React.Component { @@ -13,7 +13,8 @@ export default class Demo extends React.Component { public async componentDidMount() { const scene = new Scene({ id: 'map', - map: new GaodeMap({ + // map: new GaodeMap({ + map: new GaodeMapV2({ // map: new Mapbox({ pitch: 0, // style: 'dark', @@ -49,16 +50,16 @@ export default class Demo extends React.Component { scene.on('loaded', () => { scene.addLayer(layer); - setTimeout(() => { - // layer.style({ - // mapTexture: "" - // }) - layer.style({ - mapTexture: - 'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*GJhASbfQTK8AAAAAAAAAAAAAARQnAQ', - }); - scene.render(); - }, 2000); + // setTimeout(() => { + // // layer.style({ + // // mapTexture: "" + // // }) + // layer.style({ + // mapTexture: + // 'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*GJhASbfQTK8AAAAAAAAAAAAAARQnAQ', + // }); + // scene.render(); + // }, 2000); }); } diff --git a/stories/Object/components/planeTerrain.tsx b/stories/Object/components/planeTerrain.tsx new file mode 100644 index 0000000000..03ff518099 --- /dev/null +++ b/stories/Object/components/planeTerrain.tsx @@ -0,0 +1,81 @@ +import { GeometryLayer, Scene, IMapService } from '@antv/l7'; +import { GaodeMap, Mapbox, GaodeMapV2 } from '@antv/l7-maps'; +import * as React from 'react'; + +export default class Demo extends React.Component { + // @ts-ignore + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new GaodeMap({ + // map: new GaodeMapV2({ + // map: new Mapbox({ + pitch: 60, + center: [120.1025, 30.2594], + rotation: 220, + zoom: 14, + }), + }); + this.scene = scene; + + let layer = new GeometryLayer() + .style({ + width: 0.074, + height: 0.061, + center: [120.1025, 30.2594], + widthSegments: 200, + heightSegments: 200, + terrainClipHeight: 1, + mapTexture: + 'https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*gA0NRbuOF5cAAAAAAAAAAAAAARQnAQ', + terrainTexture: + 'https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*eYFaRYlnnOUAAAAAAAAAAAAAARQnAQ', + rgb2height: (r: number, g: number, b: number) => { + let h = + -10000.0 + + (r * 255.0 * 256.0 * 256.0 + g * 255.0 * 256.0 + b * 255.0) * 0.1; + h = h / 20 - 127600; + h = Math.max(0, h); + return h; + }, + }) + .color('#ff0'); + + scene.on('loaded', () => { + scene.addLayer(layer); + // setTimeout(() => { + // // layer.style({ + // // mapTexture: "" + // // }) + // layer.style({ + // mapTexture: + // 'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*GJhASbfQTK8AAAAAAAAAAAAAARQnAQ', + // }); + // scene.render(); + // }, 2000); + }); + } + + public render() { + return ( + <> +
+ > + ); + } +} diff --git a/stories/Object/map.stories.tsx b/stories/Object/map.stories.tsx index 68ad345a15..e848784777 100644 --- a/stories/Object/map.stories.tsx +++ b/stories/Object/map.stories.tsx @@ -6,6 +6,7 @@ import Taifong from './components/taifeng' import Radar from './components/radar'; import CanvasDemo from './components/canvas'; import Plane from './components/plane'; +import PlaneTerrain from './components/planeTerrain'; storiesOf('Object', module) .add('water', () =>