mirror of
https://github.com/pissang/claygl.git
synced 2026-02-01 17:27:08 +00:00
wip(type): add type to more util methods
This commit is contained in:
parent
b46e8c1159
commit
cf4bf27158
@ -3,7 +3,6 @@ import * as mat4 from './glmatrix/mat4';
|
||||
import BoundingBox from './math/BoundingBox';
|
||||
import GeometryBase, { AttributeValue, GeometryAttribute, GeometryBaseOpts } from './GeometryBase';
|
||||
import type Matrix4 from './math/Matrix4';
|
||||
import type Renderer from './Renderer';
|
||||
|
||||
export interface GeometryOpts extends GeometryBaseOpts {}
|
||||
|
||||
@ -81,7 +80,6 @@ class Geometry extends GeometryBase {
|
||||
/**
|
||||
* Calculated bounding box of geometry.
|
||||
*/
|
||||
boundingBox?: BoundingBox;
|
||||
|
||||
attributes!: {
|
||||
position: GeometryAttribute<3>;
|
||||
|
||||
@ -8,6 +8,7 @@ import type Renderer from './Renderer';
|
||||
import type Camera from './Camera';
|
||||
import type Renderable from './Renderable';
|
||||
import type Vector2 from './math/Vector2';
|
||||
import type BoundingBox from './math/BoundingBox';
|
||||
|
||||
export type AttributeType = 'byte' | 'ubyte' | 'short' | 'ushort' | 'float';
|
||||
export type AttributeSize = 1 | 2 | 3 | 4;
|
||||
@ -295,6 +296,8 @@ interface GeometryBase extends GeometryBaseOpts {}
|
||||
class GeometryBase {
|
||||
readonly __uid__ = genGUID();
|
||||
|
||||
// TODO put in GeometryBase?
|
||||
boundingBox?: BoundingBox;
|
||||
/**
|
||||
* Attributes of geometry.
|
||||
*/
|
||||
|
||||
@ -65,7 +65,7 @@ export interface Texture2DOpts extends TextureOpts, Texture2DData {
|
||||
|
||||
interface Texture2D extends Omit<Texture2DOpts, 'image'> {}
|
||||
class Texture2D extends Texture {
|
||||
readonly textureType: string = 'texture2D';
|
||||
readonly textureType = 'texture2D';
|
||||
|
||||
private _image?: TextureImageSource;
|
||||
private _potCanvas?: HTMLCanvasElement;
|
||||
|
||||
@ -101,7 +101,6 @@ class EnvironmentMapPass {
|
||||
return this._cameras[target];
|
||||
}
|
||||
render(renderer: Renderer, scene: Scene, notUpdateScene?: boolean) {
|
||||
const _gl = renderer.gl;
|
||||
const texture = this.texture;
|
||||
if (!notUpdateScene) {
|
||||
scene.update();
|
||||
|
||||
@ -11,7 +11,7 @@ import Shader from '../Shader';
|
||||
import Skybox from '../plugin/Skybox';
|
||||
import Scene from '../Scene';
|
||||
import EnvironmentMapPass from '../prePass/EnvironmentMap';
|
||||
import textureUtil from './texture';
|
||||
import { panoramaToCubeMap } from './texture';
|
||||
|
||||
import integrateBRDFShaderCode from './shader/integrateBRDF.glsl.js';
|
||||
import prefilterFragCode from './shader/prefilter.glsl.js';
|
||||
@ -81,7 +81,7 @@ export function prefilterEnvironmentMap(
|
||||
// FIXME FLOAT type will cause GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT error on iOS
|
||||
type: textureType === Texture.FLOAT ? Texture.HALF_FLOAT : textureType
|
||||
});
|
||||
textureUtil.panoramaToCubeMap(renderer, envMap, envCubemap, {
|
||||
panoramaToCubeMap(renderer, envMap, envCubemap, {
|
||||
// PENDING encodeRGBM so it can be decoded as RGBM
|
||||
encodeRGBM: textureOpts.decodeRGBM
|
||||
});
|
||||
|
||||
@ -4,13 +4,12 @@
|
||||
const EPSILON = 1.0 / 1048576.0;
|
||||
|
||||
function supertriangle(vertices) {
|
||||
let xmin = Number.POSITIVE_INFINITY;
|
||||
let ymin = Number.POSITIVE_INFINITY;
|
||||
let xmax = Number.NEGATIVE_INFINITY;
|
||||
let ymax = Number.NEGATIVE_INFINITY;
|
||||
let i, dx, dy, dmax, xmid, ymid;
|
||||
let xmin = Infinity;
|
||||
let ymin = Infinity;
|
||||
let xmax = -Infinity;
|
||||
let ymax = -Infinity;
|
||||
|
||||
for (i = vertices.length; i--; ) {
|
||||
for (let i = vertices.length; i--; ) {
|
||||
if (vertices[i][0] < xmin) {
|
||||
xmin = vertices[i][0];
|
||||
}
|
||||
@ -25,11 +24,11 @@ function supertriangle(vertices) {
|
||||
}
|
||||
}
|
||||
|
||||
dx = xmax - xmin;
|
||||
dy = ymax - ymin;
|
||||
dmax = Math.max(dx, dy);
|
||||
xmid = xmin + dx * 0.5;
|
||||
ymid = ymin + dy * 0.5;
|
||||
const dx = xmax - xmin;
|
||||
const dy = ymax - ymin;
|
||||
const dmax = Math.max(dx, dy);
|
||||
const xmid = xmin + dx * 0.5;
|
||||
const ymid = ymin + dy * 0.5;
|
||||
|
||||
return [
|
||||
[xmid - 20 * dmax, ymid - dmax],
|
||||
@ -126,7 +125,7 @@ const delaunay = {
|
||||
/* Make an array of indices into the vertex array, sorted by the
|
||||
* vertices' x-position. Force stable sorting by comparing indices if
|
||||
* the x-positions are equal. */
|
||||
indices = new Array(n);
|
||||
indices = [];
|
||||
|
||||
for (i = n; i--; ) {
|
||||
indices[i] = i;
|
||||
@ -222,7 +221,7 @@ const delaunay = {
|
||||
const b = tri[2][0] - tri[0][0];
|
||||
const c = tri[1][1] - tri[0][1];
|
||||
const d = tri[2][1] - tri[0][1];
|
||||
let i = a * d - b * c;
|
||||
const i = a * d - b * c;
|
||||
|
||||
/* Degenerate tri. */
|
||||
if (i === 0.0) {
|
||||
|
||||
648
src/util/mesh.ts
648
src/util/mesh.ts
@ -2,7 +2,7 @@
|
||||
// TODO test
|
||||
import Geometry from '../Geometry';
|
||||
import Mesh from '../Mesh';
|
||||
import Node from '../Node';
|
||||
import ClayNode from '../Node';
|
||||
import BoundingBox from '../math/BoundingBox';
|
||||
import vec3 from '../glmatrix/vec3';
|
||||
import mat4 from '../glmatrix/mat4';
|
||||
@ -10,330 +10,326 @@ import mat4 from '../glmatrix/mat4';
|
||||
/**
|
||||
* @namespace clay.util.mesh
|
||||
*/
|
||||
const meshUtil = {
|
||||
/**
|
||||
* Merge multiple meshes to one.
|
||||
* Note that these meshes must have the same material
|
||||
*
|
||||
* @param {Array.<clay.Mesh>} meshes
|
||||
* @param {boolean} applyWorldTransform
|
||||
* @return {clay.Mesh}
|
||||
* @memberOf clay.util.mesh
|
||||
*/
|
||||
merge: function (meshes, applyWorldTransform) {
|
||||
if (!meshes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const templateMesh = meshes[0];
|
||||
const templateGeo = templateMesh.geometry;
|
||||
const material = templateMesh.material;
|
||||
|
||||
const geometry = new Geometry({
|
||||
dynamic: false
|
||||
});
|
||||
geometry.boundingBox = new BoundingBox();
|
||||
|
||||
const attributeNames = templateGeo.getEnabledAttributes();
|
||||
|
||||
for (let i = 0; i < attributeNames.length; i++) {
|
||||
const name = attributeNames[i];
|
||||
const attr = templateGeo.attributes[name];
|
||||
// Extend custom attributes
|
||||
if (!geometry.attributes[name]) {
|
||||
geometry.attributes[name] = attr.clone(false);
|
||||
}
|
||||
}
|
||||
|
||||
const inverseTransposeMatrix = mat4.create();
|
||||
// Initialize the array data and merge bounding box
|
||||
let nVertex = 0;
|
||||
let nFace = 0;
|
||||
for (let k = 0; k < meshes.length; k++) {
|
||||
const currentGeo = meshes[k].geometry;
|
||||
if (currentGeo.boundingBox) {
|
||||
currentGeo.boundingBox.applyTransform(
|
||||
applyWorldTransform ? meshes[k].worldTransform : meshes[k].localTransform
|
||||
);
|
||||
geometry.boundingBox.union(currentGeo.boundingBox);
|
||||
}
|
||||
nVertex += currentGeo.vertexCount;
|
||||
nFace += currentGeo.triangleCount;
|
||||
}
|
||||
for (let n = 0; n < attributeNames.length; n++) {
|
||||
const name = attributeNames[n];
|
||||
const attrib = geometry.attributes[name];
|
||||
attrib.init(nVertex);
|
||||
}
|
||||
if (nVertex >= 0xffff) {
|
||||
geometry.indices = new Uint32Array(nFace * 3);
|
||||
} else {
|
||||
geometry.indices = new Uint16Array(nFace * 3);
|
||||
}
|
||||
|
||||
let vertexOffset = 0;
|
||||
let indicesOffset = 0;
|
||||
const useIndices = templateGeo.isUseIndices();
|
||||
|
||||
for (let mm = 0; mm < meshes.length; mm++) {
|
||||
const mesh = meshes[mm];
|
||||
const currentGeo = mesh.geometry;
|
||||
|
||||
const nVertex = currentGeo.vertexCount;
|
||||
|
||||
const matrix = applyWorldTransform ? mesh.worldTransform.array : mesh.localTransform.array;
|
||||
mat4.invert(inverseTransposeMatrix, matrix);
|
||||
mat4.transpose(inverseTransposeMatrix, inverseTransposeMatrix);
|
||||
|
||||
for (let nn = 0; nn < attributeNames.length; nn++) {
|
||||
const name = attributeNames[nn];
|
||||
const currentAttr = currentGeo.attributes[name];
|
||||
const targetAttr = geometry.attributes[name];
|
||||
// Skip the unused attributes;
|
||||
if (!currentAttr.value.length) {
|
||||
continue;
|
||||
}
|
||||
const len = currentAttr.value.length;
|
||||
const size = currentAttr.size;
|
||||
const offset = vertexOffset * size;
|
||||
const count = len / size;
|
||||
for (let i = 0; i < len; i++) {
|
||||
targetAttr.value[offset + i] = currentAttr.value[i];
|
||||
}
|
||||
// Transform position, normal and tangent
|
||||
if (name === 'position') {
|
||||
vec3.forEach(targetAttr.value, size, offset, count, vec3.transformMat4, matrix);
|
||||
} else if (name === 'normal' || name === 'tangent') {
|
||||
vec3.forEach(
|
||||
targetAttr.value,
|
||||
size,
|
||||
offset,
|
||||
count,
|
||||
vec3.transformMat4,
|
||||
inverseTransposeMatrix
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (useIndices) {
|
||||
const len = currentGeo.indices.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
geometry.indices[i + indicesOffset] = currentGeo.indices[i] + vertexOffset;
|
||||
}
|
||||
indicesOffset += len;
|
||||
}
|
||||
|
||||
vertexOffset += nVertex;
|
||||
}
|
||||
|
||||
return new Mesh({
|
||||
material: material,
|
||||
geometry: geometry
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Split mesh into sub meshes, each mesh will have maxJointNumber joints.
|
||||
* @param {clay.Mesh} mesh
|
||||
* @param {number} maxJointNumber
|
||||
* @param {boolean} inPlace
|
||||
* @return {clay.Node}
|
||||
*
|
||||
* @memberOf clay.util.mesh
|
||||
*/
|
||||
|
||||
// FIXME, Have issues on some models
|
||||
splitByJoints: function (mesh, maxJointNumber, inPlace) {
|
||||
const geometry = mesh.geometry;
|
||||
const skeleton = mesh.skeleton;
|
||||
const material = mesh.material;
|
||||
const joints = mesh.joints;
|
||||
if (!geometry || !skeleton || !joints.length) {
|
||||
return;
|
||||
}
|
||||
if (joints.length < maxJointNumber) {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
const indices = geometry.indices;
|
||||
|
||||
const faceLen = geometry.triangleCount;
|
||||
let rest = faceLen;
|
||||
const isFaceAdded = [];
|
||||
const jointValues = geometry.attributes.joint.value;
|
||||
for (let i = 0; i < faceLen; i++) {
|
||||
isFaceAdded[i] = false;
|
||||
}
|
||||
const addedJointIdxPerFace = [];
|
||||
|
||||
const buckets = [];
|
||||
|
||||
const getJointByIndex = function (idx) {
|
||||
return joints[idx];
|
||||
};
|
||||
while (rest > 0) {
|
||||
const bucketTriangles = [];
|
||||
const bucketJointReverseMap = [];
|
||||
const bucketJoints = [];
|
||||
let subJointNumber = 0;
|
||||
for (let i = 0; i < joints.length; i++) {
|
||||
bucketJointReverseMap[i] = -1;
|
||||
}
|
||||
for (let f = 0; f < faceLen; f++) {
|
||||
if (isFaceAdded[f]) {
|
||||
continue;
|
||||
}
|
||||
let canAddToBucket = true;
|
||||
let addedNumber = 0;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const idx = indices[f * 3 + i];
|
||||
|
||||
for (let j = 0; j < 4; j++) {
|
||||
const jointIdx = jointValues[idx * 4 + j];
|
||||
|
||||
if (jointIdx >= 0) {
|
||||
if (bucketJointReverseMap[jointIdx] === -1) {
|
||||
if (subJointNumber < maxJointNumber) {
|
||||
bucketJointReverseMap[jointIdx] = subJointNumber;
|
||||
bucketJoints[subJointNumber++] = jointIdx;
|
||||
addedJointIdxPerFace[addedNumber++] = jointIdx;
|
||||
} else {
|
||||
canAddToBucket = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!canAddToBucket) {
|
||||
// Reverse operation
|
||||
for (let i = 0; i < addedNumber; i++) {
|
||||
bucketJointReverseMap[addedJointIdxPerFace[i]] = -1;
|
||||
bucketJoints.pop();
|
||||
subJointNumber--;
|
||||
}
|
||||
} else {
|
||||
bucketTriangles.push(indices.subarray(f * 3, (f + 1) * 3));
|
||||
|
||||
isFaceAdded[f] = true;
|
||||
rest--;
|
||||
}
|
||||
}
|
||||
buckets.push({
|
||||
triangles: bucketTriangles,
|
||||
joints: bucketJoints.map(getJointByIndex),
|
||||
jointReverseMap: bucketJointReverseMap
|
||||
});
|
||||
}
|
||||
|
||||
const root = new Node({
|
||||
name: mesh.name
|
||||
});
|
||||
const attribNames = geometry.getEnabledAttributes();
|
||||
|
||||
attribNames.splice(attribNames.indexOf('joint'), 1);
|
||||
// Map from old vertex index to new vertex index
|
||||
const newIndices = [];
|
||||
for (let b = 0; b < buckets.length; b++) {
|
||||
const bucket = buckets[b];
|
||||
const jointReverseMap = bucket.jointReverseMap;
|
||||
|
||||
const subGeo = new Geometry();
|
||||
|
||||
const subMesh = new Mesh({
|
||||
name: [mesh.name, b].join('-'),
|
||||
// DON'T clone material.
|
||||
material: material,
|
||||
geometry: subGeo,
|
||||
skeleton: skeleton,
|
||||
joints: bucket.joints.slice()
|
||||
});
|
||||
let nVertex = 0;
|
||||
const nVertex2 = geometry.vertexCount;
|
||||
for (let i = 0; i < nVertex2; i++) {
|
||||
newIndices[i] = -1;
|
||||
}
|
||||
// Count sub geo number
|
||||
for (let f = 0; f < bucket.triangles.length; f++) {
|
||||
const face = bucket.triangles[f];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const idx = face[i];
|
||||
if (newIndices[idx] === -1) {
|
||||
newIndices[idx] = nVertex;
|
||||
nVertex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let a = 0; a < attribNames.length; a++) {
|
||||
const attribName = attribNames[a];
|
||||
const subAttrib = subGeo.attributes[attribName];
|
||||
subAttrib.init(nVertex);
|
||||
}
|
||||
subGeo.attributes.joint.value = new Float32Array(nVertex * 4);
|
||||
|
||||
if (nVertex > 0xffff) {
|
||||
subGeo.indices = new Uint32Array(bucket.triangles.length * 3);
|
||||
} else {
|
||||
subGeo.indices = new Uint16Array(bucket.triangles.length * 3);
|
||||
}
|
||||
|
||||
let indicesOffset = 0;
|
||||
nVertex = 0;
|
||||
for (let i = 0; i < nVertex2; i++) {
|
||||
newIndices[i] = -1;
|
||||
}
|
||||
|
||||
for (let f = 0; f < bucket.triangles.length; f++) {
|
||||
const triangle = bucket.triangles[f];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const idx = triangle[i];
|
||||
|
||||
if (newIndices[idx] === -1) {
|
||||
newIndices[idx] = nVertex;
|
||||
for (let a = 0; a < attribNames.length; a++) {
|
||||
const attribName = attribNames[a];
|
||||
const attrib = geometry.attributes[attribName];
|
||||
const subAttrib = subGeo.attributes[attribName];
|
||||
const size = attrib.size;
|
||||
|
||||
for (let j = 0; j < size; j++) {
|
||||
subAttrib.value[nVertex * size + j] = attrib.value[idx * size + j];
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < 4; j++) {
|
||||
const jointIdx = geometry.attributes.joint.value[idx * 4 + j];
|
||||
const offset = nVertex * 4 + j;
|
||||
if (jointIdx >= 0) {
|
||||
subGeo.attributes.joint.value[offset] = jointReverseMap[jointIdx];
|
||||
} else {
|
||||
subGeo.attributes.joint.value[offset] = -1;
|
||||
}
|
||||
}
|
||||
nVertex++;
|
||||
}
|
||||
subGeo.indices[indicesOffset++] = newIndices[idx];
|
||||
}
|
||||
}
|
||||
subGeo.updateBoundingBox();
|
||||
|
||||
root.add(subMesh);
|
||||
}
|
||||
const children = mesh.children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
root.add(children[i]);
|
||||
}
|
||||
root.position.copy(mesh.position);
|
||||
root.rotation.copy(mesh.rotation);
|
||||
root.scale.copy(mesh.scale);
|
||||
|
||||
if (inPlace) {
|
||||
if (mesh.getParent()) {
|
||||
const parent = mesh.getParent();
|
||||
parent.remove(mesh);
|
||||
parent.add(root);
|
||||
}
|
||||
}
|
||||
return root;
|
||||
/**
|
||||
* Merge multiple meshes to one.
|
||||
* Note that these meshes must have the same material
|
||||
*
|
||||
* @param {Array.<clay.Mesh>} meshes
|
||||
* @param {boolean} applyWorldTransform
|
||||
* @return {clay.Mesh}
|
||||
* @memberOf clay.util.mesh
|
||||
*/
|
||||
export function merge(meshes, applyWorldTransform) {
|
||||
if (!meshes.length) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
export default meshUtil;
|
||||
const templateMesh = meshes[0];
|
||||
const templateGeo = templateMesh.geometry;
|
||||
const material = templateMesh.material;
|
||||
|
||||
const geometry = new Geometry({
|
||||
dynamic: false
|
||||
});
|
||||
geometry.boundingBox = new BoundingBox();
|
||||
|
||||
const attributeNames = templateGeo.getEnabledAttributes();
|
||||
|
||||
for (let i = 0; i < attributeNames.length; i++) {
|
||||
const name = attributeNames[i];
|
||||
const attr = templateGeo.attributes[name];
|
||||
// Extend custom attributes
|
||||
if (!geometry.attributes[name]) {
|
||||
geometry.attributes[name] = attr.clone(false);
|
||||
}
|
||||
}
|
||||
|
||||
const inverseTransposeMatrix = mat4.create();
|
||||
// Initialize the array data and merge bounding box
|
||||
let nVertex = 0;
|
||||
let nFace = 0;
|
||||
for (let k = 0; k < meshes.length; k++) {
|
||||
const currentGeo = meshes[k].geometry;
|
||||
if (currentGeo.boundingBox) {
|
||||
currentGeo.boundingBox.applyTransform(
|
||||
applyWorldTransform ? meshes[k].worldTransform : meshes[k].localTransform
|
||||
);
|
||||
geometry.boundingBox.union(currentGeo.boundingBox);
|
||||
}
|
||||
nVertex += currentGeo.vertexCount;
|
||||
nFace += currentGeo.triangleCount;
|
||||
}
|
||||
for (let n = 0; n < attributeNames.length; n++) {
|
||||
const name = attributeNames[n];
|
||||
const attrib = geometry.attributes[name];
|
||||
attrib.init(nVertex);
|
||||
}
|
||||
if (nVertex >= 0xffff) {
|
||||
geometry.indices = new Uint32Array(nFace * 3);
|
||||
} else {
|
||||
geometry.indices = new Uint16Array(nFace * 3);
|
||||
}
|
||||
|
||||
let vertexOffset = 0;
|
||||
let indicesOffset = 0;
|
||||
const useIndices = templateGeo.isUseIndices();
|
||||
|
||||
for (let mm = 0; mm < meshes.length; mm++) {
|
||||
const mesh = meshes[mm];
|
||||
const currentGeo = mesh.geometry;
|
||||
|
||||
const nVertex = currentGeo.vertexCount;
|
||||
|
||||
const matrix = applyWorldTransform ? mesh.worldTransform.array : mesh.localTransform.array;
|
||||
mat4.invert(inverseTransposeMatrix, matrix);
|
||||
mat4.transpose(inverseTransposeMatrix, inverseTransposeMatrix);
|
||||
|
||||
for (let nn = 0; nn < attributeNames.length; nn++) {
|
||||
const name = attributeNames[nn];
|
||||
const currentAttr = currentGeo.attributes[name];
|
||||
const targetAttr = geometry.attributes[name];
|
||||
// Skip the unused attributes;
|
||||
if (!currentAttr.value.length) {
|
||||
continue;
|
||||
}
|
||||
const len = currentAttr.value.length;
|
||||
const size = currentAttr.size;
|
||||
const offset = vertexOffset * size;
|
||||
const count = len / size;
|
||||
for (let i = 0; i < len; i++) {
|
||||
targetAttr.value[offset + i] = currentAttr.value[i];
|
||||
}
|
||||
// Transform position, normal and tangent
|
||||
if (name === 'position') {
|
||||
vec3.forEach(targetAttr.value, size, offset, count, vec3.transformMat4, matrix);
|
||||
} else if (name === 'normal' || name === 'tangent') {
|
||||
vec3.forEach(
|
||||
targetAttr.value,
|
||||
size,
|
||||
offset,
|
||||
count,
|
||||
vec3.transformMat4,
|
||||
inverseTransposeMatrix
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (useIndices) {
|
||||
const len = currentGeo.indices.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
geometry.indices[i + indicesOffset] = currentGeo.indices[i] + vertexOffset;
|
||||
}
|
||||
indicesOffset += len;
|
||||
}
|
||||
|
||||
vertexOffset += nVertex;
|
||||
}
|
||||
|
||||
return new Mesh({
|
||||
material: material,
|
||||
geometry: geometry
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Split mesh into sub meshes, each mesh will have maxJointNumber joints.
|
||||
* @param {clay.Mesh} mesh
|
||||
* @param {number} maxJointNumber
|
||||
* @param {boolean} inPlace
|
||||
* @return {clay.Node}
|
||||
*
|
||||
* @memberOf clay.util.mesh
|
||||
*/
|
||||
|
||||
// FIXME, Have issues on some models
|
||||
export function splitByJoints(mesh, maxJointNumber, inPlace) {
|
||||
const geometry = mesh.geometry;
|
||||
const skeleton = mesh.skeleton;
|
||||
const material = mesh.material;
|
||||
const joints = mesh.joints;
|
||||
if (!geometry || !skeleton || !joints.length) {
|
||||
return;
|
||||
}
|
||||
if (joints.length < maxJointNumber) {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
const indices = geometry.indices;
|
||||
|
||||
const faceLen = geometry.triangleCount;
|
||||
let rest = faceLen;
|
||||
const isFaceAdded = [];
|
||||
const jointValues = geometry.attributes.joint.value;
|
||||
for (let i = 0; i < faceLen; i++) {
|
||||
isFaceAdded[i] = false;
|
||||
}
|
||||
const addedJointIdxPerFace = [];
|
||||
|
||||
const buckets = [];
|
||||
|
||||
const getJointByIndex = function (idx) {
|
||||
return joints[idx];
|
||||
};
|
||||
while (rest > 0) {
|
||||
const bucketTriangles = [];
|
||||
const bucketJointReverseMap = [];
|
||||
const bucketJoints = [];
|
||||
let subJointNumber = 0;
|
||||
for (let i = 0; i < joints.length; i++) {
|
||||
bucketJointReverseMap[i] = -1;
|
||||
}
|
||||
for (let f = 0; f < faceLen; f++) {
|
||||
if (isFaceAdded[f]) {
|
||||
continue;
|
||||
}
|
||||
let canAddToBucket = true;
|
||||
let addedNumber = 0;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const idx = indices[f * 3 + i];
|
||||
|
||||
for (let j = 0; j < 4; j++) {
|
||||
const jointIdx = jointValues[idx * 4 + j];
|
||||
|
||||
if (jointIdx >= 0) {
|
||||
if (bucketJointReverseMap[jointIdx] === -1) {
|
||||
if (subJointNumber < maxJointNumber) {
|
||||
bucketJointReverseMap[jointIdx] = subJointNumber;
|
||||
bucketJoints[subJointNumber++] = jointIdx;
|
||||
addedJointIdxPerFace[addedNumber++] = jointIdx;
|
||||
} else {
|
||||
canAddToBucket = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!canAddToBucket) {
|
||||
// Reverse operation
|
||||
for (let i = 0; i < addedNumber; i++) {
|
||||
bucketJointReverseMap[addedJointIdxPerFace[i]] = -1;
|
||||
bucketJoints.pop();
|
||||
subJointNumber--;
|
||||
}
|
||||
} else {
|
||||
bucketTriangles.push(indices.subarray(f * 3, (f + 1) * 3));
|
||||
|
||||
isFaceAdded[f] = true;
|
||||
rest--;
|
||||
}
|
||||
}
|
||||
buckets.push({
|
||||
triangles: bucketTriangles,
|
||||
joints: bucketJoints.map(getJointByIndex),
|
||||
jointReverseMap: bucketJointReverseMap
|
||||
});
|
||||
}
|
||||
|
||||
const root = new ClayNode({
|
||||
name: mesh.name
|
||||
});
|
||||
const attribNames = geometry.getEnabledAttributes();
|
||||
|
||||
attribNames.splice(attribNames.indexOf('joint'), 1);
|
||||
// Map from old vertex index to new vertex index
|
||||
const newIndices = [];
|
||||
for (let b = 0; b < buckets.length; b++) {
|
||||
const bucket = buckets[b];
|
||||
const jointReverseMap = bucket.jointReverseMap;
|
||||
|
||||
const subGeo = new Geometry();
|
||||
|
||||
const subMesh = new Mesh({
|
||||
name: [mesh.name, b].join('-'),
|
||||
// DON'T clone material.
|
||||
material: material,
|
||||
geometry: subGeo,
|
||||
skeleton: skeleton,
|
||||
joints: bucket.joints.slice()
|
||||
});
|
||||
let nVertex = 0;
|
||||
const nVertex2 = geometry.vertexCount;
|
||||
for (let i = 0; i < nVertex2; i++) {
|
||||
newIndices[i] = -1;
|
||||
}
|
||||
// Count sub geo number
|
||||
for (let f = 0; f < bucket.triangles.length; f++) {
|
||||
const face = bucket.triangles[f];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const idx = face[i];
|
||||
if (newIndices[idx] === -1) {
|
||||
newIndices[idx] = nVertex;
|
||||
nVertex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let a = 0; a < attribNames.length; a++) {
|
||||
const attribName = attribNames[a];
|
||||
const subAttrib = subGeo.attributes[attribName];
|
||||
subAttrib.init(nVertex);
|
||||
}
|
||||
subGeo.attributes.joint.value = new Float32Array(nVertex * 4);
|
||||
|
||||
if (nVertex > 0xffff) {
|
||||
subGeo.indices = new Uint32Array(bucket.triangles.length * 3);
|
||||
} else {
|
||||
subGeo.indices = new Uint16Array(bucket.triangles.length * 3);
|
||||
}
|
||||
|
||||
let indicesOffset = 0;
|
||||
nVertex = 0;
|
||||
for (let i = 0; i < nVertex2; i++) {
|
||||
newIndices[i] = -1;
|
||||
}
|
||||
|
||||
for (let f = 0; f < bucket.triangles.length; f++) {
|
||||
const triangle = bucket.triangles[f];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const idx = triangle[i];
|
||||
|
||||
if (newIndices[idx] === -1) {
|
||||
newIndices[idx] = nVertex;
|
||||
for (let a = 0; a < attribNames.length; a++) {
|
||||
const attribName = attribNames[a];
|
||||
const attrib = geometry.attributes[attribName];
|
||||
const subAttrib = subGeo.attributes[attribName];
|
||||
const size = attrib.size;
|
||||
|
||||
for (let j = 0; j < size; j++) {
|
||||
subAttrib.value[nVertex * size + j] = attrib.value[idx * size + j];
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < 4; j++) {
|
||||
const jointIdx = geometry.attributes.joint.value[idx * 4 + j];
|
||||
const offset = nVertex * 4 + j;
|
||||
if (jointIdx >= 0) {
|
||||
subGeo.attributes.joint.value[offset] = jointReverseMap[jointIdx];
|
||||
} else {
|
||||
subGeo.attributes.joint.value[offset] = -1;
|
||||
}
|
||||
}
|
||||
nVertex++;
|
||||
}
|
||||
subGeo.indices[indicesOffset++] = newIndices[idx];
|
||||
}
|
||||
}
|
||||
subGeo.updateBoundingBox();
|
||||
|
||||
root.add(subMesh);
|
||||
}
|
||||
const children = mesh.children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
root.add(children[i]);
|
||||
}
|
||||
root.position.copy(mesh.position);
|
||||
root.rotation.copy(mesh.rotation);
|
||||
root.scale.copy(mesh.scale);
|
||||
|
||||
if (inPlace) {
|
||||
if (mesh.getParent()) {
|
||||
const parent = mesh.getParent();
|
||||
parent.remove(mesh);
|
||||
parent.add(root);
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
105
src/util/sh.ts
105
src/util/sh.ts
@ -1,31 +1,26 @@
|
||||
// @ts-nocheck
|
||||
// Spherical Harmonic Helpers
|
||||
import Texture from '../Texture';
|
||||
import FrameBuffer from '../FrameBuffer';
|
||||
import Texture2D from '../Texture2D';
|
||||
import Pass from '../compositor/Pass';
|
||||
import vendor from '../core/vendor';
|
||||
import Skybox from '../plugin/Skybox';
|
||||
import Skydome from '../plugin/Skydome';
|
||||
import EnvironmentMapPass from '../prePass/EnvironmentMap';
|
||||
import Scene from '../Scene';
|
||||
import vec3 from '../glmatrix/vec3';
|
||||
import * as vec3 from '../glmatrix/vec3';
|
||||
const sh = {};
|
||||
|
||||
import projectEnvMapShaderCode from './shader/projectEnvMap.glsl.js';
|
||||
|
||||
const targets = ['px', 'nx', 'py', 'ny', 'pz', 'nz'];
|
||||
import CompositorFullscreenQuadPass from '../compositor/Pass';
|
||||
import type Renderer from '../Renderer';
|
||||
import TextureCube, { CubeTarget, cubeTargets } from '../TextureCube';
|
||||
|
||||
// Project on gpu, but needs browser to support readPixels as Float32Array.
|
||||
function projectEnvironmentMapGPU(renderer, envMap) {
|
||||
function projectEnvironmentMapGPU(renderer: Renderer, envMap: TextureCube) {
|
||||
const shTexture = new Texture2D({
|
||||
width: 9,
|
||||
height: 1,
|
||||
type: Texture.FLOAT
|
||||
});
|
||||
const pass = new Pass({
|
||||
fragment: projectEnvMapShaderCode
|
||||
});
|
||||
const pass = new CompositorFullscreenQuadPass(projectEnvMapShaderCode);
|
||||
pass.material.define('fragment', 'TEXTURE_SIZE', envMap.width);
|
||||
pass.setUniform('environmentMap', envMap);
|
||||
|
||||
@ -51,29 +46,30 @@ function projectEnvironmentMapGPU(renderer, envMap) {
|
||||
return coeff;
|
||||
}
|
||||
|
||||
function harmonics(normal, index) {
|
||||
function harmonics(normal: vec3.Vec3Array, index: number) {
|
||||
const x = normal[0];
|
||||
const y = normal[1];
|
||||
const z = normal[2];
|
||||
|
||||
if (index === 0) {
|
||||
return 1.0;
|
||||
} else if (index === 1) {
|
||||
return x;
|
||||
} else if (index === 2) {
|
||||
return y;
|
||||
} else if (index === 3) {
|
||||
return z;
|
||||
} else if (index === 4) {
|
||||
return x * z;
|
||||
} else if (index === 5) {
|
||||
return y * z;
|
||||
} else if (index === 6) {
|
||||
return x * y;
|
||||
} else if (index === 7) {
|
||||
return 3.0 * z * z - 1.0;
|
||||
} else {
|
||||
return x * x - y * y;
|
||||
switch (index) {
|
||||
case 0:
|
||||
return 1.0;
|
||||
case 1:
|
||||
return x;
|
||||
case 2:
|
||||
return y;
|
||||
case 3:
|
||||
return z;
|
||||
case 4:
|
||||
return x * z;
|
||||
case 5:
|
||||
return y * z;
|
||||
case 6:
|
||||
return x * y;
|
||||
case 7:
|
||||
return 3.0 * z * z - 1.0;
|
||||
default:
|
||||
return x * x - y * y;
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,20 +83,25 @@ const normalTransform = {
|
||||
};
|
||||
|
||||
// Project on cpu.
|
||||
function projectEnvironmentMapCPU(renderer, cubePixels, width, height) {
|
||||
function projectEnvironmentMapCPU(
|
||||
renderer: Renderer,
|
||||
cubePixels: Record<CubeTarget, Uint8Array>,
|
||||
width: number,
|
||||
height: number
|
||||
) {
|
||||
const coeff = new Float32Array(9 * 3);
|
||||
const normal = vec3.create();
|
||||
const texel = vec3.create();
|
||||
const fetchNormal = vec3.create();
|
||||
for (let m = 0; m < 9; m++) {
|
||||
const result = vec3.create();
|
||||
for (let k = 0; k < targets.length; k++) {
|
||||
const pixels = cubePixels[targets[k]];
|
||||
for (let k = 0; k < cubeTargets.length; k++) {
|
||||
const pixels = cubePixels[cubeTargets[k]];
|
||||
|
||||
const sideResult = vec3.create();
|
||||
let divider = 0;
|
||||
let i = 0;
|
||||
const transform = normalTransform[targets[k]];
|
||||
const transform = normalTransform[cubeTargets[k]];
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
normal[0] = (x / (width - 1.0)) * 2.0 - 1.0;
|
||||
@ -137,14 +138,14 @@ function projectEnvironmentMapCPU(renderer, cubePixels, width, height) {
|
||||
return coeff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {clay.Renderer} renderer
|
||||
* @param {clay.Texture} envMap
|
||||
* @param {Object} [textureOpts]
|
||||
* @param {Object} [textureOpts.lod]
|
||||
* @param {boolean} [textureOpts.decodeRGBM]
|
||||
*/
|
||||
sh.projectEnvironmentMap = function (renderer, envMap, opts) {
|
||||
export function projectEnvironmentMap(
|
||||
renderer: Renderer,
|
||||
envMap: Texture2D | TextureCube,
|
||||
opts?: {
|
||||
decodeRGBM?: boolean;
|
||||
lod?: number;
|
||||
}
|
||||
) {
|
||||
// TODO sRGB
|
||||
|
||||
opts = opts || {};
|
||||
@ -154,7 +155,7 @@ sh.projectEnvironmentMap = function (renderer, envMap, opts) {
|
||||
const dummyScene = new Scene();
|
||||
let size = 64;
|
||||
if (envMap.textureType === 'texture2D') {
|
||||
skybox = new Skydome({
|
||||
skybox = new Skybox({
|
||||
scene: dummyScene,
|
||||
environmentMap: envMap
|
||||
});
|
||||
@ -178,13 +179,11 @@ sh.projectEnvironmentMap = function (renderer, envMap, opts) {
|
||||
skybox.material.define('fragment', 'RGBM_DECODE');
|
||||
}
|
||||
skybox.material.set('lod', opts.lod);
|
||||
const envMapPass = new EnvironmentMapPass({
|
||||
texture: rgbmTexture
|
||||
});
|
||||
const cubePixels = {};
|
||||
for (let i = 0; i < targets.length; i++) {
|
||||
cubePixels[targets[i]] = new Uint8Array(width * height * 4);
|
||||
const camera = envMapPass.getCamera(targets[i]);
|
||||
const envMapPass = new EnvironmentMapPass();
|
||||
const cubePixels = {} as Record<CubeTarget, Uint8Array>;
|
||||
for (let i = 0; i < cubeTargets.length; i++) {
|
||||
cubePixels[cubeTargets[i]] = new Uint8Array(width * height * 4);
|
||||
const camera = envMapPass.getCamera(cubeTargets[i]);
|
||||
camera.fov = 90;
|
||||
framebuffer.attach(rgbmTexture);
|
||||
framebuffer.bind(renderer);
|
||||
@ -196,7 +195,7 @@ sh.projectEnvironmentMap = function (renderer, envMap, opts) {
|
||||
height,
|
||||
Texture.RGBA,
|
||||
Texture.UNSIGNED_BYTE,
|
||||
cubePixels[targets[i]]
|
||||
cubePixels[cubeTargets[i]]
|
||||
);
|
||||
framebuffer.unbind(renderer);
|
||||
}
|
||||
@ -206,6 +205,4 @@ sh.projectEnvironmentMap = function (renderer, envMap, opts) {
|
||||
rgbmTexture.dispose(renderer);
|
||||
|
||||
return projectEnvironmentMapCPU(renderer, cubePixels, width, height);
|
||||
};
|
||||
|
||||
export default sh;
|
||||
}
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
// @ts-nocheck
|
||||
import * as util from '../core/util';
|
||||
import Geometry from '../Geometry';
|
||||
import GeometryBase, {
|
||||
AttributeSize,
|
||||
AttributeType,
|
||||
AttributeValue,
|
||||
GeometryAttribute
|
||||
} from '../GeometryBase';
|
||||
import BoundingBox from '../math/BoundingBox';
|
||||
import Vector3 from '../math/Vector3';
|
||||
|
||||
@ -10,113 +15,120 @@ const META = {
|
||||
generator: 'util.transferable.toObject'
|
||||
};
|
||||
|
||||
interface DataObject {
|
||||
meta: typeof META;
|
||||
dynamic: boolean;
|
||||
boundingBox: {
|
||||
min: number[];
|
||||
max: number[];
|
||||
};
|
||||
indices?: Uint16Array | Uint32Array;
|
||||
attributes: Record<
|
||||
string,
|
||||
{
|
||||
name: string;
|
||||
type: AttributeType;
|
||||
size: AttributeSize;
|
||||
semantic?: string;
|
||||
value: AttributeValue;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alias clay.util.transferable
|
||||
* Convert geometry to a object containing transferable data
|
||||
* @param {Geometry} geometry geometry
|
||||
* @param {Boolean} shallow whether shallow copy
|
||||
* @returns {Object} { data : data, buffers : buffers }, buffers is the transferable list
|
||||
*/
|
||||
const transferableUtil = {
|
||||
/**
|
||||
* Convert geometry to a object containing transferable data
|
||||
* @param {Geometry} geometry geometry
|
||||
* @param {Boolean} shallow whether shallow copy
|
||||
* @returns {Object} { data : data, buffers : buffers }, buffers is the transferable list
|
||||
*/
|
||||
toObject: function (geometry, shallow) {
|
||||
if (!geometry) {
|
||||
return null;
|
||||
}
|
||||
const data = {
|
||||
metadata: Object.assign({}, META)
|
||||
export function toObject(geometry: GeometryBase, shallow?: boolean) {
|
||||
const data = {
|
||||
meta: Object.assign({}, META)
|
||||
} as DataObject;
|
||||
//transferable buffers
|
||||
const buffers: ArrayBuffer[] = [];
|
||||
|
||||
//dynamic
|
||||
data.dynamic = geometry.dynamic;
|
||||
|
||||
//bounding box
|
||||
if (geometry.boundingBox) {
|
||||
data.boundingBox = {
|
||||
min: geometry.boundingBox.min.toArray(),
|
||||
max: geometry.boundingBox.max.toArray()
|
||||
};
|
||||
//transferable buffers
|
||||
const buffers = [];
|
||||
|
||||
//dynamic
|
||||
data.dynamic = geometry.dynamic;
|
||||
|
||||
//bounding box
|
||||
if (geometry.boundingBox) {
|
||||
data.boundingBox = {
|
||||
min: geometry.boundingBox.min.toArray(),
|
||||
max: geometry.boundingBox.max.toArray()
|
||||
};
|
||||
}
|
||||
|
||||
//indices
|
||||
if (geometry.indices && geometry.indices.length > 0) {
|
||||
data.indices = copyIfNecessary(geometry.indices, shallow);
|
||||
buffers.push(data.indices.buffer);
|
||||
}
|
||||
|
||||
//attributes
|
||||
data.attributes = {};
|
||||
for (const p in geometry.attributes) {
|
||||
if (util.hasOwn(geometry.attributes, p)) {
|
||||
let attr = geometry.attributes[p];
|
||||
//ignore empty attributes
|
||||
if (attr && attr.value && attr.value.length > 0) {
|
||||
attr = data.attributes[p] = copyAttribute(attr, shallow);
|
||||
buffers.push(attr.value.buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: data,
|
||||
buffers: buffers
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Reproduce a geometry from object generated by toObject
|
||||
* @param {Object} object object generated by toObject
|
||||
* @returns {Geometry} geometry
|
||||
*/
|
||||
toGeometry: function (object) {
|
||||
if (!object) {
|
||||
return null;
|
||||
}
|
||||
if (object.data && object.buffers) {
|
||||
return transferableUtil.toGeometry(object.data);
|
||||
}
|
||||
if (!object.metadata || object.metadata.generator !== META.generator) {
|
||||
throw new Error(
|
||||
'[util.transferable.toGeometry] the object is not generated by util.transferable.'
|
||||
);
|
||||
}
|
||||
|
||||
//basic options
|
||||
const options = {
|
||||
dynamic: object.dynamic,
|
||||
indices: object.indices
|
||||
};
|
||||
|
||||
if (object.boundingBox) {
|
||||
const min = new Vector3().setArray(object.boundingBox.min);
|
||||
const max = new Vector3().setArray(object.boundingBox.max);
|
||||
options.boundingBox = new BoundingBox(min, max);
|
||||
}
|
||||
|
||||
const geometry = new Geometry(options);
|
||||
|
||||
//attributes
|
||||
for (const p in object.attributes) {
|
||||
if (util.hasOwn(object.attributes, p)) {
|
||||
const attr = object.attributes[p];
|
||||
geometry.attributes[p] = new Geometry.Attribute(
|
||||
attr.name,
|
||||
attr.type,
|
||||
attr.size,
|
||||
attr.semantic
|
||||
);
|
||||
geometry.attributes[p].value = attr.value;
|
||||
}
|
||||
}
|
||||
|
||||
return geometry;
|
||||
}
|
||||
};
|
||||
|
||||
function copyAttribute(attr, shallow) {
|
||||
//indices
|
||||
if (geometry.indices && geometry.indices.length > 0) {
|
||||
data.indices = copyIfNecessary(geometry.indices, shallow);
|
||||
buffers.push(data.indices!.buffer);
|
||||
}
|
||||
|
||||
//attributes
|
||||
const geoAttributes = geometry.attributes;
|
||||
const dataAttributes = (data.attributes = {} as DataObject['attributes']);
|
||||
for (const p in geoAttributes) {
|
||||
if (util.hasOwn(geoAttributes, p)) {
|
||||
const attr = geoAttributes[p];
|
||||
//ignore empty attributes
|
||||
if (attr && attr.value && attr.value.length > 0) {
|
||||
dataAttributes[p] = copyAttribute(attr, shallow);
|
||||
buffers.push((dataAttributes[p].value as Float32Array).buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
buffers
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reproduce a geometry from object generated by toObject
|
||||
* @param {Object} object object generated by toObject
|
||||
* @returns {Geometry} geometry
|
||||
*/
|
||||
export function toGeometry(object: DataObject) {
|
||||
if (!object.meta || object.meta.generator !== META.generator) {
|
||||
throw new Error(
|
||||
'[util.transferable.toGeometry] the object is not generated by util.transferable.'
|
||||
);
|
||||
}
|
||||
|
||||
//basic options
|
||||
const options = {
|
||||
dynamic: object.dynamic,
|
||||
indices: object.indices
|
||||
};
|
||||
|
||||
const geometry = new Geometry(options);
|
||||
const bbox = object.boundingBox;
|
||||
|
||||
if (bbox) {
|
||||
const min = new Vector3().setArray(bbox.min);
|
||||
const max = new Vector3().setArray(bbox.max);
|
||||
geometry.boundingBox = new BoundingBox(min, max);
|
||||
}
|
||||
//attributes
|
||||
for (const p in object.attributes) {
|
||||
if (util.hasOwn(object.attributes, p)) {
|
||||
const attrObj = object.attributes[p];
|
||||
const attr = geometry.createAttribute(
|
||||
attrObj.name,
|
||||
attrObj.type,
|
||||
attrObj.size,
|
||||
attrObj.semantic
|
||||
);
|
||||
attr.value = attrObj.value;
|
||||
}
|
||||
}
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
function copyAttribute(attr: GeometryAttribute, shallow?: boolean) {
|
||||
return {
|
||||
name: attr.name,
|
||||
type: attr.type,
|
||||
@ -126,12 +138,6 @@ function copyAttribute(attr, shallow) {
|
||||
};
|
||||
}
|
||||
|
||||
function copyIfNecessary(arr, shallow) {
|
||||
if (!shallow) {
|
||||
return new arr.constructor(arr);
|
||||
} else {
|
||||
return arr;
|
||||
}
|
||||
function copyIfNecessary(arr: any, shallow?: boolean) {
|
||||
return !shallow ? new arr.constructor(arr) : arr;
|
||||
}
|
||||
|
||||
export default transferableUtil;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user