mirror of
https://github.com/visgl/luma.gl.git
synced 2025-12-08 17:36:19 +00:00
317 lines
9.4 KiB
TypeScript
317 lines
9.4 KiB
TypeScript
// luma.gl
|
|
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) vis.gl contributors
|
|
|
|
import {Buffer, Framebuffer} from '@luma.gl/core';
|
|
import {
|
|
AnimationLoopTemplate,
|
|
AnimationProps,
|
|
Model,
|
|
BufferTransform,
|
|
Swap,
|
|
makeRandomGenerator
|
|
} from '@luma.gl/engine';
|
|
import {picking} from '@luma.gl/shadertools';
|
|
|
|
// Ensure repeatable rendertests
|
|
const random = makeRandomGenerator();
|
|
|
|
// We simulate the wandering of agents using transform feedback in this vertex shader
|
|
// The simulation goes like this:
|
|
// Assume there's a circle in front of the agent whose radius is WANDER_CIRCLE_R
|
|
// the origin of which has a offset to the agent's pivot point, which is WANDER_CIRCLE_OFFSET
|
|
// Each frame we pick a random point on this circle
|
|
// And the agent moves MOVE_DELTA toward this target point
|
|
// We also record the rotation facing this target point, so it will be the base rotation
|
|
// for our next frame, which means the WANDER_CIRCLE_OFFSET vector will be on this direction
|
|
// Thus we fake a smooth wandering behavior
|
|
|
|
const COMPUTE_VS = /* glsl */ `\
|
|
#version 300 es
|
|
#define OFFSET_LOCATION 0
|
|
#define ROTATION_LOCATION 1
|
|
|
|
#define M_2PI 6.28318530718
|
|
|
|
#define MAP_HALF_LENGTH 1.01
|
|
#define WANDER_CIRCLE_R 0.01
|
|
#define WANDER_CIRCLE_OFFSET 0.04
|
|
#define MOVE_DELTA 0.001
|
|
precision highp float;
|
|
precision highp int;
|
|
|
|
uniform appUniforms{
|
|
float time;
|
|
} app;
|
|
|
|
layout(location = OFFSET_LOCATION) in vec2 oldPositions;
|
|
layout(location = ROTATION_LOCATION) in float oldRotations;
|
|
|
|
out vec2 newOffsets;
|
|
out float newRotations;
|
|
|
|
float rand(vec2 co)
|
|
{
|
|
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
|
|
}
|
|
|
|
void main()
|
|
{
|
|
float theta = M_2PI * rand(vec2(app.time, oldRotations + oldPositions.x + oldPositions.y));
|
|
float cos_r = cos(oldRotations);
|
|
float sin_r = sin(oldRotations);
|
|
mat2 rot = mat2(
|
|
cos_r, sin_r,
|
|
-sin_r, cos_r
|
|
);
|
|
|
|
vec2 p = WANDER_CIRCLE_R * vec2(cos(theta), sin(theta)) + vec2(WANDER_CIRCLE_OFFSET, 0.0);
|
|
|
|
vec2 move = normalize(rot * p);
|
|
newRotations = atan(move.y, move.x);
|
|
newOffsets = oldPositions + MOVE_DELTA * move;
|
|
|
|
// wrapping at edges
|
|
newOffsets = vec2 (
|
|
newOffsets.x > MAP_HALF_LENGTH ? - MAP_HALF_LENGTH :
|
|
( newOffsets.x < - MAP_HALF_LENGTH ? MAP_HALF_LENGTH : newOffsets.x ) ,
|
|
newOffsets.y > MAP_HALF_LENGTH ? - MAP_HALF_LENGTH :
|
|
( newOffsets.y < - MAP_HALF_LENGTH ? MAP_HALF_LENGTH : newOffsets.y )
|
|
);
|
|
|
|
gl_Position = vec4(newOffsets, 0.0, 1.0);
|
|
}
|
|
`;
|
|
|
|
const DRAW_VS = /* glsl */ `\
|
|
#version 300 es
|
|
#define OFFSET_LOCATION 0
|
|
#define ROTATION_LOCATION 1
|
|
#define POSITION_LOCATION 2
|
|
#define COLOR_LOCATION 3
|
|
precision highp float;
|
|
precision highp int;
|
|
layout(location = POSITION_LOCATION) in vec2 positions;
|
|
layout(location = ROTATION_LOCATION) in float instanceRotations;
|
|
layout(location = OFFSET_LOCATION) in vec2 instancePositions;
|
|
layout(location = COLOR_LOCATION) in vec3 instanceColors;
|
|
in vec2 instancePickingColors;
|
|
out vec3 vColor;
|
|
void main()
|
|
{
|
|
vColor = instanceColors;
|
|
|
|
float cos_r = cos(instanceRotations);
|
|
float sin_r = sin(instanceRotations);
|
|
mat2 rot = mat2(
|
|
cos_r, sin_r,
|
|
-sin_r, cos_r
|
|
);
|
|
gl_Position = vec4(rot * positions + instancePositions, 0.0, 1.0);
|
|
picking_setPickingColor(vec3(0., instancePickingColors));
|
|
}
|
|
`;
|
|
|
|
const DRAW_FS = /* glsl */ `\
|
|
#version 300 es
|
|
#define ALPHA 0.9
|
|
precision highp float;
|
|
precision highp int;
|
|
in vec3 vColor;
|
|
out vec4 fragColor;
|
|
void main()
|
|
{
|
|
fragColor = vec4(vColor * ALPHA, ALPHA);
|
|
fragColor = picking_filterColor(fragColor);
|
|
}
|
|
`;
|
|
|
|
const NUM_INSTANCES = 1000;
|
|
|
|
export default class AppAnimationLoopTemplate extends AnimationLoopTemplate {
|
|
static info = `
|
|
<p>
|
|
Instanced triangles animated on the GPU using a luma.gl <code>BufferTransform</code> object.
|
|
|
|
This is a port of an example from
|
|
<a href="https://github.com/WebGLSamples/WebGL2Samples/blob/master/samples/transform_feedback_instanced.html">
|
|
WebGL2Samples
|
|
</a>
|
|
`;
|
|
|
|
// Geometry of each object (a triangle)
|
|
positionBuffer: Buffer;
|
|
|
|
// Positions, rotations, colors and picking colors for each object
|
|
instancePositionBuffers: Swap<Buffer>;
|
|
instanceRotationBuffers: Swap<Buffer>;
|
|
|
|
instanceColorBuffer: Buffer;
|
|
instancePickingColorBuffer: Buffer;
|
|
|
|
renderModel: Model;
|
|
transform: BufferTransform;
|
|
pickingFramebuffer: Framebuffer;
|
|
|
|
// eslint-disable-next-line max-statements
|
|
constructor({device, width, height, animationLoop}: AnimationProps) {
|
|
super();
|
|
|
|
if (device.type !== 'webgl') {
|
|
throw new Error('This demo is only implemented for WebGL2');
|
|
}
|
|
|
|
// -- Initialize data
|
|
const trianglePositions = new Float32Array([0.015, 0.0, -0.01, 0.01, -0.01, -0.01]);
|
|
|
|
const instancePositions = new Float32Array(NUM_INSTANCES * 2);
|
|
const instanceRotations = new Float32Array(NUM_INSTANCES);
|
|
const instanceColors = new Float32Array(NUM_INSTANCES * 3);
|
|
const pickingColors = new Float32Array(NUM_INSTANCES * 2);
|
|
|
|
for (let i = 0; i < NUM_INSTANCES; ++i) {
|
|
instancePositions[i * 2] = random() * 2.0 - 1.0;
|
|
instancePositions[i * 2 + 1] = random() * 2.0 - 1.0;
|
|
instanceRotations[i] = random() * 2 * Math.PI;
|
|
|
|
const randValue = random();
|
|
if (randValue > 0.5) {
|
|
instanceColors[i * 3 + 1] = 1.0;
|
|
instanceColors[i * 3 + 2] = 1.0;
|
|
} else {
|
|
instanceColors[i * 3] = 1.0;
|
|
instanceColors[i * 3 + 2] = 1.0;
|
|
}
|
|
|
|
pickingColors[i * 2] = Math.floor(i / 255);
|
|
pickingColors[i * 2 + 1] = i - 255 * pickingColors[i * 2];
|
|
}
|
|
|
|
this.positionBuffer = device.createBuffer({data: trianglePositions});
|
|
this.instanceColorBuffer = device.createBuffer({data: instanceColors});
|
|
this.instancePositionBuffers = new Swap({
|
|
current: device.createBuffer({data: instancePositions}),
|
|
next: device.createBuffer({data: instancePositions})
|
|
});
|
|
this.instanceRotationBuffers = new Swap({
|
|
current: device.createBuffer({data: instanceRotations}),
|
|
next: device.createBuffer({data: instanceRotations})
|
|
});
|
|
this.instancePickingColorBuffer = device.createBuffer({data: pickingColors});
|
|
|
|
this.renderModel = new Model(device, {
|
|
id: 'RenderModel',
|
|
vs: DRAW_VS,
|
|
fs: DRAW_FS,
|
|
modules: [picking],
|
|
topology: 'triangle-list',
|
|
vertexCount: 3,
|
|
isInstanced: true,
|
|
instanceCount: NUM_INSTANCES,
|
|
attributes: {
|
|
positions: this.positionBuffer,
|
|
instanceColors: this.instanceColorBuffer,
|
|
instancePickingColors: this.instancePickingColorBuffer
|
|
},
|
|
bufferLayout: [
|
|
{name: 'positions', format: 'float32x2'},
|
|
{name: 'instancePositions', format: 'float32x2'},
|
|
{name: 'instanceRotations', format: 'float32'},
|
|
{name: 'instanceColors', format: 'float32x3'},
|
|
{name: 'instancePickingColors', format: 'float32x2'}
|
|
]
|
|
});
|
|
|
|
this.transform = new BufferTransform(device, {
|
|
vs: COMPUTE_VS,
|
|
vertexCount: NUM_INSTANCES,
|
|
// elementCount: NUM_INSTANCES,
|
|
bufferLayout: [
|
|
{name: 'oldPositions', format: 'float32x2'},
|
|
{name: 'oldRotations', format: 'float32'}
|
|
],
|
|
outputs: ['newOffsets', 'newRotations']
|
|
});
|
|
|
|
// picking
|
|
// device.getDefaultCanvasContext().canvas.addEventListener('mousemove', mousemove);
|
|
// device.getDefaultCanvasContext().canvas.addEventListener('mouseleave', mouseleave);
|
|
// this.pickingFramebuffer = device.createFramebuffer({width, height});
|
|
}
|
|
|
|
override onFinalize(): void {
|
|
this.renderModel.destroy();
|
|
this.transform.destroy();
|
|
}
|
|
|
|
override onRender({device, width, height, time}: AnimationProps): void {
|
|
this.transform.model.shaderInputs.setProps({app: {time}});
|
|
this.transform.run({
|
|
inputBuffers: {
|
|
oldPositions: this.instancePositionBuffers.current,
|
|
oldRotations: this.instanceRotationBuffers.current
|
|
},
|
|
outputBuffers: {
|
|
newOffsets: this.instancePositionBuffers.next,
|
|
newRotations: this.instanceRotationBuffers.next
|
|
}
|
|
});
|
|
|
|
this.instancePositionBuffers.swap();
|
|
this.instanceRotationBuffers.swap();
|
|
|
|
this.renderModel.setAttributes({
|
|
instancePositions: this.instancePositionBuffers.current,
|
|
instanceRotations: this.instanceRotationBuffers.current
|
|
});
|
|
|
|
const renderPass = device.beginRenderPass({
|
|
clearColor: [0, 0, 0, 1],
|
|
clearDepth: 1
|
|
});
|
|
|
|
this.renderModel.draw(renderPass);
|
|
|
|
// if (pickPosition) {
|
|
// // use the center pixel location in device pixel range
|
|
// const devicePixels = cssToDevicePixels(gl, pickPosition);
|
|
// const deviceX = devicePixels.x + Math.floor(devicePixels.width / 2);
|
|
// const deviceY = devicePixels.y + Math.floor(devicePixels.height / 2);
|
|
// this.pickingFramebuffer.resize({width, height});
|
|
// pickInstance(gl, deviceX, deviceY, this.renderModel, this.pickingFramebuffer);
|
|
// }
|
|
}
|
|
}
|
|
|
|
/*
|
|
function pickInstance(gl, pickX, pickY, model, framebuffer) {
|
|
if (framebuffer) {
|
|
framebuffer.clear({color: true, depth: true});
|
|
}
|
|
// Render picking colors
|
|
model.setUniforms({picking_uActive: 1});
|
|
model.draw({framebuffer});
|
|
model.setUniforms({picking_uActive: 0});
|
|
|
|
const color = readPixelsToArray(framebuffer, {
|
|
sourceX: pickX,
|
|
sourceY: pickY,
|
|
sourceWidth: 1,
|
|
sourceHeight: 1,
|
|
sourceFormat: GL.RGBA,
|
|
sourceType: GL.UNSIGNED_BYTE
|
|
});
|
|
|
|
if (color[0] + color[1] + color[2] > 0) {
|
|
model.updateModuleSettings({
|
|
pickingSelectedColor: color,
|
|
pickingHighlightColor: RED
|
|
});
|
|
} else {
|
|
model.updateModuleSettings({
|
|
pickingSelectedColor: null
|
|
});
|
|
}
|
|
}
|
|
*/
|