@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
345 lines (344 loc) • 13.6 kB
JavaScript
;
import { BaseSopOperation } from "./_Base";
import { InputCloneMode } from "../../poly/InputCloneMode";
import { SopType } from "../../poly/registers/nodes/types/Sop";
import { BufferAttribute, Float32BufferAttribute, Vector3, Line3 } from "three";
import { XAtlasLoaderHandler } from "../../../core/loader/geometry/XAtlas";
import { TypeAssert } from "../../poly/Assert";
import { Potpack } from "../../../core/libs/Potpack";
import { LIBRARY_INSTALL_HINT } from "../../../core/loader/common";
import { DEFAULT_UV_LIGHT_MAP_ATTRIB_NAME } from "../../nodes/cop/utils/lightMap/LightMapMaterial";
import { UVUnwrapper } from "xatlas-three";
export var UvUnwrapMethod = /* @__PURE__ */ ((UvUnwrapMethod2) => {
UvUnwrapMethod2["POTPACK"] = "potpack";
UvUnwrapMethod2["XATLAS"] = "xatlas";
return UvUnwrapMethod2;
})(UvUnwrapMethod || {});
export const UV_UNWRAP_METHODS = [
"xatlas" /* XATLAS */,
// UvUnwrapMethod.XATLAS_2,
"potpack" /* POTPACK */
];
const v1 = new Vector3();
const v2 = new Vector3();
const v3 = new Vector3();
const vMid = new Vector3();
const vEnd = new Vector3();
const line = new Line3();
export class UvUnwrapSopOperation extends BaseSopOperation {
static type() {
return SopType.UV_UNWRAP;
}
async cook(inputCoreGroups, params) {
const method = UV_UNWRAP_METHODS[params.method];
switch (method) {
case "xatlas" /* XATLAS */: {
return await this._unwrapMeshUVsWithXAtlas2(inputCoreGroups, params);
}
case "potpack" /* POTPACK */: {
return this._unwrapMeshUVsWithPotpack(inputCoreGroups, params);
}
}
TypeAssert.unreachable(method);
}
async _unwrapMeshUVsWithXAtlas2(inputCoreGroups, params) {
var _a, _b;
const coreGroup = inputCoreGroups[0];
const unwrapper = new UVUnwrapper({ BufferAttribute });
unwrapper.chartOptions = {
fixWinding: false,
maxBoundaryLength: 0,
maxChartArea: 0,
maxCost: 2,
maxIterations: 1,
normalDeviationWeight: 2,
normalSeamWeight: 4,
roundnessWeight: 0.009999999776482582,
straightnessWeight: 6,
textureSeamWeight: 0.5,
useInputMeshUvs: false
};
unwrapper.packOptions = {
bilinear: true,
blockAlign: true,
bruteForce: false,
createImage: false,
maxChartSize: 0,
padding: params.padding,
resolution: params.resolution,
rotateCharts: true,
rotateChartsToAxis: true,
texelsPerUnit: 0
};
if (!this._node) {
(_a = this.states) == null ? void 0 : _a.error.set("no node");
return coreGroup;
}
const xatlasData = await XAtlasLoaderHandler.loadWasm(this._node);
if (!xatlasData) {
(_b = this.states) == null ? void 0 : _b.error.set(`failed to load xatlas. Make sure this is installed. ${LIBRARY_INSTALL_HINT}`);
return coreGroup;
}
await unwrapper.loadLibrary(
(mode, progress) => {
},
xatlasData.wasm,
//'https://cdn.jsdelivr.net/npm/xatlasjs@0.1.0/dist/xatlas.wasm',
xatlasData.js
//'https://cdn.jsdelivr.net/npm/xatlasjs@0.1.0/dist/xatlas.js'
);
const objects = coreGroup.threejsObjectsWithGeo();
for (let object of objects) {
const mesh = object;
if (mesh.isMesh) {
await unwrapper.packAtlas([mesh.geometry], params.uv);
}
}
return coreGroup;
}
// private async _unwrapMeshUVsWithXAtlas(inputCoreGroups: CoreGroup[], params: UvUnwrapSopParams) {
// const coreGroup = inputCoreGroups[0];
// if (!this._node) {
// return coreGroup;
// }
// const xatlas = await XAtlasLoaderHandler.xatlas(this._node);
// if (!xatlas) {
// this.states?.error.set(`failed to load xatlas. Make sure this is installed. ${LIBRARY_INSTALL_HINT}`);
// return coreGroup;
// }
// const objects = coreGroup.threejsObjectsWithGeo();
// for (let object of objects) {
// const mesh = object as Mesh;
// if (mesh.isMesh) {
// this._unwrapMeshUVsWithAtlas(xatlas, mesh, params);
// }
// }
// return coreGroup;
// }
// private _unwrapMeshUVsWithAtlas(xatlas: XAtlasManager, mesh: Mesh, params: UvUnwrapSopParams) {
// const geometry = mesh.geometry;
// if (!geometry.index) {
// return;
// }
// const originalVertexCount = geometry.attributes.position.count;
// const originalIndexCount = geometry.index.count;
// try {
// xatlas.createAtlas();
// } catch (err) {
// this._node?.states.error.set('failed to create atlas');
// return;
// }
// const meshInfo = xatlas.createMesh(originalVertexCount, originalIndexCount, true, true);
// const index = geometry.getIndex();
// const positionAttrib = geometry.getAttribute(Attribute.POSITION);
// const normalAttrib = geometry.getAttribute(Attribute.NORMAL);
// const uvAttrib = geometry.getAttribute(Attribute.UV);
// if (!(index && positionAttrib && normalAttrib && uvAttrib)) {
// this.states?.error.set(`the geometry needs to have an index, position, normal and uv attributes`);
// return;
// }
// xatlas.HEAPU16.set(geometry.index.array, meshInfo.indexOffset / Uint16Array.BYTES_PER_ELEMENT);
// xatlas.HEAPF32.set(
// (geometry.attributes.position as BufferAttribute).array,
// meshInfo.positionOffset / Float32Array.BYTES_PER_ELEMENT
// );
// xatlas.HEAPF32.set(
// (geometry.attributes.normal as BufferAttribute).array,
// meshInfo.normalOffset / Float32Array.BYTES_PER_ELEMENT
// );
// xatlas.HEAPF32.set(
// (geometry.attributes.uv as BufferAttribute).array,
// meshInfo.uvOffset / Float32Array.BYTES_PER_ELEMENT
// );
// const statusCode = xatlas.addMesh();
// if (statusCode !== AddMeshStatus.Success) {
// throw new Error(`UVUnwrapper: Error adding mesh. Status code ${statusCode}`);
// }
// // const chartOptions: ChartOptions = {
// // fixWinding: true,
// // maxBoundaryLength: 0,
// // maxChartArea: 0,
// // maxCost: 2,
// // maxIterations: 1,
// // normalDeviationWeight: 2,
// // normalSeamWeight: 4,
// // roundnessWeight: 0.009999999776482582,
// // straightnessWeight: 6,
// // textureSeamWeight: 0.5,
// // useInputMeshUvs: false,
// // };
// // const packOptions: PackOptions = {
// // bilinear: true,
// // blockAlign: false,
// // bruteForce: false,
// // createImage: false,
// // maxChartSize: 0,
// // padding: 0,
// // resolution: 0,
// // rotateCharts: true,
// // rotateChartsToAxis: true,
// // texelsPerUnit: 0,
// // };
// // console.log({chartOptions, packOptions});
// try {
// xatlas.generateAtlas();
// } catch (err) {
// this._node?.states.error.set('failed to generate atlas');
// console.log(err);
// return;
// }
// const meshData = xatlas.getMeshData(meshInfo.meshId);
// const oldPositionArray = (geometry.attributes.position as BufferAttribute).array;
// const oldNormalArray = (geometry.attributes.normal as BufferAttribute).array;
// const oldUvArray = (geometry.attributes.uv as BufferAttribute).array;
// const newPositionArray = new Float32Array(meshData.newVertexCount * 3);
// const newNormalArray = new Float32Array(meshData.newVertexCount * 3);
// const newUvArray = new Float32Array(meshData.newVertexCount * 2);
// const newUv2Array = new Float32Array(xatlas.HEAPF32.buffer, meshData.uvOffset, meshData.newVertexCount * 2);
// const newIndexArray = new Uint32Array(xatlas.HEAPU32.buffer, meshData.indexOffset, meshData.newIndexCount);
// const originalIndexArray = new Uint32Array(
// xatlas.HEAPU32.buffer,
// meshData.originalIndexOffset,
// meshData.newVertexCount
// );
// for (let i = 0; i < meshData.newVertexCount; i++) {
// const originalIndex = originalIndexArray[i];
// // P
// newPositionArray[i * 3] = oldPositionArray[originalIndex * 3];
// newPositionArray[i * 3 + 1] = oldPositionArray[originalIndex * 3 + 1];
// newPositionArray[i * 3 + 2] = oldPositionArray[originalIndex * 3 + 2];
// // N
// newNormalArray[i * 3] = oldNormalArray[originalIndex * 3];
// newNormalArray[i * 3 + 1] = oldNormalArray[originalIndex * 3 + 1];
// newNormalArray[i * 3 + 2] = oldNormalArray[originalIndex * 3 + 2];
// // uv
// newUvArray[i * 2] = oldUvArray[originalIndex * 2];
// newUvArray[i * 2 + 1] = oldUvArray[originalIndex * 2 + 1];
// }
// // check inverted uvs (which face toward -z when set onto P)
// // const pointsCount = newPositionArray.length / 3;
// // const polyCount = newIndexArray.length / 3;
// // const maxI = polyCount * 3;
// // const uvLightmapFlipped: number[] = new Array(newPositionArray.length / 3).fill(-1);
// // for (let i = 0; i < maxI; i += 3) {
// // const i0 = newIndexArray[i];
// // const i1 = newIndexArray[i + 1];
// // const i2 = newIndexArray[i + 2];
// // _uvTriangle.a.set(newUv2Array[i0 * 2], newUv2Array[i0 * 2 + 1], 0);
// // _uvTriangle.b.set(newUv2Array[i1 * 2], newUv2Array[i1 * 2 + 1], 0);
// // _uvTriangle.c.set(newUv2Array[i2 * 2], newUv2Array[i2 * 2 + 1], 0);
// // _uvTriangle.getNormal(_uvTriangleN);
// // const flipped = _uvTriangleN.z < 0 ? 1 : 0;
// // // if (flipped) {
// // // // newIndexArray[i] = i2;
// // // // newIndexArray[i + 2] = i0;
// // // // newUv2Array[i0 * 2] = _uvTriangle.c.x;
// // // // newUv2Array[i0 * 2 + 1] = _uvTriangle.c.y;
// // // // newUv2Array[i2 * 2] = _uvTriangle.a.x;
// // // // newUv2Array[i2 * 2 + 1] = _uvTriangle.a.y;
// // // }
// // uvLightmapFlipped[i0] = flipped;
// // uvLightmapFlipped[i1] = flipped;
// // uvLightmapFlipped[i2] = flipped;
// // // if (_uvTriangleN.z < 0) {
// // // newNormalArray[i] *= -1;
// // // newNormalArray[i + 1] *= -1;
// // // newNormalArray[i + 2] *= -1;
// // // }
// // }
// // for (let i = 0; i < pointsCount; i++) {
// // const flipped = uvFlip[i];
// // console.log(i, flipped);
// // newNormalArray[i * 3] *= flipped;
// // newNormalArray[i * 3 + 1] *= flipped;
// // newNormalArray[i * 3 + 2] *= flipped;
// // }
// // create geo
// const newGeometry = new BufferGeometry();
// newGeometry.setAttribute('position', new Float32BufferAttribute(newPositionArray, 3));
// newGeometry.setAttribute('normal', new Float32BufferAttribute(newNormalArray, 3));
// if (params.uv != Attribute.UV) {
// newGeometry.setAttribute('uv', new Float32BufferAttribute(newUvArray, 2));
// }
// newGeometry.setAttribute(params.uv, new Float32BufferAttribute(newUv2Array, 2));
// // newGeometry.setAttribute(UV_LIGHT_MAP_FLIPPED_ATTRIB_NAME, new Float32BufferAttribute(uvLightmapFlipped, 1));
// newGeometry.setIndex(new Uint32BufferAttribute(newIndexArray, 1));
// mesh.geometry = newGeometry;
// xatlas.destroyAtlas();
// }
_unwrapMeshUVsWithPotpack(inputCoreGroups, params) {
const coreGroup = inputCoreGroups[0];
const objects = coreGroup.threejsObjectsWithGeo();
for (let object of objects) {
const mesh = object;
if (mesh.isMesh) {
this._unwrapUVsWithPotpack(mesh, params);
}
}
return coreGroup;
}
// TODO: at the moment each polygon will fix a single box
// when ideally this should find when 2 triangles form a quad or square
// and could then fit in the box
_unwrapUVsWithPotpack(mesh, params) {
var _a, _b, _c;
const geometry = mesh.geometry;
const indexArray = (_a = geometry.getIndex()) == null ? void 0 : _a.array;
if (!indexArray) {
return;
}
const positionArray = (_b = geometry.attributes.position) == null ? void 0 : _b.array;
if (!positionArray) {
return;
}
const uvArray = (_c = geometry.attributes["uv"]) == null ? void 0 : _c.array;
if (!uvArray) {
return;
}
const polyCount = indexArray.length / 3;
const boxes = new Array(polyCount);
for (let i = 0; i < polyCount; i++) {
v1.fromArray(positionArray, 3 * indexArray[3 * i + 0]);
v2.fromArray(positionArray, 3 * indexArray[3 * i + 1]);
v3.fromArray(positionArray, 3 * indexArray[3 * i + 2]);
let w = v1.distanceTo(v2);
vMid.copy(v1).add(v2).multiplyScalar(0.5);
line.start.copy(v3);
line.end.copy(v3).add(v2).sub(v1);
line.closestPointToPoint(vMid, false, vEnd);
let h = vMid.distanceTo(vEnd);
if (h < w) {
const tmp = h;
h = w;
w = tmp;
}
boxes[i] = { w, h };
}
const result = Potpack(boxes);
const newUvValues = new Array(uvArray.length);
for (let i = 0; i < polyCount; i++) {
const box = boxes[i];
const x = box.x / result.w;
const y = box.y / result.h;
const w = box.w / result.w;
const h = box.h / result.h;
const index0 = 2 * indexArray[i * 3 + 0];
const index1 = 2 * indexArray[i * 3 + 1];
const index2 = 2 * indexArray[i * 3 + 2];
newUvValues[index0] = x;
newUvValues[index0 + 1] = y;
newUvValues[index1] = x + w;
newUvValues[index1 + 1] = y;
newUvValues[index2] = x;
newUvValues[index2 + 1] = y + h;
}
geometry.setAttribute(params.uv, new Float32BufferAttribute(newUvValues, 2));
}
}
UvUnwrapSopOperation.DEFAULT_PARAMS = {
method: UV_UNWRAP_METHODS.indexOf("xatlas" /* XATLAS */),
uv: DEFAULT_UV_LIGHT_MAP_ATTRIB_NAME,
resolution: 2048,
padding: 4
};
UvUnwrapSopOperation.INPUT_CLONED_STATE = InputCloneMode.FROM_NODE;