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 element + this._containerIsCanvas = !!(map.getContainer() as HTMLCanvasElement).getContext; + this._registerEvents(); + this._loopTime = 0; + this._resizeEventList = []; + this._resizeTime = -Infinity; + this._frameCycleRenderCount = 0; + } + + load() { + this.initContainer(); + } + + /** + * render layers in current frame + * @returns return false to cease frame loop + */ + renderFrame(framestamp: number): boolean { + const map = this.map; + if (!map || !map.options['renderable']) { + return false; + } + this._handleResizeEventList(framestamp); + //not render anything when map container is hide + if (map.options['stopRenderOnOffscreen'] && this._containerIsOffscreen()) { + return false; + } + this._updateDomPosition(framestamp); + delete this._isViewChanged; + map._fireEvent('framestart'); + this.updateMapDOM(); + map.clearCollisionIndex(); + const layers = this._getAllLayerToRender(); + const updated = 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(); + } + // CAUTION: the order to fire frameend and layerload events + // fire frameend before layerload, reason: + // 1. frameend is often used internally by maptalks and plugins + // 2. layerload is often used externally by tests or user apps + map._fireEvent('frameend'); + this._recordView(); + // refresh map's state + // 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.executeFrameCallbacks(); + //loop ui Collides + map.uiCollides(); + return true; + } + + getFrameTimestamp() { + return this._frameTimestamp || 0; + } + + updateMapDOM() { + const map = this.map; + // when map is zooming, container is being transformed with matrix, panel doesn't need to be moved. + if (map.isZooming()) { + return; + } + const offset = map.getViewPointFrameOffset(); + if (offset) { + map.offsetPlatform(offset); + } else if (this.domChanged()) { + this.offsetPlatform(null, true); + } + } + + //need redraw all layer,cause by collision/crs change/view change etc... + //@internal + _checkIfNeedToRedrawLayers(layers: Layer[]) { + if (this.isSpatialReferenceChanged()) { + return true; + } + for (let i = 0, len = layers.length; i < len; i++) { + if (this._checkLayerRedraw(layers[i])) { + return true; + } + } + return false; + } + + drawLayers(layers: Layer[], framestamp: number) { + const needRedraw = this._checkIfNeedToRedrawLayers(layers); + if (!needRedraw && !this.map.options['forceRedrawPerFrame']) { + return false; + } + this.clearCanvas(); + const map = this.map, + isInteracting = map.isInteracting(), + l = layers.length; + for (let i = 0; i < l; i++) { + const layer = layers[i]; + if (!layer.isVisible()) { + continue; + } + const isCanvas = layer.isCanvasRender(); + const renderer = layer._getRenderer(); + if (!renderer) { + continue; + } + if (isCanvas) { + this.clearLayerCanvasContext(layer); + } + if (isInteracting && isCanvas) { + this._drawCanvasLayerOnInteracting(layer, framestamp); + } else if (isInteracting && renderer.drawOnInteracting) { + // dom layers + if (renderer.prepareRender) { + renderer.prepareRender(); + } + if (renderer.checkAndDraw) { + // for canvas renderers + renderer.checkAndDraw(renderer.drawOnInteracting, this._eventParam, framestamp); + } else { + renderer.drawOnInteracting(this._eventParam, framestamp); + } + } else { + // map is not interacting, call layer's render + renderer.render(framestamp); + } + } + return true; + } + + /** + * check if need to call layer's draw/drawInteracting + * @param layer + */ + //@internal + _checkLayerRedraw(layer: Layer): boolean { + const map = this.map; + const renderer = layer._getRenderer(); + if (!renderer) { + return false; + } + if (layer.isCanvasRender()) { + return renderer.testIfNeedRedraw(); + } else { + if (renderer.needToRedraw && renderer.needToRedraw()) { + return true; + } + // dom layers, redraw it if map is interacting or state is changed + return map.isInteracting() || this.isViewChanged(); + } + } + + /** + * Draw canvas rendered layer when map is interacting + * @param layer + * @param t current consumed time of layer drawing + * @param timeLimit time limit for layer drawing + * @param framestamp + * @returns time to draw this layer + * @private + */ + //@internal + _drawCanvasLayerOnInteracting(layer: Layer, framestamp: number): number { + const renderer = layer._getRenderer(); + if (renderer.mustRenderOnInteracting && renderer.mustRenderOnInteracting()) { + renderer.render(framestamp); + } else if (renderer.drawOnInteracting) { + // call drawOnInteracting to redraw the layer + renderer.prepareRender(); + const canvas = renderer.prepareCanvas(); + if (renderer.checkAndDraw) { + // for canvas renderers + renderer.checkAndDraw(renderer.drawOnInteracting, this._eventParam, framestamp); + } else { + renderer.drawOnInteracting(this._eventParam, framestamp); + } + return canvas; + } + return null; + } + + /** + * Fire layerload events. + * Make sure layer are drawn on map when firing the events + * @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()) { + continue; + } + /** + * layerload event, fired when layer is loaded. + * + * @event Layer#layerload + * @type {Object} + * @property {String} type - layerload + * @property {Layer} target - layer + */ + layer.fire('layerload'); + } + } + + setToRedraw() { + const layers = this._getAllLayerToRender(); + //set maprender for clear canvas + for (let i = 0, l = layers.length; i < l; i++) { + const renderer = layers[i].getRenderer(); + if (renderer && renderer.canvas && renderer.setToRedraw) { + //to fix lost webgl context + renderer.setToRedraw(); + } + } + } + + updateMapSize(size: Size) { + if (!size || this._containerIsCanvas) { + return; + } + const width = size['width'] + 'px', + height = size['height'] + 'px'; + const panels = this.map.getPanels(); + panels.mapWrapper.style.width = width; + panels.mapWrapper.style.height = height; + this._updateCanvasSize(); + } + + getMainPanel() { + if (!this.map) { + return null; + } + if (this._containerIsCanvas) { + return this.map.getContainer(); + } + if (this.map.getPanels()) { + return this.map.getPanels().mapWrapper; + } + return null; + } + + toDataURL(mimeType: string, quality?: number) { + if (!this.canvas) { + return null; + } + return this.canvas.toDataURL(mimeType, quality); + } + + remove() { + if (Browser.webgl && typeof document !== 'undefined') { + GlobalEvent.off(EVENT_DPR_CHANGE, this._thisDocDPRChange, this); + GlobalEvent.off(EVENT_DOC_VISIBILITY_CHANGE, this._thisDocVisibilitychange, this); + GlobalEvent.off(EVENT_DOC_DRAGSTART, this._thisDocDragStart, this); + GlobalEvent.off(EVENT_DOC_DRAGEND, this._thisDocDragEnd, this); + // removeDomEvent(document, 'visibilitychange', this._thisDocVisibilitychange, this); + // removeDomEvent(document, 'dragstart', this._thisDocDragStart, this); + // removeDomEvent(document, 'dragend', this._thisDocDragEnd, this); + } + if (this._resizeInterval) { + clearInterval(this._resizeInterval); + } + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + } + delete this.canvas; + delete this.map; + delete this._spatialRefChanged; + this._cancelFrameLoop(); + } + + hitDetect(point: Point) { + const map = this.map; + if (!map || !map.options['hitDetect'] || map.isInteracting()) { + return; + } + const layers = map._getLayers(); + let cursor = 'default'; + const limit = map.options['hitDetectLimit'] || 0; + let counter = 0; + if (point && point._round) { + // map size is decimal,containerPoint.x/containerPoint.y is decimal + point._round(); + } + for (let i = layers.length - 1; i >= 0; i--) { + const layer = layers[i]; + // 此处如果未开启,无需执行后面判断 + if (!layer.options['hitDetect'] || (layer.isEmpty && layer.isEmpty()) || !layer.options['geometryEvents']) { + continue; + } + const renderer = layer._getRenderer(); + if (!renderer || !renderer.hitDetect) { + continue; + } + if (renderer.isBlank && renderer.isBlank()) { + continue; + } + // renderer.hitDetect(point)) . This can't ignore the shadows. + /** + * TODO + * This requires a better way to judge + */ + if (layer.options['cursor'] !== 'default' && renderer.hitDetect(point)) { + cursor = layer.options['cursor'] || 'pointer'; + break; + } + counter++; + if (limit > 0 && counter > limit) { + break; + } + } + + map._trySetCursor(cursor); + } + + /** + * initialize container DOM of panels + */ + initContainer() { + const panels = this.map.getPanels(); + + function createContainer(name: string, className: string, cssText: string, enableSelect?: boolean): PanelDom { + const c = createEl('div', className) as PanelDom; + if (cssText) { + c.style.cssText = cssText; + } + panels[name] = c; + if (!enableSelect) { + preventSelection(c); + } + return c; + } + const containerDOM = this.map.getContainer(); + + if (this._containerIsCanvas) { + //container is a element. + return; + } + + containerDOM.innerHTML = ''; + + const POSITION0 = 'position:absolute;top:0px;left:0px;'; + + const mapWrapper = createContainer('mapWrapper', 'maptalks-wrapper', 'position:absolute;overflow:hidden;', true), + mapAllLayers = createContainer('allLayers', 'maptalks-all-layers', POSITION0 + 'padding:0px;margin:0px;z-index:0;overflow:visible;', true), + backStatic = createContainer('backStatic', 'maptalks-back-static', POSITION0 + 'z-index:0;', true), + back = createContainer('back', 'maptalks-back', POSITION0 + 'z-index:1;'), + backLayer = createContainer('backLayer', 'maptalks-back-layer', POSITION0), + canvasContainer = createContainer('canvasContainer', 'maptalks-canvas-layer', POSITION0 + 'border:none;z-index:2;'), + frontStatic = createContainer('frontStatic', 'maptalks-front-static', POSITION0 + 'z-index:3;', true), + front = createContainer('front', 'maptalks-front', POSITION0 + 'z-index:4;', true), + frontLayer = createContainer('frontLayer', 'maptalks-front-layer', POSITION0 + 'z-index:0;'), + // children's zIndex in frontLayer will be set by map.addLayer, ui container's z-index is set to 10000 to make sure it's always on the top. + ui = createContainer('ui', 'maptalks-ui', POSITION0 + 'border:none;z-index:1;', true), + control = createContainer('control', 'maptalks-control', 'z-index:1', true); + + containerDOM.appendChild(mapWrapper); + + mapAllLayers.appendChild(backStatic); + back.appendChild(backLayer); + back.layerDOM = backLayer; + mapAllLayers.appendChild(back); + mapAllLayers.appendChild(canvasContainer); + front.appendChild(frontLayer); + front.layerDOM = frontLayer; + front.uiDOM = ui; + mapAllLayers.appendChild(frontStatic); + mapAllLayers.appendChild(front); + front.appendChild(ui); + + mapWrapper.appendChild(mapAllLayers); + mapWrapper.appendChild(control); + + this.createCanvas(); + + this.resetContainer(); + const mapSize = this.map._getContainerDomSize(); + this.updateMapSize(mapSize); + } + + /** + * Is current map's state changed? + */ + isViewChanged() { + if (this._isViewChanged !== undefined) { + return this._isViewChanged; + } + const previous = this._mapview; + const view = this._getMapView(); + this._isViewChanged = !previous || !equalMapView(previous, view); + return this._isViewChanged; + } + + //@internal + _recordView() { + const map = this.map; + if (!map._onViewChange || map.isInteracting() || map.isAnimating()) { + return; + } + if (!equalMapView(map.getView(), map._getCurrentView())) { + map._onViewChange(map.getView()); + } + } + + isSpatialReferenceChanged() { + return this._spatialRefChanged; + } + + //@internal + _getMapView(): MapView { + const map = this.map; + const center = map._getPrjCenter(); + return { + x: center.x, + y: center.y, + zoom: map.getZoom(), + pitch: map.getPitch(), + bearing: map.getBearing(), + width: map.width, + height: map.height + }; + } + + //@internal + _lockFrameRenderEnable() { + const { maxFPS } = this.map.options || {}; + if (maxFPS <= 0 || GlobalConfig.maxFPS <= maxFPS) { + return true; + } + const count = Math.ceil(GlobalConfig.maxFPS / maxFPS); + return this._frameCycleRenderCount >= count; + } + + /** + * Main frame loop + */ + //@internal + _frameLoop(framestamp: number) { + if (!this.map) { + this._cancelFrameLoop(); + return; + } + this._frameCycleRenderCount++; + if (this._lockFrameRenderEnable()) { + framestamp = framestamp || 0; + this._frameTimestamp = framestamp; + this._resizeCount = 0; + this.renderFrame(framestamp); + this._frameCycleRenderCount = 0; + } else if (this.map.options.debug) { + console.log('skip frame ing,frameCycleRenderCount:', this._frameCycleRenderCount); + } + // Keep registering ourselves for the next animation frame + this._animationFrame = requestAnimFrame((framestamp: number) => { this._frameLoop(framestamp); }); + } + + //@internal + _cancelFrameLoop() { + if (this._animationFrame) { + cancelAnimFrame(this._animationFrame); + } + } + + //@internal + _getAllLayerToRender() { + return this.map._getLayers(); + } + + + //@internal + _updateCanvasSize() { + if (!this.canvas || this._containerIsCanvas) { + return; + } + const map = this.map, + mapSize = map.getSize(), + canvas = this.canvas, + r = map.getDevicePixelRatio(); + // width/height不变并不意味着 css width/height 不变 + const { width, height, cssWidth, cssHeight } = calCanvasSize(mapSize, r); + if (canvas.style && (canvas.style.width !== cssWidth || canvas.style.height !== cssHeight)) { + 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; + } + } + + 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 + } + + clearCanvas() { + // should be implemented by child class + } + + // canvas for tops + createTopCanvas() { + 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; + } + + //@internal + _updateDomPosition(framestamp: number) { + if (this._checkPositionTime === undefined) { + this._checkPositionTime = -Infinity; + } + const dTime = Math.abs(framestamp - this._checkPositionTime); + if (dTime >= 500) { + // refresh map's dom position + computeDomPosition(this.map.getContainer()); + this._checkPositionTime = Math.min(framestamp, this._checkPositionTime); + } + return this; + } + + //@internal + _handleResizeEventList(time: number) { + if (!this._resizeEventList) { + return this; + } + const len = this._resizeEventList.length; + if (len === 0) { + return this; + } + if (this._resizeTime && time - this._resizeTime < 60) { + return this; + } + const contentRect = this._resizeEventList[len - 1].contentRect; + this.map.setContainerDomRect(contentRect); + this._resizeEventList = []; + this._checkSize(); + this._resizeCount = this._resizeCount || 0; + //force render all layers,这两句代码不能颠倒,因为要先重置所有图层的size,才能正确的渲染所有图层 + this.renderFrame((this._frameTimestamp || 0) + (++this._resizeCount) / 100); + this._resizeTime = time; + return this; + } + + //@internal + _checkSize() { + if (!this.map) { + return; + } + this.map.checkSize(); + } + + //@internal + _setCheckSizeInterval(interval: number) { + // ResizeObserver priority of use + // https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserver + if (Browser.resizeObserver) { + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + } + if (this.map) { + // eslint-disable-next-line no-unused-vars + this._resizeObserver = new ResizeObserver((entries) => { + if (!this.map || this.map.isRemoved()) { + this._resizeObserver.disconnect(); + } else if (entries.length) { + this._resizeEventList = this._resizeEventList || []; + this._resizeEventList.push(entries[0]); + } + }); + this._resizeObserver.observe(this.map.getContainer()); + } + } else { + clearInterval(this._resizeInterval); + this._checkSizeInterval = interval; + this._resizeInterval = setInterval(() => { + if (!this.map || this.map.isRemoved()) { + //is deleted + clearInterval(this._resizeInterval); + } else { + this._checkSize(); + } + }, this._checkSizeInterval) as unknown as number; + } + } + + //@internal + _registerEvents() { + const map = this.map; + + if (map.options['checkSize'] && !IS_NODE && (typeof window !== 'undefined')) { + this._setCheckSizeInterval(map.options['checkSizeInterval']); + } + if (!Browser.mobile) { + map.on('_mousemove', this._onMapMouseMove, this); + } + + map.on('_dragrotatestart _dragrotating _dragrotateend _movestart _moving _moveend _zoomstart', (param: any) => { + this._eventParam = param; + }); + + map.on('_zooming', (param: any) => { + if (!map.getPitch()) { + this._zoomMatrix = param['matrix']['container']; + } + this._eventParam = param; + }); + + map.on('_zoomend', (param: any) => { + this._eventParam = param; + delete this._zoomMatrix; + }); + + map.on('_spatialreferencechange', () => { + this._spatialRefChanged = true; + }); + + if (Browser.webgl && typeof document !== 'undefined') { + GlobalEvent.on(EVENT_DPR_CHANGE, this._thisDocDPRChange, this); + GlobalEvent.on(EVENT_DOC_VISIBILITY_CHANGE, this._thisDocVisibilitychange, this); + GlobalEvent.on(EVENT_DOC_DRAGSTART, this._thisDocDragStart, this); + GlobalEvent.on(EVENT_DOC_DRAGEND, this._thisDocDragEnd, this); + + // addDomEvent(document, 'visibilitychange', this._thisDocVisibilitychange, this); + // addDomEvent(document, 'dragstart', this._thisDocDragStart, this); + // addDomEvent(document, 'dragend', this._thisDocDragEnd, this); + } + } + + //@internal + _onMapMouseMove(param: any) { + const map = this.map; + if (map.isInteracting() || !map.options['hitDetect']) { + return; + } + if (this._hitDetectFrame) { + cancelAnimFrame(this._hitDetectFrame); + } + this._hitDetectFrame = requestAnimFrame(() => { + this.hitDetect(param['containerPoint']); + }); + } + + //@internal + _getCanvasLayers() { + return this.map._getLayers(layer => layer.isCanvasRender()); + } + + //----------- top elements methods ------------- + // edit handles or edit outlines + addTopElement(e: EditHandle | EditOutline) { + if (!this._tops) { + this._tops = []; + } + this._tops.push(e); + } + + removeTopElement(e: EditHandle | EditOutline) { + if (!this._tops) { + return; + } + const idx = this._tops.indexOf(e); + if (idx >= 0) { + this._tops.splice(idx, 1); + } + } + + getTopElements() { + return this._tops || []; + } + + sortTopElements() { + this._tops = this._tops.sort((top1, top2) => { + const zIndex1 = (top1.options || {}).zIndex || 0; + const zIndex2 = (top2.options || {}).zIndex || 0; + return zIndex2 - zIndex1; + }); + } + + 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'); + + let updated = false; + const dpr = this.map.getDevicePixelRatio(); + const geos = []; + for (let i = 0; i < tops.length; i++) { + const top = tops[i]; + if (top.needCollision && top.needCollision()) { + const bbox = top.getRenderBBOX(dpr); + if (bbox) { + if (collisionIndex.collides(bbox)) { + const geometry = top.target && top.target._geometry; + if (geometry && geos.indexOf(geometry) === -1) { + geos.push(geometry); + geometry.fire('handlecollision'); + } + continue; + } else { + collisionIndex.insertBox(bbox); + } + } + } + if (top.render(this.topCtx)) { + updated = true; + } + } + if (updated) { + this.context.drawImage(this.topLayer, 0, 0); + } + this.map.fire('drawtopsend'); + } +} + +export type MapView = { + x: number; + y: number; + zoom: number; + pitch: number; + bearing: number; + width: number; + height: number; +} + +export default MapGLAbstractRenderer; + + diff --git a/packages/maptalks-gl/package.json b/packages/maptalks-gl/package.json index f429fcdf69..3e39960c0c 100644 --- a/packages/maptalks-gl/package.json +++ b/packages/maptalks-gl/package.json @@ -48,6 +48,6 @@ "@maptalks/transform-control": "workspace:*", "@maptalks/video-layer": "workspace:*", "@maptalks/vt": "workspace:*", - "@maptalks/map": "workspace:*" + "maptalks": "workspace:*" } } diff --git a/packages/reshader.gl/package.json b/packages/reshader.gl/package.json index 8bf7050be7..7fc921fa98 100644 --- a/packages/reshader.gl/package.json +++ b/packages/reshader.gl/package.json @@ -25,8 +25,10 @@ "dependencies": { "@maptalks/regl": "^3.4.0", "@maptalks/tbn-packer": "^1.4.5", + "@webgpu/types": "0.1.52", "earcut": "^3.0.1", - "gl-matrix": "^3.4.0" + "gl-matrix": "^3.4.0", + "wgsl_reflect": "^1.0.16" }, "devDependencies": { "@maptalks/rollup-plugin-glsl-minify": "^0.1.7", @@ -38,7 +40,10 @@ "eslint-plugin-mocha": "^10.5.0", "mocha": "^10.3.0", "rollup": "^4.17.2", - "rollup-plugin-dts": "^6.1.0" + "rollup-plugin-dts": "^6.1.0", + "tslib": "^2.6.2", + "typescript": "^5.4.2", + "typescript-eslint": "^7.18.0" }, "gitHead": "9eeea4807723d460fa7e09969c6556d53f6bed84" } diff --git a/packages/reshader.gl/rollup.config.js b/packages/reshader.gl/rollup.config.js index 281e907efe..a302a81998 100644 --- a/packages/reshader.gl/rollup.config.js +++ b/packages/reshader.gl/rollup.config.js @@ -68,7 +68,7 @@ if (production) { module.exports = [ { input: 'src/index.ts', - external : ['gl-matrix', '@maptalks/gltf-loader', '@maptalks/tbn-packer'], + external : production ? ['gl-matrix', '@maptalks/gltf-loader', '@maptalks/tbn-packer'] : [], plugins : plugins, output: [ { diff --git a/packages/reshader.gl/src/AbstractTexture.ts b/packages/reshader.gl/src/AbstractTexture.ts index 2940d48f50..df92e58eb9 100644 --- a/packages/reshader.gl/src/AbstractTexture.ts +++ b/packages/reshader.gl/src/AbstractTexture.ts @@ -1,6 +1,6 @@ -import { isFunction, hasOwn, getTextureByteWidth, getTextureChannels, supportNPOT } from './common/Util.js'; +import { isFunction, hasOwn, getTextureByteWidth, getTextureChannels } from './common/Util.js'; import Eventable from './common/Eventable'; -import { KEY_DISPOSED } from './common/Constants.js'; +import { ERROR_NOT_IMPLEMENTED, KEY_DISPOSED } from './common/Constants.js'; import ResourceLoader from './ResourceLoader'; import REGL, { Regl, Texture2D } from '@maptalks/regl'; import { TextureConfig } from './types/typings'; @@ -104,7 +104,7 @@ class AbstractTexture extends Eventable(Base) { return null; } - getREGLTexture(regl: Regl) { + getREGLTexture(regl: any): any { if (!this._texture) { this._texture = this.createREGLTexture(regl); if (!this.config.persistent) { @@ -129,7 +129,7 @@ class AbstractTexture extends Eventable(Base) { } } if (this.dirty) { - this._updateREGL(); + this._update(); } return this._texture; } @@ -149,12 +149,8 @@ class AbstractTexture extends Eventable(Base) { // } } - //@internal - _updateREGL() { - if (this._texture && !this._texture[KEY_DISPOSED]) { - this._texture(this.config as any); - } - this.dirty = false; + _update() { + throw new Error(ERROR_NOT_IMPLEMENTED); } dispose() { @@ -185,16 +181,7 @@ class AbstractTexture extends Eventable(Base) { } } - //@internal - _needPowerOf2(regl) { - if (supportNPOT(regl)) { - return false; - } - const config = this.config; - const isRepeat = config.wrap && config.wrap !== 'clamp' || config.wrapS && config.wrapS !== 'clamp' || - config.wrapT && config.wrapT !== 'clamp'; - return isRepeat || config.min && config.min !== 'nearest' && config.min !== 'linear'; - } + } export default AbstractTexture; diff --git a/packages/reshader.gl/src/EdgeGeometry.ts b/packages/reshader.gl/src/EdgeGeometry.ts index 08ad9a5f73..e7555ccf63 100644 --- a/packages/reshader.gl/src/EdgeGeometry.ts +++ b/packages/reshader.gl/src/EdgeGeometry.ts @@ -17,7 +17,7 @@ export default class EdgeGeometry extends Geometry { } //@internal - _getPosAttritute(): number[] { + _getPosAttribute(): number[] { const pos = this.data[this.desc.positionAttribute]; if (!pos.length) { return []; diff --git a/packages/reshader.gl/src/Geometry.ts b/packages/reshader.gl/src/Geometry.ts index 93faa97931..e95b65e8e4 100644 --- a/packages/reshader.gl/src/Geometry.ts +++ b/packages/reshader.gl/src/Geometry.ts @@ -1,11 +1,13 @@ import { vec3, vec4 } from 'gl-matrix'; import { packTangentFrame, buildTangents, buildNormals } from '@maptalks/tbn-packer'; -import { isNumber, extend, isArray, isSupportVAO, hasOwn, getBufferSize, isInStride, isInterleaved } from './common/Util'; +import { isNumber, extend, isArray, isSupportVAO, hasOwn, getBufferSize, isInStride, isInterleaved, getArrayCtor } from './common/Util'; import BoundingBox from './BoundingBox'; import { KEY_DISPOSED } from './common/Constants'; import { getGLTFLoaderBundle } from './common/GLTFBundle' import { ActiveAttributes, AttributeData, GeometryDesc, NumberArray } from './types/typings'; -import REGL, { Regl } from '@maptalks/regl'; +import REGL from '@maptalks/regl'; +import { flatten } from 'earcut'; +import { getGPUVertexType, getFormatFromGLTFAccessor, getItemBytesFromGLTFAccessor } from './webgpu/common/Types'; const EMPTY_VAO_BUFFER = []; @@ -51,6 +53,25 @@ function GUID() { const REF_COUNT_KEY = '_reshader_refCount'; export default class Geometry { + + static createElementBuffer(device: any, elements: any): any { + if (device.wgpu) { + return createGPUBuffer(device, elements, GPUBufferUsage.INDEX, 'index buffer'); + } else { + // regl + return device.elements(elements); + } + } + + static createBuffer(device: any, data: any, name?: string) { + if (device.wgpu) { + return createGPUBuffer(device, data, GPUBufferUsage.VERTEX, name); + } else { + // regl + return device.buffer(data); + } + } + data: Record elements: any desc: GeometryDesc @@ -87,7 +108,7 @@ export default class Geometry { this.elements = elements; this.desc = extend({}, DEFAULT_DESC, desc); - const pos = this._getPosAttritute(); + const pos = this._getPosAttribute(); this.data[this.desc.positionAttribute] = pos; if (!count) { if (this.elements) { @@ -125,7 +146,7 @@ export default class Geometry { // } //@internal - _getPosAttritute() { + _getPosAttribute() { return this.data[this.desc.positionAttribute]; } @@ -184,6 +205,10 @@ export default class Geometry { } } + getBuffer(name: string) { + return this.data[name] && this.data[name].buffer; + } + getAttrData(activeAttributes: ActiveAttributes) { const key = activeAttributes.key; const updated = !this._reglData || !this._reglData[key]; @@ -223,9 +248,9 @@ export default class Geometry { getREGLData(regl: any, activeAttributes: ActiveAttributes, disableVAO: boolean): AttributeData { this.getAttrData(activeAttributes); - const updated = !this._reglData || !this._reglData[activeAttributes.key]; //support vao if (isSupportVAO(regl) && !disableVAO) { + const updated = !this._reglData || !this._reglData[activeAttributes.key]; const key = activeAttributes && activeAttributes.key || 'default'; if (!this._vao[key] || updated || this._vao[key].dirty) { const reglData = this._reglData[activeAttributes.key]; @@ -315,13 +340,13 @@ export default class Geometry { return false; } - generateBuffers(regl: Regl) { + generateBuffers(device: any) { //generate regl buffers beforehand to avoid repeated bufferData //提前处理addBuffer插入的arraybuffer const allocatedBuffers = this._buffers; for (const p in allocatedBuffers) { if (!allocatedBuffers[p].buffer) { - allocatedBuffers[p].buffer = regl.buffer(allocatedBuffers[p].data); + allocatedBuffers[p].buffer = Geometry.createBuffer(device, allocatedBuffers[p].data, p); } delete allocatedBuffers[p].data; } @@ -334,6 +359,9 @@ export default class Geometry { if (!data[key]) { continue; } + if (Array.isArray(data[key])) { + data[key] = new Float32Array(data[key]); + } //如果调用过addBuffer,buffer有可能是ArrayBuffer if (data[key].buffer !== undefined && !(data[key].buffer instanceof ArrayBuffer)) { if (data[key].buffer.destroy) { @@ -348,7 +376,7 @@ export default class Geometry { const dimension = arr.length / vertexCount; const info = data[key].data ? data[key] : { data: data[key] }; info.dimension = dimension; - const buffer = regl.buffer(info); + const buffer = Geometry.createBuffer(device, info, key); buffer[REF_COUNT_KEY] = 1; buffers[key] = { buffer @@ -383,7 +411,7 @@ export default class Geometry { this.indices[i] = elements[i]; } } - this.elements = this.elements.destroy ? this.elements : regl.elements(info); + this.elements = this.elements.destroy ? this.elements : Geometry.createElementBuffer(device, this.elements); const elements = this.elements; if (!elements[REF_COUNT_KEY]) { elements[REF_COUNT_KEY] = 0; @@ -393,6 +421,7 @@ export default class Geometry { } } + getVertexCount(): number { const { positionAttribute, positionSize, color0Attribute } = this.desc; let data = this.data[positionAttribute]; @@ -432,29 +461,29 @@ export default class Geometry { * @param {String} key - 属性 * @param {ArrayBuffer|REGLBuffer} data - 数据 */ - addBuffer(key: string, data: ArrayBuffer | REGL.Buffer): this { - this._buffers[key] = { - data - }; - delete this._reglData; - this._deleteVAO(); - return this; - } + // addBuffer(key: string, data: ArrayBuffer | REGL.Buffer): this { + // this._buffers[key] = { + // data + // }; + // delete this._reglData; + // this._deleteVAO(); + // return this; + // } - updateBuffer(key: string, data: ArrayBuffer | REGL.Buffer): this { - if (!this._buffers[key]) { - throw new Error(`invalid buffer ${key} in geometry`); - } - // this._buffers[key].data = data; - if (this._buffers[key].buffer) { - this._buffers[key].buffer.subdata(data); - } else { - this._buffers[key].data = data; - } - delete this._reglData; - this._deleteVAO(); - return this; - } + // updateBuffer(key: string, data: ArrayBuffer | REGL.Buffer): this { + // if (!this._buffers[key]) { + // throw new Error(`invalid buffer ${key} in geometry`); + // } + // // this._buffers[key].data = data; + // if (this._buffers[key].buffer) { + // this._buffers[key].buffer.subdata(data); + // } else { + // this._buffers[key].data = data; + // } + // delete this._reglData; + // this._deleteVAO(); + // return this; + // } deleteData(name: string): this { const buf = this.data[name]; @@ -652,6 +681,14 @@ export default class Geometry { this._disposed = true; } + //@internal + _deleteVAO() { + for (const p in this._vao) { + this._vao[p].vao.destroy(); + } + this._vao = {}; + } + isDisposed() { return !!this._disposed; } @@ -853,7 +890,8 @@ export default class Geometry { throw new Error(name + ' must be array to build unique vertex.'); } oldData[name] = attr; - data[name] = new attr.constructor(l * size); + const ctor = getArrayCtor(attr); + data[name] = new ctor(l * size); } let cursor = 0; @@ -896,14 +934,6 @@ export default class Geometry { return size; } - //@internal - _deleteVAO() { - for (const p in this._vao) { - this._vao[p].vao.destroy(); - } - this._vao = {}; - } - //@internal _forEachBuffer(fn: (buffer: any) => void) { if (this.elements && this.elements.destroy) { @@ -942,6 +972,71 @@ export default class Geometry { _incrVersion() { this._version++; } + + getCommandKey(device: any) { + if (device && device.wgpu) { + // 因为attribute组织方式会影响pipeline的创建,所以attributes组织方式不同时,需要创建不同的command key + // 我们这里暂时不考虑Geometry会更新attributes组织方式的情况,因为在maptalks的使用场景不会出现 + // 可能有些attribute没有被wgsl使用,但组织方式不同,这里也不考虑这种情况 + const keys = []; + for (const p in this.data) { + const attr = this.data[p]; + if (attr.byteStride) { + keys.push(`${p}(${attr.byteOffset}/${attr.byteStride}})`); + } else { + keys.push(p); + } + } + return keys.sort().join('-'); + } else { + // regl + return ''; + } + } + + getBufferDescriptor(vertexInfo) { + const data = this.data; + const bufferDesc = []; + const bufferMapping = {}; + for (const p in data) { + const attr = data[p]; + if (!attr) { + continue; + } + const info = vertexInfo[p]; + const accessorName = attr.accessorName; + const byteStride = attr.byteStride; + if (byteStride && accessorName) { + // a GLTF accessor style attribute + const format = getFormatFromGLTFAccessor(attr.componentType, attr.itemSize); + let desc = bufferMapping[accessorName]; + if (desc) { + desc.attributes.push({ + shaderLocation: info.location, + format, + offset: attr.byteOffset + }); + } else { + desc = { + arrayStride: byteStride, + attributes: [ + { + shaderLocation: info.location, + format, + offset: attr.byteOffset + } + ] + } + bufferMapping[accessorName] = desc; + bufferDesc.push(desc); + } + } else { + const desc = getAttrBufferDescriptor(attr, info); + bufferDesc.push(desc); + } + } + return bufferDesc; + } } function getElementLength(elements) { @@ -951,8 +1046,13 @@ function getElementLength(elements) { // object buffer form return elements.count; } else if (elements.destroy) { - // a regl element buffer - return elements['_elements'].vertCount; + if (elements['_elements']) { + // a regl element buffer + return elements['_elements'].vertCount; + } else { + //GPUBuffer + return elements.itemCount; + } } else if (elements.length !== undefined) { return elements.length; } else if (elements.data) { @@ -974,86 +1074,94 @@ function getTypeCtor(arr: NumberArray, byteWidth: number) { return null; } -// function buildTangents2(vertices, normals, uvs, indices) { -// const vtxCount = vertices.length / 3; -// const tangent = new Array(vtxCount * 4); -// const tanA = new Array(vertices.length); -// const tanB = new Array(vertices.length); - -// // (1) -// const indexCount = indices.length; -// for (let i = 0; i < indexCount; i += 3) { -// const i0 = indices[i]; -// const i1 = indices[i + 1]; -// const i2 = indices[i + 2]; - -// const pos0 = vec3.set([], vertices[i0 * 3], vertices[i0 * 3 + 1], vertices[i0 * 3 + 2]); -// const pos1 = vec3.set([], vertices[i1 * 3], vertices[i1 * 3 + 1], vertices[i1 * 3 + 2]); -// const pos2 = vec3.set([], vertices[i2 * 3], vertices[i2 * 3 + 1], vertices[i2 * 3 + 2]); - -// const tex0 = vec2.set([], uvs[i0 * 2], uvs[i0 * 2 + 1]); -// const tex1 = vec2.set([], uvs[i1 * 2], uvs[i1 * 2 + 1]); -// const tex2 = vec2.set([], uvs[i2 * 2], uvs[i2 * 2 + 1]); - -// const edge1 = vec3.sub([], pos1, pos0); -// const edge2 = vec3.sub([], pos2, pos0); - -// const uv1 = vec2.sub([], tex1, tex0); -// const uv2 = vec2.sub([], tex2, tex0); - -// const r = 1.0 / (uv1[0] * uv2[1] - uv1[1] * uv2[0]); - -// const tangent = [ -// ((edge1[0] * uv2[1]) - (edge2[0] * uv1[1])) * r, -// ((edge1[1] * uv2[1]) - (edge2[1] * uv1[1])) * r, -// ((edge1[2] * uv2[1]) - (edge2[2] * uv1[1])) * r -// ]; - -// const bitangent = [ -// ((edge1[0] * uv2[0]) - (edge2[0] * uv1[0])) * r, -// ((edge1[1] * uv2[0]) - (edge2[1] * uv1[0])) * r, -// ((edge1[2] * uv2[0]) - (edge2[2] * uv1[0])) * r -// ]; - -// tanA[i0] = tanA[i0] || [0, 0, 0]; -// tanA[i1] = tanA[i1] || [0, 0, 0]; -// tanA[i2] = tanA[i2] || [0, 0, 0]; -// vec3.add(tanA[i0], tanA[i0], tangent); -// vec3.add(tanA[i1], tanA[i1], tangent); -// vec3.add(tanA[i2], tanA[i2], tangent); -// // tanA[i0] += tangent; -// // tanA[i1] += tangent; -// // tanA[i2] += tangent; - -// tanB[i0] = tanB[i0] || [0, 0, 0]; -// tanB[i1] = tanB[i1] || [0, 0, 0]; -// tanB[i2] = tanB[i2] || [0, 0, 0]; -// vec3.add(tanB[i0], tanB[i0], bitangent); -// vec3.add(tanB[i1], tanB[i1], bitangent); -// vec3.add(tanB[i2], tanB[i2], bitangent); -// // tanB[i0] += bitangent; -// // tanB[i1] += bitangent; -// // tanB[i2] += bitangent; -// } - -// // (2) -// for (let j = 0; j < vtxCount; j++) { -// const n = vec3.set([], normals[j * 3], normals[j * 3 + 1], normals[j * 3 + 2]); -// const t0 = tanA[j]; -// const t1 = tanB[j]; - -// const n1 = vec3.scale([], n, vec3.dot(n, t0)); -// const t = vec3.sub([], t0, n1); -// vec3.normalize(t, t); -// // const t = t0 - (n * dot(n, t0)); -// // t = normaljze(t); - -// const c = vec3.cross(n, n, t0); -// const w = (vec3.dot(c, t1) < 0) ? -1.0 : 1.0; -// tangent[j * 4] = t[0]; -// tangent[j * 4 + 1] = t[1]; -// tangent[j * 4 + 2] = t[2]; -// tangent[j * 4 + 3] = w; -// } -// return tangent; -// } +export function getAttrBufferDescriptor(attr, info): GPUVertexBufferLayout { + const array = attr.data || attr.array || attr; + if (attr.buffer && !isArray(attr)) { + const itemBytes = attr.buffer.itemBytes; + const format = getItemFormat(attr, info.itemSize); + return { + arrayStride: info.itemSize * itemBytes, + attributes: [ + { + shaderLocation: info.location, + format, + offset: 0 + } + ] + }; + } else if (isArray(array)) { + let format, itemBytes; + if (attr.componentType) { + format = getFormatFromGLTFAccessor(attr.componentType, attr.itemSize); + itemBytes = getItemBytesFromGLTFAccessor(attr.componentType); + } else { + format = getItemFormat(array, info.itemSize); + itemBytes = getItemBytes(array); + } + return { + arrayStride: info.itemSize * itemBytes, + attributes: [ + { + shaderLocation: info.location, + format, + offset: 0 + } + ] + }; + } +} + +function getItemBytes(data) { + const array = getAttrArray(data); + if (array.destroy) { + return array.itemBytes; + } + if (array.BYTES_PER_ELEMENT) { + return array.BYTES_PER_ELEMENT; + } else if (Array.isArray(array)) { + // float + return 4; + } else { + const item = array; + const gltf = getGLTFLoaderBundle(); + const ctor = gltf.GLTFLoader.getTypedArrayCtor(item.componentType); + return ctor.BYTES_PER_ELEMENT; + } +} + +function getItemFormat(data, itemSize) { + const array = getAttrArray(data); + let format; + if (array.destroy) { + format = array.itemType; + } else { + format = getGPUVertexType(array); + } + return itemSize > 1 ? (format + 'x' + itemSize) : format; +} + +function getAttrArray(data) { + return data.data || (data.buffer.destroy && data.buffer) || data; +} + +function createGPUBuffer(device, data, usage, label) { + data = data.data || data; + if (Array.isArray(data[0])) { + data = flatten(data); + } + const ctor = data.constructor; + // f32 in default + const size = Array.isArray(data) ? data.length * 4 : data.byteLength; + const buffer = device.wgpu.createBuffer({ + label, + size, + usage, + mappedAtCreation: true + }); + new ctor(buffer.getMappedRange()).set(data); + buffer.unmap(); + buffer.itemCount = data.length; + buffer.itemBytes = getItemBytes(data); + buffer.itemType = getGPUVertexType(data); // uint8, sint8, uint16, sint16, uint32, sint32, float32 + return buffer; +} diff --git a/packages/reshader.gl/src/InstancedMesh.ts b/packages/reshader.gl/src/InstancedMesh.ts index 14da18bb10..cac251e3b9 100644 --- a/packages/reshader.gl/src/InstancedMesh.ts +++ b/packages/reshader.gl/src/InstancedMesh.ts @@ -4,7 +4,7 @@ import { KEY_DISPOSED } from './common/Constants'; import REGL, { BufferOptions, Regl } from '@maptalks/regl'; import { ActiveAttributes, AttributeBufferData, InstancedAttribute, MeshOptions, NumberArray } from './types/typings'; import Material from './Material'; -import Geometry from './Geometry'; +import Geometry, { getAttrBufferDescriptor } from './Geometry'; export default class InstancedMesh extends Mesh { //@internal @@ -107,8 +107,8 @@ export default class InstancedMesh extends Mesh { return defines; } - getCommandKey(regl: Regl) { - return 'i_' + super.getCommandKey(regl); + getCommandKey(device: any) { + return 'i_' + super.getCommandKey(device); } //因为 updateBoundingBox 需要, 不再自动生成buffer,而是把原有的buffer销毁 @@ -142,7 +142,11 @@ export default class InstancedMesh extends Mesh { return this; } - generateInstancedBuffers(regl: Regl) { + getInstancedBuffer(name: string) { + return this.instancedData[name] && (this.instancedData[name] as any).buffer; + } + + generateInstancedBuffers(device: any) { const data = this.instancedData; const buffers: Record = {}; for (const key in data) { @@ -161,11 +165,12 @@ export default class InstancedMesh extends Mesh { divisor: 1 }; } else { + const bufferOptions = { + data: data[key], + dimension: (data[key] as NumberArray).length / this._instanceCount + } as BufferOptions; buffers[key] = { - buffer: regl.buffer({ - data: data[key], - dimension: (data[key] as NumberArray).length / this._instanceCount - } as BufferOptions), + buffer: Geometry.createBuffer(device, bufferOptions, key), divisor: 1 }; } @@ -174,8 +179,8 @@ export default class InstancedMesh extends Mesh { return this; } - getREGLProps(regl: Regl, activeAttributes: ActiveAttributes) { - const props = super.getREGLProps(regl, activeAttributes); + getRenderProps(regl: Regl) { + const props = super.getRenderProps(regl); if (!isSupportVAO(regl)) { extend(props, this.instancedData); } @@ -205,6 +210,22 @@ export default class InstancedMesh extends Mesh { this._vao = {}; } + getBufferDescriptor(vertexInfo) { + const data = this.instancedData; + const bufferDesc = []; + for (const p in data) { + const attr = data[p]; + if (!attr) { + continue; + } + const info = vertexInfo[p]; + const desc = getAttrBufferDescriptor(attr, info); + desc.stepMode = 'instance'; + bufferDesc.push(desc); + } + return bufferDesc; + } + // getBoundingBox() { // if (!this._bbox) { // this.updateBoundingBox(); diff --git a/packages/reshader.gl/src/Material.ts b/packages/reshader.gl/src/Material.ts index 0420152537..a94bec0010 100644 --- a/packages/reshader.gl/src/Material.ts +++ b/packages/reshader.gl/src/Material.ts @@ -3,12 +3,12 @@ import { isNil, extendWithoutNil, hasOwn, getTexMemorySize } from './common/Util import AbstractTexture from './AbstractTexture'; import { KEY_DISPOSED } from './common/Constants'; import { ShaderUniforms, ShaderUniformValue, ShaderDefines } from './types/typings'; -import { Regl, Texture } from '@maptalks/regl'; +import { Texture } from '@maptalks/regl'; import Geometry from './Geometry'; class Base {} -class Material extends Eventable(Base) { +export default class Material extends Eventable(Base) { uniforms: ShaderUniforms refCount: number // 如果unlit,则不产生阴影(但接受阴影) @@ -75,10 +75,70 @@ class Material extends Eventable(Base) { return !!this.uniforms.doubleSided; } + getUniforms(device: any) { + if (this._reglUniforms && !this.isDirty()) { + return this._reglUniforms; + } + const uniforms = this.uniforms; + const realUniforms: ShaderUniforms = {}; + for (const p in uniforms) { + if (this.isTexture(p)) { + Object.defineProperty(realUniforms, p, { + enumerable: true, + configurable: true, + get: function () { + return (uniforms[p] as AbstractTexture).getREGLTexture(device); + } + }); + } else { + const descriptor = Object.getOwnPropertyDescriptor(uniforms, p); + if (descriptor.get) { + Object.defineProperty(realUniforms, p, { + enumerable: true, + configurable: true, + get: function () { + return uniforms[p]; + } + }); + } else { + realUniforms[p] = uniforms[p]; + } + } + } + this._reglUniforms = realUniforms; + this._uniformVer = this.version; + return realUniforms; + } + + getMemorySize() { + const uniforms = this.uniforms; + let size = 0; + for (const p in uniforms) { + if (this.isTexture(p)) { + size += (uniforms[p] as AbstractTexture).getMemorySize(); + } else if (this.uniforms[p] && (this.uniforms[p] as any).destroy) { + size += getTexMemorySize(this.uniforms[p] as Texture); + } + } + return size; + } + isReady() { return this._loadingCount <= 0; } + hasUniform(k: string) { + return Object.prototype.hasOwnProperty.call(this.uniforms, k); + } + + setUniform(k: string, v: ShaderUniformValue) { + return this.set(k, v); + } + + getUniform(k: string): ShaderUniformValue { + return this.get(k); + } + set(k: string, v: ShaderUniformValue): this { if (this.get(k) === v) { return this; @@ -141,7 +201,8 @@ class Material extends Eventable(Base) { * Get shader defines * @return {Object} */ - appendDefines(defines: ShaderDefines, geometry: Geometry) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + appendDefines(defines: ShaderDefines, _geometry: Geometry) { const uniforms = this.uniforms; if (!uniforms) { return defines; @@ -162,40 +223,6 @@ class Material extends Eventable(Base) { return !!(this.uniforms && this.uniforms['jointTexture'] && this.uniforms['skinAnimation']); } - getUniforms(regl: Regl) { - if (this._reglUniforms && !this.isDirty()) { - return this._reglUniforms; - } - const uniforms = this.uniforms; - const realUniforms: ShaderUniforms = {}; - for (const p in uniforms) { - if (this.isTexture(p)) { - Object.defineProperty(realUniforms, p, { - enumerable: true, - configurable: true, - get: function () { - return (uniforms[p] as AbstractTexture).getREGLTexture(regl); - } - }); - } else { - const descriptor = Object.getOwnPropertyDescriptor(uniforms, p); - if (descriptor.get) { - Object.defineProperty(realUniforms, p, { - enumerable: true, - configurable: true, - get: function () { - return uniforms[p]; - } - }); - } else { - realUniforms[p] = uniforms[p]; - } - } - } - this._reglUniforms = realUniforms; - this._uniformVer = this.version; - return realUniforms; - } isTexture(k: string) { const v = this.uniforms[k]; @@ -272,19 +299,4 @@ class Material extends Eventable(Base) { _incrVersion() { this._version++; } - - getMemorySize() { - const uniforms = this.uniforms; - let size = 0; - for (const p in uniforms) { - if (this.isTexture(p)) { - size += (uniforms[p] as AbstractTexture).getMemorySize(); - } else if (this.uniforms[p] && (this.uniforms[p] as any).destroy) { - size += getTexMemorySize(this.uniforms[p] as Texture); - } - } - return size; - } } - -export default Material; diff --git a/packages/reshader.gl/src/Mesh.ts b/packages/reshader.gl/src/Mesh.ts index e35673f294..c9a8819ef3 100644 --- a/packages/reshader.gl/src/Mesh.ts +++ b/packages/reshader.gl/src/Mesh.ts @@ -4,7 +4,8 @@ import BoundingBox from './BoundingBox.js'; import Geometry from './Geometry'; import Material from './Material'; import { ActiveAttributes, MatrixFunction, MeshOptions, ShaderDefines, ShaderUniformValue, ShaderUniforms } from './types/typings'; -import { Regl } from '@maptalks/regl'; +import DynamicBuffer from './webgpu/DynamicBuffer'; +import DynamicOffsets from './webgpu/DynamicOffsets'; const tempMat4: mat4 = new Array(16) as mat4; @@ -15,7 +16,7 @@ let uuid = 0; * Config: * transparent, castShadow */ -class Mesh { +export default class Mesh { //@internal _version: number //@internal @@ -244,6 +245,10 @@ class Mesh { } } + hasUniform(k: string): boolean { + return this.uniforms[k] !== undefined; + } + getUniform(k: string): ShaderUniformValue { return this.uniforms[k]; } @@ -299,10 +304,10 @@ class Mesh { } //eslint-disable-next-line - getCommandKey(regl: Regl): string { + getCommandKey(device: any): string { if (!this._commandKey || this.dirtyDefines || (this._material && this._materialKeys !== this._material.getUniformKeys())) { //TODO geometry的data变动也可能会改变commandKey,但鉴于geometry一般不会发生变化,暂时不管 - let dKey = this._getDefinesKey(); + let dKey = this.geometry.getCommandKey(device) + '_' + this._getDefinesKey(); const elementType = isNumber(this.getElements()) ? 'count' : 'elements'; dKey += '_' + elementType; dKey += '_' + +(!!this.disableVAO); @@ -330,7 +335,31 @@ class Mesh { // return uniforms; // } - getUniforms(regl: Regl): ShaderUniforms { + getRenderProps(device: any) { + const props = this.getUniforms(device); + props.meshProperties = this.properties; + props.geometryProperties = this._geometry.properties; + props.meshConfig = this.config; + props.count = this._geometry.getDrawCount(); + props.offset = this._geometry.getDrawOffset(); + // command primitive : triangle, triangle strip, etc + props.primitive = this._geometry.getPrimitive(); + return props; + } + + //@internal + _getGeometryAttributes(device, activeAttributes) { + return this._geometry.getREGLData(device, activeAttributes, this.disableVAO); + } + + appendGeoAttributes(props, device, activeAttributes) { + extend(props, this._getGeometryAttributes(device, activeAttributes)); + if (!isSupportVAO(device) || this.disableVAO) { + props.elements = this._geometry.getElements(); + } + } + + getUniforms(device: any): ShaderUniforms { if (this._dirtyUniforms || this._dirtyGeometry || this._material && this._materialVer !== this._material.version) { this._uniformDescriptors = new Set(); this._realUniforms = { @@ -355,7 +384,7 @@ class Mesh { } } if (this._material) { - const materialUniforms = this._material.getUniforms(regl); + const materialUniforms = this._material.getUniforms(device); for (const p in materialUniforms) { if (hasOwn(materialUniforms, p) && !hasOwn(this._realUniforms, p)) { const descriptor = Object.getOwnPropertyDescriptor(materialUniforms, p); @@ -387,7 +416,7 @@ class Mesh { } } if (this._material && this._material.propVersion !== this._materialPropVer) { - const materialUniforms = this._material.getUniforms(regl); + const materialUniforms = this._material.getUniforms(device); for (const p in materialUniforms) { if (hasOwn(materialUniforms, p) && !this._uniformDescriptors.has(p)) { const descriptor = Object.getOwnPropertyDescriptor(materialUniforms, p); @@ -439,31 +468,15 @@ class Mesh { return this._geometry.getElements(); } - //@internal - _getREGLAttrData(regl, activeAttributes) { - return this._geometry.getREGLData(regl, activeAttributes, this.disableVAO); - } - - getREGLProps(regl: Regl, activeAttributes: ActiveAttributes) { - const props = this.getUniforms(regl); - extend(props, this._getREGLAttrData(regl, activeAttributes)); - if (!isSupportVAO(regl) || this.disableVAO) { - props.elements = this._geometry.getElements(); - } - props.meshProperties = this.properties; - props.geometryProperties = this._geometry.properties; - props.meshConfig = this.config; - props.count = this._geometry.getDrawCount(); - props.offset = this._geometry.getDrawOffset(); - // command primitive : triangle, triangle strip, etc - props.primitive = this._geometry.getPrimitive(); - return props; - } dispose() { delete this._geometry; delete this._material; this.uniforms = null; + if (this._meshBuffer) { + this._meshBuffer.dispose(); + delete this._meshBuffer; + } return this; } @@ -537,9 +550,18 @@ class Mesh { } return this.localTransform; } + + _meshBuffer: DynamicBuffer; + // 实现webgpu相关的逻辑 + writeDynamicBuffer(renderProps, bindGroupMapping, pool, dynamicOffsets: DynamicOffsets) { + if (!this._meshBuffer) { + this._meshBuffer = new DynamicBuffer(bindGroupMapping, pool); + } + this._meshBuffer.writeBuffer(renderProps, dynamicOffsets); + return this._meshBuffer; + } } -export default Mesh; function equalDefine(obj0, obj1) { if (!obj0 && !obj1) { diff --git a/packages/reshader.gl/src/Renderer.ts b/packages/reshader.gl/src/Renderer.ts index b50a422b29..feb7569b16 100644 --- a/packages/reshader.gl/src/Renderer.ts +++ b/packages/reshader.gl/src/Renderer.ts @@ -1,4 +1,4 @@ -import REGL, { Regl, Uniforms } from "@maptalks/regl"; +import REGL, { Uniforms } from "@maptalks/regl"; import Scene from "./Scene"; const EMPTY_UNIFORMS = {}; @@ -6,10 +6,10 @@ const EMPTY_UNIFORMS = {}; * A basic renderer to render meshes in fashion of forward rendering */ class Renderer { - regl: Regl + device: any - constructor(regl: Regl) { - this.regl = regl; + constructor(device: any) { + this.device = device; } render(shader, uniforms: Uniforms, scene: Scene, framebuffer: REGL.Framebuffer) { @@ -21,16 +21,16 @@ class Renderer { let count = 0; if (scene) { const { opaques, transparents } = scene.getSortedMeshes(); - count += shader.draw(this.regl, opaques); - count += shader.draw(this.regl, transparents); + count += shader.draw(this.device, opaques); + count += shader.draw(this.device, transparents); } else { - count += shader.draw(this.regl); + count += shader.draw(this.device); } return count; } clear(options: REGL.ClearOptions) { - this.regl.clear(options); + this.device.clear(options); } } diff --git a/packages/reshader.gl/src/Texture2D.ts b/packages/reshader.gl/src/Texture2D.ts index bb0fd5de65..07e27c95ab 100644 --- a/packages/reshader.gl/src/Texture2D.ts +++ b/packages/reshader.gl/src/Texture2D.ts @@ -1,20 +1,24 @@ import parseRGBE from './common/HDR'; -import { isArray, isPowerOfTwo, resizeToPowerOfTwo } from './common/Util'; +import { isArray, isFunction, isPowerOfTwo, resizeToPowerOfTwo, supportNPOT } from './common/Util'; import { default as Texture, REF_COUNT_KEY } from './AbstractTexture'; import { getUniqueTexture } from './common/REGLHelper'; import REGL, { Regl } from '@maptalks/regl'; import DataUtils from './common/DataUtils'; +import { KEY_DISPOSED } from './common/Constants'; /** * config properties: * https://github.com/regl-project/regl/blob/gh-pages/API.md#textures */ -class Texture2D extends Texture { +export default class Texture2D extends Texture { + _version: number = 0; - texParameteri(key: number, value: number) { - if (this._texture) { - (this._texture as any).texParameteri(key, value); - } + get version() { + return this._version; + } + + set version(version) { + throw new Error('Texture2D.version is read only.'); } onLoad({ data }) { @@ -47,7 +51,26 @@ class Texture2D extends Texture { if (this._regl) { this._checkNPOT(this._regl); } - this._updateREGL(); + this._update(); + } + + //@internal + _update() { + if (this._texture && !this._texture[KEY_DISPOSED]) { + this._version++; + if (isFunction(this._texture)) { + this._texture(this.config as any); + } else { + (this._texture as any).update(this.config); + } + } + this.dirty = false; + } + + texParameteri(key: number, value: number) { + if (this._texture) { + (this._texture as any).texParameteri(key, value); + } } createREGLTexture(regl: Regl): REGL.Texture2D { @@ -78,6 +101,15 @@ class Texture2D extends Texture { } } } -} -export default Texture2D; + //@internal + _needPowerOf2(regl) { + if (supportNPOT(regl)) { + return false; + } + const config = this.config; + const isRepeat = config.wrap && config.wrap !== 'clamp' || config.wrapS && config.wrapS !== 'clamp' || + config.wrapT && config.wrapT !== 'clamp'; + return isRepeat || config.min && config.min !== 'nearest' && config.min !== 'linear'; + } +} diff --git a/packages/reshader.gl/src/TextureCube.ts b/packages/reshader.gl/src/TextureCube.ts index 2e7ad5f5b5..d845f96c35 100644 --- a/packages/reshader.gl/src/TextureCube.ts +++ b/packages/reshader.gl/src/TextureCube.ts @@ -8,7 +8,7 @@ class TextureCube extends Texture { } // const faces = this._createFaces(images); // config.faces = faces.map(face => face.data); - this._updateREGL(); + this._update(); } createREGLTexture(regl) { diff --git a/packages/reshader.gl/src/ToonMaterial.js b/packages/reshader.gl/src/ToonMaterial.js deleted file mode 100644 index d431cd9b21..0000000000 --- a/packages/reshader.gl/src/ToonMaterial.js +++ /dev/null @@ -1,13 +0,0 @@ -import PhongMaterial from './PhongMaterial.js'; - -const DEFAULT_UNIFORMS = { - 'toons': 4, - 'specularToons': 2, -}; - -class ToonMaterial extends PhongMaterial { - constructor(uniforms) { - super(uniforms, DEFAULT_UNIFORMS); - } -} -export default ToonMaterial; diff --git a/packages/reshader.gl/src/common/Constants.ts b/packages/reshader.gl/src/common/Constants.ts index 0d21bc3ac6..004281e814 100644 --- a/packages/reshader.gl/src/common/Constants.ts +++ b/packages/reshader.gl/src/common/Constants.ts @@ -10,3 +10,4 @@ export const WEBGL_OPTIONAL_EXTENSIONS = [ 'OES_texture_float', 'OES_texture_float_linear', 'WEBGL_depth_texture', /*'WEBGL_draw_buffers', */'EXT_shader_texture_lod', 'EXT_texture_filter_anisotropic' ]; +export const ERROR_NOT_IMPLEMENTED = 'not implemented'; diff --git a/packages/reshader.gl/src/common/GLTFBundle.ts b/packages/reshader.gl/src/common/GLTFBundle.ts index bf2871b02c..ccf5636c2e 100644 --- a/packages/reshader.gl/src/common/GLTFBundle.ts +++ b/packages/reshader.gl/src/common/GLTFBundle.ts @@ -2,7 +2,6 @@ const getGlobal = function () { if (typeof globalThis !== 'undefined') { return globalThis; } if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } - if (typeof global !== 'undefined') { return global; } throw new Error('unable to locate global object'); }; diff --git a/packages/reshader.gl/src/common/Util.ts b/packages/reshader.gl/src/common/Util.ts index c1f05b2d95..1648b7856c 100644 --- a/packages/reshader.gl/src/common/Util.ts +++ b/packages/reshader.gl/src/common/Util.ts @@ -160,6 +160,19 @@ export function isArray(arr) { (arr instanceof Float64Array); } +export function getArrayCtor(arr) { + return Array.isArray(arr) && Array || + (arr instanceof Uint8Array) && Uint8Array || + (arr instanceof Int8Array) && Int8Array || + (arr instanceof Uint16Array) && Uint16Array || + (arr instanceof Int16Array) && Int16Array || + (arr instanceof Uint32Array) && Uint32Array || + (arr instanceof Int32Array) && Int32Array || + (arr instanceof Uint8ClampedArray) && Uint8ClampedArray || + (arr instanceof Float32Array) && Float32Array || + (arr instanceof Float64Array) && Float64Array; +} + /** * 对两个矢量执行线性推算 * @@ -228,7 +241,7 @@ export function clamp(n: number, min: number, max: number) { */ export function isSupportVAO(regl: Regl) { // return false; - return regl && regl.hasExtension('oes_vertex_array_object'); + return regl && regl.hasExtension && regl.hasExtension('oes_vertex_array_object'); } /** @@ -486,5 +499,5 @@ export function resizeToPowerOfTwo(image: HTMLImageElement | NumberArray, width? } export function supportNPOT(regl: any) { - return regl['_gl'] instanceof WebGL2RenderingContext; + return !regl['_gl'] || (regl['_gl'] instanceof WebGL2RenderingContext); } diff --git a/packages/reshader.gl/src/extensions/KHRTechniquesWebglManager.js b/packages/reshader.gl/src/extensions/KHRTechniquesWebglManager.js index 167dfcf1c6..bab2868c88 100644 --- a/packages/reshader.gl/src/extensions/KHRTechniquesWebglManager.js +++ b/packages/reshader.gl/src/extensions/KHRTechniquesWebglManager.js @@ -182,7 +182,7 @@ export default class KHRTechniquesWebglManager { } 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] = { diff --git a/packages/reshader.gl/src/gltf/GLTFPack.js b/packages/reshader.gl/src/gltf/GLTFPack.js index 908172f637..e9b9763cbc 100644 --- a/packages/reshader.gl/src/gltf/GLTFPack.js +++ b/packages/reshader.gl/src/gltf/GLTFPack.js @@ -489,7 +489,7 @@ function createGeometry(primitive, regl, hasAOMap) { // 把原有的array赋给attr,用于计算 bbox、buildUniqueVertex attrs[name] = extend({}, attributes[name]); if (regl) { - attrs[name].buffer = getUniqueREGLBuffer(regl, attributes[name], { dimension: attributes[name].itemSize }); + attrs[name].buffer = getUniqueREGLBuffer(regl, attributes[name], { dimension: attributes[name].itemSize, name }); } } diff --git a/packages/reshader.gl/src/index.ts b/packages/reshader.gl/src/index.ts index e77065b659..5c45a3ac34 100644 --- a/packages/reshader.gl/src/index.ts +++ b/packages/reshader.gl/src/index.ts @@ -116,3 +116,7 @@ export { quat, quat2, vec2, vec3, vec4 } from 'gl-matrix'; + +export { default as GraphicsDevice } from './webgpu/GraphicsDevice'; +export { default as GraphicsTexture } from './webgpu/GraphicsTexture'; +export { default as GraphicsFramebuffer } from './webgpu/GraphicsFramebuffer'; diff --git a/packages/reshader.gl/src/pbr/PBRHelper.js b/packages/reshader.gl/src/pbr/PBRHelper.js index f86e8bd41e..a86d6cb686 100644 --- a/packages/reshader.gl/src/pbr/PBRHelper.js +++ b/packages/reshader.gl/src/pbr/PBRHelper.js @@ -404,8 +404,8 @@ export function generateDFGLUT(regl, size, sampleSize, roughnessLevels) { mag : 'nearest' }); - const quadBuf = regl.buffer(quadVertices); - const quadTexBuf = regl.buffer(quadTexcoords); + const quadBuf = regl.buffer({ data: quadVertices, name: 'aPosition' }); + const quadTexBuf = regl.buffer({ data: quadTexcoords, name: 'aTexCoord' }); const fbo = regl.framebuffer({ radius : size, colorType: 'uint8', diff --git a/packages/reshader.gl/src/pbr/old/PBRHelper.js b/packages/reshader.gl/src/pbr/old/PBRHelper.js index 9335984698..99ea819aca 100644 --- a/packages/reshader.gl/src/pbr/old/PBRHelper.js +++ b/packages/reshader.gl/src/pbr/old/PBRHelper.js @@ -244,8 +244,8 @@ function generateBRDFLUT(regl, size, sampleSize, roughnessLevels) { mag : 'nearest' }); - const quadBuf = regl.buffer(quadVertices); - const quadTexBuf = regl.buffer(quadTexcoords); + const quadBuf = regl.buffer({ data: quadVertices, name: 'aPosition' }); + const quadTexBuf = regl.buffer({ data: quadTexcoords, name: 'aTexCoord' }); const fbo = regl.framebuffer({ radius : size, type : 'float', diff --git a/packages/reshader.gl/src/shader/BloomExtractShader.js b/packages/reshader.gl/src/shader/BloomExtractShader.js index c1939f69b1..79b2d6e94e 100644 --- a/packages/reshader.gl/src/shader/BloomExtractShader.js +++ b/packages/reshader.gl/src/shader/BloomExtractShader.js @@ -24,11 +24,7 @@ class BloomExtractShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['bloom_extract']) { - this.commands['bloom_extract'] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands['bloom_extract'] = this.createMeshCommand(regl, mesh); } return this.commands['bloom_extract']; } diff --git a/packages/reshader.gl/src/shader/BoxBlurShader.js b/packages/reshader.gl/src/shader/BoxBlurShader.js index b77472f7cb..70fe226ef0 100644 --- a/packages/reshader.gl/src/shader/BoxBlurShader.js +++ b/packages/reshader.gl/src/shader/BoxBlurShader.js @@ -29,11 +29,7 @@ class BoxBlurShader extends QuadShader { getMeshCommand(regl, mesh) { const key = 'box_blur_' + this._blurOffset; if (!this.commands[key]) { - this.commands[key] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands[key] = this.createMeshCommand(regl, mesh); } return this.commands[key]; } diff --git a/packages/reshader.gl/src/shader/BoxShadowBlurShader.js b/packages/reshader.gl/src/shader/BoxShadowBlurShader.js index a98fa0478b..563410e052 100644 --- a/packages/reshader.gl/src/shader/BoxShadowBlurShader.js +++ b/packages/reshader.gl/src/shader/BoxShadowBlurShader.js @@ -17,11 +17,7 @@ class BoxShadowBlurShader extends QuadShader { getMeshCommand(regl, mesh) { const key = 'box_shadow_blur_' + this._blurOffset; if (!this.commands[key]) { - this.commands[key] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands[key] = this.createMeshCommand(regl, mesh); } return this.commands[key]; } diff --git a/packages/reshader.gl/src/shader/CopyDepthShader.js b/packages/reshader.gl/src/shader/CopyDepthShader.js index 9cf843d6a9..3c31862f48 100644 --- a/packages/reshader.gl/src/shader/CopyDepthShader.js +++ b/packages/reshader.gl/src/shader/CopyDepthShader.js @@ -37,11 +37,7 @@ class CopyDepthShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['copy_depth']) { - this.commands['copy_depth'] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands['copy_depth'] = this.createMeshCommand(regl, mesh); } return this.commands['copy_depth']; } diff --git a/packages/reshader.gl/src/shader/CopyShader.js b/packages/reshader.gl/src/shader/CopyShader.js index ac7881aca1..3a025a7087 100644 --- a/packages/reshader.gl/src/shader/CopyShader.js +++ b/packages/reshader.gl/src/shader/CopyShader.js @@ -24,11 +24,7 @@ class CopyShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['copy']) { - this.commands['copy'] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands['copy'] = this.createMeshCommand(regl, mesh); } return this.commands['copy']; } diff --git a/packages/reshader.gl/src/shader/ExtentPass.js b/packages/reshader.gl/src/shader/ExtentPass.js index 1c347aade7..4ff5154fcb 100644 --- a/packages/reshader.gl/src/shader/ExtentPass.js +++ b/packages/reshader.gl/src/shader/ExtentPass.js @@ -17,8 +17,8 @@ class ExtentPass { } _init() { - this._maskColorFbo = this.renderer.regl.framebuffer({ - color: this.renderer.regl.texture({ + this._maskColorFbo = this.renderer.device.framebuffer({ + color: this.renderer.device.texture({ width: 1, height: 1, wrap: 'clamp', @@ -27,8 +27,8 @@ class ExtentPass { }), depth: true }); - this._maskModeFbo = this.renderer.regl.framebuffer({ - color: this.renderer.regl.texture({ + this._maskModeFbo = this.renderer.device.framebuffer({ + color: this.renderer.device.texture({ width: 1, height: 1, wrap: 'clamp', @@ -57,7 +57,7 @@ class ExtentPass { this._maskModeShader = new MeshShader({ vert, frag: maskModeExtent, - uniforms, + uniforms, extraCommandProps: { viewport: this._viewport, } diff --git a/packages/reshader.gl/src/shader/FxaaShader.js b/packages/reshader.gl/src/shader/FxaaShader.js index ce442f8567..ac2106d181 100644 --- a/packages/reshader.gl/src/shader/FxaaShader.js +++ b/packages/reshader.gl/src/shader/FxaaShader.js @@ -24,11 +24,7 @@ class FxaaShader extends QuadShader { getMeshCommand(regl, mesh) { const key = this.dkey || ''; if (!this.commands[key + '_fxaa']) { - this.commands[key + '_fxaa'] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands[key + '_fxaa'] = this.createMeshCommand(regl, mesh); } return this.commands[key + '_fxaa']; } diff --git a/packages/reshader.gl/src/shader/ImageShader.js b/packages/reshader.gl/src/shader/ImageShader.js index d7c670958b..8cdcf89531 100644 --- a/packages/reshader.gl/src/shader/ImageShader.js +++ b/packages/reshader.gl/src/shader/ImageShader.js @@ -28,12 +28,7 @@ class ImageShader extends MeshShader { getMeshCommand(regl, mesh) { const key = mesh.getCommandKey(regl); if (!this.commands['image_' + key]) { - const defines = mesh.getDefines(); - this.commands['image_' + key] = this.createREGLCommand( - regl, - defines, - mesh.getElements() - ); + this.commands['image_' + key] = this.createMeshCommand(regl, mesh); } return this.commands['image_' + key]; } diff --git a/packages/reshader.gl/src/shader/MeshShader.js b/packages/reshader.gl/src/shader/MeshShader.js index 92e7523751..1234225a5c 100644 --- a/packages/reshader.gl/src/shader/MeshShader.js +++ b/packages/reshader.gl/src/shader/MeshShader.js @@ -1,9 +1,8 @@ -import Shader from './Shader.js'; -import InstancedMesh from '../InstancedMesh.js'; +import Shader from './Shader'; class MeshShader extends Shader { - draw(regl, meshes) { + draw(device, meshes) { if (!meshes || !meshes.length) { return 0; } @@ -18,37 +17,41 @@ class MeshShader extends Shader { continue; } if (!meshes[i].geometry.getDrawCount() || !this._runFilter(meshes[i])) { - //此处regl有个潜在的bug: - //如果count为0的geometry不过滤掉,regl生成的函数中,bind的texture不会执行unbind + //此处device有个潜在的bug: + //如果count为0的geometry不过滤掉,device生成的函数中,bind的texture不会执行unbind if (i === l - 1 && preCommand && props.length) { preCommand(props); } continue; } - const command = this.getMeshCommand(regl, meshes[i]); + + const v = meshes[i].getRenderProps(device); + this._ensureContextDefines(v); + v.shaderContext = this.context; + v.meshObject = meshes[i]; + this.appendDescUniforms(device, v); + + const command = this.getMeshCommand(device, meshes[i], v); + meshes[i].appendGeoAttributes(v, device, command.activeAttributes); //run command one by one, for debug - // const props = extend({}, this.context, meshes[i].getREGLProps()); + // const props = extend({}, this.context, meshes[i].getRenderProps()); // console.log(i); // command(props); if (props.length && preCommand !== command) { //batch mode - preCommand(props); + this.run(device, command, props); props.length = 0; } - const v = meshes[i].getREGLProps(regl, command.activeAttributes); - this._ensureContextDefines(v); - v.shaderContext = this.context; - this.appendDescUniforms(regl, v); props.push(v); count++; if (i < l - 1) { preCommand = command; } else if (i === l - 1) { - command(props); + this.run(device, command, props); } } return count; @@ -109,42 +112,36 @@ class MeshShader extends Shader { return filters(m); } - getMeshCommand(regl, mesh) { + getMeshCommand(device, mesh, uniformValues) { if (!this._cmdKeys) { this._cmdKeys = {}; } - const key = this.dkey || 'default'; + const material = mesh.getMaterial(); + let doubleSided = false; + if (material) { + doubleSided = material.doubleSided; + } + const key = this.getShaderCommandKey(device, mesh, uniformValues, doubleSided); let storedKeys = this._cmdKeys[key]; if (!storedKeys) { storedKeys = this._cmdKeys[key] = {}; } - const meshKey = mesh.getCommandKey(regl); + const meshKey = mesh.getCommandKey(device); if (!storedKeys[meshKey]) { - storedKeys[meshKey] = key + '_' + mesh.getCommandKey(regl); + storedKeys[meshKey] = key + '_' + mesh.getCommandKey(); } const dKey = storedKeys[meshKey]; // const key = this.dkey || ''; - // const dKey = key + '_' + mesh.getCommandKey(regl); + // const dKey = key + '_' + mesh.getCommandKey(); let command = this.commands[dKey]; if (!command) { - const defines = mesh.getDefines(); - const material = mesh.getMaterial(); + + const commandProps = {}; - if (material) { - const doubleSided = material.doubleSided; - if (doubleSided && this.extraCommandProps) { - commandProps.cull = { enable: false }; - } + if (doubleSided && this.extraCommandProps) { + commandProps.cull = { enable: false }; } - command = this.commands[dKey] = - this.createREGLCommand( - regl, - defines, - mesh.getElements(), - mesh instanceof InstancedMesh, - mesh.disableVAO, - commandProps - ); + command = this.commands[dKey] = this.createMeshCommand(device, mesh, commandProps, uniformValues); } return command; } diff --git a/packages/reshader.gl/src/shader/PostProcessShader.js b/packages/reshader.gl/src/shader/PostProcessShader.js index 3c0e78fdb2..3be434ba45 100644 --- a/packages/reshader.gl/src/shader/PostProcessShader.js +++ b/packages/reshader.gl/src/shader/PostProcessShader.js @@ -23,11 +23,7 @@ class PostProcessShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['postprocess']) { - this.commands['postprocess'] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands['postprocess'] = this.createMeshCommand(regl, mesh); } return this.commands['postprocess']; } diff --git a/packages/reshader.gl/src/shader/QuadShader.js b/packages/reshader.gl/src/shader/QuadShader.js index 4b636ab1e9..61201502e9 100644 --- a/packages/reshader.gl/src/shader/QuadShader.js +++ b/packages/reshader.gl/src/shader/QuadShader.js @@ -51,11 +51,7 @@ class QuadShader extends MeshShader { getMeshCommand(regl) { const keys = this.dkey || ''; if (!this.commands[keys + '_quad']) { - this.commands[keys + '_quad'] = this.createREGLCommand( - regl, - null, - this._quadMesh[0].getElements() - ); + this.commands[keys + '_quad'] = this.createMeshCommand(regl, this._quadMesh[0]); } return this.commands[keys + '_quad']; } diff --git a/packages/reshader.gl/src/shader/Shader.js b/packages/reshader.gl/src/shader/Shader.ts similarity index 55% rename from packages/reshader.gl/src/shader/Shader.js rename to packages/reshader.gl/src/shader/Shader.ts index 2af5a1b190..ebac8987ac 100644 --- a/packages/reshader.gl/src/shader/Shader.js +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -1,6 +1,17 @@ import { extend, isString, isFunction, isNumber, isSupportVAO, hasOwn, hashCode } from '../common/Util.js'; + import ShaderLib from '../shaderlib/ShaderLib.js'; import { KEY_DISPOSED } from '../common/Constants.js'; +import { ShaderUniforms, ShaderUniformValue } from '../types/typings'; +import PipelineDescriptor from '../webgpu/common/PipelineDesc'; +import InstancedMesh from '../InstancedMesh'; +import Mesh from '../Mesh'; +import DynamicBuffer from '../webgpu/DynamicBuffer'; +import CommandBuilder from '../webgpu/CommandBuilder'; +import GraphicsDevice from '../webgpu/GraphicsDevice'; +import GraphicsFramebuffer from '../webgpu/GraphicsFramebuffer'; +import DynamicOffsets from '../webgpu/DynamicOffsets'; + const UNIFORM_TYPE = { function : 'function', @@ -11,8 +22,30 @@ let uid = 0; const activeVarsCache = {}; -class Shader { - constructor({ vert, frag, uniforms, defines, extraCommandProps }) { +export class GLShader { + vert: string; + frag: string; + uid: number; + version: number; + //@internal + uniforms: ShaderUniformValue[]; + //@internal + contextDesc: Record; + extraCommandProps: any; + //@internal + commands: any; + //@internal + _shaderDefines: Record; + //@internal + dkey: string; + //@internal + context: any; + //@internal + contextKeys: string; + name: string; + + constructor({ vert, frag, uniforms, defines, extraCommandProps, name }) { + this.name = name; this.vert = vert; this.frag = frag; const shaderId = uid++; @@ -80,70 +113,14 @@ class Shader { return this; } - /** - * Get shader's context uniforms values - * @param {Object} meshProps - mesh uniforms - */ - appendDescUniforms(regl, meshProps) { - // const context = this.context; - //TODO 这里以前是extend2,需要明确改用extend后是否会有bug - // const props = extend(meshProps, context); - const uniforms = meshProps; - const desc = this.contextDesc; - for (const p in desc) { - if (!desc[p]) { - continue; - } - if (desc[p].type === 'array') { - //an array uniform's value - const name = p, len = desc[p].length; - // change uniform value to the following form as regl requires: - // foo[0]: 'value' - // foo[1]: 'value' - // foo[2]: 'value' - let values = meshProps[p]; - if (desc[p].fn) { - // an array function - values = desc[p].fn(null, meshProps); - } - if (!values) { - continue; - } - if (values.length !== len) { - throw new Error(`${name} uniform's length is not ${len}`); - } - uniforms[name] = uniforms[name] || {}; - for (let i = 0; i < len; i++) { - uniforms[name][`${i}`] = values[i].getREGLTexture ? values[i].getREGLTexture(regl) : values[i]; - } - } else if (desc[p].type === 'function') { - if (!Object.getOwnPropertyDescriptor(uniforms, p)) { - Object.defineProperty(uniforms, p, { - configurable: false, - enumerable: true, - get: function () { - return desc[p].fn(null, meshProps); - } - }); - } - - } - } - - return uniforms; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + run(regl: any, command, props) { + return command(props); } - /** - * Set or get context uniform to or from the shader - * @returns {this} - */ - setUniforms(uniforms) { - if (uniforms['modelMatrix'] || uniforms['positionMatrix']) { - throw new Error('modelMatrix or positionMatrix is reserved uniform name for Mesh, please change to another name'); - } - this.contextKeys = uniforms ? Object.keys(uniforms).join() : null; - this.context = uniforms; - return this; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getShaderCommandKey(device, mesh, uniformValues, doubleSided) { + return this.dkey || 'default'; } getVersion(regl, source) { @@ -219,7 +196,23 @@ class Shader { return activeVarsCache[cacheKey]; } - createREGLCommand(regl, materialDefines, elements, isInstanced, disableVAO, commandProps = {}) { + + _insertDefines(source, defines) { + const defineHeaders = []; + for (const p in defines) { + if (hasOwn(defines, p) && !isFunction(defines[p])) { + defineHeaders.push(`#define ${p} ${defines[p]}\n`); + } + } + return defineHeaders.join('') + source; + } + + createMeshCommand(regl, mesh, commandProps = {}) { + const materialDefines = mesh.getDefines(); + const elements = mesh.getElements(); + const isInstanced = mesh instanceof InstancedMesh; + const disableVAO = mesh.disableVAO; + const isVAO = isSupportVAO(regl) && !disableVAO; const defines = extend({}, this.shaderDefines || {}, materialDefines || {}); const vertSource = this._insertDefines(this.vert, defines); @@ -265,7 +258,7 @@ class Shader { } } - const command = { + const command: any = { vert, frag, uniforms, attributes }; if (isVAO) { @@ -304,19 +297,78 @@ class Shader { delete this.frag; } - _insertDefines(source, defines) { - const defineHeaders = []; - for (const p in defines) { - if (hasOwn(defines, p) && !isFunction(defines[p])) { - defineHeaders.push(`#define ${p} ${defines[p]}\n`); + /** + * Get shader's context uniforms values + * @param {Object} meshProps - mesh uniforms + */ + appendDescUniforms(regl, meshProps) { + // const context = this.context; + //TODO 这里以前是extend2,需要明确改用extend后是否会有bug + // const props = extend(meshProps, context); + const uniforms = meshProps; + const desc = this.contextDesc; + for (const p in desc) { + if (!desc[p]) { + continue; + } + if (desc[p].type === 'array') { + //an array uniform's value + const name = p, len = desc[p].length; + // change uniform value to the following form as regl requires: + // foo[0]: 'value' + // foo[1]: 'value' + // foo[2]: 'value' + let values = meshProps[p]; + if (desc[p].fn) { + // an array function + values = desc[p].fn(null, meshProps); + } + if (!values) { + continue; + } + if (values.length !== len) { + throw new Error(`${name} uniform's length is not ${len}`); + } + uniforms[name] = uniforms[name] || {}; + for (let i = 0; i < len; i++) { + uniforms[name][`${i}`] = values[i].getREGLTexture ? values[i].getREGLTexture(regl) : values[i]; + } + } else if (desc[p].type === 'function') { + if (!Object.getOwnPropertyDescriptor(uniforms, p)) { + Object.defineProperty(uniforms, p, { + configurable: false, + enumerable: true, + get: function () { + return desc[p].fn(null, meshProps); + } + }); + } + } } - return defineHeaders.join('') + source; + + return uniforms; } + /** + * Set or get context uniform to or from the shader + * @returns {this} + */ + setUniforms(uniforms) { + if (uniforms['modelMatrix'] || uniforms['positionMatrix']) { + throw new Error('modelMatrix or positionMatrix is reserved uniform name for Mesh, please change to another name'); + } + this.contextKeys = uniforms ? Object.keys(uniforms).join() : null; + this.context = uniforms; + return this; + } + + _compileSource() { this.vert = ShaderLib.compile(this.vert); - this.frag = ShaderLib.compile(this.frag); + if (this.frag) { + this.frag = ShaderLib.compile(this.frag); + } } } @@ -326,4 +378,187 @@ function parseArrayName(p) { return { name, len }; } -export default Shader; +const pipelineDesc = new PipelineDescriptor(); + +export default class GPUShader extends GLShader { + //@internal + gpuCommands: any[]; + //@internal + isGPU: boolean; + //@internal + _presentationFormat: GPUTextureFormat; + //@internal + _bindGroupCache: Record; + //@internal + _buffers: Record; + //@internal + _passEncoders: Record; + //@internal + _currentPassEncoder: GPURenderPassEncoder + //@internal + _gpuFramebuffer: GraphicsFramebuffer; + //@internal + _dynamicOffsets: DynamicOffsets; + + getShaderCommandKey(device, mesh, renderValues, doubleSided) { + if (device && device.wgpu) { + // 获取pipeline所需要的特征变量,即任何变量发生变化后,就需要创建新的pipeline + const fbo = this._gpuFramebuffer; + const commandProps = this.extraCommandProps; + pipelineDesc.readFromREGLCommand(commandProps, mesh, renderValues, doubleSided, fbo); + return pipelineDesc.getSignatureKey(); + } else { + // regl + return super.getShaderCommandKey(device, mesh, renderValues, doubleSided); + } + } + + createMeshCommand(device: any, mesh: Mesh, commandProps: any) { + if (device && device.wgpu) { + // 生成期: + // 1. 负责对 wgsl 做预处理,生成最终执行的wgsl代码 + // 2. 解析wgsl,生成 bind group mapping 信息,用于运行时,mesh生成bind group + // 3. 解析wgsl,获得全局 uniform 变量名和类型 + // 4. 生成 layout 和 pipeline + + // preprocess vert and frag codes + const uniformValues = this.context; + const fbo = this._gpuFramebuffer; + const builder = new CommandBuilder(this.name, device, this.vert, this.frag, mesh, uniformValues); + return builder.build(pipelineDesc, fbo); + } else { + // regl + return super.createMeshCommand(device, mesh, commandProps); + } + } + + run(deviceOrRegl: any, command, props) { + if (!deviceOrRegl.wgpu) { + // regl command + return super.run(deviceOrRegl, command, props); + } + const shaderUniforms = this.context; + const device = deviceOrRegl as GraphicsDevice; + this.isGPU = true; + const buffersPool = device.dynamicBufferPool; + const passEncoder: GPURenderPassEncoder = this._getCurrentRenderPassEncoder(device); + passEncoder.setPipeline(command.pipeline); + + const { key, bindGroupFormat, pipeline, vertexInfo } = command; + const layout = pipeline.getBindGroupLayout(0); + // 1. 生成shader uniform 需要的dynamic buffer + if (!this._buffers) { + this._buffers = {}; + } + let shaderBuffer = this._buffers[key] as DynamicBuffer; + if (!shaderBuffer) { + shaderBuffer = this._buffers[key] = new DynamicBuffer(bindGroupFormat.getShaderUniforms(), buffersPool); + } + if (!this._bindGroupCache) { + this._bindGroupCache = {}; + } + + if (!this._dynamicOffsets) { + this._dynamicOffsets = new DynamicOffsets(); + } + this._dynamicOffsets.reset(); + // 向buffer中填入shader uniform值 + shaderBuffer.writeBuffer(shaderUniforms, this._dynamicOffsets); + const shaderDynamicOffsets = this._dynamicOffsets.items.slice(); + + for (let i = 0; i < props.length; i++) { + this._dynamicOffsets.reset(); + const mesh = props[i].meshObject as Mesh; + // 获取mesh的dynamicBuffer + const meshBuffer = mesh.writeDynamicBuffer(props[i], bindGroupFormat.getMeshUniforms(), buffersPool, this._dynamicOffsets); + const groupKey = meshBuffer.version + '-' + shaderBuffer.version; + // 获取或者生成bind group + let bindGroup = this._bindGroupCache[groupKey]; + if (!bindGroup || (bindGroup as any).outdated) { + bindGroup = bindGroupFormat.createBindGroup(device, mesh, shaderUniforms, layout, shaderBuffer, meshBuffer); + // 缓存bind group,只要buffer没有发生变化,即可以重用 + // TODO 可以考虑每帧开始把缓存 bind group 标记为 retire,每帧结束时把不是 current 的 bind group 销毁掉 + this._bindGroupCache[groupKey] = bindGroup; + } + + // 获取 dynamicOffsets + this._dynamicOffsets.addItems(shaderDynamicOffsets); + const dynamicOffsets = this._dynamicOffsets.getDynamicOffsets(); + passEncoder.setBindGroup(0, bindGroup, dynamicOffsets); + + let instancedMesh; + if (mesh instanceof InstancedMesh) { + instancedMesh = mesh as InstancedMesh; + } + for (const name in vertexInfo) { + const vertex = vertexInfo[name]; + let vertexBuffer = mesh.geometry.getBuffer(name); + if (!vertexBuffer && instancedMesh) { + vertexBuffer = instancedMesh.getInstancedBuffer(name); + } + passEncoder.setVertexBuffer(vertex.location, vertexBuffer); + } + + const elements = mesh.getElements(); + const drawOffset = mesh.geometry.getDrawOffset(); + const drawCount = mesh.geometry.getDrawCount(); + let instanceCount = 1; + if (mesh instanceof InstancedMesh) { + const instancedMesh = mesh as InstancedMesh; + instanceCount = instancedMesh.instanceCount; + } + if (isNumber(elements)) { + passEncoder.draw(drawCount, instanceCount, drawOffset); + } else { + passEncoder.setIndexBuffer(elements.getBuffer(), elements.getFormat()); + passEncoder.drawIndexed(drawCount, instanceCount, drawOffset); + } + } + passEncoder.end(); + } + + _getCurrentRenderPassEncoder(device: GraphicsDevice) { + // stencilLoadOp?: GPULoadOp, + // stencilClearValue?: number, + // colorLoadOp?: GPULoadOp, + // depthLoadOp?: GPULoadOp + return device.getRenderPassEncoder(this._gpuFramebuffer); + } + + setFramebuffer(framebuffer) { + if (!framebuffer) { + if (this._gpuFramebuffer) { + this._gpuFramebuffer = null; + return this; + } + return super.setFramebuffer(framebuffer); + } + if (!framebuffer.getRenderPassDescriptor) { + return super.setFramebuffer(framebuffer); + } + this._gpuFramebuffer = framebuffer; + return this; + } + + dispose() { + if (!this.isGPU) { + super.dispose(); + return; + } + for (const key in this._buffers) { + if (this._buffers[key]) { + this._buffers[key].dispose(); + } + } + const commands = this.gpuCommands; + for (let i = 0; i < commands.length; i++) { + if (!commands[i]) { + continue; + } + if (commands[i].bindGroupFormat) { + commands[i].bindGroupFormat.dispose(); + } + } + } +} + diff --git a/packages/reshader.gl/src/shader/SsrCombineShader.js b/packages/reshader.gl/src/shader/SsrCombineShader.js index b7cf6e736a..1f2a2eece9 100644 --- a/packages/reshader.gl/src/shader/SsrCombineShader.js +++ b/packages/reshader.gl/src/shader/SsrCombineShader.js @@ -24,11 +24,7 @@ class SsrCombineShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssr_combine']) { - this.commands['ssr_combine'] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands['ssr_combine'] = this.createMeshCommand(regl, mesh); } return this.commands['ssr_combine']; } diff --git a/packages/reshader.gl/src/shader/SsrMipmapShader.js b/packages/reshader.gl/src/shader/SsrMipmapShader.js index 1918546a2a..adeb212b9a 100644 --- a/packages/reshader.gl/src/shader/SsrMipmapShader.js +++ b/packages/reshader.gl/src/shader/SsrMipmapShader.js @@ -23,11 +23,7 @@ class SsrMipmapShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssr_mimap']) { - this.commands['ssr_mimap'] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands['ssr_mimap'] = this.createMeshCommand(regl, mesh); } return this.commands['ssr_mimap']; } diff --git a/packages/reshader.gl/src/shader/TaaShader.js b/packages/reshader.gl/src/shader/TaaShader.js index 940c7507ab..7f44b3753b 100644 --- a/packages/reshader.gl/src/shader/TaaShader.js +++ b/packages/reshader.gl/src/shader/TaaShader.js @@ -26,11 +26,7 @@ class TaaShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['taa']) { - this.commands['taa'] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands['taa'] = this.createMeshCommand(regl, mesh); } return this.commands['taa']; } diff --git a/packages/reshader.gl/src/shadow/ShadowDisplayShader.js b/packages/reshader.gl/src/shadow/ShadowDisplayShader.js index 5a50d38c19..5ac8091b48 100644 --- a/packages/reshader.gl/src/shadow/ShadowDisplayShader.js +++ b/packages/reshader.gl/src/shadow/ShadowDisplayShader.js @@ -44,11 +44,7 @@ class ShadowDisplayShader extends MeshShader { getMeshCommand(regl, mesh) { if (!this.commands['shadow_display']) { - this.commands['shadow_display'] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands['shadow_display'] = this.createMeshCommand(regl, mesh); } return this.commands['shadow_display']; } diff --git a/packages/reshader.gl/src/shadow/ShadowPass.js b/packages/reshader.gl/src/shadow/ShadowPass.js index dce2d4dc78..8c799e6a7f 100644 --- a/packages/reshader.gl/src/shadow/ShadowPass.js +++ b/packages/reshader.gl/src/shadow/ShadowPass.js @@ -80,13 +80,13 @@ class ShadowPass { } _init(defines) { - const regl = this.renderer.regl; + const regl = this.renderer.device; const type = 'uint8'; const width = this.width, height = this.height; this.depthTex = regl.texture({ width, height, - format: 'rgb', + format: 'rgba', type, min: 'nearest', mag: 'nearest', @@ -105,7 +105,7 @@ class ShadowPass { this.blurTex = regl.texture({ width, height, - format : 'rgb', + format : 'rgba', type, min : 'linear', mag : 'linear' diff --git a/packages/reshader.gl/src/skybox/SkyboxShader.js b/packages/reshader.gl/src/skybox/SkyboxShader.js index 5f9c7e0a34..e3063e780e 100644 --- a/packages/reshader.gl/src/skybox/SkyboxShader.js +++ b/packages/reshader.gl/src/skybox/SkyboxShader.js @@ -58,7 +58,7 @@ class SkyboxShader extends MeshShader { _createSkyboxMesh(regl) { const geometry = new Geometry( { - aPosition : new Int8Array(skyboxData.vertices) + aPosition: new Int8Array(skyboxData.vertices) }, null, skyboxData.vertices.length / 3 diff --git a/packages/reshader.gl/src/ssao/SsaoBlurShader.js b/packages/reshader.gl/src/ssao/SsaoBlurShader.js index 43ae398ee4..6f3db3b434 100644 --- a/packages/reshader.gl/src/ssao/SsaoBlurShader.js +++ b/packages/reshader.gl/src/ssao/SsaoBlurShader.js @@ -23,11 +23,7 @@ class SsaoBlurShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssao_blur']) { - this.commands['ssao_blur'] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands['ssao_blur'] = this.createMeshCommand(regl, mesh); } return this.commands['ssao_blur']; } diff --git a/packages/reshader.gl/src/ssao/SsaoExtractShader.js b/packages/reshader.gl/src/ssao/SsaoExtractShader.js index 61b4a5c8b8..368af5c55a 100644 --- a/packages/reshader.gl/src/ssao/SsaoExtractShader.js +++ b/packages/reshader.gl/src/ssao/SsaoExtractShader.js @@ -54,11 +54,7 @@ class SsaoExtactShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssao_extract']) { - this.commands['ssao_extract'] = this.createREGLCommand( - regl, - null, - mesh.getElements() - ); + this.commands['ssao_extract'] = this.createMeshCommand(regl, mesh); } return this.commands['ssao_extract']; } diff --git a/packages/reshader.gl/src/types/typings.ts b/packages/reshader.gl/src/types/typings.ts index db368f495a..5385d0d8dc 100644 --- a/packages/reshader.gl/src/types/typings.ts +++ b/packages/reshader.gl/src/types/typings.ts @@ -1,6 +1,7 @@ import REGL, { Texture, Texture2DOptions, TextureImageData } from "@maptalks/regl"; import { mat4 } from "gl-matrix"; import AbstractTexture from "../AbstractTexture"; +import GraphicsTexture from "../webgpu/GraphicsTexture"; export type UrlModifierFunction = (url: string) => string @@ -37,7 +38,7 @@ export type GeometryElements = { array: NumberArray } export type AttributeKey = { key: string } export type ActiveAttributes = { name: string, type: number }[] & AttributeKey -export type ShaderUniformValue = number | boolean | string | NumberArray | null | AbstractTexture | Texture +export type ShaderUniformValue = number | boolean | string | NumberArray | null | AbstractTexture | Texture | GraphicsTexture export type ShaderUniforms = { meshConfig?: MeshOptions, @@ -85,5 +86,6 @@ export type TextureConfig = { */ maxRange?: number, promise?: Promise, - persistent?: boolean + persistent?: boolean, + compare?: GPUCompareFunction, } & Texture2DOptions; diff --git a/packages/reshader.gl/src/weather/fog/FogShader.js b/packages/reshader.gl/src/weather/fog/FogShader.js index 2d7b558568..da1311266d 100644 --- a/packages/reshader.gl/src/weather/fog/FogShader.js +++ b/packages/reshader.gl/src/weather/fog/FogShader.js @@ -24,7 +24,7 @@ class FogShader extends QuadShader { // getMeshCommand(regl, mesh) { // const key = this.dkey || ''; // if (!this.commands[key + '_fxaa']) { - // this.commands[key + '_fxaa'] = this.createREGLCommand( + // this.commands[key + '_fxaa'] = this.createMeshCommand( // regl, // null, // mesh.getElements() diff --git a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts new file mode 100644 index 0000000000..a4430ec59e --- /dev/null +++ b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts @@ -0,0 +1,139 @@ +import { ResourceType } from "wgsl_reflect"; +import { toGPUSampler } from "./common/ReglTranslator"; +import DynamicBuffer from "./DynamicBuffer"; +import Mesh from "../Mesh"; +import Texture2D from "../Texture2D"; +import GraphicsDevice from "./GraphicsDevice"; +import { ShaderUniforms } from "../types/typings"; +import AbstractTexture from "../AbstractTexture"; +import GraphicsTexture from "./GraphicsTexture"; + +export default class BindGroupFormat { + bytes: number; + //@internal + alignment: number; + //@internal + groups: any; + //@internal + _shaderUniforms: any; + //@internal + _meshUniforms: any; + + constructor(bindGroupMapping, minUniformBufferOffsetAlignment) { + this.groups = bindGroupMapping.groups; + this.alignment = minUniformBufferOffsetAlignment; + this._parse(bindGroupMapping); + } + + getShaderUniforms() { + return this._shaderUniforms; + } + + getMeshUniforms() { + return this._meshUniforms; + } + + _parse(bindGroupMapping) { + this._shaderUniforms = []; + this._shaderUniforms.index = 0; + this._shaderUniforms.totalSize = 0; + this._meshUniforms = []; + this._meshUniforms.index = 0; + this._meshUniforms.totalSize = 0; + const groups = bindGroupMapping.groups; + if (!groups) { + return; + } + const group = groups[0]; + if (!group) { + return; + } + for (let i = 0; i < group.length; i++) { + const uniform = group[i]; + if (!uniform) { + continue; + } + if (uniform.isGlobal) { + let index = this._shaderUniforms.index; + this._shaderUniforms[index++] = uniform; + this._shaderUniforms.index = index; + this._shaderUniforms.totalSize += uniform.size; + } else { + let index = this._meshUniforms.index; + this._meshUniforms[index++] = uniform; + this._meshUniforms.index = index; + this._meshUniforms.totalSize += uniform.size; + } + } + } + + createBindGroup(device: GraphicsDevice, mesh: Mesh, shaderUniforms: ShaderUniforms, layout: GPUBindGroupLayout, shaderBuffer: DynamicBuffer, meshBuffer: DynamicBuffer) { + if (!this.groups) { + return device.wgpu.createBindGroup({ + layout, + label: '', + entries: [] + }); + } + const groups = this.groups[0]; + const entries = []; + const textures = []; + for (let i = 0; i < groups.length; i++) { + const group = groups[i]; + if (!group) { + continue; + } + const name = group.name; + if (group.resourceType === ResourceType.Sampler) { + // we assume sampler's name always be [textureName]Sampler + const textureName = name.substring(0, name.length - 7); + const texture = shaderUniforms && shaderUniforms[textureName] || (mesh.getUniform(textureName) || mesh.material && mesh.material.getUniform(textureName)) as Texture2D; + //TODO texture是否存在 + const { min, mag, wrapS, wrapT, compare } = (texture as Texture2D).config; + const filters = toGPUSampler(min, mag, wrapS, wrapT, compare); + const sampler = device.wgpu.createSampler(filters); + entries.push({ + binding: group.binding, + resource: sampler + }); + } else if (group.resourceType === ResourceType.Texture) { + const texture = shaderUniforms && shaderUniforms[name] || (mesh.getUniform(name) || mesh.material && mesh.material.getUniform(name)) as Texture2D; + let graphicsTexture = texture; + if (texture instanceof AbstractTexture) { + graphicsTexture = (texture as AbstractTexture).getREGLTexture(device); + } + textures.push(graphicsTexture); + entries.push({ + binding: group.binding, + resource: (graphicsTexture as GraphicsTexture).getView() + }); + } else { + const allocation = group.isGlobal ? shaderBuffer.allocation : meshBuffer.allocation; + entries.push({ + binding: group.binding, + resource: { + buffer: allocation.gpuBuffer, + // offset 永远设为0,在setBindGroup中设置dynamicOffsets + // offset: 0, + size: Math.max(group.size, this.alignment) + } + }); + } + } + const bindGroup = device.wgpu.createBindGroup({ + layout, + label: '', + entries + }); + for (let i = 0; i < textures.length; i++) { + textures[i].addBindGroup(bindGroup); + } + return bindGroup; + } + + dispose() { + delete this._shaderUniforms; + delete this._meshUniforms; + delete this.groups; + } +} diff --git a/packages/reshader.gl/src/webgpu/CommandBuilder.ts b/packages/reshader.gl/src/webgpu/CommandBuilder.ts new file mode 100644 index 0000000000..170d2204bf --- /dev/null +++ b/packages/reshader.gl/src/webgpu/CommandBuilder.ts @@ -0,0 +1,345 @@ +import { WgslReflect } from "wgsl_reflect/wgsl_reflect.module.js"; +import BindGroupFormat from '../webgpu/BindGroupFormat'; +import Mesh from '../Mesh'; +import { ResourceType } from 'wgsl_reflect'; +import GraphicsDevice from './GraphicsDevice'; +import PipelineDescriptor from './common/PipelineDesc'; +import { ActiveAttributes, ShaderUniforms } from '../types/typings'; +import InstancedMesh from '../InstancedMesh'; +import { WGSLParseDefines } from "./common/WGSLParseDefines"; +import GraphicsFramebuffer from "./GraphicsFramebuffer"; +import Texture2D from "../Texture2D"; +import GraphicsTexture from "./GraphicsTexture"; + +const ERROR_INFO = 'global uniform and mesh owned uniform can not be in the same struct'; + +export default class CommandBuilder { + //@internal + device: GraphicsDevice; + //@internal + vert: string; + //@internal + frag: string; + //@internal + mesh: Mesh; + //@internal + _presentationFormat: any; + //@internal + name: string; + //@internal + uniformValues: ShaderUniforms; + + constructor(name: string, device: GraphicsDevice, vert: string, frag: string, mesh: Mesh, uniformValues: ShaderUniforms) { + this.name = name; + this.device = device; + this.vert = vert; + this.frag = frag; + this.mesh = mesh; + this.uniformValues = uniformValues; + } + + build(pipelineDesc: PipelineDescriptor, fbo: GraphicsFramebuffer) { + const mesh = this.mesh; + const device = this.device; + const defines = this.mesh.getDefines(); + //FIXME 如何在wgsl中实现defined + const vert = WGSLParseDefines(this.vert, defines); + const frag = WGSLParseDefines(this.frag, defines); + + const vertReflect = new WgslReflect(vert); + const vertexInfo = this._formatBufferInfo(vertReflect, mesh); + const fragReflect = new WgslReflect(frag); + + const vertGroups = vertReflect.getBindGroups(); + const fragGroups = fragReflect.getBindGroups(); + // 生成 bind group layout + const layout = this._createBindGroupLayout(vertGroups, fragGroups, mesh, this.uniformValues); + const pipeline = this._createPipeline(device, vert, vertexInfo, frag, layout, mesh, pipelineDesc, fbo); + + const bindGroupMapping = this._createBindGroupMapping(vertGroups, fragGroups, mesh); + const bindGroupFormat = new BindGroupFormat(bindGroupMapping, device.wgpu.limits.minUniformBufferOffsetAlignment); + const activeAttributes = this._getActiveAttributes(vertexInfo); + + return { + pipeline, + vertexInfo, + bindGroupMapping, + bindGroupFormat, + activeAttributes + }; + } + + _getActiveAttributes(vertexInfo): ActiveAttributes { + //TODO + const attributes = [{ name: 'position', type: 1 }]; + (attributes as any).key = attributes.map(attr => attr.name).join(); + return attributes as ActiveAttributes; + } + + _createBindGroupLayout(vertGroups: any, fragGroups: any, mesh: Mesh, uniformValues: ShaderUniforms) { + + const entries = []; + for (let i = 0; i < vertGroups.length; i++) { + const groupInfo = vertGroups[i]; + for (let ii = 0; ii < groupInfo.length; ii++) { + const uniform = groupInfo[ii]; + if (!uniform) { + continue; + } + const entry = this._createLayoutEntry(uniform.binding, GPUShaderStage.VERTEX, uniform, mesh, uniformValues); + entries.push(entry); + } + } + for (let i = 0; i < fragGroups.length; i++) { + const groupInfo = fragGroups[i]; + for (let ii = 0; ii < groupInfo.length; ii++) { + const uniform = groupInfo[ii]; + if (!uniform) { + continue; + } + const entry = this._createLayoutEntry(uniform.binding, GPUShaderStage.FRAGMENT, uniform, mesh, uniformValues); + entries.push(entry); + } + } + // sort by binding + entries.sort(sortByBinding); + + return this.device.wgpu.createBindGroupLayout({ + label: this.name + '-bindgrouplayout', + entries + }); + } + + _createLayoutEntry(binding, visibility, groupInfo, mesh, uniformValues): GPUBindGroupLayoutEntry { + if (groupInfo.resourceType === ResourceType.Sampler) { + const sampler: GPUSamplerBindingLayout = {}; + if (groupInfo.type && groupInfo.type.name === 'sampler_comparison') { + sampler.type = 'comparison'; + } + return { + binding, + visibility, + sampler + }; + } else if (groupInfo.resourceType === ResourceType.Texture) { + const name = groupInfo.name; + const texture = uniformValues[name] || mesh.material && mesh.material.get(name); + let format; + if (texture) { + if (texture instanceof Texture2D) { + format = (texture as Texture2D).config.type; + } else if (texture instanceof GraphicsTexture) { + format = (texture as GraphicsTexture).gpuFormat.format; + } + } + let sampleType: GPUTextureSampleType = 'float';//sint, uint + if (format && format.startsWith('depth')) { + sampleType = 'depth'; + } + return { + binding, + visibility, + texture: { + sampleType + } + }; + } else { + return { + binding, + visibility, + buffer: { + type: 'uniform', + hasDynamicOffset: true + } + }; + } + } + + // 从vertex的entry function读出vertex的信息(如location,format等) + _formatBufferInfo(vertReflect: any, mesh: Mesh) { + const vertEntryFuncion = vertReflect.entry.vertex[0]; + const inputs = vertEntryFuncion.inputs; + const inputMapping = {}; + for (let i = 0; i < inputs.length; i++) { + const name = inputs[i].name; + inputMapping[name] = inputs[i]; + } + const vertexInfo = {}; + const data = mesh.geometry.data; + for (const name in data) { + if (inputMapping[name]) { + vertexInfo[name] = { + location: inputMapping[name].location, + itemSize: getItemSize(inputMapping[name].type) + }; + } + } + if (mesh instanceof InstancedMesh) { + const data = (mesh as InstancedMesh).instancedData; + for (const name in data) { + if (inputMapping[name]) { + vertexInfo[name] = { + location: inputMapping[name].location, + itemSize: getItemSize(inputMapping[name].type) + }; + } + } + } + return vertexInfo; + } + + _createBindGroupMapping(vertGroups: any, fragGroups: any, mesh: Mesh) { + const mapping = {}; + // 解析vertInfo和fragInfo,生成一个 bindGroupMapping,用于mesh在运行时,生成bindGroup + // mapping 中包含 uniform 变量名对应的 group index 和 binding index + this._parseGroupMapping(mapping, vertGroups[0], mesh); + this._parseGroupMapping(mapping, fragGroups[0], mesh); + return mapping; + } + + _parseGroupMapping(mapping, groupReflect, mesh) { + if (!groupReflect) { + return; + } + for (let i = 0; i < groupReflect.length; i++) { + const groupInfo = groupReflect[i]; + if (!groupInfo) { + continue; + } + const { group, binding } = groupInfo; + const members = groupInfo.members; + let isGlobal = false; + if (members && members.length) { + for (let ii = 0; ii < members.length; ii++) { + const name = members[ii].name; + if (!meshHasUniform(mesh, name)) { + if (!isGlobal && ii > 0) { + throw new Error(ERROR_INFO + groupInfo); + } + isGlobal = true; + } else if (isGlobal) { + throw new Error(ERROR_INFO + groupInfo); + } + } + } else { + const name = groupInfo.name; + if (!meshHasUniform(mesh, name)) { + isGlobal = true; + } + } + mapping.groups = mapping.groups || []; + mapping.groups[group] = mapping.groups[group] || []; + groupInfo.isGlobal = isGlobal; + groupInfo.index = + // we assume all the members in the same struct is all global or mesh owned + mapping.groups[group][binding] = groupInfo;//extend({ + } + } + // 运行时调用,生成 uniform buffer, 用来存放全局 uniform 变量的值 + _createPipeline(graphicsDevice: GraphicsDevice, + vert: string, vertInfo, frag: string, layout: GPUBindGroupLayout, mesh:Mesh, + pipelineDesc: PipelineDescriptor, fbo: GraphicsFramebuffer): GPURenderPipeline { + const device = graphicsDevice.wgpu; + const vertModule = device.createShaderModule({ + code: vert, + }); + let fragModule; + if (frag) { + fragModule = device.createShaderModule({ + code: frag, + }); + } + + if (!this._presentationFormat) { + this._presentationFormat = navigator.gpu.getPreferredCanvasFormat(); + } + const buffers = mesh.geometry.getBufferDescriptor(vertInfo); + if (mesh instanceof InstancedMesh) { + const instanceBuffers = (mesh as InstancedMesh).getBufferDescriptor(vertInfo); + buffers.push(...instanceBuffers); + } + const pipelineLayout = device.createPipelineLayout({ + label: this.name + '-pipelinelayout', + bindGroupLayouts: [layout] + }); + const pipelineOptions: GPURenderPipelineDescriptor = { + label: this.name + '-pipeline', + layout: pipelineLayout, + vertex: { + module: vertModule, + buffers + }, + primitive: { + topology: pipelineDesc.topology, + cullMode: pipelineDesc.cullMode + } + }; + const depthEnabled = !fbo || !!fbo.depthTexture; + + if (depthEnabled) { + const depthTexture = fbo && fbo.depthTexture; + pipelineOptions.depthStencil = { + depthBias: pipelineDesc.depthBias, + depthBiasSlopeScale: pipelineDesc.depthBiasSlopeScale, + depthWriteEnabled: pipelineDesc.depthWriteEnabled, + depthCompare: pipelineDesc.depthCompare, + format: (!depthTexture || depthTexture.gpuFormat.isDepthStencil) ? 'depth24plus-stencil8' : 'depth24plus' + }; + } + if (fragModule) { + pipelineOptions.fragment = { + module: fragModule, + targets: [ + { + format: this._presentationFormat, + } + ], + }; + } + if (pipelineDesc.stencilFrontCompare) { + pipelineOptions.depthStencil.stencilBack = + pipelineOptions.depthStencil.stencilFront = { + compare: pipelineDesc.stencilFrontCompare, + passOp: pipelineDesc.stencilFrontPassOp + }; + } + if (fragModule && pipelineDesc.blendAlphaDst) { + const fragTargets = pipelineOptions.fragment.targets; + for (const target of fragTargets) { + target.blend = { + color: { + srcFactor: pipelineDesc.blendColorSrc, + dstFactor: pipelineDesc.blendColorDst, + operation: 'add' + }, + alpha: { + srcFactor: pipelineDesc.blendAlphaSrc, + dstFactor: pipelineDesc.blendAlphaDst, + operation: 'add' + } + }; + } + } + return device.createRenderPipeline(pipelineOptions); + } +} + +function meshHasUniform(mesh: Mesh, name: string) { + if (name === 'modelMatrix' || name === 'positionMatrix') { + return true; + } + return mesh.hasUniform(name) || (mesh.material && mesh.material.hasUniform(name)); +} + +function getItemSize(type) { + if (type.name.startsWith('vec')) { + return parseInt(type.name[3]); + } else { + return 1; + } +} + +function sortByBinding(a: GPUBindGroupLayoutEntry, b: GPUBindGroupLayoutEntry): number { + return a.binding - b.binding; +} + diff --git a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts new file mode 100644 index 0000000000..8122c49d2b --- /dev/null +++ b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts @@ -0,0 +1,74 @@ +import { ResourceType } from "wgsl_reflect"; +import { ShaderUniformValue } from "../types/typings"; +import DynamicBufferPool, { DynamicBufferAllocation } from "./DynamicBufferPool"; +import { isArray, isFunction } from "../common/Util"; +import DynamicOffsets from "./DynamicOffsets"; + +export default class DynamicBuffer { + bindgroupMapping: any; + dynamicOffsets: number[]; + pool: DynamicBufferPool; + allocation: DynamicBufferAllocation; + version: number = 0; + + constructor(bindgroupMapping, pool: DynamicBufferPool) { + this.bindgroupMapping = bindgroupMapping; + this.pool = pool; + this.allocation = {}; + } + + writeBuffer(uniformValues: Record, dynamicOffsets: DynamicOffsets) { + const totalSize = this.bindgroupMapping.totalSize; + const gpuBuffer = this.allocation.gpuBuffer; + const bufferAlignment = this.pool.bufferAlignment; + this.pool.alloc(this.allocation, totalSize); + if (gpuBuffer !== this.allocation.gpuBuffer) { + this.version++; + } + + let dynamicOffset = this.allocation.offset; + + const mapping = this.bindgroupMapping; + const storage = this.allocation.storage; + + for (let i = 0; i < mapping.length; i++) { + const uniform = mapping[i]; + if (uniform.members) { + dynamicOffsets.addItem({ binding: uniform.binding, offset: dynamicOffset }); + for (let j = 0; j < uniform.members.length; j++) { + const member = uniform.members[j]; + const value = uniformValues[member.name] as number | number[]; + const offset = dynamicOffset + member.offset; + const size = member.size; + this._fillValue(storage, offset, size, value); + } + dynamicOffset += Math.max(mapping[i].size, bufferAlignment); + } else if (uniform.resourceType === ResourceType.Uniform) { + dynamicOffsets.addItem({ binding: uniform.binding, offset: dynamicOffset }); + const value = uniformValues[uniform.name]; + const size = isFunction(uniform.size) ? uniform.size() : uniform.size; + this._fillValue(storage, dynamicOffset, size, value); + dynamicOffset += Math.max(size, bufferAlignment); + } + } + + // console.log(debugInfo.join()); + } + + _fillValue(buffer, offset, size, value) { + // we always use f32 in WGSL + const view = new Float32Array(buffer, offset, size / 4); + if (isArray(value)) { + for (let i = 0; i < value.length; i++) { + view[i] = value[i]; + } + } else { + view[0] = value; + } + } + + dispose() { + delete this.pool; + delete this.allocation; + } +} diff --git a/packages/reshader.gl/src/webgpu/DynamicBufferPool.ts b/packages/reshader.gl/src/webgpu/DynamicBufferPool.ts new file mode 100644 index 0000000000..0a93f7a1ab --- /dev/null +++ b/packages/reshader.gl/src/webgpu/DynamicBufferPool.ts @@ -0,0 +1,146 @@ +// contains codes from playcanvas +// https://github.com/playcanvas/engine +// MIT License +import * as math from './common/math'; + +export type DynamicBufferAllocation = { + storage?: ArrayBuffer; + gpuBuffer?: GPUBuffer; + offset?: number; + size?: number; +} + +export default class DynamicBufferPool { + + /** + * Allocation size of the underlying buffers. + * + */ + bufferSize: number; + /** + * Internally allocated gpu buffers. + * + */ + poolBuffers: DynamicBufferAllocation[]; + + usedBuffers: DynamicBufferAllocation[]; + + /** + * @type {DynamicBufferAllocation|null} + */ + activeBuffer: DynamicBufferAllocation = null; + device: GPUDevice; + bufferAlignment: number; + + /** + * Create the system of dynamic buffers. + * + * @param device - The graphics device. + * @param bufferSize - The size of the underlying large buffers. + * @param bufferAlignment - Alignment of each allocation. + */ + constructor(device: GPUDevice, bufferSize: number, bufferAlignment: number) { + this.device = device; + this.usedBuffers = []; + this.poolBuffers = []; + this.bufferSize = bufferSize; + this.bufferAlignment = bufferAlignment; + } + + /** + * Destroy the system of dynamic buffers. + */ + destroy() { + this.poolBuffers.forEach((poolBuffer) => { + poolBuffer.gpuBuffer.destroy(); + }); + this.poolBuffers.length = 0; + this.usedBuffers.length = 0; + this.activeBuffer = null; + } + + /** + * Allocate an aligned space of the given size from a dynamic buffer. + * + * @param {DynamicBufferAllocation} allocation - The allocation info to fill. + * @param {number} size - The size of the allocation. + */ + alloc(allocation, size) { + + // if we have active buffer without enough space + if (this.activeBuffer) { + const alignedStart = math.roundUp(this.activeBuffer.size, this.bufferAlignment); + const space = this.bufferSize - alignedStart; + if (space < size) { + + // we're done with this buffer, schedule it for submit + this.scheduleSubmit(); + } + } + + // if we don't have an active buffer, allocate new one + if (!this.activeBuffer) { + + // gpu buffer + this.activeBuffer = this.poolBuffers.pop(); + if (!this.activeBuffer) { + this.activeBuffer = { + gpuBuffer: this.createBuffer(this.device, this.bufferSize), + storage: new ArrayBuffer(this.bufferSize), + offset: 0, + size: 0 + }; + } + this.activeBuffer.offset = 0; + this.activeBuffer.size = 0; + } + + // allocate from active buffer + const activeBuffer = this.activeBuffer; + const alignedStart = math.roundUp(activeBuffer.size, this.bufferAlignment); + // Debug.assert(alignedStart + size <= this.bufferSize, `The allocation size of ${size} is larger than the buffer size of ${this.bufferSize}`); + + allocation.gpuBuffer = activeBuffer.gpuBuffer; + allocation.offset = alignedStart; + allocation.storage = activeBuffer.storage; + + // take the allocation from the buffer + activeBuffer.size = alignedStart + size; + } + + createBuffer(device: GPUDevice, size: number) { + return device.createBuffer({ + size: size, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST + }); + } + + scheduleSubmit() { + if (this.activeBuffer) { + this.usedBuffers.push(this.activeBuffer); + this.activeBuffer = null; + } + } + + submit() { + + // schedule currently active buffer for submit + this.scheduleSubmit(); + + // submit all used buffers + const count = this.usedBuffers.length; + if (count) { + const device = this.device; + const poolBuffers = this.poolBuffers; + // run this loop backwards to preserve the order of buffers in gpuBuffers array + for (let i = count - 1; i >= 0; i--) { + const usedBuffer = this.usedBuffers[i]; + const { storage, gpuBuffer, offset, size } = usedBuffer; + device.queue.writeBuffer(gpuBuffer, 0, storage, offset, size); + poolBuffers.push(usedBuffer); + } + + this.usedBuffers.length = 0; + } + } +} diff --git a/packages/reshader.gl/src/webgpu/DynamicOffsets.ts b/packages/reshader.gl/src/webgpu/DynamicOffsets.ts new file mode 100644 index 0000000000..56140602ff --- /dev/null +++ b/packages/reshader.gl/src/webgpu/DynamicOffsets.ts @@ -0,0 +1,37 @@ +export default class DynamicOffsets { + + items: any[]; + offsets: number[]; + index: number; + + constructor() { + this.items = []; + this.offsets = []; + this.index = 0; + } + + reset() { + this.index = 0; + this.items.length = 0; + this.offsets.length = 0; + } + + addItem(item) { + this.items[this.index++] = item; + } + + addItems(items: any[]) { + for (let i = 0; i < items.length; i++) { + this.addItem(items[i]); + } + } + + getDynamicOffsets() { + const items = this.items; + items.sort((a, b) => a.binding - b.binding); + for (let i = 0; i < items.length; i++) { + this.offsets[i] = items[i].offset; + } + return this.offsets; + } +} diff --git a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts new file mode 100644 index 0000000000..66541806e0 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts @@ -0,0 +1,200 @@ +import { isNumber } from "../common/Util"; +import Geometry from "../Geometry"; +import DynamicBufferPool from "./DynamicBufferPool"; +import GraphicsFramebuffer from "./GraphicsFramebuffer"; +import GraphicsTexture from "./GraphicsTexture"; + +export default class GraphicsDevice { + wgpu: GPUDevice; + context: GPUCanvasContext; + //@internal + commandBuffers: GPUCommandBuffer[] = []; + //@internal + dynamicBufferPool: DynamicBufferPool; + //@internal + commandEncoder: GPUCommandEncoder; + //@internal + _defaultFramebuffer: GraphicsFramebuffer; + //@internal + _readTargets: Record = {}; + + constructor(device: GPUDevice, context: GPUCanvasContext) { + this.wgpu = device; + const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); + context.configure({ + device, + format: presentationFormat, + }); + this.context = context; + // 1M for each buffer + const bufferSize = 1024 * 1024; + const limits = device.limits; + this.dynamicBufferPool = new DynamicBufferPool( + device, + bufferSize, + limits.minUniformBufferOffsetAlignment, + ); + } + + getCommandEncoder(): GPUCommandEncoder { + // use existing or create new encoder + let commandEncoder = this.commandEncoder; + if (!commandEncoder) { + commandEncoder = this.wgpu.createCommandEncoder(); + this.commandEncoder = commandEncoder; + } + + return commandEncoder; + } + + endCommandEncoder() { + const { commandEncoder } = this; + if (commandEncoder) { + const cb = commandEncoder.finish(); + this.addCommandBuffer(cb, false); + this.commandEncoder = null; + } + } + + _getDefaultFramebuffer() { + let fbo = this._defaultFramebuffer; + if (!fbo) { + const canvas = this.context.canvas; + fbo = this._defaultFramebuffer = new GraphicsFramebuffer(this, { + width: canvas.width, + height: canvas.height, + depthStencil: true + }); + } + return fbo; + } + + getRenderPassEncoder(fbo: GraphicsFramebuffer) { + fbo = fbo || this._getDefaultFramebuffer(); + const desc = fbo.getRenderPassDescriptor(); + if (fbo === this._defaultFramebuffer) { + desc.colorAttachments[0].view = this.context + .getCurrentTexture() + .createView(); + desc.colorAttachments[0].view.label = 'default canvas view'; + } + const commandEncoder = this.getCommandEncoder(); + return commandEncoder.beginRenderPass(desc); + } + + addCommandBuffer(commandBuffer: GPUCommandBuffer, front: boolean) { + if (front) { + this.commandBuffers.unshift(commandBuffer); + } else { + this.commandBuffers.push(commandBuffer); + } + } + + submit() { + // end the current encoder + this.endCommandEncoder(); + // copy dynamic buffers data to the GPU (this schedules the copy CB to run before all other CBs) + this.dynamicBufferPool.submit(); + if (this.commandBuffers.length > 0) { + this.wgpu.queue.submit(this.commandBuffers); + this.commandBuffers.length = 0; + } + } + + // implementation of regl.buffer + buffer(options) { + return Geometry.createBuffer(this.wgpu, options, options.name); + } + + // implementation of regl.framebuffer + framebuffer(width, height) { + let reglFBODescriptor; + if (!isNumber(width)) { + reglFBODescriptor = width; + } else { + if (height === undefined) { + height = width; + } + reglFBODescriptor = { color: true, depthStencil: true, width, height }; + } + return new GraphicsFramebuffer(this, reglFBODescriptor); + } + + // implementation of regl.texture + texture(config) { + return new GraphicsTexture(this, config); + } + + // implementation of regl.clear + clear(options) { + const fbo = options.fbo || this._getDefaultFramebuffer(); + fbo.setClearOptions(options); + } + + + // implementation of regl.read + async read(options) { + const framebuffer = options.framebuffer || this._defaultFramebuffer; + let { width, height } = options; + if (!width) { + width = framebuffer.width; + } + if (!height) { + height = framebuffer.height; + } + const device = this.wgpu; + const colorTexture = framebuffer.colorTexture; + const { bytesPerTexel } = colorTexture.gpuFormat; + let bytesPerRow = options.width * bytesPerTexel; + bytesPerRow = Math.ceil( bytesPerRow / 256 ) * 256; // Align to 256 bytes + const encoder = device.createCommandEncoder(); + + const bufferSize = width * height * bytesPerTexel; + let readBuffer = this._readTargets[bufferSize]; + if (!readBuffer) { + readBuffer = device.createBuffer( + { + size: width * height * bytesPerTexel, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + } + ); + } + + encoder.copyTextureToBuffer( + { + texture: colorTexture.texture, + origin: { x: options.x, y: options.y, z: 0 }, + }, + { + buffer: readBuffer, + bytesPerRow: bytesPerRow + }, + { + width, + height + } + ); + + const typedArrayType = options.data.constructor; + device.queue.submit([encoder.finish()]); + await readBuffer.mapAsync(GPUMapMode.READ); + const buffer = readBuffer.getMappedRange(); + const result = new typedArrayType(buffer); + options.data.set(result); + readBuffer.unmap(); + } + + destroy() { + if (this._defaultFramebuffer) { + this._defaultFramebuffer.destroy(); + delete this._defaultFramebuffer; + } + for (const p in this._readTargets) { + const buffer = this._readTargets[p]; + if (buffer) { + buffer.destroy(); + } + } + this._readTargets = {}; + } +} diff --git a/packages/reshader.gl/src/webgpu/GraphicsFramebuffer.ts b/packages/reshader.gl/src/webgpu/GraphicsFramebuffer.ts new file mode 100644 index 0000000000..b820feb2af --- /dev/null +++ b/packages/reshader.gl/src/webgpu/GraphicsFramebuffer.ts @@ -0,0 +1,149 @@ +import GraphicsDevice from './GraphicsDevice'; +import GraphicsTexture from './GraphicsTexture'; + +export default class GraphicsFramebuffer { + device: GraphicsDevice; + options: any; + //@internal + colorTexture: GraphicsTexture; + //@internal + depthTexture: GraphicsTexture; + //@internal + _renderPass: GPURenderPassDescriptor; + width: number; + height: number; + //@internal + colorLoadOp: GPULoadOp; + //@internal + colorClearValue: number[]; + //@internal + depthLoadOp: GPULoadOp; + //@internal + depthClearValue: number; + //@internal + stencilLoadOp: GPULoadOp; + //@internal + stencilClearValue: number; + + constructor(device, options) { + this.device = device; + this.options = options; + this._update(); + } + + _update() { + let color = this.options.colors && this.options.colors[0] || this.options.color; + const { width, height } = this.options; + this.width = width; + this.height = height; + if (color && !(color instanceof GraphicsTexture)) { + color = new GraphicsTexture(this.device, color); + } + let depth = this.options.depth; + if (depth) { + if (depth === true) { + depth = new GraphicsTexture(this.device, { width, height, format: 'depth' }); + } else { + if (!(depth instanceof GraphicsTexture)) { + depth = new GraphicsTexture(this.device, depth); + } + } + } + // we assume depth and depthStencil won't be used at the same time + const depthStencil = this.options.depthStencil; + if (depthStencil) { + if (depthStencil === true) { + depth = new GraphicsTexture(this.device, { width, height, format: 'depth stencil' }); + } else { + if (depthStencil instanceof GraphicsTexture) { + depth = depthStencil; + } else { + depth = new GraphicsTexture(this.device, depthStencil); + } + } + } + + this.colorTexture = color; + this.depthTexture = depth; + this._renderPass = { + colorAttachments: [] + }; + if (color !== null) { + this._renderPass.colorAttachments[0] = { + view: this.colorTexture && this.colorTexture.getView(), // Assigned later + clearValue: [0, 0, 0, 0], + loadOp: 'load', + storeOp: 'store', + }; + } + if (depth) { + const depthAttchment = this._renderPass.depthStencilAttachment = { + view: this.depthTexture.getView(), + depthClearValue: 1, + depthLoadOp: 'load', + depthStoreOp: 'store' + } as GPURenderPassDepthStencilAttachment; + if (depth.gpuFormat.isDepthStencil) { + depthAttchment.stencilReadOnly = false; + depthAttchment.stencilClearValue = 0; + depthAttchment.stencilLoadOp = 'load'; + depthAttchment.stencilStoreOp = 'store'; + } + } + } + + getRenderPassDescriptor() { + const colorAttachment = this._renderPass.colorAttachments[0]; + const depthStencilAttachment = this._renderPass.depthStencilAttachment; + if (colorAttachment) { + colorAttachment.loadOp = this.colorLoadOp || 'load'; + colorAttachment.clearValue = this.colorClearValue || [0, 0, 0, 0]; + } + if (depthStencilAttachment) { + depthStencilAttachment.depthLoadOp = this.depthLoadOp || 'load'; + depthStencilAttachment.depthClearValue = this.depthClearValue || 1; + if (this.depthTexture.gpuFormat.isDepthStencil) { + depthStencilAttachment.stencilLoadOp = this.stencilLoadOp || 'load'; + depthStencilAttachment.stencilClearValue = this.stencilClearValue || 0; + } + + } + this._resetClearOptions(); + return this._renderPass; + } + + setClearOptions(options) { + if (options.color) { + this.colorLoadOp = 'clear'; + this.colorClearValue = options.color; + } + if (options.depth) { + this.depthLoadOp = 'clear'; + this.depthClearValue = options.depth; + } + if (options.stencil) { + this.stencilLoadOp = 'clear'; + this.stencilClearValue = options.stencil; + } + } + + _resetClearOptions() { + this.colorLoadOp = null; + this.colorClearValue = null; + this.depthLoadOp = null; + this.depthClearValue = null; + this.stencilLoadOp = null; + this.stencilClearValue = null; + } + + destroy() { + if (this.colorTexture) { + this.colorTexture.destroy(); + delete this.colorTexture; + } + if (this.depthTexture) { + this.depthTexture.destroy(); + delete this.depthTexture; + } + } +} diff --git a/packages/reshader.gl/src/webgpu/GraphicsTexture.ts b/packages/reshader.gl/src/webgpu/GraphicsTexture.ts new file mode 100644 index 0000000000..a9d31f65e5 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/GraphicsTexture.ts @@ -0,0 +1,98 @@ +import { isArray } from "../common/Util"; +import { GPUTexFormat, toTextureFormat } from "./common/ReglTranslator"; +import GraphicsDevice from "./GraphicsDevice"; + +export default class GraphicsTexture { + texture: GPUTexture; + device: GraphicsDevice; + config: any; + //@internal + _bindGroups: GPUBindGroup[] = []; + //@internal + gpuFormat: GPUTexFormat; + + constructor(device: GraphicsDevice, config) { + this.device = device; + this.config = config; + this.gpuFormat = toTextureFormat(config.format, config.type); + this.update(this.config); + } + + update(config) { + const device = this.device.wgpu; + if (this.texture) { + this.texture.destroy(); + if (this._bindGroups.length) { + for (let i = 0; i < this._bindGroups.length; i++) { + (this._bindGroups[i] as any).outdated = true; + } + this._bindGroups = []; + } + } + let texture: GPUTexture; + { + let width = config.width; + let height = config.height; + if (width === undefined || height === undefined) { + const data = config.data; + if (isArray(config.data)) { + const length = config.data.length; + width = Math.sqrt(length / 4); + height = width; + } else { + width = data.width; + height = data.height; + } + } + const format = this.gpuFormat.format; + const isDepth = format === 'depth24plus' || format === 'depth24plus-stencil8'; + const usage = isDepth ? + GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT + : + GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT; + + texture = device.createTexture({ + size: [width, height, 1], + format, + usage + }); + if (config.data) { + if (isArray(config.data)) { + let data =config.data; + if (Array.isArray(config.data)) { + data = new Float32Array(data); + } + device.queue.writeTexture( + { texture: texture }, + data.buffer, + {}, + [width, height] + ); + } else { + device.queue.copyExternalImageToTexture( + { source: config.data }, + { texture: texture }, + [width, height] + ); + } + } + } + this.texture = texture; + } + + getView() { + return this.texture.createView(); + } + + addBindGroup(bindGroup) { + this._bindGroups.push(bindGroup); + } + + destroy() { + if (this.texture) { + this.texture.destroy(); + delete this.texture; + } + delete this._bindGroups; + } +} diff --git a/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts b/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts new file mode 100644 index 0000000000..23532baee4 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts @@ -0,0 +1,153 @@ +import { isFunction, isNil } from "../../common/Util"; +import GraphicsFramebuffer from "../GraphicsFramebuffer"; +import { toGPUCompareFunction, toTopology, toGPUBlendFactor } from "./ReglTranslator"; + +// Pipeline states we cared +export default class PipelineDescriptor { + depthBias?: number; + depthBiasSlopeScale?: number; + depthCompare?: GPUCompareFunction; + depthWriteEnabled?: boolean; + + stencilFrontCompare?: GPUCompareFunction; + stencilFrontPassOp?: GPUStencilOperation; + + blendAlphaSrc?: GPUBlendFactor; + blendAlphaDst?: GPUBlendFactor; + blendColorSrc?: GPUBlendFactor; + blendColorDst?: GPUBlendFactor; + + cullMode?: GPUCullMode; + frontFace?: GPUFrontFace; + topology?: GPUPrimitiveTopology; + + readFromREGLCommand(commandProps: any, mesh, uniformValues, doubleSided, fbo: GraphicsFramebuffer) { + const primitive = mesh.geometry.desc.primitive; + this.topology = toTopology(primitive); + + const depthEnabled = !fbo || !!fbo.depthTexture; + const stencilEnabled = !fbo || fbo.depthTexture.gpuFormat.isDepthStencil; + + let depthBias, depthBiasSlopeScale; + if (depthEnabled && commandProps.polygonOffset && isEnable(commandProps.polygonOffset.enable, uniformValues)) { + depthBias = 0; + depthBiasSlopeScale = 0; + const offsetProps = commandProps.polygonOffset.offset; + if (offsetProps && !isNil(offsetProps.units)) { + depthBias = isFunction(offsetProps.units) && offsetProps.units(null, uniformValues) || offsetProps.units; + } + if (offsetProps && !isNil(offsetProps.factor)) { + depthBiasSlopeScale = isFunction(offsetProps.factor) && offsetProps.factor(null, uniformValues) || offsetProps.factor; + } + } + this.depthBias = depthBias; + this.depthBiasSlopeScale = depthBiasSlopeScale; + + let depthCompare: GPUCompareFunction = 'always'; + let depthWriteEnable = false; + const depthProps = commandProps.depth; + if (depthEnabled && depthProps && isEnable(depthProps.enable, uniformValues)) { + depthCompare = 'less'; + if (depthProps.func) { + const depthFunc = isFunction(depthProps.func) && depthProps.func(null, uniformValues) || depthProps.func; + depthCompare = toGPUCompareFunction(depthFunc); + } + + depthWriteEnable = true; + if (!isNil(depthProps.mask)) { + const maskFunc = isFunction(depthProps.mask) && depthProps.mask(null, uniformValues) || depthProps.mask; + depthWriteEnable = !!maskFunc; + } + + //TODO where is depth range? + } + this.depthCompare = depthCompare; + this.depthWriteEnabled = depthWriteEnable; + + let stencilFrontCompare, stencilFrontPassOp; + const stencilProps = commandProps.stencil; + if (stencilEnabled && stencilProps && isEnable(stencilProps.enable, uniformValues)) { + if (stencilProps.op) { + // 目前还没遇到op是函数的情况,所以可以直接读取 + stencilFrontPassOp = stencilProps.op.zpass; + } + + if (stencilProps.func) { + const stencilFunc = isFunction(stencilProps.func) && stencilProps.func(null, uniformValues) || stencilProps.func; + stencilFrontCompare = toGPUCompareFunction(stencilFunc); + } + } + this.stencilFrontCompare = stencilFrontCompare; + this.stencilFrontPassOp = stencilFrontPassOp; + + let blendAlphaSrc, blendAlphaDst; + let blendColorSrc, blendColorDst; + const blendProps = commandProps.blend; + if (blendProps && isEnable(blendProps.enable, uniformValues)) { + if (blendProps.func) { + if (blendProps.src) { + const blendSrc = isFunction(blendProps.src) && blendProps.src(null, uniformValues) || blendProps.src; + blendAlphaSrc = toGPUBlendFactor(blendSrc); + blendColorSrc = blendAlphaSrc; + } + if (blendProps.dst) { + const blendDst = isFunction(blendProps.dst) && blendProps.dst(null, uniformValues) || blendProps.dst; + blendAlphaDst = toGPUBlendFactor(blendDst); + blendColorDst = blendAlphaDst; + } + if (blendProps.srcAlpha) { + const blendSrcAlpha = isFunction(blendProps.srcAlpha) && blendProps.srcAlpha(null, uniformValues) || blendProps.srcAlpha; + blendAlphaSrc = toGPUBlendFactor(blendSrcAlpha); + } + if (blendProps.srcRGB) { + const blendSrcRGB = isFunction(blendProps.srcRGB) && blendProps.srcRGB(null, uniformValues) || blendProps.srcRGB; + blendColorSrc = toGPUBlendFactor(blendSrcRGB); + } + if (blendProps.dstAlpha) { + const blendDstAlpha = isFunction(blendProps.dstAlpha) && blendProps.dstAlpha(null, uniformValues) || blendProps.dstAlpha; + blendAlphaDst = toGPUBlendFactor(blendDstAlpha); + } + if (blendProps.dstRGB) { + const blendDstRGB = isFunction(blendProps.dstRGB) && blendProps.dstRGB(null, uniformValues) || blendProps.dstRGB; + blendColorDst = toGPUBlendFactor(blendDstRGB); + } + } + } + this.blendAlphaDst = blendAlphaDst; + this.blendAlphaSrc = blendAlphaSrc; + this.blendColorDst = blendColorDst; + this.blendColorSrc = blendColorSrc; + + let cullMode: GPUCullMode = 'none'; + if (!doubleSided) { + const cullProps = commandProps.cull; + if (cullProps && isEnable(cullProps.enable, uniformValues)) { + cullMode = 'back'; + if (cullProps.face) { + cullMode = isFunction(cullProps.face) && cullProps.face(null, uniformValues) || cullProps.face; + } + } + } + this.cullMode = cullMode; + + // frontFace不存在函数的情况,所以可以直接读取 + this.frontFace = commandProps.frontFace; + + //TODO mesh中buffer的组织方式也需要考虑进来 + } + + getSignatureKey() { + return (this.depthBias || 0) + '-' + (this.depthBiasSlopeScale || 0) + '-' + (this.depthCompare || 0) + '-' + (this.depthWriteEnabled || 0) + + (this.stencilFrontCompare || 0) + '-' + (this.stencilFrontPassOp || 0) + + (this.blendAlphaSrc || 0) + '-' + (this.blendAlphaDst || 0) + '-' + (this.blendColorSrc || 0) + '-' + (this.blendColorDst || 0) + + (this.cullMode || 0) + '-' + (this.frontFace || 0) + '-' + (this.topology || 0); + } +} + +function isEnable(enable, props) { + if (!isFunction(enable)) { + return !!enable; + } + enable = enable(null, props); + return !!enable; +} diff --git a/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts b/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts new file mode 100644 index 0000000000..17a9ef7a70 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts @@ -0,0 +1,102 @@ +const DEPTH_DICTIONARY = { + '=': 'equal', + '<': 'less', + '<=': 'less-equal', + 'lequal': 'less-equal', + '>': 'greater', + '!=': 'not-equal', + 'notequal': 'not-equal', + '>=': 'greater-equal', + 'gequal': 'greater-equal' +} +export function toGPUCompareFunction(func): GPUCompareFunction { + return DEPTH_DICTIONARY[func] || func; +} + +const TOPOLOGY_DICTIONARY = { + 'points': 'point-list', + 'triangles': 'triangle-list', + 'triangle strip': 'triangle-strip', + 'lines': 'line-list', + 'line strip': 'line-strip' +}; +export function toTopology(primitive): GPUPrimitiveTopology { + return TOPOLOGY_DICTIONARY[primitive] || 'triangle-list'; +} + +const BLENDFACTOR_DICTIONARY = { + 0: 'zero', + 1: 'one', + 'src color': 'src', + 'one minus src color': 'one-minus-src', + 'src alpha': 'src-alpha', + 'one minus src alpha': 'one-minus-src-alpha', + 'dst color': 'dst', + 'one minus dst color': 'one-minus-dst', + 'dst alpha': 'dst-alpha', + 'one minus dst alpha': 'one-minus-dst-alpha' + // 其实还有一些别的值,但因为没用到,这里就不用继续翻译了 +} + +export function toGPUBlendFactor(blendFactor): GPUBlendFactor { + return BLENDFACTOR_DICTIONARY[blendFactor] || blendFactor; +} + +const ADDRESS_MODE_DICTIONARY = { + 'clamp': 'clamp-to-edge', + 'mirror': 'mirror-repeat' +}; + +export function toGPUSampler(minFilter: string, magFilter: GPUFilterMode, + wrapS: string, wrapT: string, compare?: GPUCompareFunction) { + const sampler: GPUSamplerDescriptor = { + magFilter + }; + if (minFilter === 'nearest' || minFilter === 'linear') { + sampler.minFilter = minFilter; + } else if (minFilter === 'linear mipmap linear' || minFilter === 'mipmap') { + sampler.minFilter = 'linear'; + sampler.mipmapFilter = 'linear'; + } else if (minFilter === 'nearest mipmap nearest') { + sampler.minFilter = 'linear'; + sampler.mipmapFilter = 'nearest'; + } else if (minFilter === 'nearest mipmap linear') { + sampler.minFilter = 'nearest'; + sampler.mipmapFilter = 'linear'; + } else if (minFilter === 'linear mipmap nearest') { + sampler.minFilter = 'linear'; + sampler.mipmapFilter = 'nearest'; + } + sampler.addressModeU = ADDRESS_MODE_DICTIONARY[wrapS || 'clamp'] || wrapS; + sampler.addressModeV = ADDRESS_MODE_DICTIONARY[wrapT || 'clamp'] || wrapT; + if (compare) { + sampler.compare = compare; + } + return sampler; +} + +export type GPUTexFormat = { format: GPUTextureFormat, bytesPerTexel: number, isDepthStencil: boolean }; + +export function toTextureFormat(format: string, type: string): GPUTexFormat { + format = format || 'rgba'; + type = type || 'uint8'; + + if (format === 'depth stencil') { + return { format: 'depth24plus-stencil8', bytesPerTexel: 4, isDepthStencil: true}; + } else if (format === 'depth') { + return { format: 'depth24plus', bytesPerTexel: 4, isDepthStencil: false }; + } + + if (type === 'uint8') { + if (format === 'rgba') { + return { format: 'rgba8unorm', bytesPerTexel: 1, isDepthStencil: false }; + } else { + //TODO 各种压缩纹理的类型 + } + } else if (type === 'float16' || type === 'half float') { + return { format: 'r16float', bytesPerTexel: 2, isDepthStencil: false }; + } else if (type === 'float') { + return { format: 'r32float', bytesPerTexel: 4, isDepthStencil: false }; + } + return { format: 'rgba8unorm', bytesPerTexel: 1, isDepthStencil: false }; +} diff --git a/packages/reshader.gl/src/webgpu/common/Types.ts b/packages/reshader.gl/src/webgpu/common/Types.ts new file mode 100644 index 0000000000..d28da16383 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/common/Types.ts @@ -0,0 +1,41 @@ +export function getGPUVertexType(array) { + let format; + if (Array.isArray(array) || array instanceof Float32Array) { + format = 'float32'; + } else if (array instanceof Uint32Array) { + format = 'uint32'; + } else if (array instanceof Int32Array) { + format = 'sint32'; + } else if (array instanceof Uint16Array) { + format = 'uint16'; + } else if (array instanceof Int16Array) { + format = 'sint16'; + } else if (array instanceof Uint8Array) { + format = 'uint8'; + } else if (array instanceof Int8Array) { + format = 'sint8'; + } + return format; +} + +const component_ctors = { + 5120: { name: 'sint8', bytes: 1 }, + 5122: { name: 'sint16', bytes: 2 }, + 5124: { name: 'sint32', bytes: 4 }, + 5121: { name: 'uint8', bytes: 1 }, + 5123: { name: 'uint16', bytes: 2 }, + 5125: { name: 'uint32', bytes: 4 }, + 5126: { name: 'float32', bytes: 4 } +}; +export function getFormatFromGLTFAccessor(componentType, itemSize) { + const format = component_ctors[componentType].name; + if (itemSize > 1) { + return format + 'x' + itemSize; + } else { + return format; + } +} + +export function getItemBytesFromGLTFAccessor(componentType) { + return component_ctors[componentType].bytes; +} diff --git a/packages/reshader.gl/src/webgpu/common/WGSLParseDefines.ts b/packages/reshader.gl/src/webgpu/common/WGSLParseDefines.ts new file mode 100644 index 0000000000..458d3d1885 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/common/WGSLParseDefines.ts @@ -0,0 +1,150 @@ +/*! + * codes from GEngine + * https://github.com/GEngine-js/GEngine/blob/e9c0e2c4a28cc6b9fec133c75958b80115e53a63/src/shader/WGSLParseDefines.ts + * ISC License + */ +import { ShaderDefines } from "../../types/typings"; + +const preprocessorSymbols = /#([^\s]*)(\s*)/gm; +const defineRexg = /\b[0-9A-Z_&&||]+\b/g; +const isNumeric = (n) => !isNaN(n); +export function WGSLParseDefines(shader: string, defines: ShaderDefines): string { + if (!shader) return undefined; + // parse shader inner const define + const notDefineConstShader = ParseDefinesConst(shader, defines); + // filter "&&","||",number + const rexgDefines = notDefineConstShader + .match(defineRexg) + ?.filter((define) => !["&&", "||", "_"].includes(define) && !isNumeric(define) && define != ""); + // normallize defines + const normalizeDefines = getNormalizeDefines(rexgDefines, defines); + // split Shader + const shaderStrs = splitShaderStrsByDefine(notDefineConstShader, rexgDefines); + // parse conditional macro definition + return shaderStrs.length > 0 ? ParseDefines(shaderStrs, normalizeDefines) : notDefineConstShader; +} +function ParseDefines(strings: Array, values: Array): string { + const stateStack = []; + let state = { frag: "", elseIsValid: false, expression: true }; + let depth = 1; + for (let i = 0; i < strings.length; ++i) { + const frag = strings[i]; + const matchedSymbols = frag.matchAll(preprocessorSymbols); + + let lastIndex = 0; + let valueConsumed = false; + for (const match of matchedSymbols) { + state.frag += frag.substring(lastIndex, match.index); + + switch (match[1]) { + case "if": + if (match.index + match[0].length != frag.length) { + throw new Error("#if must be immediately followed by a template expression (ie: ${value})"); + } + valueConsumed = true; + stateStack.push(state); + depth++; + state = { frag: "", elseIsValid: true, expression: !!values[i] }; + break; + case "elif": + if (match.index + match[0].length != frag.length) { + throw new Error("#elif must be immediately followed by a template expression (ie: ${value})"); + } else if (!state.elseIsValid) { + throw new Error("#elif not preceeded by an #if or #elif"); + } + valueConsumed = true; + if (state.expression && stateStack.length != depth) { + stateStack.push(state); + } + state = { frag: "", elseIsValid: true, expression: !!values[i] }; + break; + case "else": + if (!state.elseIsValid) { + throw new Error("#else not preceeded by an #if or #elif"); + } + if (state.expression && stateStack.length != depth) { + stateStack.push(state); + } + state = { frag: match[2], elseIsValid: false, expression: true }; + break; + case "endif": + if (!stateStack.length) { + throw new Error("#endif not preceeded by an #if"); + } + // eslint-disable-next-line no-case-declarations + const branchState = stateStack.length == depth ? stateStack.pop() : state; + state = stateStack.pop(); + depth--; + if (branchState.expression) { + state.frag += branchState.frag; + } + state.frag += match[2]; + break; + default: + // Unknown preprocessor symbol. Emit it back into the output frag unchanged. + state.frag += match[0]; + break; + } + + lastIndex = match.index + match[0].length; + } + + // If the frag didn't end on one of the preprocessor symbols append the rest of it here. + if (lastIndex != frag.length) { + state.frag += frag.substring(lastIndex, frag.length); + } + + // If the next value wasn't consumed by the preprocessor symbol, append it here. + if (!valueConsumed && values.length > i) { + state.frag += values[i]; + } + } + if (stateStack.length) { + throw new Error("Mismatched #if/#endif count"); + } + return state.frag; +} +function ParseDefinesConst(sourceShader: string, defines: ShaderDefines) { + if (!defines) return sourceShader; + let result = sourceShader; + const constDefineKeys = Object.keys(defines)?.filter?.((key) => key != key.toUpperCase()); + constDefineKeys?.forEach?.((key: string) => { + result = result.replaceAll(key, defines[key] + ''); + }); + return result; +} +function getNormalizeDefines(rexgDefines: Array, defines: any) { + return rexgDefines?.map?.((define) => { + if (define?.includes("&&") || define?.includes("||")) { + if (define.includes("&&")) { + const splitDefines = define.split("&&"); + return getAndDefineValue(splitDefines, defines); + } + const splitDefines = define.split("||"); + return !getOrDefineValue(splitDefines, defines); + } + return defines[define]; + }); +} +function getAndDefineValue(splitDefines: Array, defines: ShaderDefines): boolean { + let total = 0; + splitDefines?.forEach?.((defineKey) => (total += Number(defines[defineKey]) > 1 ? 1 : Number(defines[defineKey]))); + return total === splitDefines.length; +} +function getOrDefineValue(splitDefines: Array, defines: ShaderDefines): boolean { + let total = 0; + splitDefines?.forEach?.((defineKey) => (total += Number(defines[defineKey]) > 1 ? 1 : Number(defines[defineKey]))); + return total === 0; +} +function splitShaderStrsByDefine(shader: string, defines: Array): Array { + let currentShaderStr = shader; + const shaderStrs = + defines?.map((define) => { + const length = currentShaderStr.indexOf(define); + const sliceStr = currentShaderStr.slice(0, length); + currentShaderStr = currentShaderStr.slice(length + define.length); + return sliceStr; + }) || []; + if (shaderStrs?.length) shaderStrs.push(currentShaderStr); + return shaderStrs; +} diff --git a/packages/reshader.gl/src/webgpu/common/math.ts b/packages/reshader.gl/src/webgpu/common/math.ts new file mode 100644 index 0000000000..e05478f9f1 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/common/math.ts @@ -0,0 +1,13 @@ +/** + * Rounds a number up to nearest multiple. + * + * @param numToRound - The number to round up. + * @param multiple - The multiple to round up to. + * @returns A number rounded up to nearest multiple. + */ +export function roundUp(numToRound: number, multiple: number) { + if (multiple === 0) { + return numToRound; + } + return Math.ceil(numToRound / multiple) * multiple; +} diff --git a/packages/reshader.gl/tsconfig.json b/packages/reshader.gl/tsconfig.json index 67d905bb38..2a9d1908a9 100644 --- a/packages/reshader.gl/tsconfig.json +++ b/packages/reshader.gl/tsconfig.json @@ -1,9 +1,11 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "lib": ["ES2021", "DOM"], "outDir": "./dist/", "rootDir": "./src", "baseUrl": "./", + "types": ["@webgpu/types"] }, "include": [ "src/**/*.ts", diff --git a/packages/traffic/package.json b/packages/traffic/package.json index 67794f4ae2..9ddb6ef599 100644 --- a/packages/traffic/package.json +++ b/packages/traffic/package.json @@ -50,7 +50,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", "rollup-plugin-dts": "^6.1.0" diff --git a/packages/traffic/src/TrafficScene.ts b/packages/traffic/src/TrafficScene.ts index 2afaa14686..622b75dad0 100644 --- a/packages/traffic/src/TrafficScene.ts +++ b/packages/traffic/src/TrafficScene.ts @@ -1,4 +1,4 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import * as turf from "@turf/turf"; //@ts-expect-error-error import { GLTFLayer, MultiGLTFMarker } from "@maptalks/gltf-layer"; diff --git a/packages/transform-control/package.json b/packages/transform-control/package.json index 595d3adda9..342c857fb0 100644 --- a/packages/transform-control/package.json +++ b/packages/transform-control/package.json @@ -33,7 +33,7 @@ "@rollup/plugin-terser": "0.4.4", "eslint": "^8.57.0", "eslint-plugin-mocha": "^10.5.0", - "@maptalks/map": "workspace:*", + "maptalks": "workspace:*", "pixelmatch": "^4.0.2", "rollup": "^4.17.2" }, diff --git a/packages/transform-control/src/TransformControl.js b/packages/transform-control/src/TransformControl.js index 1047e1a6ba..5bb47be574 100644 --- a/packages/transform-control/src/TransformControl.js +++ b/packages/transform-control/src/TransformControl.js @@ -1,5 +1,5 @@ import { mat4, quat, vec3, vec2, reshader } from '@maptalks/gl'; -import { Handlerable, Eventable, Class, Point, Coordinate, INTERNAL_LAYER_PREFIX } from '@maptalks/map'; +import { Handlerable, Eventable, Class, Point, Coordinate, INTERNAL_LAYER_PREFIX } from 'maptalks'; import TransformHelper from './helper/TransformHelper'; import { calFixedScale, getTranslationPoint } from './common/Util'; import vert from './common/helper.vert'; diff --git a/packages/transform-control/src/TransformTarget.js b/packages/transform-control/src/TransformTarget.js index 9c39cef4ab..f105b272fe 100644 --- a/packages/transform-control/src/TransformTarget.js +++ b/packages/transform-control/src/TransformTarget.js @@ -1,4 +1,4 @@ -import { Coordinate, Point } from "@maptalks/map"; +import { Coordinate, Point } from "maptalks"; import { vec3, mat4, quat } from '@maptalks/gl'; const EMPTY_VEC = [], EMPTY_QUAT = [], EMPTY_MAT = [], TEMP_POINT = new Point(0, 0), TEMP_VEC_1 = [], TEMP_VEC_2 = [], TEMP_SCALE = [], EMPTY_TRANS = [0, 0, 0]; diff --git a/packages/transform-control/src/common/Util.js b/packages/transform-control/src/common/Util.js index 75d1c8e16e..09264a4869 100644 --- a/packages/transform-control/src/common/Util.js +++ b/packages/transform-control/src/common/Util.js @@ -1,6 +1,6 @@ import { mat4, reshader, quat, vec3 } from '@maptalks/gl'; import partsModels from './models'; -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { getGLTFLoaderBundle } from '@maptalks/gl/dist/transcoders'; const gltfloader = getGLTFLoaderBundle(); diff --git a/packages/vt/package.json b/packages/vt/package.json index e63d2d4215..d0c734156b 100644 --- a/packages/vt/package.json +++ b/packages/vt/package.json @@ -61,7 +61,7 @@ "eslint-plugin-mocha": "^10.5.0", "express": "^4.17.1", "happen": "^0.3.1", - "@maptalks/map": "workspace:*", + "maptalks": "workspace:*", "mocha": "^10.3.0", "pixelmatch": "^4.0.2", "rollup": "^4.17.2", diff --git a/packages/vt/src/common/IconRequestor.js b/packages/vt/src/common/IconRequestor.js index b122d7708e..d69d5598e3 100644 --- a/packages/vt/src/common/IconRequestor.js +++ b/packages/vt/src/common/IconRequestor.js @@ -1,4 +1,4 @@ -import { Marker, Util } from '@maptalks/map'; +import { Marker, Util } from 'maptalks'; import LRUCache from '../packer/LRUCache'; export default class IconRequestor { diff --git a/packages/vt/src/layer/layer/GeojsonVectorTileLayer.ts b/packages/vt/src/layer/layer/GeojsonVectorTileLayer.ts index 07ea6ea78c..c6fd688aff 100644 --- a/packages/vt/src/layer/layer/GeojsonVectorTileLayer.ts +++ b/packages/vt/src/layer/layer/GeojsonVectorTileLayer.ts @@ -1,9 +1,9 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import { isNumber, isObject, isString } from "../../common/Util"; import Ajax from "../../worker/util/Ajax"; -import type { ArrayExtent, Callback, LayerJSONType } from "@maptalks/map"; +import type { ArrayExtent, Callback, LayerJSONType } from "maptalks"; import VectorTileLayer, { VectorTileLayerOptionsType } from "./VectorTileLayer"; const options = { diff --git a/packages/vt/src/layer/layer/VectorTileLayer.ts b/packages/vt/src/layer/layer/VectorTileLayer.ts index 4bd8bfb7b1..90bcd6c9fc 100644 --- a/packages/vt/src/layer/layer/VectorTileLayer.ts +++ b/packages/vt/src/layer/layer/VectorTileLayer.ts @@ -1,4 +1,4 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import type { BackgroundConfig, @@ -18,8 +18,8 @@ import { extend, hasOwn, isNil, isObject, isString, pushIn } from "../../common/ import Ajax from "../../worker/util/Ajax"; import VectorTileLayerRenderer from "../renderer/VectorTileLayerRenderer"; import { isFunctionDefinition } from "@maptalks/function-type"; -import { LayerIdentifyOptionsType } from "@maptalks/map"; -import { PositionArray, TileLayerOptionsType } from "@maptalks/map"; +import { LayerIdentifyOptionsType } from "maptalks"; +import { PositionArray, TileLayerOptionsType } from "maptalks"; const { PackUtil } = getVectorPacker(); diff --git a/packages/vt/src/layer/plugins/Util.js b/packages/vt/src/layer/plugins/Util.js index 838bf32f63..ba6548451c 100644 --- a/packages/vt/src/layer/plugins/Util.js +++ b/packages/vt/src/layer/plugins/Util.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { isFunctionDefinition } from '@maptalks/function-type'; import Color from 'color'; diff --git a/packages/vt/src/layer/plugins/painters/CollisionPainter.js b/packages/vt/src/layer/plugins/painters/CollisionPainter.js index a8aaf71329..d453ccce75 100644 --- a/packages/vt/src/layer/plugins/painters/CollisionPainter.js +++ b/packages/vt/src/layer/plugins/painters/CollisionPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader, vec4 } from '@maptalks/gl'; import collisionVert from './glsl/collision.vert'; import collisionFrag from './glsl/collision.frag'; diff --git a/packages/vt/src/layer/plugins/painters/FillPainter.js b/packages/vt/src/layer/plugins/painters/FillPainter.js index 37562c0874..a0605749b8 100644 --- a/packages/vt/src/layer/plugins/painters/FillPainter.js +++ b/packages/vt/src/layer/plugins/painters/FillPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import BasicPainter from './BasicPainter'; import { vec2, reshader, mat4 } from '@maptalks/gl'; import vert from './glsl/fill.vert'; diff --git a/packages/vt/src/layer/plugins/painters/IconPainter.js b/packages/vt/src/layer/plugins/painters/IconPainter.js index eca75673bc..416fd4d59e 100644 --- a/packages/vt/src/layer/plugins/painters/IconPainter.js +++ b/packages/vt/src/layer/plugins/painters/IconPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { isFunctionDefinition } from '@maptalks/function-type'; import CollisionPainter from './CollisionPainter'; import { reshader } from '@maptalks/gl'; diff --git a/packages/vt/src/layer/plugins/painters/LinePainter.js b/packages/vt/src/layer/plugins/painters/LinePainter.js index 452c6f7bff..c0dd69cbeb 100644 --- a/packages/vt/src/layer/plugins/painters/LinePainter.js +++ b/packages/vt/src/layer/plugins/painters/LinePainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import Color from 'color'; import BasicPainter from './BasicPainter'; import { reshader } from '@maptalks/gl'; @@ -529,7 +529,6 @@ class LinePainter extends BasicPainter { enable: () => { return this.isEnableTileStencil(context); }, - mask: 0xff, func: { cmp: () => { return '<='; diff --git a/packages/vt/src/layer/plugins/painters/MeshPainter.js b/packages/vt/src/layer/plugins/painters/MeshPainter.js index d7a6772905..ac7836d80d 100644 --- a/packages/vt/src/layer/plugins/painters/MeshPainter.js +++ b/packages/vt/src/layer/plugins/painters/MeshPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader } from '@maptalks/gl'; import { vec2, mat4 } from '@maptalks/gl'; import Painter from './Painter'; diff --git a/packages/vt/src/layer/plugins/painters/NativelinePainter.js b/packages/vt/src/layer/plugins/painters/NativelinePainter.js index ef70410c0a..3a4f747c87 100644 --- a/packages/vt/src/layer/plugins/painters/NativelinePainter.js +++ b/packages/vt/src/layer/plugins/painters/NativelinePainter.js @@ -103,15 +103,13 @@ class NativeLinePainter extends BasicPainter { enable: () => { return this.isEnableTileStencil(context); }, - mask: 0xFF, func: { cmp: () => { return this.isOnly2D() ? '=' : '<='; }, ref: (context, props) => { return props.stencilRef; - }, - mask: 0xFF + } }, op: { fail: 'keep', diff --git a/packages/vt/src/layer/plugins/painters/Painter.js b/packages/vt/src/layer/plugins/painters/Painter.js index 2c1d38f527..f2394bd5c2 100644 --- a/packages/vt/src/layer/plugins/painters/Painter.js +++ b/packages/vt/src/layer/plugins/painters/Painter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader, vec3, mat4, HighlightUtil } from '@maptalks/gl'; import { getVectorPacker } from '../../../packer/inject'; import { isFunctionDefinition, interpolated, piecewiseConstant } from '@maptalks/function-type'; diff --git a/packages/vt/src/layer/plugins/painters/PhongPainter.js b/packages/vt/src/layer/plugins/painters/PhongPainter.js index e499245784..6541d05db2 100644 --- a/packages/vt/src/layer/plugins/painters/PhongPainter.js +++ b/packages/vt/src/layer/plugins/painters/PhongPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader } from '@maptalks/gl'; import { mat4 } from '@maptalks/gl'; import { extend, hasOwn } from '../Util'; diff --git a/packages/vt/src/layer/plugins/painters/TubePainter.js b/packages/vt/src/layer/plugins/painters/TubePainter.js index f84e34d547..15bb094cf3 100644 --- a/packages/vt/src/layer/plugins/painters/TubePainter.js +++ b/packages/vt/src/layer/plugins/painters/TubePainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import Color from 'color'; import { reshader, mat4, mat3 } from '@maptalks/gl'; import BasicPainter from './BasicPainter'; diff --git a/packages/vt/src/layer/plugins/painters/WaterPainter.js b/packages/vt/src/layer/plugins/painters/WaterPainter.js index 1caffb8dbc..3660a66360 100644 --- a/packages/vt/src/layer/plugins/painters/WaterPainter.js +++ b/packages/vt/src/layer/plugins/painters/WaterPainter.js @@ -330,11 +330,9 @@ class WaterPainter extends BasicPainter { colorMask: [false, false, false, false], stencil: { enable: true, - mask: 0xFF, func: { cmp: '<=', - ref: 0xFE, - mask: 0xFF + ref: 0xFE }, op: { fail: 'keep', @@ -357,11 +355,9 @@ class WaterPainter extends BasicPainter { viewport, stencil: { enable: true, - mask: 0xFF, func: { cmp: '==', - ref: 0xFE, - mask: 0xFF + ref: 0xFE }, op: { fail: 'keep', @@ -464,7 +460,7 @@ class WaterPainter extends BasicPainter { planeGeo.data.aTexCoord = new Uint8Array( [0, 1, 1, 1, 0, 0, 1, 0] ); - planeGeo.generateBuffers(this.renderer.regl); + planeGeo.generateBuffers(this.renderer.device); this._water = new reshader.Mesh(planeGeo, null, { castShadow: false }); this._waterScene = new reshader.Scene([this._water]); diff --git a/packages/vt/src/layer/plugins/painters/pbr/PBRPainter.js b/packages/vt/src/layer/plugins/painters/pbr/PBRPainter.js index d99ad39200..3870d9d829 100644 --- a/packages/vt/src/layer/plugins/painters/pbr/PBRPainter.js +++ b/packages/vt/src/layer/plugins/painters/pbr/PBRPainter.js @@ -198,9 +198,6 @@ class PBRPainter extends Painter { enable: true, face: 'back' }, - stencil: { - enable: false - }, viewport // polygonOffset: { // enable: true, diff --git a/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js b/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js index bd1cdf801b..4fc596dd45 100644 --- a/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js +++ b/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader, mat4 } from '@maptalks/gl'; import { extend, hasOwn, isNil } from '../../Util'; import MeshPainter from '../MeshPainter'; @@ -223,9 +223,6 @@ class StandardPainter extends MeshPainter { return this.sceneConfig.cullFace || 'back'; } }, - stencil: { - enable: false - }, viewport, depth: { enable: true, diff --git a/packages/vt/src/layer/plugins/painters/pbr/StencilShadowPass.js b/packages/vt/src/layer/plugins/painters/pbr/StencilShadowPass.js index fa469e9686..cf3bbf29a3 100644 --- a/packages/vt/src/layer/plugins/painters/pbr/StencilShadowPass.js +++ b/packages/vt/src/layer/plugins/painters/pbr/StencilShadowPass.js @@ -42,7 +42,7 @@ export default class StencilShadowPass { const shadow = new reshader.Geometry({ aPosition: data.vertices }, data.indices); - shadow.generateBuffers(this.renderer.regl); + shadow.generateBuffers(this.renderer.device); return [new reshader.Mesh(shadow)]; } diff --git a/packages/vt/src/layer/plugins/painters/util/create_text_painter.js b/packages/vt/src/layer/plugins/painters/util/create_text_painter.js index 53ba53f78b..16dd1d0206 100644 --- a/packages/vt/src/layer/plugins/painters/util/create_text_painter.js +++ b/packages/vt/src/layer/plugins/painters/util/create_text_painter.js @@ -337,25 +337,18 @@ export function createTextShader(canvas, sceneConfig) { viewport, stencil: { //fix #94, intel显卡的崩溃和blending关系比较大,开启stencil来避免blending enable: true, - mask: 0xFF, func: { //halo的stencil ref更大,允许文字填充在halo上绘制 cmp: '<=', //renderer.isEnableWorkAround('win-intel-gpu-crash') ? '<' : '<=', ref: (context, props) => { return props.stencilRef; - }, - mask: 0xFF + } }, op: { fail: 'keep', zfail: 'keep', zpass: 'replace' - }, - // opBack: { - // fail: 'keep', - // zfail: 'keep', - // zpass: 'replace' - // } + } }, blend: { enable: true, diff --git a/packages/vt/src/layer/plugins/painters/util/line_offset.js b/packages/vt/src/layer/plugins/painters/util/line_offset.js index 2278749474..90067661bd 100644 --- a/packages/vt/src/layer/plugins/painters/util/line_offset.js +++ b/packages/vt/src/layer/plugins/painters/util/line_offset.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import Point from '@mapbox/point-geometry'; import { projectPoint } from './projection'; import { vec3 } from '@maptalks/gl'; diff --git a/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js b/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js index 2e3a8d2c72..4ee35d3eff 100644 --- a/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js +++ b/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { mat4, vec3, createREGL, GroundPainter } from '@maptalks/gl'; import WorkerConnection from './worker/WorkerConnection'; import { EMPTY_VECTOR_TILE } from '../core/Constant'; @@ -248,20 +248,11 @@ class VectorTileLayerRenderer extends maptalks.renderer.TileLayerCanvasRenderer return !!inGroup; } - createContext() { - const inGroup = this.canvas.gl && this.canvas.gl.wrap; - if (inGroup) { - this.gl = this.canvas.gl.wrap(); - this.regl = this.canvas.gl.regl; - } else { - const { gl, regl, attributes } = this._createREGLContext(this.canvas); - this.gl = gl; - this.regl = regl; - this.glOptions = attributes; - } - if (inGroup) { - this.canvas.pickingFBO = this.canvas.pickingFBO || this.regl.framebuffer(this.canvas.width, this.canvas.height); - } + initContext() { + const { regl, reglGL } = this.context; + this.regl = regl; + this.gl = reglGL; + this.canvas.pickingFBO = this.canvas.pickingFBO || this.regl.framebuffer(this.canvas.width, this.canvas.height); this.pickingFBO = this.canvas.pickingFBO || this.regl.framebuffer(this.canvas.width, this.canvas.height); this._debugPainter = new DebugPainter(this.regl, this.getMap()); this._prepareWorker(); @@ -326,19 +317,6 @@ class VectorTileLayerRenderer extends maptalks.renderer.TileLayerCanvasRenderer this._workerCacheIndex++; } - clearCanvas() { - super.clearCanvas(); - if (!this.regl) { - return; - } - //这里必须通过regl来clear,如果直接调用webgl context的clear,则brdf的texture会被设为0 - this.regl.clear({ - color: CLEAR_COLOR, - depth: 1, - stencil: 0 - }); - } - isDrawable() { return true; } @@ -1611,6 +1589,8 @@ class VectorTileLayerRenderer extends maptalks.renderer.TileLayerCanvasRenderer this._groundPainter.dispose(); delete this._groundPainter; } + delete this.gl; + delete this.regl; if (super.onRemove) super.onRemove(); this._clearPlugin(); } @@ -2006,8 +1986,7 @@ class VectorTileLayerRenderer extends maptalks.renderer.TileLayerCanvasRenderer _getLayerOpacity() { const layerOpacity = this.layer.options['opacity']; - // 不在GroupGLLayer中时,MapCanvasRenderer会读取opacity并按照透明度绘制,所以layerOpacity设成1 - return this._isInGroupGLLayer() ? (isNil(layerOpacity) ? 1 : layerOpacity) : 1; + return (isNil(layerOpacity) ? 1 : layerOpacity); } } diff --git a/packages/vt/src/layer/renderer/stencil/TileStencilRenderer.js b/packages/vt/src/layer/renderer/stencil/TileStencilRenderer.js index 47218a1235..f8d8bf7b11 100644 --- a/packages/vt/src/layer/renderer/stencil/TileStencilRenderer.js +++ b/packages/vt/src/layer/renderer/stencil/TileStencilRenderer.js @@ -104,13 +104,11 @@ export default class TileStencilRenderer { viewport, stencil: { enable: true, - mask: 0xFF, func: { cmp: 'always', ref: (context, props) => { return props.ref; - }, - mask: 0xFF + } }, op: { fail: 'replace', diff --git a/packages/vt/src/layer/renderer/utils/DebugPainter.js b/packages/vt/src/layer/renderer/utils/DebugPainter.js index eeb88c852c..430d3928fe 100644 --- a/packages/vt/src/layer/renderer/utils/DebugPainter.js +++ b/packages/vt/src/layer/renderer/utils/DebugPainter.js @@ -160,9 +160,6 @@ class DebugPainter { }, equation: 'add' }, - stencil: { - enable: false - }, viewport: { x: 0, y: 0, diff --git a/packages/vt/src/layer/renderer/utils/convert_to_painter_features.js b/packages/vt/src/layer/renderer/utils/convert_to_painter_features.js index 76ffe911f4..d0aa35c191 100644 --- a/packages/vt/src/layer/renderer/utils/convert_to_painter_features.js +++ b/packages/vt/src/layer/renderer/utils/convert_to_painter_features.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { KEY_IDX } from '../../../common/Constant'; import { extend } from '../../../common/Util'; diff --git a/packages/vt/src/layer/renderer/worker/WorkerConnection.js b/packages/vt/src/layer/renderer/worker/WorkerConnection.js index 4ea0ff4367..1264f7402e 100644 --- a/packages/vt/src/layer/renderer/worker/WorkerConnection.js +++ b/packages/vt/src/layer/renderer/worker/WorkerConnection.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { extend, uid, toJSON } from '../../../common/Util'; import IconRequestor from '../../../common/IconRequestor'; import { getVectorPacker } from '../../../packer/inject'; diff --git a/packages/vt/src/layer/vector/ExtrudePolygonLayer.ts b/packages/vt/src/layer/vector/ExtrudePolygonLayer.ts index ae11d7865f..79e570ca49 100644 --- a/packages/vt/src/layer/vector/ExtrudePolygonLayer.ts +++ b/packages/vt/src/layer/vector/ExtrudePolygonLayer.ts @@ -1,11 +1,11 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import type { LitDataConfig, LitMaterial } from "../../types"; import { extend, isNil } from "../../common/Util"; import { getVectorPacker } from "../../packer/inject"; import { ID_PROP } from "./util/convert_to_feature"; -import type { OverlayLayerOptionsType } from "@maptalks/map"; +import type { OverlayLayerOptionsType } from "maptalks"; import { PROP_OMBB } from "../../common/Constant"; import { PolygonLayerRenderer } from "./PolygonLayer"; import Vector3DLayer from "./Vector3DLayer"; diff --git a/packages/vt/src/layer/vector/LineStringLayer.ts b/packages/vt/src/layer/vector/LineStringLayer.ts index 56be13d4b9..c0a8e3dae2 100644 --- a/packages/vt/src/layer/vector/LineStringLayer.ts +++ b/packages/vt/src/layer/vector/LineStringLayer.ts @@ -1,4 +1,4 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import { LINE_GRADIENT_PROP_KEY, LINE_SYMBOL } from "./util/symbols"; diff --git a/packages/vt/src/layer/vector/PointLayer.ts b/packages/vt/src/layer/vector/PointLayer.ts index 9dffbfba0b..6202e49dbd 100644 --- a/packages/vt/src/layer/vector/PointLayer.ts +++ b/packages/vt/src/layer/vector/PointLayer.ts @@ -1,7 +1,7 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import { ICON_PAINTER_SCENECONFIG } from "../core/Constant"; -import type { OverlayLayerOptionsType } from "@maptalks/map"; +import type { OverlayLayerOptionsType } from "maptalks"; import Vector3DLayer from "./Vector3DLayer"; import Vector3DLayerRenderer from "./Vector3DLayerRenderer"; import { extend } from "../../common/Util"; diff --git a/packages/vt/src/layer/vector/PolygonLayer.ts b/packages/vt/src/layer/vector/PolygonLayer.ts index aa878112e0..88e8ee0101 100644 --- a/packages/vt/src/layer/vector/PolygonLayer.ts +++ b/packages/vt/src/layer/vector/PolygonLayer.ts @@ -1,4 +1,4 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import { ID_PROP } from "./util/convert_to_feature"; import { getVectorPacker } from "../../packer/inject"; diff --git a/packages/vt/src/layer/vector/Vector3DLayer.ts b/packages/vt/src/layer/vector/Vector3DLayer.ts index 8d26826246..2d232c1815 100644 --- a/packages/vt/src/layer/vector/Vector3DLayer.ts +++ b/packages/vt/src/layer/vector/Vector3DLayer.ts @@ -1,6 +1,6 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; -import { LayerIdentifyOptionsType } from "@maptalks/map"; +import { LayerIdentifyOptionsType } from "maptalks"; import { extend } from "../../common/Util"; const defaultOptions = { diff --git a/packages/vt/src/layer/vector/Vector3DLayerRenderer.js b/packages/vt/src/layer/vector/Vector3DLayerRenderer.js index 2df36ca683..efdf3ad3a8 100644 --- a/packages/vt/src/layer/vector/Vector3DLayerRenderer.js +++ b/packages/vt/src/layer/vector/Vector3DLayerRenderer.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { createREGL, reshader, mat4, vec3 } from '@maptalks/gl'; import { convertToFeature, ID_PROP } from './util/convert_to_feature'; import { extend, hasOwn, getCentiMeterScale, isNil } from '../../common/Util'; @@ -1530,19 +1530,6 @@ class Vector3DLayerRenderer extends maptalks.renderer.CanvasRenderer { /* eslint-enable no-empty */ } - clearCanvas() { - super.clearCanvas(); - if (!this.regl) { - return; - } - //这里必须通过regl来clear,如果直接调用webgl context的clear,则brdf的texture会被设为0 - this.regl.clear({ - color: [0, 0, 0, 0], - depth: 1, - stencil: 0xFF - }); - } - resizeCanvas(canvasSize) { super.resizeCanvas(canvasSize); const canvas = this.canvas; @@ -1654,8 +1641,7 @@ class Vector3DLayerRenderer extends maptalks.renderer.CanvasRenderer { _getLayerOpacity() { const layerOpacity = this.layer.options['opacity']; - // 不在GroupGLLayer中时,MapCanvasRenderer会读取opacity并按照透明度绘制,所以layerOpacity设成1 - return this._isInGroupGLLayer() ? (isNil(layerOpacity) ? 1 : layerOpacity) : 1; + return (isNil(layerOpacity) ? 1 : layerOpacity); } } diff --git a/packages/vt/src/layer/vector/util/convert_to_feature.js b/packages/vt/src/layer/vector/util/convert_to_feature.js index c0b3ec47f8..306002c0c0 100644 --- a/packages/vt/src/layer/vector/util/convert_to_feature.js +++ b/packages/vt/src/layer/vector/util/convert_to_feature.js @@ -1,5 +1,5 @@ import { extend, hasOwn } from '../../../common/Util'; -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { KEY_IDX } from '../../../common/Constant'; import { LINE_GRADIENT_PROP_KEY } from './symbols'; import { getVectorPacker } from '../../../packer/inject'; diff --git a/packages/vt/src/layer/vector/util/from_json.js b/packages/vt/src/layer/vector/util/from_json.js index 73cf7eeefb..34f46daad5 100644 --- a/packages/vt/src/layer/vector/util/from_json.js +++ b/packages/vt/src/layer/vector/util/from_json.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; export function fromJSON(json, type, clazz) { if (!json || json['type'] !== type) { diff --git a/packages/vt/src/packer/IconRequestor.js b/packages/vt/src/packer/IconRequestor.js index 2dab3472c9..7cd49d2ad0 100644 --- a/packages/vt/src/packer/IconRequestor.js +++ b/packages/vt/src/packer/IconRequestor.js @@ -1,4 +1,4 @@ -import { Marker, Util } from '@maptalks/map'; +import { Marker, Util } from 'maptalks'; import LRUCache from './LRUCache'; export default class IconRequestor { diff --git a/packages/vt/src/types/index.ts b/packages/vt/src/types/index.ts index 6a025b9ca0..4fbda1549d 100644 --- a/packages/vt/src/types/index.ts +++ b/packages/vt/src/types/index.ts @@ -3,7 +3,7 @@ import type { LineSymbol, MarkerCommonSymbol, TextSymbol, -} from "@maptalks/map"; +} from "maptalks"; export interface FillDataConfig { type?: "fill"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fd872033b..142aae9788 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,9 +54,6 @@ importers: '@maptalks/gltf-layer': specifier: workspace:* version: link:../layer-gltf - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/vt': specifier: workspace:* version: link:../vt @@ -93,6 +90,9 @@ importers: karma-mocha-reporter: specifier: ^2.2.5 version: 2.2.5(karma@6.4.4) + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 @@ -133,9 +133,6 @@ importers: '@maptalks/gltf-loader': specifier: workspace:* version: link:../gltf-loader - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/martini': specifier: ^0.4.0 version: 0.4.0 @@ -151,6 +148,9 @@ importers: colorin: specifier: ^0.6.0 version: 0.6.0 + maptalks: + specifier: workspace:* + version: link:../map devDependencies: '@rollup/plugin-commonjs': specifier: ^25.0.7 @@ -213,9 +213,6 @@ importers: specifier: ^3.4.0 version: 3.4.3 devDependencies: - '@maptalks/map': - specifier: workspace:* - version: link:../map '@rollup/plugin-commonjs': specifier: ^25.0.7 version: 25.0.7(rollup@4.17.2) @@ -249,6 +246,9 @@ importers: karma-mocha-reporter: specifier: ^2.2.5 version: 2.2.5(karma@6.4.4) + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 @@ -261,9 +261,6 @@ importers: '@maptalks/function-type': specifier: ^1.4.0 version: 1.4.0 - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/tbn-packer': specifier: ^1.4.5 version: 1.4.5 @@ -276,6 +273,9 @@ importers: gl-matrix: specifier: ^3.4.0 version: 3.4.3 + maptalks: + specifier: workspace:* + version: link:../map pako: specifier: ^2.0.4 version: 2.1.0 @@ -362,9 +362,6 @@ importers: '@maptalks/gl': specifier: workspace:* version: link:../gl - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/transcoders.draco': specifier: workspace:* version: link:../transcoders.draco @@ -416,6 +413,9 @@ importers: karma-mocha-reporter: specifier: ^2.2.5 version: 2.2.5(karma@6.4.4) + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 @@ -432,9 +432,6 @@ importers: '@maptalks/gl': specifier: workspace:* version: link:../gl - '@maptalks/map': - specifier: workspace:* - version: link:../map '@rollup/plugin-commonjs': specifier: ^25.0.7 version: 25.0.7(rollup@4.17.2) @@ -480,6 +477,9 @@ importers: karma-mocha-reporter: specifier: ^2.2.5 version: 2.2.5(karma@6.4.4) + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 @@ -510,13 +510,6 @@ importers: simplify-js: specifier: ^1.2.1 version: 1.2.4 - optionalDependencies: - '@rollup/rollup-darwin-x64': - specifier: ^4.13.0 - version: 4.17.2 - '@rollup/rollup-linux-x64-gnu': - specifier: ^4.13.0 - version: 4.17.2 devDependencies: '@babel/preset-env': specifier: ^7.24.1 @@ -620,6 +613,13 @@ importers: typescript-eslint: specifier: ^7.18.0 version: 7.18.0(eslint@8.57.0)(typescript@5.4.5) + optionalDependencies: + '@rollup/rollup-darwin-x64': + specifier: ^4.13.0 + version: 4.17.2 + '@rollup/rollup-linux-x64-gnu': + specifier: ^4.13.0 + version: 4.17.2 packages/maptalks-gl: dependencies: @@ -632,9 +632,6 @@ importers: '@maptalks/gltf-layer': specifier: workspace:* version: link:../layer-gltf - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/transform-control': specifier: workspace:* version: link:../transform-control @@ -644,6 +641,9 @@ importers: '@maptalks/vt': specifier: workspace:* version: link:../vt + maptalks: + specifier: workspace:* + version: link:../map devDependencies: '@rollup/plugin-commonjs': specifier: ^25.0.7 @@ -669,12 +669,18 @@ importers: '@maptalks/tbn-packer': specifier: ^1.4.5 version: 1.4.5 + '@webgpu/types': + specifier: 0.1.52 + version: 0.1.52 earcut: specifier: ^3.0.1 version: 3.0.1 gl-matrix: specifier: ^3.4.0 version: 3.4.3 + wgsl_reflect: + specifier: ^1.0.16 + version: 1.0.16 devDependencies: '@maptalks/rollup-plugin-glsl-minify': specifier: ^0.1.7 @@ -688,6 +694,9 @@ importers: '@rollup/plugin-terser': specifier: 0.4.4 version: 0.4.4(rollup@4.17.2) + '@rollup/plugin-typescript': + specifier: ^11.1.6 + version: 11.1.6(rollup@4.17.2)(tslib@2.6.2)(typescript@5.4.5) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -703,6 +712,15 @@ importers: rollup-plugin-dts: specifier: ^6.1.0 version: 6.1.1(rollup@4.17.2)(typescript@5.4.5) + tslib: + specifier: ^2.6.2 + version: 2.6.2 + typescript: + specifier: ^5.4.2 + version: 5.4.5 + typescript-eslint: + specifier: ^7.18.0 + version: 7.18.0(eslint@8.57.0)(typescript@5.4.5) packages/traffic: dependencies: @@ -710,9 +728,6 @@ importers: specifier: workspace:* version: link:../layer-gltf devDependencies: - '@maptalks/map': - specifier: workspace:* - version: link:../map '@rollup/plugin-commonjs': specifier: ^25.0.7 version: 25.0.7(rollup@4.17.2) @@ -755,6 +770,9 @@ importers: karma-mocha-reporter: specifier: ^2.2.5 version: 2.2.5(karma@6.4.4) + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 @@ -861,9 +879,6 @@ importers: specifier: workspace:* version: link:../layer-gltf devDependencies: - '@maptalks/map': - specifier: workspace:* - version: link:../map '@rollup/plugin-commonjs': specifier: ^25.0.7 version: 25.0.7(rollup@4.17.2) @@ -882,6 +897,9 @@ importers: eslint-plugin-mocha: specifier: ^10.5.0 version: 10.5.0(eslint@8.57.0) + maptalks: + specifier: workspace:* + version: link:../map pixelmatch: specifier: ^4.0.2 version: 4.0.2 @@ -958,9 +976,6 @@ importers: specifier: ^3.1.0 version: 3.1.3 devDependencies: - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/rollup-plugin-glsl-minify': specifier: ^0.1.7 version: 0.1.7 @@ -1000,6 +1015,9 @@ importers: happen: specifier: ^0.3.1 version: 0.3.2 + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 @@ -2418,6 +2436,9 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@webgpu/types@0.1.52': + resolution: {integrity: sha512-eI883Nlag2hGIkhXxAnq8s4APpqXWuPL3Gbn2ghiU12UjLvfCbVqHK4XfXl3eLRTatqcMmeK7jws7IwWsGfbzw==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -4807,6 +4828,9 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + wgsl_reflect@1.0.16: + resolution: {integrity: sha512-OE3urfXXbHMD5lhKZwxOxC9SFYynEGEkWXQmvi7B1gzzr5jb9+drh9A8MeBvVqKqznCoBuh8WOzVuSGSZs4CkQ==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -7273,6 +7297,8 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@webgpu/types@0.1.52': {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -9769,6 +9795,8 @@ snapshots: dependencies: makeerror: 1.0.12 + wgsl_reflect@1.0.16: {} + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4