@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
83 lines (72 loc) • 2.62 kB
text/typescript
import {BaseSopOperation} from './_Base';
import {CoreGroup} from '../../../core/geometry/Group';
import {InputCloneMode} from '../../poly/InputCloneMode';
import {SopType} from '../../poly/registers/nodes/types/Sop';
import {BufferAttribute, Mesh} from 'three';
import {Potpack, PotPackBox, PotPackBoxResult} from '../../../core/libs/Potpack';
import {DefaultOperationParams} from '../../../core/operations/_Base';
interface UvLayoutSopParams extends DefaultOperationParams {
res: number;
padding: number;
uv: string;
uv2: string;
}
export class UvLayoutSopOperation extends BaseSopOperation {
static override readonly DEFAULT_PARAMS: UvLayoutSopParams = {
res: 1024,
padding: 3,
uv: 'uv',
uv2: 'uvLightMap',
};
static override readonly INPUT_CLONED_STATE = InputCloneMode.FROM_NODE;
static override type(): Readonly<SopType.UV_LAYOUT> {
return SopType.UV_LAYOUT;
}
override cook(inputCoreGroups: CoreGroup[], params: UvLayoutSopParams) {
const coreGroup = inputCoreGroups[0];
const objects = coreGroup.threejsObjectsWithGeo();
const meshes: Mesh[] = [];
for (let object of objects) {
const mesh = object as Mesh;
if (mesh.isMesh) {
meshes.push(mesh);
}
}
this._layoutUVs(meshes, params);
return coreGroup;
}
private _layoutUVs(meshes: Mesh[], params: UvLayoutSopParams) {
const uv_boxes: PotPackBox[] = [];
const objIndexByBox: WeakMap<PotPackBox, number> = new WeakMap();
const padding = params.padding / params.res;
let index = 0;
for (let mesh of meshes) {
if (!mesh.geometry.hasAttribute(params.uv)) {
this.states?.error.set(`attribute ${params.uv} not found`);
}
// Prepare UV boxes for potpack
// TODO: Size these by object surface area
const box = {w: 1 + padding * 2, h: 1 + padding * 2};
uv_boxes.push(box);
objIndexByBox.set(box, index);
index++;
}
// Pack the objects' lightmap UVs into the same global space
const dimensions = Potpack(uv_boxes);
for (let uv_box of uv_boxes) {
const uvBoxResult = uv_box as PotPackBoxResult;
const index = objIndexByBox.get(uv_box);
if (index != null) {
const mesh = meshes[index] as Mesh;
const uv2 = (mesh.geometry.getAttribute(params.uv) as BufferAttribute).clone();
const array = uv2.array;
for (let i = 0; i < uv2.array.length; i += uv2.itemSize) {
array[i] = (uv2.array[i] + uvBoxResult.x + padding) / dimensions.w;
array[i + 1] = (uv2.array[i + 1] + uvBoxResult.y + padding) / dimensions.h;
}
mesh.geometry.setAttribute(params.uv2, uv2);
mesh.geometry.getAttribute(params.uv2).needsUpdate = true;
}
}
}
}