diff --git a/.circleci/config.yml b/.circleci/config.yml
index 633cb017af..0540d11554 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,22 +3,17 @@
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2.1
+executors:
+ node-browser-executor:
+ docker:
+ - image: cimg/node:22.14-browsers
orbs:
browser-tools: circleci/browser-tools@1.4.8
jobs:
- build:
- docker:
- # specify the version you desire here
- - image: cimg/node:20.11-browsers
- environment:
- CHROME_BIN: "/usr/bin/google-chrome"
-
- # Specify service dependencies here if necessary
- # CircleCI maintains a library of pre-built images
- # documented at https://circleci.com/docs/2.0/circleci-images/
- # - image: circleci/mongo:3.4.4
+ maptalks-test:
+ executor: node-browser-executor
- working_directory: ~/repo
+ working_directory: ~/repo/
steps:
- browser-tools/install-chrome
@@ -26,19 +21,26 @@ jobs:
# Download and cache dependencies
- restore_cache:
+ name: Restore pnpm Package Cache
keys:
- - v1-dependencies-{{ checksum "package-lock.json" }}
- # fallback to using the latest cache if no exact match is found
- - v1-dependencies-
+ - pnpm-packages-{{ checksum "pnpm-lock.yaml" }}
- - run: npm install
+ - run:
+ name: Install pnpm package manager
+ command: |
+ sudo npm install --global pnpm
+
+ - run: pnpm i
- save_cache:
+ name: Save pnpm Package Cache
+ key: pnpm-packages-{{ checksum "pnpm-lock.yaml" }}
paths:
- - node_modules
- key: v1-dependencies-{{ checksum "package-lock.json" }}
-
+ - .pnpm-store
# run tests!
- - run: npm test
-
+ - run: npm run maptalks-test
+workflows:
+ maptalks:
+ jobs:
+ - maptalks-test
diff --git a/.gitignore b/.gitignore
index 05378950fe..51aeeb0074 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,6 @@ coverage
.env
.env.local
web.config
-debug
+
*.tsbuildinfo
*.map
diff --git a/debug/terrain.html b/debug/terrain.html
index 266a4b060a..d432b1e49d 100644
--- a/debug/terrain.html
+++ b/debug/terrain.html
@@ -127,8 +127,6 @@
//mapbox服务
debug: true,
type: 'mapbox',
- tileSize: 256,
-
urlTemplate: 'https://{s}.tiles.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=pk.eyJ1IjoibWFwYm94LWdsLWpzIiwiYSI6ImNram9ybGI1ajExYjQyeGxlemppb2pwYjIifQ.LGy5UGNIsXUZdYMvfYRiAQ',
// urlTemplate: 'https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}@2x.png?access_token=pk.eyJ1Ijoidmhhd2siLCJhIjoiY2xmbWpqOXBoMGNmZDN2cjJwZXk0MXBzZiJ9.192VNPJG0VV9dGOCOX1gUw',
// urlTemplate: 'https://osm-tiles.ams3.cdn.digitaloceanspaces.com/elevation/{z}/{x}/{y}.png',
diff --git a/debug/webgpu/Di-3d.png b/debug/webgpu/Di-3d.png
new file mode 100644
index 0000000000..ebbff45ead
Binary files /dev/null and b/debug/webgpu/Di-3d.png differ
diff --git a/debug/webgpu/cube-instance.html b/debug/webgpu/cube-instance.html
new file mode 100644
index 0000000000..d349c86618
--- /dev/null
+++ b/debug/webgpu/cube-instance.html
@@ -0,0 +1,198 @@
+
+
+
+
+
Cube Wireframe
+
+
+
+
+
+
+
+
diff --git a/debug/webgpu/cube-texture.html b/debug/webgpu/cube-texture.html
new file mode 100644
index 0000000000..6db21ef4fc
--- /dev/null
+++ b/debug/webgpu/cube-texture.html
@@ -0,0 +1,161 @@
+
+
+
+
+ Cube Wireframe
+
+
+
+
+
+
+
+
diff --git a/debug/webgpu/cube.html b/debug/webgpu/cube.html
new file mode 100644
index 0000000000..b90d432345
--- /dev/null
+++ b/debug/webgpu/cube.html
@@ -0,0 +1,173 @@
+
+
+
+
+ Cube Wireframe
+
+
+
+
+
+
+
+
diff --git a/debug/webgpu/gl-matrix/common.js b/debug/webgpu/gl-matrix/common.js
new file mode 100644
index 0000000000..fa58cf074c
--- /dev/null
+++ b/debug/webgpu/gl-matrix/common.js
@@ -0,0 +1,51 @@
+/**
+ * Common utilities
+ * @module glMatrix
+ */
+// Configuration Constants
+export var EPSILON = 0.000001;
+export var ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array;
+export var RANDOM = Math.random;
+export var ANGLE_ORDER = "zyx";
+/**
+ * Sets the type of array used when creating new vectors and matrices
+ *
+ * @param {Float32ArrayConstructor | ArrayConstructor} type Array type, such as Float32Array or Array
+ */
+
+export function setMatrixArrayType(type) {
+ ARRAY_TYPE = type;
+}
+var degree = Math.PI / 180;
+/**
+ * Convert Degree To Radian
+ *
+ * @param {Number} a Angle in Degrees
+ */
+
+export function toRadian(a) {
+ return a * degree;
+}
+/**
+ * Tests whether or not the arguments have approximately the same value, within an absolute
+ * or relative tolerance of glMatrix.EPSILON (an absolute tolerance is used for values less
+ * than or equal to 1.0, and a relative tolerance is used for larger values)
+ *
+ * @param {Number} a The first number to test.
+ * @param {Number} b The second number to test.
+ * @returns {Boolean} True if the numbers are approximately equal, false otherwise.
+ */
+
+export function equals(a, b) {
+ return Math.abs(a - b) <= EPSILON * Math.max(1.0, Math.abs(a), Math.abs(b));
+}
+if (!Math.hypot) Math.hypot = function () {
+ var y = 0,
+ i = arguments.length;
+
+ while (i--) {
+ y += arguments[i] * arguments[i];
+ }
+
+ return Math.sqrt(y);
+};
\ No newline at end of file
diff --git a/debug/webgpu/gl-matrix/index.js b/debug/webgpu/gl-matrix/index.js
new file mode 100644
index 0000000000..e89dffe691
--- /dev/null
+++ b/debug/webgpu/gl-matrix/index.js
@@ -0,0 +1,11 @@
+import * as glMatrix from "./common.js";
+import * as mat2 from "./mat2.js";
+import * as mat2d from "./mat2d.js";
+import * as mat3 from "./mat3.js";
+import * as mat4 from "./mat4.js";
+import * as quat from "./quat.js";
+import * as quat2 from "./quat2.js";
+import * as vec2 from "./vec2.js";
+import * as vec3 from "./vec3.js";
+import * as vec4 from "./vec4.js";
+export { glMatrix, mat2, mat2d, mat3, mat4, quat, quat2, vec2, vec3, vec4 };
\ No newline at end of file
diff --git a/debug/webgpu/gl-matrix/mat2.js b/debug/webgpu/gl-matrix/mat2.js
new file mode 100644
index 0000000000..d3fbcdebc0
--- /dev/null
+++ b/debug/webgpu/gl-matrix/mat2.js
@@ -0,0 +1,432 @@
+import * as glMatrix from "./common.js";
+/**
+ * 2x2 Matrix
+ * @module mat2
+ */
+
+/**
+ * Creates a new identity mat2
+ *
+ * @returns {mat2} a new 2x2 matrix
+ */
+
+export function create() {
+ var out = new glMatrix.ARRAY_TYPE(4);
+
+ if (glMatrix.ARRAY_TYPE != Float32Array) {
+ out[1] = 0;
+ out[2] = 0;
+ }
+
+ out[0] = 1;
+ out[3] = 1;
+ return out;
+}
+/**
+ * Creates a new mat2 initialized with values from an existing matrix
+ *
+ * @param {ReadonlyMat2} a matrix to clone
+ * @returns {mat2} a new 2x2 matrix
+ */
+
+export function clone(a) {
+ var out = new glMatrix.ARRAY_TYPE(4);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ return out;
+}
+/**
+ * Copy the values from one mat2 to another
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the source matrix
+ * @returns {mat2} out
+ */
+
+export function copy(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ return out;
+}
+/**
+ * Set a mat2 to the identity matrix
+ *
+ * @param {mat2} out the receiving matrix
+ * @returns {mat2} out
+ */
+
+export function identity(out) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ return out;
+}
+/**
+ * Create a new mat2 with the given values
+ *
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m10 Component in column 1, row 0 position (index 2)
+ * @param {Number} m11 Component in column 1, row 1 position (index 3)
+ * @returns {mat2} out A new 2x2 matrix
+ */
+
+export function fromValues(m00, m01, m10, m11) {
+ var out = new glMatrix.ARRAY_TYPE(4);
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m10;
+ out[3] = m11;
+ return out;
+}
+/**
+ * Set the components of a mat2 to the given values
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m10 Component in column 1, row 0 position (index 2)
+ * @param {Number} m11 Component in column 1, row 1 position (index 3)
+ * @returns {mat2} out
+ */
+
+export function set(out, m00, m01, m10, m11) {
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m10;
+ out[3] = m11;
+ return out;
+}
+/**
+ * Transpose the values of a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the source matrix
+ * @returns {mat2} out
+ */
+
+export function transpose(out, a) {
+ // If we are transposing ourselves we can skip a few steps but have to cache
+ // some values
+ if (out === a) {
+ var a1 = a[1];
+ out[1] = a[2];
+ out[2] = a1;
+ } else {
+ out[0] = a[0];
+ out[1] = a[2];
+ out[2] = a[1];
+ out[3] = a[3];
+ }
+
+ return out;
+}
+/**
+ * Inverts a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the source matrix
+ * @returns {mat2} out
+ */
+
+export function invert(out, a) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3]; // Calculate the determinant
+
+ var det = a0 * a3 - a2 * a1;
+
+ if (!det) {
+ return null;
+ }
+
+ det = 1.0 / det;
+ out[0] = a3 * det;
+ out[1] = -a1 * det;
+ out[2] = -a2 * det;
+ out[3] = a0 * det;
+ return out;
+}
+/**
+ * Calculates the adjugate of a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the source matrix
+ * @returns {mat2} out
+ */
+
+export function adjoint(out, a) {
+ // Caching this value is necessary if out == a
+ var a0 = a[0];
+ out[0] = a[3];
+ out[1] = -a[1];
+ out[2] = -a[2];
+ out[3] = a0;
+ return out;
+}
+/**
+ * Calculates the determinant of a mat2
+ *
+ * @param {ReadonlyMat2} a the source matrix
+ * @returns {Number} determinant of a
+ */
+
+export function determinant(a) {
+ return a[0] * a[3] - a[2] * a[1];
+}
+/**
+ * Multiplies two mat2's
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the first operand
+ * @param {ReadonlyMat2} b the second operand
+ * @returns {mat2} out
+ */
+
+export function multiply(out, a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3];
+ out[0] = a0 * b0 + a2 * b1;
+ out[1] = a1 * b0 + a3 * b1;
+ out[2] = a0 * b2 + a2 * b3;
+ out[3] = a1 * b2 + a3 * b3;
+ return out;
+}
+/**
+ * Rotates a mat2 by the given angle
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2} out
+ */
+
+export function rotate(out, a, rad) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ out[0] = a0 * c + a2 * s;
+ out[1] = a1 * c + a3 * s;
+ out[2] = a0 * -s + a2 * c;
+ out[3] = a1 * -s + a3 * c;
+ return out;
+}
+/**
+ * Scales the mat2 by the dimensions in the given vec2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the matrix to rotate
+ * @param {ReadonlyVec2} v the vec2 to scale the matrix by
+ * @returns {mat2} out
+ **/
+
+export function scale(out, a, v) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var v0 = v[0],
+ v1 = v[1];
+ out[0] = a0 * v0;
+ out[1] = a1 * v0;
+ out[2] = a2 * v1;
+ out[3] = a3 * v1;
+ return out;
+}
+/**
+ * Creates a matrix from a given angle
+ * This is equivalent to (but much faster than):
+ *
+ * mat2.identity(dest);
+ * mat2.rotate(dest, dest, rad);
+ *
+ * @param {mat2} out mat2 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2} out
+ */
+
+export function fromRotation(out, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ out[0] = c;
+ out[1] = s;
+ out[2] = -s;
+ out[3] = c;
+ return out;
+}
+/**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ * mat2.identity(dest);
+ * mat2.scale(dest, dest, vec);
+ *
+ * @param {mat2} out mat2 receiving operation result
+ * @param {ReadonlyVec2} v Scaling vector
+ * @returns {mat2} out
+ */
+
+export function fromScaling(out, v) {
+ out[0] = v[0];
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = v[1];
+ return out;
+}
+/**
+ * Returns a string representation of a mat2
+ *
+ * @param {ReadonlyMat2} a matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+
+export function str(a) {
+ return "mat2(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")";
+}
+/**
+ * Returns Frobenius norm of a mat2
+ *
+ * @param {ReadonlyMat2} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+
+export function frob(a) {
+ return Math.hypot(a[0], a[1], a[2], a[3]);
+}
+/**
+ * Returns L, D and U matrices (Lower triangular, Diagonal and Upper triangular) by factorizing the input matrix
+ * @param {ReadonlyMat2} L the lower triangular matrix
+ * @param {ReadonlyMat2} D the diagonal matrix
+ * @param {ReadonlyMat2} U the upper triangular matrix
+ * @param {ReadonlyMat2} a the input matrix to factorize
+ */
+
+export function LDU(L, D, U, a) {
+ L[2] = a[2] / a[0];
+ U[0] = a[0];
+ U[1] = a[1];
+ U[3] = a[3] - L[2] * U[1];
+ return [L, D, U];
+}
+/**
+ * Adds two mat2's
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the first operand
+ * @param {ReadonlyMat2} b the second operand
+ * @returns {mat2} out
+ */
+
+export function add(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ return out;
+}
+/**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the first operand
+ * @param {ReadonlyMat2} b the second operand
+ * @returns {mat2} out
+ */
+
+export function subtract(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ out[3] = a[3] - b[3];
+ return out;
+}
+/**
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyMat2} a The first matrix.
+ * @param {ReadonlyMat2} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+export function exactEquals(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
+}
+/**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyMat2} a The first matrix.
+ * @param {ReadonlyMat2} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+export function equals(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3];
+ return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3));
+}
+/**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {ReadonlyMat2} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat2} out
+ */
+
+export function multiplyScalar(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ return out;
+}
+/**
+ * Adds two mat2's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat2} out the receiving vector
+ * @param {ReadonlyMat2} a the first operand
+ * @param {ReadonlyMat2} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat2} out
+ */
+
+export function multiplyScalarAndAdd(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ out[3] = a[3] + b[3] * scale;
+ return out;
+}
+/**
+ * Alias for {@link mat2.multiply}
+ * @function
+ */
+
+export var mul = multiply;
+/**
+ * Alias for {@link mat2.subtract}
+ * @function
+ */
+
+export var sub = subtract;
\ No newline at end of file
diff --git a/debug/webgpu/gl-matrix/mat2d.js b/debug/webgpu/gl-matrix/mat2d.js
new file mode 100644
index 0000000000..ce6988c3ee
--- /dev/null
+++ b/debug/webgpu/gl-matrix/mat2d.js
@@ -0,0 +1,486 @@
+import * as glMatrix from "./common.js";
+/**
+ * 2x3 Matrix
+ * @module mat2d
+ * @description
+ * A mat2d contains six elements defined as:
+ *
+ * [a, b,
+ * c, d,
+ * tx, ty]
+ *
+ * This is a short form for the 3x3 matrix:
+ *
+ * [a, b, 0,
+ * c, d, 0,
+ * tx, ty, 1]
+ *
+ * The last column is ignored so the array is shorter and operations are faster.
+ */
+
+/**
+ * Creates a new identity mat2d
+ *
+ * @returns {mat2d} a new 2x3 matrix
+ */
+
+export function create() {
+ var out = new glMatrix.ARRAY_TYPE(6);
+
+ if (glMatrix.ARRAY_TYPE != Float32Array) {
+ out[1] = 0;
+ out[2] = 0;
+ out[4] = 0;
+ out[5] = 0;
+ }
+
+ out[0] = 1;
+ out[3] = 1;
+ return out;
+}
+/**
+ * Creates a new mat2d initialized with values from an existing matrix
+ *
+ * @param {ReadonlyMat2d} a matrix to clone
+ * @returns {mat2d} a new 2x3 matrix
+ */
+
+export function clone(a) {
+ var out = new glMatrix.ARRAY_TYPE(6);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ return out;
+}
+/**
+ * Copy the values from one mat2d to another
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the source matrix
+ * @returns {mat2d} out
+ */
+
+export function copy(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ return out;
+}
+/**
+ * Set a mat2d to the identity matrix
+ *
+ * @param {mat2d} out the receiving matrix
+ * @returns {mat2d} out
+ */
+
+export function identity(out) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ out[4] = 0;
+ out[5] = 0;
+ return out;
+}
+/**
+ * Create a new mat2d with the given values
+ *
+ * @param {Number} a Component A (index 0)
+ * @param {Number} b Component B (index 1)
+ * @param {Number} c Component C (index 2)
+ * @param {Number} d Component D (index 3)
+ * @param {Number} tx Component TX (index 4)
+ * @param {Number} ty Component TY (index 5)
+ * @returns {mat2d} A new mat2d
+ */
+
+export function fromValues(a, b, c, d, tx, ty) {
+ var out = new glMatrix.ARRAY_TYPE(6);
+ out[0] = a;
+ out[1] = b;
+ out[2] = c;
+ out[3] = d;
+ out[4] = tx;
+ out[5] = ty;
+ return out;
+}
+/**
+ * Set the components of a mat2d to the given values
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {Number} a Component A (index 0)
+ * @param {Number} b Component B (index 1)
+ * @param {Number} c Component C (index 2)
+ * @param {Number} d Component D (index 3)
+ * @param {Number} tx Component TX (index 4)
+ * @param {Number} ty Component TY (index 5)
+ * @returns {mat2d} out
+ */
+
+export function set(out, a, b, c, d, tx, ty) {
+ out[0] = a;
+ out[1] = b;
+ out[2] = c;
+ out[3] = d;
+ out[4] = tx;
+ out[5] = ty;
+ return out;
+}
+/**
+ * Inverts a mat2d
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the source matrix
+ * @returns {mat2d} out
+ */
+
+export function invert(out, a) {
+ var aa = a[0],
+ ab = a[1],
+ ac = a[2],
+ ad = a[3];
+ var atx = a[4],
+ aty = a[5];
+ var det = aa * ad - ab * ac;
+
+ if (!det) {
+ return null;
+ }
+
+ det = 1.0 / det;
+ out[0] = ad * det;
+ out[1] = -ab * det;
+ out[2] = -ac * det;
+ out[3] = aa * det;
+ out[4] = (ac * aty - ad * atx) * det;
+ out[5] = (ab * atx - aa * aty) * det;
+ return out;
+}
+/**
+ * Calculates the determinant of a mat2d
+ *
+ * @param {ReadonlyMat2d} a the source matrix
+ * @returns {Number} determinant of a
+ */
+
+export function determinant(a) {
+ return a[0] * a[3] - a[1] * a[2];
+}
+/**
+ * Multiplies two mat2d's
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the first operand
+ * @param {ReadonlyMat2d} b the second operand
+ * @returns {mat2d} out
+ */
+
+export function multiply(out, a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3],
+ b4 = b[4],
+ b5 = b[5];
+ out[0] = a0 * b0 + a2 * b1;
+ out[1] = a1 * b0 + a3 * b1;
+ out[2] = a0 * b2 + a2 * b3;
+ out[3] = a1 * b2 + a3 * b3;
+ out[4] = a0 * b4 + a2 * b5 + a4;
+ out[5] = a1 * b4 + a3 * b5 + a5;
+ return out;
+}
+/**
+ * Rotates a mat2d by the given angle
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2d} out
+ */
+
+export function rotate(out, a, rad) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5];
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ out[0] = a0 * c + a2 * s;
+ out[1] = a1 * c + a3 * s;
+ out[2] = a0 * -s + a2 * c;
+ out[3] = a1 * -s + a3 * c;
+ out[4] = a4;
+ out[5] = a5;
+ return out;
+}
+/**
+ * Scales the mat2d by the dimensions in the given vec2
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the matrix to translate
+ * @param {ReadonlyVec2} v the vec2 to scale the matrix by
+ * @returns {mat2d} out
+ **/
+
+export function scale(out, a, v) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5];
+ var v0 = v[0],
+ v1 = v[1];
+ out[0] = a0 * v0;
+ out[1] = a1 * v0;
+ out[2] = a2 * v1;
+ out[3] = a3 * v1;
+ out[4] = a4;
+ out[5] = a5;
+ return out;
+}
+/**
+ * Translates the mat2d by the dimensions in the given vec2
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the matrix to translate
+ * @param {ReadonlyVec2} v the vec2 to translate the matrix by
+ * @returns {mat2d} out
+ **/
+
+export function translate(out, a, v) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5];
+ var v0 = v[0],
+ v1 = v[1];
+ out[0] = a0;
+ out[1] = a1;
+ out[2] = a2;
+ out[3] = a3;
+ out[4] = a0 * v0 + a2 * v1 + a4;
+ out[5] = a1 * v0 + a3 * v1 + a5;
+ return out;
+}
+/**
+ * Creates a matrix from a given angle
+ * This is equivalent to (but much faster than):
+ *
+ * mat2d.identity(dest);
+ * mat2d.rotate(dest, dest, rad);
+ *
+ * @param {mat2d} out mat2d receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2d} out
+ */
+
+export function fromRotation(out, rad) {
+ var s = Math.sin(rad),
+ c = Math.cos(rad);
+ out[0] = c;
+ out[1] = s;
+ out[2] = -s;
+ out[3] = c;
+ out[4] = 0;
+ out[5] = 0;
+ return out;
+}
+/**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ * mat2d.identity(dest);
+ * mat2d.scale(dest, dest, vec);
+ *
+ * @param {mat2d} out mat2d receiving operation result
+ * @param {ReadonlyVec2} v Scaling vector
+ * @returns {mat2d} out
+ */
+
+export function fromScaling(out, v) {
+ out[0] = v[0];
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = v[1];
+ out[4] = 0;
+ out[5] = 0;
+ return out;
+}
+/**
+ * Creates a matrix from a vector translation
+ * This is equivalent to (but much faster than):
+ *
+ * mat2d.identity(dest);
+ * mat2d.translate(dest, dest, vec);
+ *
+ * @param {mat2d} out mat2d receiving operation result
+ * @param {ReadonlyVec2} v Translation vector
+ * @returns {mat2d} out
+ */
+
+export function fromTranslation(out, v) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ out[4] = v[0];
+ out[5] = v[1];
+ return out;
+}
+/**
+ * Returns a string representation of a mat2d
+ *
+ * @param {ReadonlyMat2d} a matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+
+export function str(a) {
+ return "mat2d(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ")";
+}
+/**
+ * Returns Frobenius norm of a mat2d
+ *
+ * @param {ReadonlyMat2d} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+
+export function frob(a) {
+ return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], 1);
+}
+/**
+ * Adds two mat2d's
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the first operand
+ * @param {ReadonlyMat2d} b the second operand
+ * @returns {mat2d} out
+ */
+
+export function add(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ out[4] = a[4] + b[4];
+ out[5] = a[5] + b[5];
+ return out;
+}
+/**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the first operand
+ * @param {ReadonlyMat2d} b the second operand
+ * @returns {mat2d} out
+ */
+
+export function subtract(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ out[3] = a[3] - b[3];
+ out[4] = a[4] - b[4];
+ out[5] = a[5] - b[5];
+ return out;
+}
+/**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {ReadonlyMat2d} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat2d} out
+ */
+
+export function multiplyScalar(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ out[4] = a[4] * b;
+ out[5] = a[5] * b;
+ return out;
+}
+/**
+ * Adds two mat2d's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat2d} out the receiving vector
+ * @param {ReadonlyMat2d} a the first operand
+ * @param {ReadonlyMat2d} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat2d} out
+ */
+
+export function multiplyScalarAndAdd(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ out[3] = a[3] + b[3] * scale;
+ out[4] = a[4] + b[4] * scale;
+ out[5] = a[5] + b[5] * scale;
+ return out;
+}
+/**
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyMat2d} a The first matrix.
+ * @param {ReadonlyMat2d} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+export function exactEquals(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5];
+}
+/**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyMat2d} a The first matrix.
+ * @param {ReadonlyMat2d} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+export function equals(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3],
+ b4 = b[4],
+ b5 = b[5];
+ return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5));
+}
+/**
+ * Alias for {@link mat2d.multiply}
+ * @function
+ */
+
+export var mul = multiply;
+/**
+ * Alias for {@link mat2d.subtract}
+ * @function
+ */
+
+export var sub = subtract;
\ No newline at end of file
diff --git a/debug/webgpu/gl-matrix/mat3.js b/debug/webgpu/gl-matrix/mat3.js
new file mode 100644
index 0000000000..13974ab104
--- /dev/null
+++ b/debug/webgpu/gl-matrix/mat3.js
@@ -0,0 +1,778 @@
+import * as glMatrix from "./common.js";
+/**
+ * 3x3 Matrix
+ * @module mat3
+ */
+
+/**
+ * Creates a new identity mat3
+ *
+ * @returns {mat3} a new 3x3 matrix
+ */
+
+export function create() {
+ var out = new glMatrix.ARRAY_TYPE(9);
+
+ if (glMatrix.ARRAY_TYPE != Float32Array) {
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ }
+
+ out[0] = 1;
+ out[4] = 1;
+ out[8] = 1;
+ return out;
+}
+/**
+ * Copies the upper-left 3x3 values into the given mat3.
+ *
+ * @param {mat3} out the receiving 3x3 matrix
+ * @param {ReadonlyMat4} a the source 4x4 matrix
+ * @returns {mat3} out
+ */
+
+export function fromMat4(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[4];
+ out[4] = a[5];
+ out[5] = a[6];
+ out[6] = a[8];
+ out[7] = a[9];
+ out[8] = a[10];
+ return out;
+}
+/**
+ * Creates a new mat3 initialized with values from an existing matrix
+ *
+ * @param {ReadonlyMat3} a matrix to clone
+ * @returns {mat3} a new 3x3 matrix
+ */
+
+export function clone(a) {
+ var out = new glMatrix.ARRAY_TYPE(9);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ return out;
+}
+/**
+ * Copy the values from one mat3 to another
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the source matrix
+ * @returns {mat3} out
+ */
+
+export function copy(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ return out;
+}
+/**
+ * Create a new mat3 with the given values
+ *
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m10 Component in column 1, row 0 position (index 3)
+ * @param {Number} m11 Component in column 1, row 1 position (index 4)
+ * @param {Number} m12 Component in column 1, row 2 position (index 5)
+ * @param {Number} m20 Component in column 2, row 0 position (index 6)
+ * @param {Number} m21 Component in column 2, row 1 position (index 7)
+ * @param {Number} m22 Component in column 2, row 2 position (index 8)
+ * @returns {mat3} A new mat3
+ */
+
+export function fromValues(m00, m01, m02, m10, m11, m12, m20, m21, m22) {
+ var out = new glMatrix.ARRAY_TYPE(9);
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m02;
+ out[3] = m10;
+ out[4] = m11;
+ out[5] = m12;
+ out[6] = m20;
+ out[7] = m21;
+ out[8] = m22;
+ return out;
+}
+/**
+ * Set the components of a mat3 to the given values
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m10 Component in column 1, row 0 position (index 3)
+ * @param {Number} m11 Component in column 1, row 1 position (index 4)
+ * @param {Number} m12 Component in column 1, row 2 position (index 5)
+ * @param {Number} m20 Component in column 2, row 0 position (index 6)
+ * @param {Number} m21 Component in column 2, row 1 position (index 7)
+ * @param {Number} m22 Component in column 2, row 2 position (index 8)
+ * @returns {mat3} out
+ */
+
+export function set(out, m00, m01, m02, m10, m11, m12, m20, m21, m22) {
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m02;
+ out[3] = m10;
+ out[4] = m11;
+ out[5] = m12;
+ out[6] = m20;
+ out[7] = m21;
+ out[8] = m22;
+ return out;
+}
+/**
+ * Set a mat3 to the identity matrix
+ *
+ * @param {mat3} out the receiving matrix
+ * @returns {mat3} out
+ */
+
+export function identity(out) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 1;
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 1;
+ return out;
+}
+/**
+ * Transpose the values of a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the source matrix
+ * @returns {mat3} out
+ */
+
+export function transpose(out, a) {
+ // If we are transposing ourselves we can skip a few steps but have to cache some values
+ if (out === a) {
+ var a01 = a[1],
+ a02 = a[2],
+ a12 = a[5];
+ out[1] = a[3];
+ out[2] = a[6];
+ out[3] = a01;
+ out[5] = a[7];
+ out[6] = a02;
+ out[7] = a12;
+ } else {
+ out[0] = a[0];
+ out[1] = a[3];
+ out[2] = a[6];
+ out[3] = a[1];
+ out[4] = a[4];
+ out[5] = a[7];
+ out[6] = a[2];
+ out[7] = a[5];
+ out[8] = a[8];
+ }
+
+ return out;
+}
+/**
+ * Inverts a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the source matrix
+ * @returns {mat3} out
+ */
+
+export function invert(out, a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2];
+ var a10 = a[3],
+ a11 = a[4],
+ a12 = a[5];
+ var a20 = a[6],
+ a21 = a[7],
+ a22 = a[8];
+ var b01 = a22 * a11 - a12 * a21;
+ var b11 = -a22 * a10 + a12 * a20;
+ var b21 = a21 * a10 - a11 * a20; // Calculate the determinant
+
+ var det = a00 * b01 + a01 * b11 + a02 * b21;
+
+ if (!det) {
+ return null;
+ }
+
+ det = 1.0 / det;
+ out[0] = b01 * det;
+ out[1] = (-a22 * a01 + a02 * a21) * det;
+ out[2] = (a12 * a01 - a02 * a11) * det;
+ out[3] = b11 * det;
+ out[4] = (a22 * a00 - a02 * a20) * det;
+ out[5] = (-a12 * a00 + a02 * a10) * det;
+ out[6] = b21 * det;
+ out[7] = (-a21 * a00 + a01 * a20) * det;
+ out[8] = (a11 * a00 - a01 * a10) * det;
+ return out;
+}
+/**
+ * Calculates the adjugate of a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the source matrix
+ * @returns {mat3} out
+ */
+
+export function adjoint(out, a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2];
+ var a10 = a[3],
+ a11 = a[4],
+ a12 = a[5];
+ var a20 = a[6],
+ a21 = a[7],
+ a22 = a[8];
+ out[0] = a11 * a22 - a12 * a21;
+ out[1] = a02 * a21 - a01 * a22;
+ out[2] = a01 * a12 - a02 * a11;
+ out[3] = a12 * a20 - a10 * a22;
+ out[4] = a00 * a22 - a02 * a20;
+ out[5] = a02 * a10 - a00 * a12;
+ out[6] = a10 * a21 - a11 * a20;
+ out[7] = a01 * a20 - a00 * a21;
+ out[8] = a00 * a11 - a01 * a10;
+ return out;
+}
+/**
+ * Calculates the determinant of a mat3
+ *
+ * @param {ReadonlyMat3} a the source matrix
+ * @returns {Number} determinant of a
+ */
+
+export function determinant(a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2];
+ var a10 = a[3],
+ a11 = a[4],
+ a12 = a[5];
+ var a20 = a[6],
+ a21 = a[7],
+ a22 = a[8];
+ return a00 * (a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * (a21 * a10 - a11 * a20);
+}
+/**
+ * Multiplies two mat3's
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the first operand
+ * @param {ReadonlyMat3} b the second operand
+ * @returns {mat3} out
+ */
+
+export function multiply(out, a, b) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2];
+ var a10 = a[3],
+ a11 = a[4],
+ a12 = a[5];
+ var a20 = a[6],
+ a21 = a[7],
+ a22 = a[8];
+ var b00 = b[0],
+ b01 = b[1],
+ b02 = b[2];
+ var b10 = b[3],
+ b11 = b[4],
+ b12 = b[5];
+ var b20 = b[6],
+ b21 = b[7],
+ b22 = b[8];
+ out[0] = b00 * a00 + b01 * a10 + b02 * a20;
+ out[1] = b00 * a01 + b01 * a11 + b02 * a21;
+ out[2] = b00 * a02 + b01 * a12 + b02 * a22;
+ out[3] = b10 * a00 + b11 * a10 + b12 * a20;
+ out[4] = b10 * a01 + b11 * a11 + b12 * a21;
+ out[5] = b10 * a02 + b11 * a12 + b12 * a22;
+ out[6] = b20 * a00 + b21 * a10 + b22 * a20;
+ out[7] = b20 * a01 + b21 * a11 + b22 * a21;
+ out[8] = b20 * a02 + b21 * a12 + b22 * a22;
+ return out;
+}
+/**
+ * Translate a mat3 by the given vector
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the matrix to translate
+ * @param {ReadonlyVec2} v vector to translate by
+ * @returns {mat3} out
+ */
+
+export function translate(out, a, v) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a10 = a[3],
+ a11 = a[4],
+ a12 = a[5],
+ a20 = a[6],
+ a21 = a[7],
+ a22 = a[8],
+ x = v[0],
+ y = v[1];
+ out[0] = a00;
+ out[1] = a01;
+ out[2] = a02;
+ out[3] = a10;
+ out[4] = a11;
+ out[5] = a12;
+ out[6] = x * a00 + y * a10 + a20;
+ out[7] = x * a01 + y * a11 + a21;
+ out[8] = x * a02 + y * a12 + a22;
+ return out;
+}
+/**
+ * Rotates a mat3 by the given angle
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat3} out
+ */
+
+export function rotate(out, a, rad) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a10 = a[3],
+ a11 = a[4],
+ a12 = a[5],
+ a20 = a[6],
+ a21 = a[7],
+ a22 = a[8],
+ s = Math.sin(rad),
+ c = Math.cos(rad);
+ out[0] = c * a00 + s * a10;
+ out[1] = c * a01 + s * a11;
+ out[2] = c * a02 + s * a12;
+ out[3] = c * a10 - s * a00;
+ out[4] = c * a11 - s * a01;
+ out[5] = c * a12 - s * a02;
+ out[6] = a20;
+ out[7] = a21;
+ out[8] = a22;
+ return out;
+}
+/**
+ * Scales the mat3 by the dimensions in the given vec2
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the matrix to rotate
+ * @param {ReadonlyVec2} v the vec2 to scale the matrix by
+ * @returns {mat3} out
+ **/
+
+export function scale(out, a, v) {
+ var x = v[0],
+ y = v[1];
+ out[0] = x * a[0];
+ out[1] = x * a[1];
+ out[2] = x * a[2];
+ out[3] = y * a[3];
+ out[4] = y * a[4];
+ out[5] = y * a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ return out;
+}
+/**
+ * Creates a matrix from a vector translation
+ * This is equivalent to (but much faster than):
+ *
+ * mat3.identity(dest);
+ * mat3.translate(dest, dest, vec);
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {ReadonlyVec2} v Translation vector
+ * @returns {mat3} out
+ */
+
+export function fromTranslation(out, v) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 1;
+ out[5] = 0;
+ out[6] = v[0];
+ out[7] = v[1];
+ out[8] = 1;
+ return out;
+}
+/**
+ * Creates a matrix from a given angle
+ * This is equivalent to (but much faster than):
+ *
+ * mat3.identity(dest);
+ * mat3.rotate(dest, dest, rad);
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat3} out
+ */
+
+export function fromRotation(out, rad) {
+ var s = Math.sin(rad),
+ c = Math.cos(rad);
+ out[0] = c;
+ out[1] = s;
+ out[2] = 0;
+ out[3] = -s;
+ out[4] = c;
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 1;
+ return out;
+}
+/**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ * mat3.identity(dest);
+ * mat3.scale(dest, dest, vec);
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {ReadonlyVec2} v Scaling vector
+ * @returns {mat3} out
+ */
+
+export function fromScaling(out, v) {
+ out[0] = v[0];
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = v[1];
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 1;
+ return out;
+}
+/**
+ * Copies the values from a mat2d into a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat2d} a the matrix to copy
+ * @returns {mat3} out
+ **/
+
+export function fromMat2d(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = 0;
+ out[3] = a[2];
+ out[4] = a[3];
+ out[5] = 0;
+ out[6] = a[4];
+ out[7] = a[5];
+ out[8] = 1;
+ return out;
+}
+/**
+ * Calculates a 3x3 matrix from the given quaternion
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {ReadonlyQuat} q Quaternion to create matrix from
+ *
+ * @returns {mat3} out
+ */
+
+export function fromQuat(out, q) {
+ var x = q[0],
+ y = q[1],
+ z = q[2],
+ w = q[3];
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+ var xx = x * x2;
+ var yx = y * x2;
+ var yy = y * y2;
+ var zx = z * x2;
+ var zy = z * y2;
+ var zz = z * z2;
+ var wx = w * x2;
+ var wy = w * y2;
+ var wz = w * z2;
+ out[0] = 1 - yy - zz;
+ out[3] = yx - wz;
+ out[6] = zx + wy;
+ out[1] = yx + wz;
+ out[4] = 1 - xx - zz;
+ out[7] = zy - wx;
+ out[2] = zx - wy;
+ out[5] = zy + wx;
+ out[8] = 1 - xx - yy;
+ return out;
+}
+/**
+ * Calculates a 3x3 normal matrix (transpose inverse) from the 4x4 matrix
+ *
+ * @param {mat3} out mat3 receiving operation result
+ * @param {ReadonlyMat4} a Mat4 to derive the normal matrix from
+ *
+ * @returns {mat3} out
+ */
+
+export function normalFromMat4(out, a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a10 = a[4],
+ a11 = a[5],
+ a12 = a[6],
+ a13 = a[7];
+ var a20 = a[8],
+ a21 = a[9],
+ a22 = a[10],
+ a23 = a[11];
+ var a30 = a[12],
+ a31 = a[13],
+ a32 = a[14],
+ a33 = a[15];
+ var b00 = a00 * a11 - a01 * a10;
+ var b01 = a00 * a12 - a02 * a10;
+ var b02 = a00 * a13 - a03 * a10;
+ var b03 = a01 * a12 - a02 * a11;
+ var b04 = a01 * a13 - a03 * a11;
+ var b05 = a02 * a13 - a03 * a12;
+ var b06 = a20 * a31 - a21 * a30;
+ var b07 = a20 * a32 - a22 * a30;
+ var b08 = a20 * a33 - a23 * a30;
+ var b09 = a21 * a32 - a22 * a31;
+ var b10 = a21 * a33 - a23 * a31;
+ var b11 = a22 * a33 - a23 * a32; // Calculate the determinant
+
+ var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+
+ if (!det) {
+ return null;
+ }
+
+ det = 1.0 / det;
+ out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
+ out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
+ out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
+ out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
+ out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
+ out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
+ out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
+ out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
+ out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
+ return out;
+}
+/**
+ * Generates a 2D projection matrix with the given bounds
+ *
+ * @param {mat3} out mat3 frustum matrix will be written into
+ * @param {number} width Width of your gl context
+ * @param {number} height Height of gl context
+ * @returns {mat3} out
+ */
+
+export function projection(out, width, height) {
+ out[0] = 2 / width;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = -2 / height;
+ out[5] = 0;
+ out[6] = -1;
+ out[7] = 1;
+ out[8] = 1;
+ return out;
+}
+/**
+ * Returns a string representation of a mat3
+ *
+ * @param {ReadonlyMat3} a matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+
+export function str(a) {
+ return "mat3(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ", " + a[8] + ")";
+}
+/**
+ * Returns Frobenius norm of a mat3
+ *
+ * @param {ReadonlyMat3} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+
+export function frob(a) {
+ return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]);
+}
+/**
+ * Adds two mat3's
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the first operand
+ * @param {ReadonlyMat3} b the second operand
+ * @returns {mat3} out
+ */
+
+export function add(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ out[4] = a[4] + b[4];
+ out[5] = a[5] + b[5];
+ out[6] = a[6] + b[6];
+ out[7] = a[7] + b[7];
+ out[8] = a[8] + b[8];
+ return out;
+}
+/**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the first operand
+ * @param {ReadonlyMat3} b the second operand
+ * @returns {mat3} out
+ */
+
+export function subtract(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ out[3] = a[3] - b[3];
+ out[4] = a[4] - b[4];
+ out[5] = a[5] - b[5];
+ out[6] = a[6] - b[6];
+ out[7] = a[7] - b[7];
+ out[8] = a[8] - b[8];
+ return out;
+}
+/**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {ReadonlyMat3} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat3} out
+ */
+
+export function multiplyScalar(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ out[4] = a[4] * b;
+ out[5] = a[5] * b;
+ out[6] = a[6] * b;
+ out[7] = a[7] * b;
+ out[8] = a[8] * b;
+ return out;
+}
+/**
+ * Adds two mat3's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat3} out the receiving vector
+ * @param {ReadonlyMat3} a the first operand
+ * @param {ReadonlyMat3} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat3} out
+ */
+
+export function multiplyScalarAndAdd(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ out[3] = a[3] + b[3] * scale;
+ out[4] = a[4] + b[4] * scale;
+ out[5] = a[5] + b[5] * scale;
+ out[6] = a[6] + b[6] * scale;
+ out[7] = a[7] + b[7] * scale;
+ out[8] = a[8] + b[8] * scale;
+ return out;
+}
+/**
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyMat3} a The first matrix.
+ * @param {ReadonlyMat3} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+export function exactEquals(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && a[8] === b[8];
+}
+/**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyMat3} a The first matrix.
+ * @param {ReadonlyMat3} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+export function equals(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5],
+ a6 = a[6],
+ a7 = a[7],
+ a8 = a[8];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3],
+ b4 = b[4],
+ b5 = b[5],
+ b6 = b[6],
+ b7 = b[7],
+ b8 = b[8];
+ return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7)) && Math.abs(a8 - b8) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a8), Math.abs(b8));
+}
+/**
+ * Alias for {@link mat3.multiply}
+ * @function
+ */
+
+export var mul = multiply;
+/**
+ * Alias for {@link mat3.subtract}
+ * @function
+ */
+
+export var sub = subtract;
\ No newline at end of file
diff --git a/debug/webgpu/gl-matrix/mat4.js b/debug/webgpu/gl-matrix/mat4.js
new file mode 100644
index 0000000000..16203051c5
--- /dev/null
+++ b/debug/webgpu/gl-matrix/mat4.js
@@ -0,0 +1,1987 @@
+import * as glMatrix from "./common.js";
+/**
+ * 4x4 Matrix
Format: column-major, when typed out it looks like row-major
The matrices are being post multiplied.
+ * @module mat4
+ */
+
+/**
+ * Creates a new identity mat4
+ *
+ * @returns {mat4} a new 4x4 matrix
+ */
+
+export function create() {
+ var out = new glMatrix.ARRAY_TYPE(16);
+
+ if (glMatrix.ARRAY_TYPE != Float32Array) {
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ }
+
+ out[0] = 1;
+ out[5] = 1;
+ out[10] = 1;
+ out[15] = 1;
+ return out;
+}
+/**
+ * Creates a new mat4 initialized with values from an existing matrix
+ *
+ * @param {ReadonlyMat4} a matrix to clone
+ * @returns {mat4} a new 4x4 matrix
+ */
+
+export function clone(a) {
+ var out = new glMatrix.ARRAY_TYPE(16);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ out[9] = a[9];
+ out[10] = a[10];
+ out[11] = a[11];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ return out;
+}
+/**
+ * Copy the values from one mat4 to another
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the source matrix
+ * @returns {mat4} out
+ */
+
+export function copy(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ out[9] = a[9];
+ out[10] = a[10];
+ out[11] = a[11];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ return out;
+}
+/**
+ * Create a new mat4 with the given values
+ *
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m03 Component in column 0, row 3 position (index 3)
+ * @param {Number} m10 Component in column 1, row 0 position (index 4)
+ * @param {Number} m11 Component in column 1, row 1 position (index 5)
+ * @param {Number} m12 Component in column 1, row 2 position (index 6)
+ * @param {Number} m13 Component in column 1, row 3 position (index 7)
+ * @param {Number} m20 Component in column 2, row 0 position (index 8)
+ * @param {Number} m21 Component in column 2, row 1 position (index 9)
+ * @param {Number} m22 Component in column 2, row 2 position (index 10)
+ * @param {Number} m23 Component in column 2, row 3 position (index 11)
+ * @param {Number} m30 Component in column 3, row 0 position (index 12)
+ * @param {Number} m31 Component in column 3, row 1 position (index 13)
+ * @param {Number} m32 Component in column 3, row 2 position (index 14)
+ * @param {Number} m33 Component in column 3, row 3 position (index 15)
+ * @returns {mat4} A new mat4
+ */
+
+export function fromValues(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) {
+ var out = new glMatrix.ARRAY_TYPE(16);
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m02;
+ out[3] = m03;
+ out[4] = m10;
+ out[5] = m11;
+ out[6] = m12;
+ out[7] = m13;
+ out[8] = m20;
+ out[9] = m21;
+ out[10] = m22;
+ out[11] = m23;
+ out[12] = m30;
+ out[13] = m31;
+ out[14] = m32;
+ out[15] = m33;
+ return out;
+}
+/**
+ * Set the components of a mat4 to the given values
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {Number} m00 Component in column 0, row 0 position (index 0)
+ * @param {Number} m01 Component in column 0, row 1 position (index 1)
+ * @param {Number} m02 Component in column 0, row 2 position (index 2)
+ * @param {Number} m03 Component in column 0, row 3 position (index 3)
+ * @param {Number} m10 Component in column 1, row 0 position (index 4)
+ * @param {Number} m11 Component in column 1, row 1 position (index 5)
+ * @param {Number} m12 Component in column 1, row 2 position (index 6)
+ * @param {Number} m13 Component in column 1, row 3 position (index 7)
+ * @param {Number} m20 Component in column 2, row 0 position (index 8)
+ * @param {Number} m21 Component in column 2, row 1 position (index 9)
+ * @param {Number} m22 Component in column 2, row 2 position (index 10)
+ * @param {Number} m23 Component in column 2, row 3 position (index 11)
+ * @param {Number} m30 Component in column 3, row 0 position (index 12)
+ * @param {Number} m31 Component in column 3, row 1 position (index 13)
+ * @param {Number} m32 Component in column 3, row 2 position (index 14)
+ * @param {Number} m33 Component in column 3, row 3 position (index 15)
+ * @returns {mat4} out
+ */
+
+export function set(out, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) {
+ out[0] = m00;
+ out[1] = m01;
+ out[2] = m02;
+ out[3] = m03;
+ out[4] = m10;
+ out[5] = m11;
+ out[6] = m12;
+ out[7] = m13;
+ out[8] = m20;
+ out[9] = m21;
+ out[10] = m22;
+ out[11] = m23;
+ out[12] = m30;
+ out[13] = m31;
+ out[14] = m32;
+ out[15] = m33;
+ return out;
+}
+/**
+ * Set a mat4 to the identity matrix
+ *
+ * @param {mat4} out the receiving matrix
+ * @returns {mat4} out
+ */
+
+export function identity(out) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = 1;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = 1;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+}
+/**
+ * Transpose the values of a mat4
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the source matrix
+ * @returns {mat4} out
+ */
+
+export function transpose(out, a) {
+ // If we are transposing ourselves we can skip a few steps but have to cache some values
+ if (out === a) {
+ var a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a12 = a[6],
+ a13 = a[7];
+ var a23 = a[11];
+ out[1] = a[4];
+ out[2] = a[8];
+ out[3] = a[12];
+ out[4] = a01;
+ out[6] = a[9];
+ out[7] = a[13];
+ out[8] = a02;
+ out[9] = a12;
+ out[11] = a[14];
+ out[12] = a03;
+ out[13] = a13;
+ out[14] = a23;
+ } else {
+ out[0] = a[0];
+ out[1] = a[4];
+ out[2] = a[8];
+ out[3] = a[12];
+ out[4] = a[1];
+ out[5] = a[5];
+ out[6] = a[9];
+ out[7] = a[13];
+ out[8] = a[2];
+ out[9] = a[6];
+ out[10] = a[10];
+ out[11] = a[14];
+ out[12] = a[3];
+ out[13] = a[7];
+ out[14] = a[11];
+ out[15] = a[15];
+ }
+
+ return out;
+}
+/**
+ * Inverts a mat4
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the source matrix
+ * @returns {mat4} out
+ */
+
+export function invert(out, a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a10 = a[4],
+ a11 = a[5],
+ a12 = a[6],
+ a13 = a[7];
+ var a20 = a[8],
+ a21 = a[9],
+ a22 = a[10],
+ a23 = a[11];
+ var a30 = a[12],
+ a31 = a[13],
+ a32 = a[14],
+ a33 = a[15];
+ var b00 = a00 * a11 - a01 * a10;
+ var b01 = a00 * a12 - a02 * a10;
+ var b02 = a00 * a13 - a03 * a10;
+ var b03 = a01 * a12 - a02 * a11;
+ var b04 = a01 * a13 - a03 * a11;
+ var b05 = a02 * a13 - a03 * a12;
+ var b06 = a20 * a31 - a21 * a30;
+ var b07 = a20 * a32 - a22 * a30;
+ var b08 = a20 * a33 - a23 * a30;
+ var b09 = a21 * a32 - a22 * a31;
+ var b10 = a21 * a33 - a23 * a31;
+ var b11 = a22 * a33 - a23 * a32; // Calculate the determinant
+
+ var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+
+ if (!det) {
+ return null;
+ }
+
+ det = 1.0 / det;
+ out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
+ out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
+ out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
+ out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
+ out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
+ out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
+ out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
+ out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
+ out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
+ out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
+ out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
+ out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
+ out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
+ out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
+ out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
+ out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
+ return out;
+}
+/**
+ * Calculates the adjugate of a mat4
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the source matrix
+ * @returns {mat4} out
+ */
+
+export function adjoint(out, a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a10 = a[4],
+ a11 = a[5],
+ a12 = a[6],
+ a13 = a[7];
+ var a20 = a[8],
+ a21 = a[9],
+ a22 = a[10],
+ a23 = a[11];
+ var a30 = a[12],
+ a31 = a[13],
+ a32 = a[14],
+ a33 = a[15];
+ var b00 = a00 * a11 - a01 * a10;
+ var b01 = a00 * a12 - a02 * a10;
+ var b02 = a00 * a13 - a03 * a10;
+ var b03 = a01 * a12 - a02 * a11;
+ var b04 = a01 * a13 - a03 * a11;
+ var b05 = a02 * a13 - a03 * a12;
+ var b06 = a20 * a31 - a21 * a30;
+ var b07 = a20 * a32 - a22 * a30;
+ var b08 = a20 * a33 - a23 * a30;
+ var b09 = a21 * a32 - a22 * a31;
+ var b10 = a21 * a33 - a23 * a31;
+ var b11 = a22 * a33 - a23 * a32;
+ out[0] = a11 * b11 - a12 * b10 + a13 * b09;
+ out[1] = a02 * b10 - a01 * b11 - a03 * b09;
+ out[2] = a31 * b05 - a32 * b04 + a33 * b03;
+ out[3] = a22 * b04 - a21 * b05 - a23 * b03;
+ out[4] = a12 * b08 - a10 * b11 - a13 * b07;
+ out[5] = a00 * b11 - a02 * b08 + a03 * b07;
+ out[6] = a32 * b02 - a30 * b05 - a33 * b01;
+ out[7] = a20 * b05 - a22 * b02 + a23 * b01;
+ out[8] = a10 * b10 - a11 * b08 + a13 * b06;
+ out[9] = a01 * b08 - a00 * b10 - a03 * b06;
+ out[10] = a30 * b04 - a31 * b02 + a33 * b00;
+ out[11] = a21 * b02 - a20 * b04 - a23 * b00;
+ out[12] = a11 * b07 - a10 * b09 - a12 * b06;
+ out[13] = a00 * b09 - a01 * b07 + a02 * b06;
+ out[14] = a31 * b01 - a30 * b03 - a32 * b00;
+ out[15] = a20 * b03 - a21 * b01 + a22 * b00;
+ return out;
+}
+/**
+ * Calculates the determinant of a mat4
+ *
+ * @param {ReadonlyMat4} a the source matrix
+ * @returns {Number} determinant of a
+ */
+
+export function determinant(a) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a10 = a[4],
+ a11 = a[5],
+ a12 = a[6],
+ a13 = a[7];
+ var a20 = a[8],
+ a21 = a[9],
+ a22 = a[10],
+ a23 = a[11];
+ var a30 = a[12],
+ a31 = a[13],
+ a32 = a[14],
+ a33 = a[15];
+ var b0 = a00 * a11 - a01 * a10;
+ var b1 = a00 * a12 - a02 * a10;
+ var b2 = a01 * a12 - a02 * a11;
+ var b3 = a20 * a31 - a21 * a30;
+ var b4 = a20 * a32 - a22 * a30;
+ var b5 = a21 * a32 - a22 * a31;
+ var b6 = a00 * b5 - a01 * b4 + a02 * b3;
+ var b7 = a10 * b5 - a11 * b4 + a12 * b3;
+ var b8 = a20 * b2 - a21 * b1 + a22 * b0;
+ var b9 = a30 * b2 - a31 * b1 + a32 * b0; // Calculate the determinant
+
+ return a13 * b6 - a03 * b7 + a33 * b8 - a23 * b9;
+}
+/**
+ * Multiplies two mat4s
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the first operand
+ * @param {ReadonlyMat4} b the second operand
+ * @returns {mat4} out
+ */
+
+export function multiply(out, a, b) {
+ var a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a03 = a[3];
+ var a10 = a[4],
+ a11 = a[5],
+ a12 = a[6],
+ a13 = a[7];
+ var a20 = a[8],
+ a21 = a[9],
+ a22 = a[10],
+ a23 = a[11];
+ var a30 = a[12],
+ a31 = a[13],
+ a32 = a[14],
+ a33 = a[15]; // Cache only the current line of the second matrix
+
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3];
+ out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+ b0 = b[4];
+ b1 = b[5];
+ b2 = b[6];
+ b3 = b[7];
+ out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+ b0 = b[8];
+ b1 = b[9];
+ b2 = b[10];
+ b3 = b[11];
+ out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+ b0 = b[12];
+ b1 = b[13];
+ b2 = b[14];
+ b3 = b[15];
+ out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+ return out;
+}
+/**
+ * Translate a mat4 by the given vector
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to translate
+ * @param {ReadonlyVec3} v vector to translate by
+ * @returns {mat4} out
+ */
+
+export function translate(out, a, v) {
+ var x = v[0],
+ y = v[1],
+ z = v[2];
+ var a00, a01, a02, a03;
+ var a10, a11, a12, a13;
+ var a20, a21, a22, a23;
+
+ if (a === out) {
+ out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
+ out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
+ out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
+ out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
+ } else {
+ a00 = a[0];
+ a01 = a[1];
+ a02 = a[2];
+ a03 = a[3];
+ a10 = a[4];
+ a11 = a[5];
+ a12 = a[6];
+ a13 = a[7];
+ a20 = a[8];
+ a21 = a[9];
+ a22 = a[10];
+ a23 = a[11];
+ out[0] = a00;
+ out[1] = a01;
+ out[2] = a02;
+ out[3] = a03;
+ out[4] = a10;
+ out[5] = a11;
+ out[6] = a12;
+ out[7] = a13;
+ out[8] = a20;
+ out[9] = a21;
+ out[10] = a22;
+ out[11] = a23;
+ out[12] = a00 * x + a10 * y + a20 * z + a[12];
+ out[13] = a01 * x + a11 * y + a21 * z + a[13];
+ out[14] = a02 * x + a12 * y + a22 * z + a[14];
+ out[15] = a03 * x + a13 * y + a23 * z + a[15];
+ }
+
+ return out;
+}
+/**
+ * Scales the mat4 by the dimensions in the given vec3 not using vectorization
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to scale
+ * @param {ReadonlyVec3} v the vec3 to scale the matrix by
+ * @returns {mat4} out
+ **/
+
+export function scale(out, a, v) {
+ var x = v[0],
+ y = v[1],
+ z = v[2];
+ out[0] = a[0] * x;
+ out[1] = a[1] * x;
+ out[2] = a[2] * x;
+ out[3] = a[3] * x;
+ out[4] = a[4] * y;
+ out[5] = a[5] * y;
+ out[6] = a[6] * y;
+ out[7] = a[7] * y;
+ out[8] = a[8] * z;
+ out[9] = a[9] * z;
+ out[10] = a[10] * z;
+ out[11] = a[11] * z;
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ return out;
+}
+/**
+ * Rotates a mat4 by the given angle around the given axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @param {ReadonlyVec3} axis the axis to rotate around
+ * @returns {mat4} out
+ */
+
+export function rotate(out, a, rad, axis) {
+ var x = axis[0],
+ y = axis[1],
+ z = axis[2];
+ var len = Math.hypot(x, y, z);
+ var s, c, t;
+ var a00, a01, a02, a03;
+ var a10, a11, a12, a13;
+ var a20, a21, a22, a23;
+ var b00, b01, b02;
+ var b10, b11, b12;
+ var b20, b21, b22;
+
+ if (len < glMatrix.EPSILON) {
+ return null;
+ }
+
+ len = 1 / len;
+ x *= len;
+ y *= len;
+ z *= len;
+ s = Math.sin(rad);
+ c = Math.cos(rad);
+ t = 1 - c;
+ a00 = a[0];
+ a01 = a[1];
+ a02 = a[2];
+ a03 = a[3];
+ a10 = a[4];
+ a11 = a[5];
+ a12 = a[6];
+ a13 = a[7];
+ a20 = a[8];
+ a21 = a[9];
+ a22 = a[10];
+ a23 = a[11]; // Construct the elements of the rotation matrix
+
+ b00 = x * x * t + c;
+ b01 = y * x * t + z * s;
+ b02 = z * x * t - y * s;
+ b10 = x * y * t - z * s;
+ b11 = y * y * t + c;
+ b12 = z * y * t + x * s;
+ b20 = x * z * t + y * s;
+ b21 = y * z * t - x * s;
+ b22 = z * z * t + c; // Perform rotation-specific matrix multiplication
+
+ out[0] = a00 * b00 + a10 * b01 + a20 * b02;
+ out[1] = a01 * b00 + a11 * b01 + a21 * b02;
+ out[2] = a02 * b00 + a12 * b01 + a22 * b02;
+ out[3] = a03 * b00 + a13 * b01 + a23 * b02;
+ out[4] = a00 * b10 + a10 * b11 + a20 * b12;
+ out[5] = a01 * b10 + a11 * b11 + a21 * b12;
+ out[6] = a02 * b10 + a12 * b11 + a22 * b12;
+ out[7] = a03 * b10 + a13 * b11 + a23 * b12;
+ out[8] = a00 * b20 + a10 * b21 + a20 * b22;
+ out[9] = a01 * b20 + a11 * b21 + a21 * b22;
+ out[10] = a02 * b20 + a12 * b21 + a22 * b22;
+ out[11] = a03 * b20 + a13 * b21 + a23 * b22;
+
+ if (a !== out) {
+ // If the source and destination differ, copy the unchanged last row
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ }
+
+ return out;
+}
+/**
+ * Rotates a matrix by the given angle around the X axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+export function rotateX(out, a, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ var a10 = a[4];
+ var a11 = a[5];
+ var a12 = a[6];
+ var a13 = a[7];
+ var a20 = a[8];
+ var a21 = a[9];
+ var a22 = a[10];
+ var a23 = a[11];
+
+ if (a !== out) {
+ // If the source and destination differ, copy the unchanged rows
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ } // Perform axis-specific matrix multiplication
+
+
+ out[4] = a10 * c + a20 * s;
+ out[5] = a11 * c + a21 * s;
+ out[6] = a12 * c + a22 * s;
+ out[7] = a13 * c + a23 * s;
+ out[8] = a20 * c - a10 * s;
+ out[9] = a21 * c - a11 * s;
+ out[10] = a22 * c - a12 * s;
+ out[11] = a23 * c - a13 * s;
+ return out;
+}
+/**
+ * Rotates a matrix by the given angle around the Y axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+export function rotateY(out, a, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ var a00 = a[0];
+ var a01 = a[1];
+ var a02 = a[2];
+ var a03 = a[3];
+ var a20 = a[8];
+ var a21 = a[9];
+ var a22 = a[10];
+ var a23 = a[11];
+
+ if (a !== out) {
+ // If the source and destination differ, copy the unchanged rows
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ } // Perform axis-specific matrix multiplication
+
+
+ out[0] = a00 * c - a20 * s;
+ out[1] = a01 * c - a21 * s;
+ out[2] = a02 * c - a22 * s;
+ out[3] = a03 * c - a23 * s;
+ out[8] = a00 * s + a20 * c;
+ out[9] = a01 * s + a21 * c;
+ out[10] = a02 * s + a22 * c;
+ out[11] = a03 * s + a23 * c;
+ return out;
+}
+/**
+ * Rotates a matrix by the given angle around the Z axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+export function rotateZ(out, a, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad);
+ var a00 = a[0];
+ var a01 = a[1];
+ var a02 = a[2];
+ var a03 = a[3];
+ var a10 = a[4];
+ var a11 = a[5];
+ var a12 = a[6];
+ var a13 = a[7];
+
+ if (a !== out) {
+ // If the source and destination differ, copy the unchanged last row
+ out[8] = a[8];
+ out[9] = a[9];
+ out[10] = a[10];
+ out[11] = a[11];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ } // Perform axis-specific matrix multiplication
+
+
+ out[0] = a00 * c + a10 * s;
+ out[1] = a01 * c + a11 * s;
+ out[2] = a02 * c + a12 * s;
+ out[3] = a03 * c + a13 * s;
+ out[4] = a10 * c - a00 * s;
+ out[5] = a11 * c - a01 * s;
+ out[6] = a12 * c - a02 * s;
+ out[7] = a13 * c - a03 * s;
+ return out;
+}
+/**
+ * Creates a matrix from a vector translation
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.translate(dest, dest, vec);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {ReadonlyVec3} v Translation vector
+ * @returns {mat4} out
+ */
+
+export function fromTranslation(out, v) {
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = 1;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = 1;
+ out[11] = 0;
+ out[12] = v[0];
+ out[13] = v[1];
+ out[14] = v[2];
+ out[15] = 1;
+ return out;
+}
+/**
+ * Creates a matrix from a vector scaling
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.scale(dest, dest, vec);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {ReadonlyVec3} v Scaling vector
+ * @returns {mat4} out
+ */
+
+export function fromScaling(out, v) {
+ out[0] = v[0];
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = v[1];
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = v[2];
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+}
+/**
+ * Creates a matrix from a given angle around a given axis
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.rotate(dest, dest, rad, axis);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @param {ReadonlyVec3} axis the axis to rotate around
+ * @returns {mat4} out
+ */
+
+export function fromRotation(out, rad, axis) {
+ var x = axis[0],
+ y = axis[1],
+ z = axis[2];
+ var len = Math.hypot(x, y, z);
+ var s, c, t;
+
+ if (len < glMatrix.EPSILON) {
+ return null;
+ }
+
+ len = 1 / len;
+ x *= len;
+ y *= len;
+ z *= len;
+ s = Math.sin(rad);
+ c = Math.cos(rad);
+ t = 1 - c; // Perform rotation-specific matrix multiplication
+
+ out[0] = x * x * t + c;
+ out[1] = y * x * t + z * s;
+ out[2] = z * x * t - y * s;
+ out[3] = 0;
+ out[4] = x * y * t - z * s;
+ out[5] = y * y * t + c;
+ out[6] = z * y * t + x * s;
+ out[7] = 0;
+ out[8] = x * z * t + y * s;
+ out[9] = y * z * t - x * s;
+ out[10] = z * z * t + c;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+}
+/**
+ * Creates a matrix from the given angle around the X axis
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.rotateX(dest, dest, rad);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+export function fromXRotation(out, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad); // Perform axis-specific matrix multiplication
+
+ out[0] = 1;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = c;
+ out[6] = s;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = -s;
+ out[10] = c;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+}
+/**
+ * Creates a matrix from the given angle around the Y axis
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.rotateY(dest, dest, rad);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+export function fromYRotation(out, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad); // Perform axis-specific matrix multiplication
+
+ out[0] = c;
+ out[1] = 0;
+ out[2] = -s;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = 1;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = s;
+ out[9] = 0;
+ out[10] = c;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+}
+/**
+ * Creates a matrix from the given angle around the Z axis
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.rotateZ(dest, dest, rad);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+
+export function fromZRotation(out, rad) {
+ var s = Math.sin(rad);
+ var c = Math.cos(rad); // Perform axis-specific matrix multiplication
+
+ out[0] = c;
+ out[1] = s;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = -s;
+ out[5] = c;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = 1;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+}
+/**
+ * Creates a matrix from a quaternion rotation and vector translation
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.translate(dest, vec);
+ * let quatMat = mat4.create();
+ * quat4.toMat4(quat, quatMat);
+ * mat4.multiply(dest, quatMat);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {quat4} q Rotation quaternion
+ * @param {ReadonlyVec3} v Translation vector
+ * @returns {mat4} out
+ */
+
+export function fromRotationTranslation(out, q, v) {
+ // Quaternion math
+ var x = q[0],
+ y = q[1],
+ z = q[2],
+ w = q[3];
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+ var xx = x * x2;
+ var xy = x * y2;
+ var xz = x * z2;
+ var yy = y * y2;
+ var yz = y * z2;
+ var zz = z * z2;
+ var wx = w * x2;
+ var wy = w * y2;
+ var wz = w * z2;
+ out[0] = 1 - (yy + zz);
+ out[1] = xy + wz;
+ out[2] = xz - wy;
+ out[3] = 0;
+ out[4] = xy - wz;
+ out[5] = 1 - (xx + zz);
+ out[6] = yz + wx;
+ out[7] = 0;
+ out[8] = xz + wy;
+ out[9] = yz - wx;
+ out[10] = 1 - (xx + yy);
+ out[11] = 0;
+ out[12] = v[0];
+ out[13] = v[1];
+ out[14] = v[2];
+ out[15] = 1;
+ return out;
+}
+/**
+ * Creates a new mat4 from a dual quat.
+ *
+ * @param {mat4} out Matrix
+ * @param {ReadonlyQuat2} a Dual Quaternion
+ * @returns {mat4} mat4 receiving operation result
+ */
+
+export function fromQuat2(out, a) {
+ var translation = new glMatrix.ARRAY_TYPE(3);
+ var bx = -a[0],
+ by = -a[1],
+ bz = -a[2],
+ bw = a[3],
+ ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7];
+ var magnitude = bx * bx + by * by + bz * bz + bw * bw; //Only scale if it makes sense
+
+ if (magnitude > 0) {
+ translation[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2 / magnitude;
+ translation[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2 / magnitude;
+ translation[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2 / magnitude;
+ } else {
+ translation[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2;
+ translation[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2;
+ translation[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2;
+ }
+
+ fromRotationTranslation(out, a, translation);
+ return out;
+}
+/**
+ * Returns the translation vector component of a transformation
+ * matrix. If a matrix is built with fromRotationTranslation,
+ * the returned vector will be the same as the translation vector
+ * originally supplied.
+ * @param {vec3} out Vector to receive translation component
+ * @param {ReadonlyMat4} mat Matrix to be decomposed (input)
+ * @return {vec3} out
+ */
+
+export function getTranslation(out, mat) {
+ out[0] = mat[12];
+ out[1] = mat[13];
+ out[2] = mat[14];
+ return out;
+}
+/**
+ * Returns the scaling factor component of a transformation
+ * matrix. If a matrix is built with fromRotationTranslationScale
+ * with a normalized Quaternion paramter, the returned vector will be
+ * the same as the scaling vector
+ * originally supplied.
+ * @param {vec3} out Vector to receive scaling factor component
+ * @param {ReadonlyMat4} mat Matrix to be decomposed (input)
+ * @return {vec3} out
+ */
+
+export function getScaling(out, mat) {
+ var m11 = mat[0];
+ var m12 = mat[1];
+ var m13 = mat[2];
+ var m21 = mat[4];
+ var m22 = mat[5];
+ var m23 = mat[6];
+ var m31 = mat[8];
+ var m32 = mat[9];
+ var m33 = mat[10];
+ out[0] = Math.hypot(m11, m12, m13);
+ out[1] = Math.hypot(m21, m22, m23);
+ out[2] = Math.hypot(m31, m32, m33);
+ return out;
+}
+/**
+ * Returns a quaternion representing the rotational component
+ * of a transformation matrix. If a matrix is built with
+ * fromRotationTranslation, the returned quaternion will be the
+ * same as the quaternion originally supplied.
+ * @param {quat} out Quaternion to receive the rotation component
+ * @param {ReadonlyMat4} mat Matrix to be decomposed (input)
+ * @return {quat} out
+ */
+
+export function getRotation(out, mat) {
+ var scaling = new glMatrix.ARRAY_TYPE(3);
+ getScaling(scaling, mat);
+ var is1 = 1 / scaling[0];
+ var is2 = 1 / scaling[1];
+ var is3 = 1 / scaling[2];
+ var sm11 = mat[0] * is1;
+ var sm12 = mat[1] * is2;
+ var sm13 = mat[2] * is3;
+ var sm21 = mat[4] * is1;
+ var sm22 = mat[5] * is2;
+ var sm23 = mat[6] * is3;
+ var sm31 = mat[8] * is1;
+ var sm32 = mat[9] * is2;
+ var sm33 = mat[10] * is3;
+ var trace = sm11 + sm22 + sm33;
+ var S = 0;
+
+ if (trace > 0) {
+ S = Math.sqrt(trace + 1.0) * 2;
+ out[3] = 0.25 * S;
+ out[0] = (sm23 - sm32) / S;
+ out[1] = (sm31 - sm13) / S;
+ out[2] = (sm12 - sm21) / S;
+ } else if (sm11 > sm22 && sm11 > sm33) {
+ S = Math.sqrt(1.0 + sm11 - sm22 - sm33) * 2;
+ out[3] = (sm23 - sm32) / S;
+ out[0] = 0.25 * S;
+ out[1] = (sm12 + sm21) / S;
+ out[2] = (sm31 + sm13) / S;
+ } else if (sm22 > sm33) {
+ S = Math.sqrt(1.0 + sm22 - sm11 - sm33) * 2;
+ out[3] = (sm31 - sm13) / S;
+ out[0] = (sm12 + sm21) / S;
+ out[1] = 0.25 * S;
+ out[2] = (sm23 + sm32) / S;
+ } else {
+ S = Math.sqrt(1.0 + sm33 - sm11 - sm22) * 2;
+ out[3] = (sm12 - sm21) / S;
+ out[0] = (sm31 + sm13) / S;
+ out[1] = (sm23 + sm32) / S;
+ out[2] = 0.25 * S;
+ }
+
+ return out;
+}
+/**
+ * Decomposes a transformation matrix into its rotation, translation
+ * and scale components. Returns only the rotation component
+ * @param {quat} out_r Quaternion to receive the rotation component
+ * @param {vec3} out_t Vector to receive the translation vector
+ * @param {vec3} out_s Vector to receive the scaling factor
+ * @param {ReadonlyMat4} mat Matrix to be decomposed (input)
+ * @returns {quat} out_r
+ */
+
+export function decompose(out_r, out_t, out_s, mat) {
+ out_t[0] = mat[12];
+ out_t[1] = mat[13];
+ out_t[2] = mat[14];
+ var m11 = mat[0];
+ var m12 = mat[1];
+ var m13 = mat[2];
+ var m21 = mat[4];
+ var m22 = mat[5];
+ var m23 = mat[6];
+ var m31 = mat[8];
+ var m32 = mat[9];
+ var m33 = mat[10];
+ out_s[0] = Math.hypot(m11, m12, m13);
+ out_s[1] = Math.hypot(m21, m22, m23);
+ out_s[2] = Math.hypot(m31, m32, m33);
+ var is1 = 1 / out_s[0];
+ var is2 = 1 / out_s[1];
+ var is3 = 1 / out_s[2];
+ var sm11 = m11 * is1;
+ var sm12 = m12 * is2;
+ var sm13 = m13 * is3;
+ var sm21 = m21 * is1;
+ var sm22 = m22 * is2;
+ var sm23 = m23 * is3;
+ var sm31 = m31 * is1;
+ var sm32 = m32 * is2;
+ var sm33 = m33 * is3;
+ var trace = sm11 + sm22 + sm33;
+ var S = 0;
+
+ if (trace > 0) {
+ S = Math.sqrt(trace + 1.0) * 2;
+ out_r[3] = 0.25 * S;
+ out_r[0] = (sm23 - sm32) / S;
+ out_r[1] = (sm31 - sm13) / S;
+ out_r[2] = (sm12 - sm21) / S;
+ } else if (sm11 > sm22 && sm11 > sm33) {
+ S = Math.sqrt(1.0 + sm11 - sm22 - sm33) * 2;
+ out_r[3] = (sm23 - sm32) / S;
+ out_r[0] = 0.25 * S;
+ out_r[1] = (sm12 + sm21) / S;
+ out_r[2] = (sm31 + sm13) / S;
+ } else if (sm22 > sm33) {
+ S = Math.sqrt(1.0 + sm22 - sm11 - sm33) * 2;
+ out_r[3] = (sm31 - sm13) / S;
+ out_r[0] = (sm12 + sm21) / S;
+ out_r[1] = 0.25 * S;
+ out_r[2] = (sm23 + sm32) / S;
+ } else {
+ S = Math.sqrt(1.0 + sm33 - sm11 - sm22) * 2;
+ out_r[3] = (sm12 - sm21) / S;
+ out_r[0] = (sm31 + sm13) / S;
+ out_r[1] = (sm23 + sm32) / S;
+ out_r[2] = 0.25 * S;
+ }
+
+ return out_r;
+}
+/**
+ * Creates a matrix from a quaternion rotation, vector translation and vector scale
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.translate(dest, vec);
+ * let quatMat = mat4.create();
+ * quat4.toMat4(quat, quatMat);
+ * mat4.multiply(dest, quatMat);
+ * mat4.scale(dest, scale)
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {quat4} q Rotation quaternion
+ * @param {ReadonlyVec3} v Translation vector
+ * @param {ReadonlyVec3} s Scaling vector
+ * @returns {mat4} out
+ */
+
+export function fromRotationTranslationScale(out, q, v, s) {
+ // Quaternion math
+ var x = q[0],
+ y = q[1],
+ z = q[2],
+ w = q[3];
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+ var xx = x * x2;
+ var xy = x * y2;
+ var xz = x * z2;
+ var yy = y * y2;
+ var yz = y * z2;
+ var zz = z * z2;
+ var wx = w * x2;
+ var wy = w * y2;
+ var wz = w * z2;
+ var sx = s[0];
+ var sy = s[1];
+ var sz = s[2];
+ out[0] = (1 - (yy + zz)) * sx;
+ out[1] = (xy + wz) * sx;
+ out[2] = (xz - wy) * sx;
+ out[3] = 0;
+ out[4] = (xy - wz) * sy;
+ out[5] = (1 - (xx + zz)) * sy;
+ out[6] = (yz + wx) * sy;
+ out[7] = 0;
+ out[8] = (xz + wy) * sz;
+ out[9] = (yz - wx) * sz;
+ out[10] = (1 - (xx + yy)) * sz;
+ out[11] = 0;
+ out[12] = v[0];
+ out[13] = v[1];
+ out[14] = v[2];
+ out[15] = 1;
+ return out;
+}
+/**
+ * Creates a matrix from a quaternion rotation, vector translation and vector scale, rotating and scaling around the given origin
+ * This is equivalent to (but much faster than):
+ *
+ * mat4.identity(dest);
+ * mat4.translate(dest, vec);
+ * mat4.translate(dest, origin);
+ * let quatMat = mat4.create();
+ * quat4.toMat4(quat, quatMat);
+ * mat4.multiply(dest, quatMat);
+ * mat4.scale(dest, scale)
+ * mat4.translate(dest, negativeOrigin);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {quat4} q Rotation quaternion
+ * @param {ReadonlyVec3} v Translation vector
+ * @param {ReadonlyVec3} s Scaling vector
+ * @param {ReadonlyVec3} o The origin vector around which to scale and rotate
+ * @returns {mat4} out
+ */
+
+export function fromRotationTranslationScaleOrigin(out, q, v, s, o) {
+ // Quaternion math
+ var x = q[0],
+ y = q[1],
+ z = q[2],
+ w = q[3];
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+ var xx = x * x2;
+ var xy = x * y2;
+ var xz = x * z2;
+ var yy = y * y2;
+ var yz = y * z2;
+ var zz = z * z2;
+ var wx = w * x2;
+ var wy = w * y2;
+ var wz = w * z2;
+ var sx = s[0];
+ var sy = s[1];
+ var sz = s[2];
+ var ox = o[0];
+ var oy = o[1];
+ var oz = o[2];
+ var out0 = (1 - (yy + zz)) * sx;
+ var out1 = (xy + wz) * sx;
+ var out2 = (xz - wy) * sx;
+ var out4 = (xy - wz) * sy;
+ var out5 = (1 - (xx + zz)) * sy;
+ var out6 = (yz + wx) * sy;
+ var out8 = (xz + wy) * sz;
+ var out9 = (yz - wx) * sz;
+ var out10 = (1 - (xx + yy)) * sz;
+ out[0] = out0;
+ out[1] = out1;
+ out[2] = out2;
+ out[3] = 0;
+ out[4] = out4;
+ out[5] = out5;
+ out[6] = out6;
+ out[7] = 0;
+ out[8] = out8;
+ out[9] = out9;
+ out[10] = out10;
+ out[11] = 0;
+ out[12] = v[0] + ox - (out0 * ox + out4 * oy + out8 * oz);
+ out[13] = v[1] + oy - (out1 * ox + out5 * oy + out9 * oz);
+ out[14] = v[2] + oz - (out2 * ox + out6 * oy + out10 * oz);
+ out[15] = 1;
+ return out;
+}
+/**
+ * Calculates a 4x4 matrix from the given quaternion
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {ReadonlyQuat} q Quaternion to create matrix from
+ *
+ * @returns {mat4} out
+ */
+
+export function fromQuat(out, q) {
+ var x = q[0],
+ y = q[1],
+ z = q[2],
+ w = q[3];
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+ var xx = x * x2;
+ var yx = y * x2;
+ var yy = y * y2;
+ var zx = z * x2;
+ var zy = z * y2;
+ var zz = z * z2;
+ var wx = w * x2;
+ var wy = w * y2;
+ var wz = w * z2;
+ out[0] = 1 - yy - zz;
+ out[1] = yx + wz;
+ out[2] = zx - wy;
+ out[3] = 0;
+ out[4] = yx - wz;
+ out[5] = 1 - xx - zz;
+ out[6] = zy + wx;
+ out[7] = 0;
+ out[8] = zx + wy;
+ out[9] = zy - wx;
+ out[10] = 1 - xx - yy;
+ out[11] = 0;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = 0;
+ out[15] = 1;
+ return out;
+}
+/**
+ * Generates a frustum matrix with the given bounds
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {Number} left Left bound of the frustum
+ * @param {Number} right Right bound of the frustum
+ * @param {Number} bottom Bottom bound of the frustum
+ * @param {Number} top Top bound of the frustum
+ * @param {Number} near Near bound of the frustum
+ * @param {Number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+
+export function frustum(out, left, right, bottom, top, near, far) {
+ var rl = 1 / (right - left);
+ var tb = 1 / (top - bottom);
+ var nf = 1 / (near - far);
+ out[0] = near * 2 * rl;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = near * 2 * tb;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = (right + left) * rl;
+ out[9] = (top + bottom) * tb;
+ out[10] = (far + near) * nf;
+ out[11] = -1;
+ out[12] = 0;
+ out[13] = 0;
+ out[14] = far * near * 2 * nf;
+ out[15] = 0;
+ return out;
+}
+/**
+ * Generates a perspective projection matrix with the given bounds.
+ * The near/far clip planes correspond to a normalized device coordinate Z range of [-1, 1],
+ * which matches WebGL/OpenGL's clip volume.
+ * Passing null/undefined/no value for far will generate infinite projection matrix.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} fovy Vertical field of view in radians
+ * @param {number} aspect Aspect ratio. typically viewport width/height
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum, can be null or Infinity
+ * @returns {mat4} out
+ */
+
+export function perspectiveNO(out, fovy, aspect, near, far) {
+ var f = 1.0 / Math.tan(fovy / 2);
+ out[0] = f / aspect;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = f;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[11] = -1;
+ out[12] = 0;
+ out[13] = 0;
+ out[15] = 0;
+
+ if (far != null && far !== Infinity) {
+ var nf = 1 / (near - far);
+ out[10] = (far + near) * nf;
+ out[14] = 2 * far * near * nf;
+ } else {
+ out[10] = -1;
+ out[14] = -2 * near;
+ }
+
+ return out;
+}
+/**
+ * Alias for {@link mat4.perspectiveNO}
+ * @function
+ */
+
+export var perspective = perspectiveNO;
+/**
+ * Generates a perspective projection matrix suitable for WebGPU with the given bounds.
+ * The near/far clip planes correspond to a normalized device coordinate Z range of [0, 1],
+ * which matches WebGPU/Vulkan/DirectX/Metal's clip volume.
+ * Passing null/undefined/no value for far will generate infinite projection matrix.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} fovy Vertical field of view in radians
+ * @param {number} aspect Aspect ratio. typically viewport width/height
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum, can be null or Infinity
+ * @returns {mat4} out
+ */
+
+export function perspectiveZO(out, fovy, aspect, near, far) {
+ var f = 1.0 / Math.tan(fovy / 2);
+ out[0] = f / aspect;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = f;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[11] = -1;
+ out[12] = 0;
+ out[13] = 0;
+ out[15] = 0;
+
+ if (far != null && far !== Infinity) {
+ var nf = 1 / (near - far);
+ out[10] = far * nf;
+ out[14] = far * near * nf;
+ } else {
+ out[10] = -1;
+ out[14] = -near;
+ }
+
+ return out;
+}
+/**
+ * Generates a perspective projection matrix with the given field of view.
+ * This is primarily useful for generating projection matrices to be used
+ * with the still experiemental WebVR API.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {Object} fov Object containing the following values: upDegrees, downDegrees, leftDegrees, rightDegrees
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+
+export function perspectiveFromFieldOfView(out, fov, near, far) {
+ var upTan = Math.tan(fov.upDegrees * Math.PI / 180.0);
+ var downTan = Math.tan(fov.downDegrees * Math.PI / 180.0);
+ var leftTan = Math.tan(fov.leftDegrees * Math.PI / 180.0);
+ var rightTan = Math.tan(fov.rightDegrees * Math.PI / 180.0);
+ var xScale = 2.0 / (leftTan + rightTan);
+ var yScale = 2.0 / (upTan + downTan);
+ out[0] = xScale;
+ out[1] = 0.0;
+ out[2] = 0.0;
+ out[3] = 0.0;
+ out[4] = 0.0;
+ out[5] = yScale;
+ out[6] = 0.0;
+ out[7] = 0.0;
+ out[8] = -((leftTan - rightTan) * xScale * 0.5);
+ out[9] = (upTan - downTan) * yScale * 0.5;
+ out[10] = far / (near - far);
+ out[11] = -1.0;
+ out[12] = 0.0;
+ out[13] = 0.0;
+ out[14] = far * near / (near - far);
+ out[15] = 0.0;
+ return out;
+}
+/**
+ * Generates a orthogonal projection matrix with the given bounds.
+ * The near/far clip planes correspond to a normalized device coordinate Z range of [-1, 1],
+ * which matches WebGL/OpenGL's clip volume.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} left Left bound of the frustum
+ * @param {number} right Right bound of the frustum
+ * @param {number} bottom Bottom bound of the frustum
+ * @param {number} top Top bound of the frustum
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+
+export function orthoNO(out, left, right, bottom, top, near, far) {
+ var lr = 1 / (left - right);
+ var bt = 1 / (bottom - top);
+ var nf = 1 / (near - far);
+ out[0] = -2 * lr;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = -2 * bt;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = 2 * nf;
+ out[11] = 0;
+ out[12] = (left + right) * lr;
+ out[13] = (top + bottom) * bt;
+ out[14] = (far + near) * nf;
+ out[15] = 1;
+ return out;
+}
+/**
+ * Alias for {@link mat4.orthoNO}
+ * @function
+ */
+
+export var ortho = orthoNO;
+/**
+ * Generates a orthogonal projection matrix with the given bounds.
+ * The near/far clip planes correspond to a normalized device coordinate Z range of [0, 1],
+ * which matches WebGPU/Vulkan/DirectX/Metal's clip volume.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} left Left bound of the frustum
+ * @param {number} right Right bound of the frustum
+ * @param {number} bottom Bottom bound of the frustum
+ * @param {number} top Top bound of the frustum
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+
+export function orthoZO(out, left, right, bottom, top, near, far) {
+ var lr = 1 / (left - right);
+ var bt = 1 / (bottom - top);
+ var nf = 1 / (near - far);
+ out[0] = -2 * lr;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ out[4] = 0;
+ out[5] = -2 * bt;
+ out[6] = 0;
+ out[7] = 0;
+ out[8] = 0;
+ out[9] = 0;
+ out[10] = nf;
+ out[11] = 0;
+ out[12] = (left + right) * lr;
+ out[13] = (top + bottom) * bt;
+ out[14] = near * nf;
+ out[15] = 1;
+ return out;
+}
+/**
+ * Generates a look-at matrix with the given eye position, focal point, and up axis.
+ * If you want a matrix that actually makes an object look at another object, you should use targetTo instead.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {ReadonlyVec3} eye Position of the viewer
+ * @param {ReadonlyVec3} center Point the viewer is looking at
+ * @param {ReadonlyVec3} up vec3 pointing up
+ * @returns {mat4} out
+ */
+
+export function lookAt(out, eye, center, up) {
+ var x0, x1, x2, y0, y1, y2, z0, z1, z2, len;
+ var eyex = eye[0];
+ var eyey = eye[1];
+ var eyez = eye[2];
+ var upx = up[0];
+ var upy = up[1];
+ var upz = up[2];
+ var centerx = center[0];
+ var centery = center[1];
+ var centerz = center[2];
+
+ if (Math.abs(eyex - centerx) < glMatrix.EPSILON && Math.abs(eyey - centery) < glMatrix.EPSILON && Math.abs(eyez - centerz) < glMatrix.EPSILON) {
+ return identity(out);
+ }
+
+ z0 = eyex - centerx;
+ z1 = eyey - centery;
+ z2 = eyez - centerz;
+ len = 1 / Math.hypot(z0, z1, z2);
+ z0 *= len;
+ z1 *= len;
+ z2 *= len;
+ x0 = upy * z2 - upz * z1;
+ x1 = upz * z0 - upx * z2;
+ x2 = upx * z1 - upy * z0;
+ len = Math.hypot(x0, x1, x2);
+
+ if (!len) {
+ x0 = 0;
+ x1 = 0;
+ x2 = 0;
+ } else {
+ len = 1 / len;
+ x0 *= len;
+ x1 *= len;
+ x2 *= len;
+ }
+
+ y0 = z1 * x2 - z2 * x1;
+ y1 = z2 * x0 - z0 * x2;
+ y2 = z0 * x1 - z1 * x0;
+ len = Math.hypot(y0, y1, y2);
+
+ if (!len) {
+ y0 = 0;
+ y1 = 0;
+ y2 = 0;
+ } else {
+ len = 1 / len;
+ y0 *= len;
+ y1 *= len;
+ y2 *= len;
+ }
+
+ out[0] = x0;
+ out[1] = y0;
+ out[2] = z0;
+ out[3] = 0;
+ out[4] = x1;
+ out[5] = y1;
+ out[6] = z1;
+ out[7] = 0;
+ out[8] = x2;
+ out[9] = y2;
+ out[10] = z2;
+ out[11] = 0;
+ out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
+ out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
+ out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
+ out[15] = 1;
+ return out;
+}
+/**
+ * Generates a matrix that makes something look at something else.
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {ReadonlyVec3} eye Position of the viewer
+ * @param {ReadonlyVec3} center Point the viewer is looking at
+ * @param {ReadonlyVec3} up vec3 pointing up
+ * @returns {mat4} out
+ */
+
+export function targetTo(out, eye, target, up) {
+ var eyex = eye[0],
+ eyey = eye[1],
+ eyez = eye[2],
+ upx = up[0],
+ upy = up[1],
+ upz = up[2];
+ var z0 = eyex - target[0],
+ z1 = eyey - target[1],
+ z2 = eyez - target[2];
+ var len = z0 * z0 + z1 * z1 + z2 * z2;
+
+ if (len > 0) {
+ len = 1 / Math.sqrt(len);
+ z0 *= len;
+ z1 *= len;
+ z2 *= len;
+ }
+
+ var x0 = upy * z2 - upz * z1,
+ x1 = upz * z0 - upx * z2,
+ x2 = upx * z1 - upy * z0;
+ len = x0 * x0 + x1 * x1 + x2 * x2;
+
+ if (len > 0) {
+ len = 1 / Math.sqrt(len);
+ x0 *= len;
+ x1 *= len;
+ x2 *= len;
+ }
+
+ out[0] = x0;
+ out[1] = x1;
+ out[2] = x2;
+ out[3] = 0;
+ out[4] = z1 * x2 - z2 * x1;
+ out[5] = z2 * x0 - z0 * x2;
+ out[6] = z0 * x1 - z1 * x0;
+ out[7] = 0;
+ out[8] = z0;
+ out[9] = z1;
+ out[10] = z2;
+ out[11] = 0;
+ out[12] = eyex;
+ out[13] = eyey;
+ out[14] = eyez;
+ out[15] = 1;
+ return out;
+}
+/**
+ * Returns a string representation of a mat4
+ *
+ * @param {ReadonlyMat4} a matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+
+export function str(a) {
+ return "mat4(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ", " + a[8] + ", " + a[9] + ", " + a[10] + ", " + a[11] + ", " + a[12] + ", " + a[13] + ", " + a[14] + ", " + a[15] + ")";
+}
+/**
+ * Returns Frobenius norm of a mat4
+ *
+ * @param {ReadonlyMat4} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+
+export function frob(a) {
+ return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]);
+}
+/**
+ * Adds two mat4's
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the first operand
+ * @param {ReadonlyMat4} b the second operand
+ * @returns {mat4} out
+ */
+
+export function add(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ out[4] = a[4] + b[4];
+ out[5] = a[5] + b[5];
+ out[6] = a[6] + b[6];
+ out[7] = a[7] + b[7];
+ out[8] = a[8] + b[8];
+ out[9] = a[9] + b[9];
+ out[10] = a[10] + b[10];
+ out[11] = a[11] + b[11];
+ out[12] = a[12] + b[12];
+ out[13] = a[13] + b[13];
+ out[14] = a[14] + b[14];
+ out[15] = a[15] + b[15];
+ return out;
+}
+/**
+ * Subtracts matrix b from matrix a
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the first operand
+ * @param {ReadonlyMat4} b the second operand
+ * @returns {mat4} out
+ */
+
+export function subtract(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ out[3] = a[3] - b[3];
+ out[4] = a[4] - b[4];
+ out[5] = a[5] - b[5];
+ out[6] = a[6] - b[6];
+ out[7] = a[7] - b[7];
+ out[8] = a[8] - b[8];
+ out[9] = a[9] - b[9];
+ out[10] = a[10] - b[10];
+ out[11] = a[11] - b[11];
+ out[12] = a[12] - b[12];
+ out[13] = a[13] - b[13];
+ out[14] = a[14] - b[14];
+ out[15] = a[15] - b[15];
+ return out;
+}
+/**
+ * Multiply each element of the matrix by a scalar.
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {ReadonlyMat4} a the matrix to scale
+ * @param {Number} b amount to scale the matrix's elements by
+ * @returns {mat4} out
+ */
+
+export function multiplyScalar(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ out[4] = a[4] * b;
+ out[5] = a[5] * b;
+ out[6] = a[6] * b;
+ out[7] = a[7] * b;
+ out[8] = a[8] * b;
+ out[9] = a[9] * b;
+ out[10] = a[10] * b;
+ out[11] = a[11] * b;
+ out[12] = a[12] * b;
+ out[13] = a[13] * b;
+ out[14] = a[14] * b;
+ out[15] = a[15] * b;
+ return out;
+}
+/**
+ * Adds two mat4's after multiplying each element of the second operand by a scalar value.
+ *
+ * @param {mat4} out the receiving vector
+ * @param {ReadonlyMat4} a the first operand
+ * @param {ReadonlyMat4} b the second operand
+ * @param {Number} scale the amount to scale b's elements by before adding
+ * @returns {mat4} out
+ */
+
+export function multiplyScalarAndAdd(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ out[3] = a[3] + b[3] * scale;
+ out[4] = a[4] + b[4] * scale;
+ out[5] = a[5] + b[5] * scale;
+ out[6] = a[6] + b[6] * scale;
+ out[7] = a[7] + b[7] * scale;
+ out[8] = a[8] + b[8] * scale;
+ out[9] = a[9] + b[9] * scale;
+ out[10] = a[10] + b[10] * scale;
+ out[11] = a[11] + b[11] * scale;
+ out[12] = a[12] + b[12] * scale;
+ out[13] = a[13] + b[13] * scale;
+ out[14] = a[14] + b[14] * scale;
+ out[15] = a[15] + b[15] * scale;
+ return out;
+}
+/**
+ * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyMat4} a The first matrix.
+ * @param {ReadonlyMat4} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+export function exactEquals(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && a[8] === b[8] && a[9] === b[9] && a[10] === b[10] && a[11] === b[11] && a[12] === b[12] && a[13] === b[13] && a[14] === b[14] && a[15] === b[15];
+}
+/**
+ * Returns whether or not the matrices have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyMat4} a The first matrix.
+ * @param {ReadonlyMat4} b The second matrix.
+ * @returns {Boolean} True if the matrices are equal, false otherwise.
+ */
+
+export function equals(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var a4 = a[4],
+ a5 = a[5],
+ a6 = a[6],
+ a7 = a[7];
+ var a8 = a[8],
+ a9 = a[9],
+ a10 = a[10],
+ a11 = a[11];
+ var a12 = a[12],
+ a13 = a[13],
+ a14 = a[14],
+ a15 = a[15];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3];
+ var b4 = b[4],
+ b5 = b[5],
+ b6 = b[6],
+ b7 = b[7];
+ var b8 = b[8],
+ b9 = b[9],
+ b10 = b[10],
+ b11 = b[11];
+ var b12 = b[12],
+ b13 = b[13],
+ b14 = b[14],
+ b15 = b[15];
+ return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7)) && Math.abs(a8 - b8) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a8), Math.abs(b8)) && Math.abs(a9 - b9) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a9), Math.abs(b9)) && Math.abs(a10 - b10) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a10), Math.abs(b10)) && Math.abs(a11 - b11) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a11), Math.abs(b11)) && Math.abs(a12 - b12) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a12), Math.abs(b12)) && Math.abs(a13 - b13) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a13), Math.abs(b13)) && Math.abs(a14 - b14) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a14), Math.abs(b14)) && Math.abs(a15 - b15) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a15), Math.abs(b15));
+}
+/**
+ * Alias for {@link mat4.multiply}
+ * @function
+ */
+
+export var mul = multiply;
+/**
+ * Alias for {@link mat4.subtract}
+ * @function
+ */
+
+export var sub = subtract;
\ No newline at end of file
diff --git a/debug/webgpu/gl-matrix/quat.js b/debug/webgpu/gl-matrix/quat.js
new file mode 100644
index 0000000000..fd18975135
--- /dev/null
+++ b/debug/webgpu/gl-matrix/quat.js
@@ -0,0 +1,760 @@
+import * as glMatrix from "./common.js";
+import * as mat3 from "./mat3.js";
+import * as vec3 from "./vec3.js";
+import * as vec4 from "./vec4.js";
+/**
+ * Quaternion in the format XYZW
+ * @module quat
+ */
+
+/**
+ * Creates a new identity quat
+ *
+ * @returns {quat} a new quaternion
+ */
+
+export function create() {
+ var out = new glMatrix.ARRAY_TYPE(4);
+
+ if (glMatrix.ARRAY_TYPE != Float32Array) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ }
+
+ out[3] = 1;
+ return out;
+}
+/**
+ * Set a quat to the identity quaternion
+ *
+ * @param {quat} out the receiving quaternion
+ * @returns {quat} out
+ */
+
+export function identity(out) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ return out;
+}
+/**
+ * Sets a quat from the given angle and rotation axis,
+ * then returns it.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyVec3} axis the axis around which to rotate
+ * @param {Number} rad the angle in radians
+ * @returns {quat} out
+ **/
+
+export function setAxisAngle(out, axis, rad) {
+ rad = rad * 0.5;
+ var s = Math.sin(rad);
+ out[0] = s * axis[0];
+ out[1] = s * axis[1];
+ out[2] = s * axis[2];
+ out[3] = Math.cos(rad);
+ return out;
+}
+/**
+ * Gets the rotation axis and angle for a given
+ * quaternion. If a quaternion is created with
+ * setAxisAngle, this method will return the same
+ * values as providied in the original parameter list
+ * OR functionally equivalent values.
+ * Example: The quaternion formed by axis [0, 0, 1] and
+ * angle -90 is the same as the quaternion formed by
+ * [0, 0, 1] and 270. This method favors the latter.
+ * @param {vec3} out_axis Vector receiving the axis of rotation
+ * @param {ReadonlyQuat} q Quaternion to be decomposed
+ * @return {Number} Angle, in radians, of the rotation
+ */
+
+export function getAxisAngle(out_axis, q) {
+ var rad = Math.acos(q[3]) * 2.0;
+ var s = Math.sin(rad / 2.0);
+
+ if (s > glMatrix.EPSILON) {
+ out_axis[0] = q[0] / s;
+ out_axis[1] = q[1] / s;
+ out_axis[2] = q[2] / s;
+ } else {
+ // If s is zero, return any axis (no rotation - axis does not matter)
+ out_axis[0] = 1;
+ out_axis[1] = 0;
+ out_axis[2] = 0;
+ }
+
+ return rad;
+}
+/**
+ * Gets the angular distance between two unit quaternions
+ *
+ * @param {ReadonlyQuat} a Origin unit quaternion
+ * @param {ReadonlyQuat} b Destination unit quaternion
+ * @return {Number} Angle, in radians, between the two quaternions
+ */
+
+export function getAngle(a, b) {
+ var dotproduct = dot(a, b);
+ return Math.acos(2 * dotproduct * dotproduct - 1);
+}
+/**
+ * Multiplies two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @returns {quat} out
+ */
+
+export function multiply(out, a, b) {
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ var bx = b[0],
+ by = b[1],
+ bz = b[2],
+ bw = b[3];
+ out[0] = ax * bw + aw * bx + ay * bz - az * by;
+ out[1] = ay * bw + aw * by + az * bx - ax * bz;
+ out[2] = az * bw + aw * bz + ax * by - ay * bx;
+ out[3] = aw * bw - ax * bx - ay * by - az * bz;
+ return out;
+}
+/**
+ * Rotates a quaternion by the given angle about the X axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {ReadonlyQuat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+
+export function rotateX(out, a, rad) {
+ rad *= 0.5;
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ var bx = Math.sin(rad),
+ bw = Math.cos(rad);
+ out[0] = ax * bw + aw * bx;
+ out[1] = ay * bw + az * bx;
+ out[2] = az * bw - ay * bx;
+ out[3] = aw * bw - ax * bx;
+ return out;
+}
+/**
+ * Rotates a quaternion by the given angle about the Y axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {ReadonlyQuat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+
+export function rotateY(out, a, rad) {
+ rad *= 0.5;
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ var by = Math.sin(rad),
+ bw = Math.cos(rad);
+ out[0] = ax * bw - az * by;
+ out[1] = ay * bw + aw * by;
+ out[2] = az * bw + ax * by;
+ out[3] = aw * bw - ay * by;
+ return out;
+}
+/**
+ * Rotates a quaternion by the given angle about the Z axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {ReadonlyQuat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+
+export function rotateZ(out, a, rad) {
+ rad *= 0.5;
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ var bz = Math.sin(rad),
+ bw = Math.cos(rad);
+ out[0] = ax * bw + ay * bz;
+ out[1] = ay * bw - ax * bz;
+ out[2] = az * bw + aw * bz;
+ out[3] = aw * bw - az * bz;
+ return out;
+}
+/**
+ * Calculates the W component of a quat from the X, Y, and Z components.
+ * Assumes that quaternion is 1 unit in length.
+ * Any existing W component will be ignored.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate W component of
+ * @returns {quat} out
+ */
+
+export function calculateW(out, a) {
+ var x = a[0],
+ y = a[1],
+ z = a[2];
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+ out[3] = Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z));
+ return out;
+}
+/**
+ * Calculate the exponential of a unit quaternion.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate the exponential of
+ * @returns {quat} out
+ */
+
+export function exp(out, a) {
+ var x = a[0],
+ y = a[1],
+ z = a[2],
+ w = a[3];
+ var r = Math.sqrt(x * x + y * y + z * z);
+ var et = Math.exp(w);
+ var s = r > 0 ? et * Math.sin(r) / r : 0;
+ out[0] = x * s;
+ out[1] = y * s;
+ out[2] = z * s;
+ out[3] = et * Math.cos(r);
+ return out;
+}
+/**
+ * Calculate the natural logarithm of a unit quaternion.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate the exponential of
+ * @returns {quat} out
+ */
+
+export function ln(out, a) {
+ var x = a[0],
+ y = a[1],
+ z = a[2],
+ w = a[3];
+ var r = Math.sqrt(x * x + y * y + z * z);
+ var t = r > 0 ? Math.atan2(r, w) / r : 0;
+ out[0] = x * t;
+ out[1] = y * t;
+ out[2] = z * t;
+ out[3] = 0.5 * Math.log(x * x + y * y + z * z + w * w);
+ return out;
+}
+/**
+ * Calculate the scalar power of a unit quaternion.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate the exponential of
+ * @param {Number} b amount to scale the quaternion by
+ * @returns {quat} out
+ */
+
+export function pow(out, a, b) {
+ ln(out, a);
+ scale(out, out, b);
+ exp(out, out);
+ return out;
+}
+/**
+ * Performs a spherical linear interpolation between two quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {quat} out
+ */
+
+export function slerp(out, a, b, t) {
+ // benchmarks:
+ // http://jsperf.com/quaternion-slerp-implementations
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ var bx = b[0],
+ by = b[1],
+ bz = b[2],
+ bw = b[3];
+ var omega, cosom, sinom, scale0, scale1; // calc cosine
+
+ cosom = ax * bx + ay * by + az * bz + aw * bw; // adjust signs (if necessary)
+
+ if (cosom < 0.0) {
+ cosom = -cosom;
+ bx = -bx;
+ by = -by;
+ bz = -bz;
+ bw = -bw;
+ } // calculate coefficients
+
+
+ if (1.0 - cosom > glMatrix.EPSILON) {
+ // standard case (slerp)
+ omega = Math.acos(cosom);
+ sinom = Math.sin(omega);
+ scale0 = Math.sin((1.0 - t) * omega) / sinom;
+ scale1 = Math.sin(t * omega) / sinom;
+ } else {
+ // "from" and "to" quaternions are very close
+ // ... so we can do a linear interpolation
+ scale0 = 1.0 - t;
+ scale1 = t;
+ } // calculate final values
+
+
+ out[0] = scale0 * ax + scale1 * bx;
+ out[1] = scale0 * ay + scale1 * by;
+ out[2] = scale0 * az + scale1 * bz;
+ out[3] = scale0 * aw + scale1 * bw;
+ return out;
+}
+/**
+ * Generates a random unit quaternion
+ *
+ * @param {quat} out the receiving quaternion
+ * @returns {quat} out
+ */
+
+export function random(out) {
+ // Implementation of http://planning.cs.uiuc.edu/node198.html
+ // TODO: Calling random 3 times is probably not the fastest solution
+ var u1 = glMatrix.RANDOM();
+ var u2 = glMatrix.RANDOM();
+ var u3 = glMatrix.RANDOM();
+ var sqrt1MinusU1 = Math.sqrt(1 - u1);
+ var sqrtU1 = Math.sqrt(u1);
+ out[0] = sqrt1MinusU1 * Math.sin(2.0 * Math.PI * u2);
+ out[1] = sqrt1MinusU1 * Math.cos(2.0 * Math.PI * u2);
+ out[2] = sqrtU1 * Math.sin(2.0 * Math.PI * u3);
+ out[3] = sqrtU1 * Math.cos(2.0 * Math.PI * u3);
+ return out;
+}
+/**
+ * Calculates the inverse of a quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate inverse of
+ * @returns {quat} out
+ */
+
+export function invert(out, a) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var dot = a0 * a0 + a1 * a1 + a2 * a2 + a3 * a3;
+ var invDot = dot ? 1.0 / dot : 0; // TODO: Would be faster to return [0,0,0,0] immediately if dot == 0
+
+ out[0] = -a0 * invDot;
+ out[1] = -a1 * invDot;
+ out[2] = -a2 * invDot;
+ out[3] = a3 * invDot;
+ return out;
+}
+/**
+ * Calculates the conjugate of a quat
+ * If the quaternion is normalized, this function is faster than quat.inverse and produces the same result.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quat to calculate conjugate of
+ * @returns {quat} out
+ */
+
+export function conjugate(out, a) {
+ out[0] = -a[0];
+ out[1] = -a[1];
+ out[2] = -a[2];
+ out[3] = a[3];
+ return out;
+}
+/**
+ * Creates a quaternion from the given 3x3 rotation matrix.
+ *
+ * NOTE: The resultant quaternion is not normalized, so you should be sure
+ * to renormalize the quaternion yourself where necessary.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyMat3} m rotation matrix
+ * @returns {quat} out
+ * @function
+ */
+
+export function fromMat3(out, m) {
+ // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes
+ // article "Quaternion Calculus and Fast Animation".
+ var fTrace = m[0] + m[4] + m[8];
+ var fRoot;
+
+ if (fTrace > 0.0) {
+ // |w| > 1/2, may as well choose w > 1/2
+ fRoot = Math.sqrt(fTrace + 1.0); // 2w
+
+ out[3] = 0.5 * fRoot;
+ fRoot = 0.5 / fRoot; // 1/(4w)
+
+ out[0] = (m[5] - m[7]) * fRoot;
+ out[1] = (m[6] - m[2]) * fRoot;
+ out[2] = (m[1] - m[3]) * fRoot;
+ } else {
+ // |w| <= 1/2
+ var i = 0;
+ if (m[4] > m[0]) i = 1;
+ if (m[8] > m[i * 3 + i]) i = 2;
+ var j = (i + 1) % 3;
+ var k = (i + 2) % 3;
+ fRoot = Math.sqrt(m[i * 3 + i] - m[j * 3 + j] - m[k * 3 + k] + 1.0);
+ out[i] = 0.5 * fRoot;
+ fRoot = 0.5 / fRoot;
+ out[3] = (m[j * 3 + k] - m[k * 3 + j]) * fRoot;
+ out[j] = (m[j * 3 + i] + m[i * 3 + j]) * fRoot;
+ out[k] = (m[k * 3 + i] + m[i * 3 + k]) * fRoot;
+ }
+
+ return out;
+}
+/**
+ * Creates a quaternion from the given euler angle x, y, z using the provided intrinsic order for the conversion.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {x} x Angle to rotate around X axis in degrees.
+ * @param {y} y Angle to rotate around Y axis in degrees.
+ * @param {z} z Angle to rotate around Z axis in degrees.
+ * @param {'zyx'|'xyz'|'yxz'|'yzx'|'zxy'|'zyx'} order Intrinsic order for conversion, default is zyx.
+ * @returns {quat} out
+ * @function
+ */
+
+export function fromEuler(out, x, y, z) {
+ var order = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : glMatrix.ANGLE_ORDER;
+ var halfToRad = Math.PI / 360;
+ x *= halfToRad;
+ z *= halfToRad;
+ y *= halfToRad;
+ var sx = Math.sin(x);
+ var cx = Math.cos(x);
+ var sy = Math.sin(y);
+ var cy = Math.cos(y);
+ var sz = Math.sin(z);
+ var cz = Math.cos(z);
+
+ switch (order) {
+ case "xyz":
+ out[0] = sx * cy * cz + cx * sy * sz;
+ out[1] = cx * sy * cz - sx * cy * sz;
+ out[2] = cx * cy * sz + sx * sy * cz;
+ out[3] = cx * cy * cz - sx * sy * sz;
+ break;
+
+ case "xzy":
+ out[0] = sx * cy * cz - cx * sy * sz;
+ out[1] = cx * sy * cz - sx * cy * sz;
+ out[2] = cx * cy * sz + sx * sy * cz;
+ out[3] = cx * cy * cz + sx * sy * sz;
+ break;
+
+ case "yxz":
+ out[0] = sx * cy * cz + cx * sy * sz;
+ out[1] = cx * sy * cz - sx * cy * sz;
+ out[2] = cx * cy * sz - sx * sy * cz;
+ out[3] = cx * cy * cz + sx * sy * sz;
+ break;
+
+ case "yzx":
+ out[0] = sx * cy * cz + cx * sy * sz;
+ out[1] = cx * sy * cz + sx * cy * sz;
+ out[2] = cx * cy * sz - sx * sy * cz;
+ out[3] = cx * cy * cz - sx * sy * sz;
+ break;
+
+ case "zxy":
+ out[0] = sx * cy * cz - cx * sy * sz;
+ out[1] = cx * sy * cz + sx * cy * sz;
+ out[2] = cx * cy * sz + sx * sy * cz;
+ out[3] = cx * cy * cz - sx * sy * sz;
+ break;
+
+ case "zyx":
+ out[0] = sx * cy * cz - cx * sy * sz;
+ out[1] = cx * sy * cz + sx * cy * sz;
+ out[2] = cx * cy * sz - sx * sy * cz;
+ out[3] = cx * cy * cz + sx * sy * sz;
+ break;
+
+ default:
+ throw new Error('Unknown angle order ' + order);
+ }
+
+ return out;
+}
+/**
+ * Returns a string representation of a quaternion
+ *
+ * @param {ReadonlyQuat} a vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+
+export function str(a) {
+ return "quat(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")";
+}
+/**
+ * Creates a new quat initialized with values from an existing quaternion
+ *
+ * @param {ReadonlyQuat} a quaternion to clone
+ * @returns {quat} a new quaternion
+ * @function
+ */
+
+export var clone = vec4.clone;
+/**
+ * Creates a new quat initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {quat} a new quaternion
+ * @function
+ */
+
+export var fromValues = vec4.fromValues;
+/**
+ * Copy the values from one quat to another
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the source quaternion
+ * @returns {quat} out
+ * @function
+ */
+
+export var copy = vec4.copy;
+/**
+ * Set the components of a quat to the given values
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {quat} out
+ * @function
+ */
+
+export var set = vec4.set;
+/**
+ * Adds two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @returns {quat} out
+ * @function
+ */
+
+export var add = vec4.add;
+/**
+ * Alias for {@link quat.multiply}
+ * @function
+ */
+
+export var mul = multiply;
+/**
+ * Scales a quat by a scalar number
+ *
+ * @param {quat} out the receiving vector
+ * @param {ReadonlyQuat} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {quat} out
+ * @function
+ */
+
+export var scale = vec4.scale;
+/**
+ * Calculates the dot product of two quat's
+ *
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @returns {Number} dot product of a and b
+ * @function
+ */
+
+export var dot = vec4.dot;
+/**
+ * Performs a linear interpolation between two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {quat} out
+ * @function
+ */
+
+export var lerp = vec4.lerp;
+/**
+ * Calculates the length of a quat
+ *
+ * @param {ReadonlyQuat} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+
+export var length = vec4.length;
+/**
+ * Alias for {@link quat.length}
+ * @function
+ */
+
+export var len = length;
+/**
+ * Calculates the squared length of a quat
+ *
+ * @param {ReadonlyQuat} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ * @function
+ */
+
+export var squaredLength = vec4.squaredLength;
+/**
+ * Alias for {@link quat.squaredLength}
+ * @function
+ */
+
+export var sqrLen = squaredLength;
+/**
+ * Normalize a quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a quaternion to normalize
+ * @returns {quat} out
+ * @function
+ */
+
+export var normalize = vec4.normalize;
+/**
+ * Returns whether or not the quaternions have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyQuat} a The first quaternion.
+ * @param {ReadonlyQuat} b The second quaternion.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+export var exactEquals = vec4.exactEquals;
+/**
+ * Returns whether or not the quaternions point approximately to the same direction.
+ *
+ * Both quaternions are assumed to be unit length.
+ *
+ * @param {ReadonlyQuat} a The first unit quaternion.
+ * @param {ReadonlyQuat} b The second unit quaternion.
+ * @returns {Boolean} True if the quaternions are equal, false otherwise.
+ */
+
+export function equals(a, b) {
+ return Math.abs(vec4.dot(a, b)) >= 1 - glMatrix.EPSILON;
+}
+/**
+ * Sets a quaternion to represent the shortest rotation from one
+ * vector to another.
+ *
+ * Both vectors are assumed to be unit length.
+ *
+ * @param {quat} out the receiving quaternion.
+ * @param {ReadonlyVec3} a the initial vector
+ * @param {ReadonlyVec3} b the destination vector
+ * @returns {quat} out
+ */
+
+export var rotationTo = function () {
+ var tmpvec3 = vec3.create();
+ var xUnitVec3 = vec3.fromValues(1, 0, 0);
+ var yUnitVec3 = vec3.fromValues(0, 1, 0);
+ return function (out, a, b) {
+ var dot = vec3.dot(a, b);
+
+ if (dot < -0.999999) {
+ vec3.cross(tmpvec3, xUnitVec3, a);
+ if (vec3.len(tmpvec3) < 0.000001) vec3.cross(tmpvec3, yUnitVec3, a);
+ vec3.normalize(tmpvec3, tmpvec3);
+ setAxisAngle(out, tmpvec3, Math.PI);
+ return out;
+ } else if (dot > 0.999999) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ return out;
+ } else {
+ vec3.cross(tmpvec3, a, b);
+ out[0] = tmpvec3[0];
+ out[1] = tmpvec3[1];
+ out[2] = tmpvec3[2];
+ out[3] = 1 + dot;
+ return normalize(out, out);
+ }
+ };
+}();
+/**
+ * Performs a spherical linear interpolation with two control points
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {ReadonlyQuat} a the first operand
+ * @param {ReadonlyQuat} b the second operand
+ * @param {ReadonlyQuat} c the third operand
+ * @param {ReadonlyQuat} d the fourth operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {quat} out
+ */
+
+export var sqlerp = function () {
+ var temp1 = create();
+ var temp2 = create();
+ return function (out, a, b, c, d, t) {
+ slerp(temp1, a, d, t);
+ slerp(temp2, b, c, t);
+ slerp(out, temp1, temp2, 2 * t * (1 - t));
+ return out;
+ };
+}();
+/**
+ * Sets the specified quaternion with values corresponding to the given
+ * axes. Each axis is a vec3 and is expected to be unit length and
+ * perpendicular to all other specified axes.
+ *
+ * @param {ReadonlyVec3} view the vector representing the viewing direction
+ * @param {ReadonlyVec3} right the vector representing the local "right" direction
+ * @param {ReadonlyVec3} up the vector representing the local "up" direction
+ * @returns {quat} out
+ */
+
+export var setAxes = function () {
+ var matr = mat3.create();
+ return function (out, view, right, up) {
+ matr[0] = right[0];
+ matr[3] = right[1];
+ matr[6] = right[2];
+ matr[1] = up[0];
+ matr[4] = up[1];
+ matr[7] = up[2];
+ matr[2] = -view[0];
+ matr[5] = -view[1];
+ matr[8] = -view[2];
+ return normalize(out, fromMat3(out, matr));
+ };
+}();
\ No newline at end of file
diff --git a/debug/webgpu/gl-matrix/quat2.js b/debug/webgpu/gl-matrix/quat2.js
new file mode 100644
index 0000000000..a29b804df1
--- /dev/null
+++ b/debug/webgpu/gl-matrix/quat2.js
@@ -0,0 +1,835 @@
+import * as glMatrix from "./common.js";
+import * as quat from "./quat.js";
+import * as mat4 from "./mat4.js";
+/**
+ * Dual Quaternion
+ * Format: [real, dual]
+ * Quaternion format: XYZW
+ * Make sure to have normalized dual quaternions, otherwise the functions may not work as intended.
+ * @module quat2
+ */
+
+/**
+ * Creates a new identity dual quat
+ *
+ * @returns {quat2} a new dual quaternion [real -> rotation, dual -> translation]
+ */
+
+export function create() {
+ var dq = new glMatrix.ARRAY_TYPE(8);
+
+ if (glMatrix.ARRAY_TYPE != Float32Array) {
+ dq[0] = 0;
+ dq[1] = 0;
+ dq[2] = 0;
+ dq[4] = 0;
+ dq[5] = 0;
+ dq[6] = 0;
+ dq[7] = 0;
+ }
+
+ dq[3] = 1;
+ return dq;
+}
+/**
+ * Creates a new quat initialized with values from an existing quaternion
+ *
+ * @param {ReadonlyQuat2} a dual quaternion to clone
+ * @returns {quat2} new dual quaternion
+ * @function
+ */
+
+export function clone(a) {
+ var dq = new glMatrix.ARRAY_TYPE(8);
+ dq[0] = a[0];
+ dq[1] = a[1];
+ dq[2] = a[2];
+ dq[3] = a[3];
+ dq[4] = a[4];
+ dq[5] = a[5];
+ dq[6] = a[6];
+ dq[7] = a[7];
+ return dq;
+}
+/**
+ * Creates a new dual quat initialized with the given values
+ *
+ * @param {Number} x1 X component
+ * @param {Number} y1 Y component
+ * @param {Number} z1 Z component
+ * @param {Number} w1 W component
+ * @param {Number} x2 X component
+ * @param {Number} y2 Y component
+ * @param {Number} z2 Z component
+ * @param {Number} w2 W component
+ * @returns {quat2} new dual quaternion
+ * @function
+ */
+
+export function fromValues(x1, y1, z1, w1, x2, y2, z2, w2) {
+ var dq = new glMatrix.ARRAY_TYPE(8);
+ dq[0] = x1;
+ dq[1] = y1;
+ dq[2] = z1;
+ dq[3] = w1;
+ dq[4] = x2;
+ dq[5] = y2;
+ dq[6] = z2;
+ dq[7] = w2;
+ return dq;
+}
+/**
+ * Creates a new dual quat from the given values (quat and translation)
+ *
+ * @param {Number} x1 X component
+ * @param {Number} y1 Y component
+ * @param {Number} z1 Z component
+ * @param {Number} w1 W component
+ * @param {Number} x2 X component (translation)
+ * @param {Number} y2 Y component (translation)
+ * @param {Number} z2 Z component (translation)
+ * @returns {quat2} new dual quaternion
+ * @function
+ */
+
+export function fromRotationTranslationValues(x1, y1, z1, w1, x2, y2, z2) {
+ var dq = new glMatrix.ARRAY_TYPE(8);
+ dq[0] = x1;
+ dq[1] = y1;
+ dq[2] = z1;
+ dq[3] = w1;
+ var ax = x2 * 0.5,
+ ay = y2 * 0.5,
+ az = z2 * 0.5;
+ dq[4] = ax * w1 + ay * z1 - az * y1;
+ dq[5] = ay * w1 + az * x1 - ax * z1;
+ dq[6] = az * w1 + ax * y1 - ay * x1;
+ dq[7] = -ax * x1 - ay * y1 - az * z1;
+ return dq;
+}
+/**
+ * Creates a dual quat from a quaternion and a translation
+ *
+ * @param {ReadonlyQuat2} dual quaternion receiving operation result
+ * @param {ReadonlyQuat} q a normalized quaternion
+ * @param {ReadonlyVec3} t translation vector
+ * @returns {quat2} dual quaternion receiving operation result
+ * @function
+ */
+
+export function fromRotationTranslation(out, q, t) {
+ var ax = t[0] * 0.5,
+ ay = t[1] * 0.5,
+ az = t[2] * 0.5,
+ bx = q[0],
+ by = q[1],
+ bz = q[2],
+ bw = q[3];
+ out[0] = bx;
+ out[1] = by;
+ out[2] = bz;
+ out[3] = bw;
+ out[4] = ax * bw + ay * bz - az * by;
+ out[5] = ay * bw + az * bx - ax * bz;
+ out[6] = az * bw + ax * by - ay * bx;
+ out[7] = -ax * bx - ay * by - az * bz;
+ return out;
+}
+/**
+ * Creates a dual quat from a translation
+ *
+ * @param {ReadonlyQuat2} dual quaternion receiving operation result
+ * @param {ReadonlyVec3} t translation vector
+ * @returns {quat2} dual quaternion receiving operation result
+ * @function
+ */
+
+export function fromTranslation(out, t) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ out[4] = t[0] * 0.5;
+ out[5] = t[1] * 0.5;
+ out[6] = t[2] * 0.5;
+ out[7] = 0;
+ return out;
+}
+/**
+ * Creates a dual quat from a quaternion
+ *
+ * @param {ReadonlyQuat2} dual quaternion receiving operation result
+ * @param {ReadonlyQuat} q the quaternion
+ * @returns {quat2} dual quaternion receiving operation result
+ * @function
+ */
+
+export function fromRotation(out, q) {
+ out[0] = q[0];
+ out[1] = q[1];
+ out[2] = q[2];
+ out[3] = q[3];
+ out[4] = 0;
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ return out;
+}
+/**
+ * Creates a new dual quat from a matrix (4x4)
+ *
+ * @param {quat2} out the dual quaternion
+ * @param {ReadonlyMat4} a the matrix
+ * @returns {quat2} dual quat receiving operation result
+ * @function
+ */
+
+export function fromMat4(out, a) {
+ //TODO Optimize this
+ var outer = quat.create();
+ mat4.getRotation(outer, a);
+ var t = new glMatrix.ARRAY_TYPE(3);
+ mat4.getTranslation(t, a);
+ fromRotationTranslation(out, outer, t);
+ return out;
+}
+/**
+ * Copy the values from one dual quat to another
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the source dual quaternion
+ * @returns {quat2} out
+ * @function
+ */
+
+export function copy(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ return out;
+}
+/**
+ * Set a dual quat to the identity dual quaternion
+ *
+ * @param {quat2} out the receiving quaternion
+ * @returns {quat2} out
+ */
+
+export function identity(out) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 1;
+ out[4] = 0;
+ out[5] = 0;
+ out[6] = 0;
+ out[7] = 0;
+ return out;
+}
+/**
+ * Set the components of a dual quat to the given values
+ *
+ * @param {quat2} out the receiving quaternion
+ * @param {Number} x1 X component
+ * @param {Number} y1 Y component
+ * @param {Number} z1 Z component
+ * @param {Number} w1 W component
+ * @param {Number} x2 X component
+ * @param {Number} y2 Y component
+ * @param {Number} z2 Z component
+ * @param {Number} w2 W component
+ * @returns {quat2} out
+ * @function
+ */
+
+export function set(out, x1, y1, z1, w1, x2, y2, z2, w2) {
+ out[0] = x1;
+ out[1] = y1;
+ out[2] = z1;
+ out[3] = w1;
+ out[4] = x2;
+ out[5] = y2;
+ out[6] = z2;
+ out[7] = w2;
+ return out;
+}
+/**
+ * Gets the real part of a dual quat
+ * @param {quat} out real part
+ * @param {ReadonlyQuat2} a Dual Quaternion
+ * @return {quat} real part
+ */
+
+export var getReal = quat.copy;
+/**
+ * Gets the dual part of a dual quat
+ * @param {quat} out dual part
+ * @param {ReadonlyQuat2} a Dual Quaternion
+ * @return {quat} dual part
+ */
+
+export function getDual(out, a) {
+ out[0] = a[4];
+ out[1] = a[5];
+ out[2] = a[6];
+ out[3] = a[7];
+ return out;
+}
+/**
+ * Set the real component of a dual quat to the given quaternion
+ *
+ * @param {quat2} out the receiving quaternion
+ * @param {ReadonlyQuat} q a quaternion representing the real part
+ * @returns {quat2} out
+ * @function
+ */
+
+export var setReal = quat.copy;
+/**
+ * Set the dual component of a dual quat to the given quaternion
+ *
+ * @param {quat2} out the receiving quaternion
+ * @param {ReadonlyQuat} q a quaternion representing the dual part
+ * @returns {quat2} out
+ * @function
+ */
+
+export function setDual(out, q) {
+ out[4] = q[0];
+ out[5] = q[1];
+ out[6] = q[2];
+ out[7] = q[3];
+ return out;
+}
+/**
+ * Gets the translation of a normalized dual quat
+ * @param {vec3} out translation
+ * @param {ReadonlyQuat2} a Dual Quaternion to be decomposed
+ * @return {vec3} translation
+ */
+
+export function getTranslation(out, a) {
+ var ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7],
+ bx = -a[0],
+ by = -a[1],
+ bz = -a[2],
+ bw = a[3];
+ out[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2;
+ out[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2;
+ out[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2;
+ return out;
+}
+/**
+ * Translates a dual quat by the given vector
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to translate
+ * @param {ReadonlyVec3} v vector to translate by
+ * @returns {quat2} out
+ */
+
+export function translate(out, a, v) {
+ var ax1 = a[0],
+ ay1 = a[1],
+ az1 = a[2],
+ aw1 = a[3],
+ bx1 = v[0] * 0.5,
+ by1 = v[1] * 0.5,
+ bz1 = v[2] * 0.5,
+ ax2 = a[4],
+ ay2 = a[5],
+ az2 = a[6],
+ aw2 = a[7];
+ out[0] = ax1;
+ out[1] = ay1;
+ out[2] = az1;
+ out[3] = aw1;
+ out[4] = aw1 * bx1 + ay1 * bz1 - az1 * by1 + ax2;
+ out[5] = aw1 * by1 + az1 * bx1 - ax1 * bz1 + ay2;
+ out[6] = aw1 * bz1 + ax1 * by1 - ay1 * bx1 + az2;
+ out[7] = -ax1 * bx1 - ay1 * by1 - az1 * bz1 + aw2;
+ return out;
+}
+/**
+ * Rotates a dual quat around the X axis
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @param {number} rad how far should the rotation be
+ * @returns {quat2} out
+ */
+
+export function rotateX(out, a, rad) {
+ var bx = -a[0],
+ by = -a[1],
+ bz = -a[2],
+ bw = a[3],
+ ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7],
+ ax1 = ax * bw + aw * bx + ay * bz - az * by,
+ ay1 = ay * bw + aw * by + az * bx - ax * bz,
+ az1 = az * bw + aw * bz + ax * by - ay * bx,
+ aw1 = aw * bw - ax * bx - ay * by - az * bz;
+ quat.rotateX(out, a, rad);
+ bx = out[0];
+ by = out[1];
+ bz = out[2];
+ bw = out[3];
+ out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by;
+ out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz;
+ out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx;
+ out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz;
+ return out;
+}
+/**
+ * Rotates a dual quat around the Y axis
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @param {number} rad how far should the rotation be
+ * @returns {quat2} out
+ */
+
+export function rotateY(out, a, rad) {
+ var bx = -a[0],
+ by = -a[1],
+ bz = -a[2],
+ bw = a[3],
+ ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7],
+ ax1 = ax * bw + aw * bx + ay * bz - az * by,
+ ay1 = ay * bw + aw * by + az * bx - ax * bz,
+ az1 = az * bw + aw * bz + ax * by - ay * bx,
+ aw1 = aw * bw - ax * bx - ay * by - az * bz;
+ quat.rotateY(out, a, rad);
+ bx = out[0];
+ by = out[1];
+ bz = out[2];
+ bw = out[3];
+ out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by;
+ out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz;
+ out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx;
+ out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz;
+ return out;
+}
+/**
+ * Rotates a dual quat around the Z axis
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @param {number} rad how far should the rotation be
+ * @returns {quat2} out
+ */
+
+export function rotateZ(out, a, rad) {
+ var bx = -a[0],
+ by = -a[1],
+ bz = -a[2],
+ bw = a[3],
+ ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7],
+ ax1 = ax * bw + aw * bx + ay * bz - az * by,
+ ay1 = ay * bw + aw * by + az * bx - ax * bz,
+ az1 = az * bw + aw * bz + ax * by - ay * bx,
+ aw1 = aw * bw - ax * bx - ay * by - az * bz;
+ quat.rotateZ(out, a, rad);
+ bx = out[0];
+ by = out[1];
+ bz = out[2];
+ bw = out[3];
+ out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by;
+ out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz;
+ out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx;
+ out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz;
+ return out;
+}
+/**
+ * Rotates a dual quat by a given quaternion (a * q)
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @param {ReadonlyQuat} q quaternion to rotate by
+ * @returns {quat2} out
+ */
+
+export function rotateByQuatAppend(out, a, q) {
+ var qx = q[0],
+ qy = q[1],
+ qz = q[2],
+ qw = q[3],
+ ax = a[0],
+ ay = a[1],
+ az = a[2],
+ aw = a[3];
+ out[0] = ax * qw + aw * qx + ay * qz - az * qy;
+ out[1] = ay * qw + aw * qy + az * qx - ax * qz;
+ out[2] = az * qw + aw * qz + ax * qy - ay * qx;
+ out[3] = aw * qw - ax * qx - ay * qy - az * qz;
+ ax = a[4];
+ ay = a[5];
+ az = a[6];
+ aw = a[7];
+ out[4] = ax * qw + aw * qx + ay * qz - az * qy;
+ out[5] = ay * qw + aw * qy + az * qx - ax * qz;
+ out[6] = az * qw + aw * qz + ax * qy - ay * qx;
+ out[7] = aw * qw - ax * qx - ay * qy - az * qz;
+ return out;
+}
+/**
+ * Rotates a dual quat by a given quaternion (q * a)
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat} q quaternion to rotate by
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @returns {quat2} out
+ */
+
+export function rotateByQuatPrepend(out, q, a) {
+ var qx = q[0],
+ qy = q[1],
+ qz = q[2],
+ qw = q[3],
+ bx = a[0],
+ by = a[1],
+ bz = a[2],
+ bw = a[3];
+ out[0] = qx * bw + qw * bx + qy * bz - qz * by;
+ out[1] = qy * bw + qw * by + qz * bx - qx * bz;
+ out[2] = qz * bw + qw * bz + qx * by - qy * bx;
+ out[3] = qw * bw - qx * bx - qy * by - qz * bz;
+ bx = a[4];
+ by = a[5];
+ bz = a[6];
+ bw = a[7];
+ out[4] = qx * bw + qw * bx + qy * bz - qz * by;
+ out[5] = qy * bw + qw * by + qz * bx - qx * bz;
+ out[6] = qz * bw + qw * bz + qx * by - qy * bx;
+ out[7] = qw * bw - qx * bx - qy * by - qz * bz;
+ return out;
+}
+/**
+ * Rotates a dual quat around a given axis. Does the normalisation automatically
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the dual quaternion to rotate
+ * @param {ReadonlyVec3} axis the axis to rotate around
+ * @param {Number} rad how far the rotation should be
+ * @returns {quat2} out
+ */
+
+export function rotateAroundAxis(out, a, axis, rad) {
+ //Special case for rad = 0
+ if (Math.abs(rad) < glMatrix.EPSILON) {
+ return copy(out, a);
+ }
+
+ var axisLength = Math.hypot(axis[0], axis[1], axis[2]);
+ rad = rad * 0.5;
+ var s = Math.sin(rad);
+ var bx = s * axis[0] / axisLength;
+ var by = s * axis[1] / axisLength;
+ var bz = s * axis[2] / axisLength;
+ var bw = Math.cos(rad);
+ var ax1 = a[0],
+ ay1 = a[1],
+ az1 = a[2],
+ aw1 = a[3];
+ out[0] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by;
+ out[1] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz;
+ out[2] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx;
+ out[3] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz;
+ var ax = a[4],
+ ay = a[5],
+ az = a[6],
+ aw = a[7];
+ out[4] = ax * bw + aw * bx + ay * bz - az * by;
+ out[5] = ay * bw + aw * by + az * bx - ax * bz;
+ out[6] = az * bw + aw * bz + ax * by - ay * bx;
+ out[7] = aw * bw - ax * bx - ay * by - az * bz;
+ return out;
+}
+/**
+ * Adds two dual quat's
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the first operand
+ * @param {ReadonlyQuat2} b the second operand
+ * @returns {quat2} out
+ * @function
+ */
+
+export function add(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ out[4] = a[4] + b[4];
+ out[5] = a[5] + b[5];
+ out[6] = a[6] + b[6];
+ out[7] = a[7] + b[7];
+ return out;
+}
+/**
+ * Multiplies two dual quat's
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a the first operand
+ * @param {ReadonlyQuat2} b the second operand
+ * @returns {quat2} out
+ */
+
+export function multiply(out, a, b) {
+ var ax0 = a[0],
+ ay0 = a[1],
+ az0 = a[2],
+ aw0 = a[3],
+ bx1 = b[4],
+ by1 = b[5],
+ bz1 = b[6],
+ bw1 = b[7],
+ ax1 = a[4],
+ ay1 = a[5],
+ az1 = a[6],
+ aw1 = a[7],
+ bx0 = b[0],
+ by0 = b[1],
+ bz0 = b[2],
+ bw0 = b[3];
+ out[0] = ax0 * bw0 + aw0 * bx0 + ay0 * bz0 - az0 * by0;
+ out[1] = ay0 * bw0 + aw0 * by0 + az0 * bx0 - ax0 * bz0;
+ out[2] = az0 * bw0 + aw0 * bz0 + ax0 * by0 - ay0 * bx0;
+ out[3] = aw0 * bw0 - ax0 * bx0 - ay0 * by0 - az0 * bz0;
+ out[4] = ax0 * bw1 + aw0 * bx1 + ay0 * bz1 - az0 * by1 + ax1 * bw0 + aw1 * bx0 + ay1 * bz0 - az1 * by0;
+ out[5] = ay0 * bw1 + aw0 * by1 + az0 * bx1 - ax0 * bz1 + ay1 * bw0 + aw1 * by0 + az1 * bx0 - ax1 * bz0;
+ out[6] = az0 * bw1 + aw0 * bz1 + ax0 * by1 - ay0 * bx1 + az1 * bw0 + aw1 * bz0 + ax1 * by0 - ay1 * bx0;
+ out[7] = aw0 * bw1 - ax0 * bx1 - ay0 * by1 - az0 * bz1 + aw1 * bw0 - ax1 * bx0 - ay1 * by0 - az1 * bz0;
+ return out;
+}
+/**
+ * Alias for {@link quat2.multiply}
+ * @function
+ */
+
+export var mul = multiply;
+/**
+ * Scales a dual quat by a scalar number
+ *
+ * @param {quat2} out the receiving dual quat
+ * @param {ReadonlyQuat2} a the dual quat to scale
+ * @param {Number} b amount to scale the dual quat by
+ * @returns {quat2} out
+ * @function
+ */
+
+export function scale(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ out[4] = a[4] * b;
+ out[5] = a[5] * b;
+ out[6] = a[6] * b;
+ out[7] = a[7] * b;
+ return out;
+}
+/**
+ * Calculates the dot product of two dual quat's (The dot product of the real parts)
+ *
+ * @param {ReadonlyQuat2} a the first operand
+ * @param {ReadonlyQuat2} b the second operand
+ * @returns {Number} dot product of a and b
+ * @function
+ */
+
+export var dot = quat.dot;
+/**
+ * Performs a linear interpolation between two dual quats's
+ * NOTE: The resulting dual quaternions won't always be normalized (The error is most noticeable when t = 0.5)
+ *
+ * @param {quat2} out the receiving dual quat
+ * @param {ReadonlyQuat2} a the first operand
+ * @param {ReadonlyQuat2} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {quat2} out
+ */
+
+export function lerp(out, a, b, t) {
+ var mt = 1 - t;
+ if (dot(a, b) < 0) t = -t;
+ out[0] = a[0] * mt + b[0] * t;
+ out[1] = a[1] * mt + b[1] * t;
+ out[2] = a[2] * mt + b[2] * t;
+ out[3] = a[3] * mt + b[3] * t;
+ out[4] = a[4] * mt + b[4] * t;
+ out[5] = a[5] * mt + b[5] * t;
+ out[6] = a[6] * mt + b[6] * t;
+ out[7] = a[7] * mt + b[7] * t;
+ return out;
+}
+/**
+ * Calculates the inverse of a dual quat. If they are normalized, conjugate is cheaper
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a dual quat to calculate inverse of
+ * @returns {quat2} out
+ */
+
+export function invert(out, a) {
+ var sqlen = squaredLength(a);
+ out[0] = -a[0] / sqlen;
+ out[1] = -a[1] / sqlen;
+ out[2] = -a[2] / sqlen;
+ out[3] = a[3] / sqlen;
+ out[4] = -a[4] / sqlen;
+ out[5] = -a[5] / sqlen;
+ out[6] = -a[6] / sqlen;
+ out[7] = a[7] / sqlen;
+ return out;
+}
+/**
+ * Calculates the conjugate of a dual quat
+ * If the dual quaternion is normalized, this function is faster than quat2.inverse and produces the same result.
+ *
+ * @param {quat2} out the receiving quaternion
+ * @param {ReadonlyQuat2} a quat to calculate conjugate of
+ * @returns {quat2} out
+ */
+
+export function conjugate(out, a) {
+ out[0] = -a[0];
+ out[1] = -a[1];
+ out[2] = -a[2];
+ out[3] = a[3];
+ out[4] = -a[4];
+ out[5] = -a[5];
+ out[6] = -a[6];
+ out[7] = a[7];
+ return out;
+}
+/**
+ * Calculates the length of a dual quat
+ *
+ * @param {ReadonlyQuat2} a dual quat to calculate length of
+ * @returns {Number} length of a
+ * @function
+ */
+
+export var length = quat.length;
+/**
+ * Alias for {@link quat2.length}
+ * @function
+ */
+
+export var len = length;
+/**
+ * Calculates the squared length of a dual quat
+ *
+ * @param {ReadonlyQuat2} a dual quat to calculate squared length of
+ * @returns {Number} squared length of a
+ * @function
+ */
+
+export var squaredLength = quat.squaredLength;
+/**
+ * Alias for {@link quat2.squaredLength}
+ * @function
+ */
+
+export var sqrLen = squaredLength;
+/**
+ * Normalize a dual quat
+ *
+ * @param {quat2} out the receiving dual quaternion
+ * @param {ReadonlyQuat2} a dual quaternion to normalize
+ * @returns {quat2} out
+ * @function
+ */
+
+export function normalize(out, a) {
+ var magnitude = squaredLength(a);
+
+ if (magnitude > 0) {
+ magnitude = Math.sqrt(magnitude);
+ var a0 = a[0] / magnitude;
+ var a1 = a[1] / magnitude;
+ var a2 = a[2] / magnitude;
+ var a3 = a[3] / magnitude;
+ var b0 = a[4];
+ var b1 = a[5];
+ var b2 = a[6];
+ var b3 = a[7];
+ var a_dot_b = a0 * b0 + a1 * b1 + a2 * b2 + a3 * b3;
+ out[0] = a0;
+ out[1] = a1;
+ out[2] = a2;
+ out[3] = a3;
+ out[4] = (b0 - a0 * a_dot_b) / magnitude;
+ out[5] = (b1 - a1 * a_dot_b) / magnitude;
+ out[6] = (b2 - a2 * a_dot_b) / magnitude;
+ out[7] = (b3 - a3 * a_dot_b) / magnitude;
+ }
+
+ return out;
+}
+/**
+ * Returns a string representation of a dual quaternion
+ *
+ * @param {ReadonlyQuat2} a dual quaternion to represent as a string
+ * @returns {String} string representation of the dual quat
+ */
+
+export function str(a) {
+ return "quat2(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ")";
+}
+/**
+ * Returns whether or not the dual quaternions have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyQuat2} a the first dual quaternion.
+ * @param {ReadonlyQuat2} b the second dual quaternion.
+ * @returns {Boolean} true if the dual quaternions are equal, false otherwise.
+ */
+
+export function exactEquals(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7];
+}
+/**
+ * Returns whether or not the dual quaternions have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyQuat2} a the first dual quat.
+ * @param {ReadonlyQuat2} b the second dual quat.
+ * @returns {Boolean} true if the dual quats are equal, false otherwise.
+ */
+
+export function equals(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3],
+ a4 = a[4],
+ a5 = a[5],
+ a6 = a[6],
+ a7 = a[7];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3],
+ b4 = b[4],
+ b5 = b[5],
+ b6 = b[6],
+ b7 = b[7];
+ return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7));
+}
\ No newline at end of file
diff --git a/debug/webgpu/gl-matrix/vec2.js b/debug/webgpu/gl-matrix/vec2.js
new file mode 100644
index 0000000000..ec22f91e4f
--- /dev/null
+++ b/debug/webgpu/gl-matrix/vec2.js
@@ -0,0 +1,624 @@
+import * as glMatrix from "./common.js";
+/**
+ * 2 Dimensional Vector
+ * @module vec2
+ */
+
+/**
+ * Creates a new, empty vec2
+ *
+ * @returns {vec2} a new 2D vector
+ */
+
+export function create() {
+ var out = new glMatrix.ARRAY_TYPE(2);
+
+ if (glMatrix.ARRAY_TYPE != Float32Array) {
+ out[0] = 0;
+ out[1] = 0;
+ }
+
+ return out;
+}
+/**
+ * Creates a new vec2 initialized with values from an existing vector
+ *
+ * @param {ReadonlyVec2} a vector to clone
+ * @returns {vec2} a new 2D vector
+ */
+
+export function clone(a) {
+ var out = new glMatrix.ARRAY_TYPE(2);
+ out[0] = a[0];
+ out[1] = a[1];
+ return out;
+}
+/**
+ * Creates a new vec2 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @returns {vec2} a new 2D vector
+ */
+
+export function fromValues(x, y) {
+ var out = new glMatrix.ARRAY_TYPE(2);
+ out[0] = x;
+ out[1] = y;
+ return out;
+}
+/**
+ * Copy the values from one vec2 to another
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the source vector
+ * @returns {vec2} out
+ */
+
+export function copy(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ return out;
+}
+/**
+ * Set the components of a vec2 to the given values
+ *
+ * @param {vec2} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @returns {vec2} out
+ */
+
+export function set(out, x, y) {
+ out[0] = x;
+ out[1] = y;
+ return out;
+}
+/**
+ * Adds two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+export function add(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ return out;
+}
+/**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+export function subtract(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ return out;
+}
+/**
+ * Multiplies two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+export function multiply(out, a, b) {
+ out[0] = a[0] * b[0];
+ out[1] = a[1] * b[1];
+ return out;
+}
+/**
+ * Divides two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+export function divide(out, a, b) {
+ out[0] = a[0] / b[0];
+ out[1] = a[1] / b[1];
+ return out;
+}
+/**
+ * Math.ceil the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to ceil
+ * @returns {vec2} out
+ */
+
+export function ceil(out, a) {
+ out[0] = Math.ceil(a[0]);
+ out[1] = Math.ceil(a[1]);
+ return out;
+}
+/**
+ * Math.floor the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to floor
+ * @returns {vec2} out
+ */
+
+export function floor(out, a) {
+ out[0] = Math.floor(a[0]);
+ out[1] = Math.floor(a[1]);
+ return out;
+}
+/**
+ * Returns the minimum of two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+export function min(out, a, b) {
+ out[0] = Math.min(a[0], b[0]);
+ out[1] = Math.min(a[1], b[1]);
+ return out;
+}
+/**
+ * Returns the maximum of two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec2} out
+ */
+
+export function max(out, a, b) {
+ out[0] = Math.max(a[0], b[0]);
+ out[1] = Math.max(a[1], b[1]);
+ return out;
+}
+/**
+ * Math.round the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to round
+ * @returns {vec2} out
+ */
+
+export function round(out, a) {
+ out[0] = Math.round(a[0]);
+ out[1] = Math.round(a[1]);
+ return out;
+}
+/**
+ * Scales a vec2 by a scalar number
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec2} out
+ */
+
+export function scale(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ return out;
+}
+/**
+ * Adds two vec2's after scaling the second operand by a scalar value
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec2} out
+ */
+
+export function scaleAndAdd(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ return out;
+}
+/**
+ * Calculates the euclidian distance between two vec2's
+ *
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {Number} distance between a and b
+ */
+
+export function distance(a, b) {
+ var x = b[0] - a[0],
+ y = b[1] - a[1];
+ return Math.hypot(x, y);
+}
+/**
+ * Calculates the squared euclidian distance between two vec2's
+ *
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+
+export function squaredDistance(a, b) {
+ var x = b[0] - a[0],
+ y = b[1] - a[1];
+ return x * x + y * y;
+}
+/**
+ * Calculates the length of a vec2
+ *
+ * @param {ReadonlyVec2} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+
+export function length(a) {
+ var x = a[0],
+ y = a[1];
+ return Math.hypot(x, y);
+}
+/**
+ * Calculates the squared length of a vec2
+ *
+ * @param {ReadonlyVec2} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+
+export function squaredLength(a) {
+ var x = a[0],
+ y = a[1];
+ return x * x + y * y;
+}
+/**
+ * Negates the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to negate
+ * @returns {vec2} out
+ */
+
+export function negate(out, a) {
+ out[0] = -a[0];
+ out[1] = -a[1];
+ return out;
+}
+/**
+ * Returns the inverse of the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to invert
+ * @returns {vec2} out
+ */
+
+export function inverse(out, a) {
+ out[0] = 1.0 / a[0];
+ out[1] = 1.0 / a[1];
+ return out;
+}
+/**
+ * Normalize a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a vector to normalize
+ * @returns {vec2} out
+ */
+
+export function normalize(out, a) {
+ var x = a[0],
+ y = a[1];
+ var len = x * x + y * y;
+
+ if (len > 0) {
+ //TODO: evaluate use of glm_invsqrt here?
+ len = 1 / Math.sqrt(len);
+ }
+
+ out[0] = a[0] * len;
+ out[1] = a[1] * len;
+ return out;
+}
+/**
+ * Calculates the dot product of two vec2's
+ *
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+
+export function dot(a, b) {
+ return a[0] * b[0] + a[1] * b[1];
+}
+/**
+ * Computes the cross product of two vec2's
+ * Note that the cross product must by definition produce a 3D vector
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @returns {vec3} out
+ */
+
+export function cross(out, a, b) {
+ var z = a[0] * b[1] - a[1] * b[0];
+ out[0] = out[1] = 0;
+ out[2] = z;
+ return out;
+}
+/**
+ * Performs a linear interpolation between two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the first operand
+ * @param {ReadonlyVec2} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec2} out
+ */
+
+export function lerp(out, a, b, t) {
+ var ax = a[0],
+ ay = a[1];
+ out[0] = ax + t * (b[0] - ax);
+ out[1] = ay + t * (b[1] - ay);
+ return out;
+}
+/**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec2} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If omitted, a unit vector will be returned
+ * @returns {vec2} out
+ */
+
+export function random(out, scale) {
+ scale = scale === undefined ? 1.0 : scale;
+ var r = glMatrix.RANDOM() * 2.0 * Math.PI;
+ out[0] = Math.cos(r) * scale;
+ out[1] = Math.sin(r) * scale;
+ return out;
+}
+/**
+ * Transforms the vec2 with a mat2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the vector to transform
+ * @param {ReadonlyMat2} m matrix to transform with
+ * @returns {vec2} out
+ */
+
+export function transformMat2(out, a, m) {
+ var x = a[0],
+ y = a[1];
+ out[0] = m[0] * x + m[2] * y;
+ out[1] = m[1] * x + m[3] * y;
+ return out;
+}
+/**
+ * Transforms the vec2 with a mat2d
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the vector to transform
+ * @param {ReadonlyMat2d} m matrix to transform with
+ * @returns {vec2} out
+ */
+
+export function transformMat2d(out, a, m) {
+ var x = a[0],
+ y = a[1];
+ out[0] = m[0] * x + m[2] * y + m[4];
+ out[1] = m[1] * x + m[3] * y + m[5];
+ return out;
+}
+/**
+ * Transforms the vec2 with a mat3
+ * 3rd vector component is implicitly '1'
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the vector to transform
+ * @param {ReadonlyMat3} m matrix to transform with
+ * @returns {vec2} out
+ */
+
+export function transformMat3(out, a, m) {
+ var x = a[0],
+ y = a[1];
+ out[0] = m[0] * x + m[3] * y + m[6];
+ out[1] = m[1] * x + m[4] * y + m[7];
+ return out;
+}
+/**
+ * Transforms the vec2 with a mat4
+ * 3rd vector component is implicitly '0'
+ * 4th vector component is implicitly '1'
+ *
+ * @param {vec2} out the receiving vector
+ * @param {ReadonlyVec2} a the vector to transform
+ * @param {ReadonlyMat4} m matrix to transform with
+ * @returns {vec2} out
+ */
+
+export function transformMat4(out, a, m) {
+ var x = a[0];
+ var y = a[1];
+ out[0] = m[0] * x + m[4] * y + m[12];
+ out[1] = m[1] * x + m[5] * y + m[13];
+ return out;
+}
+/**
+ * Rotate a 2D vector
+ * @param {vec2} out The receiving vec2
+ * @param {ReadonlyVec2} a The vec2 point to rotate
+ * @param {ReadonlyVec2} b The origin of the rotation
+ * @param {Number} rad The angle of rotation in radians
+ * @returns {vec2} out
+ */
+
+export function rotate(out, a, b, rad) {
+ //Translate point to the origin
+ var p0 = a[0] - b[0],
+ p1 = a[1] - b[1],
+ sinC = Math.sin(rad),
+ cosC = Math.cos(rad); //perform rotation and translate to correct position
+
+ out[0] = p0 * cosC - p1 * sinC + b[0];
+ out[1] = p0 * sinC + p1 * cosC + b[1];
+ return out;
+}
+/**
+ * Get the angle between two 2D vectors
+ * @param {ReadonlyVec2} a The first operand
+ * @param {ReadonlyVec2} b The second operand
+ * @returns {Number} The angle in radians
+ */
+
+export function angle(a, b) {
+ var x1 = a[0],
+ y1 = a[1],
+ x2 = b[0],
+ y2 = b[1],
+ // mag is the product of the magnitudes of a and b
+ mag = Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)),
+ // mag &&.. short circuits if mag == 0
+ cosine = mag && (x1 * x2 + y1 * y2) / mag; // Math.min(Math.max(cosine, -1), 1) clamps the cosine between -1 and 1
+
+ return Math.acos(Math.min(Math.max(cosine, -1), 1));
+}
+/**
+ * Set the components of a vec2 to zero
+ *
+ * @param {vec2} out the receiving vector
+ * @returns {vec2} out
+ */
+
+export function zero(out) {
+ out[0] = 0.0;
+ out[1] = 0.0;
+ return out;
+}
+/**
+ * Returns a string representation of a vector
+ *
+ * @param {ReadonlyVec2} a vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+
+export function str(a) {
+ return "vec2(" + a[0] + ", " + a[1] + ")";
+}
+/**
+ * Returns whether or not the vectors exactly have the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyVec2} a The first vector.
+ * @param {ReadonlyVec2} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+export function exactEquals(a, b) {
+ return a[0] === b[0] && a[1] === b[1];
+}
+/**
+ * Returns whether or not the vectors have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyVec2} a The first vector.
+ * @param {ReadonlyVec2} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+export function equals(a, b) {
+ var a0 = a[0],
+ a1 = a[1];
+ var b0 = b[0],
+ b1 = b[1];
+ return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1));
+}
+/**
+ * Alias for {@link vec2.length}
+ * @function
+ */
+
+export var len = length;
+/**
+ * Alias for {@link vec2.subtract}
+ * @function
+ */
+
+export var sub = subtract;
+/**
+ * Alias for {@link vec2.multiply}
+ * @function
+ */
+
+export var mul = multiply;
+/**
+ * Alias for {@link vec2.divide}
+ * @function
+ */
+
+export var div = divide;
+/**
+ * Alias for {@link vec2.distance}
+ * @function
+ */
+
+export var dist = distance;
+/**
+ * Alias for {@link vec2.squaredDistance}
+ * @function
+ */
+
+export var sqrDist = squaredDistance;
+/**
+ * Alias for {@link vec2.squaredLength}
+ * @function
+ */
+
+export var sqrLen = squaredLength;
+/**
+ * Perform some operation over an array of vec2s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec2. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+
+export var forEach = function () {
+ var vec = create();
+ return function (a, stride, offset, count, fn, arg) {
+ var i, l;
+
+ if (!stride) {
+ stride = 2;
+ }
+
+ if (!offset) {
+ offset = 0;
+ }
+
+ if (count) {
+ l = Math.min(count * stride + offset, a.length);
+ } else {
+ l = a.length;
+ }
+
+ for (i = offset; i < l; i += stride) {
+ vec[0] = a[i];
+ vec[1] = a[i + 1];
+ fn(vec, vec, arg);
+ a[i] = vec[0];
+ a[i + 1] = vec[1];
+ }
+
+ return a;
+ };
+}();
\ No newline at end of file
diff --git a/debug/webgpu/gl-matrix/vec3.js b/debug/webgpu/gl-matrix/vec3.js
new file mode 100644
index 0000000000..7f07ef5d95
--- /dev/null
+++ b/debug/webgpu/gl-matrix/vec3.js
@@ -0,0 +1,805 @@
+import * as glMatrix from "./common.js";
+/**
+ * 3 Dimensional Vector
+ * @module vec3
+ */
+
+/**
+ * Creates a new, empty vec3
+ *
+ * @returns {vec3} a new 3D vector
+ */
+
+export function create() {
+ var out = new glMatrix.ARRAY_TYPE(3);
+
+ if (glMatrix.ARRAY_TYPE != Float32Array) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ }
+
+ return out;
+}
+/**
+ * Creates a new vec3 initialized with values from an existing vector
+ *
+ * @param {ReadonlyVec3} a vector to clone
+ * @returns {vec3} a new 3D vector
+ */
+
+export function clone(a) {
+ var out = new glMatrix.ARRAY_TYPE(3);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ return out;
+}
+/**
+ * Calculates the length of a vec3
+ *
+ * @param {ReadonlyVec3} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+
+export function length(a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ return Math.hypot(x, y, z);
+}
+/**
+ * Creates a new vec3 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @returns {vec3} a new 3D vector
+ */
+
+export function fromValues(x, y, z) {
+ var out = new glMatrix.ARRAY_TYPE(3);
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+ return out;
+}
+/**
+ * Copy the values from one vec3 to another
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the source vector
+ * @returns {vec3} out
+ */
+
+export function copy(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ return out;
+}
+/**
+ * Set the components of a vec3 to the given values
+ *
+ * @param {vec3} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @returns {vec3} out
+ */
+
+export function set(out, x, y, z) {
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+ return out;
+}
+/**
+ * Adds two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+export function add(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ return out;
+}
+/**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+export function subtract(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ return out;
+}
+/**
+ * Multiplies two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+export function multiply(out, a, b) {
+ out[0] = a[0] * b[0];
+ out[1] = a[1] * b[1];
+ out[2] = a[2] * b[2];
+ return out;
+}
+/**
+ * Divides two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+export function divide(out, a, b) {
+ out[0] = a[0] / b[0];
+ out[1] = a[1] / b[1];
+ out[2] = a[2] / b[2];
+ return out;
+}
+/**
+ * Math.ceil the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to ceil
+ * @returns {vec3} out
+ */
+
+export function ceil(out, a) {
+ out[0] = Math.ceil(a[0]);
+ out[1] = Math.ceil(a[1]);
+ out[2] = Math.ceil(a[2]);
+ return out;
+}
+/**
+ * Math.floor the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to floor
+ * @returns {vec3} out
+ */
+
+export function floor(out, a) {
+ out[0] = Math.floor(a[0]);
+ out[1] = Math.floor(a[1]);
+ out[2] = Math.floor(a[2]);
+ return out;
+}
+/**
+ * Returns the minimum of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+export function min(out, a, b) {
+ out[0] = Math.min(a[0], b[0]);
+ out[1] = Math.min(a[1], b[1]);
+ out[2] = Math.min(a[2], b[2]);
+ return out;
+}
+/**
+ * Returns the maximum of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+export function max(out, a, b) {
+ out[0] = Math.max(a[0], b[0]);
+ out[1] = Math.max(a[1], b[1]);
+ out[2] = Math.max(a[2], b[2]);
+ return out;
+}
+/**
+ * Math.round the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to round
+ * @returns {vec3} out
+ */
+
+export function round(out, a) {
+ out[0] = Math.round(a[0]);
+ out[1] = Math.round(a[1]);
+ out[2] = Math.round(a[2]);
+ return out;
+}
+/**
+ * Scales a vec3 by a scalar number
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec3} out
+ */
+
+export function scale(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ return out;
+}
+/**
+ * Adds two vec3's after scaling the second operand by a scalar value
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec3} out
+ */
+
+export function scaleAndAdd(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ return out;
+}
+/**
+ * Calculates the euclidian distance between two vec3's
+ *
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {Number} distance between a and b
+ */
+
+export function distance(a, b) {
+ var x = b[0] - a[0];
+ var y = b[1] - a[1];
+ var z = b[2] - a[2];
+ return Math.hypot(x, y, z);
+}
+/**
+ * Calculates the squared euclidian distance between two vec3's
+ *
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+
+export function squaredDistance(a, b) {
+ var x = b[0] - a[0];
+ var y = b[1] - a[1];
+ var z = b[2] - a[2];
+ return x * x + y * y + z * z;
+}
+/**
+ * Calculates the squared length of a vec3
+ *
+ * @param {ReadonlyVec3} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+
+export function squaredLength(a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ return x * x + y * y + z * z;
+}
+/**
+ * Negates the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to negate
+ * @returns {vec3} out
+ */
+
+export function negate(out, a) {
+ out[0] = -a[0];
+ out[1] = -a[1];
+ out[2] = -a[2];
+ return out;
+}
+/**
+ * Returns the inverse of the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to invert
+ * @returns {vec3} out
+ */
+
+export function inverse(out, a) {
+ out[0] = 1.0 / a[0];
+ out[1] = 1.0 / a[1];
+ out[2] = 1.0 / a[2];
+ return out;
+}
+/**
+ * Normalize a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a vector to normalize
+ * @returns {vec3} out
+ */
+
+export function normalize(out, a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ var len = x * x + y * y + z * z;
+
+ if (len > 0) {
+ //TODO: evaluate use of glm_invsqrt here?
+ len = 1 / Math.sqrt(len);
+ }
+
+ out[0] = a[0] * len;
+ out[1] = a[1] * len;
+ out[2] = a[2] * len;
+ return out;
+}
+/**
+ * Calculates the dot product of two vec3's
+ *
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+
+export function dot(a, b) {
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+}
+/**
+ * Computes the cross product of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @returns {vec3} out
+ */
+
+export function cross(out, a, b) {
+ var ax = a[0],
+ ay = a[1],
+ az = a[2];
+ var bx = b[0],
+ by = b[1],
+ bz = b[2];
+ out[0] = ay * bz - az * by;
+ out[1] = az * bx - ax * bz;
+ out[2] = ax * by - ay * bx;
+ return out;
+}
+/**
+ * Performs a linear interpolation between two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec3} out
+ */
+
+export function lerp(out, a, b, t) {
+ var ax = a[0];
+ var ay = a[1];
+ var az = a[2];
+ out[0] = ax + t * (b[0] - ax);
+ out[1] = ay + t * (b[1] - ay);
+ out[2] = az + t * (b[2] - az);
+ return out;
+}
+/**
+ * Performs a spherical linear interpolation between two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec3} out
+ */
+
+export function slerp(out, a, b, t) {
+ var angle = Math.acos(Math.min(Math.max(dot(a, b), -1), 1));
+ var sinTotal = Math.sin(angle);
+ var ratioA = Math.sin((1 - t) * angle) / sinTotal;
+ var ratioB = Math.sin(t * angle) / sinTotal;
+ out[0] = ratioA * a[0] + ratioB * b[0];
+ out[1] = ratioA * a[1] + ratioB * b[1];
+ out[2] = ratioA * a[2] + ratioB * b[2];
+ return out;
+}
+/**
+ * Performs a hermite interpolation with two control points
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @param {ReadonlyVec3} c the third operand
+ * @param {ReadonlyVec3} d the fourth operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec3} out
+ */
+
+export function hermite(out, a, b, c, d, t) {
+ var factorTimes2 = t * t;
+ var factor1 = factorTimes2 * (2 * t - 3) + 1;
+ var factor2 = factorTimes2 * (t - 2) + t;
+ var factor3 = factorTimes2 * (t - 1);
+ var factor4 = factorTimes2 * (3 - 2 * t);
+ out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4;
+ out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4;
+ out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4;
+ return out;
+}
+/**
+ * Performs a bezier interpolation with two control points
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the first operand
+ * @param {ReadonlyVec3} b the second operand
+ * @param {ReadonlyVec3} c the third operand
+ * @param {ReadonlyVec3} d the fourth operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec3} out
+ */
+
+export function bezier(out, a, b, c, d, t) {
+ var inverseFactor = 1 - t;
+ var inverseFactorTimesTwo = inverseFactor * inverseFactor;
+ var factorTimes2 = t * t;
+ var factor1 = inverseFactorTimesTwo * inverseFactor;
+ var factor2 = 3 * t * inverseFactorTimesTwo;
+ var factor3 = 3 * factorTimes2 * inverseFactor;
+ var factor4 = factorTimes2 * t;
+ out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4;
+ out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4;
+ out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4;
+ return out;
+}
+/**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec3} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If omitted, a unit vector will be returned
+ * @returns {vec3} out
+ */
+
+export function random(out, scale) {
+ scale = scale === undefined ? 1.0 : scale;
+ var r = glMatrix.RANDOM() * 2.0 * Math.PI;
+ var z = glMatrix.RANDOM() * 2.0 - 1.0;
+ var zScale = Math.sqrt(1.0 - z * z) * scale;
+ out[0] = Math.cos(r) * zScale;
+ out[1] = Math.sin(r) * zScale;
+ out[2] = z * scale;
+ return out;
+}
+/**
+ * Transforms the vec3 with a mat4.
+ * 4th vector component is implicitly '1'
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the vector to transform
+ * @param {ReadonlyMat4} m matrix to transform with
+ * @returns {vec3} out
+ */
+
+export function transformMat4(out, a, m) {
+ var x = a[0],
+ y = a[1],
+ z = a[2];
+ var w = m[3] * x + m[7] * y + m[11] * z + m[15];
+ w = w || 1.0;
+ out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w;
+ out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w;
+ out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;
+ return out;
+}
+/**
+ * Transforms the vec3 with a mat3.
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the vector to transform
+ * @param {ReadonlyMat3} m the 3x3 matrix to transform with
+ * @returns {vec3} out
+ */
+
+export function transformMat3(out, a, m) {
+ var x = a[0],
+ y = a[1],
+ z = a[2];
+ out[0] = x * m[0] + y * m[3] + z * m[6];
+ out[1] = x * m[1] + y * m[4] + z * m[7];
+ out[2] = x * m[2] + y * m[5] + z * m[8];
+ return out;
+}
+/**
+ * Transforms the vec3 with a quat
+ * Can also be used for dual quaternions. (Multiply it with the real part)
+ *
+ * @param {vec3} out the receiving vector
+ * @param {ReadonlyVec3} a the vector to transform
+ * @param {ReadonlyQuat} q quaternion to transform with
+ * @returns {vec3} out
+ */
+
+export function transformQuat(out, a, q) {
+ // benchmarks: https://jsperf.com/quaternion-transform-vec3-implementations-fixed
+ var qx = q[0],
+ qy = q[1],
+ qz = q[2],
+ qw = q[3];
+ var x = a[0],
+ y = a[1],
+ z = a[2]; // var qvec = [qx, qy, qz];
+ // var uv = vec3.cross([], qvec, a);
+
+ var uvx = qy * z - qz * y,
+ uvy = qz * x - qx * z,
+ uvz = qx * y - qy * x; // var uuv = vec3.cross([], qvec, uv);
+
+ var uuvx = qy * uvz - qz * uvy,
+ uuvy = qz * uvx - qx * uvz,
+ uuvz = qx * uvy - qy * uvx; // vec3.scale(uv, uv, 2 * w);
+
+ var w2 = qw * 2;
+ uvx *= w2;
+ uvy *= w2;
+ uvz *= w2; // vec3.scale(uuv, uuv, 2);
+
+ uuvx *= 2;
+ uuvy *= 2;
+ uuvz *= 2; // return vec3.add(out, a, vec3.add(out, uv, uuv));
+
+ out[0] = x + uvx + uuvx;
+ out[1] = y + uvy + uuvy;
+ out[2] = z + uvz + uuvz;
+ return out;
+}
+/**
+ * Rotate a 3D vector around the x-axis
+ * @param {vec3} out The receiving vec3
+ * @param {ReadonlyVec3} a The vec3 point to rotate
+ * @param {ReadonlyVec3} b The origin of the rotation
+ * @param {Number} rad The angle of rotation in radians
+ * @returns {vec3} out
+ */
+
+export function rotateX(out, a, b, rad) {
+ var p = [],
+ r = []; //Translate point to the origin
+
+ p[0] = a[0] - b[0];
+ p[1] = a[1] - b[1];
+ p[2] = a[2] - b[2]; //perform rotation
+
+ r[0] = p[0];
+ r[1] = p[1] * Math.cos(rad) - p[2] * Math.sin(rad);
+ r[2] = p[1] * Math.sin(rad) + p[2] * Math.cos(rad); //translate to correct position
+
+ out[0] = r[0] + b[0];
+ out[1] = r[1] + b[1];
+ out[2] = r[2] + b[2];
+ return out;
+}
+/**
+ * Rotate a 3D vector around the y-axis
+ * @param {vec3} out The receiving vec3
+ * @param {ReadonlyVec3} a The vec3 point to rotate
+ * @param {ReadonlyVec3} b The origin of the rotation
+ * @param {Number} rad The angle of rotation in radians
+ * @returns {vec3} out
+ */
+
+export function rotateY(out, a, b, rad) {
+ var p = [],
+ r = []; //Translate point to the origin
+
+ p[0] = a[0] - b[0];
+ p[1] = a[1] - b[1];
+ p[2] = a[2] - b[2]; //perform rotation
+
+ r[0] = p[2] * Math.sin(rad) + p[0] * Math.cos(rad);
+ r[1] = p[1];
+ r[2] = p[2] * Math.cos(rad) - p[0] * Math.sin(rad); //translate to correct position
+
+ out[0] = r[0] + b[0];
+ out[1] = r[1] + b[1];
+ out[2] = r[2] + b[2];
+ return out;
+}
+/**
+ * Rotate a 3D vector around the z-axis
+ * @param {vec3} out The receiving vec3
+ * @param {ReadonlyVec3} a The vec3 point to rotate
+ * @param {ReadonlyVec3} b The origin of the rotation
+ * @param {Number} rad The angle of rotation in radians
+ * @returns {vec3} out
+ */
+
+export function rotateZ(out, a, b, rad) {
+ var p = [],
+ r = []; //Translate point to the origin
+
+ p[0] = a[0] - b[0];
+ p[1] = a[1] - b[1];
+ p[2] = a[2] - b[2]; //perform rotation
+
+ r[0] = p[0] * Math.cos(rad) - p[1] * Math.sin(rad);
+ r[1] = p[0] * Math.sin(rad) + p[1] * Math.cos(rad);
+ r[2] = p[2]; //translate to correct position
+
+ out[0] = r[0] + b[0];
+ out[1] = r[1] + b[1];
+ out[2] = r[2] + b[2];
+ return out;
+}
+/**
+ * Get the angle between two 3D vectors
+ * @param {ReadonlyVec3} a The first operand
+ * @param {ReadonlyVec3} b The second operand
+ * @returns {Number} The angle in radians
+ */
+
+export function angle(a, b) {
+ var ax = a[0],
+ ay = a[1],
+ az = a[2],
+ bx = b[0],
+ by = b[1],
+ bz = b[2],
+ mag = Math.sqrt((ax * ax + ay * ay + az * az) * (bx * bx + by * by + bz * bz)),
+ cosine = mag && dot(a, b) / mag;
+ return Math.acos(Math.min(Math.max(cosine, -1), 1));
+}
+/**
+ * Set the components of a vec3 to zero
+ *
+ * @param {vec3} out the receiving vector
+ * @returns {vec3} out
+ */
+
+export function zero(out) {
+ out[0] = 0.0;
+ out[1] = 0.0;
+ out[2] = 0.0;
+ return out;
+}
+/**
+ * Returns a string representation of a vector
+ *
+ * @param {ReadonlyVec3} a vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+
+export function str(a) {
+ return "vec3(" + a[0] + ", " + a[1] + ", " + a[2] + ")";
+}
+/**
+ * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyVec3} a The first vector.
+ * @param {ReadonlyVec3} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+export function exactEquals(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
+}
+/**
+ * Returns whether or not the vectors have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyVec3} a The first vector.
+ * @param {ReadonlyVec3} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+export function equals(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2];
+ return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2));
+}
+/**
+ * Alias for {@link vec3.subtract}
+ * @function
+ */
+
+export var sub = subtract;
+/**
+ * Alias for {@link vec3.multiply}
+ * @function
+ */
+
+export var mul = multiply;
+/**
+ * Alias for {@link vec3.divide}
+ * @function
+ */
+
+export var div = divide;
+/**
+ * Alias for {@link vec3.distance}
+ * @function
+ */
+
+export var dist = distance;
+/**
+ * Alias for {@link vec3.squaredDistance}
+ * @function
+ */
+
+export var sqrDist = squaredDistance;
+/**
+ * Alias for {@link vec3.length}
+ * @function
+ */
+
+export var len = length;
+/**
+ * Alias for {@link vec3.squaredLength}
+ * @function
+ */
+
+export var sqrLen = squaredLength;
+/**
+ * Perform some operation over an array of vec3s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec3. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec3s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+
+export var forEach = function () {
+ var vec = create();
+ return function (a, stride, offset, count, fn, arg) {
+ var i, l;
+
+ if (!stride) {
+ stride = 3;
+ }
+
+ if (!offset) {
+ offset = 0;
+ }
+
+ if (count) {
+ l = Math.min(count * stride + offset, a.length);
+ } else {
+ l = a.length;
+ }
+
+ for (i = offset; i < l; i += stride) {
+ vec[0] = a[i];
+ vec[1] = a[i + 1];
+ vec[2] = a[i + 2];
+ fn(vec, vec, arg);
+ a[i] = vec[0];
+ a[i + 1] = vec[1];
+ a[i + 2] = vec[2];
+ }
+
+ return a;
+ };
+}();
\ No newline at end of file
diff --git a/debug/webgpu/gl-matrix/vec4.js b/debug/webgpu/gl-matrix/vec4.js
new file mode 100644
index 0000000000..cd84dcf67d
--- /dev/null
+++ b/debug/webgpu/gl-matrix/vec4.js
@@ -0,0 +1,663 @@
+import * as glMatrix from "./common.js";
+/**
+ * 4 Dimensional Vector
+ * @module vec4
+ */
+
+/**
+ * Creates a new, empty vec4
+ *
+ * @returns {vec4} a new 4D vector
+ */
+
+export function create() {
+ var out = new glMatrix.ARRAY_TYPE(4);
+
+ if (glMatrix.ARRAY_TYPE != Float32Array) {
+ out[0] = 0;
+ out[1] = 0;
+ out[2] = 0;
+ out[3] = 0;
+ }
+
+ return out;
+}
+/**
+ * Creates a new vec4 initialized with values from an existing vector
+ *
+ * @param {ReadonlyVec4} a vector to clone
+ * @returns {vec4} a new 4D vector
+ */
+
+export function clone(a) {
+ var out = new glMatrix.ARRAY_TYPE(4);
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ return out;
+}
+/**
+ * Creates a new vec4 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {vec4} a new 4D vector
+ */
+
+export function fromValues(x, y, z, w) {
+ var out = new glMatrix.ARRAY_TYPE(4);
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+ out[3] = w;
+ return out;
+}
+/**
+ * Copy the values from one vec4 to another
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the source vector
+ * @returns {vec4} out
+ */
+
+export function copy(out, a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ return out;
+}
+/**
+ * Set the components of a vec4 to the given values
+ *
+ * @param {vec4} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {vec4} out
+ */
+
+export function set(out, x, y, z, w) {
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+ out[3] = w;
+ return out;
+}
+/**
+ * Adds two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+export function add(out, a, b) {
+ out[0] = a[0] + b[0];
+ out[1] = a[1] + b[1];
+ out[2] = a[2] + b[2];
+ out[3] = a[3] + b[3];
+ return out;
+}
+/**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+export function subtract(out, a, b) {
+ out[0] = a[0] - b[0];
+ out[1] = a[1] - b[1];
+ out[2] = a[2] - b[2];
+ out[3] = a[3] - b[3];
+ return out;
+}
+/**
+ * Multiplies two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+export function multiply(out, a, b) {
+ out[0] = a[0] * b[0];
+ out[1] = a[1] * b[1];
+ out[2] = a[2] * b[2];
+ out[3] = a[3] * b[3];
+ return out;
+}
+/**
+ * Divides two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+export function divide(out, a, b) {
+ out[0] = a[0] / b[0];
+ out[1] = a[1] / b[1];
+ out[2] = a[2] / b[2];
+ out[3] = a[3] / b[3];
+ return out;
+}
+/**
+ * Math.ceil the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to ceil
+ * @returns {vec4} out
+ */
+
+export function ceil(out, a) {
+ out[0] = Math.ceil(a[0]);
+ out[1] = Math.ceil(a[1]);
+ out[2] = Math.ceil(a[2]);
+ out[3] = Math.ceil(a[3]);
+ return out;
+}
+/**
+ * Math.floor the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to floor
+ * @returns {vec4} out
+ */
+
+export function floor(out, a) {
+ out[0] = Math.floor(a[0]);
+ out[1] = Math.floor(a[1]);
+ out[2] = Math.floor(a[2]);
+ out[3] = Math.floor(a[3]);
+ return out;
+}
+/**
+ * Returns the minimum of two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+export function min(out, a, b) {
+ out[0] = Math.min(a[0], b[0]);
+ out[1] = Math.min(a[1], b[1]);
+ out[2] = Math.min(a[2], b[2]);
+ out[3] = Math.min(a[3], b[3]);
+ return out;
+}
+/**
+ * Returns the maximum of two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {vec4} out
+ */
+
+export function max(out, a, b) {
+ out[0] = Math.max(a[0], b[0]);
+ out[1] = Math.max(a[1], b[1]);
+ out[2] = Math.max(a[2], b[2]);
+ out[3] = Math.max(a[3], b[3]);
+ return out;
+}
+/**
+ * Math.round the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to round
+ * @returns {vec4} out
+ */
+
+export function round(out, a) {
+ out[0] = Math.round(a[0]);
+ out[1] = Math.round(a[1]);
+ out[2] = Math.round(a[2]);
+ out[3] = Math.round(a[3]);
+ return out;
+}
+/**
+ * Scales a vec4 by a scalar number
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec4} out
+ */
+
+export function scale(out, a, b) {
+ out[0] = a[0] * b;
+ out[1] = a[1] * b;
+ out[2] = a[2] * b;
+ out[3] = a[3] * b;
+ return out;
+}
+/**
+ * Adds two vec4's after scaling the second operand by a scalar value
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec4} out
+ */
+
+export function scaleAndAdd(out, a, b, scale) {
+ out[0] = a[0] + b[0] * scale;
+ out[1] = a[1] + b[1] * scale;
+ out[2] = a[2] + b[2] * scale;
+ out[3] = a[3] + b[3] * scale;
+ return out;
+}
+/**
+ * Calculates the euclidian distance between two vec4's
+ *
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {Number} distance between a and b
+ */
+
+export function distance(a, b) {
+ var x = b[0] - a[0];
+ var y = b[1] - a[1];
+ var z = b[2] - a[2];
+ var w = b[3] - a[3];
+ return Math.hypot(x, y, z, w);
+}
+/**
+ * Calculates the squared euclidian distance between two vec4's
+ *
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+
+export function squaredDistance(a, b) {
+ var x = b[0] - a[0];
+ var y = b[1] - a[1];
+ var z = b[2] - a[2];
+ var w = b[3] - a[3];
+ return x * x + y * y + z * z + w * w;
+}
+/**
+ * Calculates the length of a vec4
+ *
+ * @param {ReadonlyVec4} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+
+export function length(a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ var w = a[3];
+ return Math.hypot(x, y, z, w);
+}
+/**
+ * Calculates the squared length of a vec4
+ *
+ * @param {ReadonlyVec4} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+
+export function squaredLength(a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ var w = a[3];
+ return x * x + y * y + z * z + w * w;
+}
+/**
+ * Negates the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to negate
+ * @returns {vec4} out
+ */
+
+export function negate(out, a) {
+ out[0] = -a[0];
+ out[1] = -a[1];
+ out[2] = -a[2];
+ out[3] = -a[3];
+ return out;
+}
+/**
+ * Returns the inverse of the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to invert
+ * @returns {vec4} out
+ */
+
+export function inverse(out, a) {
+ out[0] = 1.0 / a[0];
+ out[1] = 1.0 / a[1];
+ out[2] = 1.0 / a[2];
+ out[3] = 1.0 / a[3];
+ return out;
+}
+/**
+ * Normalize a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a vector to normalize
+ * @returns {vec4} out
+ */
+
+export function normalize(out, a) {
+ var x = a[0];
+ var y = a[1];
+ var z = a[2];
+ var w = a[3];
+ var len = x * x + y * y + z * z + w * w;
+
+ if (len > 0) {
+ len = 1 / Math.sqrt(len);
+ }
+
+ out[0] = x * len;
+ out[1] = y * len;
+ out[2] = z * len;
+ out[3] = w * len;
+ return out;
+}
+/**
+ * Calculates the dot product of two vec4's
+ *
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+
+export function dot(a, b) {
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
+}
+/**
+ * Returns the cross-product of three vectors in a 4-dimensional space
+ *
+ * @param {ReadonlyVec4} result the receiving vector
+ * @param {ReadonlyVec4} U the first vector
+ * @param {ReadonlyVec4} V the second vector
+ * @param {ReadonlyVec4} W the third vector
+ * @returns {vec4} result
+ */
+
+export function cross(out, u, v, w) {
+ var A = v[0] * w[1] - v[1] * w[0],
+ B = v[0] * w[2] - v[2] * w[0],
+ C = v[0] * w[3] - v[3] * w[0],
+ D = v[1] * w[2] - v[2] * w[1],
+ E = v[1] * w[3] - v[3] * w[1],
+ F = v[2] * w[3] - v[3] * w[2];
+ var G = u[0];
+ var H = u[1];
+ var I = u[2];
+ var J = u[3];
+ out[0] = H * F - I * E + J * D;
+ out[1] = -(G * F) + I * C - J * B;
+ out[2] = G * E - H * C + J * A;
+ out[3] = -(G * D) + H * B - I * A;
+ return out;
+}
+/**
+ * Performs a linear interpolation between two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the first operand
+ * @param {ReadonlyVec4} b the second operand
+ * @param {Number} t interpolation amount, in the range [0-1], between the two inputs
+ * @returns {vec4} out
+ */
+
+export function lerp(out, a, b, t) {
+ var ax = a[0];
+ var ay = a[1];
+ var az = a[2];
+ var aw = a[3];
+ out[0] = ax + t * (b[0] - ax);
+ out[1] = ay + t * (b[1] - ay);
+ out[2] = az + t * (b[2] - az);
+ out[3] = aw + t * (b[3] - aw);
+ return out;
+}
+/**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec4} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If omitted, a unit vector will be returned
+ * @returns {vec4} out
+ */
+
+export function random(out, scale) {
+ scale = scale === undefined ? 1.0 : scale; // Marsaglia, George. Choosing a Point from the Surface of a
+ // Sphere. Ann. Math. Statist. 43 (1972), no. 2, 645--646.
+ // http://projecteuclid.org/euclid.aoms/1177692644;
+
+ var v1, v2, v3, v4;
+ var s1, s2;
+
+ do {
+ v1 = glMatrix.RANDOM() * 2 - 1;
+ v2 = glMatrix.RANDOM() * 2 - 1;
+ s1 = v1 * v1 + v2 * v2;
+ } while (s1 >= 1);
+
+ do {
+ v3 = glMatrix.RANDOM() * 2 - 1;
+ v4 = glMatrix.RANDOM() * 2 - 1;
+ s2 = v3 * v3 + v4 * v4;
+ } while (s2 >= 1);
+
+ var d = Math.sqrt((1 - s1) / s2);
+ out[0] = scale * v1;
+ out[1] = scale * v2;
+ out[2] = scale * v3 * d;
+ out[3] = scale * v4 * d;
+ return out;
+}
+/**
+ * Transforms the vec4 with a mat4.
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the vector to transform
+ * @param {ReadonlyMat4} m matrix to transform with
+ * @returns {vec4} out
+ */
+
+export function transformMat4(out, a, m) {
+ var x = a[0],
+ y = a[1],
+ z = a[2],
+ w = a[3];
+ out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
+ out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
+ out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
+ out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
+ return out;
+}
+/**
+ * Transforms the vec4 with a quat
+ *
+ * @param {vec4} out the receiving vector
+ * @param {ReadonlyVec4} a the vector to transform
+ * @param {ReadonlyQuat} q quaternion to transform with
+ * @returns {vec4} out
+ */
+
+export function transformQuat(out, a, q) {
+ var x = a[0],
+ y = a[1],
+ z = a[2];
+ var qx = q[0],
+ qy = q[1],
+ qz = q[2],
+ qw = q[3]; // calculate quat * vec
+
+ var ix = qw * x + qy * z - qz * y;
+ var iy = qw * y + qz * x - qx * z;
+ var iz = qw * z + qx * y - qy * x;
+ var iw = -qx * x - qy * y - qz * z; // calculate result * inverse quat
+
+ out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
+ out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
+ out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
+ out[3] = a[3];
+ return out;
+}
+/**
+ * Set the components of a vec4 to zero
+ *
+ * @param {vec4} out the receiving vector
+ * @returns {vec4} out
+ */
+
+export function zero(out) {
+ out[0] = 0.0;
+ out[1] = 0.0;
+ out[2] = 0.0;
+ out[3] = 0.0;
+ return out;
+}
+/**
+ * Returns a string representation of a vector
+ *
+ * @param {ReadonlyVec4} a vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+
+export function str(a) {
+ return "vec4(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")";
+}
+/**
+ * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
+ *
+ * @param {ReadonlyVec4} a The first vector.
+ * @param {ReadonlyVec4} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+export function exactEquals(a, b) {
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
+}
+/**
+ * Returns whether or not the vectors have approximately the same elements in the same position.
+ *
+ * @param {ReadonlyVec4} a The first vector.
+ * @param {ReadonlyVec4} b The second vector.
+ * @returns {Boolean} True if the vectors are equal, false otherwise.
+ */
+
+export function equals(a, b) {
+ var a0 = a[0],
+ a1 = a[1],
+ a2 = a[2],
+ a3 = a[3];
+ var b0 = b[0],
+ b1 = b[1],
+ b2 = b[2],
+ b3 = b[3];
+ return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3));
+}
+/**
+ * Alias for {@link vec4.subtract}
+ * @function
+ */
+
+export var sub = subtract;
+/**
+ * Alias for {@link vec4.multiply}
+ * @function
+ */
+
+export var mul = multiply;
+/**
+ * Alias for {@link vec4.divide}
+ * @function
+ */
+
+export var div = divide;
+/**
+ * Alias for {@link vec4.distance}
+ * @function
+ */
+
+export var dist = distance;
+/**
+ * Alias for {@link vec4.squaredDistance}
+ * @function
+ */
+
+export var sqrDist = squaredDistance;
+/**
+ * Alias for {@link vec4.length}
+ * @function
+ */
+
+export var len = length;
+/**
+ * Alias for {@link vec4.squaredLength}
+ * @function
+ */
+
+export var sqrLen = squaredLength;
+/**
+ * Perform some operation over an array of vec4s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec4. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec4s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+
+export var forEach = function () {
+ var vec = create();
+ return function (a, stride, offset, count, fn, arg) {
+ var i, l;
+
+ if (!stride) {
+ stride = 4;
+ }
+
+ if (!offset) {
+ offset = 0;
+ }
+
+ if (count) {
+ l = Math.min(count * stride + offset, a.length);
+ } else {
+ l = a.length;
+ }
+
+ for (i = offset; i < l; i += stride) {
+ vec[0] = a[i];
+ vec[1] = a[i + 1];
+ vec[2] = a[i + 2];
+ vec[3] = a[i + 3];
+ fn(vec, vec, arg);
+ a[i] = vec[0];
+ a[i + 1] = vec[1];
+ a[i + 2] = vec[2];
+ a[i + 3] = vec[3];
+ }
+
+ return a;
+ };
+}();
\ No newline at end of file
diff --git a/debug/webgpu/index.html b/debug/webgpu/index.html
new file mode 100644
index 0000000000..5570e07211
--- /dev/null
+++ b/debug/webgpu/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+ reshader(WebGPU) examples
+
+
+
+
+
diff --git a/debug/webgpu/meshes/cube.js b/debug/webgpu/meshes/cube.js
new file mode 100644
index 0000000000..0411d80523
--- /dev/null
+++ b/debug/webgpu/meshes/cube.js
@@ -0,0 +1,124 @@
+export const cubeVertexCount = 36;
+
+// prettier-ignore
+export const cubeVertexArray = new Float32Array([
+ 1, -1, 1, 1,
+ -1, -1, 1, 1,
+ -1, -1, -1, 1,
+ 1, -1, -1, 1,
+ 1, -1, 1, 1,
+ -1, -1, -1, 1,
+
+ 1, 1, 1, 1,
+ 1, -1, 1, 1,
+ 1, -1, -1, 1,
+ 1, 1, -1, 1,
+ 1, 1, 1, 1,
+ 1, -1, -1, 1,
+
+ -1, 1, 1, 1,
+ 1, 1, 1, 1,
+ 1, 1, -1, 1,
+ -1, 1, -1, 1,
+ -1, 1, 1, 1,
+ 1, 1, -1, 1,
+
+ -1, -1, 1, 1,
+ -1, 1, 1, 1,
+ -1, 1, -1, 1,
+ -1, -1, -1, 1,
+ -1, -1, 1, 1,
+ -1, 1, -1, 1,
+
+ 1, 1, 1, 1,
+ -1, 1, 1, 1,
+ -1, -1, 1, 1,
+ -1, -1, 1, 1,
+ 1, -1, 1, 1,
+ 1, 1, 1, 1,
+
+ 1, -1, -1, 1,
+ -1, -1, -1, 1,
+ -1, 1, -1, 1,
+ 1, 1, -1, 1,
+ 1, -1, -1, 1,
+ -1, 1, -1, 1
+]);
+
+export const cubeVertexColors = new Float32Array([
+ 1, 0, 1, 1,
+ 0, 0, 1, 1,
+ 0, 0, 0, 1,
+ 1, 0, 0, 1,
+ 1, 0, 1, 1,
+ 0, 0, 0, 1,
+ 1, 1, 1, 1,
+ 1, 0, 1, 1,
+ 1, 0, 0, 1,
+ 1, 1, 0, 1,
+ 1, 1, 1, 1,
+ 1, 0, 0, 1,
+ 0, 1, 1, 1,
+ 1, 1, 1, 1,
+ 1, 1, 0, 1,
+ 0, 1, 0, 1,
+ 0, 1, 1, 1,
+ 1, 1, 0, 1,
+ 0, 0, 1, 1,
+ 0, 1, 1, 1,
+ 0, 1, 0, 1,
+ 0, 0, 0, 1,
+ 0, 0, 1, 1,
+ 0, 1, 0, 1,
+ 1, 1, 1, 1,
+ 0, 1, 1, 1,
+ 0, 0, 1, 1,
+ 0, 0, 1, 1,
+ 1, 0, 1, 1,
+ 1, 1, 1, 1,
+ 1, 0, 0, 1,
+ 0, 0, 0, 1,
+ 0, 1, 0, 1,
+ 1, 1, 0, 1,
+ 1, 0, 0, 1,
+ 0, 1, 0, 1
+]);
+
+export const cubeVertexUV = new Float32Array([
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0,
+ 0, 1,
+ 1, 0,
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0,
+ 0, 1,
+ 1, 0,
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0,
+ 0, 1,
+ 1, 0,
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0,
+ 0, 1,
+ 1, 0,
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ 1, 0,
+ 0, 0,
+ 0, 1,
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0,
+ 0, 1,
+ 1, 0
+]);
diff --git a/debug/webgpu/meshes/ground.js b/debug/webgpu/meshes/ground.js
new file mode 100644
index 0000000000..75baaeb396
--- /dev/null
+++ b/debug/webgpu/meshes/ground.js
@@ -0,0 +1,33 @@
+//
+export function getGroundVertexes() {
+ // Push vertex attributes for an additional ground plane
+ // prettier-ignore
+ return [
+ 3, -3, -3, 1,
+ 3, 3, -3, 1,
+ -3, 3, -3, 1,
+
+ 3, -3, -3, 1,
+ -3, 3, -3, 1,
+ -3, -3, -3, 1
+ ];
+
+ // if (triangles) {
+ // // Push indices for an additional ground plane
+ // triangles.push(
+ // [positions.length, positions.length + 2, positions.length + 1],
+ // [positions.length, positions.length + 1, positions.length + 3]
+ // );
+ // }
+
+
+ // if (uvs) {
+ // uvs.push(
+ // [0, 0], //
+ // [1, 1], //
+ // [0, 1], //
+ // [1, 0]
+ // );
+ // }
+
+}
diff --git a/debug/webgpu/shadowmapping.html b/debug/webgpu/shadowmapping.html
new file mode 100644
index 0000000000..3eb31a75ac
--- /dev/null
+++ b/debug/webgpu/shadowmapping.html
@@ -0,0 +1,312 @@
+
+
+
+
+ Shadow mapping
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index 17e481a8e1..168eacc787 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"build-dev": "turbo run build-dev*",
"lint": "turbo run lint",
"prettier": "prettier --parser=typescript --write **/*.{js,ts,md}",
- "test": "turbo run test",
+ "maptalks-test": "pnpm --filter=maptalks run test",
"changeset": "changeset",
"changeset-version": "changeset version",
"release": "pnpm build && changeset publish"
@@ -43,6 +43,6 @@
"git add"
]
},
- "packageManager": "pnpm@9.1.2",
+ "packageManager": "pnpm@10.4.1",
"license": "MIT"
}
diff --git a/packages/analysis/package.json b/packages/analysis/package.json
index 8d82415132..427da955eb 100644
--- a/packages/analysis/package.json
+++ b/packages/analysis/package.json
@@ -48,7 +48,7 @@
"karma-expect": "^1.1.3",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
- "@maptalks/map": "workspace:*",
+ "maptalks": "workspace:*",
"mocha": "^10.3.0",
"pixelmatch": "^4.0.2",
"rollup": "^4.17.2"
diff --git a/packages/analysis/src/Analysis.js b/packages/analysis/src/Analysis.js
index 1f9ebdd0b8..14504fe01f 100644
--- a/packages/analysis/src/Analysis.js
+++ b/packages/analysis/src/Analysis.js
@@ -1,5 +1,5 @@
import { reshader, mat4, quat, earcut } from '@maptalks/gl';
-import { Class, Eventable, Handlerable, Polygon } from '@maptalks/map';
+import { Class, Eventable, Handlerable, Polygon } from 'maptalks';
import ExtentPass from './pass/ExtentPass';
import { coordinateToWorld } from './common/Util';
diff --git a/packages/analysis/src/CrossCutAnalysis.js b/packages/analysis/src/CrossCutAnalysis.js
index 39e0eb2cba..2e29c654c7 100644
--- a/packages/analysis/src/CrossCutAnalysis.js
+++ b/packages/analysis/src/CrossCutAnalysis.js
@@ -1,5 +1,5 @@
import Analysis from './Analysis';
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { reshader, mat4 } from '@maptalks/gl';
import CrossCutPass from './pass/CrossCutPass';
import pickingVert from './pass/glsl/picking.vert';
diff --git a/packages/analysis/src/ExcavateRenderer.js b/packages/analysis/src/ExcavateRenderer.js
index fea9c1d33e..756c6f0758 100644
--- a/packages/analysis/src/ExcavateRenderer.js
+++ b/packages/analysis/src/ExcavateRenderer.js
@@ -1,5 +1,5 @@
import { reshader, mat4, vec4 } from '@maptalks/gl';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
import HeightmapPass from './pass/HeightmapPass';
import { coordinateToWorld, altitudeToDistance } from './common/Util';
import { ExtrudePolygonLayer } from '@maptalks/vt';
diff --git a/packages/analysis/src/common/Util.js b/packages/analysis/src/common/Util.js
index 4d084db405..7aadcd5100 100644
--- a/packages/analysis/src/common/Util.js
+++ b/packages/analysis/src/common/Util.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
export function containerPointToWorldPoint(point, map) {
diff --git a/packages/analysis/src/pass/CrossCutPass.js b/packages/analysis/src/pass/CrossCutPass.js
index 474c2506f2..d44687079b 100644
--- a/packages/analysis/src/pass/CrossCutPass.js
+++ b/packages/analysis/src/pass/CrossCutPass.js
@@ -1,7 +1,7 @@
import { reshader, mat4 } from '@maptalks/gl';
import vert from './glsl/crosscut.vert';
import frag from './glsl/crosscut.frag';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
import AnalysisPass from './AnalysisPass';
export default class CrossCutPass extends AnalysisPass {
diff --git a/packages/analysis/src/pass/CutAnalysisController.js b/packages/analysis/src/pass/CutAnalysisController.js
index 417cc0d5e2..bc0d2dafc3 100644
--- a/packages/analysis/src/pass/CutAnalysisController.js
+++ b/packages/analysis/src/pass/CutAnalysisController.js
@@ -1,6 +1,6 @@
import { reshader, mat4, quat, vec3, vec2 } from '@maptalks/gl';
import partsModels from '../common/parts';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
import { defined } from '../common/Util';
import pickingVert from './glsl/picking.vert';
diff --git a/packages/analysis/src/pass/CutPass.js b/packages/analysis/src/pass/CutPass.js
index b9bf1b689b..e2b3beb351 100644
--- a/packages/analysis/src/pass/CutPass.js
+++ b/packages/analysis/src/pass/CutPass.js
@@ -2,7 +2,7 @@ import { mat4 } from '@maptalks/gl';
import { reshader } from '@maptalks/gl';
import vert from './glsl/cut.vert';
import frag from './glsl/cut.frag';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
import CutAnalysisController from './CutAnalysisController';
import CutShader from './CutShader';
import AnalysisPass from './AnalysisPass';
diff --git a/packages/analysis/src/pass/ExcavatePass.js b/packages/analysis/src/pass/ExcavatePass.js
index 30ecd697d9..2ebc67869c 100644
--- a/packages/analysis/src/pass/ExcavatePass.js
+++ b/packages/analysis/src/pass/ExcavatePass.js
@@ -2,7 +2,7 @@ import { mat4 } from '@maptalks/gl';
import { reshader } from '@maptalks/gl';
import vert from './glsl/excavate.vert';
import frag from './glsl/excavate.frag';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
import AnalysisPass from './AnalysisPass';
const clearColor = [1.0, 0.0, 0.0, 1];
diff --git a/packages/analysis/src/pass/ExtentPass.js b/packages/analysis/src/pass/ExtentPass.js
index f4e3727abf..17df8ce580 100644
--- a/packages/analysis/src/pass/ExtentPass.js
+++ b/packages/analysis/src/pass/ExtentPass.js
@@ -1,5 +1,5 @@
import { reshader, mat4 } from '@maptalks/gl';
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import vert from './glsl/excavateExtent.vert';
import frag from './glsl/excavateExtent.frag';
import AnalysisPass from './AnalysisPass';
diff --git a/packages/analysis/src/pass/FloodPass.js b/packages/analysis/src/pass/FloodPass.js
index ff0f100e20..4214931bee 100644
--- a/packages/analysis/src/pass/FloodPass.js
+++ b/packages/analysis/src/pass/FloodPass.js
@@ -1,7 +1,7 @@
import { reshader } from '@maptalks/gl';
import vert from './glsl/flood.vert';
import frag from './glsl/flood.frag';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
import AnalysisPass from './AnalysisPass';
export default class FloodPass extends AnalysisPass {
diff --git a/packages/analysis/src/pass/HeightmapPass.js b/packages/analysis/src/pass/HeightmapPass.js
index b17c4333ee..13fcdfa3bb 100644
--- a/packages/analysis/src/pass/HeightmapPass.js
+++ b/packages/analysis/src/pass/HeightmapPass.js
@@ -1,5 +1,5 @@
import { reshader, mat4 } from '@maptalks/gl';
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import vert from './glsl/heightmap.vert';
import frag from './glsl/heightmap.frag';
import AnalysisPass from './AnalysisPass';
diff --git a/packages/analysis/src/pass/InSightPass.js b/packages/analysis/src/pass/InSightPass.js
index 8fd3e8ecd3..ba431a8cb8 100644
--- a/packages/analysis/src/pass/InSightPass.js
+++ b/packages/analysis/src/pass/InSightPass.js
@@ -2,7 +2,7 @@ import { mat4 } from '@maptalks/gl';
import { reshader } from '@maptalks/gl';
import vert from './glsl/insight.vert';
import frag from './glsl/insight.frag';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
import AnalysisPass from './AnalysisPass';
const helperPos = [
diff --git a/packages/analysis/src/pass/OutlinePass.js b/packages/analysis/src/pass/OutlinePass.js
index 7218f613ca..ba3b03782b 100644
--- a/packages/analysis/src/pass/OutlinePass.js
+++ b/packages/analysis/src/pass/OutlinePass.js
@@ -3,7 +3,7 @@ import quadVert from './glsl/quad.vert';
import extentFrag from './glsl/extent.frag';
import outlineFrag from './glsl/outline.frag';
import sceneVert from './glsl/sceneVert.vert';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
import AnalysisPass from './AnalysisPass';
export default class OutlinePass extends AnalysisPass {
diff --git a/packages/analysis/src/pass/ViewShedPass.js b/packages/analysis/src/pass/ViewShedPass.js
index b6167ea707..9a8edea866 100644
--- a/packages/analysis/src/pass/ViewShedPass.js
+++ b/packages/analysis/src/pass/ViewShedPass.js
@@ -2,7 +2,7 @@ import { mat4, quat, vec3 } from '@maptalks/gl';
import { reshader } from '@maptalks/gl';
import vert from './glsl/viewshed.vert';
import frag from './glsl/viewshed.frag';
-import { Util, Point } from '@maptalks/map';
+import { Util, Point } from 'maptalks';
import AnalysisPass from './AnalysisPass';
const helperPos = [
diff --git a/packages/gl/package.json b/packages/gl/package.json
index c70a0bece1..98b10c3893 100644
--- a/packages/gl/package.json
+++ b/packages/gl/package.json
@@ -33,7 +33,7 @@
"dependencies": {
"@maptalks/fusiongl": "workspace:*",
"@maptalks/gltf-loader": "workspace:*",
- "@maptalks/map": "workspace:*",
+ "maptalks": "workspace:*",
"@maptalks/martini": "^0.4.0",
"@maptalks/regl": "^3.4.0",
"@maptalks/reshader.gl": "workspace:*",
diff --git a/packages/gl/src/analysis/AnalysisPainter.js b/packages/gl/src/analysis/AnalysisPainter.js
index 115e5fb8e9..fbaa3644c0 100644
--- a/packages/gl/src/analysis/AnalysisPainter.js
+++ b/packages/gl/src/analysis/AnalysisPainter.js
@@ -1,7 +1,7 @@
import * as reshader from '@maptalks/reshader.gl';
import AnalysisShader from './AnalysisShader.js';
import { extend } from '../layer/util/util.js';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
class AnalysisPainter {
constructor(regl, layer, config) {
diff --git a/packages/gl/src/analysis/Area3DTool.js b/packages/gl/src/analysis/Area3DTool.js
index 1c17f36953..1fd5e3f75b 100644
--- a/packages/gl/src/analysis/Area3DTool.js
+++ b/packages/gl/src/analysis/Area3DTool.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import Measure3DTool from './Measure3DTool';
export default class Area3DTool extends Measure3DTool {
diff --git a/packages/gl/src/analysis/Distance3DTool.js b/packages/gl/src/analysis/Distance3DTool.js
index 14cf2d6235..490c828ed6 100644
--- a/packages/gl/src/analysis/Distance3DTool.js
+++ b/packages/gl/src/analysis/Distance3DTool.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import Measure3DTool from './Measure3DTool';
export default class Distance3DTool extends Measure3DTool {
diff --git a/packages/gl/src/analysis/Height3DTool.js b/packages/gl/src/analysis/Height3DTool.js
index 155b1dbe7d..9c4a417572 100644
--- a/packages/gl/src/analysis/Height3DTool.js
+++ b/packages/gl/src/analysis/Height3DTool.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import Measure3DTool from './Measure3DTool';
const MEASURE_HEIGHT_NAMES = [['直线距离', '垂直高度', '水平距离'], ['spatial distance', 'vertical height', 'horizontal distance']];
diff --git a/packages/gl/src/analysis/Measure3DTool.js b/packages/gl/src/analysis/Measure3DTool.js
index c7b4822660..ba6bd88044 100644
--- a/packages/gl/src/analysis/Measure3DTool.js
+++ b/packages/gl/src/analysis/Measure3DTool.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
const options = {
'mode': 'LineString',
diff --git a/packages/gl/src/index-dev.js b/packages/gl/src/index-dev.js
index 40f29013fa..987dcc8967 100644
--- a/packages/gl/src/index-dev.js
+++ b/packages/gl/src/index-dev.js
@@ -18,6 +18,6 @@ export * from './index.ts';
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import chunk from '../build/worker.js';
maptalks.registerWorkerAdapter('@maptalks/terrain', chunk);
diff --git a/packages/gl/src/layer/GroundPainter.js b/packages/gl/src/layer/GroundPainter.js
index 806c95ff5a..2a5c120fe4 100644
--- a/packages/gl/src/layer/GroundPainter.js
+++ b/packages/gl/src/layer/GroundPainter.js
@@ -340,7 +340,7 @@ class GroundPainter {
]
);
planeGeo.createTangent();
- planeGeo.generateBuffers(this.renderer.regl);
+ planeGeo.generateBuffers(this.renderer.device);
//TODO 还需要构造 tangent
this._ground = new reshader.Mesh(planeGeo, null, { castShadow: false });
diff --git a/packages/gl/src/layer/GroupGLLayer.ts b/packages/gl/src/layer/GroupGLLayer.ts
index a23dde88fc..651ed8d3e0 100644
--- a/packages/gl/src/layer/GroupGLLayer.ts
+++ b/packages/gl/src/layer/GroupGLLayer.ts
@@ -1,11 +1,11 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import Renderer from './GroupGLLayerRenderer.js';
import { vec3 } from '@maptalks/reshader.gl';
import { isNil, extend } from './util/util.js';
import TerrainLayer from './terrain/TerrainLayer';
import RayCaster from './raycaster/RayCaster.js';
import Mask from './mask/Mask.js';
-import { LayerJSONType } from '@maptalks/map';
+import { LayerJSONType } from 'maptalks';
const options: GroupGLLayerOptions = {
renderer : 'gl',
diff --git a/packages/gl/src/layer/GroupGLLayerRenderer.js b/packages/gl/src/layer/GroupGLLayerRenderer.js
index cd2bee5902..2681eb5ba9 100644
--- a/packages/gl/src/layer/GroupGLLayerRenderer.js
+++ b/packages/gl/src/layer/GroupGLLayerRenderer.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { vec2, vec3, mat4 } from '@maptalks/reshader.gl';
import ShadowProcess from './shadow/ShadowProcess';
import * as reshader from '@maptalks/reshader.gl';
diff --git a/packages/gl/src/layer/HeatmapProcess.js b/packages/gl/src/layer/HeatmapProcess.js
index a65fbb7768..f1cdf8bc09 100644
--- a/packages/gl/src/layer/HeatmapProcess.js
+++ b/packages/gl/src/layer/HeatmapProcess.js
@@ -24,7 +24,7 @@ export default class HeatmapProcess {
render(scene, uniforms, fbo) {
this._check();
const map = this._layer.getMap();
- this.renderer.regl.clear({
+ this.renderer.device.clear({
color: EMPTY_COLOR,
depth: 1,
stencil: 0xFF,
@@ -86,7 +86,7 @@ export default class HeatmapProcess {
if (this._colorRampTex) {
this._colorRampTex.destroy();
}
- const regl = this.renderer.regl;
+ const regl = this.renderer.device;
this._colorRampTex = regl.texture({
width: 256,
height: 1,
@@ -116,7 +116,7 @@ export default class HeatmapProcess {
_createGround() {
const planeGeo = new reshader.Plane();
- planeGeo.generateBuffers(this.renderer.regl);
+ planeGeo.generateBuffers(this.renderer.device);
this._ground = new reshader.Mesh(planeGeo);
this._groundScene = new reshader.Scene([this._ground]);
}
@@ -129,7 +129,7 @@ export default class HeatmapProcess {
_createHeatmapTex() {
const canvas = this._layer.getRenderer().canvas;
- const regl = this.renderer.regl;
+ const regl = this.renderer.device;
const colorType = regl.hasExtension('OES_texture_half_float') ? 'half float' : 'float';
const width = Math.ceil(canvas.width / 4);
const height = Math.ceil(canvas.height / 4);
@@ -185,9 +185,6 @@ export default class HeatmapProcess {
}
},{
extraCommandProps: {
- stencil: {
- enable: false
- },
depth: {
enable: true,
range: depthRange || [0, 1],
@@ -196,9 +193,6 @@ export default class HeatmapProcess {
polygonOffset: {
enable: true,
offset: this._polygonOffset
- },
- scissor: {
- enable: false
}
}
});
diff --git a/packages/gl/src/layer/TileLayerGLRenderer.ts b/packages/gl/src/layer/TileLayerGLRenderer.ts
index a320e3aa7a..47f040d82b 100644
--- a/packages/gl/src/layer/TileLayerGLRenderer.ts
+++ b/packages/gl/src/layer/TileLayerGLRenderer.ts
@@ -1,5 +1,5 @@
-import * as maptalks from '@maptalks/map';
-import { RenderContext, Tile } from '@maptalks/map';
+import * as maptalks from 'maptalks';
+import { RenderContext, Tile } from 'maptalks';
import * as reshader from '@maptalks/reshader.gl';
import { mat4 } from '@maptalks/reshader.gl';
diff --git a/packages/gl/src/layer/mask/BoxClipMask.js b/packages/gl/src/layer/mask/BoxClipMask.js
index 1fa0a25ee9..d9f9738baf 100644
--- a/packages/gl/src/layer/mask/BoxClipMask.js
+++ b/packages/gl/src/layer/mask/BoxClipMask.js
@@ -1,4 +1,4 @@
-import { Point } from '@maptalks/map';
+import { Point } from 'maptalks';
import { vec2 } from '@maptalks/reshader.gl';
import ClipMask from './ClipMask';
diff --git a/packages/gl/src/layer/mask/Mask.js b/packages/gl/src/layer/mask/Mask.js
index 21722dda3e..036a0aa068 100644
--- a/packages/gl/src/layer/mask/Mask.js
+++ b/packages/gl/src/layer/mask/Mask.js
@@ -1,4 +1,4 @@
-import { Coordinate, Polygon, Point } from "@maptalks/map";
+import { Coordinate, Polygon, Point } from "maptalks";
import * as reshader from '@maptalks/reshader.gl';
import { mat4, quat } from '@maptalks/reshader.gl';
import { earcut } from '@maptalks/reshader.gl';
diff --git a/packages/gl/src/layer/mask/MaskLayerMixin.ts b/packages/gl/src/layer/mask/MaskLayerMixin.ts
index c4f260cef9..e92ff2674e 100644
--- a/packages/gl/src/layer/mask/MaskLayerMixin.ts
+++ b/packages/gl/src/layer/mask/MaskLayerMixin.ts
@@ -1,8 +1,8 @@
-import { Coordinate, Extent } from "@maptalks/map";
+import { Coordinate, Extent } from "maptalks";
import { mat4, vec3 } from '@maptalks/reshader.gl';
import Mask from "./Mask";
import { extend } from "../util/util";
-import { MixinConstructor } from "@maptalks/map";
+import { MixinConstructor } from "maptalks";
const maskLayerEvents = ['shapechange', 'heightrangechange', 'flatheightchange'];
const COORD_EXTENT = new Coordinate(0, 0);
diff --git a/packages/gl/src/layer/raycaster/RayCaster.js b/packages/gl/src/layer/raycaster/RayCaster.js
index 026e5e3155..8aaaa1bb2d 100644
--- a/packages/gl/src/layer/raycaster/RayCaster.js
+++ b/packages/gl/src/layer/raycaster/RayCaster.js
@@ -1,6 +1,6 @@
import { vec3, vec4, mat4 } from '@maptalks/reshader.gl';
-import { Coordinate, Point, Util } from '@maptalks/map';
-import * as maptalks from '@maptalks/map';
+import { Coordinate, Point, Util } from 'maptalks';
+import * as maptalks from 'maptalks';
const TRIANGLE = [], POS_A = [], POS_B = [], POS_C = [], TEMP_POINT = new Point(0, 0), NULL_ALTITUDES = [];
const TEMP_VEC_AB = [], TEMP_VEC_AC = [];
diff --git a/packages/gl/src/layer/shadow/ShadowProcess.js b/packages/gl/src/layer/shadow/ShadowProcess.js
index bfbd517904..f2f9db8bd2 100644
--- a/packages/gl/src/layer/shadow/ShadowProcess.js
+++ b/packages/gl/src/layer/shadow/ShadowProcess.js
@@ -219,7 +219,7 @@ class ShadowProcess {
_createGround() {
const planeGeo = new reshader.Plane();
- planeGeo.generateBuffers(this.renderer.regl);
+ planeGeo.generateBuffers(this.renderer.device);
this._ground = new reshader.Mesh(planeGeo);
this._groundScene = new reshader.Scene([this._ground]);
}
diff --git a/packages/gl/src/layer/terrain/TerrainLayer.js b/packages/gl/src/layer/terrain/TerrainLayer.js
index 9503ed6045..42d2466791 100644
--- a/packages/gl/src/layer/terrain/TerrainLayer.js
+++ b/packages/gl/src/layer/terrain/TerrainLayer.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import TerrainLayerRenderer from './TerrainLayerRenderer';
import { getTileIdsAtLevel, getSkinTileScale, getSkinTileRes, getCascadeTileIds } from './TerrainTileUtil';
import { extend } from '../util/util';
diff --git a/packages/gl/src/layer/terrain/TerrainLayerRenderer.js b/packages/gl/src/layer/terrain/TerrainLayerRenderer.js
index 5ec1f258c3..42153ce907 100644
--- a/packages/gl/src/layer/terrain/TerrainLayerRenderer.js
+++ b/packages/gl/src/layer/terrain/TerrainLayerRenderer.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import TerrainWorkerConnection from './TerrainWorkerConnection';
import * as reshader from '@maptalks/reshader.gl';
import skinVert from './glsl/terrainSkin.vert';
@@ -1395,9 +1395,6 @@ class TerrainLayerRenderer extends MaskRendererMixin(maptalks.renderer.TileLayer
depth: {
enable: false
},
- stencil: {
- enable: false
- },
blend: {
enable: true,
func: { src: 'one', dst: 'one minus src alpha' },
diff --git a/packages/gl/src/layer/terrain/TerrainPainter.js b/packages/gl/src/layer/terrain/TerrainPainter.js
index af4a7bc6cf..7a6695c1f3 100644
--- a/packages/gl/src/layer/terrain/TerrainPainter.js
+++ b/packages/gl/src/layer/terrain/TerrainPainter.js
@@ -230,9 +230,6 @@ class TerrainPainter {
return canvas ? canvas.height : 1;
}
},
- stencil: {
- enable: false
- },
cull: {
enable: true,
face: 'back'
diff --git a/packages/gl/src/layer/terrain/TerrainTileUtil.js b/packages/gl/src/layer/terrain/TerrainTileUtil.js
index 320869f4d2..1680f4671f 100644
--- a/packages/gl/src/layer/terrain/TerrainTileUtil.js
+++ b/packages/gl/src/layer/terrain/TerrainTileUtil.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { createMartiniData } from './util/martini.js';
export function getCascadeTileIds(layer, x, y, z, center, offset, terrainTileScaleY, scale, levelLimit) {
diff --git a/packages/gl/src/layer/terrain/TerrainWorkerConnection.js b/packages/gl/src/layer/terrain/TerrainWorkerConnection.js
index f16f3c94a2..158d0913c5 100644
--- a/packages/gl/src/layer/terrain/TerrainWorkerConnection.js
+++ b/packages/gl/src/layer/terrain/TerrainWorkerConnection.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
export default class TerrainWorkerConnection extends maptalks.worker.Actor {
constructor(mapId) {
diff --git a/packages/gl/src/layer/util/util.js b/packages/gl/src/layer/util/util.js
index c336d5b9f9..888d908de7 100644
--- a/packages/gl/src/layer/util/util.js
+++ b/packages/gl/src/layer/util/util.js
@@ -1,7 +1,7 @@
const supportAssign = typeof Object.assign === 'function';
import { vec3, mat4 } from '@maptalks/reshader.gl';
import Color from 'color';
-import { Coordinate } from '@maptalks/map';
+import { Coordinate } from 'maptalks';
/**
* Merges the properties of sources into destination object.
diff --git a/packages/gl/src/layer/util/uvUniforms.js b/packages/gl/src/layer/util/uvUniforms.js
index ccec194e01..3583235746 100644
--- a/packages/gl/src/layer/util/uvUniforms.js
+++ b/packages/gl/src/layer/util/uvUniforms.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { vec2 } from '@maptalks/reshader.gl';
const DEFAULT_TEX_OFFSET = [0, 0];
diff --git a/packages/gl/src/layer/weather/RainPainter.js b/packages/gl/src/layer/weather/RainPainter.js
index 3645e25865..e30a9603c4 100644
--- a/packages/gl/src/layer/weather/RainPainter.js
+++ b/packages/gl/src/layer/weather/RainPainter.js
@@ -170,7 +170,7 @@ class RainPainer {
uv0Attribute: 'TEXCOORD_0'
}
);
- geometry.generateBuffers(this.renderer.regl);
+ geometry.generateBuffers(this.renderer.device);
const material = new reshader.Material({
rainMap: this._regl.texture({ width: 2, height: 2 }),
diffuse: rainConfig.color || [1, 1, 1],
diff --git a/packages/gl/src/light/MapLights.js b/packages/gl/src/light/MapLights.js
index 75bc9539eb..a88cd24339 100644
--- a/packages/gl/src/light/MapLights.js
+++ b/packages/gl/src/light/MapLights.js
@@ -1,4 +1,4 @@
-import { Map } from '@maptalks/map';
+import { Map } from 'maptalks';
import LightManager from './LightManager.js';
diff --git a/packages/gl/src/map/MapGLRenderer.js b/packages/gl/src/map/MapGLRenderer.js
index 5997ac68d9..2f4435ee16 100644
--- a/packages/gl/src/map/MapGLRenderer.js
+++ b/packages/gl/src/map/MapGLRenderer.js
@@ -1,6 +1,6 @@
import { GLContext } from '@maptalks/fusiongl';
import createREGL from '@maptalks/regl';
-import { Map, renderer } from '@maptalks/map';
+import { Map, renderer } from 'maptalks';
export default class MapGLRenderer extends renderer.MapCanvasRenderer {
// createCanvas, createContext, getContextInstance, clearLayerCanvasContext 和 clearCanvas 方法都应该动态注入
diff --git a/packages/gl/src/map/MapPostProcess.js b/packages/gl/src/map/MapPostProcess.js
index 1ec9c9ce1b..5761b58c28 100644
--- a/packages/gl/src/map/MapPostProcess.js
+++ b/packages/gl/src/map/MapPostProcess.js
@@ -1,4 +1,4 @@
-import { Map, renderer } from '@maptalks/map';
+import { Map, renderer } from 'maptalks';
import createREGL from '@maptalks/regl';
import PostProcess from '../layer/postprocess/PostProcess';
diff --git a/packages/gltf-loader/package.json b/packages/gltf-loader/package.json
index 116e9275fb..45c05928cf 100644
--- a/packages/gltf-loader/package.json
+++ b/packages/gltf-loader/package.json
@@ -32,7 +32,7 @@
"karma-expect": "^1.1.3",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
- "@maptalks/map": "workspace:*",
+ "maptalks": "workspace:*",
"mocha": "^10.3.0",
"rollup": "^4.17.2"
},
diff --git a/packages/layer-3dtiles/package.json b/packages/layer-3dtiles/package.json
index 70996f5c0a..3e8df780aa 100644
--- a/packages/layer-3dtiles/package.json
+++ b/packages/layer-3dtiles/package.json
@@ -40,7 +40,7 @@
"homepage": "https://github.com/fuzhenn/3dtiles-issues/",
"dependencies": {
"@maptalks/function-type": "^1.4.0",
- "@maptalks/map": "workspace:*",
+ "maptalks": "workspace:*",
"@maptalks/tbn-packer": "^1.4.5",
"frustum-intersects": "^0.2.4",
"gl-matrix": "^3.4.0",
diff --git a/packages/layer-3dtiles/src/layer/Geo3DTilesLayer.ts b/packages/layer-3dtiles/src/layer/Geo3DTilesLayer.ts
index 1a3fed40f0..22ae9d26ff 100644
--- a/packages/layer-3dtiles/src/layer/Geo3DTilesLayer.ts
+++ b/packages/layer-3dtiles/src/layer/Geo3DTilesLayer.ts
@@ -2,7 +2,7 @@
// box: http://localhost/3dtiles/debug/xx.html
// region:
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { quat, vec2, vec3, mat3, mat4, MaskLayerMixin, ClipOutsideMask } from '@maptalks/gl';
//https://github.com/fuzhenn/frustum-intersects
import { intersectsSphere, intersectsOrientedBox } from 'frustum-intersects';
@@ -14,7 +14,7 @@ import { radianToCartesian3, cartesian3ToDegree } from '../common/Transform';
import { distanceToCamera } from '../common/intersects_oriented_box.js';
import TileBoundingRegion from './renderer/TileBoundingRegion';
import { eastNorthUpToFixedFrame } from '../common/TileHelper';
-import { LayerJSONType } from '@maptalks/map';
+import { LayerJSONType } from 'maptalks';
type BBOX = [number, number, number, number];
diff --git a/packages/layer-3dtiles/src/layer/Geo3DTilesWorkerConnection.js b/packages/layer-3dtiles/src/layer/Geo3DTilesWorkerConnection.js
index a302cf917e..00f459d864 100644
--- a/packages/layer-3dtiles/src/layer/Geo3DTilesWorkerConnection.js
+++ b/packages/layer-3dtiles/src/layer/Geo3DTilesWorkerConnection.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
const canvas = typeof document === 'undefined' ? null : document.createElement('canvas');
diff --git a/packages/layer-3dtiles/src/layer/renderer/Geo3DTilesRenderer.js b/packages/layer-3dtiles/src/layer/renderer/Geo3DTilesRenderer.js
index a5973957ca..4e3ca10b65 100644
--- a/packages/layer-3dtiles/src/layer/renderer/Geo3DTilesRenderer.js
+++ b/packages/layer-3dtiles/src/layer/renderer/Geo3DTilesRenderer.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { reshader } from '@maptalks/gl';
import { getGLTFLoaderBundle } from '@maptalks/gl/dist/transcoders.js';
import { MaskRendererMixin } from '@maptalks/gl';
diff --git a/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js b/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js
index c13376d493..917983e40a 100644
--- a/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js
+++ b/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { reshader, vec3, vec4, mat3, mat4, quat, HighlightUtil, ContextUtil } from '@maptalks/gl';
import { iterateMesh, iterateBufferData, getItemAtBufferData, setInstanceData, } from '../../common/GLTFHelpers';
import pntsVert from './glsl/pnts.vert';
@@ -803,7 +803,8 @@ export default class TileMeshPainter {
instanceBuffers[p] = {
buffer: this._regl.buffer({
dimension: instanceData[p].length / instanceCount,
- data: instanceData[p]
+ data: instanceData[p],
+ name: p
}),
divisor: 1
};
@@ -1447,17 +1448,14 @@ export default class TileMeshPainter {
}
const attrs = {};
for (const p in attributes) {
- const buffer = getUniqueREGLBuffer(this._regl, attributes[p], { dimension: attributes[p].itemSize });
+ const buffer = getUniqueREGLBuffer(this._regl, attributes[p], { dimension: attributes[p].itemSize, name: p });
// 优先采用 attributeSemantics中定义的属性
const name = attributeSemantics[p] || p;
- attrs[name] = { buffer };
- if (attributes[p].quantization) {
- attrs[name].quantization = attributes[p].quantization;
- }
+ attrs[name] = extend({}, attributes[p]);
+ attrs[name].buffer = buffer;
+ delete attrs[name].array;
if (name === attributeSemantics['POSITION']) {
attrs[name].array = attributes[p].array;
- attrs[name].min = attributes[p].min;
- attrs[name].max = attributes[p].max;
}
}
// createColorArray(attrs);
@@ -1723,12 +1721,7 @@ export default class TileMeshPainter {
},
// mask: 0xff
},
- opFront: {
- fail: 'keep',
- zfail: 'keep',
- zpass: 'replace'
- },
- opBack: {
+ op: {
fail: 'keep',
zfail: 'keep',
zpass: 'replace'
diff --git a/packages/layer-gltf/package.json b/packages/layer-gltf/package.json
index 780a7fde2a..3e7c6f4431 100644
--- a/packages/layer-gltf/package.json
+++ b/packages/layer-gltf/package.json
@@ -47,7 +47,7 @@
"karma-happen": "^0.1.0",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
- "@maptalks/map": "workspace:*",
+ "maptalks": "workspace:*",
"mocha": "^10.3.0",
"rollup": "^4.17.2"
},
diff --git a/packages/layer-gltf/src/AEMarker.js b/packages/layer-gltf/src/AEMarker.js
index 85974f7fa5..814f209966 100644
--- a/packages/layer-gltf/src/AEMarker.js
+++ b/packages/layer-gltf/src/AEMarker.js
@@ -1,5 +1,5 @@
import EffectMarker from './EffectMarker';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
const DEFAULT_SYMBOL = {
animation : true,
diff --git a/packages/layer-gltf/src/EffectLine.js b/packages/layer-gltf/src/EffectLine.js
index 6efc785bae..3304d3685e 100644
--- a/packages/layer-gltf/src/EffectLine.js
+++ b/packages/layer-gltf/src/EffectLine.js
@@ -1,5 +1,5 @@
import EffectMarker from './EffectMarker';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
const DEFAULT_SYMBOL = {
url: 'plane',
diff --git a/packages/layer-gltf/src/EffectRing.js b/packages/layer-gltf/src/EffectRing.js
index f9b5568134..4a05d1740a 100644
--- a/packages/layer-gltf/src/EffectRing.js
+++ b/packages/layer-gltf/src/EffectRing.js
@@ -1,5 +1,5 @@
import GLTFMarker from './GLTFMarker';
-import { Util } from '@maptalks/map';
+import { Util } from 'maptalks';
const DEFAULT_UNIFORM = {
offsetX: 0,
diff --git a/packages/layer-gltf/src/GLTFLayer.js b/packages/layer-gltf/src/GLTFLayer.js
index 72ee76b477..c154297d99 100644
--- a/packages/layer-gltf/src/GLTFLayer.js
+++ b/packages/layer-gltf/src/GLTFLayer.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { reshader, MaskLayerMixin } from '@maptalks/gl';
import GLTFLayerRenderer from './GLTFLayerRenderer';
import GLTFMarker from './GLTFMarker';
diff --git a/packages/layer-gltf/src/GLTFLayerRenderer.js b/packages/layer-gltf/src/GLTFLayerRenderer.js
index 3cee75f860..80da2555a2 100644
--- a/packages/layer-gltf/src/GLTFLayerRenderer.js
+++ b/packages/layer-gltf/src/GLTFLayerRenderer.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { defined } from './common/Util';
import { createREGL, mat4, vec2, reshader, MaskRendererMixin } from '@maptalks/gl';
import { intersectsBox } from 'frustum-intersects';
diff --git a/packages/layer-gltf/src/GLTFLineString.js b/packages/layer-gltf/src/GLTFLineString.js
index 5b7f2515bc..b87de4e9b8 100644
--- a/packages/layer-gltf/src/GLTFLineString.js
+++ b/packages/layer-gltf/src/GLTFLineString.js
@@ -1,5 +1,5 @@
import MultiGLTFMarker from "./MultiGLTFMarker";
-import { Coordinate, Util } from "@maptalks/map";
+import { Coordinate, Util } from "maptalks";
import { vec3 } from '@maptalks/gl';
const options = {
diff --git a/packages/layer-gltf/src/GLTFMarker.js b/packages/layer-gltf/src/GLTFMarker.js
index 1cc72f744c..d60e8302c1 100644
--- a/packages/layer-gltf/src/GLTFMarker.js
+++ b/packages/layer-gltf/src/GLTFMarker.js
@@ -1,4 +1,4 @@
-import { Marker, Util, Point, Extent } from '@maptalks/map';
+import { Marker, Util, Point, Extent } from 'maptalks';
import { mat4, quat, vec3, vec4, reshader } from '@maptalks/gl';
import { defined, coordinateToWorld, getAbsoluteValue, getGLTFAnchorsAlongLine } from './common/Util';
import { loadFunctionTypes, hasFunctionDefinition } from '@maptalks/function-type';
diff --git a/packages/layer-gltf/src/GLTFMercatorGeometry.js b/packages/layer-gltf/src/GLTFMercatorGeometry.js
index af7c8d4aa3..7993ce49c9 100644
--- a/packages/layer-gltf/src/GLTFMercatorGeometry.js
+++ b/packages/layer-gltf/src/GLTFMercatorGeometry.js
@@ -1,5 +1,5 @@
import GLTFMarker from './GLTFMarker';
-import { Coordinate, Point, projection } from '@maptalks/map';
+import { Coordinate, Point, projection } from 'maptalks';
const COORD = new Coordinate(0, 0), POINT = new Point(0, 0), TEMP_POINT1 = new Point(0, 0), TEMP_POINT2 = new Point(0, 0);
export default class GLTFMercatorGeometry extends GLTFMarker {
_calSpatialScale(out) {
diff --git a/packages/layer-gltf/src/MultiGLTFMarker.js b/packages/layer-gltf/src/MultiGLTFMarker.js
index fde119fa79..8b6a918156 100644
--- a/packages/layer-gltf/src/MultiGLTFMarker.js
+++ b/packages/layer-gltf/src/MultiGLTFMarker.js
@@ -1,6 +1,6 @@
import GLTFMarker from './GLTFMarker';
-import { Coordinate } from '@maptalks/map';
+import { Coordinate } from 'maptalks';
import { mat4, vec3, quat, reshader } from '@maptalks/gl';
import { coordinateToWorld, defined } from './common/Util';
// The structure of MultiGLTFMarker will like below:
diff --git a/packages/layer-gltf/src/common/AbstractGLTFLayer.js b/packages/layer-gltf/src/common/AbstractGLTFLayer.js
index 6a01f5648c..0eed4af151 100644
--- a/packages/layer-gltf/src/common/AbstractGLTFLayer.js
+++ b/packages/layer-gltf/src/common/AbstractGLTFLayer.js
@@ -1,6 +1,6 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { compileStyle } from '@maptalks/feature-filter';
import { isNil } from './Util';
import { GeoJSON, GEOJSON_TYPES } from './GeoJSON';
diff --git a/packages/layer-gltf/src/common/GLTFWorkerConnection.js b/packages/layer-gltf/src/common/GLTFWorkerConnection.js
index 4058da6f72..82b538261d 100644
--- a/packages/layer-gltf/src/common/GLTFWorkerConnection.js
+++ b/packages/layer-gltf/src/common/GLTFWorkerConnection.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { getAbsoluteURL } from './Util';
const canvas = typeof document === 'undefined' ? null : document.createElement('canvas');
diff --git a/packages/layer-gltf/src/common/GeoJSON.js b/packages/layer-gltf/src/common/GeoJSON.js
index c9797c329a..ce08002f57 100644
--- a/packages/layer-gltf/src/common/GeoJSON.js
+++ b/packages/layer-gltf/src/common/GeoJSON.js
@@ -1,4 +1,4 @@
-import { Util, Geometry } from '@maptalks/map';
+import { Util, Geometry } from 'maptalks';
import GLTFMarker from '../GLTFMarker';
import EffectMarker from '../EffectMarker';
diff --git a/packages/layer-gltf/src/common/Util.js b/packages/layer-gltf/src/common/Util.js
index 897090b1e2..a034e9166a 100644
--- a/packages/layer-gltf/src/common/Util.js
+++ b/packages/layer-gltf/src/common/Util.js
@@ -1,4 +1,4 @@
-import { Coordinate, measurer, Util } from '@maptalks/map';
+import { Coordinate, measurer, Util } from 'maptalks';
/**
* Whether the object is null or undefined.
* @param {Object} obj - object
diff --git a/packages/layer-video/package.json b/packages/layer-video/package.json
index 548bbdd129..921ce243c6 100644
--- a/packages/layer-video/package.json
+++ b/packages/layer-video/package.json
@@ -44,7 +44,7 @@
"karma-happen": "^0.1.0",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
- "@maptalks/map": "workspace:*",
+ "maptalks": "workspace:*",
"mocha": "^10.3.0",
"rollup": "^4.17.2"
},
diff --git a/packages/layer-video/src/VideoLayer.ts b/packages/layer-video/src/VideoLayer.ts
index 4fbb4e3e75..86a6a67fdb 100644
--- a/packages/layer-video/src/VideoLayer.ts
+++ b/packages/layer-video/src/VideoLayer.ts
@@ -1,4 +1,4 @@
-import { Layer } from "@maptalks/map";
+import { Layer } from "maptalks";
import VideoLayerRenderer from "./VideoLayerRenderer";
import VideoSurface from "./VideoSurface";
diff --git a/packages/layer-video/src/VideoLayerRenderer.js b/packages/layer-video/src/VideoLayerRenderer.js
index 2477f8a1fa..6954e3e8c9 100644
--- a/packages/layer-video/src/VideoLayerRenderer.js
+++ b/packages/layer-video/src/VideoLayerRenderer.js
@@ -1,4 +1,4 @@
-import * as maptalks from '@maptalks/map';
+import * as maptalks from 'maptalks';
import { createREGL, reshader, mat4, quat } from '@maptalks/gl';
import { intersectsBox } from 'frustum-intersects';
diff --git a/packages/layer-video/src/VideoSurface.ts b/packages/layer-video/src/VideoSurface.ts
index 5bc393883a..b65853e8f3 100644
--- a/packages/layer-video/src/VideoSurface.ts
+++ b/packages/layer-video/src/VideoSurface.ts
@@ -5,7 +5,7 @@ import {
Handlerable,
Polygon,
DrawToolLayer,
-} from "@maptalks/map";
+} from "maptalks";
interface VideoSurfaceOptions {
url?: string;
diff --git a/packages/map/bulid/api-files.js b/packages/map/build/api-files.js
similarity index 100%
rename from packages/map/bulid/api-files.js
rename to packages/map/build/api-files.js
diff --git a/packages/map/bulid/karma.config.js b/packages/map/build/karma.config.js
similarity index 100%
rename from packages/map/bulid/karma.config.js
rename to packages/map/build/karma.config.js
diff --git a/packages/map/package.json b/packages/map/package.json
index 6d1ef90863..8a9aaf551e 100644
--- a/packages/map/package.json
+++ b/packages/map/package.json
@@ -1,5 +1,5 @@
{
- "name": "@maptalks/map",
+ "name": "maptalks",
"version": "1.0.0",
"description": "A light JavaScript library to create integrated 2D/3D maps.",
"license": "BSD-3-Clause",
diff --git a/packages/map/src/core/Canvas.ts b/packages/map/src/core/Canvas.ts
index 376dd9765d..8140392ce8 100644
--- a/packages/map/src/core/Canvas.ts
+++ b/packages/map/src/core/Canvas.ts
@@ -116,7 +116,7 @@ const defaultChars = getDefaultCharacterSet();
/**
* 文本是否全部是默认字符
* @param chars
- * @returns
+ * @returns
*/
function textIsDefaultChars(chars: string[]) {
for (let i = 0, len = chars.length; i < len; i++) {
@@ -141,12 +141,12 @@ function reverseChars(chars: string[]) {
/**
* 字符的旋转角度
- * @param p1
- * @param p2
- * @param char
- * @param direction
- * @param isDefaultChars
- * @returns
+ * @param p1
+ * @param p2
+ * @param char
+ * @param direction
+ * @param isDefaultChars
+ * @returns
*/
function getCharRotation(p1: Point, p2: Point, char: string, direction: string, isDefaultChars: boolean) {
const x0 = p1.x, y0 = p1.y;
@@ -176,9 +176,9 @@ function getCharRotation(p1: Point, p2: Point, char: string, direction: string,
/**
* 测量字符的大小
- * @param char
- * @param fontSize
- * @returns
+ * @param char
+ * @param fontSize
+ * @returns
*/
function measureCharSize(char: string, fontSize: number) {
let w = fontSize, h = fontSize;
@@ -192,9 +192,9 @@ function measureCharSize(char: string, fontSize: number) {
/**
* 计算文本的长度
- * @param textName
- * @param fontSize
- * @returns
+ * @param textName
+ * @param fontSize
+ * @returns
*/
function measureTextLength(textName: string, fontSize: number) {
let textLen = 0;
@@ -220,9 +220,9 @@ function getPercentPoint(segment: segmentType, dis: number) {
/**
* path 分割
* https://github.com/deyihu/lineseg
- * @param points
- * @param options
- * @returns
+ * @param points
+ * @param options
+ * @returns
*/
function lineSeg(points: Array, options: any) {
options = Object.assign({ segDistance: 1, isGeo: true }, options);
@@ -298,8 +298,8 @@ function lineSeg(points: Array, options: any) {
/**
* 文本路径方向
- * @param path
- * @returns
+ * @param path
+ * @returns
*/
function textPathDirection(path: Array) {
const len = path.length;
@@ -330,10 +330,10 @@ function textPathDirection(path: Array) {
/**
* 获取文本沿线路径的点
- * @param chunk
- * @param chars
- * @param fontSize
- * @returns
+ * @param chunk
+ * @param chars
+ * @param fontSize
+ * @returns
*/
function getTextPath(chunk: Array, chars: string[], fontSize: number, globalCollisonIndex: CollisionIndex) {
const total = pathDistance(chunk);
@@ -810,12 +810,11 @@ const Canvas = {
// @ts-ignore
ctx.canvas._drawn = true;
try {
- // if (isNumber(width) && isNumber(height)) {
- // ctx.drawImage(img, x, y, width, height);
- // } else {
- // ctx.drawImage(img, x, y);
- // }
- ctx.drawImage(img, 0, 0, (img as any).width, (img as any).height, x, y, width, height)
+ if (isNumber(width) && isNumber(height)) {
+ ctx.drawImage(img, x, y, width, height);
+ } else {
+ ctx.drawImage(img, x, y);
+ }
} catch (error) {
if (console) {
console.warn('error when drawing image on canvas:', error);
@@ -1123,12 +1122,12 @@ const Canvas = {
/**
* mock gradient path
* 利用颜色插值来模拟渐变的Path
- * @param ctx
- * @param points
- * @param lineDashArray
- * @param lineOpacity
- * @param isRing
- * @returns
+ * @param ctx
+ * @param points
+ * @param lineDashArray
+ * @param lineOpacity
+ * @param isRing
+ * @returns
*/
_gradientPath(ctx: CanvasRenderingContext2D, points, lineDashArray, lineOpacity, isRing = false) {
if (!isNumber(lineOpacity)) {
diff --git a/packages/map/src/geometry/GeoJSON.ts b/packages/map/src/geometry/GeoJSON.ts
index 4d3f3ffc93..78038aa2bc 100644
--- a/packages/map/src/geometry/GeoJSON.ts
+++ b/packages/map/src/geometry/GeoJSON.ts
@@ -7,7 +7,8 @@ import {
isNumber,
extend,
getAbsoluteURL,
- GUID
+ GUID,
+ isFunction
} from '../core/util';
import Marker from './Marker';
import LineString from './LineString';
@@ -148,6 +149,7 @@ const GeoJSON = {
* Convert one or more GeoJSON objects to geometry
* @param {String|Object|Object[]} geoJSON - GeoJSON objects or GeoJSON string
* @param {Function} [foreachFn=undefined] - callback function for each geometry
+ * @param {Function} [filterFn=undefined] - filter function for each geometry
* @return {Geometry|Geometry[]} a geometry array when input is a FeatureCollection
* @example
* var collection = {
@@ -187,12 +189,13 @@ const GeoJSON = {
* // A geometry array.
* const geometries = GeoJSON.toGeometry(collection, geometry => { geometry.config('draggable', true); });
*/
- toGeometry: function (geoJSON: any, foreachFn?: any): any {
+ toGeometry: function (geoJSON: any, foreachFn?: (geo: Geometry) => void, filterFn?: (geo: Geometry) => boolean): any {
if (isString(geoJSON)) {
geoJSON = parseJSON(geoJSON);
}
+ let resultGeos;
if (Array.isArray(geoJSON)) {
- const resultGeos = [];
+ resultGeos = [];
for (let i = 0, len = geoJSON.length; i < len; i++) {
const geo = GeoJSON._convert(geoJSON[i], foreachFn);
if (Array.isArray(geo)) {
@@ -201,10 +204,13 @@ const GeoJSON = {
resultGeos.push(geo);
}
}
- return resultGeos;
} else {
- const resultGeo = GeoJSON._convert(geoJSON, foreachFn);
- return resultGeo;
+ resultGeos = GeoJSON._convert(geoJSON, foreachFn);
+ }
+ if (filterFn && isFunction(filterFn) && Array.isArray(resultGeos)) {
+ return resultGeos.filter(filterFn);
+ } else {
+ return resultGeos;
}
},
@@ -215,13 +221,14 @@ const GeoJSON = {
* @param {String|Object|Object[]} geoJSON - GeoJSON objects or GeoJSON string
* @param {Function} [foreachFn=undefined] - callback function for each geometry
* @param {Number} [countPerTime=2000] - Number of graphics converted per time
+ * @param {Function} [filterFn=undefined] - filter function for each geometry
* @return {Promise}
* @example
* GeoJSON.toGeometryAsync(geoJSON).then(geos=>{
* console.log(geos);
* })
* */
- toGeometryAsync(geoJSON: any, foreachFn: any, countPerTime: number = 2000): any {
+ toGeometryAsync(geoJSON: any, foreachFn?: (geo: Geometry) => void, countPerTime?: number, filterFn?: (geo: Geometry) => boolean): any {
if (isString(geoJSON)) {
geoJSON = parseJSON(geoJSON);
}
@@ -235,7 +242,7 @@ const GeoJSON = {
const run = () => {
const startIndex = (page - 1) * pageSize, endIndex = (page) * pageSize;
const fs = features.slice(startIndex, endIndex);
- const geos = GeoJSON.toGeometry(fs, foreachFn);
+ const geos = GeoJSON.toGeometry(fs, foreachFn, filterFn);
page++;
return geos;
};
diff --git a/packages/map/src/geometry/Geometry.ts b/packages/map/src/geometry/Geometry.ts
index 74a365b414..0f47e87590 100644
--- a/packages/map/src/geometry/Geometry.ts
+++ b/packages/map/src/geometry/Geometry.ts
@@ -1912,9 +1912,13 @@ export class Geometry extends JSONAble(Eventable(Handlerable(Class))) {
//update coordinates.z
setCoordinatesAlt(coordinates, alt);
if (layer) {
- // const render = layer.getRenderer();
+ const renderer = layer.getRenderer();
//for webgllayer,pointlayer/linestringlayer/polygonlayer
- this.setCoordinates(coordinates);
+ if (renderer && (renderer.gl || (renderer as any).device)) {
+ this.setCoordinates(coordinates);
+ } else if (renderer) {
+ this._repaint();
+ }
}
this._clearAltitudeCache();
this.onPositionChanged();
diff --git a/packages/map/src/geometry/ext/Geometry.Drag.ts b/packages/map/src/geometry/ext/Geometry.Drag.ts
index df7f3f04a8..5eb6d76390 100644
--- a/packages/map/src/geometry/ext/Geometry.Drag.ts
+++ b/packages/map/src/geometry/ext/Geometry.Drag.ts
@@ -5,9 +5,8 @@ import Browser from '../../core/Browser';
import Handler from '../../handler/Handler';
import Geometry from '../Geometry';
import DragHandler from '../../handler/Drag';
-// import VectorLayer from '../../layer/VectorLayer';
import { ConnectorLine } from '../ConnectorLine';
-// import { ResourceCache } from '../../renderer/layer/CanvasRenderer';
+import { ResourceCache } from '../../renderer/layer/CanvasRenderer';
import Point from '../../geo/Point';
import Coordinate from '../../geo/Coordinate';
@@ -110,7 +109,7 @@ class GeometryDragHandler extends Handler {
if (!target.options.dragShadow) {
return;
}
- // this._prepareDragStageLayer();
+ this._prepareDragStageLayer();
if (this._shadow) {
this._shadow.remove();
}
@@ -143,7 +142,7 @@ class GeometryDragHandler extends Handler {
//copy connectors
const target = this.target;
const shadow = this._shadow;
- // const resources = this._dragStageLayer._getRenderer().resources;
+ const resources = this._dragStageLayer._getRenderer().resources;
const shadowConnectors = [];
if (ConnectorLine._hasConnectors(target)) {
@@ -160,15 +159,15 @@ class GeometryDragHandler extends Handler {
conn = new targetConn.constructor(targetConn.getConnectSource(), shadow, connOptions);
}
shadowConnectors.push(conn);
- // if (targetConn.getLayer() && targetConn.getLayer()._getRenderer()) {
- // resources.merge(targetConn.getLayer()._getRenderer().resources);
- // }
+ if (targetConn.getLayer() && targetConn.getLayer()._getRenderer()) {
+ resources.merge(targetConn.getLayer()._getRenderer().resources);
+ }
}
}
this._shadowConnectors = shadowConnectors;
shadowConnectors.push(shadow);
- // this._dragStageLayer.bringToFront().addGeometry(shadowConnectors);
+ this._dragStageLayer.bringToFront().addGeometry(shadowConnectors);
}
//@internal
@@ -179,22 +178,25 @@ class GeometryDragHandler extends Handler {
}
//@internal
- // _prepareDragStageLayer(): void {
- // const map = this.target.getMap(),
- // layer = this.target.getLayer();
- // this._dragStageLayer = map.getLayer(DRAG_STAGE_LAYER_ID);
- // if (!this._dragStageLayer) {
- // this._dragStageLayer = new VectorLayer(DRAG_STAGE_LAYER_ID, {
- // enableAltitude: layer.options['enableAltitude'],
- // altitudeProperty: layer.options['altitudeProperty']
- // });
- // map.addLayer(this._dragStageLayer);
- // }
- // //copy resources to avoid repeat resource loading.
- // const resources = new ResourceCache();
- // resources.merge(layer._getRenderer().resources);
- // this._dragStageLayer._getRenderer().resources = resources;
- // }
+ _prepareDragStageLayer(): void {
+ const map = this.target.getMap(),
+ layer = this.target.getLayer();
+ const prevLayer = map.getLayer(DRAG_STAGE_LAYER_ID);
+ if (prevLayer) {
+ prevLayer.remove();
+ }
+
+ const layerClazz = layer.constructor;
+ this._dragStageLayer = new layerClazz(DRAG_STAGE_LAYER_ID, {
+ enableAltitude: layer.options['enableAltitude'],
+ altitudeProperty: layer.options['altitudeProperty']
+ });
+ map.addLayer(this._dragStageLayer);
+ //copy resources to avoid repeat resource loading.
+ const resources = new ResourceCache();
+ resources.merge(layer._getRenderer().resources);
+ this._dragStageLayer._getRenderer().resources = resources;
+ }
//@internal
_startDrag(param: any): void {
@@ -412,10 +414,10 @@ class GeometryDragHandler extends Handler {
map.getLayer(DRAG_STAGE_LAYER_ID).removeGeometry(this._shadowConnectors);
delete this._shadowConnectors;
}
- // if (this._dragStageLayer) {
- // this._dragStageLayer._getRenderer().resources = new ResourceCache();
- // this._dragStageLayer.remove();
- // }
+ if (this._dragStageLayer) {
+ this._dragStageLayer._getRenderer().resources = new ResourceCache();
+ this._dragStageLayer.remove();
+ }
}
//find correct coordinate for coordOffset if geometry has altitude
diff --git a/packages/map/src/layer/DrawToolLayer.ts b/packages/map/src/layer/DrawToolLayer.ts
index 0b1ac6794b..5737adaea4 100644
--- a/packages/map/src/layer/DrawToolLayer.ts
+++ b/packages/map/src/layer/DrawToolLayer.ts
@@ -1,5 +1,7 @@
+import { GEOJSON_TYPES } from "../core/Constants";
+import { pushIn } from "../core/util/util";
import { Geometry, LineString, Marker, MultiLineString, MultiPoint, MultiPolygon, Polygon } from "../geometry/index";
-import OverlayLayer, { OverlayLayerOptionsType } from "./OverlayLayer";
+import OverlayLayer, { isGeometry, OverlayLayerOptionsType } from "./OverlayLayer";
const options: DrawToolLayerOptionsType = {
// disable renderer of DrawToolLayer
@@ -33,13 +35,20 @@ export default class DrawToolLayer extends OverlayLayer {
* @param options=null - construct options
* @param options.style=null - drawToolLayer's style
*/
- constructor(id: string, geometries?: OverlayLayerOptionsType | Array, options?: DrawToolLayerOptionsType) {
- super(id, geometries, options);
+ constructor(id: string, geometries?: DrawToolLayerOptionsType | Array, options: DrawToolLayerOptionsType = {}) {
+ if (geometries && (!isGeometry(geometries) && !Array.isArray(geometries) && GEOJSON_TYPES.indexOf((geometries as any).type) < 0)) {
+ options = geometries;
+ geometries = null;
+ }
+ super(id, options);
const depthFunc = this.options.depthFunc || 'always';
options.sceneConfig = { depthFunc };
this._markerLayer = new DrawToolLayer.markerLayerClazz(id + '_marker', options);
this._lineLayer = new DrawToolLayer.lineLayerClazz(id + '_line', options);
this._polygonLayer = new DrawToolLayer.polygonLayerClazz(id + '_polygon', options);
+ if (geometries) {
+ this.addGeometry(geometries as Array);
+ }
}
bringToFront() {
@@ -53,7 +62,12 @@ export default class DrawToolLayer extends OverlayLayer {
if (!Array.isArray(geometries)) {
geometries = [geometries];
}
+ pushIn(this._geoList, geometries);
for (let i = 0; i < geometries.length; i++) {
+ if (this._markerLayer.isVectorLayer) {
+ this._markerLayer.addGeometry(geometries[i]);
+ continue;
+ }
if (geometries[i] instanceof Marker || geometries[i] instanceof MultiPoint) {
this._markerLayer.addGeometry(geometries[i]);
} else if (geometries[i] instanceof LineString || geometries[i] instanceof MultiLineString) {
@@ -69,6 +83,11 @@ export default class DrawToolLayer extends OverlayLayer {
geometries = [geometries];
}
for (let i = 0; i < geometries.length; i++) {
+ this._geoList.splice(geometries[i] as any, 1);
+ if (this._markerLayer.isVectorLayer) {
+ this._markerLayer.removeGeometry(geometries[i]);
+ continue;
+ }
if (geometries[i] instanceof Marker || geometries[i] instanceof MultiPoint) {
this._markerLayer.removeGeometry(geometries[i]);
} else if (geometries[i] instanceof LineString || geometries[i] instanceof MultiLineString) {
@@ -79,7 +98,22 @@ export default class DrawToolLayer extends OverlayLayer {
}
}
+ _onRemoveDrawToolGeo(params) {
+ const geometries = params.geometries;
+ for (let i = 0; i < geometries.length; i++) {
+ if (geometries[i]) {
+ this._geoList.splice(geometries[i] as any, 1);
+ }
+ }
+ }
+
onRemove(): void {
+ this._geoList = [];
+
+ this._markerLayer.off('removegeo', this._onRemoveDrawToolGeo, this);
+ this._lineLayer.off('removegeo', this._onRemoveDrawToolGeo, this);
+ this._polygonLayer.off('removegeo', this._onRemoveDrawToolGeo, this);
+
this._markerLayer.remove();
this._lineLayer.remove();
this._polygonLayer.remove();
@@ -95,8 +129,20 @@ export default class DrawToolLayer extends OverlayLayer {
this._polygonLayer.addTo(map);
this._lineLayer.addTo(map);
this._markerLayer.addTo(map);
+
+ this._markerLayer.on('removegeo', this._onRemoveDrawToolGeo, this);
+ this._lineLayer.on('removegeo', this._onRemoveDrawToolGeo, this);
+ this._polygonLayer.on('removegeo', this._onRemoveDrawToolGeo, this);
return super.onAdd();
}
+
+ getRenderer() {
+ return this._getRenderer();
+ }
+
+ _getRenderer() {
+ return this._markerLayer.getRenderer();
+ }
}
DrawToolLayer.mergeOptions(options);
@@ -104,4 +150,6 @@ DrawToolLayer.mergeOptions(options);
type DrawToolLayerOptionsType = OverlayLayerOptionsType & {
depthFunc?: string
sceneConfig?: any
+ enableAltitude?: boolean
+ enableSimplify?: boolean
}
diff --git a/packages/map/src/layer/ImageLayer.ts b/packages/map/src/layer/ImageLayer.ts
index 99c7548311..2ab53d68d3 100644
--- a/packages/map/src/layer/ImageLayer.ts
+++ b/packages/map/src/layer/ImageLayer.ts
@@ -298,6 +298,12 @@ export class ImageLayerGLRenderer extends ImageGLRenderable(ImageLayerCanvasRend
}
}
}
+
+ //override to set to always drawable
+ isDrawable() {
+ return true;
+ }
+
//@internal
_drawImage(image: LayerImageType, extent: PointExtent, opacity: number) {
this.drawGLImage(image, extent.xmin, extent.ymax, extent.getWidth(), extent.getHeight(), 1, opacity);
@@ -307,6 +313,22 @@ export class ImageLayerGLRenderer extends ImageGLRenderable(ImageLayerCanvasRend
this.createGLContext();
}
+ resizeCanvas(canvasSize) {
+ if (!this.canvas) {
+ return;
+ }
+ super.resizeCanvas(canvasSize);
+ this.resizeGLCanvas();
+ }
+
+ clearCanvas() {
+ if (!this.canvas) {
+ return;
+ }
+ super.clearCanvas();
+ this.clearGLCanvas();
+ }
+
retireImage(image: LayerImageType) {
const img = image as ImageBitmap;
if (img.close) {
diff --git a/packages/map/src/layer/Layer.ts b/packages/map/src/layer/Layer.ts
index 506454ffeb..453846e095 100644
--- a/packages/map/src/layer/Layer.ts
+++ b/packages/map/src/layer/Layer.ts
@@ -139,9 +139,11 @@ class Layer extends JSONAble(Eventable(Renderable(Class))) {
this._initRenderer();
const zIndex = this.getZIndex();
if (!isNil(zIndex)) {
- this._renderer.setZIndex(zIndex);
- if (!this.isCanvasRender()) {
- this._renderer.render();
+ if (this._renderer) {
+ this._renderer.setZIndex(zIndex);
+ if (!this.isCanvasRender()) {
+ this._renderer.render();
+ }
}
}
this.onLoadEnd();
diff --git a/packages/map/src/layer/OverlayLayer.ts b/packages/map/src/layer/OverlayLayer.ts
index 800527c1f9..6e55e6ab25 100644
--- a/packages/map/src/layer/OverlayLayer.ts
+++ b/packages/map/src/layer/OverlayLayer.ts
@@ -8,7 +8,7 @@ import GeoJSON from '../geometry/GeoJSON';
import { type OverlayLayerCanvasRenderer } from '../renderer';
import { HandlerFnResultType } from '../core/Eventable';
-function isGeometry(geo) {
+export function isGeometry(geo) {
return geo && (geo instanceof Geometry);
}
@@ -468,23 +468,6 @@ class OverlayLayer extends Layer {
if (!geometries[i] || this !== geometries[i].getLayer()) continue;
geometries[i].remove();
}
- /**
- * removegeo 事件
- *
- * @english
- * removegeo event.
- *
- * @event OverlayLayer#removegeo
- * @type {Object}
- * @property {String} type - removegeo
- * @property {OverlayLayer} target - layer
- * @property {Geometry[]} geometries - the geometries to remove
- */
- this.fire('removegeo', {
- 'type': 'removegeo',
- 'target': this,
- 'geometries': geometries
- });
return this;
}
@@ -884,6 +867,7 @@ OverlayLayer.mergeOptions(options);
export default OverlayLayer;
export type OverlayLayerOptionsType = LayerOptionsType & {
+ drawImmediate?: boolean,
geometryEvents?: boolean,
geometryEventTolerance?: number,
style?: any;
@@ -894,6 +878,7 @@ export type addGeometryFitViewOptions = {
duration?: number,
step?: (frame) => void
}
+
export type LayerIdentifyOptionsType = {
onlyVisible?: boolean;
tolerance?: number;
diff --git a/packages/map/src/layer/ParticleLayer.ts b/packages/map/src/layer/ParticleLayer.ts
new file mode 100644
index 0000000000..cd76cf22e6
--- /dev/null
+++ b/packages/map/src/layer/ParticleLayer.ts
@@ -0,0 +1,135 @@
+import { now } from '../core/util';
+import CanvasLayer, { CanvasLayerOptionsType } from './CanvasLayer';
+import CanvasLayerRenderer from '../renderer/layer/canvaslayer/CanvasLayerRenderer';
+import Point from '../geo/Point';
+
+const TEMP_POINT = new Point(0, 0);
+/**
+ * @property {Object} options - configuration options
+ * @property {Boolean} [options.animation=true] - if the layer is an animated layer
+ * @memberOf ParticleLayer
+ * @instance
+ */
+const options: ParticleLayerOptionsType = {
+ 'animation': true
+};
+
+/**
+ * 粒子图层
+ * 提供了一些渲染粒子的接口方法。
+ * 你可以直接使用它,但不能以这种方式用JSON序列化/反序列化一个 particelayer
+ * 更建议使用子类来扩展它
+ *
+ * @english
+ * @classdesc
+ * A layer to draw particles.
+ * ParticleLayer provides some interface methods to render particles.
+ * You can use it directly, but can't serialize/deserialize a ParticleLayer with JSON in this way.
+ * It is more recommended to extend it with a subclass.
+ * @example
+ * import { ParticleLayer } from 'maptalks';
+ * var layer = new ParticleLayer('particle');
+ *
+ * layer.getParticles = function (t) {
+ * return particles[t];
+ * };
+ * layer.addTo(map);
+ * @category layer
+ * @extends CanvasLayer
+ * @param {String} id - layer's id
+ * @param {Object} [options=null] - options defined in [options]{@link ParticleLayer#options}
+ */
+class ParticleLayer extends CanvasLayer {
+ options: ParticleLayerOptionsType;
+
+ /**
+ * 获取t时刻的例子位置
+ *
+ * @english
+ * Interface method to get particles's position at time t.
+ * @param t - current time in milliseconds
+ */
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ getParticles(t?: number) {
+ }
+
+ draw(context: CanvasRenderingContext2D, view: any) {
+ const points: any = this.getParticles(now());
+ if (!points || points.length === 0) {
+ const renderer = this._getRenderer();
+ if (renderer) {
+ this._getRenderer()._shouldClear = true;
+ }
+ return;
+ }
+ const map = this.getMap();
+ let extent = view.extent;
+ if (view.maskExtent) {
+ extent = view.extent.intersection(view.maskExtent);
+ }
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore 当前 map 接口中目前没有_pointToContainerPoint方法
+ extent = extent.convertTo(c => map._pointToContainerPoint(c, undefined, 0, TEMP_POINT));
+ const e = 2 * Math.PI;
+ for (let i = 0, l = points.length; i < l; i++) {
+ const pos = points[i].point;
+ if (extent.contains(pos)) {
+ const color = points[i].color || this.options['lineColor'] || '#fff',
+ r = points[i].r;
+ if (context.fillStyle !== color) {
+ context.fillStyle = color;
+ }
+ if (r <= 2) {
+ context.fillRect(pos.x - r / 2, pos.y - r / 2, r, r);
+ } else {
+ context.beginPath();
+ context.arc(pos.x, pos.y, r / 2, 0, e);
+ context.fill();
+ }
+ }
+ }
+ this._fillCanvas(context);
+ }
+
+ //@internal
+ _fillCanvas(context: CanvasRenderingContext2D) {
+ const g = context.globalCompositeOperation;
+ context.globalCompositeOperation = 'destination-out';
+ const trail = this.options['trail'] || 30;
+ context.fillStyle = 'rgba(0, 0, 0, ' + (1 / trail) + ')';
+ context.fillRect(0, 0, context.canvas.width, context.canvas.height);
+ context.globalCompositeOperation = g;
+ }
+}
+
+ParticleLayer.mergeOptions(options);
+ParticleLayer.registerRenderer('canvas', class extends CanvasLayerRenderer {
+ //@internal
+ _shouldClear: boolean;
+ layer: ParticleLayer;
+
+ draw() {
+ if (!this.canvas || !this.layer.options['animation'] || this._shouldClear) {
+ this.prepareCanvas();
+ this._shouldClear = false;
+ }
+ this.prepareDrawContext();
+ this._drawLayer();
+ }
+
+ drawOnInteracting() {
+ this.draw();
+ this._shouldClear = false;
+ }
+
+ onSkipDrawOnInteracting() {
+ this._shouldClear = true;
+ }
+});
+
+export default ParticleLayer;
+
+export type ParticleLayerOptionsType = CanvasLayerOptionsType & {
+ animation?: boolean;
+}
diff --git a/packages/map/src/layer/VectorLayer.ts b/packages/map/src/layer/VectorLayer.ts
new file mode 100644
index 0000000000..46d3326031
--- /dev/null
+++ b/packages/map/src/layer/VectorLayer.ts
@@ -0,0 +1,403 @@
+import Browser from '../core/Browser';
+import { isNil, isNumber } from '../core/util';
+import Extent from '../geo/Extent';
+import Geometry from '../geometry/Geometry';
+import OverlayLayer, { LayerIdentifyOptionsType, OverlayLayerOptionsType } from './OverlayLayer';
+import Painter from '../renderer/geometry/Painter';
+import CollectionPainter from '../renderer/geometry/CollectionPainter';
+import Coordinate from '../geo/Coordinate';
+import Point from '../geo/Point';
+import { LineString, Curve, Marker } from '../geometry';
+import PointExtent from '../geo/PointExtent';
+import { VectorLayerCanvasRenderer } from '../renderer';
+import { LayerJSONType } from './Layer';
+import DrawToolLayer from './DrawToolLayer';
+
+type VectorLayerToJSONOptions = {
+ geometries: any,
+ clipExtent: Extent
+}
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore /src/geo/PointExtent.js -> Ts 支持不传参数
+const TEMP_EXTENT = new PointExtent();
+/**
+ * 配置参数
+ *
+ * @english
+ * @property {Object} options - VectorLayer's options
+ * @property {Boolean} options.debug=false - whether the geometries on the layer is in debug mode.
+ * @property {Boolean} options.enableSimplify=true - whether to simplify geometries before rendering.
+ * @property {String} options.cursor=default - the cursor style of the layer
+ * @property {Boolean} options.geometryEvents=true - enable/disable firing geometry events, disable it to improve performance.
+ * @property {Boolean} options.defaultIconSize=[20,20] - default size of a marker's icon
+ * @property {Boolean} [options.enableAltitude=false] - whether to enable render geometry with altitude, false by default
+ * @property {Boolean} [options.altitudeProperty=altitude] - geometry's altitude property name, if enableAltitude is true, "altitude" by default
+ * @property {Boolean} [options.drawAltitude=false] - whether to draw altitude: a vertical line for marker, a vertical polygon for line
+ * @property {Boolean} [options.sortByDistanceToCamera=false] - markers Sort by camera distance
+ * @property {Boolean} [options.roundPoint=false] - round point before painting to improve performance, but will cause geometry shaking in animation
+ * @property {Number} [options.altitude=0] - layer altitude
+ * @property {Boolean} [options.debug=false] - whether the geometries on the layer is in debug mode.
+ * @property {Boolean} [options.collision=false] - whether collision
+ * @property {Number} [options.collisionBufferSize=2] - collision buffer size
+ * @property {Number} [options.collisionDelay=250] - collision delay time when map Interacting
+ * @property {String} [options.collisionScope=layer] - Collision range:layer or map
+ * @property {Boolean} [options.progressiveRender=false] - progressive Render
+ * @property {Number} [options.progressiveRenderCount=1000] - progressive Render page size
+ * @property {Boolean} [options.progressiveRenderDebug=false] - progressive Render debug
+ * @memberOf VectorLayer
+ * @instance
+ */
+const options: VectorLayerOptionsType = {
+ 'debug': false,
+ 'enableSimplify': true,
+ 'defaultIconSize': [20, 20],
+ 'cacheVectorOnCanvas': true,
+ 'cacheSvgOnCanvas': Browser.gecko,
+ 'enableAltitude': true,
+ 'altitudeProperty': 'altitude',
+ 'drawAltitude': false,
+ 'sortByDistanceToCamera': false,
+ 'roundPoint': false,
+ 'altitude': 0,
+ 'clipBBoxBufferSize': 3,
+ 'collision': false,
+ 'collisionBufferSize': 2,
+ 'collisionDelay': 250,
+ 'collisionScope': 'layer',
+ 'progressiveRender': false,
+ 'progressiveRenderCount': 1000,
+ 'progressiveRenderDebug': false
+};
+// Polyline is for custom line geometry
+// const TYPES = ['LineString', 'Polyline', 'Polygon', 'MultiLineString', 'MultiPolygon'];
+/**
+ * 用于管理、呈现 geometries 的 layer
+ *
+ * @english
+ * @classdesc
+ * A layer for managing and rendering geometries.
+ * @category layer
+ * @extends OverlayLayer
+ */
+class VectorLayer extends OverlayLayer {
+
+ options: VectorLayerOptionsType;
+ isVectorLayer: boolean;
+ /**
+ * @param id - layer's id
+ * @param geometries=null - geometries to add
+ * @param options=null - construct options
+ * @param options.style=null - vectorlayer's style
+ * @param options.*=null - options defined in [VectorLayer]{@link VectorLayer#options}
+ */
+ constructor(id: string, geometries?: VectorLayerOptionsType | Array, options?: VectorLayerOptionsType) {
+ super(id, geometries, options);
+ this.isVectorLayer = true;
+ }
+
+ onConfig(conf: Record) {
+ super.onConfig(conf);
+ if (!isNil(conf['enableAltitude'])) {
+ const geos = this.getGeometries() || [];
+ for (let i = 0, len = geos.length; i < len; i++) {
+ const geo = geos[i];
+ if (geo) {
+ geo._clearAltitudeCache();
+ geo.fire('positionchange');
+ }
+ }
+ }
+ if (conf['enableAltitude'] || conf['drawAltitude'] || conf['altitudeProperty']) {
+ const renderer = this.getRenderer();
+ if (renderer && renderer.setToRedraw) {
+ renderer.setToRedraw();
+ }
+ }
+ }
+
+ /**
+ * 通过给定 coordinate 识别 geometries
+ *
+ * @english
+ * Identify the geometries on the given coordinate
+ * @param {maptalks.Coordinate} coordinate - coordinate to identify
+ * @param {Object} [options=null] - options
+ * @param {Object} [options.tolerance=0] - identify tolerance in pixel
+ * @param {Object} [options.count=null] - result count
+ * @return {Geometry[]} geometries identified
+ */
+ identify(coordinate: Coordinate, options?: LayerIdentifyOptionsType): Geometry[] {
+ options = options || {};
+ const renderer = this.getRenderer();
+ if (!(coordinate instanceof Coordinate)) {
+ coordinate = new Coordinate(coordinate);
+ }
+ const cp = this.getMap().coordToContainerPoint(coordinate);
+ // only iterate drawn geometries when onlyVisible is true.
+ if (options['onlyVisible'] && renderer && renderer.identifyAtPoint) {
+ return renderer.identifyAtPoint(cp, options);
+ }
+ return this._hitGeos(this._geoList, cp, options);
+ }
+
+ /**
+ * 通过给定 point 识别 geometries
+ *
+ * @english
+ * Identify the geometries on the given container point
+ * @param {maptalks.Point} point - container point to identify
+ * @param {Object} [options=null] - options
+ * @param {Object} [options.tolerance=0] - identify tolerance in pixel
+ * @param {Object} [options.count=null] - result count
+ * @return {Geometry[]} geometries identified
+ */
+ identifyAtPoint(point: Point, options?: LayerIdentifyOptionsType) {
+ options = options || {};
+ const renderer = this.getRenderer();
+ if (!(point instanceof Point)) {
+ point = new Point(point);
+ }
+ // only iterate drawn geometries when onlyVisible is true.
+ if (options['onlyVisible'] && renderer && renderer.identifyAtPoint) {
+ return renderer.identifyAtPoint(point, options);
+ }
+ return this._hitGeos(this._geoList, point, options);
+ }
+
+ //@internal
+ _hitGeos(geometries: Array, cp: Point, options: LayerIdentifyOptionsType = {}) {
+ if (!geometries || !geometries.length) {
+ return [];
+ }
+ const filterGeos = [];
+ let idx = 0;
+ for (let i = 0, len = geometries.length; i < len; i++) {
+ const geo = geometries[i];
+ // Ignore collided Points
+ if (geo.isPoint && (geo as Marker)._collided === true) {
+ continue;
+ }
+ filterGeos[idx] = geo;
+ idx++;
+ }
+ geometries = filterGeos;
+
+ const filter = options['filter'],
+ hits: Geometry[] = [];
+ const tolerance = options['tolerance'];
+ const map = this.getMap();
+ const renderer = this.getRenderer();
+ const imageData = renderer && renderer.getImageData && renderer.getImageData();
+ if (imageData) {
+ let hitTolerance = 0;
+ const maxTolerance = renderer.maxTolerance;
+ //for performance
+ if (isNumber(maxTolerance)) {
+ hitTolerance = maxTolerance;
+ } else {
+ for (let i = geometries.length - 1; i >= 0; i--) {
+ const t = geometries[i]._hitTestTolerance() + (tolerance || 0);
+ if (t > hitTolerance) {
+ hitTolerance = t;
+ }
+ }
+ }
+
+ const r = map.getDevicePixelRatio();
+ (imageData as any).r = r;
+ let hit = false;
+ const cpx = cp.x - hitTolerance;
+ const cpy = cp.y - hitTolerance;
+ for (let i = -hitTolerance; i <= hitTolerance; i++) {
+ for (let j = -hitTolerance; j <= hitTolerance; j++) {
+ const x = Math.round((cpx + i) * r),
+ y = Math.round((cpy + j) * r);
+ const idx = y * imageData.width * 4 + x * 4;
+ if (imageData.data[idx + 3] > 0) {
+ hit = true;
+ break;
+ }
+ }
+ if (hit) {
+ break;
+ }
+ }
+
+ //空白的直接返回,避免下面的逻辑,假设有50%的概率不命中(要么命中,要么不命中),可以节省大量的时间
+ if (!hit) {
+ return hits;
+ }
+ }
+ const onlyVisible = options.onlyVisible;
+ for (let i = geometries.length - 1; i >= 0; i--) {
+ const geo = geometries[i];
+ if (!geo || !geo.options['interactive']) {
+ continue;
+ }
+ //当onlyVisible===false时才需要判断isVisible,因为渲染时已经判断过isVisible的值了
+ if (!onlyVisible && (!geo.isVisible())) {
+ continue;
+ }
+ const painter = geo._getPainter();
+ if (!painter) {
+ continue;
+ }
+ const bbox = painter.getRenderBBOX && painter.getRenderBBOX();
+ if (bbox) {
+ const { x, y } = cp;
+ if (x < bbox[0] || y < bbox[1] || x > bbox[2] || y > bbox[3]) {
+ continue;
+ }
+ }
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore /src/geometry/LineString.js -> ts LineString 无 _getArrowStyle 属性
+ if (!(geo instanceof LineString) || (!geo._getArrowStyle() && !(geo instanceof Curve))) {
+ // Except for LineString with arrows or curves
+ let extent = geo.getContainerExtent(TEMP_EXTENT);
+ if (tolerance) {
+ extent = extent._expand(tolerance);
+ }
+ if (!extent || !extent.contains(cp)) {
+ continue;
+ }
+ }
+ if (geo._containsPoint(cp, tolerance) && (!filter || filter(geo))) {
+ hits.push(geo);
+ if (options['count']) {
+ if (hits.length >= options['count']) {
+ break;
+ }
+ }
+ }
+ }
+ return hits;
+ }
+
+ getAltitude() {
+ return this.options['altitude'] || 0;
+ }
+
+ /**
+ * 输出 VectorLayer 的 json
+ *
+ * @english
+ * Export the VectorLayer's JSON.
+ * @param {Object} [options=null] - export options
+ * @param {Object} [options.geometries=null] - If not null and the layer is a [OverlayerLayer]{@link OverlayLayer},
+ * the layer's geometries will be exported with the given "options.geometries" as a parameter of geometry's toJSON.
+ * @param {Extent} [options.clipExtent=null] - if set, only the geometries intersectes with the extent will be exported.
+ * @return layer's JSON
+ */
+
+ toJSON(options?: VectorLayerToJSONOptions): LayerJSONType {
+ if (!options) {
+ options = {
+ 'clipExtent': null,
+ 'geometries': null
+ };
+ }
+ const profile = {
+ 'type': this.getJSONType(),
+ 'id': this.getId(),
+ 'options': this.config()
+ };
+ if (isNil(options['geometries']) || options['geometries']) {
+ let clipExtent;
+ if (options['clipExtent']) {
+ const map = this.getMap();
+ const projection = map ? map.getProjection() : null;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore 需/src/geo/Extent.js -> ts 并支持只传两个个参数
+ clipExtent = new Extent(options['clipExtent'], projection);
+ }
+ const geoJSONs = [];
+ const geometries = this.getGeometries();
+ for (let i = 0, len = geometries.length; i < len; i++) {
+ const geo = geometries[i];
+ const geoExt = geo.getExtent();
+ if (!geoExt || (clipExtent && !clipExtent.intersects(geoExt))) {
+ continue;
+ }
+ const json = geo.toJSON(options['geometries']);
+ geoJSONs.push(json);
+ }
+ profile['geometries'] = geoJSONs;
+ }
+ return profile;
+ }
+
+ getRenderer(): VectorLayerCanvasRenderer {
+ return super.getRenderer() as VectorLayerCanvasRenderer;
+ }
+
+ /**
+ * 通过 json 生成 VectorLayer
+ *
+ * @english
+ * Reproduce a VectorLayer from layer's JSON.
+ * @param {Object} layerJSON - layer's JSON
+ * @return {VectorLayer}
+ * @static
+ * @private
+ * @function
+ */
+ static fromJSON(json: Record): VectorLayer {
+ if (!json || json['type'] !== 'VectorLayer') {
+ return null;
+ }
+ const layer = new VectorLayer(json['id'], json['options']);
+ const geoJSONs = json['geometries'];
+ const geometries = [];
+ for (let i = 0; i < geoJSONs.length; i++) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore 未找到fromJSON属性
+ const geo = Geometry.fromJSON(geoJSONs[i]);
+ if (geo) {
+ geometries.push(geo);
+ }
+ }
+ layer.addGeometry(geometries);
+ return layer;
+ }
+
+ static getPainterClass() {
+ return Painter;
+ }
+
+ static getCollectionPainterClass() {
+ return CollectionPainter;
+ }
+}
+
+VectorLayer.mergeOptions(options);
+
+VectorLayer.registerJSONType('VectorLayer');
+
+export default VectorLayer;
+
+export type VectorLayerOptionsType = OverlayLayerOptionsType & {
+ debug?: boolean,
+ enableSimplify?: boolean,
+ cursor?: string,
+ geometryEvents?: boolean
+ defaultIconSize?: [number, number],
+ cacheVectorOnCanvas?: boolean,
+ cacheSvgOnCanvas?: boolean,
+ enableAltitude?: boolean,
+ altitudeProperty?: string,
+ drawAltitude?: boolean,
+ sortByDistanceToCamera?: boolean,
+ roundPoint?: boolean,
+ altitude?: number,
+ clipBBoxBufferSize?: number,
+ collision?: boolean,
+ collisionBufferSize?: number,
+ collisionDelay?: number,
+ collisionScope?: 'layer' | 'map',
+ progressiveRender?: boolean,
+ progressiveRenderCount?: number,
+ progressiveRenderDebug?: boolean
+};
+
+DrawToolLayer.setLayerClass(VectorLayer, VectorLayer, VectorLayer);
diff --git a/packages/map/src/layer/index.ts b/packages/map/src/layer/index.ts
index 0a137d8202..2b92bf23fc 100644
--- a/packages/map/src/layer/index.ts
+++ b/packages/map/src/layer/index.ts
@@ -6,7 +6,9 @@ import CanvasTileLayer from './tile/CanvasTileLayer';
import ImageLayer from './ImageLayer';
import OverlayLayer from './OverlayLayer';
import DrawToolLayer from './DrawToolLayer';
+import VectorLayer from './VectorLayer';
import CanvasLayer from './CanvasLayer';
+import ParticleLayer from './ParticleLayer';
import TileSystem from './tile/tileinfo/TileSystem';
import TileConfig from './tile/tileinfo/TileConfig';
@@ -19,7 +21,9 @@ export {
ImageLayer,
OverlayLayer,
DrawToolLayer,
+ VectorLayer,
CanvasLayer,
+ ParticleLayer,
TileSystem,
TileConfig
};
diff --git a/packages/map/src/layer/tile/TileLayer.ts b/packages/map/src/layer/tile/TileLayer.ts
index f83c1aff15..7d5161e7eb 100644
--- a/packages/map/src/layer/tile/TileLayer.ts
+++ b/packages/map/src/layer/tile/TileLayer.ts
@@ -26,7 +26,7 @@ import * as vec3 from '../../core/util/vec3';
import { formatResourceUrl } from '../../core/ResourceProxy';
import { Coordinate, Extent } from '../../geo';
import { type TileLayerCanvasRenderer } from '../../renderer';
-import { Tile } from '../../renderer/layer/tilelayer/TileLayerCanvasRenderer';
+import { Tile } from '../../renderer/layer/tilelayer/TileLayerRendererable';
import { BBOX, bboxInMask } from '../../core/util/bbox';
const DEFAULT_MAXERROR = 1;
diff --git a/packages/map/src/map/Map.ts b/packages/map/src/map/Map.ts
index e6e0b099f4..b6b5fcddc7 100644
--- a/packages/map/src/map/Map.ts
+++ b/packages/map/src/map/Map.ts
@@ -26,7 +26,7 @@ import Layer from '../layer/Layer';
import Renderable from '../renderer/Renderable';
import SpatialReference, { type SpatialReferenceType } from './spatial-reference/SpatialReference';
import { computeDomPosition, MOUSEMOVE_THROTTLE_TIME } from '../core/util/dom';
-import EPSG9807, { type EPSG9807ProjectionType } from '../geo/projection/Projection.EPSG9807';
+import EPSG9807, { type EPSG9807ProjectionType } from '../geo/projection/Projection.EPSG9807.js';
import { AnimationOptionsType, EasingType } from '../core/Animation';
import { BBOX, bboxInBBOX, getDefaultBBOX, pointsBBOX } from '../core/util/bbox';
import { Attribution } from '../control';
@@ -34,6 +34,7 @@ import { AttributionOptionsType } from '../control/Control.Attribution';
const TEMP_COORD = new Coordinate(0, 0);
const TEMP_POINT = new Point(0, 0);
+const REDRAW_OPTIONS_PROPERTIES = ['centerCross', 'fog', 'fogColor', 'debugSky'];
/**
* @property {Object} options - map's options, options must be updated by config method:
map.config('zoomAnimation', false);
* @property {Boolean} [options.centerCross=false] - Display a red cross in the center of map
@@ -85,7 +86,10 @@ const TEMP_POINT = new Point(0, 0);
* @property {Boolean|Object} [options.scaleControl=false] - display the scale control on the map if set to true or a object as the control construct option.
* @property {Boolean|Object} [options.overviewControl=false] - display the overview control on the map if set to true or a object as the control construct option.
*
- * @property {String} [options.renderer=gl] - renderer type. Don't change it if you are not sure about it. About renderer, see [TODO]{@link tutorial.renderer}.
+ * @property {Boolean} [options.fog=true] - whether to draw fog in far distance.
+ * @property {Number[]} [options.fogColor=[233, 233, 233]] - color of fog: [r, g, b]
+ *
+ * @property {String | String[]} [options.renderer=['canvas', 'gl', 'gpu']] - renderer type. Don't change it if you are not sure about it. About renderer, see [TODO]{@link tutorial.renderer}.
* @property {Number} [options.devicePixelRatio=null] - device pixel ratio to override device's default one
* @property {Number} [options.heightFactor=1] - the factor for height/altitude calculation,This affects the height calculation of all layers(vectortilelayer/gllayer/threelayer/3dtilelayer)
* @property {Boolean} [options.stopRenderOnOffscreen=true] - whether to stop map rendering when container is offscreen
@@ -141,7 +145,7 @@ const options: MapOptionsType = {
'checkSize': true,
'checkSizeInterval': 1000,
- 'renderer': 'gl',
+ 'renderer': ['canvas', 'gl', 'gpu'],
'cascadePitches': [10, 60],
'renderable': true,
@@ -206,7 +210,7 @@ const options: MapOptionsType = {
* subdomains:['a','b','c']
* }),
* layers : [
- * new maptalks.PointLayer('v', [new maptalks.Marker([180, 0])])
+ * new maptalks.VectorLayer('v', [new maptalks.Marker([180, 0])])
* ]
* });
*/
@@ -513,6 +517,19 @@ export class Map extends Handlerable(Eventable(Renderable(Class))) {
if (!isNil(ref)) {
this._updateSpatialReference(ref, null);
}
+ if (this.options.renderer === 'canvas') {
+ let needUpdate = false;
+ for (let i = 0, len = REDRAW_OPTIONS_PROPERTIES.length; i < len; i++) {
+ const key = REDRAW_OPTIONS_PROPERTIES[i];
+ if (!isNil(conf[key])) {
+ needUpdate = true;
+ break;
+ }
+ }
+ if (!needUpdate) {
+ return this;
+ }
+ }
const renderer = this.getRenderer();
if (renderer) {
renderer.setToRedraw();
@@ -1449,7 +1466,11 @@ export class Map extends Handlerable(Eventable(Renderable(Class))) {
if (removed.length > 0) {
const renderer = this.getRenderer();
if (renderer) {
- renderer.setToRedraw();
+ if (this.options.renderer === 'canvas') {
+ renderer.setLayerCanvasUpdated();
+ } else {
+ renderer.setToRedraw();
+ }
}
this.once('frameend', () => {
removed.forEach(layer => {
@@ -2081,10 +2102,19 @@ export class Map extends Handlerable(Eventable(Renderable(Class))) {
//@internal
_initRenderer() {
- const renderer = this.options['renderer'];
- const clazz = Map.getRendererClass(renderer) as any;
- this._renderer = new clazz(this);
- this._renderer.load();
+ let renderer = this.options['renderer'];
+ if (!Array.isArray(renderer)) {
+ renderer = [renderer];
+ }
+ for (let i = 0; i < renderer.length; i++) {
+ const clazz = Map.getRendererClass(renderer[i]) as any;
+ if (clazz) {
+ this._renderer = new clazz(this);
+ this._renderer.load();
+ break;
+ }
+ }
+
}
//@internal
@@ -2728,6 +2758,8 @@ Map.mergeOptions(options);
export default Map;
+export type MapRendererType = 'canvas' | 'gl' | 'gpu';
+
export type MapOptionsType = {
// center: Array | Coordinate;
// zoom: number;
@@ -2754,6 +2786,8 @@ export type MapOptionsType = {
zoomControl?: boolean;
scaleControl?: boolean;
overviewControl?: boolean;
+ fog?: boolean;
+ fogColor?: any; // fixme 确认类型
devicePixelRatio?: number;
heightFactor?: number;
originLatitudeForAltitude?: number;
@@ -2785,7 +2819,7 @@ export type MapOptionsType = {
fixCenterOnResize?: boolean;
checkSize?: boolean;
checkSizeInterval?: number;
- renderer?: 'gl';
+ renderer?: MapRendererType | MapRendererType[];
cascadePitches?: Array;
renderable?: boolean;
clickTimeThreshold?: number;
diff --git a/packages/map/src/map/tool/DistanceTool.ts b/packages/map/src/map/tool/DistanceTool.ts
index 22c7eb3842..e30621e181 100644
--- a/packages/map/src/map/tool/DistanceTool.ts
+++ b/packages/map/src/map/tool/DistanceTool.ts
@@ -318,12 +318,15 @@ class DistanceTool extends DrawTool {
const layerId = 'distancetool_' + uid;
const markerLayerId = 'distancetool_markers_' + uid;
const zIndex = this.options.zIndex;
+ const enableAltitude = this.options.enableAltitude;
if (!map.getLayer(layerId)) {
this._measureLineLayer = new DrawToolLayer(layerId, {
- zIndex
+ zIndex,
+ enableAltitude
}).addTo(map);
this._measureMarkerLayer = new DrawToolLayer(markerLayerId, {
- zIndex
+ zIndex,
+ enableAltitude
}).addTo(map);
} else {
this._measureLineLayer = map.getLayer(layerId);
diff --git a/packages/map/src/map/tool/DrawTool.ts b/packages/map/src/map/tool/DrawTool.ts
index 887aa192c7..31db9b8730 100644
--- a/packages/map/src/map/tool/DrawTool.ts
+++ b/packages/map/src/map/tool/DrawTool.ts
@@ -413,7 +413,7 @@ class DrawTool extends MapTool {
const resources = getExternalResources(symbol);
if (resources.length > 0) {
//load external resources at first
- this._drawToolLayer._getRenderer().loadResources(resources);
+ this._drawToolLayer.getRenderer().loadResources(resources);
}
}
@@ -877,6 +877,8 @@ class DrawTool extends MapTool {
let drawToolLayer: any = this._map.getLayer(drawLayerId);
if (!drawToolLayer) {
drawToolLayer = new DrawToolLayer(drawLayerId, {
+ 'enableSimplify': false,
+ 'enableAltitude': this.options['enableAltitude'],
'zIndex': this.options.zIndex
});
this._map.addLayer(drawToolLayer);
diff --git a/packages/map/src/renderer/index.ts b/packages/map/src/renderer/index.ts
index 0234a01e06..c06869e8db 100644
--- a/packages/map/src/renderer/index.ts
+++ b/packages/map/src/renderer/index.ts
@@ -2,6 +2,7 @@
export { ResourceCache } from './layer/CanvasRenderer';
export { default as CanvasRenderer } from './layer/CanvasRenderer';
+export { default as LayerGLRenderer } from './layer/LayerGLRenderer';
export { default as ImageGLRenderable } from './layer/ImageGLRenderable';
export * from './layer/tilelayer';
@@ -9,6 +10,7 @@ export * from './layer/vectorlayer';
export * from './layer/canvaslayer';
export { default as MapRenderer } from './map/MapRenderer';
export { default as MapCanvasRenderer } from './map/MapCanvasRenderer';
+export { default as MapGLAbstractRenderer } from './map/MapGLAbstractRenderer';
export { default as Renderable } from './Renderable';
diff --git a/packages/map/src/renderer/layer/CanvasRenderer.ts b/packages/map/src/renderer/layer/CanvasRenderer.ts
index 7918078f6a..e4471dd7f0 100644
--- a/packages/map/src/renderer/layer/CanvasRenderer.ts
+++ b/packages/map/src/renderer/layer/CanvasRenderer.ts
@@ -1,16 +1,17 @@
/* eslint-disable @typescript-eslint/ban-types */
-import { now, isNil, isArrayHasData, isSVG, IS_NODE, loadImage, hasOwn, getImageBitMap, isImageBitMap } from '../../core/util';
+import { now, isNil, isArrayHasData, isSVG, IS_NODE, loadImage, hasOwn, getImageBitMap, calCanvasSize, isImageBitMap } from '../../core/util';
import Class from '../../core/Class';
import Browser from '../../core/Browser';
import Canvas2D from '../../core/Canvas';
import Actor from '../../core/worker/Actor';
import Point from '../../geo/Point';
import Extent from '../../geo/Extent';
+import { SizeLike } from '../../geo/Size';
import { imageFetchWorkerKey } from '../../core/worker/CoreWorkers';
import { registerWorkerAdapter } from '../../core/worker/Worker';
import { formatResourceUrl } from '../../core/ResourceProxy';
-import { TileRenderingCanvas, ImageType } from '../types';
+import { TileRenderingCanvas, TileRenderingContext, ImageType } from '../types';
const EMPTY_ARRAY = [];
class ResourceWorkerConnection extends Actor {
@@ -39,8 +40,9 @@ class CanvasRenderer extends Class {
layer: any;
resources: ResourceCache;
- context: any;
+ context: CanvasRenderingContext2D;
canvas: TileRenderingCanvas;
+ gl: TileRenderingContext;
middleWest: Point;
canvasExtent2D: Extent;
//@internal
@@ -63,6 +65,8 @@ class CanvasRenderer extends Class {
_loadingResource: boolean;
//@internal
_renderComplete: boolean;
+ //@internal
+ _canvasUpdated: boolean;
//@internal
_renderZoom: number;
@@ -203,7 +207,7 @@ class CanvasRenderer extends Class {
const map = this.getMap();
if (map.isInteracting() || map.getRenderer().isViewChanged()) {
// don't redraw when map is moving without any pitch
- return true;
+ return !(!map.getPitch() && map.isMoving() && !map.isZooming() && !map.isRotating() && !this.layer.options['forceRenderOnMoving']);
}
return false;
}
@@ -236,6 +240,23 @@ class CanvasRenderer extends Class {
return this;
}
+ /**
+ * Mark layer's canvas updated
+ */
+ setCanvasUpdated() {
+ this._canvasUpdated = true;
+ return this;
+ }
+
+ /**
+ * Only called by map's renderer to check whether the layer's canvas is updated
+ * @protected
+ * @return {Boolean}
+ */
+ isCanvasUpdated(): boolean {
+ return !!this._canvasUpdated;
+ }
+
/**
* Remove the renderer, will be called when layer is removed
*/
@@ -277,6 +298,7 @@ class CanvasRenderer extends Class {
*/
getCanvasImage(): any {
const map = this.getMap();
+ this._canvasUpdated = false;
if (this._renderZoom !== map.getZoom() || !this.canvas || !this._extent2D) {
return null;
}
@@ -300,6 +322,7 @@ class CanvasRenderer extends Class {
* Clear canvas
*/
clear(): void {
+ this.clearCanvas();
}
/**
@@ -417,6 +440,118 @@ class CanvasRenderer extends Class {
this.middleWest = map._containerPointToPoint(new Point(0, map.height / 2));
}
+ /**
+ * Create renderer's Canvas
+ */
+ createCanvas(): void {
+ if (this.canvas) {
+ return;
+ }
+ const map = this.getMap();
+ const size = map.getSize();
+ const r = map.getDevicePixelRatio(),
+ w = Math.round(r * size.width),
+ h = Math.round(r * size.height);
+ if (this.layer._canvas) {
+ const canvas = this.layer._canvas;
+ canvas.width = w;
+ canvas.height = h;
+ if (canvas.style) {
+ canvas.style.width = size.width + 'px';
+ canvas.style.height = size.height + 'px';
+ }
+ this.canvas = this.layer._canvas;
+ } else {
+ this.canvas = Canvas2D.createCanvas(w, h, map.CanvasClass);
+ }
+
+ this.onCanvasCreate();
+
+ }
+
+ onCanvasCreate(): void {
+
+ }
+
+ //@internal
+ _canvasContextScale(context: CanvasRenderingContext2D, dpr: number) {
+ context.scale(dpr, dpr);
+ context.dpr = dpr;
+ return this;
+ }
+
+ createContext(): void {
+ //Be compatible with layer renderers that overrides create canvas and create gl/context
+ if (this.gl && this.gl.canvas === this.canvas || this.context) {
+ return;
+ }
+ this.context = Canvas2D.getCanvas2DContext(this.canvas);
+ if (!this.context) {
+ return;
+ }
+ this.context.dpr = 1;
+ if (this.layer.options['globalCompositeOperation']) {
+ this.context.globalCompositeOperation = this.layer.options['globalCompositeOperation'];
+ }
+ const dpr = this.getMap().getDevicePixelRatio();
+ if (dpr !== 1) {
+ this._canvasContextScale(this.context, dpr);
+ }
+ }
+
+ resetCanvasTransform(): void {
+ if (!this.context) {
+ return;
+ }
+ const dpr = this.getMap().getDevicePixelRatio();
+ this.context.setTransform(dpr, 0, 0, dpr, 0, 0);
+ }
+
+ /**
+ * Resize the canvas
+ * @param canvasSize the size resizing to
+ */
+ resizeCanvas(canvasSize?: SizeLike): void {
+ const canvas = this.canvas;
+ if (!canvas) {
+ return;
+ }
+ const size = canvasSize || this.getMap().getSize();
+ const r = this.getMap().getDevicePixelRatio();
+ const { width, height, cssWidth, cssHeight } = calCanvasSize(size, r);
+ // width/height不变并不意味着 css width/height 不变
+ if (this.layer._canvas && (canvas.style.width !== cssWidth || canvas.style.height !== cssHeight)) {
+ canvas.style.width = cssWidth;
+ canvas.style.height = cssHeight;
+ }
+
+ if (canvas.width === width && canvas.height === height) {
+ return;
+ }
+ //retina support
+ canvas.height = height;
+ canvas.width = width;
+ if (this.context) {
+ this.context.dpr = 1;
+ }
+ if (r !== 1 && this.context) {
+ this._canvasContextScale(this.context, r);
+ }
+ }
+
+ /**
+ * Clear the canvas to blank
+ */
+ clearCanvas(): void {
+ if (!this.context || !this.getMap()) {
+ return;
+ }
+ //fix #1597
+ const r = this.getMap().getDevicePixelRatio();
+ const rScale = 1 / r;
+ const w = this.canvas.width * rScale, h = this.canvas.height * rScale;
+ Canvas2D.clearRect(this.context, 0, 0, Math.max(w, this.canvas.width), Math.max(h, this.canvas.height));
+ }
/**
* @english
@@ -427,18 +562,35 @@ class CanvasRenderer extends Class {
*/
prepareCanvas(): any {
if (!this.canvas) {
- const map = this.getMap();
- this.canvas = map.getRenderer().canvas;
- this.context = map.getRenderer().context;
- this.initContext();
+ this.createCanvas();
+ this.createContext();
+ this.layer.onCanvasCreate();
+ /**
+ * canvascreate event, fired when canvas created.
+ *
+ * @event Layer#canvascreate
+ * @type {Object}
+ * @property {String} type - canvascreate
+ * @property {Layer} target - layer
+ * @property {CanvasRenderingContext2D} context - canvas's context
+ * @property {WebGLRenderingContext2D} gl - canvas's webgl context
+ */
+ this.layer.fire('canvascreate', {
+ 'context': this.context,
+ 'gl': this.gl
+ });
+ } else {
+ this.resetCanvasTransform();
+ this.clearCanvas();
+ this.resizeCanvas();
}
- this.prepareContext();
delete this._maskExtent;
const mask = this.layer.getMask();
// this.context may be not available
if (!mask) {
this.layer.fire('renderstart', {
- 'context': this.context
+ 'context': this.context,
+ 'gl': this.gl
});
return null;
}
@@ -446,7 +598,8 @@ class CanvasRenderer extends Class {
//fix vt _extent2D is null
if (maskExtent2D && this._extent2D && !maskExtent2D.intersects(this._extent2D)) {
this.layer.fire('renderstart', {
- 'context': this.context
+ 'context': this.context,
+ 'gl': this.gl
});
return maskExtent2D;
}
@@ -460,23 +613,12 @@ class CanvasRenderer extends Class {
* @property {CanvasRenderingContext2D} context - canvas's context
*/
this.layer.fire('renderstart', {
- 'context': this.context
+ 'context': this.context,
+ 'gl': this.gl
});
return maskExtent2D;
}
- initContext() {
-
- }
-
- prepareContext() {
-
- }
-
- clearContext() {
-
- }
-
clipCanvas(context: CanvasRenderingContext2D) {
const mask = this.layer.getMask();
if (!mask) {
@@ -493,7 +635,7 @@ class CanvasRenderer extends Class {
const dpr = map.getDevicePixelRatio();
if (dpr !== 1) {
context.save();
- // this._canvasContextScale(context, dpr);
+ this._canvasContextScale(context, dpr);
}
// Handle MultiPolygon
if (mask.getGeometries) {
@@ -554,8 +696,10 @@ class CanvasRenderer extends Class {
* @property {CanvasRenderingContext2D} context - canvas's context
*/
this.layer.fire('renderend', {
- 'context': this.context
+ 'context': this.context,
+ 'gl': this.gl
});
+ this.setCanvasUpdated();
}
}
@@ -627,6 +771,7 @@ class CanvasRenderer extends Class {
*/
onResize(param: any) {
delete this._extent2D;
+ this.resizeCanvas();
this.setToRedraw();
}
diff --git a/packages/map/src/renderer/layer/ImageGLRenderable.ts b/packages/map/src/renderer/layer/ImageGLRenderable.ts
index f8dcd4e8f0..b4b12eeb82 100644
--- a/packages/map/src/renderer/layer/ImageGLRenderable.ts
+++ b/packages/map/src/renderer/layer/ImageGLRenderable.ts
@@ -329,6 +329,35 @@ const ImageGLRenderable = function (Base: T) {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
}
+ /**
+ * Resize GL canvas with renderer's 2D canvas
+ */
+ resizeGLCanvas(): void {
+ if (this.gl) {
+ this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
+ }
+ if (!this.canvas2) {
+ return;
+ }
+ if (this.canvas2.width !== this.canvas.width || this.canvas2.height !== this.canvas.height) {
+ this.canvas2.width = this.canvas.width;
+ this.canvas2.height = this.canvas.height;
+ }
+ }
+
+ /**
+ * Clear gl canvas
+ */
+ clearGLCanvas(): void {
+ if (this.gl) {
+ this.gl.clearStencil(0xFF);
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.STENCIL_BUFFER_BIT);
+ }
+ if (!this.gl.wrap) {
+ this.gl.clear(this.gl.DEPTH_BUFFER_BIT);
+ }
+ }
+
disposeImage(image: TileImageType): void {
if (!image) {
return;
diff --git a/packages/map/src/renderer/layer/LayerGLRenderer.ts b/packages/map/src/renderer/layer/LayerGLRenderer.ts
new file mode 100644
index 0000000000..d967cdff58
--- /dev/null
+++ b/packages/map/src/renderer/layer/LayerGLRenderer.ts
@@ -0,0 +1,961 @@
+/* eslint-disable @typescript-eslint/ban-types */
+
+import { now, isNil, isArrayHasData, isSVG, IS_NODE, loadImage, hasOwn, getImageBitMap, isImageBitMap } from '../../core/util';
+import Class from '../../core/Class';
+import Browser from '../../core/Browser';
+import Canvas2D from '../../core/Canvas';
+import Actor from '../../core/worker/Actor';
+import Point from '../../geo/Point';
+import Extent from '../../geo/Extent';
+import { imageFetchWorkerKey } from '../../core/worker/CoreWorkers';
+import { registerWorkerAdapter } from '../../core/worker/Worker';
+import { formatResourceUrl } from '../../core/ResourceProxy';
+import { TileRenderingCanvas, ImageType } from '../types';
+
+const EMPTY_ARRAY = [];
+class ResourceWorkerConnection extends Actor {
+ constructor() {
+ super(imageFetchWorkerKey);
+ }
+
+ fetchImage(url: string, cb: Function) {
+ const data = {
+ url
+ };
+ this.send(data, EMPTY_ARRAY, cb);
+ }
+}
+
+/**
+ * 在 HTMLCanvasElement 上渲染图层的基类
+ * @english
+ * Base Class to render layer on HTMLCanvasElement
+ * @abstract
+ * @protected
+ * @memberOf renderer
+ * @extends Class
+ */
+class LayerGLRenderer extends Class {
+ layer: any;
+ resources: ResourceCache;
+
+ context: any;
+ canvas: TileRenderingCanvas;
+ middleWest: Point;
+ canvasExtent2D: Extent;
+ //@internal
+ _extent2D: Extent;
+ //@internal
+ _maskExtent: Extent;
+
+ //@internal
+ _painted: boolean;
+ //@internal
+ _drawTime: number;
+ //@internal
+ _frameTime: number;
+ //@internal
+ _resWorkerConn: ResourceWorkerConnection;
+
+ //@internal
+ _toRedraw: boolean;
+ //@internal
+ _loadingResource: boolean;
+ //@internal
+ _renderComplete: boolean;
+
+ //@internal
+ _renderZoom: number;
+ //@internal
+ _errorThrown: boolean;
+ //@internal
+ __zoomTransformMatrix: number[];
+
+ drawOnInteracting?(...args: any[]): void;
+ checkResources?(): any[];
+ getImageData?(): ImageData;
+ draw?(...args: any[]): void;
+
+ /**
+ * @param {Layer} layer the layer to render
+ */
+ constructor(layer: any) {
+ super();
+ this.layer = layer;
+ this._painted = false;
+ this._drawTime = 0;
+ if (Browser.decodeImageInWorker && !Browser.safari && !Browser.iosWeixin) {
+ this._resWorkerConn = new ResourceWorkerConnection();
+ }
+ this.setToRedraw();
+ }
+
+
+ /**
+ * Render the layer.
+ * Call checkResources
+ */
+ render(framestamp?: number): void {
+ this.prepareRender();
+ if (!this.getMap() || !this.layer.isVisible()) {
+ return;
+ }
+ if (!this.resources) {
+ /* eslint-disable no-use-before-define */
+ this.resources = new ResourceCache();
+ /* eslint-enable no-use-before-define */
+ }
+ this.checkAndDraw(this._tryToDraw, framestamp);
+ this._frameTime = framestamp;
+ }
+
+ getFrameTimestamp(): number {
+ return this._frameTime || 0;
+ }
+
+ checkAndDraw(drawFn, ...args) {
+ this._toRedraw = false;
+ if (this.checkResources) {
+ const resources = this.checkResources();
+ if (resources.length > 0) {
+ this._loadingResource = true;
+ this.loadResources(resources).then(() => {
+ this._loadingResource = false;
+ if (this.layer) {
+ /**
+ * resourceload event, fired when external resources of the layer complete loading.
+ *
+ * @event Layer#resourceload
+ * @type {Object}
+ * @property {String} type - resourceload
+ * @property {Layer} target - layer
+ */
+ this.layer.fire('resourceload');
+ const map = this.layer.getMap();
+ this.setToRedraw();
+ map.getRenderer().callInNextFrame(() => {
+ // sometimes renderer still fails to fetch loaded images, an additional frame will solved it
+ this.setToRedraw();
+ });
+ }
+ });
+ } else {
+ drawFn.call(this, ...args);
+ }
+ } else {
+ drawFn.call(this, ...args);
+ }
+ }
+
+ /**
+ * Check if has any external resources to load
+ * If yes, load the resources before calling draw method
+ * @abstract
+ * @method checkResources
+ * @instance
+ * @returns {Array[]} an array of resource arrays [ [url1, width, height], [url2, width, height], [url3, width, height] .. ]
+ * @memberOf renderer.LayerGLRenderer
+ */
+
+ /**
+ * a required abstract method to implement
+ * draw the layer when map is not interacting
+ * @abstract
+ * @instance
+ * @method draw
+ * @memberOf renderer.LayerGLRenderer
+ */
+
+ /**
+ * an optional abstract method to implement
+ * draw the layer when map is interacting (moving/zooming/dragrotating)
+ * @abstract
+ * @instance
+ * @method drawOnInteracting
+ * @param {Object} eventParam event parameters
+ * @memberOf renderer.LayerGLRenderer
+ */
+
+ /**
+ * @private
+ */
+ testIfNeedRedraw(): boolean {
+ const map = this.getMap();
+ if (this._loadingResource) {
+ return false;
+ }
+ if (this._toRedraw) {
+ return true;
+ }
+ if (map.isInteracting() && !this.drawOnInteracting) {
+ return false;
+ }
+ if (this.needToRedraw()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Ask whether the layer renderer needs to redraw
+ */
+ needToRedraw(): boolean {
+ const map = this.getMap();
+ if (map.isInteracting() || map.getRenderer().isViewChanged()) {
+ // don't redraw when map is moving without any pitch
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * A callback for overriding when drawOnInteracting is skipped due to low fps
+ */
+ onSkipDrawOnInteracting(): void { }
+
+ isLoadingResource(): boolean {
+ return this._loadingResource;
+ }
+
+ isRenderComplete(): boolean {
+ return !!this._renderComplete;
+ }
+
+ /**
+ * Whether must call render instead of drawOnInteracting when map is interacting
+ */
+ mustRenderOnInteracting(): boolean {
+ return !this._painted;
+ }
+
+ /**
+ * Set to redraw, ask map to call draw/drawOnInteracting to redraw the layer
+ */
+ setToRedraw() {
+ this._toRedraw = true;
+ return this;
+ }
+
+ /**
+ * Remove the renderer, will be called when layer is removed
+ */
+ remove(): void {
+ this.onRemove();
+ delete this._loadingResource;
+ delete this.middleWest;
+ delete this.canvas;
+ delete this.context;
+ delete this.canvasExtent2D;
+ delete this._extent2D;
+ if (this.resources) {
+ this.resources.remove();
+ }
+ delete this.resources;
+ if (this._resWorkerConn) {
+ this._resWorkerConn.remove();
+ delete this._resWorkerConn;
+ }
+ delete this.layer;
+ }
+
+ onRemove(): void { }
+
+ onAdd(): void { }
+
+ /**
+ * Get map
+ */
+ getMap(): any {
+ if (!this.layer) {
+ return null;
+ }
+ return this.layer.getMap();
+ }
+
+ /**
+ * Get renderer's Canvas image object
+ */
+ getCanvasImage(): any {
+ const map = this.getMap();
+ if (this._renderZoom !== map.getZoom() || !this.canvas || !this._extent2D) {
+ return null;
+ }
+ if (this.isBlank()) {
+ return null;
+ }
+ if (this.layer.isEmpty && this.layer.isEmpty()) {
+ return null;
+ }
+ // size = this._extent2D.getSize(),
+ const containerPoint = map._pointToContainerPoint(this.middleWest)._add(0, -map.height / 2);
+ return {
+ 'image': this.canvas,
+ 'layer': this.layer,
+ 'point': containerPoint/* ,
+ 'size': size */
+ };
+ }
+
+ /**
+ * Clear canvas
+ */
+ clear(): void {
+ }
+
+ /**
+ * A method to help improve performance.
+ * If you are sure that layer's canvas is blank, returns true to save unnecessary layer works of maps.
+ */
+ isBlank(): boolean {
+ return !this._painted;
+ }
+
+ /**
+ * Show the layer
+ */
+ show(): void {
+ this.setToRedraw();
+ }
+
+ /**
+ * Hide the layer
+ */
+ hide(): void {
+ this.clear();
+ this.setToRedraw();
+ }
+
+ /**
+ * Set z-index of layer
+ */
+ setZIndex(_z?: number): void {
+ this.setToRedraw();
+ }
+
+ /**
+ * Detect if there is anything painted on the given point
+ * @param point containerPoint
+ */
+ hitDetect(point: Point): boolean {
+ if (!this.context || (this.layer.isEmpty && this.layer.isEmpty()) || this.isBlank() || this._errorThrown || (this.layer.isVisible && !this.layer.isVisible())) {
+ return false;
+ }
+ const map = this.getMap();
+ const r = map.getDevicePixelRatio();
+ const size = map.getSize();
+ if (point.x < 0 || point.x > size['width'] * r || point.y < 0 || point.y > size['height'] * r) {
+ return false;
+ }
+ const imageData = this.getImageData && this.getImageData();
+ if (imageData) {
+ const x = Math.round(r * point.x), y = Math.round(r * point.y);
+ const idx = y * imageData.width * 4 + x * 4;
+ //索引下标从0开始需要-1
+ return imageData.data[idx + 3] > 0;
+ }
+ try {
+ const imgData = this.context.getImageData(r * point.x, r * point.y, 1, 1).data;
+ if (imgData[3] > 0) {
+ return true;
+ }
+ } catch (error) {
+ if (!this._errorThrown) {
+ if (console) {
+ console.warn('hit detect failed with tainted canvas, some geometries have external resources in another domain:\n', error);
+ }
+ this._errorThrown = true;
+ }
+ //usually a CORS error will be thrown if the canvas uses resources from other domain.
+ //this may happen when a geometry is filled with pattern file.
+ return false;
+ }
+ return false;
+
+ }
+
+ /**
+ * loadResource from resourceUrls
+ * @param {String[]} resourceUrls - Array of urls to load
+ * @returns {Promise[]}
+ */
+ loadResources(resourceUrls: string[][]): Promise {
+ if (!this.resources) {
+ /* eslint-disable no-use-before-define */
+ this.resources = new ResourceCache();
+ /* eslint-enable no-use-before-define */
+ }
+ const resources = this.resources,
+ promises = [];
+ if (isArrayHasData(resourceUrls)) {
+ const cache = {};
+ for (let i = resourceUrls.length - 1; i >= 0; i--) {
+ const url = resourceUrls[i];
+ if (!url || !url.length || cache[url.join('-')]) {
+ continue;
+ }
+ cache[url.join('-')] = 1;
+ if (!resources.isResourceLoaded(url, true)) {
+ //closure it to preserve url's value
+ promises.push(new Promise(this._promiseResource(url)));
+ }
+ }
+ }
+ return Promise.all(promises);
+ }
+
+ /**
+ * Prepare rendering
+ * Set necessary properties, like this._renderZoom/ this.canvasExtent2D, this.middleWest
+ * @private
+ */
+ prepareRender(): void {
+ delete this._renderComplete;
+ const map = this.getMap();
+ this._renderZoom = map.getZoom();
+ this.canvasExtent2D = this._extent2D = map.get2DExtent();
+ //change from northWest to middleWest, because northwest's point <=> containerPoint changes when pitch >= 72
+ this.middleWest = map._containerPointToPoint(new Point(0, map.height / 2));
+ }
+
+
+ /**
+ * @english
+ * Prepare the canvas for rendering.
+ * 1. Clear the canvas to blank.
+ * 2. Clip the canvas by mask if there is any and return the mask's extent
+ * @return {PointExtent} mask's extent of current zoom's 2d point.
+ */
+ prepareCanvas(): any {
+ if (!this.canvas) {
+ const map = this.getMap();
+ this.canvas = map.getRenderer().canvas;
+ this.context = map.getRenderer().context;
+ this.initContext();
+ }
+ this.prepareContext();
+ delete this._maskExtent;
+ const mask = this.layer.getMask();
+ // this.context may be not available
+ if (!mask) {
+ this.layer.fire('renderstart', {
+ 'context': this.context
+ });
+ return null;
+ }
+ const maskExtent2D = this._maskExtent = mask._getMaskPainter().get2DExtent();
+ //fix vt _extent2D is null
+ if (maskExtent2D && this._extent2D && !maskExtent2D.intersects(this._extent2D)) {
+ this.layer.fire('renderstart', {
+ 'context': this.context
+ });
+ return maskExtent2D;
+ }
+ /**
+ * renderstart event, fired when layer starts to render.
+ *
+ * @event Layer#renderstart
+ * @type {Object}
+ * @property {String} type - renderstart
+ * @property {Layer} target - layer
+ * @property {CanvasRenderingContext2D} context - canvas's context
+ */
+ this.layer.fire('renderstart', {
+ 'context': this.context
+ });
+ return maskExtent2D;
+ }
+
+ initContext() {
+
+ }
+
+ prepareContext() {
+
+ }
+
+ clearContext() {
+
+ }
+
+ clipCanvas(context: CanvasRenderingContext2D) {
+ const mask = this.layer.getMask();
+ if (!mask) {
+ return false;
+ }
+ if (!this.layer.options.maskClip) {
+ return false;
+ }
+ const old = this.middleWest;
+ const map = this.getMap();
+ //when clipping, layer's middleWest needs to be reset for mask's containerPoint conversion
+ this.middleWest = map._containerPointToPoint(new Point(0, map.height / 2));
+ context.save();
+ const dpr = map.getDevicePixelRatio();
+ if (dpr !== 1) {
+ context.save();
+ // this._canvasContextScale(context, dpr);
+ }
+ // Handle MultiPolygon
+ if (mask.getGeometries) {
+ context.isMultiClip = true;
+ const masks = mask.getGeometries() || [];
+ context.beginPath();
+ masks.forEach(_mask => {
+ const painter = _mask._getMaskPainter();
+ painter.paint(null, context);
+ });
+ context.stroke();
+ context.isMultiClip = false;
+ } else {
+ context.isClip = true;
+ context.beginPath();
+ const painter = mask._getMaskPainter();
+ painter.paint(null, context);
+ context.isClip = false;
+ }
+ if (dpr !== 1) {
+ context.restore();
+ }
+ try {
+ context.clip('evenodd');
+ } catch (error) {
+ console.error(error);
+ }
+ this.middleWest = old;
+ return true;
+ }
+
+ /**
+ * Get renderer's current view extent in 2d point
+ * @return {Object} view.extent, view.maskExtent, view.zoom, view.middleWest
+ */
+ getViewExtent() {
+ return {
+ 'extent': this._extent2D,
+ 'maskExtent': this._maskExtent,
+ 'zoom': this._renderZoom,
+ 'middleWest': this.middleWest
+ };
+ }
+
+ /**
+ * call when rendering completes, this will fire necessary events and call setCanvasUpdated
+ */
+ completeRender(): void {
+ if (this.getMap()) {
+ this._renderComplete = true;
+ /**
+ * renderend event, fired when layer ends rendering.
+ *
+ * @event Layer#renderend
+ * @type {Object}
+ * @property {String} type - renderend
+ * @property {Layer} target - layer
+ * @property {CanvasRenderingContext2D} context - canvas's context
+ */
+ this.layer.fire('renderend', {
+ 'context': this.context
+ });
+ }
+ }
+
+ /**
+ * Get renderer's event map registered on the map
+ * @return {Object} events
+ */
+ getEvents() {
+ return {
+ '_zoomstart': this.onZoomStart,
+ '_zooming': this.onZooming,
+ '_zoomend': this.onZoomEnd,
+ '_resize': this.onResize,
+ '_movestart': this.onMoveStart,
+ '_moving': this.onMoving,
+ '_moveend': this.onMoveEnd,
+ '_dragrotatestart': this.onDragRotateStart,
+ '_dragrotating': this.onDragRotating,
+ '_dragrotateend': this.onDragRotateEnd,
+ '_spatialreferencechange': this.onSpatialReferenceChange
+ };
+ }
+
+ /* eslint-disable @typescript-eslint/no-unused-vars */
+ /**
+ * onZoomStart
+ * @param {Object} param event parameters
+ */
+ onZoomStart(param: any): void {
+ }
+
+ /**
+ * onZoomEnd
+ * @param {Object} param event parameters
+ */
+ onZoomEnd(param: any): void {
+ this.setToRedraw();
+ }
+
+ /**
+ * onZooming
+ * @param {Object} param event parameters
+ */
+ onZooming(param: any) { }
+
+ /**
+ * onMoveStart
+ * @param {Object} param event parameters
+ */
+ onMoveStart(param: any) { }
+
+ /**
+ * onMoving
+ * @param {Object} param event parameters
+ */
+ onMoving(param: any) { }
+
+ /**
+ * onMoveEnd
+ * @param {Object} param event parameters
+ */
+ onMoveEnd(param: any) {
+ this.setToRedraw();
+ }
+
+ /**
+ * onResize
+ * @param {Object} param event parameters
+ */
+ onResize(param: any) {
+ delete this._extent2D;
+ this.setToRedraw();
+ }
+
+ /**
+ * onDragRotateStart
+ * @param {Object} param event parameters
+ */
+ onDragRotateStart(param: any) { }
+
+ /**
+ * onDragRotating
+ * @param {Object} param event parameters
+ */
+ onDragRotating(param: any) { }
+
+ /**
+ * onDragRotateEnd
+ * @param {Object} param event parameters
+ */
+ onDragRotateEnd(param: any) {
+ this.setToRedraw();
+ }
+
+ /**
+ * onSpatialReferenceChange
+ * @param {Object} param event parameters
+ */
+ onSpatialReferenceChange(param: any) {
+ }
+
+ /* eslint-disable @typescript-eslint/no-unused-vars */
+
+ /**
+ * Get ellapsed time of previous drawing
+ * @return {Number}
+ */
+ getDrawTime() {
+ return this._drawTime;
+ }
+
+ //@internal
+ _tryToDraw(framestamp) {
+ this._toRedraw = false;
+ if (!this.canvas && this.layer.isEmpty && this.layer.isEmpty()) {
+ this._renderComplete = true;
+ // not to create canvas when layer is empty
+ return;
+ }
+ this._drawAndRecord(framestamp);
+ }
+
+ //@internal
+ _drawAndRecord(framestamp: number) {
+ if (!this.getMap()) {
+ return;
+ }
+ const painted = this._painted;
+ this._painted = true;
+ let t = now();
+ this.draw(framestamp);
+ t = now() - t;
+ //reduce some time in the first draw
+ this._drawTime = painted ? t : t / 2;
+ if (painted && this.layer && this.layer.options['logDrawTime']) {
+ console.log(this.layer.getId(), 'frameTimeStamp:', framestamp, 'drawTime:', this._drawTime);
+ }
+ }
+
+ //@internal
+ _promiseResource(url) {
+ const layer = this.layer;
+ const resources = this.resources;
+ const crossOrigin = layer.options['crossOrigin'];
+ const renderer = layer.options['renderer'] || '';
+ return (resolve) => {
+ if (resources.isResourceLoaded(url, true)) {
+ resolve(url);
+ return;
+ }
+ const imageURL = formatResourceUrl(url[0]);
+
+ if (isImageBitMap(imageURL)) {
+ createImageBitmap(imageURL).then(newbitmap => {
+ //新的数据为layer提供服务
+ this._cacheResource(url, newbitmap);
+ resolve(url);
+ }).catch(err => {
+ console.error(err);
+ resolve(url);
+ });
+ return;
+ }
+ const fetchInWorker = !isSVG(url[0]) && this._resWorkerConn && (layer.options['renderer'] !== 'canvas' || layer.options['decodeImageInWorker']);
+ if (fetchInWorker) {
+ // const uri = getAbsoluteURL(url[0]);
+ this._resWorkerConn.fetchImage(imageURL, (err, data) => {
+ if (err) {
+ if (err && typeof console !== 'undefined') {
+ console.warn(err);
+ }
+ resolve(url);
+ return;
+ }
+ getImageBitMap(data, bitmap => {
+ this._cacheResource(url, bitmap);
+ resolve(url);
+ });
+ });
+ } else {
+ const img = new Image();
+ if (!isNil(crossOrigin)) {
+ img['crossOrigin'] = crossOrigin;
+ } else if (renderer !== 'canvas') {
+ img['crossOrigin'] = '';
+ }
+ if (isSVG(url[0]) && !IS_NODE) {
+ //amplify the svg image to reduce loading.
+ if (url[1]) { url[1] *= 2; }
+ if (url[2]) { url[2] *= 2; }
+ }
+ img.onload = () => {
+ this._cacheResource(url, img);
+ resolve(url);
+ };
+ img.onabort = function (err) {
+ if (console) { console.warn('image loading aborted: ' + url[0]); }
+ if (err) {
+ if (console) { console.warn(err); }
+ }
+ resolve(url);
+ };
+ img.onerror = function (err) {
+ // if (console) { console.warn('image loading failed: ' + url[0]); }
+ if (err && typeof console !== 'undefined') {
+ console.warn(err);
+ }
+ resources.markErrorResource(url);
+ resolve(url);
+ };
+ loadImage(img, [imageURL]);
+ }
+ };
+
+ }
+
+ //@internal
+ _cacheResource(url: [string, number | string, string | number], img: ImageType) {
+ if (!this.layer || !this.resources) {
+ return;
+ }
+ let w = url[1], h = url[2];
+ if (this.layer.options['cacheSvgOnCanvas'] && isSVG(url[0]) === 1 && (Browser.edge || Browser.ie)) {
+ //opacity of svg img painted on canvas is always 1, so we paint svg on a canvas at first.
+ if (isNil(w)) {
+ w = img.width || this.layer.options['defaultIconSize'][0];
+ }
+ if (isNil(h)) {
+ h = img.height || this.layer.options['defaultIconSize'][1];
+ }
+ const canvas = Canvas2D.createCanvas(w as number, h as number);
+ Canvas2D.image(canvas.getContext('2d'), img, 0, 0, w as number, h as number);
+ img = canvas;
+ }
+ this.resources.addResource(url, img);
+ }
+}
+
+export default LayerGLRenderer;
+
+export type ResourceUrl = string | string[]
+
+export class ResourceCache {
+ resources: any;
+
+ //@internal
+ _errors: any;
+
+ constructor() {
+ this.resources = {};
+ this._errors = {};
+ }
+
+ addResource(url: [string, number | string, number | string], img) {
+ this.resources[url[0]] = {
+ image: img,
+ width: +url[1],
+ height: +url[2],
+ refCnt: 0
+ };
+ if (img && img.width && img.height && !img.close && Browser.imageBitMap && !Browser.safari && !Browser.iosWeixin) {
+ if (img.src && isSVG(img.src)) {
+ return;
+ }
+ createImageBitmap(img).then(imageBitmap => {
+ if (!this.resources[url[0]]) {
+ //removed
+ return;
+ }
+ this.resources[url[0]].image = imageBitmap;
+ });
+ }
+ }
+
+ isResourceLoaded(url: ResourceUrl, checkSVG?: boolean) {
+ if (!url) {
+ return false;
+ }
+ const imgUrl = this._getImgUrl(url);
+ if (this._errors[imgUrl]) {
+ return true;
+ }
+ const img = this.resources[imgUrl];
+ if (!img) {
+ return false;
+ }
+ if (checkSVG && isSVG(url[0]) && (+url[1] > img.width || +url[2] > img.height)) {
+ return false;
+ }
+ return true;
+ }
+
+ login(url: string) {
+ const res = this.resources[url];
+ if (res) {
+ res.refCnt++;
+ }
+ }
+
+ logout(url: string) {
+ const res = this.resources[url];
+ if (res && res.refCnt-- <= 0) {
+ if (res.image && res.image.close) {
+ res.image.close();
+ }
+ delete this.resources[url];
+ }
+ }
+
+ getImage(url: ResourceUrl) {
+ const imgUrl = this._getImgUrl(url);
+ if (!this.isResourceLoaded(url) || this._errors[imgUrl]) {
+ return null;
+ }
+ return this.resources[imgUrl].image;
+ }
+
+ markErrorResource(url: ResourceUrl) {
+ this._errors[this._getImgUrl(url)] = 1;
+ }
+
+ merge(res: any) {
+ if (!res) {
+ return this;
+ }
+ for (const p in res.resources) {
+ const img = res.resources[p];
+ this.addResource([p, img.width, img.height], img.image);
+ }
+ return this;
+ }
+
+ forEach(fn: Function) {
+ if (!this.resources) {
+ return this;
+ }
+ for (const p in this.resources) {
+ if (hasOwn(this.resources, p)) {
+ fn(p, this.resources[p]);
+ }
+ }
+ return this;
+ }
+
+ //@internal
+ _getImgUrl(url: ResourceUrl) {
+ if (!Array.isArray(url)) {
+ return url;
+ }
+ return url[0];
+ }
+
+ remove() {
+ for (const p in this.resources) {
+ const res = this.resources[p];
+ if (res && res.image && res.image.close) {
+ // close bitmap
+ res.image.close();
+ }
+ }
+ this.resources = {};
+ }
+}
+
+const workerSource = `
+function (exports) {
+ exports.onmessage = function (msg, postResponse) {
+ var url = msg.data.url;
+ var fetchOptions = msg.data.fetchOptions;
+ requestImageOffscreen(url, function (err, data) {
+ var buffers = [];
+ if (data && data.data) {
+ buffers.push(data.data);
+ }
+ postResponse(err, data, buffers);
+ }, fetchOptions);
+ };
+
+ function requestImageOffscreen(url, cb, fetchOptions) {
+ fetch(url, fetchOptions ? fetchOptions : {})
+ .then(response => response.arrayBuffer())
+ .then(arrayBuffer => {
+ const blob=new Blob([arrayBuffer]);
+ return createImageBitmap(blob);
+ })
+ .then(bitmap => {
+ cb(null, {data:bitmap});
+ }).catch(err => {
+ console.warn('error when loading tile:', url);
+ console.warn(err);
+ cb(err);
+ });
+ }
+}`;
+
+function registerWorkerSource() {
+ if (!Browser.decodeImageInWorker) {
+ return;
+ }
+ registerWorkerAdapter(imageFetchWorkerKey, function () { return workerSource; });
+}
+
+registerWorkerSource();
diff --git a/packages/map/src/renderer/layer/canvaslayer/CanvasLayerRenderer.ts b/packages/map/src/renderer/layer/canvaslayer/CanvasLayerRenderer.ts
index e28a92d51e..c0cfd4563e 100644
--- a/packages/map/src/renderer/layer/canvaslayer/CanvasLayerRenderer.ts
+++ b/packages/map/src/renderer/layer/canvaslayer/CanvasLayerRenderer.ts
@@ -45,17 +45,6 @@ export default class CanvasLayerRenderer extends CanvasRenderer {
this._drawLayerOnInteracting(...args);
}
- clearCanvas() {
- if (!this.context || !this.getMap()) {
- return;
- }
- //fix #1597
- const r = this.getMap().getDevicePixelRatio();
- const rScale = 1 / r;
- const w = this.canvas.width * rScale, h = this.canvas.height * rScale;
- Canvas.clearRect(this.context, 0, 0, Math.max(w, this.canvas.width), Math.max(h, this.canvas.height));
- }
-
getCanvasImage() {
const canvasImg = super.getCanvasImage();
if (canvasImg && canvasImg.image && this.layer.options['doubleBuffer']) {
diff --git a/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts b/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts
index 692f002d19..8da3ed0ac8 100644
--- a/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts
+++ b/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts
@@ -1,109 +1,15 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-import {
- isNil,
- loadImage,
- emptyImageUrl,
- now,
- isFunction,
- getImageBitMap,
- isString,
- getAbsoluteURL,
- pushIn
-} from '../../../core/util';
-import Browser from '../../../core/Browser';
+import Canvas2D from '../../../core/Canvas';
import { default as TileLayer } from '../../../layer/tile/TileLayer';
import WMSTileLayer from '../../../layer/tile/WMSTileLayer';
import CanvasRenderer from '../CanvasRenderer';
import Point from '../../../geo/Point';
-import Extent from '../../../geo/Extent';
-import LRUCache from '../../../core/util/LRUCache';
-import Canvas from '../../../core/Canvas';
-import Actor from '../../../core/worker/Actor';
-import { imageFetchWorkerKey } from '../../../core/worker/CoreWorkers';
-import { TileImageBuffer, TileImageTexture } from '../../types';
-import type { WithUndef } from '../../../types/typings';
+import TileLayerRenderable, { RenderContext, Tile } from './TileLayerRendererable';
+const TILE_POINT = new Point(0, 0);
+const TEMP_POINT = new Point(0, 0);
-const TEMP_POINT1 = new Point(0, 0);
-const TEMP_POINT2 = new Point(0, 0);
-
-const EMPTY_ARRAY = [];
-class TileWorkerConnection extends Actor {
- constructor() {
- super(imageFetchWorkerKey);
- }
-
- checkUrl(url: string) {
- if (!url || !isString(url)) {
- return url;
- }
- //The URL is processed. Here, only the relative protocol is processed
- return getAbsoluteURL(url);
-
- }
-
- // eslint-disable-next-line @typescript-eslint/ban-types
- fetchImage(url: string, workerId: number, cb: Function, fetchOptions: any) {
- url = this.checkUrl(url);
- const data = {
- url,
- fetchOptions
- };
- this.send(data, EMPTY_ARRAY, cb, workerId);
- }
-}
-
-/**
- * 瓦片图层的渲染器抽象类,实现瓦片的遍历功能,可以继承并实现 drawTile 等方法来实现瓦片图层渲染
- *
- * @english
- * Abstract renderer class for TileLayers
- * @class
- * @protected
- * @group renderer
- * @extends {renderer.CanvasRenderer}
- */
-class TileLayerCanvasRenderer extends CanvasRenderer {
- tilesInView: TilesInViewType;
- tilesLoading: { [key: string]: any };
- //@internal
- _parentTiles: any[];
- //@internal
- _childTiles: any[];
- //@internal
- _tileZoom: number;
- //@internal
- _tileQueue: {
- tileInfo: any;
- tileData: any;
- }[];
- //@internal
- _tileQueueIds: Set;
- tileCache: typeof LRUCache;
- //@internal
- _compareTiles: any;
- //@internal
- _tileImageWorkerConn: TileWorkerConnection;
- //@internal
- _renderTimestamp: number;
- //@internal
- _frameTiles: {
- empty: boolean;
- timestamp: number;
- };
-
- //@internal
- _terrainHelper: TerrainHelper;
- //@internal
- _tilePlaceHolder: any;
- //@internal
- _frameTileGrids: TileGrids;
-
- drawingCurrentTiles: WithUndef;
- drawingChildTiles: WithUndef;
- drawingParentTiles: WithUndef;
- avgMinAltitude: number;
- avgMaxAltitude: number;
+export default class TileLayerCanvasRenderer extends TileLayerRenderable(CanvasRenderer) {
/**
*
@@ -111,339 +17,9 @@ class TileLayerCanvasRenderer extends CanvasRenderer {
*/
constructor(layer: TileLayer) {
super(layer);
- this.tilesInView = {};
- this.tilesLoading = {};
- this._parentTiles = [];
- this._childTiles = [];
- this._tileQueue = [];
- this._tileQueueIds = new Set();
- const tileSize = layer.getTileSize().width;
- this.tileCache = new LRUCache(layer.options['maxCacheSize'] * tileSize / 512 * tileSize / 512, (tile: Tile) => {
- this.deleteTile(tile);
- });
- if (Browser.decodeImageInWorker && this.layer.options['decodeImageInWorker'] && (layer.options['renderer'] === 'gl' || !Browser.safari && !Browser.iosWeixin)) {
- this._tileImageWorkerConn = new TileWorkerConnection();
- }
- this._compareTiles = compareTiles.bind(this);
- }
-
- getCurrentTileZoom(): number {
- return this._tileZoom;
+ this.init(layer);
}
- draw(timestamp: number, context): number {
- const map = this.getMap();
- const mask2DExtent = this.prepareCanvas();
- if (mask2DExtent) {
- if (!mask2DExtent.intersects(this.canvasExtent2D)) {
- this.completeRender();
- return;
- }
- }
-
- if (this._renderTimestamp !== timestamp) {
- // maptalks/issues#10
- // 如果consumeTileQueue方法在每个renderMode都会调用,但多边形只在fxaa mode下才会绘制。
- // 导致可能出现consumeTileQueue在fxaa阶段后调用,之后的阶段就不再绘制。
- // 改为consumeTileQueue只在finalRender时调用即解决问题
- this._consumeTileQueue();
- this._computeAvgTileAltitude();
- this._renderTimestamp = timestamp;
- }
-
-
- let currentTiles;
- let hasFreshTiles = false;
- const frameTiles = this._frameTiles;
- if (frameTiles && timestamp === frameTiles.timestamp) {
- if (frameTiles.empty) {
- return;
- }
- currentTiles = frameTiles;
- } else {
- currentTiles = this._getTilesInCurrentFrame();
- if (!currentTiles) {
- this._frameTiles = { empty: true, timestamp };
- this.completeRender();
- return;
- }
- hasFreshTiles = true;
- this._frameTiles = currentTiles;
- this._frameTiles.timestamp = timestamp;
- if (currentTiles.loadingCount) {
- this.loadTileQueue(currentTiles.tileQueue);
- }
- }
- const { tiles, childTiles, parentTiles, placeholders, loading, loadingCount, missedTiles, incompleteTiles } = currentTiles;
-
- this._drawTiles(tiles, parentTiles, childTiles, placeholders, context, missedTiles, incompleteTiles);
- if (!loadingCount) {
- if (!loading) {
- //redraw to remove parent tiles if any left in last paint
- if (!map.isAnimating() && (this._parentTiles.length || this._childTiles.length)) {
- this._parentTiles = [];
- this._childTiles = [];
- this.setToRedraw();
- }
- this.completeRender();
- }
- }
- if (hasFreshTiles) {
- this.retireTiles();
- }
- }
-
- getTileGridsInCurrentFrame(): TileGrids {
- return this._frameTileGrids;
- }
-
- getCurrentTimestamp(): number {
- return this._renderTimestamp || 0;
- }
-
- //@internal
- _getTilesInCurrentFrame() {
- const map = this.getMap();
- const layer = this.layer;
- const terrainTileMode = layer._isPyramidMode() && layer.options['terrainTileMode'];
- let tileGrids = layer.getTiles();
- this._frameTileGrids = tileGrids;
- tileGrids = tileGrids.tileGrids;
- if (!tileGrids || !tileGrids.length) {
- return null;
- }
- const count = tileGrids.reduce((acc, curr) => acc + (curr && curr.tiles && curr.tiles.length || 0), 0);
- if (count >= (this.tileCache.max / 2)) {
- this.tileCache.setMaxSize(count * 2 + 1);
- }
- let loadingCount = 0;
- let loading = false;
- const checkedTiles = {};
- const tiles = [],
- parentTiles = [], parentKeys = {},
- childTiles = [], childKeys = {},
- placeholders = [], placeholderKeys = {};
- //visit all the tiles
- const tileQueue = {};
- const preLoadingCount = this.markTiles();
- const loadingLimit = this._getLoadLimit();
-
- const l = tileGrids.length;
- // !this._terrainHelper can't be deleted as parent tiles are part of terrain skin, maptalks/issues#608
- const isFirstRender = this._tileZoom === undefined && layer.options['currentTilesFirst'] && !this._terrainHelper;
- // main tile grid is the last one (draws on top)
- this._tileZoom = tileGrids[0]['zoom'];
-
- // let dirtyParentTiles = null;
- let missingTiles = null;
- let incompleteTiles = null;
- if (terrainTileMode) {
- // dirtyParentTiles = new Set();
- missingTiles = [];
- incompleteTiles = new Map();
- }
-
- for (let i = 0; i < l; i++) {
- const tileGrid = tileGrids[i];
- const gridTiles = tileGrid['tiles'];
- const parents = tileGrid['parents'] || EMPTY_ARRAY;
- const parentCount = parents.length;
- const allTiles = isFirstRender ? gridTiles : parents.concat(gridTiles);
-
- let placeholder;
- if (allTiles.length) {
- placeholder = this._generatePlaceHolder(allTiles[0].res);
- }
-
- for (let j = 0, l = allTiles.length; j < l; j++) {
- const tile = allTiles[j];
- const tileId = tile.id;
- const isParentTile = !isFirstRender && j < parentCount;
- //load tile in cache at first if it has.
- let tileLoading = false;
- const tilesCount = tiles.length;
- if (this._isLoadingTile(tileId)) {
- tileLoading = loading = true;
- this.markCurrent(this.tilesLoading[tileId], true);
- } else {
- const cached = this.getCachedTile(tile, isParentTile);
- if (cached) {
- if (!isParentTile) {
- if (cached.image && this.isTileFadingIn(cached.image)) {
- tileLoading = loading = true;
- this.setToRedraw();
- }
-
- if (this.isTileComplete(cached)) {
- tiles.push(cached);
- } else {
- tileLoading = true;
- if (terrainTileMode) {
- incompleteTiles.set(tileId, cached);
- }
- }
- }
- } else {
- tileLoading = loading = true;
- const hitLimit = loadingLimit && (loadingCount + preLoadingCount[0]) > loadingLimit;
- if (!this._tileQueueIds.has(tile.id) && !hitLimit && (!map.isInteracting() || (map.isMoving() || map.isRotating()))) {
- loadingCount++;
- const key = tileId;
- tileQueue[key] = tile;
- }
- }
- }
-
- if (terrainTileMode && !isParentTile) {
- if (tiles.length === tilesCount) {
- missingTiles.push(tile);
- } else {
- checkedTiles[tile.id] = 1;
- // if (tile.parent) {
- // dirtyParentTiles.add(tile.parent);
- // }
- }
- }
-
- if (terrainTileMode) continue;
- if (isParentTile) continue;
- if (!tileLoading) continue;
- if (checkedTiles[tileId]) continue;
-
- checkedTiles[tileId] = 1;
- if (placeholder && !placeholderKeys[tileId]) {
- //tell gl renderer not to bind gl buffer with image
- tile.cache = false;
- placeholders.push({
- image: placeholder,
- info: tile
- });
-
- placeholderKeys[tileId] = 1;
- }
-
- const children = this._findChildTiles(tile);
- if (children.length) {
- children.forEach(c => {
- if (!childKeys[c.info.id]) {
- childTiles.push(c);
- childKeys[c.info.id] = 1;
- }
- });
- }
- // (children.length !== 4) means it's not complete, we still need a parent tile
- if (!children.length || children.length !== 4) {
- const parentTile = this._findParentTile(tile);
- if (parentTile) {
- const parentId = parentTile.info.id;
- if (parentKeys[parentId] === undefined) {
- parentKeys[parentId] = parentTiles.length;
- parentTiles.push(parentTile);
- }/* else {
- //replace with parentTile of above tiles
- parentTiles[parentKeys[parentId]] = parentTile;
- } */
- }
- }
- }
- }
-
- // 遍历 missingTiles ,
- const missedTiles = [];
- if (terrainTileMode) {
- for (let i = 0; i < missingTiles.length; i++) {
- const tile = missingTiles[i].info ? missingTiles[i].info : missingTiles[i];
- if (!tile.parent || checkedTiles[tile.id]) {
- continue;
- }
-
- const { tiles: children, missedTiles: childMissedTiles } = this._findChildTiles(tile);
- if (children.length) {
- pushIn(tiles, children);
- pushIn(missedTiles, childMissedTiles);
- continue;
- } else if (incompleteTiles.has(tile.id)) {
- tiles.push(incompleteTiles.get(tile.id));
- incompleteTiles.delete(tile.id);
- continue;
- }
-
- checkedTiles[tile.id] = 1;
- missedTiles.push(tile);
- // continue;
-
- // // 以下是瓦片合并的优化,但一方面优化效果并不明显,且让渲染逻辑变得复杂,故暂时放弃
- // if (dirtyParentTiles.has(tile.parent) || tile.z < this._tileZoom) {
- // // 如果sibling tile已经被加载过,或者是远处的上级瓦片,则直接加入missedTiles
- // checkedTiles[tile.id] = 1;
- // missedTiles.push(tile);
- // } else {
- // // 遍历当前级别瓦片,如果四个sibling tile都没有加载,则把parentTile加入到missedTiles,减少要处理的瓦片数量
- // let parentTile = parentKeys[tile.parent];
- // if (parentTile || parentTile === null) {
- // // parentTile已被处理过
- // // 1. parentTile存在,则parentTile已经被加入到missedTile,作为parentTile的儿子瓦片的tile可以忽略
- // // 2. parentTile不存在,则把当前瓦片加入到missedTile
- // if (parentTile === null) {
- // checkedTiles[tile.id] = 1;
- // missedTiles.push(tile);
- // }
- // continue;
- // }
- // // 只查询上一级的parentTile
- // parentTile = this._findParentTile(tile, 1) || undefined;
- // if (parentTile && parentTile.image) {
- // // 父级瓦片存在,则把parentTile放入到tiles列表直接绘制
- // tiles.push(parentTile);
- // parentKeys[tile.parent] = parentTile;
- // } else {
- // const parentTileInfo = layer.tileInfoCache.get(tile.parent);
- // // 根据parentTileInfo是否存在,选择把parentTileInfo或者tile加入到missedTiles
- // if (parentTileInfo) {
- // if (!checkedTiles[parentTileInfo.id]) {
- // checkedTiles[parentTileInfo.id] = 1;
- // missedTiles.push(parentTileInfo);
- // }
- // parentKeys[tile.parent] = parentTileInfo;
- // } else {
- // checkedTiles[tile.id] = 1;
- // missedTiles.push(tile);
- // parentKeys[tile.parent] = null;
- // }
- // }
- // }
- }
- }
-
- this.tileCache.shrink();
-
- // if (parentTiles.length) {
- // childTiles.length = 0;
- // this._childTiles.length = 0;
- // }
- return {
- childTiles, missedTiles, parentTiles, tiles, incompleteTiles: incompleteTiles && Array.from(incompleteTiles.values()), placeholders, loading, loadingCount, tileQueue
- };
- }
-
- removeTileCache(tileId: TileId) {
- delete this.tilesInView[tileId];
- this.tileCache.remove(tileId);
- }
-
- isTileCachedOrLoading(tileId: TileId) {
- return this.tileCache.get(tileId) || this.tilesInView[tileId] || this.tilesLoading[tileId];
- }
-
- isTileCached(tileId: TileId) {
- return !!(this.tileCache.get(tileId) || this.tilesInView[tileId]);
- }
-
- isTileFadingIn(tileImage: Tile['image']) {
- return this.getTileFadingOpacity(tileImage) < 1;
- }
-
- //@internal
_drawTiles(tiles, parentTiles, childTiles, placeholders, parentContext, missedTiles, incompleteTiles) {
if (parentTiles.length) {
//closer the latter (to draw on top)
@@ -467,6 +43,9 @@ class TileLayerCanvasRenderer extends CanvasRenderer {
}
}
+ // todo 当为 gl 模式时实例应为 TileLayerGLRenderer
+ const renderInGL = this.layer.options.renderer === 'gl' && (!(this as any).isGL || (this as any).isGL());
+
const context = { tiles, parentTiles: this._parentTiles, childTiles: this._childTiles, parentContext };
this.onDrawTileStart(context, parentContext);
@@ -475,8 +54,13 @@ class TileLayerCanvasRenderer extends CanvasRenderer {
const fadingAnimation = this.layer.options['fadeAnimation'];
this.layer.options['fadeAnimation'] = false;
- this._drawChildTiles(childTiles, parentContext);
- this._drawParentTiles(this._parentTiles, parentContext);
+ if (renderInGL) {
+ this._drawChildTiles(childTiles, parentContext);
+ this._drawParentTiles(this._parentTiles, parentContext);
+ } else {
+ this._drawParentTiles(this._parentTiles, parentContext);
+ this._drawChildTiles(childTiles, parentContext);
+ }
this.layer.options['fadeAnimation'] = fadingAnimation;
this.layer._silentConfig = false;
@@ -494,107 +78,50 @@ class TileLayerCanvasRenderer extends CanvasRenderer {
const fadingAnimation = this.layer.options['fadeAnimation'];
this.layer.options['fadeAnimation'] = false;
- this._drawChildTiles(childTiles, parentContext);
- this._drawParentTiles(this._parentTiles, parentContext);
+ if (renderInGL) {
+ this._drawChildTiles(childTiles, parentContext);
+ this._drawParentTiles(this._parentTiles, parentContext);
+ } else {
+ this._drawParentTiles(this._parentTiles, parentContext);
+ this._drawChildTiles(childTiles, parentContext);
+ }
this.layer.options['fadeAnimation'] = fadingAnimation;
this.layer._silentConfig = false;
}
- // placeholders.forEach(t => this._drawTile(t.info, t.image, parentContext));
+ placeholders.forEach(t => this._drawTile(t.info, t.image, parentContext));
this.onDrawTileEnd(context, parentContext);
}
- //@internal
- _drawChildTiles(childTiles, parentContext) {
- // _hasOwnSR 时,瓦片之间会有重叠,会产生z-fighting,所以背景瓦片要后绘制
- this.drawingChildTiles = true;
- childTiles.forEach(t => this._drawTile(t.info, t.image, parentContext));
- delete this.drawingChildTiles;
- }
-
- //@internal
- _drawParentTiles(parentTiles, parentContext) {
- this.drawingParentTiles = true;
- parentTiles.forEach(t => this._drawTile(t.info, t.image, parentContext));
- delete this.drawingParentTiles;
- }
-
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- onDrawTileStart(context: RenderContext, parentContext: RenderContext) { }
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- onDrawTileEnd(context: RenderContext, parentContext: RenderContext) { }
-
- //@internal
- _drawTile(info, image, parentContext) {
- if (image) {
- this.drawTile(info, image, parentContext);
- }
- }
-
- drawTile(tileInfo: Tile['info'], tileImage: Tile['image'], parentContext?: RenderContext) {
- }
-
- //@internal
- _drawTileAndCache(tile: Tile, parentContext) {
- if (this.isValidCachedTile(tile)) {
- this.tilesInView[tile.info.id] = tile;
- }
- this._drawTile(tile.info, tile.image, parentContext);
- }
-
- drawOnInteracting(event: any, timestamp: number, context) {
- this.draw(timestamp, context);
- }
-
needToRedraw(): boolean {
const map = this.getMap();
- if (this._tileQueue.length) {
+ if (this.checkIfNeedRedraw()) {
return true;
}
if (map.getPitch()) {
return super.needToRedraw();
}
- if (map.isInteracting()) {
+ if (map.isRotating() || map.isZooming()) {
return true;
}
+ if (map.isMoving()) {
+ return !!this.layer.options['forceRenderOnMoving'];
+ }
return super.needToRedraw();
}
- hitDetect(): boolean {
- return false;
- }
-
- /**
- * @private
- * limit tile number to load when map is interacting
- */
- //@internal
- _getLoadLimit(): number {
- if (this.getMap().isInteracting()) {
- return this.layer.options['loadingLimitOnInteracting'];
+ isDrawable(): boolean {
+ if (this.getMap().getPitch()) {
+ if (console) {
+ console.warn('TileLayer with canvas renderer can\'t be pitched, use gl renderer (\'renderer\' : \'gl\') instead.');
+ }
+ this.clear();
+ return false;
}
- return this.layer.options['loadingLimit'] || 0;
- }
-
-
- clear(): void {
- this.retireTiles(true);
- this.tileCache.reset();
- this.tilesInView = {};
- this.tilesLoading = {};
- this._tileQueue = [];
- this._tileQueueIds.clear();
- this._parentTiles = [];
- this._childTiles = [];
- super.clear();
- }
-
- //@internal
- _isLoadingTile(tileId: TileId): boolean {
- return !!this.tilesLoading[tileId];
+ return true;
}
clipCanvas(context): boolean {
@@ -605,741 +132,107 @@ class TileLayerCanvasRenderer extends CanvasRenderer {
return super.clipCanvas(context);
}
- loadTileQueue(tileQueue): void {
- for (const p in tileQueue) {
- if (tileQueue.hasOwnProperty(p)) {
- const tile = tileQueue[p];
- const tileImage = this.loadTile(tile);
- if (tileImage.loadTime === undefined) {
- // tile image's loading may not be async
- this.tilesLoading[tile['id']] = {
- image: tileImage,
- current: true,
- info: tile
- };
- }
- }
- }
- }
-
- loadTile(tile: Tile['info']): Tile['image'] {
- let tileImage = {} as Tile['image'];
- // fixme: 无相关定义,是否实现?
- // @ts-expect-error todo
- if (this.loadTileBitmap) {
- const onLoad = (bitmap) => {
- this.onTileLoad(bitmap, tile);
- };
- const onError = (error, image) => {
- this.onTileError(image, tile, error);
- };
- // @ts-expect-error todo
- this.loadTileBitmap(tile['url'], tile, onLoad, onError);
- } else if (this._tileImageWorkerConn && this.loadTileImage === this.constructor.prototype.loadTileImage) {
- this._fetchImage(tileImage, tile);
- } else {
- const tileSize = this.layer.getTileSize(tile.layer);
- tileImage = new Image() as Tile['image'];
-
- // @ts-expect-error todo
- tileImage.width = tileSize['width'];
- // @ts-expect-error todo
- tileImage.height = tileSize['height'];
-
- (tileImage as any).onload = this.onTileLoad.bind(this, tileImage, tile);
- (tileImage as any).onerror = this.onTileError.bind(this, tileImage, tile);
-
- this.loadTileImage(tileImage, tile['url']);
- }
- return tileImage;
- }
-
- //@internal
- _fetchImage(image: any, tile: Tile['info']) {
- if (image instanceof Image) {
- image.src = tile.url;
- } else {
- const { x, y } = tile;
- const workerId = Math.abs(x + y) % this._tileImageWorkerConn.workers.length;
- this._tileImageWorkerConn.fetchImage(tile.url, workerId, (err, data) => {
- if (err) {
- this.onTileError(image, tile, err);
- } else {
- getImageBitMap(data, (bitmap: Tile['image']) => {
- this.onTileLoad(bitmap, tile);
- });
- }
- }, this.layer.options['fetchOptions'] || {
- referrer: document.location.href,
- headers: { accept: 'image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8' }
- });
- }
- }
-
- loadTileImage(tileImage, url: string) {
- const crossOrigin = this.layer.options['crossOrigin'];
- if (!isNil(crossOrigin)) {
- tileImage.crossOrigin = crossOrigin;
- }
- return loadImage(tileImage, [url]);
- }
-
- abortTileLoading(tileImage: Tile['image'], tileInfo: Tile['info']): void {
- if (tileInfo && tileInfo.id !== undefined) {
- this.removeTileLoading(tileInfo);
- }
- if (!tileImage) return;
- if (tileImage instanceof Image) {
- tileImage.onload = falseFn;
- tileImage.onerror = falseFn;
- tileImage.src = emptyImageUrl;
- }
- }
-
- onTileLoad(tileImage: Tile['image'], tileInfo: Tile['info']): void {
- this.removeTileLoading(tileInfo);
- this._tileQueue.push({ tileInfo: tileInfo, tileData: tileImage });
- this._tileQueueIds.add(tileInfo.id);
- this.setToRedraw();
- }
-
- removeTileLoading(tileInfo: Tile['info']): void {
- delete this.tilesLoading[tileInfo.id];
- // need to setToRedraw to let tiles blocked by loadingLimit continue to load
- this.setToRedraw();
- }
-
- //@internal
- _consumeTileQueue(): void {
- let count = 0;
- const limit = this.layer.options['tileLimitPerFrame'];
- const queue = this._tileQueue;
- /* eslint-disable no-unmodified-loop-condition */
- while (queue.length && (limit <= 0 || count < limit)) {
- const { tileData, tileInfo } = queue.shift();
- if (!this._tileQueueIds.has(tileInfo.id)) {
- continue;
- }
- this._tileQueueIds.delete(tileInfo.id);
- if (!this.checkTileInQueue(tileData, tileInfo)) {
- continue;
- }
- this.consumeTile(tileData, tileInfo);
- count++;
- }
- /* eslint-enable no-unmodified-loop-condition */
- }
-
- //@internal
- _computeAvgTileAltitude() {
- let sumMin = 0;
- let sumMax = 0;
- let count = 0;
- for (const p in this.tilesInView) {
- const info = this.tilesInView[p] && this.tilesInView[p].info;
- if (info) {
- sumMin += info.minAltitude || 0;
- sumMax += info.maxAltitude || 0;
- count++;
- }
- }
- this.avgMinAltitude = sumMin / count;
- this.avgMaxAltitude = sumMax / count;
- }
-
- // Parameters tileImage and tileInfo are required in VectorTileLayerRenderer
- checkTileInQueue(tileImage: Tile['image'], tileInfo: Tile['info']): boolean {
- return true;
- }
-
- consumeTile(tileImage: Tile['image'], tileInfo: Tile['info']): void {
- if (!this.layer) {
- return;
- }
- if (!this.tilesInView) {
- // removed
- return;
- }
- const e = { tile: tileInfo, tileImage: tileImage };
- // let user update tileImage in listener if needed
- tileImage = e.tileImage;
- this.resetTileLoadTime(tileImage);
- this.removeTileLoading(tileInfo);
- this._addTileToCache(tileInfo, tileImage);
- /**
- * tileload event, fired when tile is loaded.
- *
- * @event TileLayer#tileload
- * @type {Object}
- * @property {String} type - tileload
- * @property {TileLayer} target - tile layer
- * @property {Object} tileInfo - tile info
- * @property {Image} tileImage - tile image
- */
- this.layer.fire('tileload', e);
- this.setToRedraw();
- }
-
- resetTileLoadTime(tileImage: Tile['image']): void {
- // loadTime = 0 means a tile from onTileError
- if (tileImage.loadTime !== 0) {
- tileImage.loadTime = now();
- }
- }
-
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- onTileError(tileImage: Tile['image'], tileInfo: Tile['info'], error?: any) {
- if (!this.layer) {
- return;
- }
- // example:
- /* reloadErrorTileFunction: (layer, renderer, tileInfo, tileImage) => {
- const url = tileInfo.url;
- // check if need to reload, e.g. server return 500 status code temporarily
- if (needReload) {
- renderer.loadTile(tileInfo, tileImage);
- }
- } */
- const reloadErrorTileFunction = this.layer.options['reloadErrorTileFunction'];
- if (reloadErrorTileFunction) {
- reloadErrorTileFunction.call(this, this.layer, this, tileInfo, tileImage);
- return;
- }
- // tileImage.onerrorTick = tileImage.onerrorTick || 0;
- // const tileRetryCount = this.layer.options['tileRetryCount'];
- // if (tileRetryCount > tileImage.onerrorTick) {
- // tileImage.onerrorTick++;
- // this._fetchImage(tileImage, tileInfo);
- // this.removeTileLoading(tileInfo);
- // return;
- // }
- const errorUrl = this.layer.options['errorUrl'];
- if (errorUrl) {
- if ((tileImage instanceof Image) && tileImage.src !== errorUrl) {
- tileImage.src = errorUrl;
- this.removeTileLoading(tileInfo);
- return;
- } else {
- tileImage = new Image() as any;
- (tileImage as HTMLImageElement).src = errorUrl;
- }
- }
- this.abortTileLoading(tileImage, tileInfo);
-
- tileImage.loadTime = 0;
- this.removeTileLoading(tileInfo);
- this._addTileToCache(tileInfo, tileImage);
- this.setToRedraw();
- /**
- * tileerror event, fired when tile loading has error.
- *
- * @event TileLayer#tileerror
- * @type {Object}
- * @property {String} type - tileerror
- * @property {TileLayer} target - tile layer
- * @property {Object} tileInfo - tile info
- */
- this.layer.fire('tileerror', { tile: tileInfo });
- }
-
- getDebugInfo(tileId: TileId): string {
- const xyz = tileId.split('_');
- const length = xyz.length;
- return xyz[length - 3] + '/' + xyz[length - 2] + '/' + xyz[length - 1];
- }
-
- findChildTiles(info: Tile['info']) {
- return this._findChildTiles(info);
- }
-
- //@internal
- _findChildTiles(info: Tile['info']): Tile[] | any {
- const layer = this._getLayerOfTile(info.layer);
- const terrainTileMode = layer && layer.options['terrainTileMode'] && layer._isPyramidMode();
- if (!layer || !layer.options['background'] && !terrainTileMode || info.z > this.layer.getMaxZoom()) {
- return EMPTY_ARRAY;
- }
- const map = this.getMap();
- const children = [];
- if (layer._isPyramidMode()) {
- if (!terrainTileMode) {
- // a faster one
- const layer = this._getLayerOfTile(info.layer);
- const zoomDiff = 2;
- const cx = info.x * 2;
- const cy = info.y * 2;
- const cz = info.z + 1;
- const queue = [];
- for (let j = 0; j < 2; j++) {
- for (let jj = 0; jj < 2; jj++) {
- queue.push(cx + j, cy + jj, cz);
- }
- }
- while (queue.length) {
- const z = queue.pop();
- const y = queue.pop();
- const x = queue.pop();
- const id = layer._getTileId(x, y, z, info.layer);
- const canVisit = z + 1 <= info.z + zoomDiff;
- const tile = this.tileCache.getAndRemove(id);
- if (tile) {
- if (this.isValidCachedTile(tile)) {
- children.push(tile);
- this.tileCache.add(id, tile);
- } else if (canVisit) {
- for (let j = 0; j < 2; j++) {
- for (let jj = 0; jj < 2; jj++) {
- queue.push(x * 2 + j, y * 2 + jj, z + 1);
- }
- }
- }
- } else if (canVisit) {
- for (let j = 0; j < 2; j++) {
- for (let jj = 0; jj < 2; jj++) {
- queue.push(x * 2 + j, y * 2 + jj, z + 1);
- }
- }
- }
- }
- return children;
- }
- let missedTiles;
- if (terrainTileMode) {
- missedTiles = [];
- }
- // const zoomDiff = 2;
- const cx = info.x * 2;
- const cy = info.y * 2;
- const cz = info.z + 1;
- // const queue = [];
- // for the sake of performance, we only traverse next 2 levels of children tiles
- const candidates = [];
- for (let i = 0; i < 2; i++) {
- for (let ii = 0; ii < 2; ii++) {
- const x = cx + i;
- const y = cy + ii;
- const z = cz;
- const id = layer._getTileId(x, y, z, info.layer);
- const tile = this.tileCache.getAndRemove(id);
- if (tile && this.isValidCachedTile(tile)) {
- children.push(tile);
- this.tileCache.add(id, tile);
- candidates.push(null);
- } else {
- // 缺少offset
- candidates.push(id);
- }
- }
- }
-
- // children.length等于4时,说明4个一级子瓦片都放入了children中
- if (children.length < 4) {
- let index = 0;
- for (let i = 0; i < 2; i++) {
- for (let ii = 0; ii < 2; ii++) {
- const id = candidates[index++];
- if (!id) {
- continue;
- }
- const x = cx + i;
- const y = cy + ii;
- const z = cz;
- const childrenCount = children.length;
- const childCandidates = [];
- for (let j = 0; j < 2; j++) {
- for (let jj = 0; jj < 2; jj++) {
- const xx = x * 2 + j;
- const yy = y * 2 + jj;
- const zz = z + 1;
- const id = layer._getTileId(xx, yy, zz, info.layer);
- const childTile = this.tileCache.getAndRemove(id);
- if (childTile && this.isValidCachedTile(childTile)) {
- children.push(childTile);
- this.tileCache.add(id, childTile);
- childCandidates.push(null);
- } else {
- childCandidates.push(id);
- }
- }
- }
- if (!terrainTileMode) {
- continue;
- }
- if (children.length - childrenCount < 4) {
- const childTileInfo = layer.tileInfoCache.get(id) || layer._createChildNode(info, i, ii, [0, 0], id);
- if (children.length - childrenCount === 0) {
- // 四个二级子瓦片都没有被缓存,直接将当前的一级子瓦片tileInfo放入missedTiles
- missedTiles.push(childTileInfo);
- } else {
- // 四个二级子瓦片有被缓存的,将没有被缓存的tileInfo加入missedTiles
- let index = 0;
- for (let j = 0; j < 2; j++) {
- for (let jj = 0; jj < 2; jj++) {
- const id = childCandidates[index++];
- if (!id) {
- // 这个二级子瓦片已经被加入到了children
- continue;
- }
- const grandsonTileInfo = this.layer.tileInfoCache.get(id) || layer._createChildNode(childTileInfo, j, jj, [0, 0], id);
- missedTiles.push(grandsonTileInfo);
- }
- }
- }
- }
-
- }
- }
- }
- return terrainTileMode ? { tiles: children, missedTiles } : children;
- }
- const zoomDiff = 1;
- const res = info.res;
- const min = info.extent2d.getMin(),
- max = info.extent2d.getMax(),
- pmin = layer._project(map._pointToPrjAtRes(min, res, TEMP_POINT1), TEMP_POINT1),
- pmax = layer._project(map._pointToPrjAtRes(max, res, TEMP_POINT2), TEMP_POINT2);
-
- for (let i = 1; i < zoomDiff; i++) {
- this._findChildTilesAt(children, pmin, pmax, layer, info.z + i);
- }
-
- return children;
- }
-
- //@internal
- _findChildTilesAt(children: Tile[], pmin: number, pmax: number, layer: any, childZoom: number) {
- const layerId = layer.getId(),
- res = layer.getSpatialReference().getResolution(childZoom);
- if (!res) {
- return;
- }
- const dmin = layer._getTileConfig().getTileIndex(pmin, res),
- dmax = layer._getTileConfig().getTileIndex(pmax, res);
- const sx = Math.min(dmin.idx, dmax.idx), ex = Math.max(dmin.idx, dmax.idx);
- const sy = Math.min(dmin.idy, dmax.idy), ey = Math.max(dmin.idy, dmax.idy);
- let id, tile;
- for (let i = sx; i < ex; i++) {
- for (let ii = sy; ii < ey; ii++) {
- id = layer._getTileId(i, ii, childZoom, layerId);
- tile = this.tileCache.getAndRemove(id);
- if (tile) {
- if (this.isValidCachedTile(tile)) {
- children.push(tile);
- this.tileCache.add(id, tile);
- }
- }
- }
- }
- }
-
- findParentTile(info: Tile['info'], targetDiff?: number): Tile {
- return this._findParentTile(info, targetDiff);
- }
-
- //@internal
- _findParentTile(info: Tile['info'], targetDiff?: number): Tile {
- const map = this.getMap(),
- layer = this._getLayerOfTile(info.layer);
- if (!layer || !layer.options['background'] && !layer.options['terrainTileMode']) {
- return null;
- }
- const minZoom = layer.getMinZoom();
- const zoomDiff: number = targetDiff || info.z - minZoom;
- if (layer._isPyramidMode()) {
- const endZoom = info.z - zoomDiff;
- for (let z = info.z - 1; z >= endZoom; z--) {
- const diff = info.z - z;
- const scale = Math.pow(2, diff);
- const x = Math.floor(info.x / scale);
- const y = Math.floor(info.y / scale);
- let id;
- if (z === info.z - 1) {
- id = info.parent;
- } else {
- id = layer._getTileId(x, y, z, info.layer);
- }
- const tile = this.tileCache.getAndRemove(id);
- if (tile) {
- if (this.isValidCachedTile(tile)) {
- this.tileCache.add(id, tile);
- return tile;
- }
- }
- }
- return null;
- }
- const sr = layer.getSpatialReference();
- // const zoomOffset = layer.options['zoomOffset'];
- const d = sr.getZoomDirection();
- const res = info.res;
- const center = info.extent2d.getCenter(),
- prj = layer._project(map._pointToPrjAtRes(center, res));
- for (let diff = 1; diff <= zoomDiff; diff++) {
- const z = info.z - d * diff;
- const res = sr.getResolution(z);
- if (!res) continue;
- const tileIndex = layer._getTileConfig().getTileIndex(prj, res);
- const id = layer._getTileId(tileIndex.x, tileIndex.y, z, info.layer);
- const tile = this.tileCache.getAndRemove(id);
- if (tile) {
- this.tileCache.add(id, tile);
- return tile;
- }
- }
- return null;
- }
-
- isValidCachedTile(tile: Tile): boolean {
- return !!tile.image;
- }
-
- isTileComplete(tile: Tile) {
- return true;
- }
-
- //@internal
- _getLayerOfTile(layerId: LayerId) {
- return this.layer.getChildLayer ? this.layer.getChildLayer(layerId) : this.layer;
- }
-
- getCachedTile(tile: Tile, isParent: boolean) {
- const tileId = tile.id;
- const tilesInView = this.tilesInView;
- let cached = this.tileCache.getAndRemove(tileId);
- if (cached) {
- if (!isParent) {
- tilesInView[tileId] = cached;
- }
- const tilesLoading = this.tilesLoading;
- if (tilesLoading && tilesLoading[tileId]) {
- this.markCurrent(tilesLoading[tileId], false);
- const { image, info } = tilesLoading[tileId];
- this.abortTileLoading(image, info);
- delete tilesLoading[tileId];
- }
- } else {
- cached = tilesInView[tileId];
- }
- if (cached) {
- cached.current = true;
- if (this.isValidCachedTile(cached)) {
- this.tileCache.add(tileId, cached);
- }
- }
- return cached;
- }
-
- //@internal
- _addTileToCache(tileInfo: Tile['info'], tileImage: Tile['image']) {
- if (this.isValidCachedTile({ info: tileInfo, image: tileImage } as Tile)) {
- const cached = {
- image: tileImage,
- info: tileInfo
- } as Tile;
- this.tileCache.add(tileInfo.id, cached);
- }
- }
-
- getTileOpacity(tileImage: Tile['image'], tileInfo: Tile['info']): number {
- let opacity = this.getTileFadingOpacity(tileImage);
- if (this.layer.getChildLayer) {
- // in GroupTileLayer
- const childLayer = this.layer.getLayer(tileInfo.layer);
- if (childLayer) {
- opacity *= childLayer.options['opacity'];
- }
- }
- return opacity;
- }
-
- getTileFadingOpacity(tileImage: Tile['image']): number {
- if (!this.layer.options['fadeAnimation'] || !tileImage.loadTime) {
- return 1;
- }
- return Math.min(1, (now() - tileImage.loadTime) / this.layer.options['fadeDuration']);
+ clear() {
+ this.clearTileCaches();
+ super.clear();
}
- onRemove(): void {
+ onRemove() {
this.clear();
- delete this.tileCache;
- delete this._tilePlaceHolder;
- delete this._tileZoom;
+ this.removeTileCaches();
super.onRemove();
}
- markCurrent(tile: Tile, isCurrent?: boolean): void {
- tile.current = isCurrent;
- }
- markTiles(): number[] {
- let a = 0, b = 0;
- if (this.tilesLoading) {
- for (const p in this.tilesLoading) {
- this.markCurrent(this.tilesLoading[p], false);
- a++;
- }
- }
- if (this.tilesInView) {
- for (const p in this.tilesInView) {
- this.markCurrent(this.tilesInView[p], false);
- b++;
- }
- }
- return [a, b];
- }
-
- retireTiles(force?: boolean): void {
- for (const i in this.tilesLoading) {
- const tile = this.tilesLoading[i];
- if (force || !tile.current) {
- // abort loading tiles
- if (tile.image) {
- this.abortTileLoading(tile.image, tile.info);
- }
- this.deleteTile(tile);
- this.removeTileLoading(tile.info);
- }
- }
- for (const i in this.tilesInView) {
- const tile = this.tilesInView[i];
- if (!tile.current) {
- delete this.tilesInView[i];
- if (!this.tileCache.has(i)) {
- this.deleteTile(tile);
- }
- }
- }
- }
+ // clip canvas to avoid rough edge of tiles
+ //@internal
+ // _clipByPitch(ctx: CanvasRenderingContext2D): boolean {
+ // const map = this.getMap();
+ // if (map.getPitch() <= map.options['maxVisualPitch']) {
+ // return false;
+ // }
+ // if (!this.layer.options['clipByPitch']) {
+ // return false;
+ // }
+ // const clipExtent = map.getContainerExtent();
+ // const r = map.getDevicePixelRatio();
+ // ctx.save();
+ // ctx.strokeStyle = 'rgba(0, 0, 0, 0)';
+ // ctx.beginPath();
+ // ctx.rect(0, Math.ceil(clipExtent.ymin) * r, Math.ceil(clipExtent.getWidth()) * r, Math.ceil(clipExtent.getHeight()) * r);
+ // ctx.stroke();
+ // ctx.clip();
+ // return true;
+ // }
- deleteTile(tile: Tile): void {
- if (!tile || !tile.image) {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ drawTile(tileInfo: Tile['info'], tileImage: Tile['image'], parentContext?: RenderContext) {
+ if (!tileImage || !this.getMap()) {
return;
}
- const tileId = tile.info.id;
- if (this._tileQueueIds.has(tileId)) {
- this._tileQueueIds.delete(tileId);
- }
- if ((tile.image as any).close) {
- (tile.image as any).close();
- }
- if (tile.image instanceof Image) {
- tile.image.onload = null;
- tile.image.onerror = null;
- }
- }
-
- //@internal
- _generatePlaceHolder(res: number): HTMLCanvasElement {
- const map = this.getMap();
- const placeholder = this.layer.options['placeholder'];
- if (!placeholder || map.getPitch()) {
- return null;
- }
- const tileSize = this.layer.getTileSize();
- const scale = res / map._getResolution();
- const canvas = this._tilePlaceHolder = this._tilePlaceHolder || Canvas.createCanvas(1, 1, map.CanvasClass);
- canvas.width = tileSize.width * scale;
- canvas.height = tileSize.height * scale;
- if (isFunction(placeholder)) {
- placeholder(canvas);
- } else {
- defaultPlaceholder(canvas);
- }
- return canvas;
- }
-
- setTerrainHelper(helper: TerrainHelper) {
- this._terrainHelper = helper;
- }
-}
-
-function falseFn(): boolean { return false; }
-
-function defaultPlaceholder(canvas: HTMLCanvasElement): void {
- const ctx = canvas.getContext('2d'),
- cw = canvas.width, ch = canvas.height,
- w = cw / 16, h = ch / 16;
- ctx.beginPath();
- for (let i = 0; i < 16; i++) {
- ctx.moveTo(0, i * h);
- ctx.lineTo(cw, i * h);
- ctx.moveTo(i * w, 0);
- ctx.lineTo(i * w, ch);
- }
- ctx.strokeStyle = 'rgba(180, 180, 180, 0.1)';
- ctx.lineWidth = 1;
- ctx.stroke();
- ctx.beginPath();
- const path = [
- [0, 0], [cw, 0], [0, ch], [cw, ch], [0, 0], [0, ch], [cw, 0], [cw, ch], [0, ch / 2], [cw, ch / 2], [cw / 2, 0], [cw / 2, ch]
- ];
- for (let i = 1; i < path.length; i += 2) {
- ctx.moveTo(path[i - 1][0], path[i - 1][1]);
- ctx.lineTo(path[i][0], path[i][1]);
+ const { extent2d, offset } = tileInfo;
+ const point = TILE_POINT.set(extent2d.xmin - offset[0], extent2d.ymax - offset[1]),
+ tileZoom = tileInfo.z,
+ tileId = tileInfo.id;
+ const map = this.getMap(),
+ zoom = map.getZoom(),
+ ctx = this.context,
+ cp = map._pointAtResToContainerPoint(point, tileInfo.res, 0, TEMP_POINT),
+ bearing = map.getBearing(),
+ transformed = bearing || zoom !== tileZoom;
+ const opacity = this.getTileOpacity(tileImage, tileInfo);
+ const alpha = ctx.globalAlpha;
+ if (opacity < 1) {
+ ctx.globalAlpha = opacity;
+ }
+ if (!transformed) {
+ cp._round();
+ }
+ let x = cp.x,
+ y = cp.y;
+ let w = tileInfo.extent2d.xmax - tileInfo.extent2d.xmin;
+ let h = tileInfo.extent2d.ymax - tileInfo.extent2d.ymin;
+ const layer = this.layer;
+ const bufferPixel = (layer ? layer.options.bufferPixel : 0);
+ if (transformed) {
+ ctx.save();
+ ctx.translate(x, y);
+ if (bearing) {
+ ctx.rotate(-bearing * Math.PI / 180);
+ }
+ w += bufferPixel;
+ h += bufferPixel;
+ const res = map._getResolution();
+ if (res !== tileInfo.res) {
+ const scale = tileInfo.res / res;
+ ctx.scale(scale, scale);
+ }
+ x = y = 0;
+ }
+ Canvas2D.image(ctx, tileImage, x, y, w, h);
+ if (this.layer.options['debug']) {
+ const color = this.layer.options['debugOutline'];
+ ctx.save();
+ ctx.strokeStyle = color;
+ ctx.fillStyle = color;
+ // ctx.strokeWidth = 10;
+ // ctx.lineWidth = 10
+ ctx.font = '20px monospace';
+ const point = new Point(x, y);
+ Canvas2D.rectangle(ctx, point, { width: w, height: h }, 1, 0);
+ Canvas2D.fillText(ctx, this.getDebugInfo(tileId), point._add(32, h - 14), color);
+ Canvas2D.drawCross(ctx, x + w / 2, y + h / 2, 2, color);
+ ctx.restore();
+ }
+ if (transformed) {
+ ctx.restore();
+ }
+ if (ctx.globalAlpha !== alpha) {
+ ctx.globalAlpha = alpha;
+ }
+ this.setCanvasUpdated();
}
- ctx.lineWidth = 1 * 4;
- ctx.stroke();
-}
-
-export default TileLayerCanvasRenderer;
-
-function compareTiles(a: Tile, b: Tile): number {
- return Math.abs(this._tileZoom - a.info.z) - Math.abs(this._tileZoom - b.info.z);
-}
-export type TileId = string;
-export type LayerId = string | number;
-export type TerrainHelper = any;
-export type TileImage = (HTMLImageElement | HTMLCanvasElement | ImageBitmap) & {
- loadTime: number;
- glBuffer?: TileImageBuffer;
- texture?: TileImageTexture;
- // onerrorTick?: number;
}
-export interface Tile {
- id: TileId;
- info: {
- x: number;
- y: number;
- z: number;
- idx: number;
- idy: number;
- id: TileId;
- layer: number | string;
- children: [];
- error: number;
- offset: [number, number];
- extent2d: Extent;
- res: number;
- url: string;
- parent: any;
- cache?: boolean;
- // todo:检查是否存在定义
- minAltitude?: number;
- maxAltitude?: number;
- };
-
- image: TileImage;
- current?: boolean;
-}
-
-export type RenderContext = any;
-
-export type TilesInViewType = {
- [key: string]: Tile;
-}
-
-export interface TileGrid {
- extent: Extent;
- count: number;
- tiles: Tile[];
- parents: any[];
- offset: number[];
- zoom: number;
-}
-
-export interface TileGrids {
- count: number;
- tileGrids: TileGrid[];
-}
+TileLayer.registerRenderer('canvas', TileLayerCanvasRenderer);
diff --git a/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts b/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts
index ca624e3972..7e9cac5fa3 100644
--- a/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts
+++ b/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts
@@ -1,8 +1,9 @@
-// import TileLayer from '../../../layer/tile/TileLayer';
+import TileLayer from '../../../layer/tile/TileLayer';
import TileLayerCanvasRenderer from './TileLayerCanvasRenderer';
-import type { Tile, RenderContext} from './TileLayerCanvasRenderer';
+import { RenderContext, Tile } from './TileLayerRendererable';
import ImageGLRenderable from '../ImageGLRenderable';
import Point from '../../../geo/Point';
+import { SizeLike } from '../../../geo/Size';
const TILE_POINT = new Point(0, 0);
@@ -21,6 +22,11 @@ const MESH_TO_TEST = { properties: {}};
*/
class TileLayerGLRenderer extends ImageGLRenderable(TileLayerCanvasRenderer) {
+ //override to set to always drawable
+ isDrawable(): boolean {
+ return true;
+ }
+
needToRedraw(): boolean {
const map = this.getMap();
if (this.isGL() && !map.getPitch() && map.isZooming() && !map.isMoving() && !map.isRotating()) {
@@ -69,13 +75,17 @@ class TileLayerGLRenderer extends ImageGLRenderable(TileLayerCanvasRenderer) {
return;
}
- const scale = tileInfo.res / map.getGLRes();
+ const scale = tileInfo._glScale = tileInfo._glScale || tileInfo.res / map.getGLRes();
const w = tileInfo.extent2d.xmax - tileInfo.extent2d.xmin;
const h = tileInfo.extent2d.ymax - tileInfo.extent2d.ymin;
if (tileInfo.cache !== false) {
this._bindGLBuffer(tileImage, w, h);
}
-
+ if (!this.isGL()) {
+ // fall back to canvas 2D, which is faster
+ super.drawTile(tileInfo, tileImage);
+ return;
+ }
const { extent2d, offset } = tileInfo;
const point = TILE_POINT.set(extent2d.xmin - offset[0], tileInfo.extent2d.ymax - offset[1]);
const x = point.x * scale,
@@ -94,6 +104,8 @@ class TileLayerGLRenderer extends ImageGLRenderable(TileLayerCanvasRenderer) {
this.drawGLImage(tileImage as any, x, y, w, h, scale, opacity, debugInfo);
if (this.getTileFadingOpacity(tileImage) < 1) {
this.setToRedraw();
+ } else {
+ this.setCanvasUpdated();
}
}
@@ -112,6 +124,36 @@ class TileLayerGLRenderer extends ImageGLRenderable(TileLayerCanvasRenderer) {
return;
}
+ /**
+ * prepare gl, create program, create buffers and fill unchanged data: image samplers, texture coordinates
+ */
+ onCanvasCreate() {
+ //not in a GroupGLLayer
+ if (!this.canvas.gl || !this.canvas.gl.wrap) {
+ this.createCanvas2();
+ }
+ }
+
+ createContext(): void {
+ super.createContext();
+ this.createGLContext();
+ }
+
+ resizeCanvas(canvasSize: SizeLike) {
+ if (!this.canvas) {
+ return;
+ }
+ super.resizeCanvas(canvasSize);
+ this.resizeGLCanvas();
+ }
+
+ clearCanvas(): void {
+ if (!this.canvas) {
+ return;
+ }
+ super.clearCanvas();
+ this.clearGLCanvas();
+ }
getCanvasImage() {
if (!this.isGL() || !this.canvas2) {
@@ -152,6 +194,7 @@ class TileLayerGLRenderer extends ImageGLRenderable(TileLayerCanvasRenderer) {
}
}
+TileLayer.registerRenderer('gl', TileLayerGLRenderer);
export default TileLayerGLRenderer;
diff --git a/packages/map/src/renderer/layer/tilelayer/TileLayerRendererable.ts b/packages/map/src/renderer/layer/tilelayer/TileLayerRendererable.ts
new file mode 100644
index 0000000000..240fdad7e9
--- /dev/null
+++ b/packages/map/src/renderer/layer/tilelayer/TileLayerRendererable.ts
@@ -0,0 +1,1327 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import {
+ isNil,
+ loadImage,
+ emptyImageUrl,
+ now,
+ isFunction,
+ getImageBitMap,
+ isString,
+ getAbsoluteURL,
+ pushIn
+} from '../../../core/util';
+import Browser from '../../../core/Browser';
+import { default as TileLayer } from '../../../layer/tile/TileLayer';
+import WMSTileLayer from '../../../layer/tile/WMSTileLayer';
+import LayerGLRenderer from '../LayerGLRenderer';
+import Point from '../../../geo/Point';
+import Extent from '../../../geo/Extent';
+import LRUCache from '../../../core/util/LRUCache';
+import Canvas from '../../../core/Canvas';
+import Actor from '../../../core/worker/Actor';
+import { imageFetchWorkerKey } from '../../../core/worker/CoreWorkers';
+import { TileImageBuffer, TileImageTexture } from '../../types';
+import type { WithUndef } from '../../../types/typings';
+import { MixinConstructor } from '../../../core/Mixin';
+
+
+const TEMP_POINT1 = new Point(0, 0);
+const TEMP_POINT2 = new Point(0, 0);
+
+const EMPTY_ARRAY = [];
+class TileWorkerConnection extends Actor {
+ constructor() {
+ super(imageFetchWorkerKey);
+ }
+
+ checkUrl(url: string) {
+ if (!url || !isString(url)) {
+ return url;
+ }
+ //The URL is processed. Here, only the relative protocol is processed
+ return getAbsoluteURL(url);
+
+ }
+
+ // eslint-disable-next-line @typescript-eslint/ban-types
+ fetchImage(url: string, workerId: number, cb: Function, fetchOptions: any) {
+ url = this.checkUrl(url);
+ const data = {
+ url,
+ fetchOptions
+ };
+ this.send(data, EMPTY_ARRAY, cb, workerId);
+ }
+}
+
+/**
+ * 瓦片图层的渲染器抽象类,实现瓦片的遍历功能,可以继承并实现 drawTile 等方法来实现瓦片图层渲染
+ *
+ * @english
+ * Abstract renderer class for TileLayers in maptalks-gl
+ * @class
+ * @protected
+ * @group renderer
+ * @extends {renderer.LayerGLRenderer}
+ */
+const TileLayerRenderable = function (Base: T) {
+ const renderable = class extends Base {
+ [x: string]: any;
+ tilesInView: TilesInViewType;
+ tilesLoading: { [key: string]: any };
+ //@internal
+ _parentTiles: any[];
+ //@internal
+ _childTiles: any[];
+ //@internal
+ _tileZoom: number;
+ //@internal
+ _tileQueue: {
+ tileInfo: any;
+ tileData: any;
+ }[];
+ //@internal
+ _tileQueueIds: Set;
+ tileCache: typeof LRUCache;
+ //@internal
+ _compareTiles: any;
+ //@internal
+ _tileImageWorkerConn: TileWorkerConnection;
+ //@internal
+ _renderTimestamp: number;
+ //@internal
+ _frameTiles: {
+ empty: boolean;
+ timestamp: number;
+ };
+
+ //@internal
+ _terrainHelper: TerrainHelper;
+ //@internal
+ _tilePlaceHolder: any;
+ //@internal
+ _frameTileGrids: TileGrids;
+
+ drawingCurrentTiles: WithUndef;
+ drawingChildTiles: WithUndef;
+ drawingParentTiles: WithUndef;
+ avgMinAltitude: number;
+ avgMaxAltitude: number;
+
+ init(layer) {
+ this.tilesInView = {};
+ this.tilesLoading = {};
+ this._parentTiles = [];
+ this._childTiles = [];
+ this._tileQueue = [];
+ this._tileQueueIds = new Set();
+ const tileSize = layer.getTileSize().width;
+ this.tileCache = new LRUCache(layer.options['maxCacheSize'] * tileSize / 512 * tileSize / 512, (tile: Tile) => {
+ this.deleteTile(tile);
+ });
+ if (Browser.decodeImageInWorker && this.layer.options['decodeImageInWorker'] && (layer.options['renderer'] === 'gl' || !Browser.safari && !Browser.iosWeixin)) {
+ this._tileImageWorkerConn = new TileWorkerConnection();
+ }
+ this._compareTiles = compareTiles.bind(this);
+ }
+
+
+ getCurrentTileZoom(): number {
+ return this._tileZoom;
+ }
+
+ draw(timestamp: number, context): number {
+ const map = this.getMap();
+ if ((this as any).isDrawable && !(this as any).isDrawable()) {
+ return;
+ }
+ const mask2DExtent = this.prepareCanvas();
+ if (mask2DExtent) {
+ if (!mask2DExtent.intersects(this.canvasExtent2D)) {
+ this.completeRender();
+ return;
+ }
+ }
+
+ if (this._renderTimestamp !== timestamp) {
+ // maptalks/issues#10
+ // 如果consumeTileQueue方法在每个renderMode都会调用,但多边形只在fxaa mode下才会绘制。
+ // 导致可能出现consumeTileQueue在fxaa阶段后调用,之后的阶段就不再绘制。
+ // 改为consumeTileQueue只在finalRender时调用即解决问题
+ this._consumeTileQueue();
+ this._computeAvgTileAltitude();
+ this._renderTimestamp = timestamp;
+ }
+
+
+ let currentTiles;
+ let hasFreshTiles = false;
+ const frameTiles = this._frameTiles;
+ if (frameTiles && timestamp === frameTiles.timestamp) {
+ if (frameTiles.empty) {
+ return;
+ }
+ currentTiles = frameTiles;
+ } else {
+ currentTiles = this._getTilesInCurrentFrame();
+ if (!currentTiles) {
+ this._frameTiles = { empty: true, timestamp };
+ this.completeRender();
+ return;
+ }
+ hasFreshTiles = true;
+ this._frameTiles = currentTiles;
+ this._frameTiles.timestamp = timestamp;
+ if (currentTiles.loadingCount) {
+ this.loadTileQueue(currentTiles.tileQueue);
+ }
+ }
+ const { tiles, childTiles, parentTiles, placeholders, loading, loadingCount, missedTiles, incompleteTiles } = currentTiles;
+
+ this._drawTiles(tiles, parentTiles, childTiles, placeholders, context, missedTiles, incompleteTiles);
+ if (!loadingCount) {
+ if (!loading) {
+ //redraw to remove parent tiles if any left in last paint
+ if (!map.isAnimating() && (this._parentTiles.length || this._childTiles.length)) {
+ this._parentTiles = [];
+ this._childTiles = [];
+ this.setToRedraw();
+ }
+ this.completeRender();
+ }
+ }
+ if (hasFreshTiles) {
+ this.retireTiles();
+ }
+ }
+
+ getTileGridsInCurrentFrame(): TileGrids {
+ return this._frameTileGrids;
+ }
+
+ getCurrentTimestamp(): number {
+ return this._renderTimestamp || 0;
+ }
+
+ //@internal
+ _getTilesInCurrentFrame() {
+ const map = this.getMap();
+ const layer = this.layer;
+ const terrainTileMode = layer._isPyramidMode() && layer.options['terrainTileMode'];
+ let tileGrids = layer.getTiles();
+ this._frameTileGrids = tileGrids;
+ tileGrids = tileGrids.tileGrids;
+ if (!tileGrids || !tileGrids.length) {
+ return null;
+ }
+ const count = tileGrids.reduce((acc, curr) => acc + (curr && curr.tiles && curr.tiles.length || 0), 0);
+ if (count >= (this.tileCache.max / 2)) {
+ this.tileCache.setMaxSize(count * 2 + 1);
+ }
+ let loadingCount = 0;
+ let loading = false;
+ const checkedTiles = {};
+ const tiles = [],
+ parentTiles = [], parentKeys = {},
+ childTiles = [], childKeys = {},
+ placeholders = [], placeholderKeys = {};
+ //visit all the tiles
+ const tileQueue = {};
+ const preLoadingCount = this.markTiles();
+ const loadingLimit = this._getLoadLimit();
+
+ const l = tileGrids.length;
+ // !this._terrainHelper can't be deleted as parent tiles are part of terrain skin, maptalks/issues#608
+ const isFirstRender = this._tileZoom === undefined && layer.options['currentTilesFirst'] && !this._terrainHelper;
+ // main tile grid is the last one (draws on top)
+ this._tileZoom = tileGrids[0]['zoom'];
+
+ // let dirtyParentTiles = null;
+ let missingTiles = null;
+ let incompleteTiles = null;
+ if (terrainTileMode) {
+ // dirtyParentTiles = new Set();
+ missingTiles = [];
+ incompleteTiles = new Map();
+ }
+
+ for (let i = 0; i < l; i++) {
+ const tileGrid = tileGrids[i];
+ const gridTiles = tileGrid['tiles'];
+ const parents = tileGrid['parents'] || EMPTY_ARRAY;
+ const parentCount = parents.length;
+ const allTiles = isFirstRender ? gridTiles : parents.concat(gridTiles);
+
+ let placeholder;
+ if (allTiles.length) {
+ placeholder = this._generatePlaceHolder(allTiles[0].res);
+ }
+
+ for (let j = 0, l = allTiles.length; j < l; j++) {
+ const tile = allTiles[j];
+ const tileId = tile.id;
+ const isParentTile = !isFirstRender && j < parentCount;
+ //load tile in cache at first if it has.
+ let tileLoading = false;
+ const tilesCount = tiles.length;
+ if (this._isLoadingTile(tileId)) {
+ tileLoading = loading = true;
+ this.markCurrent(this.tilesLoading[tileId], true);
+ } else {
+ const cached = this.getCachedTile(tile, isParentTile);
+ if (cached) {
+ if (!isParentTile) {
+ if (cached.image && this.isTileFadingIn(cached.image)) {
+ tileLoading = loading = true;
+ this.setToRedraw();
+ }
+
+ if (this.isTileComplete(cached)) {
+ tiles.push(cached);
+ } else {
+ tileLoading = true;
+ if (terrainTileMode) {
+ incompleteTiles.set(tileId, cached);
+ }
+ }
+ }
+ } else {
+ tileLoading = loading = true;
+ const hitLimit = loadingLimit && (loadingCount + preLoadingCount[0]) > loadingLimit;
+ if (!this._tileQueueIds.has(tile.id) && !hitLimit && (!map.isInteracting() || (map.isMoving() || map.isRotating()))) {
+ loadingCount++;
+ const key = tileId;
+ tileQueue[key] = tile;
+ }
+ }
+ }
+
+ if (terrainTileMode && !isParentTile) {
+ if (tiles.length === tilesCount) {
+ missingTiles.push(tile);
+ } else {
+ checkedTiles[tile.id] = 1;
+ // if (tile.parent) {
+ // dirtyParentTiles.add(tile.parent);
+ // }
+ }
+ }
+
+ if (terrainTileMode) continue;
+ if (isParentTile) continue;
+ if (!tileLoading) continue;
+ if (checkedTiles[tileId]) continue;
+
+ checkedTiles[tileId] = 1;
+ if (placeholder && !placeholderKeys[tileId]) {
+ //tell gl renderer not to bind gl buffer with image
+ tile.cache = false;
+ placeholders.push({
+ image: placeholder,
+ info: tile
+ });
+
+ placeholderKeys[tileId] = 1;
+ }
+
+ const children = this._findChildTiles(tile);
+ if (children.length) {
+ children.forEach(c => {
+ if (!childKeys[c.info.id]) {
+ childTiles.push(c);
+ childKeys[c.info.id] = 1;
+ }
+ });
+ }
+ // (children.length !== 4) means it's not complete, we still need a parent tile
+ if (!children.length || children.length !== 4) {
+ const parentTile = this._findParentTile(tile);
+ if (parentTile) {
+ const parentId = parentTile.info.id;
+ if (parentKeys[parentId] === undefined) {
+ parentKeys[parentId] = parentTiles.length;
+ parentTiles.push(parentTile);
+ }/* else {
+ //replace with parentTile of above tiles
+ parentTiles[parentKeys[parentId]] = parentTile;
+ } */
+ }
+ }
+ }
+ }
+
+ // 遍历 missingTiles ,
+ const missedTiles = [];
+ if (terrainTileMode) {
+ for (let i = 0; i < missingTiles.length; i++) {
+ const tile = missingTiles[i].info ? missingTiles[i].info : missingTiles[i];
+ if (!tile.parent || checkedTiles[tile.id]) {
+ continue;
+ }
+
+ const { tiles: children, missedTiles: childMissedTiles } = this._findChildTiles(tile);
+ if (children.length) {
+ pushIn(tiles, children);
+ pushIn(missedTiles, childMissedTiles);
+ continue;
+ } else if (incompleteTiles.has(tile.id)) {
+ tiles.push(incompleteTiles.get(tile.id));
+ incompleteTiles.delete(tile.id);
+ continue;
+ }
+
+ checkedTiles[tile.id] = 1;
+ missedTiles.push(tile);
+ // continue;
+
+ // // 以下是瓦片合并的优化,但一方面优化效果并不明显,且让渲染逻辑变得复杂,故暂时放弃
+ // if (dirtyParentTiles.has(tile.parent) || tile.z < this._tileZoom) {
+ // // 如果sibling tile已经被加载过,或者是远处的上级瓦片,则直接加入missedTiles
+ // checkedTiles[tile.id] = 1;
+ // missedTiles.push(tile);
+ // } else {
+ // // 遍历当前级别瓦片,如果四个sibling tile都没有加载,则把parentTile加入到missedTiles,减少要处理的瓦片数量
+ // let parentTile = parentKeys[tile.parent];
+ // if (parentTile || parentTile === null) {
+ // // parentTile已被处理过
+ // // 1. parentTile存在,则parentTile已经被加入到missedTile,作为parentTile的儿子瓦片的tile可以忽略
+ // // 2. parentTile不存在,则把当前瓦片加入到missedTile
+ // if (parentTile === null) {
+ // checkedTiles[tile.id] = 1;
+ // missedTiles.push(tile);
+ // }
+ // continue;
+ // }
+ // // 只查询上一级的parentTile
+ // parentTile = this._findParentTile(tile, 1) || undefined;
+ // if (parentTile && parentTile.image) {
+ // // 父级瓦片存在,则把parentTile放入到tiles列表直接绘制
+ // tiles.push(parentTile);
+ // parentKeys[tile.parent] = parentTile;
+ // } else {
+ // const parentTileInfo = layer.tileInfoCache.get(tile.parent);
+ // // 根据parentTileInfo是否存在,选择把parentTileInfo或者tile加入到missedTiles
+ // if (parentTileInfo) {
+ // if (!checkedTiles[parentTileInfo.id]) {
+ // checkedTiles[parentTileInfo.id] = 1;
+ // missedTiles.push(parentTileInfo);
+ // }
+ // parentKeys[tile.parent] = parentTileInfo;
+ // } else {
+ // checkedTiles[tile.id] = 1;
+ // missedTiles.push(tile);
+ // parentKeys[tile.parent] = null;
+ // }
+ // }
+ // }
+ }
+ }
+
+ this.tileCache.shrink();
+
+ // if (parentTiles.length) {
+ // childTiles.length = 0;
+ // this._childTiles.length = 0;
+ // }
+ return {
+ childTiles, missedTiles, parentTiles, tiles, incompleteTiles: incompleteTiles && Array.from(incompleteTiles.values()), placeholders, loading, loadingCount, tileQueue
+ };
+ }
+
+ removeTileCache(tileId: TileId) {
+ delete this.tilesInView[tileId];
+ this.tileCache.remove(tileId);
+ }
+
+ isTileCachedOrLoading(tileId: TileId) {
+ return this.tileCache.get(tileId) || this.tilesInView[tileId] || this.tilesLoading[tileId];
+ }
+
+ isTileCached(tileId: TileId) {
+ return !!(this.tileCache.get(tileId) || this.tilesInView[tileId]);
+ }
+
+ isTileFadingIn(tileImage: Tile['image']) {
+ return this.getTileFadingOpacity(tileImage) < 1;
+ }
+
+ //@internal
+ _drawTiles(tiles, parentTiles, childTiles, placeholders, parentContext, missedTiles, incompleteTiles) {
+ if (parentTiles.length) {
+ //closer the latter (to draw on top)
+ // parentTiles.sort((t1, t2) => Math.abs(t2.info.z - this._tileZoom) - Math.abs(t1.info.z - this._tileZoom));
+ parentTiles.sort(this._compareTiles);
+ this._parentTiles = parentTiles;
+ }
+ if (childTiles.length) {
+ this._childTiles = childTiles;
+ this._childTiles.sort(this._compareTiles);
+ }
+
+ let drawBackground = true;
+ const backgroundTimestamp = this.canvas._parentTileTimestamp;
+ if (this.layer.constructor === TileLayer || this.layer.constructor === WMSTileLayer) {
+ // background tiles are only painted once for TileLayer and WMSTileLayer per frame.
+ if (this._renderTimestamp === backgroundTimestamp) {
+ drawBackground = false;
+ } else {
+ this.canvas._parentTileTimestamp = this._renderTimestamp;
+ }
+ }
+
+ const context = { tiles, parentTiles: this._parentTiles, childTiles: this._childTiles, parentContext };
+ this.onDrawTileStart(context, parentContext);
+
+ if (drawBackground && this.layer.options['opacity'] === 1) {
+ this.layer._silentConfig = true;
+ const fadingAnimation = this.layer.options['fadeAnimation'];
+ this.layer.options['fadeAnimation'] = false;
+
+ this._drawChildTiles(childTiles, parentContext);
+ this._drawParentTiles(this._parentTiles, parentContext);
+
+ this.layer.options['fadeAnimation'] = fadingAnimation;
+ this.layer._silentConfig = false;
+ }
+
+ this.drawingCurrentTiles = true;
+ tiles.sort(this._compareTiles);
+ for (let i = 0, l = tiles.length; i < l; i++) {
+ this._drawTileAndCache(tiles[i], parentContext);
+ }
+ delete this.drawingCurrentTiles;
+
+ if (drawBackground && this.layer.options['opacity'] < 1) {
+ this.layer._silentConfig = true;
+ const fadingAnimation = this.layer.options['fadeAnimation'];
+ this.layer.options['fadeAnimation'] = false;
+
+ this._drawChildTiles(childTiles, parentContext);
+ this._drawParentTiles(this._parentTiles, parentContext);
+
+ this.layer.options['fadeAnimation'] = fadingAnimation;
+ this.layer._silentConfig = false;
+ }
+
+ // placeholders.forEach(t => this._drawTile(t.info, t.image, parentContext));
+
+ this.onDrawTileEnd(context, parentContext);
+
+ }
+
+ //@internal
+ _drawChildTiles(childTiles, parentContext) {
+ // _hasOwnSR 时,瓦片之间会有重叠,会产生z-fighting,所以背景瓦片要后绘制
+ this.drawingChildTiles = true;
+ childTiles.forEach(t => this._drawTile(t.info, t.image, parentContext));
+ delete this.drawingChildTiles;
+ }
+
+ //@internal
+ _drawParentTiles(parentTiles, parentContext) {
+ this.drawingParentTiles = true;
+ parentTiles.forEach(t => this._drawTile(t.info, t.image, parentContext));
+ delete this.drawingParentTiles;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onDrawTileStart(context: RenderContext, parentContext: RenderContext) { }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onDrawTileEnd(context: RenderContext, parentContext: RenderContext) { }
+
+ //@internal
+ _drawTile(info, image, parentContext) {
+ if (image) {
+ this.drawTile(info, image, parentContext);
+ }
+ }
+
+ drawTile(tileInfo: Tile['info'], tileImage: Tile['image'], parentContext?: RenderContext) {
+ }
+
+ //@internal
+ _drawTileAndCache(tile: Tile, parentContext) {
+ if (this.isValidCachedTile(tile)) {
+ this.tilesInView[tile.info.id] = tile;
+ }
+ this._drawTile(tile.info, tile.image, parentContext);
+ }
+
+ drawOnInteracting(event: any, timestamp: number, context) {
+ this.draw(timestamp, context);
+ }
+
+ checkIfNeedRedraw(): boolean {
+ return !!this._tileQueue.length;
+ }
+
+ hitDetect(): boolean {
+ return false;
+ }
+
+ /**
+ * @private
+ * limit tile number to load when map is interacting
+ */
+ //@internal
+ _getLoadLimit(): number {
+ if (this.getMap().isInteracting()) {
+ return this.layer.options['loadingLimitOnInteracting'];
+ }
+ return this.layer.options['loadingLimit'] || 0;
+ }
+
+ //@internal
+ _isLoadingTile(tileId: TileId): boolean {
+ return !!this.tilesLoading[tileId];
+ }
+
+ loadTileQueue(tileQueue): void {
+ for (const p in tileQueue) {
+ if (tileQueue.hasOwnProperty(p)) {
+ const tile = tileQueue[p];
+ const tileImage = this.loadTile(tile);
+ if (tileImage.loadTime === undefined) {
+ // tile image's loading may not be async
+ this.tilesLoading[tile['id']] = {
+ image: tileImage,
+ current: true,
+ info: tile
+ };
+ }
+ }
+ }
+ }
+
+ loadTile(tile: Tile['info']): Tile['image'] {
+ let tileImage = {} as Tile['image'];
+ // fixme: 无相关定义,是否实现?
+ if (this.loadTileBitmap) {
+ const onLoad = (bitmap) => {
+ this.onTileLoad(bitmap, tile);
+ };
+ const onError = (error, image) => {
+ this.onTileError(image, tile, error);
+ };
+ this.loadTileBitmap(tile['url'], tile, onLoad, onError);
+ } else if (this._tileImageWorkerConn && this.loadTileImage === this.constructor.prototype.loadTileImage) {
+ this._fetchImage(tileImage, tile);
+ } else {
+ const tileSize = this.layer.getTileSize(tile.layer);
+ tileImage = new Image() as Tile['image'];
+
+ // @ts-expect-error todo
+ tileImage.width = tileSize['width'];
+ // @ts-expect-error todo
+ tileImage.height = tileSize['height'];
+
+ (tileImage as any).onload = this.onTileLoad.bind(this, tileImage, tile);
+ (tileImage as any).onerror = this.onTileError.bind(this, tileImage, tile);
+
+ this.loadTileImage(tileImage, tile['url']);
+ }
+ return tileImage;
+ }
+
+ //@internal
+ _fetchImage(image: any, tile: Tile['info']) {
+ if (image instanceof Image) {
+ image.src = tile.url;
+ } else {
+ const { x, y } = tile;
+ const workerId = Math.abs(x + y) % this._tileImageWorkerConn.workers.length;
+ this._tileImageWorkerConn.fetchImage(tile.url, workerId, (err, data) => {
+ if (err) {
+ this.onTileError(image, tile, err);
+ } else {
+ getImageBitMap(data, (bitmap: Tile['image']) => {
+ this.onTileLoad(bitmap, tile);
+ });
+ }
+ }, this.layer.options['fetchOptions'] || {
+ referrer: document.location.href,
+ headers: { accept: 'image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8' }
+ });
+ }
+ }
+
+ loadTileImage(tileImage, url: string) {
+ const crossOrigin = this.layer.options['crossOrigin'];
+ if (!isNil(crossOrigin)) {
+ tileImage.crossOrigin = crossOrigin;
+ }
+ return loadImage(tileImage, [url]);
+ }
+
+ abortTileLoading(tileImage: Tile['image'], tileInfo: Tile['info']): void {
+ if (tileInfo && tileInfo.id !== undefined) {
+ this.removeTileLoading(tileInfo);
+ }
+ if (!tileImage) return;
+ if (tileImage instanceof Image) {
+ tileImage.onload = falseFn;
+ tileImage.onerror = falseFn;
+ tileImage.src = emptyImageUrl;
+ }
+ }
+
+ onTileLoad(tileImage: Tile['image'], tileInfo: Tile['info']): void {
+ this.removeTileLoading(tileInfo);
+ this._tileQueue.push({ tileInfo: tileInfo, tileData: tileImage });
+ this._tileQueueIds.add(tileInfo.id);
+ this.setToRedraw();
+ }
+
+ removeTileLoading(tileInfo: Tile['info']): void {
+ delete this.tilesLoading[tileInfo.id];
+ // need to setToRedraw to let tiles blocked by loadingLimit continue to load
+ this.setToRedraw();
+ }
+
+ //@internal
+ _consumeTileQueue(): void {
+ let count = 0;
+ const limit = this.layer.options['tileLimitPerFrame'];
+ const queue = this._tileQueue;
+ /* eslint-disable no-unmodified-loop-condition */
+ while (queue.length && (limit <= 0 || count < limit)) {
+ const { tileData, tileInfo } = queue.shift();
+ if (!this._tileQueueIds.has(tileInfo.id)) {
+ continue;
+ }
+ this._tileQueueIds.delete(tileInfo.id);
+ if (!this.checkTileInQueue(tileData, tileInfo)) {
+ continue;
+ }
+ this.consumeTile(tileData, tileInfo);
+ count++;
+ }
+ /* eslint-enable no-unmodified-loop-condition */
+ }
+
+ //@internal
+ _computeAvgTileAltitude() {
+ let sumMin = 0;
+ let sumMax = 0;
+ let count = 0;
+ for (const p in this.tilesInView) {
+ const info = this.tilesInView[p] && this.tilesInView[p].info;
+ if (info) {
+ sumMin += info.minAltitude || 0;
+ sumMax += info.maxAltitude || 0;
+ count++;
+ }
+ }
+ this.avgMinAltitude = sumMin / count;
+ this.avgMaxAltitude = sumMax / count;
+ }
+
+ // Parameters tileImage and tileInfo are required in VectorTileLayerRenderer
+ checkTileInQueue(tileImage: Tile['image'], tileInfo: Tile['info']): boolean {
+ return true;
+ }
+
+ consumeTile(tileImage: Tile['image'], tileInfo: Tile['info']): void {
+ if (!this.layer) {
+ return;
+ }
+ if (!this.tilesInView) {
+ // removed
+ return;
+ }
+ const e = { tile: tileInfo, tileImage: tileImage };
+ // let user update tileImage in listener if needed
+ tileImage = e.tileImage;
+ this.resetTileLoadTime(tileImage);
+ this.removeTileLoading(tileInfo);
+ this._addTileToCache(tileInfo, tileImage);
+ /**
+ * tileload event, fired when tile is loaded.
+ *
+ * @event TileLayer#tileload
+ * @type {Object}
+ * @property {String} type - tileload
+ * @property {TileLayer} target - tile layer
+ * @property {Object} tileInfo - tile info
+ * @property {Image} tileImage - tile image
+ */
+ this.layer.fire('tileload', e);
+ this.setToRedraw();
+ }
+
+ resetTileLoadTime(tileImage: Tile['image']): void {
+ // loadTime = 0 means a tile from onTileError
+ if (tileImage.loadTime !== 0) {
+ tileImage.loadTime = now();
+ }
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onTileError(tileImage: Tile['image'], tileInfo: Tile['info'], error?: any) {
+ if (!this.layer) {
+ return;
+ }
+ // example:
+ /* reloadErrorTileFunction: (layer, renderer, tileInfo, tileImage) => {
+ const url = tileInfo.url;
+ // check if need to reload, e.g. server return 500 status code temporarily
+ if (needReload) {
+ renderer.loadTile(tileInfo, tileImage);
+ }
+ } */
+ const reloadErrorTileFunction = this.layer.options['reloadErrorTileFunction'];
+ if (reloadErrorTileFunction) {
+ reloadErrorTileFunction.call(this, this.layer, this, tileInfo, tileImage);
+ return;
+ }
+ // tileImage.onerrorTick = tileImage.onerrorTick || 0;
+ // const tileRetryCount = this.layer.options['tileRetryCount'];
+ // if (tileRetryCount > tileImage.onerrorTick) {
+ // tileImage.onerrorTick++;
+ // this._fetchImage(tileImage, tileInfo);
+ // this.removeTileLoading(tileInfo);
+ // return;
+ // }
+ const errorUrl = this.layer.options['errorUrl'];
+ if (errorUrl) {
+ if ((tileImage instanceof Image) && tileImage.src !== errorUrl) {
+ tileImage.src = errorUrl;
+ this.removeTileLoading(tileInfo);
+ return;
+ } else {
+ tileImage = new Image() as any;
+ (tileImage as HTMLImageElement).src = errorUrl;
+ }
+ }
+ this.abortTileLoading(tileImage, tileInfo);
+
+ tileImage.loadTime = 0;
+ this.removeTileLoading(tileInfo);
+ this._addTileToCache(tileInfo, tileImage);
+ this.setToRedraw();
+ /**
+ * tileerror event, fired when tile loading has error.
+ *
+ * @event TileLayer#tileerror
+ * @type {Object}
+ * @property {String} type - tileerror
+ * @property {TileLayer} target - tile layer
+ * @property {Object} tileInfo - tile info
+ */
+ this.layer.fire('tileerror', { tile: tileInfo });
+ }
+
+ getDebugInfo(tileId: TileId): string {
+ const xyz = tileId.split('_');
+ const length = xyz.length;
+ return xyz[length - 3] + '/' + xyz[length - 2] + '/' + xyz[length - 1];
+ }
+
+ findChildTiles(info: Tile['info']) {
+ return this._findChildTiles(info);
+ }
+
+ //@internal
+ _findChildTiles(info: Tile['info']): Tile[] | any {
+ const layer = this._getLayerOfTile(info.layer);
+ const terrainTileMode = layer && layer.options['terrainTileMode'] && layer._isPyramidMode();
+ if (!layer || !layer.options['background'] && !terrainTileMode || info.z > this.layer.getMaxZoom()) {
+ return EMPTY_ARRAY;
+ }
+ const map = this.getMap();
+ const children = [];
+ if (layer._isPyramidMode()) {
+ if (!terrainTileMode) {
+ // a faster one
+ const layer = this._getLayerOfTile(info.layer);
+ const zoomDiff = 2;
+ const cx = info.x * 2;
+ const cy = info.y * 2;
+ const cz = info.z + 1;
+ const queue = [];
+ for (let j = 0; j < 2; j++) {
+ for (let jj = 0; jj < 2; jj++) {
+ queue.push(cx + j, cy + jj, cz);
+ }
+ }
+ while (queue.length) {
+ const z = queue.pop();
+ const y = queue.pop();
+ const x = queue.pop();
+ const id = layer._getTileId(x, y, z, info.layer);
+ const canVisit = z + 1 <= info.z + zoomDiff;
+ const tile = this.tileCache.getAndRemove(id);
+ if (tile) {
+ if (this.isValidCachedTile(tile)) {
+ children.push(tile);
+ this.tileCache.add(id, tile);
+ } else if (canVisit) {
+ for (let j = 0; j < 2; j++) {
+ for (let jj = 0; jj < 2; jj++) {
+ queue.push(x * 2 + j, y * 2 + jj, z + 1);
+ }
+ }
+ }
+ } else if (canVisit) {
+ for (let j = 0; j < 2; j++) {
+ for (let jj = 0; jj < 2; jj++) {
+ queue.push(x * 2 + j, y * 2 + jj, z + 1);
+ }
+ }
+ }
+ }
+ return children;
+ }
+ let missedTiles;
+ if (terrainTileMode) {
+ missedTiles = [];
+ }
+ // const zoomDiff = 2;
+ const cx = info.x * 2;
+ const cy = info.y * 2;
+ const cz = info.z + 1;
+ // const queue = [];
+ // for the sake of performance, we only traverse next 2 levels of children tiles
+ const candidates = [];
+ for (let i = 0; i < 2; i++) {
+ for (let ii = 0; ii < 2; ii++) {
+ const x = cx + i;
+ const y = cy + ii;
+ const z = cz;
+ const id = layer._getTileId(x, y, z, info.layer);
+ const tile = this.tileCache.getAndRemove(id);
+ if (tile && this.isValidCachedTile(tile)) {
+ children.push(tile);
+ this.tileCache.add(id, tile);
+ candidates.push(null);
+ } else {
+ // 缺少offset
+ candidates.push(id);
+ }
+ }
+ }
+
+ // children.length等于4时,说明4个一级子瓦片都放入了children中
+ if (children.length < 4) {
+ let index = 0;
+ for (let i = 0; i < 2; i++) {
+ for (let ii = 0; ii < 2; ii++) {
+ const id = candidates[index++];
+ if (!id) {
+ continue;
+ }
+ const x = cx + i;
+ const y = cy + ii;
+ const z = cz;
+ const childrenCount = children.length;
+ const childCandidates = [];
+ for (let j = 0; j < 2; j++) {
+ for (let jj = 0; jj < 2; jj++) {
+ const xx = x * 2 + j;
+ const yy = y * 2 + jj;
+ const zz = z + 1;
+ const id = layer._getTileId(xx, yy, zz, info.layer);
+ const childTile = this.tileCache.getAndRemove(id);
+ if (childTile && this.isValidCachedTile(childTile)) {
+ children.push(childTile);
+ this.tileCache.add(id, childTile);
+ childCandidates.push(null);
+ } else {
+ childCandidates.push(id);
+ }
+ }
+ }
+ if (!terrainTileMode) {
+ continue;
+ }
+ if (children.length - childrenCount < 4) {
+ const childTileInfo = layer.tileInfoCache.get(id) || layer._createChildNode(info, i, ii, [0, 0], id);
+ if (children.length - childrenCount === 0) {
+ // 四个二级子瓦片都没有被缓存,直接将当前的一级子瓦片tileInfo放入missedTiles
+ missedTiles.push(childTileInfo);
+ } else {
+ // 四个二级子瓦片有被缓存的,将没有被缓存的tileInfo加入missedTiles
+ let index = 0;
+ for (let j = 0; j < 2; j++) {
+ for (let jj = 0; jj < 2; jj++) {
+ const id = childCandidates[index++];
+ if (!id) {
+ // 这个二级子瓦片已经被加入到了children
+ continue;
+ }
+ const grandsonTileInfo = this.layer.tileInfoCache.get(id) || layer._createChildNode(childTileInfo, j, jj, [0, 0], id);
+ missedTiles.push(grandsonTileInfo);
+ }
+ }
+ }
+ }
+
+ }
+ }
+ }
+ return terrainTileMode ? { tiles: children, missedTiles } : children;
+ }
+ const zoomDiff = 1;
+ const res = info.res;
+ const min = info.extent2d.getMin(),
+ max = info.extent2d.getMax(),
+ pmin = layer._project(map._pointToPrjAtRes(min, res, TEMP_POINT1), TEMP_POINT1),
+ pmax = layer._project(map._pointToPrjAtRes(max, res, TEMP_POINT2), TEMP_POINT2);
+
+ for (let i = 1; i < zoomDiff; i++) {
+ this._findChildTilesAt(children, pmin, pmax, layer, info.z + i);
+ }
+
+ return children;
+ }
+
+ //@internal
+ _findChildTilesAt(children: Tile[], pmin: number, pmax: number, layer: any, childZoom: number) {
+ const layerId = layer.getId(),
+ res = layer.getSpatialReference().getResolution(childZoom);
+ if (!res) {
+ return;
+ }
+ const dmin = layer._getTileConfig().getTileIndex(pmin, res),
+ dmax = layer._getTileConfig().getTileIndex(pmax, res);
+ const sx = Math.min(dmin.idx, dmax.idx), ex = Math.max(dmin.idx, dmax.idx);
+ const sy = Math.min(dmin.idy, dmax.idy), ey = Math.max(dmin.idy, dmax.idy);
+ let id, tile;
+ for (let i = sx; i < ex; i++) {
+ for (let ii = sy; ii < ey; ii++) {
+ id = layer._getTileId(i, ii, childZoom, layerId);
+ tile = this.tileCache.getAndRemove(id);
+ if (tile) {
+ if (this.isValidCachedTile(tile)) {
+ children.push(tile);
+ this.tileCache.add(id, tile);
+ }
+ }
+ }
+ }
+ }
+
+ findParentTile(info: Tile['info'], targetDiff?: number): Tile {
+ return this._findParentTile(info, targetDiff);
+ }
+
+ //@internal
+ _findParentTile(info: Tile['info'], targetDiff?: number): Tile {
+ const map = this.getMap(),
+ layer = this._getLayerOfTile(info.layer);
+ if (!layer || !layer.options['background'] && !layer.options['terrainTileMode']) {
+ return null;
+ }
+ const minZoom = layer.getMinZoom();
+ const zoomDiff: number = targetDiff || info.z - minZoom;
+ if (layer._isPyramidMode()) {
+ const endZoom = info.z - zoomDiff;
+ for (let z = info.z - 1; z >= endZoom; z--) {
+ const diff = info.z - z;
+ const scale = Math.pow(2, diff);
+ const x = Math.floor(info.x / scale);
+ const y = Math.floor(info.y / scale);
+ let id;
+ if (z === info.z - 1) {
+ id = info.parent;
+ } else {
+ id = layer._getTileId(x, y, z, info.layer);
+ }
+ const tile = this.tileCache.getAndRemove(id);
+ if (tile) {
+ if (this.isValidCachedTile(tile)) {
+ this.tileCache.add(id, tile);
+ return tile;
+ }
+ }
+ }
+ return null;
+ }
+ const sr = layer.getSpatialReference();
+ // const zoomOffset = layer.options['zoomOffset'];
+ const d = sr.getZoomDirection();
+ const res = info.res;
+ const center = info.extent2d.getCenter(),
+ prj = layer._project(map._pointToPrjAtRes(center, res));
+ for (let diff = 1; diff <= zoomDiff; diff++) {
+ const z = info.z - d * diff;
+ const res = sr.getResolution(z);
+ if (!res) continue;
+ const tileIndex = layer._getTileConfig().getTileIndex(prj, res);
+ const id = layer._getTileId(tileIndex.x, tileIndex.y, z, info.layer);
+ const tile = this.tileCache.getAndRemove(id);
+ if (tile) {
+ this.tileCache.add(id, tile);
+ return tile;
+ }
+ }
+ return null;
+ }
+
+ isValidCachedTile(tile: Tile): boolean {
+ return !!tile.image;
+ }
+
+ isTileComplete(tile: Tile) {
+ return true;
+ }
+
+ //@internal
+ _getLayerOfTile(layerId: LayerId) {
+ return this.layer.getChildLayer ? this.layer.getChildLayer(layerId) : this.layer;
+ }
+
+ getCachedTile(tile: Tile, isParent: boolean) {
+ const tileId = tile.id;
+ const tilesInView = this.tilesInView;
+ let cached = this.tileCache.getAndRemove(tileId);
+ if (cached) {
+ if (!isParent) {
+ tilesInView[tileId] = cached;
+ }
+ const tilesLoading = this.tilesLoading;
+ if (tilesLoading && tilesLoading[tileId]) {
+ this.markCurrent(tilesLoading[tileId], false);
+ const { image, info } = tilesLoading[tileId];
+ this.abortTileLoading(image, info);
+ delete tilesLoading[tileId];
+ }
+ } else {
+ cached = tilesInView[tileId];
+ }
+ if (cached) {
+ cached.current = true;
+ if (this.isValidCachedTile(cached)) {
+ this.tileCache.add(tileId, cached);
+ }
+ }
+ return cached;
+ }
+
+ //@internal
+ _addTileToCache(tileInfo: Tile['info'], tileImage: Tile['image']) {
+ if (this.isValidCachedTile({ info: tileInfo, image: tileImage } as Tile)) {
+ const cached = {
+ image: tileImage,
+ info: tileInfo
+ } as Tile;
+ this.tileCache.add(tileInfo.id, cached);
+ }
+ }
+
+ getTileOpacity(tileImage: Tile['image'], tileInfo: Tile['info']): number {
+ let opacity = this.getTileFadingOpacity(tileImage);
+ if (this.layer.getChildLayer) {
+ // in GroupTileLayer
+ const childLayer = this.layer.getLayer(tileInfo.layer);
+ if (childLayer) {
+ opacity *= childLayer.options['opacity'];
+ }
+ }
+ return opacity;
+ }
+
+ getTileFadingOpacity(tileImage: Tile['image']): number {
+ if (!this.layer.options['fadeAnimation'] || !tileImage.loadTime) {
+ return 1;
+ }
+ return Math.min(1, (now() - tileImage.loadTime) / this.layer.options['fadeDuration']);
+ }
+
+ clearTileCaches(): void {
+ this.retireTiles(true);
+ this.tileCache.reset();
+ this.tilesInView = {};
+ this.tilesLoading = {};
+ this._tileQueue = [];
+ this._tileQueueIds.clear();
+ this._parentTiles = [];
+ this._childTiles = [];
+ }
+
+ removeTileCaches() {
+ delete this.tileCache;
+ delete this._tilePlaceHolder;
+ delete this._tileZoom;
+ }
+
+ markCurrent(tile: Tile, isCurrent?: boolean): void {
+ tile.current = isCurrent;
+ }
+
+ markTiles(): number[] {
+ let a = 0, b = 0;
+ if (this.tilesLoading) {
+ for (const p in this.tilesLoading) {
+ this.markCurrent(this.tilesLoading[p], false);
+ a++;
+ }
+ }
+ if (this.tilesInView) {
+ for (const p in this.tilesInView) {
+ this.markCurrent(this.tilesInView[p], false);
+ b++;
+ }
+ }
+ return [a, b];
+ }
+
+ retireTiles(force?: boolean): void {
+ for (const i in this.tilesLoading) {
+ const tile = this.tilesLoading[i];
+ if (force || !tile.current) {
+ // abort loading tiles
+ if (tile.image) {
+ this.abortTileLoading(tile.image, tile.info);
+ }
+ this.deleteTile(tile);
+ this.removeTileLoading(tile.info);
+ }
+ }
+ for (const i in this.tilesInView) {
+ const tile = this.tilesInView[i];
+ if (!tile.current) {
+ delete this.tilesInView[i];
+ if (!this.tileCache.has(i)) {
+ this.deleteTile(tile);
+ }
+ }
+ }
+ }
+
+ deleteTile(tile: Tile): void {
+ if (!tile || !tile.image) {
+ return;
+ }
+ const tileId = tile.info.id;
+ if (this._tileQueueIds.has(tileId)) {
+ this._tileQueueIds.delete(tileId);
+ }
+ if ((tile.image as any).close) {
+ (tile.image as any).close();
+ }
+ if (tile.image instanceof Image) {
+ tile.image.onload = null;
+ tile.image.onerror = null;
+ }
+ }
+
+ //@internal
+ _generatePlaceHolder(res: number): HTMLCanvasElement {
+ const map = this.getMap();
+ const placeholder = this.layer.options['placeholder'];
+ if (!placeholder || map.getPitch()) {
+ return null;
+ }
+ const tileSize = this.layer.getTileSize();
+ const scale = res / map._getResolution();
+ const canvas = this._tilePlaceHolder = this._tilePlaceHolder || Canvas.createCanvas(1, 1, map.CanvasClass);
+ canvas.width = tileSize.width * scale;
+ canvas.height = tileSize.height * scale;
+ if (isFunction(placeholder)) {
+ placeholder(canvas);
+ } else {
+ defaultPlaceholder(canvas);
+ }
+ return canvas;
+ }
+
+ setTerrainHelper(helper: TerrainHelper) {
+ this._terrainHelper = helper;
+ }
+ }
+ return renderable;
+}
+
+export default TileLayerRenderable;
+
+function falseFn(): boolean { return false; }
+
+function defaultPlaceholder(canvas: HTMLCanvasElement): void {
+ const ctx = canvas.getContext('2d'),
+ cw = canvas.width, ch = canvas.height,
+ w = cw / 16, h = ch / 16;
+ ctx.beginPath();
+ for (let i = 0; i < 16; i++) {
+ ctx.moveTo(0, i * h);
+ ctx.lineTo(cw, i * h);
+ ctx.moveTo(i * w, 0);
+ ctx.lineTo(i * w, ch);
+ }
+ ctx.strokeStyle = 'rgba(180, 180, 180, 0.1)';
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ ctx.beginPath();
+ const path = [
+ [0, 0], [cw, 0], [0, ch], [cw, ch], [0, 0], [0, ch], [cw, 0], [cw, ch], [0, ch / 2], [cw, ch / 2], [cw / 2, 0], [cw / 2, ch]
+ ];
+ for (let i = 1; i < path.length; i += 2) {
+ ctx.moveTo(path[i - 1][0], path[i - 1][1]);
+ ctx.lineTo(path[i][0], path[i][1]);
+ }
+ ctx.lineWidth = 1 * 4;
+ ctx.stroke();
+}
+
+function compareTiles(a: Tile, b: Tile): number {
+ return Math.abs(this._tileZoom - a.info.z) - Math.abs(this._tileZoom - b.info.z);
+}
+
+export type TileId = string;
+export type LayerId = string | number;
+export type TerrainHelper = any;
+export type TileImage = (HTMLImageElement | HTMLCanvasElement | ImageBitmap) & {
+ loadTime: number;
+ glBuffer?: TileImageBuffer;
+ texture?: TileImageTexture;
+ // onerrorTick?: number;
+}
+
+export interface Tile {
+ id: TileId;
+ info: {
+ x: number;
+ y: number;
+ z: number;
+ idx: number;
+ idy: number;
+ id: TileId;
+ layer: number | string;
+ children: [];
+ error: number;
+ offset: [number, number];
+ extent2d: Extent;
+ res: number;
+ url: string;
+ parent: any;
+ cache?: boolean;
+ // todo:检查是否存在定义
+ minAltitude?: number;
+ maxAltitude?: number;
+ //@internal
+ _glScale: number;
+ };
+
+ image: TileImage;
+ current?: boolean;
+}
+
+export type RenderContext = any;
+
+export type TilesInViewType = {
+ [key: string]: Tile;
+}
+
+export interface TileGrid {
+ extent: Extent;
+ count: number;
+ tiles: Tile[];
+ parents: any[];
+ offset: number[];
+ zoom: number;
+}
+
+export interface TileGrids {
+ count: number;
+ tileGrids: TileGrid[];
+}
diff --git a/packages/map/src/renderer/layer/tilelayer/index.ts b/packages/map/src/renderer/layer/tilelayer/index.ts
index d7c9256754..b38a368dc8 100644
--- a/packages/map/src/renderer/layer/tilelayer/index.ts
+++ b/packages/map/src/renderer/layer/tilelayer/index.ts
@@ -1,11 +1,13 @@
import TileLayerCanvasRenderer from './TileLayerCanvasRenderer';
-// import TileLayerGLRenderer from './TileLayerGLRenderer';
+import TileLayerGLRenderer from './TileLayerGLRenderer';
+import TileLayerRendererable from './TileLayerRendererable';
import { CanvasTileLayerCanvasRenderer, CanvasTileLayerGLRenderer } from './CanvasTileLayerRenderer';
import QuadStencil from './QuadStencil';
export {
TileLayerCanvasRenderer,
- // TileLayerGLRenderer,
+ TileLayerGLRenderer,
+ TileLayerRendererable,
CanvasTileLayerCanvasRenderer,
CanvasTileLayerGLRenderer,
QuadStencil
diff --git a/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts b/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts
index 2b502c1e5b..2bad480c2d 100644
--- a/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts
+++ b/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts
@@ -1,8 +1,9 @@
import { isArrayHasData, pushIn } from '../../../core/util';
-import { type Geometry } from '../../../geometry';
import CanvasRenderer from '../CanvasRenderer';
-import { Geometries } from '../../../geometry';
+import { Geometries, Geometry } from '../../../geometry';
import Extent from '../../../geo/Extent';
+import LayerGLRenderer from '../LayerGLRenderer';
+import { MixinConstructor } from '../../../core/Mixin';
interface MapStateCacheType {
resolution: number;
@@ -17,132 +18,163 @@ interface MapStateCacheType {
offset: number;
}
-/**
- * OverlayLayer 的父呈现器类,供 OverlayLayer 的子类继承。
- *
- * @english
- *
- * A parent renderer class for OverlayLayer to inherit by OverlayLayer's subclasses.
- * @protected
- * @memberOf renderer
- * @name OverlayLayerCanvasRenderer
- * @extends renderer.CanvasRenderer
- */
-class OverlayLayerRenderer extends CanvasRenderer {
- //@internal
- _geosToCheck: Geometries[];
- //@internal
- _resourceChecked: boolean;
- clearImageData?(): void;
- //@internal
- _lastGeosToDraw: Geometry[];
- mapStateCache: MapStateCacheType;
+const OverlayLayerRenderable = function (Base: T) {
+ const renderable = class extends Base {
+ //@internal
+ _geosToCheck: Geometries[];
+ //@internal
+ _resourceChecked: boolean;
+ clearImageData?(): void;
+ //@internal
+ _lastGeosToDraw: Geometry[];
+ //@internal
+ mapStateCache: MapStateCacheType;
- /**
- * @english
- * possible memory leaks:
- * 1. if geometries' symbols with external resources change frequently,
- * resources of old symbols will still be stored.
- * 2. removed geometries' resources won't be removed.
- */
- checkResources() {
- const geometries = this._geosToCheck || [];
- if (!this._resourceChecked && this.layer._geoList) {
- pushIn(geometries, this.layer._geoList);
- }
- if (!isArrayHasData(geometries)) {
- return [];
- }
- const resources = [];
- const cache = {};
-
- for (let i = geometries.length - 1; i >= 0; i--) {
- const geo = geometries[i];
- const res = geo._getExternalResources();
- if (!res.length) {
- continue;
+ /**
+ * @english
+ * possible memory leaks:
+ * 1. if geometries' symbols with external resources change frequently,
+ * resources of old symbols will still be stored.
+ * 2. removed geometries' resources won't be removed.
+ */
+ checkResources() {
+ const geometries = this._geosToCheck || [];
+ if (!this._resourceChecked && (this as any).layer._geoList) {
+ pushIn(geometries, (this as any).layer._geoList);
+ }
+ if (!isArrayHasData(geometries)) {
+ return [];
}
- if (!this.resources) {
- // @tip 解构会有一定的性能影响,对于少量数据是否可以忽略
- resources.push(...res);
- } else {
- for (let i = 0; i < res.length; i++) {
- const url = res[i][0];
- if (!this.resources.isResourceLoaded(res[i]) && !cache[url]) {
- resources.push(res[i]);
- cache[url] = 1;
+ const resources = [];
+ const cache = {};
+
+ for (let i = geometries.length - 1; i >= 0; i--) {
+ const geo = geometries[i];
+ const res = geo._getExternalResources();
+ if (!res.length) {
+ continue;
+ }
+ if (!(this as any).resources) {
+ // @tip 解构会有一定的性能影响,对于少量数据是否可以忽略
+ resources.push(...res);
+ } else {
+ for (let i = 0; i < res.length; i++) {
+ const url = res[i][0];
+ if (!(this as any).resources.isResourceLoaded(res[i]) && !cache[url]) {
+ resources.push(res[i]);
+ cache[url] = 1;
+ }
}
}
}
+ this._resourceChecked = true;
+ delete this._geosToCheck;
+ return resources;
}
- this._resourceChecked = true;
- delete this._geosToCheck;
- return resources;
- }
- render(...args: any[]): void {
- this.layer._sortGeometries();
- return super.render.apply(this, args);
- }
+ //@internal
+ _addGeoToCheckRes(res: Geometries | Geometries[]) {
+ if (!res) {
+ return;
+ }
+ if (!Array.isArray(res)) {
+ res = [res];
+ }
+ if (!this._geosToCheck) {
+ this._geosToCheck = [];
+ }
+ pushIn(this._geosToCheck, res);
+ }
- //@internal
- _addGeoToCheckRes(res: Geometries | Geometries[]) {
- if (!res) {
- return;
+ onGeometryAdd(geometries: Geometries | Geometries[]) {
+ this._addGeoToCheckRes(geometries);
+ redraw(this);
}
- if (!Array.isArray(res)) {
- res = [res];
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onGeometryRemove(params: any) {
+ /**
+ * removegeo 事件
+ *
+ * @english
+ * removegeo event.
+ *
+ * @event OverlayLayer#removegeo
+ * @type {Object}
+ * @property {String} type - removegeo
+ * @property {OverlayLayer} target - layer
+ * @property {Geometry[]} geometries - the geometries to remove
+ */
+ (this as any).layer.fire('removegeo', {
+ 'type': 'removegeo',
+ 'target': this,
+ 'geometries': params
+ });
+ redraw(this);
}
- if (!this._geosToCheck) {
- this._geosToCheck = [];
+
+ onGeometrySymbolChange(e: { target: Geometries; }) {
+ this._addGeoToCheckRes(e.target);
+ redraw(this);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onGeometryShapeChange(params: any) {
+ redraw(this);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onGeometryPositionChange(params: any) {
+ redraw(this);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onGeometryZIndexChange(params: any) {
+ redraw(this);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onGeometryShow(params: any) {
+ redraw(this);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onGeometryHide(params: any) {
+ redraw(this);
}
- pushIn(this._geosToCheck, res);
- }
- onGeometryAdd(geometries: Geometries | Geometries[]) {
- this._addGeoToCheckRes(geometries);
- redraw(this);
- }
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- onGeometryRemove(params: any) {
- redraw(this);
+ onGeometryPropertiesChange(_: any) {
+ redraw(this);
+ }
}
+ return renderable;
+}
- onGeometrySymbolChange(e: { target: Geometries; }) {
- this._addGeoToCheckRes(e.target);
- redraw(this);
- }
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- onGeometryShapeChange(params: any) {
- redraw(this);
- }
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- onGeometryPositionChange(params: any) {
- redraw(this);
- }
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- onGeometryZIndexChange(params: any) {
- redraw(this);
- }
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- onGeometryShow(params: any) {
- redraw(this);
- }
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- onGeometryHide(params: any) {
- redraw(this);
+/**
+ * OverlayLayer 的父呈现器类,供 OverlayLayer 的子类继承。
+ *
+ * @english
+ *
+ * A parent renderer class for OverlayLayer to inherit by OverlayLayer's subclasses.
+ * @protected
+ * @memberOf renderer
+ * @name OverlayLayerCanvasRenderer
+ * @extends renderer.CanvasRenderer
+ */
+class OverlayLayerCanvasRenderer extends OverlayLayerRenderable(CanvasRenderer) {
+ render(...args: any[]): void {
+ (this as any).layer._sortGeometries();
+ return super.render.apply(this, args);
}
+}
- onGeometryPropertiesChange(_: any) {
- redraw(this);
+
+class OverlayLayerGLRenderer extends OverlayLayerRenderable(LayerGLRenderer) {
+ render(...args: any[]): void {
+ (this as any).layer._sortGeometries();
+ return super.render.apply(this, args);
}
}
-function redraw(renderer: OverlayLayerRenderer): void {
- if (renderer.layer.options['drawImmediate']) {
+function redraw(renderer): void {
+ if (renderer instanceof OverlayLayerCanvasRenderer && renderer.layer.options['drawImmediate']) {
renderer.render();
}
renderer.setToRedraw();
}
-export default OverlayLayerRenderer;
+export { OverlayLayerCanvasRenderer, OverlayLayerGLRenderer };
diff --git a/packages/map/src/renderer/layer/vectorlayer/VectorLayerCanvasRenderer.ts b/packages/map/src/renderer/layer/vectorlayer/VectorLayerCanvasRenderer.ts
new file mode 100644
index 0000000000..1142361b7c
--- /dev/null
+++ b/packages/map/src/renderer/layer/vectorlayer/VectorLayerCanvasRenderer.ts
@@ -0,0 +1,773 @@
+/* eslint-disable @typescript-eslint/ban-types */
+import { getExternalResources, now, getPointsResultPts, type Vector3, isNil } from '../../../core/util';
+import VectorLayer from '../../../layer/VectorLayer';
+import { OverlayLayerCanvasRenderer } from './OverlayLayerCanvasRenderer';
+import Extent from '../../../geo/Extent';
+import PointExtent from '../../../geo/PointExtent';
+import * as vec3 from '../../../core/util/vec3';
+import Canvas from '../../../core/Canvas';
+import type { Painter, CollectionPainter } from '../../geometry';
+import { Point } from '../../../geo';
+import { Geometries, Marker } from '../../../geometry';
+import type { WithUndef } from '../../../types/typings';
+
+const TEMP_EXTENT = new PointExtent();
+const TEMP_VEC3: Vector3 = [] as unknown as Vector3;
+const TEMP_FIXEDEXTENT = new PointExtent();
+const PLACEMENT_CENTER = 'center';
+
+function clearCanvas(canvas: HTMLCanvasElement): CanvasRenderingContext2D {
+ if (!canvas) {
+ return null;
+ }
+ const ctx = canvas.getContext('2d');
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ return ctx;
+}
+
+function isDebug(layer: any) {
+ return layer && layer.options.progressiveRender && layer.options.progressiveRenderDebug;
+}
+
+/**
+ * 基于 `HTML5 Canvas2D` 的渲染器类,用于矢量层
+ *
+ * @english
+ * Renderer class based on HTML5 Canvas2D for VectorLayers
+ * @protected
+ * @group renderer
+ * @name VectorLayerCanvasRenderer
+ * @extends renderer.OverlaylayerCanvasRenderer
+ * @param layer - layer to render
+ */
+class VectorLayerRenderer extends OverlayLayerCanvasRenderer {
+ //@internal
+ _lastRenderTime: number;
+ //@internal
+ _lastCollisionTime: number;
+ //@internal
+ _imageData: ImageData;
+ //@internal
+ _geosToDraw: Geometries[];
+ //@internal
+ _lastGeosToDraw: Geometries[];
+ //@internal
+ _hasPoint: boolean;
+ //@internal
+ _onlyHasPoint: WithUndef;
+ //@internal
+ _displayExtent: Extent;
+ //@internal
+ _drawnRes: number;
+
+ renderEnd: boolean;
+ pageGeos: Geometries[];
+ page: number;
+ maxTolerance: number;
+ geoPainterList: (Painter | CollectionPainter)[];
+ snapshotCanvas: HTMLCanvasElement;
+
+ setToRedraw(): this {
+ super.setToRedraw();
+ this._resetProgressiveRender();
+ return this;
+ }
+
+
+ //@internal
+ _geoIsCollision(geo: GeoType, collisionIndex: any) {
+ if (!geo) {
+ return false;
+ }
+ const collision = geo.options.collision;
+ if (!collision) {
+ return false;
+ }
+ // const type = geo.getType();
+ if (geo.isPoint && geo.getContainerExtent) {
+ if (!geo.bbox) {
+ geo.bbox = [0, 0, 0, 0];
+ }
+ const bufferSize = this.layer.options['collisionBufferSize'];
+ const extent = geo.getContainerExtent();
+ if (!extent) {
+ return false;
+ }
+ geo.bbox[0] = extent.xmin - bufferSize;
+ geo.bbox[1] = extent.ymin - bufferSize;
+ geo.bbox[2] = extent.xmax + bufferSize;
+ geo.bbox[3] = extent.ymax + bufferSize;
+ if (collisionIndex.collides(geo.bbox)) {
+ geo._collided = true;
+ return true;
+ }
+ collisionIndex.insertBox(geo.bbox);
+ }
+ return false;
+ }
+
+ getImageData(): ImageData {
+ //如果不开启geometry event 或者 渲染频率很高 不要取缓存了,因为getImageData是个很昂贵的操作
+ if ((!this._lastRenderTime) || (now() - this._lastRenderTime) < 32) {
+ return null;
+ }
+ if (!this.context || !this.context.canvas) {
+ return null;
+ }
+ if (!this._imageData) {
+ const { width, height } = this.context.canvas;
+ try {
+ this._imageData = this.context.getImageData(0, 0, width, height);
+ } catch (error) {
+ console.warn('hit detect failed with tainted canvas, some geometries have external resources in another domain:\n', error);
+ }
+ }
+ return this._imageData;
+ }
+
+ clearImageData(): void {
+ //每次渲染完成清除缓存的imageData
+ this._imageData = null;
+ delete this._imageData;
+ this._lastRenderTime = now();
+ }
+
+ checkResources(...args: any[]) {
+ const resources = super.checkResources.apply(this, args);
+ let style = this.layer.getStyle();
+ if (style) {
+ if (!Array.isArray(style)) {
+ style = [style];
+ }
+ style.forEach(s => {
+ const res = getExternalResources(s['symbol'], true);
+ for (let i = 0, l = res.length; i < l; i++) {
+ if (!this.resources.isResourceLoaded(res[i])) {
+ resources.push(res[i]);
+ }
+ }
+ });
+ }
+ return resources;
+ }
+
+ needToRedraw(): boolean {
+ if (this.isProgressiveRender() && !this.renderEnd) {
+ return true;
+ }
+ const map = this.getMap();
+ if (map.isInteracting() && this.layer.options['enableAltitude']) {
+ return true;
+ }
+ // don't redraw when map is zooming without pitch and layer doesn't have any point symbolizer.
+ if (map.isZooming() && !map.isRotating() && !map.getPitch() && !this._hasPoint && this.layer.constructor === VectorLayer) {
+ return false;
+ }
+ return super.needToRedraw();
+ }
+
+ /**
+ * render layer
+ */
+ draw(): void {
+ if (!this.getMap()) {
+ return;
+ }
+ if (!this.layer.isVisible() || this.layer.isEmpty()) {
+ this.clearCanvas();
+ this.completeRender();
+ return;
+ }
+
+ this.prepareCanvas();
+
+ this.drawGeos();
+
+ this.completeRender();
+ }
+
+ isBlank(): boolean {
+ if (!this.context) {
+ return false;
+ }
+ if (this.isProgressiveRender()) {
+ return false;
+ }
+ return !(this.context.canvas as any)._drawn;
+ }
+
+ drawOnInteracting() {
+ if (!this._geosToDraw) {
+ return;
+ }
+ this._updateMapStateCache();
+ this._updateDisplayExtent();
+ const map = this.getMap();
+ //refresh geometries on zooming
+ const count = this.layer.getCount();
+ const res = this.mapStateCache.resolution;
+ if (map.isZooming() &&
+ map.options['seamlessZoom'] && this._drawnRes !== undefined && res > this._drawnRes * 1.5 &&
+ this._geosToDraw.length < count || map.isMoving() || map.isInteracting()) {
+ this.prepareToDraw();
+ this._batchConversionMarkers(this.mapStateCache.glRes);
+ if (!this._onlyHasPoint) {
+ this._checkGeos();
+ }
+ this._drawnRes = res;
+ }
+ this._sortByDistanceToCamera(map.cameraPosition);
+ const { collision, collisionDelay } = this.layer.options;
+ if (collision) {
+ const time = now();
+ if (!this._lastCollisionTime) {
+ this._lastCollisionTime = time;
+ }
+ if (time - this._lastCollisionTime <= collisionDelay) {
+ this._geosToDraw = this._lastGeosToDraw || this._geosToDraw;
+ } else {
+ this._collidesGeos();
+ this._lastCollisionTime = time;
+ }
+ }
+
+ for (let i = 0, l = this._geosToDraw.length; i < l; i++) {
+ const geo = this._geosToDraw[i];
+ if (!geo._isCheck) {
+ if (!geo.isVisible()) {
+ delete geo._cPoint;
+ delete geo._inCurrentView;
+ continue;
+ }
+ }
+ geo._paint(this._displayExtent);
+ // https://richardartoul.github.io/jekyll/update/2015/04/26/hidden-classes.html
+ // https://juejin.cn/post/6972702293636415519
+ this._geosToDraw[i]._cPoint = undefined;
+ this._geosToDraw[i]._inCurrentView = undefined;
+ }
+ this.clearImageData();
+ this._lastGeosToDraw = this._geosToDraw;
+ if (isDebug(this.layer)) {
+ console.log('progressiveRender on drawOnInteracting page:', this.page);
+ }
+ }
+
+ /**
+ * Show and render
+ * @override
+ */
+ show(...args: any[]) {
+ this.layer.forEach(function (geo) {
+ geo._repaint();
+ });
+ super.show.apply(this, args);
+ }
+
+ forEachGeo(fn: Function, context?: any) {
+ this.layer.forEach(fn, context);
+ }
+
+ //@internal
+ _checkGeos() {
+ const geos = this._getCurrentNeedRenderGeos();
+ for (let i = 0, len = geos.length; i < len; i++) {
+ this.checkGeo(geos[i]);
+ }
+ return this;
+ }
+
+ drawGeos() {
+ this._drawSnapshot();
+ this._updateMapStateCache();
+ this._drawnRes = this.mapStateCache.resolution;
+ this._updateDisplayExtent();
+ this.prepareToDraw();
+ this._batchConversionMarkers(this.mapStateCache.glRes);
+ if (!this._onlyHasPoint) {
+ this._checkGeos();
+ }
+ this._sortByDistanceToCamera(this.getMap().cameraPosition);
+ this._collidesGeos();
+ for (let i = 0, len = this._geosToDraw.length; i < len; i++) {
+ this._geosToDraw[i]._paint();
+ this._geosToDraw[i]._cPoint = undefined;
+ this._geosToDraw[i]._inCurrentView = undefined;
+ }
+ this.clearImageData();
+ this._lastGeosToDraw = this._geosToDraw;
+ if (isDebug(this.layer)) {
+ console.log('progressiveRender drawGeos page:', this.page);
+ }
+ this._snapshot();
+ this._setDrawGeosDrawTime();
+ }
+
+ prepareToDraw() {
+ this.layer._drawTime = now();
+ this._hasPoint = false;
+ this._geosToDraw = [];
+ return this;
+ }
+
+ //@internal
+ _setDrawGeosDrawTime() {
+ const time = now();
+ const drawTime = this.layer._drawTime;
+ const painterList = this.getGeoPainterList();
+ for (let i = 0, len = painterList.length; i < len; i++) {
+ const painter = painterList[i];
+ if (painter && painter._setDrawTime) {
+ painter._setDrawTime(drawTime);
+ }
+ }
+ if (isDebug(this.layer)) {
+ console.log('_setDrawGeosDrawTime time:', (now() - time) + 'ms');
+ }
+ return this;
+ }
+
+ checkGeo(geo: Geometries) {
+ //点的话已经在批量处理里判断过了
+ if (geo.isPoint && this._onlyHasPoint !== undefined) {
+ if (geo._inCurrentView || geo.hasAltitude()) {
+ this._hasPoint = true;
+ geo._isCheck = true;
+ this._geosToDraw.push(geo);
+ }
+ return;
+ }
+ // LineString ,Polygon,Circle etc
+ geo._isCheck = false;
+ if (!geo || !geo.isVisible() || !geo.getMap() ||
+ !geo.getLayer() || (!geo.getLayer().isCanvasRender())) {
+ return;
+ }
+
+ const painter = geo._getPainter();
+ let inCurrentView = true;
+ if (geo._inCurrentView || !isNil(geo.options.arcDegree) || geo.hasAltitude()) {
+ inCurrentView = true;
+ } else if (geo._inCurrentView === false) {
+ inCurrentView = false;
+ } else {
+ const extent2D = painter.get2DExtent(this.resources, TEMP_EXTENT);
+ if (!extent2D || !extent2D.intersects(this._displayExtent)) {
+ inCurrentView = false;
+ }
+ }
+ if (!inCurrentView) {
+ return;
+ }
+ if (painter.hasPoint()) {
+ this._hasPoint = true;
+ }
+ geo._isCheck = true;
+ this._geosToDraw.push(geo);
+ }
+
+ //@internal
+ _collidesGeos() {
+ const geos = this._geosToDraw;
+ const collision = this.layer.options['collision'];
+ if (!collision) {
+ //reset points _collided
+ for (let i = 0, len = geos.length; i < len; i++) {
+ const geo = geos[i];
+ if (geo.isPoint) {
+ (geo as Marker)._collided = false;
+ }
+ }
+ return this;
+ }
+ const collisionScope = this.layer.options['collisionScope'];
+ const collisionIndex = this.layer.getCollisionIndex();
+ if (collisionScope === 'layer') {
+ collisionIndex.clear();
+ }
+ this._geosToDraw = [];
+ for (let i = 0, len = geos.length; i < len; i++) {
+ const geo = geos[i];
+ if (geo.isPoint) {
+ (geo as Marker)._collided = false;
+ if (this._geoIsCollision(geo, collisionIndex)) {
+ (geo as Marker)._collided = true;
+ continue;
+ }
+ }
+ this._geosToDraw.push(geos[i]);
+ }
+ return this;
+ }
+
+ onZoomEnd(...args: any[]) {
+ delete this.canvasExtent2D;
+ super.onZoomEnd.apply(this, args);
+ }
+
+ onRemove() {
+ this.forEachGeo(function (g) {
+ g.onHide();
+ });
+ delete this._geosToDraw;
+ delete this.snapshotCanvas;
+ delete this.pageGeos;
+ delete this.geoPainterList;
+ }
+
+ onGeometryPropertiesChange(param: any) {
+ if (param) {
+ this.layer._styleGeometry(param['target']);
+ }
+ super.onGeometryPropertiesChange(param);
+ }
+
+ //@internal
+ _updateDisplayExtent() {
+ let extent2D = this.canvasExtent2D;
+ if (this._maskExtent) {
+ if (!this._maskExtent.intersects(extent2D)) {
+ this.completeRender();
+ return;
+ }
+ extent2D = extent2D.intersection(this._maskExtent);
+ }
+ this._displayExtent = extent2D;
+ }
+
+ identifyAtPoint(point: Point, options = {}) {
+ const geometries = this.getGeosForIdentify();
+ if (!geometries) {
+ return [];
+ }
+ return this.layer._hitGeos(geometries, point, options);
+ }
+
+ //@internal
+ _updateMapStateCache() {
+ const map = this.getMap();
+ const offset = map._pointToContainerPoint(this.middleWest)._add(0, -map.height / 2);
+ const resolution = map.getResolution();
+ const pitch = map.getPitch();
+ const bearing = map.getBearing();
+ const glScale = map.getGLScale();
+ const glRes = map.getGLRes();
+ const containerExtent = map.getContainerExtent();
+ const _2DExtent = map.get2DExtent();
+ const glExtent = map.get2DExtentAtRes(glRes);
+ this.mapStateCache = {
+ resolution,
+ pitch,
+ bearing,
+ glScale,
+ glRes,
+ //@internal
+ _2DExtent,
+ glExtent,
+ containerExtent,
+ offset
+ };
+ return this;
+ }
+
+ /**
+ * 使用批量坐标转换提升性能
+ * 优化前 11fps
+ * 优化后 15fps
+ * Better performance of batch coordinate conversion
+ * @param glRes
+ */
+ //@internal
+ _batchConversionMarkers(glRes: number) {
+ this._onlyHasPoint = undefined;
+ if (!this._constructorIsThis()) {
+ return [];
+ }
+ const cPoints = [];
+ const markers = [];
+ const altitudes = [];
+ const altitudeCache = {};
+ const layer = this.layer;
+ const layerOpts = layer.options;
+ const layerAltitude = layer.getAltitude ? layer.getAltitude() : 0;
+ const isCanvasRender = layer.isCanvasRender();
+ this._onlyHasPoint = true;
+ //Traverse all Geo
+ let idx = 0;
+ const geos = this._getCurrentNeedRenderGeos();
+ for (let i = 0, len = geos.length; i < len; i++) {
+ const geo = geos[i];
+ // const type = geo.getType();
+ if (geo.isPoint) {
+ let painter = geo._painter as Painter;
+ if (!painter) {
+ painter = geo._getPainter();
+ }
+ const point = painter.getRenderPoints(PLACEMENT_CENTER)[0][0];
+ const altitude = layerOpts['enableAltitude'] ? geo._getAltitude() : layerAltitude;
+ //减少方法的调用
+ if (altitudeCache[altitude] === undefined) {
+ altitudeCache[altitude] = painter.getAltitude();
+ }
+ cPoints[idx] = point;
+ altitudes[idx] = altitudeCache[altitude];
+ markers[idx] = geo;
+ idx++;
+ } else {
+ this._onlyHasPoint = false;
+ }
+ }
+ if (idx === 0) {
+ return [];
+ }
+ const map = this.getMap();
+ let pts = getPointsResultPts(cPoints, '_pt');
+ pts = map._pointsAtResToContainerPoints(cPoints, glRes, altitudes, pts);
+ const containerExtent = map.getContainerExtent();
+ const { xmax, ymax, xmin, ymin } = containerExtent;
+ const extentCache = {};
+ for (let i = 0, len = markers.length; i < len; i++) {
+ const geo = markers[i];
+ geo._cPoint = pts[i];
+ if (!geo._cPoint) {
+ geo._inCurrentView = false;
+ continue;
+ }
+ const { x, y } = pts[i];
+ //Is the point in view
+ geo._inCurrentView = (x >= xmin && y >= ymin && x <= xmax && y <= ymax) || geo.hasAltitude();
+ //不在视野内的,再用fixedExtent 精确判断下
+ if (!geo._inCurrentView) {
+ const symbolkey = geo.getSymbolHash();
+ let fixedExtent;
+ if (symbolkey) {
+ //相同的symbol 不要重复计算
+ fixedExtent = extentCache[symbolkey] = (extentCache[symbolkey] || geo._painter.getFixedExtent());
+ } else {
+ fixedExtent = geo._painter.getFixedExtent();
+ }
+ TEMP_FIXEDEXTENT.set(fixedExtent.xmin, fixedExtent.ymin, fixedExtent.xmax, fixedExtent.ymax);
+ TEMP_FIXEDEXTENT._add(pts[i]);
+ geo._inCurrentView = TEMP_FIXEDEXTENT.intersects(containerExtent);
+ }
+ if (geo._inCurrentView) {
+ if (!geo.isVisible() || !isCanvasRender) {
+ geo._inCurrentView = false;
+ }
+ //如果当前图层上只有点,整个checkGeo都不用执行了,这里已经把所有的点都判断了
+ if (this._onlyHasPoint && geo._inCurrentView) {
+ this._hasPoint = true;
+ geo._isCheck = true;
+ this._geosToDraw.push(geo);
+ }
+ }
+ }
+ return pts;
+ }
+
+ //@internal
+ _sortByDistanceToCamera(cameraPosition: Vector3) {
+ if (!this.layer.options['sortByDistanceToCamera']) {
+ return;
+ }
+ if (!this._geosToDraw.length) {
+ return;
+ }
+ const map = this.getMap();
+ const p = map.distanceToPoint(1000, 0, map.getGLScale()).x;
+ const meterScale = p / 1000;
+ const placement = 'center';
+ this._geosToDraw.sort((a, b) => {
+ // const type0 = a.getType();
+ // const type1 = b.getType();
+ if (!a.isPoint || !b.isPoint) {
+ return 0;
+ }
+ const painter0 = a._painter;
+ const painter1 = b._painter;
+ if (!painter0 || !painter1) {
+ return 0;
+ }
+ const point0 = painter0.getRenderPoints(placement)[0][0];
+ const point1 = painter1.getRenderPoints(placement)[0][0];
+ const alt0 = painter0.getAltitude() * meterScale;
+ const alt1 = painter1.getAltitude() * meterScale;
+ vec3.set(TEMP_VEC3, point0.x, point0.y, alt0);
+ const dist0 = vec3.distance(TEMP_VEC3, cameraPosition);
+ vec3.set(TEMP_VEC3, point1.x, point1.y, alt1);
+ const dist1 = vec3.distance(TEMP_VEC3, cameraPosition);
+ return dist1 - dist0;
+ });
+ }
+
+ //@internal
+ _constructorIsThis(): boolean {
+ return this.constructor === VectorLayerRenderer;
+ }
+
+ isProgressiveRender(): boolean {
+ const layer = this.layer;
+ if (!layer) {
+ return false;
+ }
+ const { progressiveRender, collision } = layer.options || {};
+ if (collision) {
+ return false;
+ }
+ return progressiveRender;
+ }
+
+ getGeosForIdentify(): Geometries[] {
+ if (!this.isProgressiveRender()) {
+ return this._geosToDraw || [];
+ }
+ return this.pageGeos || [];
+ }
+
+ getGeoPainterList(): (Painter | CollectionPainter)[] {
+ if (!this.isProgressiveRender()) {
+ const list = [];
+ const geos = this._geosToDraw || [];
+ for (let i = 0, len = geos.length; i < len; i++) {
+ list.push(geos[i]._painter);
+ }
+ return list;
+ }
+ return this.geoPainterList || [];
+ }
+
+ //@internal
+ _checkSnapshotCanvas() {
+ if (!this.isProgressiveRender()) {
+ delete this.snapshotCanvas;
+ return null;
+ }
+ const canvas = this.canvas;
+ if (!canvas) {
+ delete this.snapshotCanvas;
+ return null;
+ }
+ if (!this.snapshotCanvas) {
+ this.snapshotCanvas = Canvas.createCanvas(1, 1);
+ }
+ const snapshotCanvas = this.snapshotCanvas;
+ const { width, height, style } = canvas;
+ if (snapshotCanvas.width !== width || snapshotCanvas.height !== height) {
+ snapshotCanvas.width = width;
+ snapshotCanvas.height = height;
+ }
+ if (snapshotCanvas.style.width !== style.width || snapshotCanvas.style.height !== style.height) {
+ snapshotCanvas.style.width = style.width;
+ snapshotCanvas.style.height = style.height;
+ }
+
+ return snapshotCanvas;
+
+ }
+
+ //@internal
+ _getCurrentNeedRenderGeos(): Geometries[] {
+ const geos = this.layer._geoList || [];
+ if (!this.isProgressiveRender()) {
+ return geos;
+ }
+ // if (this.renderEnd) {
+ // return [];
+ // }
+ const layer = this.layer;
+ const { progressiveRenderCount } = layer.options;
+ const pageSize = progressiveRenderCount;
+ const page = this.page;
+ const start = (page - 1) * pageSize, end = page * pageSize;
+ const pageGeos = geos.slice(start, end);
+ return pageGeos;
+ }
+
+ //@internal
+ _resetProgressiveRender() {
+ if (isDebug(this.layer)) {
+ console.log('progressiveRender resetProgressiveRender');
+ }
+ this.renderEnd = false;
+ this.page = 1;
+ this.pageGeos = [];
+ this.geoPainterList = [];
+ this.maxTolerance = 0;
+ this._clearSnapshotCanvas();
+ }
+
+ //@internal
+ _clearSnapshotCanvas() {
+ const snapshotCanvas = this._checkSnapshotCanvas();
+ if (snapshotCanvas) {
+ clearCanvas(snapshotCanvas);
+ }
+ }
+
+ //@internal
+ _snapshot() {
+ const progressiveRender = this.isProgressiveRender();
+ const geosToDraw = this._geosToDraw || [];
+ for (let i = 0, len = geosToDraw.length; i < len; i++) {
+ const geo = geosToDraw[i];
+ const t = geo._hitTestTolerance() || 0;
+ this.maxTolerance = Math.max(this.maxTolerance, t);
+ if (progressiveRender) {
+ this.pageGeos.push(geo);
+ const painter = geo._painter;
+ this.geoPainterList.push(painter);
+ }
+ }
+ if (!progressiveRender) {
+ return this;
+ }
+ const time = now();
+ const snapshotCanvas = this._checkSnapshotCanvas();
+ if (snapshotCanvas && this.canvas) {
+ const ctx = clearCanvas(snapshotCanvas);
+ ctx.drawImage(this.canvas, 0, 0);
+ }
+ const layer = this.layer;
+ const { progressiveRenderCount } = layer.options;
+ const geos = layer._geoList || [];
+ const pages = Math.ceil(geos.length / progressiveRenderCount);
+ this.renderEnd = this.page >= pages;
+ if (this.renderEnd) {
+ this._setDrawGeosDrawTime();
+ }
+ if (isDebug(this.layer)) {
+ console.log('snapshot time:', (now() - time) + 'ms');
+ }
+ if (!this.renderEnd) {
+ this.page++;
+ }
+ return this;
+ }
+
+ //@internal
+ _drawSnapshot() {
+ if (!this.isProgressiveRender()) {
+ return this;
+ }
+ const { snapshotCanvas, context } = this;
+ if (!snapshotCanvas || !context) {
+ return this;
+ }
+ const map = this.getMap();
+ if (!map) {
+ return this;
+ }
+ const dpr = map.getDevicePixelRatio() || 1;
+ const rScale = 1 / dpr;
+ this._canvasContextScale(context, rScale);
+ context.drawImage(snapshotCanvas, 0, 0);
+ this._canvasContextScale(context, dpr);
+ return this;
+ }
+}
+
+VectorLayer.registerRenderer('canvas', VectorLayerRenderer);
+
+type GeoType = any;
+
+export default VectorLayerRenderer;
diff --git a/packages/map/src/renderer/layer/vectorlayer/index.ts b/packages/map/src/renderer/layer/vectorlayer/index.ts
index f3e431843e..e9f490a8ef 100644
--- a/packages/map/src/renderer/layer/vectorlayer/index.ts
+++ b/packages/map/src/renderer/layer/vectorlayer/index.ts
@@ -1,5 +1,8 @@
-import OverlayLayerCanvasRenderer from './OverlayLayerCanvasRenderer';
+import { OverlayLayerCanvasRenderer, OverlayLayerGLRenderer } from './OverlayLayerCanvasRenderer';
+import VectorLayerCanvasRenderer from './VectorLayerCanvasRenderer';
export {
- OverlayLayerCanvasRenderer
+ OverlayLayerCanvasRenderer,
+ OverlayLayerGLRenderer,
+ VectorLayerCanvasRenderer
};
diff --git a/packages/map/src/renderer/map/MapCanvasRenderer.ts b/packages/map/src/renderer/map/MapCanvasRenderer.ts
index 6a1e9e00d6..2f5c13b450 100644
--- a/packages/map/src/renderer/map/MapCanvasRenderer.ts
+++ b/packages/map/src/renderer/map/MapCanvasRenderer.ts
@@ -1,8 +1,9 @@
-import { IS_NODE, requestAnimFrame, cancelAnimFrame, equalMapView, calCanvasSize } from '../../core/util';
-import { createEl, preventSelection, computeDomPosition, removeDomNode } from '../../core/util/dom';
+import { IS_NODE, isNumber, isFunction, requestAnimFrame, cancelAnimFrame, equalMapView, calCanvasSize, pushIn } from '../../core/util';
+import { createEl, preventSelection, computeDomPosition } from '../../core/util/dom';
import { GlobalEvent, EVENT_DOC_DRAGEND, EVENT_DOC_VISIBILITY_CHANGE, EVENT_DPR_CHANGE, EVENT_DOC_DRAGSTART } from './../../core/GlobalEvent';
import Browser from '../../core/Browser';
import Point from '../../geo/Point';
+import Canvas2D from '../../core/Canvas';
import MapRenderer from './MapRenderer';
import Map, { type PanelDom } from '../../map/Map';
import CollisionIndex from '../../core/CollisionIndex';
@@ -39,6 +40,10 @@ class MapCanvasRenderer extends MapRenderer {
//@internal
_resizeEventList: ResizeObserverEntry[];
+ //@internal
+ _needClear: boolean;
+ //@internal
+ _canvasUpdated: boolean;
//@internal
_isViewChanged: WithUndef;
//@internal
@@ -70,7 +75,7 @@ class MapCanvasRenderer extends MapRenderer {
//@internal
_tops: (EditHandle | EditOutline)[];
- context: any;
+ context: CanvasRenderingContext2D;
canvas: HTMLCanvasElement;
topLayer: HTMLCanvasElement;
topCtx: CanvasRenderingContext2D;
@@ -105,7 +110,7 @@ class MapCanvasRenderer extends MapRenderer {
this._handleResizeEventList(framestamp);
//not render anything when map container is hide
if (map.options['stopRenderOnOffscreen'] && this._containerIsOffscreen()) {
- return false;
+ return true;
}
this._updateDomPosition(framestamp);
delete this._isViewChanged;
@@ -113,12 +118,18 @@ class MapCanvasRenderer extends MapRenderer {
this.updateMapDOM();
map.clearCollisionIndex();
const layers = this._getAllLayerToRender();
- const updated = this.drawLayers(layers, framestamp);
- // const updated = this.drawLayerCanvas(layers);
+ this.drawLayers(layers, framestamp);
+ const updated = this.drawLayerCanvas(layers);
if (updated) {
// when updated is false, should escape drawing tops and centerCross to keep handle's alpha
this.drawTops();
+ this._drawCenterCross();
+ if (map.options['debugSky']) {
+ this._debugSky();
+ }
}
+ this._needClear = false;
+ // this._drawContainerExtent();
// CAUTION: the order to fire frameend and layerload events
// fire frameend before layerload, reason:
// 1. frameend is often used internally by maptalks and plugins
@@ -129,8 +140,9 @@ class MapCanvasRenderer extends MapRenderer {
// It must be before events and frame callback, because map state may be changed in callbacks.
this._mapview = this._getMapView();
delete this._spatialRefChanged;
- this._fireLayerLoadEvents(layers);
+ this._fireLayerLoadEvents();
this.executeFrameCallbacks();
+ this._canvasUpdated = false;
//loop ui Collides
map.uiCollides();
return true;
@@ -156,42 +168,97 @@ class MapCanvasRenderer extends MapRenderer {
//need redraw all layer,cause by collision/crs change/view change etc...
//@internal
- _checkIfNeedToRedrawLayers(layers: Layer[]) {
+ _needRedrawAllLayers(layers: Layer[]) {
if (this.isSpatialReferenceChanged()) {
return true;
}
- for (let i = 0, len = layers.length; i < len; i++) {
- if (this._checkLayerRedraw(layers[i])) {
+ const needRedrawLayers: Layer[] = [];
+ layers.forEach(layer => {
+ if (!layer) {
+ return;
+ }
+ //always check layer need to redraw
+ const needsRedraw = layer._toRedraw = this._checkLayerRedraw(layer);
+ if (needsRedraw) {
+ needRedrawLayers.push(layer);
+ const childLayers = layer.getLayers && layer.getLayers();
+ if (childLayers && Array.isArray(childLayers)) {
+ pushIn(needRedrawLayers, childLayers);
+ }
+ }
+ });
+ for (let i = 0, len = needRedrawLayers.length; i < len; i++) {
+ const layer = needRedrawLayers[i];
+ const layerOptions = layer && layer.options;
+ if (layerOptions && layerOptions.collision && layerOptions.collisionScope === 'map') {
return true;
}
+ //other condition if need
}
return false;
}
drawLayers(layers: Layer[], framestamp: number) {
- const needRedraw = this._checkIfNeedToRedrawLayers(layers);
- if (!needRedraw && !this.map.options['forceRedrawPerFrame']) {
- return false;
- }
- this.clearCanvas();
+ const needRedrawAllLayers = this._needRedrawAllLayers(layers);
+
const map = this.map,
isInteracting = map.isInteracting(),
+ // all the visible canvas layers' ids.
+ canvasIds: string[] = [],
+ // all the updated canvas layers's ids.
+ updatedIds: string[] = [],
+ fps = map.options['fpsOnInteracting'] || 0,
+ timeLimit = fps === 0 ? 0 : 1000 / fps,
+ // time of layer drawing
+ layerLimit = this.map.options['layerCanvasLimitOnInteracting'],
l = layers.length;
+ const baseLayer = map.getBaseLayer();
+ let t = 0;
for (let i = 0; i < l; i++) {
const layer = layers[i];
if (!layer.isVisible()) {
continue;
}
const isCanvas = layer.isCanvasRender();
+ if (isCanvas) {
+ canvasIds.push(layer.getId());
+ }
const renderer = layer._getRenderer();
if (!renderer) {
continue;
}
- if (isCanvas) {
- this.clearLayerCanvasContext(layer);
+ // if need to call layer's draw/drawInteracting
+ const needsRedraw = needRedrawAllLayers || layer._toRedraw;
+ if (isCanvas && renderer.isCanvasUpdated()) {
+ // don't need to call layer's draw/drawOnInteracting but need to redraw layer's updated canvas
+ if (!needsRedraw) {
+ updatedIds.push(layer.getId());
+ }
+ this.setLayerCanvasUpdated();
}
+ const transformMatrix = renderer.__zoomTransformMatrix;
+ delete renderer.__zoomTransformMatrix;
+ if (!needsRedraw) {
+ if (isCanvas && isInteracting) {
+ if (map.isZooming() && !map.getPitch()) {
+ // transform layer's current canvas when zooming
+ renderer.prepareRender();
+ renderer.__zoomTransformMatrix = this._zoomMatrix;
+ } else if (map.getPitch() || map.isRotating()) {
+ // when map is pitching or rotating, clear the layer canvas
+ // otherwise, leave layer's canvas unchanged
+ renderer.clearCanvas();
+ }
+ }
+ continue;
+ }
+
if (isInteracting && isCanvas) {
- this._drawCanvasLayerOnInteracting(layer, framestamp);
+ if (layerLimit > 0 && l - 1 - i > layerLimit && layer !== baseLayer) {
+ layer._getRenderer().clearCanvas();
+ continue;
+ }
+ t += this._drawCanvasLayerOnInteracting(layer, t, timeLimit, framestamp);
} else if (isInteracting && renderer.drawOnInteracting) {
// dom layers
if (renderer.prepareRender) {
@@ -206,9 +273,32 @@ class MapCanvasRenderer extends MapRenderer {
} else {
// map is not interacting, call layer's render
renderer.render(framestamp);
+ //地图缩放完以后,如果下一次render需要载入资源,仍需要设置transformMatrix
+ //防止在资源载入完成之前,缺少transformMatrix导致的绘制错误
+ if (isCanvas && transformMatrix && renderer.isLoadingResource()) {
+ renderer.__zoomTransformMatrix = transformMatrix;
+ }
+ }
+
+ if (isCanvas) {
+ updatedIds.push(layer.getId());
+ this.setLayerCanvasUpdated();
+ }
+ }
+ // compare:
+ // 1. previous drawn layers and current drawn layers
+ // 2. previous canvas layers and current canvas layers
+ // set map to redraw if either changed
+ const preCanvasIds = this._canvasIds || [];
+ const preUpdatedIds = this._updatedIds || [];
+ this._canvasIds = canvasIds;
+ this._updatedIds = updatedIds;
+ if (!this.isLayerCanvasUpdated()) {
+ const sep = '---';
+ if (preCanvasIds.join(sep) !== canvasIds.join(sep) || preUpdatedIds.join(sep) !== updatedIds.join(sep)) {
+ this.setLayerCanvasUpdated();
}
}
- return true;
}
/**
@@ -217,6 +307,9 @@ class MapCanvasRenderer extends MapRenderer {
*/
//@internal
_checkLayerRedraw(layer: Layer): boolean {
+ if (this.isSpatialReferenceChanged()) {
+ return true;
+ }
const map = this.map;
const renderer = layer._getRenderer();
if (!renderer) {
@@ -243,23 +336,47 @@ class MapCanvasRenderer extends MapRenderer {
* @private
*/
//@internal
- _drawCanvasLayerOnInteracting(layer: Layer, framestamp: number): number {
- const renderer = layer._getRenderer();
+ _drawCanvasLayerOnInteracting(layer: Layer, t: number, timeLimit: number, framestamp: number): number {
+ const map = this.map,
+ renderer = layer._getRenderer(),
+ drawTime = renderer.getDrawTime(),
+ inTime = timeLimit === 0 || timeLimit > 0 && t + drawTime <= timeLimit;
if (renderer.mustRenderOnInteracting && renderer.mustRenderOnInteracting()) {
renderer.render(framestamp);
- } else if (renderer.drawOnInteracting) {
+ } else if (renderer.drawOnInteracting &&
+ (layer === map.getBaseLayer() || inTime ||
+ map.isZooming() && layer.options['forceRenderOnZooming'] ||
+ map.isMoving() && layer.options['forceRenderOnMoving'] ||
+ map.isRotating() && layer.options['forceRenderOnRotating'])
+ ) {
// call drawOnInteracting to redraw the layer
renderer.prepareRender();
- const canvas = renderer.prepareCanvas();
+ renderer.prepareCanvas();
if (renderer.checkAndDraw) {
// for canvas renderers
renderer.checkAndDraw(renderer.drawOnInteracting, this._eventParam, framestamp);
} else {
renderer.drawOnInteracting(this._eventParam, framestamp);
}
- return canvas;
+ return drawTime;
+ } else if (map.isZooming() && !map.getPitch() && !map.isRotating()) {
+ // when:
+ // 1. layer's renderer doesn't have drawOnInteracting
+ // 2. timeLimit is exceeded
+ // then:
+ // transform layer's current canvas when zooming
+ renderer.prepareRender();
+ renderer.__zoomTransformMatrix = this._zoomMatrix;
+ } else if (map.getPitch() || map.isRotating()) {
+ // when map is pitching or rotating, clear the layer canvas
+ // otherwise, leave layer's canvas unchanged
+ renderer.clearCanvas();
}
- return null;
+ if (renderer.drawOnInteracting && !inTime) {
+ // @ts-expect-error 我也不知道怎么办,不敢乱动,可能插件里需要?
+ renderer.onSkipDrawOnInteracting(this._eventParam, framestamp);
+ }
+ return 0;
}
/**
@@ -268,29 +385,127 @@ class MapCanvasRenderer extends MapRenderer {
* @private
*/
//@internal
- _fireLayerLoadEvents(layers: Layer[]) {
- //firing order as FIFO, painting as FILO, so the order needs to be reversed
- for (let i = layers.length - 1; i >= 0; i--) {
- const layer = layers[i];
- const renderer = layer._getRenderer();
- if (!renderer || !renderer.isRenderComplete()) {
+ _fireLayerLoadEvents() {
+ if (this._updatedIds && this._updatedIds.length > 0) {
+ const map = this.map;
+ //firing order as FIFO, painting as FILO, so the order needs to be reversed
+ this._updatedIds.reverse().forEach(id => {
+ const layer = map.getLayer(id);
+ if (!layer) {
+ return;
+ }
+ const renderer = layer._getRenderer();
+ if (!renderer || !renderer.isRenderComplete()) {
+ return;
+ }
+ /**
+ * layerload event, fired when layer is loaded.
+ *
+ * @event Layer#layerload
+ * @type {Object}
+ * @property {String} type - layerload
+ * @property {Layer} target - layer
+ */
+ layer.fire('layerload');
+ });
+ }
+
+ }
+
+ isLayerCanvasUpdated() {
+ return this._canvasUpdated;
+ }
+
+ setLayerCanvasUpdated() {
+ this._canvasUpdated = true;
+ }
+
+ /**
+ * Renders the layers
+ */
+ drawLayerCanvas(layers: Layer[]) {
+ const map = this.map;
+ if (!map) {
+ return false;
+ }
+ if (!this.isLayerCanvasUpdated() && !this.isViewChanged() && this._needClear === false) {
+ return false;
+ }
+ if (!this.canvas) {
+ this.createCanvas();
+ }
+
+ /**
+ * renderstart event, an event fired when map starts to render.
+ * @event Map#renderstart
+ * @type {Object}
+ * @property {String} type - renderstart
+ * @property {Map} target - the map fires event
+ * @property {CanvasRenderingContext2D} context - canvas context
+ */
+ map._fireEvent('renderstart', {
+ 'context': this.context
+ });
+
+ if (!this._updateCanvasSize()) {
+ this.clearCanvas();
+ }
+
+ const interacting = map.isInteracting(),
+ limit = map.options['layerCanvasLimitOnInteracting'];
+ let len = layers.length;
+
+ let baseLayerImage;
+ const images = [];
+ for (let i = 0; i < len; i++) {
+ if (!layers[i].isVisible() || !layers[i].isCanvasRender()) {
continue;
}
- /**
- * layerload event, fired when layer is loaded.
- *
- * @event Layer#layerload
- * @type {Object}
- * @property {String} type - layerload
- * @property {Layer} target - layer
- */
- layer.fire('layerload');
+ const renderer = layers[i]._getRenderer();
+ if (!renderer) {
+ continue;
+ }
+ const layerImage = this._getLayerImage(layers[i]);
+ if (layerImage && layerImage['image']) {
+ if (layers[i] === map.getBaseLayer()) {
+ baseLayerImage = [layers[i], layerImage];
+ } else {
+ images.push([layers[i], layerImage]);
+ }
+ }
+ }
+
+ const targetWidth = this.canvas.width;
+ const targetHeight = this.canvas.height;
+ if (baseLayerImage) {
+ this._drawLayerCanvasImage(baseLayerImage[0], baseLayerImage[1], targetWidth, targetHeight);
+ this._drawFog();
+ }
+
+ len = images.length;
+ const start = interacting && limit >= 0 && len > limit ? len - limit : 0;
+ for (let i = start; i < len; i++) {
+ this._drawLayerCanvasImage(images[i][0], images[i][1], targetWidth, targetHeight);
}
+
+ /**
+ * renderend event, an event fired when map ends rendering.
+ * @event Map#renderend
+ * @type {Object}
+ * @property {String} type - renderend
+ * @property {Map} target - the map fires event
+ * @property {CanvasRenderingContext2D} context - canvas context
+ */
+ map._fireEvent('renderend', {
+ 'context': this.context
+ });
+ return true;
}
setToRedraw() {
const layers = this._getAllLayerToRender();
//set maprender for clear canvas
+ this._needClear = true;
for (let i = 0, l = layers.length; i < l; i++) {
const renderer = layers[i].getRenderer();
if (renderer && renderer.canvas && renderer.setToRedraw) {
@@ -348,6 +563,7 @@ class MapCanvasRenderer extends MapRenderer {
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
+ delete this.context;
delete this.canvas;
delete this.map;
delete this._spatialRefChanged;
@@ -398,6 +614,15 @@ class MapCanvasRenderer extends MapRenderer {
map._trySetCursor(cursor);
}
+ //@internal
+ _getLayerImage(layer: Layer) {
+ const renderer = layer._getRenderer();
+ if (renderer.getCanvasImage) {
+ return renderer.getCanvasImage();
+ }
+ return null;
+ }
+
/**
* initialize container DOM of panels
*/
@@ -546,16 +771,177 @@ class MapCanvasRenderer extends MapRenderer {
}
}
+ //@internal
+ _drawLayerCanvasImage(layer: Layer, layerImage: any, targetWidth?: number, targetHeight?: number) {
+ const ctx = this.context;
+ const point = layerImage['point'].round();
+ const dpr = this.map.getDevicePixelRatio();
+ if (dpr !== 1) {
+ point._multi(dpr);
+ }
+ const canvasImage = layerImage['image'];
+ const width = canvasImage.width, height = canvasImage.height;
+ if (point.x + width <= 0 || point.y + height <= 0) {
+ return;
+ }
+ //opacity of the layer image
+ let op = layer.options['opacity'];
+ if (!isNumber(op)) {
+ op = 1;
+ }
+ if (op <= 0) {
+ return;
+ }
+ let imgOp = layerImage['opacity'];
+ if (!isNumber(imgOp)) {
+ imgOp = 1;
+ }
+ if (imgOp <= 0) {
+ return;
+ }
+ const alpha = ctx.globalAlpha;
+
+ if (op < 1) {
+ ctx.globalAlpha *= op;
+ }
+ if (imgOp < 1) {
+ ctx.globalAlpha *= imgOp;
+ }
+ if (layer.options['cssFilter']) {
+ ctx.filter = layer.options['cssFilter'];
+ }
+ const renderer = layer.getRenderer();
+ const matrix = renderer.__zoomTransformMatrix;
+ const clipped = renderer.clipCanvas(this.context as any);
+ if (matrix) {
+ ctx.save();
+ ctx.setTransform(...(matrix as any));
+ }
+
+ /*let outlineColor = layer.options['debugOutline'];
+ if (outlineColor) {
+ if (outlineColor === true) {
+ outlineColor = '#0f0';
+ }
+ this.context.strokeStyle = outlineColor;
+ this.context.fillStyle = outlineColor;
+ this.context.lineWidth = 10;
+ Canvas2D.rectangle(ctx, point, layerImage.size, 1, 0);
+ ctx.fillText([layer.getId(), point.toArray().join(), layerImage.size.toArray().join(), canvasImage.width + ',' + canvasImage.height].join(' '),
+ point.x + 18, point.y + 18);
+ }*/
+
+ ctx.drawImage(canvasImage, 0, 0, width, height, point.x, point.y, targetWidth, targetHeight);
+ if (matrix) {
+ ctx.restore();
+ }
+ if (clipped) {
+ ctx.restore();
+ }
+ if (ctx.filter !== 'none') {
+ ctx.filter = 'none';
+ }
+ ctx.globalAlpha = alpha;
+ }
+
+ //@internal
+ _drawCenterCross() {
+ const cross = this.map.options['centerCross'];
+ if (cross) {
+ const ctx = this.context;
+ const p = new Point(this.canvas.width / 2, this.canvas.height / 2);
+ if (isFunction(cross)) {
+ cross(ctx, p);
+ } else {
+ Canvas2D.drawCross(this.context, p.x, p.y, 2, '#f00');
+ }
+ }
+ }
+
+ //@internal
+ _drawContainerExtent() {
+ const { cascadePitches } = this.map.options;
+ const h30 = this.map.height - this.map._getVisualHeight(cascadePitches[0]);
+ const h60 = this.map.height - this.map._getVisualHeight(cascadePitches[1]);
+
+ const extent = this.map.getContainerExtent();
+ const ctx = this.context;
+ ctx.beginPath();
+ ctx.moveTo(0, extent.ymin);
+ ctx.lineTo(extent.xmax, extent.ymin);
+ ctx.stroke();
+
+
+ ctx.beginPath();
+ ctx.moveTo(0, h30);
+ ctx.lineTo(extent.xmax, h30);
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(0, h60);
+ ctx.lineTo(extent.xmax, h60);
+ ctx.stroke();
+ // console.log(extent.ymin, h30, h60);
+ }
+
+ //@internal
+ _drawFog() {
+ const map = this.map;
+ if (map.getPitch() <= map.options['maxVisualPitch'] || !map.options['fog']) {
+ return;
+ }
+ const fogThickness = 30;
+ const r = map.getDevicePixelRatio();
+ const ctx = this.context,
+ clipExtent = map.getContainerExtent();
+ let top = (map.height - map._getVisualHeight(75)) * r;
+ if (top < 0) top = 0;
+ const bottom = clipExtent.ymin * r,
+ h = Math.ceil(bottom - top),
+ color = map.options['fogColor'].join();
+ const gradient = ctx.createLinearGradient(0, top, 0, bottom + fogThickness);
+ const landscape = 1 - fogThickness / (h + fogThickness);
+ gradient.addColorStop(0, `rgba(${color}, 0)`);
+ gradient.addColorStop(0.3, `rgba(${color}, 0.3)`);
+ gradient.addColorStop(landscape, `rgba(${color}, 1)`);
+ gradient.addColorStop(1, `rgba(${color}, 0)`);
+ ctx.beginPath();
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, top, Math.ceil(clipExtent.getWidth()) * r, Math.ceil(h + fogThickness));
+ }
+
+ //@internal
+ _debugSky() {
+ const map = this.map;
+ if (!map) {
+ return this;
+ }
+ const height = map.getContainerExtent().ymin;
+ if (height <= 0) {
+ return this;
+ }
+ const ctx = this.context;
+ ctx.strokeStyle = 'red';
+ ctx.strokeRect(0, 0, map.width, height);
+ return this;
+ }
+
//@internal
_getAllLayerToRender() {
return this.map._getLayers();
}
+ clearCanvas() {
+ if (!this.canvas) {
+ return;
+ }
+ Canvas2D.clearRect(this.context, 0, 0, this.canvas.width, this.canvas.height);
+ }
//@internal
_updateCanvasSize() {
if (!this.canvas || this._containerIsCanvas) {
- return;
+ return false;
}
const map = this.map,
mapSize = map.getSize(),
@@ -567,53 +953,29 @@ class MapCanvasRenderer extends MapRenderer {
canvas.style.width = cssWidth;
canvas.style.height = cssHeight;
}
- if (width !== canvas.width || height !== canvas.height) {
- canvas.width = width;
- canvas.height = height;
- }
- const topLayer = this.topLayer;
- if (topLayer && (width !== topLayer.width || height !== topLayer.height)) {
- topLayer.width = width;
- topLayer.height = height;
- topLayer.style.width = cssWidth;
- topLayer.style.height = cssHeight;
+ if (width === canvas.width && height === canvas.height) {
+ return false;
}
- }
-
- createCanvas() {
- this.canvas = createEl('canvas') as HTMLCanvasElement;
- const panels = this.map.getPanels();
- const canvasContainer = panels.canvasContainer;
- canvasContainer.appendChild(this.canvas);
- this._updateCanvasSize();
- this.createContext();
- }
-
- createContext() {
- // should be implemented by child class
- }
-
- clearLayerCanvasContext(_layer) {
- // should be implemented by child class
- }
+ //retina屏支持
- clearCanvas() {
- // should be implemented by child class
+ canvas.height = height;
+ canvas.width = width;
+ this.topLayer.width = canvas.width;
+ this.topLayer.height = canvas.height;
+ return true;
}
- // canvas for tops
- createTopCanvas() {
+ createCanvas() {
this.topLayer = createEl('canvas') as HTMLCanvasElement;
- const panels = this.map.getPanels();
- const canvasContainer = panels.canvasContainer;
- canvasContainer.insertBefore(this.topLayer, this.canvas);
this.topCtx = this.topLayer.getContext('2d');
- }
-
- removeTopCanvas() {
- removeDomNode(this.topLayer);
- delete this.topLayer;
- delete this.topCtx;
+ if (this._containerIsCanvas) {
+ this.canvas = this.map.getContainer() as HTMLCanvasElement;
+ } else {
+ this.canvas = createEl('canvas') as HTMLCanvasElement;
+ this._updateCanvasSize();
+ this.map.getPanels().canvasContainer.appendChild(this.canvas);
+ }
+ this.context = this.canvas.getContext('2d');
}
//@internal
@@ -789,24 +1151,13 @@ class MapCanvasRenderer extends MapRenderer {
}
drawTops() {
- const tops = this.getTopElements();
- if (tops.length) {
- if (!this.topCtx) {
- this.createTopCanvas();
- }
- } else {
- if (this.topCtx) {
- this.removeTopCanvas();
- }
- return;
- }
// clear topLayer
this.topCtx.clearRect(0, 0, this.topLayer.width, this.topLayer.height);
const collisionIndex = tempCollisionIndex;
collisionIndex.clear();
this.map.fire('drawtopstart');
this.map.fire('drawtops');
-
+ const tops = this.getTopElements();
let updated = false;
const dpr = this.map.getDevicePixelRatio();
const geos = [];
@@ -838,6 +1189,13 @@ class MapCanvasRenderer extends MapRenderer {
}
}
+Map.registerRenderer('canvas', MapCanvasRenderer);
+
+Map.mergeOptions({
+ 'fog': false,
+ 'fogColor': [233, 233, 233]
+});
+
export type MapView = {
x: number;
y: number;
@@ -849,5 +1207,3 @@ export type MapView = {
}
export default MapCanvasRenderer;
-
-
diff --git a/packages/map/src/renderer/map/MapGLAbstractRenderer.ts b/packages/map/src/renderer/map/MapGLAbstractRenderer.ts
new file mode 100644
index 0000000000..ca5eb2af50
--- /dev/null
+++ b/packages/map/src/renderer/map/MapGLAbstractRenderer.ts
@@ -0,0 +1,853 @@
+import { IS_NODE, requestAnimFrame, cancelAnimFrame, equalMapView, calCanvasSize } from '../../core/util';
+import { createEl, preventSelection, computeDomPosition, removeDomNode } from '../../core/util/dom';
+import { GlobalEvent, EVENT_DOC_DRAGEND, EVENT_DOC_VISIBILITY_CHANGE, EVENT_DPR_CHANGE, EVENT_DOC_DRAGSTART } from './../../core/GlobalEvent';
+import Browser from '../../core/Browser';
+import Point from '../../geo/Point';
+import MapRenderer from './MapRenderer';
+import Map, { type PanelDom } from '../../map/Map';
+import CollisionIndex from '../../core/CollisionIndex';
+import GlobalConfig from '../../GlobalConfig';
+import type EditHandle from '../edit/EditHandle';
+import type EditOutline from '../edit/EditOutline';
+import type { Layer } from '../../layer';
+import type Size from '../../geo/Size';
+import type { WithUndef } from '../../types/typings';
+
+const tempCollisionIndex = new CollisionIndex();
+
+/**
+ * 基于 Canvas2D 的 map 渲染器
+ *
+ * @english
+ * Renderer class based on HTML5 Canvas for maps.
+ * @class
+ * @protected
+ * @extends {renderer.MapRenderer}
+ * @memberOf renderer
+ */
+class MapGLAbstractRenderer extends MapRenderer {
+ //@internal
+ _containerIsCanvas: boolean;
+ //@internal
+ _loopTime: number;
+ //@internal
+ _resizeTime: number;
+ //@internal
+ _resizeCount: number;
+ //@internal
+ _frameCycleRenderCount: number;
+ //@internal
+ _resizeEventList: ResizeObserverEntry[];
+
+ //@internal
+ _isViewChanged: WithUndef;
+ //@internal
+ _spatialRefChanged: WithUndef;
+ //@internal
+ _resizeObserver: ResizeObserver;
+ //@internal
+ _resizeInterval: number;
+ //@internal
+ _checkSizeInterval: number;
+ //@internal
+ _hitDetectFrame: number;
+ //@internal
+ _animationFrame: number;
+ //@internal
+ _mapview: MapView;
+ //@internal
+ _zoomMatrix: number[];
+ //@internal
+ _eventParam: any;
+ //@internal
+ _canvasIds: string[];
+ //@internal
+ _updatedIds: string[];
+ //@internal
+ _frameTimestamp: number;
+ //@internal
+ _checkPositionTime: number;
+ //@internal
+ _tops: (EditHandle | EditOutline)[];
+
+ context: any;
+ canvas: HTMLCanvasElement;
+ topLayer: HTMLCanvasElement;
+ topCtx: CanvasRenderingContext2D;
+
+ /**
+ * @param map - map for the renderer
+ */
+ constructor(map: Map) {
+ super(map);
+ //container is a