UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

345 lines (344 loc) 13.6 kB
"use strict"; 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;