Hello TechSoft3D,
I’m currently working on implementing a polygon resize feature. I have created a polygon with a parent node and 6 child nodes (faces), as shown in the screenshot I’ve shared. The polygon is constructed using the code snippet below.
Now, I want to enable resize functionality where each face or node added a handle. When a user drags any of these handles, the polygon should resize in that specific direction only—adjusting the geometry accordingly.
Important Note:
I’m specifically not looking for a symmetrical scaling solution. I want directional resizing, where moving a single node or face affects only that part of the polygon.
Could you please let me know:
- Is this kind of directional resizing supported or possible to implement with your toolkit?
- If so, what would be the recommended approach to achieve this?
- Is it possible using setNodeMatrix API?
Looking forward to your guidance.
Code snippet -
function createBoxFaceMeshData(
pos: { x: number; y: number; z: number },
width: number,
height: number,
depth: number,
face: number
): MeshData {
const p0 = new Point3(pos.x, pos.y, pos.z);
const p1 = new Point3(pos.x + width, pos.y, pos.z);
const p2 = new Point3(pos.x + width, pos.y + height, pos.z);
const p3 = new Point3(pos.x, pos.y + height, pos.z);
const p4 = new Point3(pos.x, pos.y + height, pos.z + depth);
const p5 = new Point3(pos.x + width, pos.y + height, pos.z + depth);
const p6 = new Point3(pos.x + width, pos.y, pos.z + depth);
const p7 = new Point3(pos.x, pos.y, pos.z + depth);
let triangles: Point3[] = [];
// Determine which face to create based on the 'face' parameter
switch (face) {
case 0: // Front face
triangles = [p0, p2, p1, p0, p3, p2];
break;
case 1: // Top face
triangles = [p2, p3, p4, p2, p4, p5];
break;
case 2: // Right face
triangles = [p1, p2, p5, p1, p5, p6];
break;
case 3: // Left face
triangles = [p0, p7, p4, p0, p4, p3];
break;
case 4: // Back face
triangles = [p5, p4, p7, p5, p7, p6];
break;
case 5: // Bottom face
triangles = [p0, p6, p7, p0, p1, p6];
break;
default:
throw new Error("Invalid face number.");
}
const positions: number[] = [];
triangles.forEach((point) => {
positions.push(point.x, point.y, point.z);
});
// Simplified UVs and normals (optional: you can refine these for proper mapping)
const uvs: number[] = new Array(triangles.length * 2).fill(0);
const normals: number[] = new Array(triangles.length * 3).fill(0);
const meshData = new MeshData();
meshData.addFaces(positions, normals, undefined, uvs);
meshData.setFaceWinding(FaceWinding.CounterClockwise);
return meshData;
}
async function createBoxWithParent(
position: { x: number; y: number; z: number },
width: number,
height: number,
depth: number,
faceColor: Color,
hwv: WebViewer
): Promise {
// Create an empty parent node
const parentNodeId = hwv.model.createNode(null, ‘box-parent’);
// Loop through each of the 6 faces
for (let face = 0; face < 6; face++) {
const meshData = createBoxFaceMeshData(position, width, height, depth, face);
const meshId = await hwv.model.createMesh(meshData);
const meshInstanceData = new MeshInstanceData(meshId, new Matrix(), `face-${face}`);
meshInstanceData.setOpacity(0.2);
// Create the mesh instance as a child of the parent
await hwv.model.createMeshInstance(meshInstanceData, parentNodeId);
}
// Return the parent node ID so it can be manipulated later
return parentNodeId;
}
const createBoundingBox = async (hwv: WebViewer): Promise<Box | null> => {
const selectionItems = gatherLeafNodeIds(hwv);
// hwv.lineManager.removeAllLines();
try {
// Get the bounding box for the selected nodes
const boundingBox = await hwv.model.getNodesBounding(selectionItems);
const { min, max } = boundingBox;
const padding = 0.01; // or 0.1 depending on model scale
// Expanded bounding box
const expandedMin = {
x: min.x - padding,
y: min.y - padding,
z: min.z - padding,
};
const expandedMax = {
x: max.x + padding,
y: max.y + padding,
z: max.z + padding,
};
// const nodesInsideBoundingBox = await getNodesInsideBoundingBox(hwv, min, max);
// Calculate position (center) and size (dimensions)
const width = max.x - min.x;
const height = max.y - min.y;
const depth = max.z - min.z;
const corner = new Point3(min.x, min.y, min.z);
const boundingBoxNodeId = await createBoxWithParent(
corner,
width,
height,
depth,
Color.createFromFloat(255 / 255, 0 / 255, 0 / 255),
hwv,
);
var selectionManager = hwv.selectionManager;
// Function to create a plane
var createPlane = function (
px: number,
py: number,
pz: number,
nx: number,
ny: number,
nz: number,
) {
var p = new Point3(px, py, pz);
var n = new Point3(nx, ny, nz);
return Plane.createFromPointAndNormal(p, n);
};
var planes = [
createPlane(expandedMax.x, 0, 0, -1, 0, 0), // Right
createPlane(expandedMin.x, 0, 0, +1, 0, 0), // Left
createPlane(0, expandedMax.y, 0, 0, -1, 0), // Top
createPlane(0, expandedMin.y, 0, 0, +1, 0), // Bottom
createPlane(0, 0, expandedMax.z, 0, 0, -1), // Front
createPlane(0, 0, expandedMin.z, 0, 0, +1), // Back
];
// Create the configuration object
var config = new IncrementalPickConfig();
config.mustBeFullyContained = true;
var center = new Point3(0, 0, 0);
// Start the selection process
async function runSelection() {
try {
// hwv.selectionManager.clear();
const handle = await selectionManager.beginConvexPolyhedronSelection(
planes,
center,
config,
);
const loop = async (stillProcessing: boolean): Promise<void> => {
if (!stillProcessing) return;
const next = await selectionManager.advanceIncrementalSelection(handle);
return loop(next);
};
await loop(true);
await selectionManager.endIncrementalSelection(handle);
const partIds: number[] = [];
selectionManager.each((selectionItem) => {
partIds.push(selectionItem.getNodeId());
});
if (boundingBoxNodeId) {
dispatch(modelActions.setBoundingBoxNodeId(boundingBoxNodeId));
const nodeSelectionItem = Selection.SelectionItem.create(boundingBoxNodeId);
hwv.selectionManager.set(nodeSelectionItem);
}
const nodes = await getNodesInsideBoundingBox(hwv, min, max, partIds);
console.log("Nodes inside bb - ", nodes)
} catch (error) {
console.error('Error during selection process:', error);
}
}
runSelection();
// nodesInsideBoundingBox.forEach((nodeId) => {
// hwv.selectionManager.selectNode(nodeId, SelectionMode.Add)
// })
return boundingBox;
} catch (error) {
console.error('Error retrieving bounding box:', error);
return null;
}
};