Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 40 additions & 37 deletions js/displacement.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,20 @@ export function applyDisplacement(geometry, imageData, imgWidth, imgHeight, sett
const _dedupMap = new Map();
let _nextId = 0;
const vertexId = new Uint32Array(count);
const _idPosX = [];
const _idPosY = [];
const _idPosZ = [];
for (let i = 0; i < count; i++) {
const x = posAttr.getX(i), y = posAttr.getY(i), z = posAttr.getZ(i);
const key = `${Math.round(x * QUANT)}_${Math.round(y * QUANT)}_${Math.round(z * QUANT)}`;
let id = _dedupMap.get(key);
if (id === undefined) { id = _nextId++; _dedupMap.set(key, id); }
if (id === undefined) {
id = _nextId++;
_dedupMap.set(key, id);
_idPosX.push(x);
_idPosY.push(y);
_idPosZ.push(z);
}
vertexId[i] = id;
}
const uniqueCount = _nextId;
Expand Down Expand Up @@ -191,61 +200,55 @@ export function applyDisplacement(geometry, imageData, imgWidth, imgHeight, sett
let falloffArr = null;

if (boundaryFalloff > 0) {
// Build position lookup per unique vertex ID (first occurrence)
const idPosX = new Float64Array(uniqueCount);
const idPosY = new Float64Array(uniqueCount);
const idPosZ = new Float64Array(uniqueCount);
const idPosSeen = new Uint8Array(uniqueCount);
for (let i = 0; i < count; i++) {
const vid = vertexId[i];
if (!idPosSeen[vid]) {
idPosSeen[vid] = 1;
idPosX[vid] = posAttr.getX(i);
idPosY[vid] = posAttr.getY(i);
idPosZ[vid] = posAttr.getZ(i);
}
// Count boundary positions first, then allocate flat SoA arrays
let bpCount = 0;
for (let id = 0; id < uniqueCount; id++) {
const mfTotal = maskedFracTotal[id];
const maskedFrac = mfTotal > 0 ? maskedFracMasked[id] / mfTotal : 0;
const isOnExclBoundary = excludedPos && excludedPos[id] === 1;
if (isOnExclBoundary || (maskedFrac > 0 && maskedFrac < 1)) bpCount++;
}

const boundaryPositions = []; // [[x, y, z], ...]

// Collect boundary positions: vertices where maskedFrac is between 0 and 1,
// or that sit on the user-exclusion seam.
const bpX = new Float64Array(bpCount);
const bpY = new Float64Array(bpCount);
const bpZ = new Float64Array(bpCount);
let bpIdx = 0;
for (let id = 0; id < uniqueCount; id++) {
const mfTotal = maskedFracTotal[id];
const maskedFrac = mfTotal > 0 ? maskedFracMasked[id] / mfTotal : 0;
const isOnExclBoundary = excludedPos && excludedPos[id] === 1;
if (isOnExclBoundary || (maskedFrac > 0 && maskedFrac < 1)) {
boundaryPositions.push([idPosX[id], idPosY[id], idPosZ[id]]);
bpX[bpIdx] = _idPosX[id]; bpY[bpIdx] = _idPosY[id]; bpZ[bpIdx] = _idPosZ[id];
bpIdx++;
}
}

if (boundaryPositions.length > 0) {
if (bpCount > 0) {
// Build a spatial grid of boundary positions for fast nearest-neighbor lookup
let gMinX = Infinity, gMinY = Infinity, gMinZ = Infinity;
let gMaxX = -Infinity, gMaxY = -Infinity, gMaxZ = -Infinity;
for (const bp of boundaryPositions) {
if (bp[0] < gMinX) gMinX = bp[0]; if (bp[0] > gMaxX) gMaxX = bp[0];
if (bp[1] < gMinY) gMinY = bp[1]; if (bp[1] > gMaxY) gMaxY = bp[1];
if (bp[2] < gMinZ) gMinZ = bp[2]; if (bp[2] > gMaxZ) gMaxZ = bp[2];
for (let i = 0; i < bpCount; i++) {
if (bpX[i] < gMinX) gMinX = bpX[i]; if (bpX[i] > gMaxX) gMaxX = bpX[i];
if (bpY[i] < gMinY) gMinY = bpY[i]; if (bpY[i] > gMaxY) gMaxY = bpY[i];
if (bpZ[i] < gMinZ) gMinZ = bpZ[i]; if (bpZ[i] > gMaxZ) gMaxZ = bpZ[i];
}
const gPad = boundaryFalloff + 1e-3;
gMinX -= gPad; gMinY -= gPad; gMinZ -= gPad;
gMaxX += gPad; gMaxY += gPad; gMaxZ += gPad;

const gRes = Math.max(4, Math.min(128, Math.ceil(Math.cbrt(boundaryPositions.length) * 2)));
const gRes = Math.max(4, Math.min(128, Math.ceil(Math.cbrt(bpCount) * 2)));
const gDx = (gMaxX - gMinX) / gRes || 1;
const gDy = (gMaxY - gMinY) / gRes || 1;
const gDz = (gMaxZ - gMinZ) / gRes || 1;
const bGrid = new Map();
const gridSize = gRes * gRes * gRes;
const bGrid = new Array(gridSize);
const bCellKey = (ix, iy, iz) => (ix * gRes + iy) * gRes + iz;

for (const bp of boundaryPositions) {
const ix = Math.max(0, Math.min(gRes - 1, Math.floor((bp[0] - gMinX) / gDx)));
const iy = Math.max(0, Math.min(gRes - 1, Math.floor((bp[1] - gMinY) / gDy)));
const iz = Math.max(0, Math.min(gRes - 1, Math.floor((bp[2] - gMinZ) / gDz)));
for (let i = 0; i < bpCount; i++) {
const ix = Math.max(0, Math.min(gRes - 1, Math.floor((bpX[i] - gMinX) / gDx)));
const iy = Math.max(0, Math.min(gRes - 1, Math.floor((bpY[i] - gMinY) / gDy)));
const iz = Math.max(0, Math.min(gRes - 1, Math.floor((bpZ[i] - gMinZ) / gDz)));
const ck = bCellKey(ix, iy, iz);
const cell = bGrid.get(ck);
if (cell) cell.push(bp); else bGrid.set(ck, [bp]);
if (bGrid[ck]) bGrid[ck].push(i); else bGrid[ck] = [i];
}

// How many grid cells to search in each direction to cover boundaryFalloff distance
Expand All @@ -262,7 +265,7 @@ export function applyDisplacement(geometry, imageData, imgWidth, imgHeight, sett
// Only compute falloff for fully-textured, non-boundary positions
if (maskedFrac > 0 || isOnExclBoundary) continue;

const px = idPosX[id], py = idPosY[id], pz = idPosZ[id];
const px = _idPosX[id], py = _idPosY[id], pz = _idPosZ[id];
const cix = Math.max(0, Math.min(gRes - 1, Math.floor((px - gMinX) / gDx)));
const ciy = Math.max(0, Math.min(gRes - 1, Math.floor((py - gMinY) / gDy)));
const ciz = Math.max(0, Math.min(gRes - 1, Math.floor((pz - gMinZ) / gDz)));
Expand All @@ -277,10 +280,10 @@ export function applyDisplacement(geometry, imageData, imgWidth, imgHeight, sett
for (let diz = -searchZ; diz <= searchZ; diz++) {
const niz = ciz + diz;
if (niz < 0 || niz >= gRes) continue;
const cell = bGrid.get(bCellKey(nix, niy, niz));
const cell = bGrid[bCellKey(nix, niy, niz)];
if (!cell) continue;
for (const bp of cell) {
const dx = px - bp[0], dy = py - bp[1], dz = pz - bp[2];
for (const idx of cell) {
const dx = px - bpX[idx], dy = py - bpY[idx], dz = pz - bpZ[idx];
const d2 = dx * dx + dy * dy + dz * dz;
if (d2 < minDist2) minDist2 = d2;
}
Expand Down