解析如下代码:
import BaseLayerViewGL2D from "@arcgis/core/views/2d/layers/BaseLayerViewGL2D.js";
import GraphicsLayer from "@arcgis/core/layers/GraphicsLayer.js";
import * as reactiveUtils from "@arcgis/core/core/reactiveUtils.js";
const CustomLayerView2D = BaseLayerViewGL2D.createSubclass({
// Locations of the two vertex attributes that we use. They
// will be bound to the shader program before linking.
aPosition: 0,
aOffset: 1,
aTexcoord: 2,
aDistance: 3,
aUpright: 4,
constructor() {
// Geometrical transformations that must be recomputed
// from scratch at every frame.
this.transform = mat3.create();
this.rotation = mat3.create();
this.translationToCenter = vec2.create();
this.screenTranslation = vec2.create();
this.display = mat3.create();
// Whether the vertex and index buffers need to be updated
// due to a change in the layer data.
this.needsUpdate = false;
},
// Called once a custom layer is added to the map.layers collection and this layer view is instantiated.
attach() {
const gl = this.context;
// We listen for changes to the graphics collection of the layer
// and trigger the generation of new frames. A frame rendered while
// `needsUpdate` is true may cause an update of the vertex and
// index buffers.
const requestUpdate = () => {
// Tessellate graphics.
this.promises = [];
this.layer.graphics.forEach(this.processGraphic.bind(this));
Promise.all(this.promises).then((meshes) => {
this.meshes = meshes;
this.needsUpdate = true;
this.requestRender();
});
};
this.watcher = reactiveUtils.watch(
() => this.layer.graphics,
requestUpdate,
{
initial: true
}
);
// Define and compile shaders.
const vertexSource = `
precision highp float;
uniform mat3 u_transform;
uniform mat3 u_rotation;
uniform mat3 u_display;
attribute vec2 a_position;
attribute vec2 a_offset;
attribute vec2 a_texcoord;
attribute float a_distance;
attribute float a_upright;
varying vec2 v_texcoord;
void main(void) {
vec3 transformedOffset = mix(u_rotation * vec3(a_offset, 0.0), vec3(a_offset, 0.0), a_upright);
gl_Position.xy = (u_display * (u_transform * vec3(a_position, 1.0) + transformedOffset)).xy;
gl_Position.zw = vec2(0.0, 1.0);
v_texcoord = a_texcoord;
}`;
const fragmentSource = `
precision highp float;
varying vec2 v_texcoord;
void main(void) {
gl_FragColor = vec4(v_texcoord, 0.9, 0.5);
}`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
// Create the shader program.
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
// Bind attributes.
gl.bindAttribLocation(this.program, this.aPosition, "a_position");
gl.bindAttribLocation(this.program, this.aOffset, "a_offset");
gl.bindAttribLocation(this.program, this.aTexcoord, "a_texcoord");
gl.bindAttribLocation(this.program, this.aDistance, "a_distance");
gl.bindAttribLocation(this.program, this.aUpright, "a_upright");
// Link.
gl.linkProgram(this.program);
// Shader objects are not needed anymore.
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
// Retrieve uniform locations once and for all.
this.uTransform = gl.getUniformLocation(
this.program,
"u_transform"
);
this.uRotation = gl.getUniformLocation(
this.program,
"u_rotation"
);
this.uDisplay = gl.getUniformLocation(this.program, "u_display");
// Create the vertex and index buffer. They are initially empty. We need to track the
// size of the index buffer because we use indexed drawing.
this.vertexBuffer = gl.createBuffer();
this.indexBuffer = gl.createBuffer();
// Number of indices in the index buffer.
this.indexBufferSize = 0;
// When certain conditions occur, we update the buffers and re-compute and re-encode
// all the attributes. When buffer update occurs, we also take note of the current center
// of the view state, and we reset a vector called `translationToCenter` to [0, 0], meaning that the
// current center is the same as it was when the attributes were recomputed.
this.centerAtLastUpdate = vec2.fromValues(
this.view.state.center[0],
this.view.state.center[1]
);
},
// Called once a custom layer is removed from the map.layers collection and this layer view is destroyed.
detach() {
// Stop watching the `layer.graphics` collection.
this.watcher.remove();
const gl = this.context;
// Delete buffers and programs.
gl.deleteBuffer(this.vertexBuffer);
gl.deleteBuffer(this.indexBuffer);
gl.deleteProgram(this.program);
},
// Called every time a frame is rendered.
render(renderParameters) {
const gl = renderParameters.context;
const state = renderParameters.state;
// Update vertex positions. This may trigger an update of
// the vertex coordinates contained in the vertex buffer.
// There are three kinds of updates:
// - Modification of the layer.graphics collection ==> Buffer update
// - The view state becomes non-stationary ==> Only view update, no buffer update
// - The view state becomes stationary ==> Buffer update
this.updatePositions(renderParameters);
// If there is nothing to render we return.
if (this.indexBufferSize === 0) {
return;
}
// Update view `transform` matrix; it converts from map units to pixels.
mat3.identity(this.transform);
this.screenTranslation[0] = (state.pixelRatio * state.size[0]) / 2;
this.screenTranslation[1] = (state.pixelRatio * state.size[1]) / 2;
mat3.translate(
this.transform,
this.transform,
this.screenTranslation
);
mat3.rotate(
this.transform,
this.transform,
(Math.PI * state.rotation) / 180
);
mat3.scale(this.transform, this.transform, [
state.pixelRatio / state.resolution,
-state.pixelRatio / state.resolution
]);
mat3.translate(
this.transform,
this.transform,
this.translationToCenter
);
// Update view `rotate` matrix; it is the rotation component of the full `transform` matrix.
mat3.identity(this.rotation);
mat3.rotate(
this.rotation,
this.rotation,
(Math.PI * state.rotation) / 180
);
// Update view `display` matrix; it converts from pixels to normalized device coordinates.
mat3.identity(this.display);
mat3.translate(this.display, this.display, [-1, 1]);
mat3.scale(this.display, this.display, [
2 / (state.pixelRatio * state.size[0]),
-2 / (state.pixelRatio * state.size[1])
]);
// Draw.
gl.useProgram(this.program);
gl.uniformMatrix3fv(this.uTransform, false, this.transform);
gl.uniformMatrix3fv(this.uRotation, false, this.rotation);
gl.uniformMatrix3fv(this.uDisplay, false, this.display);
gl.uniform1f(this.uCurrentTime, performance.now() / 1000.0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
gl.enableVertexAttribArray(this.aPosition);
gl.enableVertexAttribArray(this.aOffset);
gl.enableVertexAttribArray(this.aTexcoord);
gl.enableVertexAttribArray(this.aDistance);
gl.enableVertexAttribArray(this.aUpright);
gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, 32, 0);
gl.vertexAttribPointer(this.aOffset, 2, gl.FLOAT, false, 32, 8);
gl.vertexAttribPointer(this.aTexcoord, 2, gl.FLOAT, false, 32, 16);
gl.vertexAttribPointer(this.aDistance, 1, gl.FLOAT, false, 32, 24);
gl.vertexAttribPointer(this.aUpright, 1, gl.FLOAT, false, 32, 28);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.disable(gl.CULL_FACE);
gl.drawElements(
gl.TRIANGLES,
this.indexBufferSize,
gl.UNSIGNED_SHORT,
0
);
// Request new render because markers are animated.
this.requestRender();
},
processGraphic(g) {
switch (g.geometry.type) {
case "extent":
this.promises.push(
this.tessellateExtent(g.geometry).then(
(mesh) => {
return {
mesh: mesh,
attributes: g.attributes,
symbol: g.symbol
};
}
)
);
break;
case "point":
this.promises.push(
this.tessellatePoint(g.geometry, {
x: (g.attributes && g.attributes.x != null) ? g.attributes.x : -16,
y: (g.attributes && g.attributes.y != null) ? g.attributes.y : 16,
width: (g.attributes && g.attributes.width != null) ? g.attributes.width : 32,
height: (g.attributes && g.attributes.height != null) ? g.attributes.height : 32,
}).then(
(mesh) => {
return {
mesh: mesh,
attributes: g.attributes,
symbol: g.symbol
};
})
);
break;
case "multipoint":
this.promises.push(
this.tessellateMultipoint(g.geometry, {
x: (g.attributes && g.attributes.x != null) ? g.attributes.x : -16,
y: (g.attributes && g.attributes.y != null) ? g.attributes.y : 16,
width: (g.attributes && g.attributes.width != null) ? g.attributes.width : 32,
height: (g.attributes && g.attributes.height != null) ? g.attributes.height : 32,
}).then(
(mesh) => {
return {
mesh: mesh,
attributes: g.attributes,
symbol: g.symbol
};
}
)
);
break;
case "polyline":
this.promises.push(
this.tessellatePolyline(
g.geometry,
(g.attributes && g.attributes.width != null) ? g.attributes.width : 20
).then(
(mesh) => {
return {
mesh: mesh,
attributes: g.attributes,
symbol: g.symbol
};
}
)
);
break;
case "polygon":
this.promises.push(
this.tessellatePolygon(g.geometry).then(
(mesh) => {
return {
mesh: mesh,
attributes: g.attributes,
symbol: g.symbol
};
}
)
);
break;
}
},
// Called internally from render().
updatePositions(renderParameters) {
const gl = renderParameters.context;
const stationary = renderParameters.stationary;
const state = renderParameters.state;
// If we are not stationary we simply update the `translationToCenter` vector.
if (!stationary) {
vec2.sub(
this.translationToCenter,
this.centerAtLastUpdate,
state.center
);
this.requestRender();
return;
}
// If we are stationary, the `layer.graphics` collection has not changed, and
// we are centered on the `centerAtLastUpdate`, we do nothing.
if (
!this.needsUpdate &&
this.translationToCenter[0] === 0 &&
this.translationToCenter[1] === 0
) {
return;
}
// Otherwise, we record the new encoded center, which imply a reset of the `translationToCenter` vector,
// we record the update time, and we proceed to update the buffers.
this.centerAtLastUpdate.set(state.center);
this.translationToCenter[0] = 0;
this.translationToCenter[1] = 0;
this.needsUpdate = false;
// Generate vertex data.
const vertexCount = this.meshes.reduce((vertexCount, item) => { return vertexCount + item.mesh.vertices.length; }, 0);
const indexCount = this.meshes.reduce((indexCount, item) => { return indexCount + item.mesh.indices.length; }, 0);
const vertexData = new Float32Array(vertexCount * 8);
const indexData = new Uint16Array(indexCount);
let currentVertex = 0;
let currentIndex = 0;
for (let meshIndex = 0; meshIndex < this.meshes.length; ++meshIndex) {
const item = this.meshes[meshIndex];
const mesh = item.mesh;
const upright = (item.attributes && item.attributes.upright) ? 1 : 0;
for (let i = 0; i < mesh.indices.length; ++i) {
const idx = mesh.indices[i];
indexData[currentIndex] = currentVertex + idx;
currentIndex++;
}
for (let i = 0; i < mesh.vertices.length; ++i) {
const v = mesh.vertices[i];
vertexData[currentVertex * 8 + 0] = v.x - this.centerAtLastUpdate[0];
vertexData[currentVertex * 8 + 1] = v.y - this.centerAtLastUpdate[1];
vertexData[currentVertex * 8 + 2] = v.xOffset;
vertexData[currentVertex * 8 + 3] = v.yOffset;
vertexData[currentVertex * 8 + 4] = v.uTexcoord;
vertexData[currentVertex * 8 + 5] = v.vTexcoord;
vertexData[currentVertex * 8 + 6] = v.distance;
vertexData[currentVertex * 8 + 7] = upright;
currentVertex++;
}
}
// Upload data to the GPU
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);
// Record number of indices.
this.indexBufferSize = indexCount;
}
});
const CustomLayer = GraphicsLayer.createSubclass({
createLayerView: function (view) {
if (view.type === "2d") {
return new CustomLayerView2D({
view: view,
layer: this
});
}
}
});
export default CustomLayer;