<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>
Animated lines with WebGL | Sample | ArcGIS Maps SDK for JavaScript 4.26
</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix.js"></script>
<!-- <script src="http://www.yanhuangxueyuan.com/versions/THREEjsR92/build/THREE.js"></script> -->
<link rel="stylesheet" href="https://js.arcgis.com/4.26/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.26/"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<script type="module">
import * as THREE from './three.js';
require([
"esri/Map",
"esri/request",
"esri/core/reactiveUtils",
"esri/geometry/support/webMercatorUtils",
"esri/layers/GraphicsLayer",
"esri/views/MapView",
"esri/views/2d/layers/BaseLayerViewGL2D",
"esri/views/layers/LayerView",
"esri/views/3d/externalRenderers",
"esri/geometry/geometryEngine",
"esri/geometry/projection",
"esri/geometry/Polyline",
"esri/views/SceneView"
], (
Map,
esriRequest,
reactiveUtils,
webMercatorUtils,
GraphicsLayer,
MapView,
BaseLayerViewGL2D,
LayerView,
externalRenderers,
geometryEngine,
projection,
Polyline,
SceneView
) => {
// Subclass the custom layer view from BaseLayerViewGL2D.
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,
aDistance: 2,
aSide: 3,
aColor: 4,
constructor: function () {
// Geometrical transformations that must be recomputed
// from scratch at every frame.
this.transform = mat3.create();
this.extrude = mat3.create();
this.translationToCenter = vec2.create();
this.screenTranslation = vec2.create();
// Geometrical transformations whose only a few elements
// must be updated per frame. Those elements are marked
// with NaN.
this.display = mat3.fromValues(NaN, 0, 0, 0, NaN, 0, -1, 1, 1);
this.screenScaling = vec3.fromValues(NaN, NaN, 1);
// 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: function () {
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 = () => {
this.needsUpdate = true;
this.requestRender();
};
this.watcher = reactiveUtils.on(
() => this.layer.graphics,
"change",
requestUpdate
);
const vertexSource = `
precision highp float;
uniform mat3 u_transform;
uniform mat3 u_extrude;
uniform mat3 u_display;
attribute vec2 a_position;
attribute vec2 a_offset;
attribute float a_distance;
attribute float a_side;
attribute vec4 a_color;
varying float v_distance;
varying float v_side;
varying vec4 v_color;
void main(void) {
gl_Position.xy = (u_display * (u_transform * vec3(a_position, 1.0) + u_extrude * vec3(a_offset, 0.0))).xy;
gl_Position.zw = vec2(0.0, 1.0);
v_distance = a_distance;
v_side = a_side;
v_color = a_color;
}`;
const fragmentSource = `
precision highp float;
uniform float u_current_time;
varying float v_distance;
varying float v_side;
varying vec4 v_color;
const float TRAIL_SPEED = 50.0;
const float TRAIL_LENGTH = 300.0;
const float TRAIL_CYCLE = 1000.0;
void main(void) {
float d = mod(v_distance - u_current_time * TRAIL_SPEED, TRAIL_CYCLE);
float a1 = d < TRAIL_LENGTH ? mix(0.0, 1.0, d / TRAIL_LENGTH) : 0.0;
float a2 = exp(-abs(v_side) * 3.0);
float a = a1 * a2;
gl_FragColor = v_color * a;
}`;
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.aDistance, "a_distance");
gl.bindAttribLocation(this.program, this.aSide, "a_side");
gl.bindAttribLocation(this.program, this.aColor, "a_color");
// 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.uExtrude = gl.getUniformLocation(this.program, "u_extrude");
this.uDisplay = gl.getUniformLocation(this.program, "u_display");
this.uCurrentTime = gl.getUniformLocation(
this.program,
"u_current_time"
);
// 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;
// Create a VAO for easier binding. Make sure to handle WebGL 1 and 2!
if (gl.getParameter(gl.VERSION).startsWith("WebGL 2.0")) {
this.vao = gl.createVertexArray();
this.bindVertexArray = (vao) => gl.bindVertexArray(vao);
this.deleteVertexArray = (vao) => gl.deleteVertexArray(vao);
} else {
const vaoExt = gl.getExtension("OES_vertex_array_object");
this.vao = vaoExt.createVertexArrayOES();
this.bindVertexArray = (vao) => vaoExt.bindVertexArrayOES(vao);
this.deleteVertexArray = (vao) =>
vaoExt.deleteVertexArrayOES(vao);
}
// Set up vertex attributes
this.bindVertexArray(this.vao);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
gl.enableVertexAttribArray(this.aPosition);