UNPKG

@polygonjs/polygonjs

Version:

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

227 lines (226 loc) 7.3 kB
"use strict"; import { Matrix4, Quaternion } from "three"; import { ThreeMeshBVHHelper } from "./../../../core/geometry/bvh/ThreeMeshBVHHelper"; import { Vector3, Ray, DoubleSide, Box3 } from "three"; import { NodeContext } from "./../../poly/NodeContext"; import { TypedCopNode } from "./_Base"; import { NodeParamsConfig, ParamConfig } from "../utils/params/ParamsConfig"; import { addSDFMetadataToContainer, createSDFTexture } from "../../../core/loader/geometry/SDF"; import { CopType } from "../../poly/registers/nodes/types/Cop"; const _bbox = new Box3(); const _rayDir = new Vector3(); const _ray = new Ray(); const _bboxSize = new Vector3(); const _resolution = new Vector3(); const _voxelSizes = new Vector3(); const _padding = new Vector3(); const objectWorldMat = new Matrix4(); const objectWorldMatInverse = new Matrix4(); const t = new Vector3(); const q = new Quaternion(); const s = new Vector3(); class SDFFromObjectCopParamsConfig extends NodeParamsConfig { constructor() { super(...arguments); /** @param which SOP node to import from */ this.geometry = ParamConfig.NODE_PATH("", { nodeSelection: { context: NodeContext.SOP } }); /** @param voxelSize */ this.voxelSize = ParamConfig.FLOAT(0.1, { range: [1e-5, 1], rangeLocked: [true, false] }); /** @param padding */ this.padding = ParamConfig.INTEGER(2, { range: [0, 5], rangeLocked: [true, false] }); /** @param resolution */ this.resolution = ParamConfig.VECTOR3([-1, -1, -1], { cook: false, editable: false, separatorBefore: true }); /** @param boundMin */ this.boundMin = ParamConfig.VECTOR3([-1, -1, -1], { cook: false, editable: false }); /** @param boundMax */ this.boundMax = ParamConfig.VECTOR3([1, 1, 1], { cook: false, editable: false }); } } const ParamsConfig = new SDFFromObjectCopParamsConfig(); export class SDFFromObjectCopNode extends TypedCopNode { constructor() { super(...arguments); this.paramsConfig = ParamsConfig; this._resolutionUsed = new Vector3(-1, -1, -1); } static type() { return CopType.SDF_FROM_OBJECT; } async cook(inputContents) { const geometryNode = this.pv.geometry.nodeWithContext(NodeContext.SOP, this.states.error); if (!geometryNode) { this.states.error.set(`node not found at path '${this.pv.geometry.path()}'`); return; } const container = await geometryNode.compute(); const coreGroup = container.coreContent(); const objects = coreGroup == null ? void 0 : coreGroup.threejsObjects(); if (!(coreGroup && objects && objects.length)) { this.states.error.set(`no objects found`); return; } let objectWithGeo; for (const object of objects) { if (object.geometry) { objectWithGeo = objectWithGeo || object; } } if (!objectWithGeo) { for (const object of objects) { object.traverse((childObject) => { if (childObject.geometry) { objectWithGeo = objectWithGeo || childObject; } }); } } if (!objectWithGeo) { this.states.error.set(`no object found with a geometry`); return; } const geometry = objectWithGeo.geometry; if (!geometry) { this.states.error.set(`no geometry found`); return; } let boundsTree = geometry.boundsTree; if (!boundsTree) { ThreeMeshBVHHelper.assignDefaultBVHIfNone(objectWithGeo); boundsTree = geometry.boundsTree; } coreGroup.boundingBox(_bbox); const _updateResolution = () => { _bbox.getSize(_bboxSize); _resolution.copy(_bboxSize).divideScalar(this.pv.voxelSize); _resolution.x = Math.ceil(_resolution.x * 0.5) * 2; _resolution.y = Math.ceil(_resolution.y * 0.5) * 2; _resolution.z = Math.ceil(_resolution.z * 0.5) * 2; _voxelSizes.copy(_bboxSize).divide(_resolution); }; _updateResolution(); _padding.copy(_voxelSizes).multiplyScalar(this.pv.padding); _bbox.expandByVector(_padding); _updateResolution(); this.scene().batchUpdates(() => { this.p.boundMin.set(_bbox.min); this.p.boundMax.set(_bbox.max); this.p.resolution.set(_resolution); }); const timeStart = performance.now(); objectWithGeo.updateMatrixWorld(true); objectWorldMat.copy(objectWithGeo.matrixWorld); objectWorldMatInverse.copy(objectWithGeo.matrixWorld).invert(); objectWorldMatInverse.decompose(t, q, s); this.createTextureTargetIfRequired(_resolution); const texture = this._fillTexture(objectWithGeo, { resolution: _resolution, bbox: _bbox, bboxSize: _bboxSize, voxelSizes: _voxelSizes }); const totalTime = performance.now() - timeStart; console.log("SDF generation time", totalTime); if (texture) { this.setTexture(texture); } else { this.cookController.endCook(); } } /* * * FILL TEXTURE * */ _fillTexture(object, options) { const { resolution, bbox, bboxSize, voxelSizes } = options; const boundsTree = object.geometry.boundsTree; const texture = this._dataTexture(resolution); const data = texture.image.data; const pos = new Vector3(); const distanceResult = { point: new Vector3(), distance: -1, faceIndex: -1 }; const resx = resolution.x; const resy = resolution.y; const resz = resolution.z; const minx = bbox.min.x + voxelSizes.x * 0.5; const miny = bbox.min.y + voxelSizes.y * 0.5; const minz = bbox.min.z + voxelSizes.z * 0.5; const sizex = bboxSize.x; const sizey = bboxSize.y; const sizez = bboxSize.z; let i = 0; for (let z = 0; z < resz; z++) { for (let y = 0; y < resy; y++) { for (let x = 0; x < resx; x++) { pos.x = x / resx * sizex + minx; pos.y = y / resy * sizey + miny; pos.z = z / resz * sizez + minz; boundsTree.closestPointToPoint(pos, distanceResult); _rayDir.copy(distanceResult.point).sub(pos); _ray.origin.copy(pos); const res = boundsTree.raycastFirst(_ray, DoubleSide); const inside = res && res.face && res.face.normal.dot(_ray.direction) > 0; const d = distanceResult.distance; data[i] = inside ? -d : d; i++; } } } addSDFMetadataToContainer(texture, { boundMin: bbox.min, boundMax: bbox.max, resolution }); return texture; } _dataTexture(resolution) { return this.__dataTexture = this.__dataTexture || this._createTexture(resolution); } createTextureTargetIfRequired(resolution) { if (!this.__dataTexture || !this._textureResolutionValid(resolution)) { this.__dataTexture = this._createTexture(resolution); this._resolutionUsed.copy(resolution); } } _textureResolutionValid(resolution) { if (this.__dataTexture) { return resolution.equals(this._resolutionUsed); } else { return false; } } _createTexture(resolution) { return createSDFTexture(resolution.x, resolution.y, resolution.z); } /* * * CALLBACK * */ // static PARAM_CALLBACK_render(node: SDFFromObjectCopNode) { // node.setDirty(); // } }