mirror of
https://github.com/visgl/luma.gl.git
synced 2025-12-08 17:36:19 +00:00
273 lines
7.3 KiB
TypeScript
273 lines
7.3 KiB
TypeScript
import maplibregl, {CustomRenderMethodInput} from 'maplibre-gl';
|
|
import {Matrix4, radians} from '@math.gl/core';
|
|
import {UniformStore} from '@luma.gl/core';
|
|
import type {Buffer} from '@luma.gl/core';
|
|
import {Model} from '@luma.gl/engine';
|
|
import type {WebGLDevice} from '@luma.gl/webgl';
|
|
import {webgl2Adapter} from '@luma.gl/webgl';
|
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
|
|
export const title = 'External WebGL Context';
|
|
export const description = 'Attach luma.gl to a MapLibre-managed WebGL context.';
|
|
|
|
type AppUniforms = {
|
|
uModelViewProjection: Float32Array;
|
|
};
|
|
|
|
type ExternalWebGLContextHandle = {
|
|
destroy: () => void;
|
|
};
|
|
|
|
type ExternalWebGLContextOptions = {
|
|
container?: HTMLElement | null;
|
|
};
|
|
|
|
const POSITIONS = new Float32Array([
|
|
0.0, 0.15, 0.0, -0.1, -0.15, 0.0, 0.1, -0.15, 0.0, 0.0, 0.15, 0.0, 0.1, -0.15, 0.0, 0.0, -0.35,
|
|
0.0
|
|
]);
|
|
|
|
const COLORS = new Float32Array([
|
|
0.0, 0.6, 1.0, 0.0, 0.4, 0.8, 0.0, 0.8, 0.8, 0.0, 0.6, 1.0, 0.0, 0.8, 0.8, 0.0, 0.4, 0.8
|
|
]);
|
|
|
|
const WGSL_SHADER = /* WGSL */ `\
|
|
struct AppUniforms {
|
|
uModelViewProjection : mat4x4<f32>,
|
|
}
|
|
|
|
@group(0) @binding(0) var<uniform> app : AppUniforms;
|
|
|
|
struct VertexInput {
|
|
@location(0) positions : vec3<f32>,
|
|
@location(1) colors : vec3<f32>
|
|
}
|
|
|
|
struct VertexOutput {
|
|
@builtin(position) position : vec4<f32>,
|
|
@location(0) colors : vec3<f32>
|
|
}
|
|
|
|
@vertex
|
|
fn vertexMain(input : VertexInput) -> VertexOutput {
|
|
var output : VertexOutput;
|
|
output.position = app.uModelViewProjection * vec4<f32>(input.positions, 1.0);
|
|
output.colors = input.colors;
|
|
return output;
|
|
}
|
|
|
|
@fragment
|
|
fn fragmentMain(input : VertexOutput) -> @location(0) vec4<f32> {
|
|
return vec4<f32>(input.colors, 0.8);
|
|
}
|
|
`;
|
|
|
|
const VS_GLSL = /* glsl */ `\
|
|
#version 300 es
|
|
layout(location = 0) in vec3 positions;
|
|
layout(location = 1) in vec3 colors;
|
|
|
|
layout(std140) uniform app {
|
|
mat4 uModelViewProjection;
|
|
};
|
|
|
|
out vec3 vColor;
|
|
|
|
void main(void) {
|
|
gl_Position = uModelViewProjection * vec4(positions, 1.0);
|
|
vColor = colors;
|
|
}
|
|
`;
|
|
|
|
const FS_GLSL = /* glsl */ `\
|
|
#version 300 es
|
|
precision highp float;
|
|
|
|
in vec3 vColor;
|
|
|
|
out vec4 fragColor;
|
|
|
|
void main(void) {
|
|
fragColor = vec4(vColor, 0.8);
|
|
}
|
|
`;
|
|
|
|
export async function initializeExternalWebGLContext(
|
|
options: ExternalWebGLContextOptions = {}
|
|
): Promise<ExternalWebGLContextHandle> {
|
|
const container = options.container || document.body;
|
|
|
|
const uniformStore = new UniformStore<{app: AppUniforms}>({
|
|
app: {
|
|
uniformTypes: {
|
|
uModelViewProjection: 'mat4x4<f32>'
|
|
}
|
|
}
|
|
});
|
|
|
|
let device: WebGLDevice | null = null;
|
|
let model: Model | null = null;
|
|
let activeWebglContext: WebGL2RenderingContext | null = null;
|
|
let positionsBuffer: Buffer | null = null;
|
|
let colorsBuffer: Buffer | null = null;
|
|
|
|
const baseModelMatrix = new Matrix4();
|
|
const modelViewProjectionMatrix = new Matrix4();
|
|
const modelMatrix = new Matrix4();
|
|
const rotation = 0;
|
|
|
|
const maplibreMap = new maplibregl.Map({
|
|
container,
|
|
style: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
|
|
center: [-122.43, 37.77],
|
|
pitch: 60,
|
|
zoom: 12.5,
|
|
antialias: true
|
|
});
|
|
|
|
const resizeCanvasContext = (webglContext: WebGL2RenderingContext) => {
|
|
if (device) {
|
|
device.canvasContext.resize({
|
|
width: webglContext.drawingBufferWidth,
|
|
height: webglContext.drawingBufferHeight
|
|
});
|
|
}
|
|
};
|
|
|
|
const customLayer: maplibregl.CustomLayerInterface = {
|
|
id: 'luma-gl-overlay',
|
|
type: 'custom',
|
|
renderingMode: '3d',
|
|
onAdd: async (maplibreInstance, maplibreWebglContext) => {
|
|
if (!(maplibreWebglContext instanceof WebGL2RenderingContext)) {
|
|
throw new Error(
|
|
'MapLibre needs to provide a WebGL2RenderingContext to attach a luma.gl device.'
|
|
);
|
|
}
|
|
|
|
activeWebglContext = maplibreWebglContext;
|
|
device = await webgl2Adapter.attach(maplibreWebglContext, {
|
|
createCanvasContext: {autoResize: false}
|
|
});
|
|
|
|
resizeCanvasContext(maplibreWebglContext);
|
|
|
|
const mercator = maplibregl.MercatorCoordinate.fromLngLat(maplibreInstance.getCenter(), 250);
|
|
const meterScale = mercator.meterInMercatorCoordinateUnits();
|
|
const overlaySizeMeters = 800;
|
|
|
|
baseModelMatrix
|
|
.identity()
|
|
.translate([mercator.x, mercator.y, mercator.z])
|
|
.scale([
|
|
meterScale * overlaySizeMeters,
|
|
-meterScale * overlaySizeMeters,
|
|
meterScale * overlaySizeMeters
|
|
]);
|
|
|
|
positionsBuffer = device.createBuffer({data: POSITIONS});
|
|
colorsBuffer = device.createBuffer({data: COLORS});
|
|
|
|
model = new Model(device, {
|
|
id: 'maplibre-overlay-model',
|
|
source: WGSL_SHADER,
|
|
vs: VS_GLSL,
|
|
fs: FS_GLSL,
|
|
bufferLayout: [
|
|
{name: 'positions', format: 'float32x3'},
|
|
{name: 'colors', format: 'float32x3'}
|
|
],
|
|
attributes: {
|
|
positions: positionsBuffer,
|
|
colors: colorsBuffer
|
|
},
|
|
vertexCount: POSITIONS.length / 3,
|
|
bindings: {
|
|
app: uniformStore.getManagedUniformBuffer(device, 'app')
|
|
},
|
|
parameters: {
|
|
depthWriteEnabled: false,
|
|
depthCompare: 'always'
|
|
// blend: true,
|
|
// blendColor: [0, 0, 0, 0],
|
|
// blendEquation: 'add',
|
|
// blendFunc: {
|
|
// srcRGB: 'src-alpha',
|
|
// dstRGB: 'one-minus-src-alpha',
|
|
// srcAlpha: 'src-alpha',
|
|
// dstAlpha: 'one-minus-src-alpha'
|
|
// }
|
|
}
|
|
});
|
|
},
|
|
render: (maplibreWebglContext, customRenderInput: CustomRenderMethodInput) => {
|
|
if (!(maplibreWebglContext instanceof WebGL2RenderingContext) || !device || !model) {
|
|
return;
|
|
}
|
|
|
|
const {modelViewProjectionMatrix: matrix} = customRenderInput;
|
|
const maplibreModelViewProjectionMatrix = Array.from(matrix);
|
|
if (maplibreModelViewProjectionMatrix.some(value => !Number.isFinite(value))) {
|
|
throw new Error('Invalid values in modelViewProjectionMatrix');
|
|
}
|
|
|
|
activeWebglContext = maplibreWebglContext;
|
|
resizeCanvasContext(maplibreWebglContext);
|
|
|
|
// rotation += 0.01
|
|
// modelMatrix.copy(baseModelMatrix).rotateX(radians(50)).rotateZ(rotation)
|
|
// modelViewProjectionMatrix
|
|
// .fromArray(maplibreModelViewProjectionMatrix)
|
|
// .multiplyRight(modelMatrix)
|
|
|
|
uniformStore.setUniforms({
|
|
app: {
|
|
uModelViewProjection: modelViewProjectionMatrix.toFloat32Array()
|
|
}
|
|
});
|
|
uniformStore.updateUniformBuffers();
|
|
|
|
const renderPass = device.beginRenderPass({
|
|
clearColor: false,
|
|
clearDepth: 1.0
|
|
});
|
|
model.draw(renderPass);
|
|
renderPass.end();
|
|
|
|
maplibreMap.triggerRepaint();
|
|
}
|
|
};
|
|
|
|
const resizeHandler = () => {
|
|
if (activeWebglContext) {
|
|
resizeCanvasContext(activeWebglContext);
|
|
}
|
|
};
|
|
|
|
maplibreMap.on('load', () => {
|
|
maplibreMap.addLayer(customLayer);
|
|
resizeHandler();
|
|
});
|
|
maplibreMap.on('resize', resizeHandler);
|
|
|
|
return {
|
|
destroy: () => {
|
|
maplibreMap.off('resize', resizeHandler);
|
|
|
|
if (maplibreMap.getLayer(customLayer.id)) {
|
|
maplibreMap.removeLayer(customLayer.id);
|
|
}
|
|
maplibreMap.remove();
|
|
|
|
positionsBuffer?.destroy();
|
|
colorsBuffer?.destroy();
|
|
model?.destroy();
|
|
uniformStore.destroy();
|
|
device?.destroy();
|
|
}
|
|
};
|
|
}
|
|
|
|
export type {ExternalWebGLContextHandle, ExternalWebGLContextOptions};
|
|
export default initializeExternalWebGLContext;
|