Skip to content

Instantly share code, notes, and snippets.

@yiskang
Last active August 23, 2022 03:21
Show Gist options
  • Save yiskang/d3c088f2633228a474301ce68f764c68 to your computer and use it in GitHub Desktop.
Save yiskang/d3c088f2633228a474301ce68f764c68 to your computer and use it in GitHub Desktop.
Forge Viewer: Demo how to get bounds of current section box and draw it on the screen
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////
(function () {
// three.js r71 doesn't have addScaledVector for THREE.Vector3
if (!THREE.Vector3.prototype.addScaledVector) {
THREE.Vector3.prototype.addScaledVector = function (v, s) {
this.x += v.x * s;
this.y += v.y * s;
this.z += v.z * s;
return this;
};
}
/**
* Helper of converting THREE.Box3 to THREE.Mesh
* @class
*/
class BoxMeshHelper extends THREE.Mesh {
constructor(box) {
const geometry = new THREE.BufferGeometry();
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(36 * positionNumComponents), positionNumComponents));
geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(36 * positionNumComponents), normalNumComponents));
geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(36 * uvNumComponents), uvNumComponents));
super(geometry, new THREE.MeshPhongMaterial({ color: 0xffff00, side: THREE.DoubleSide, opacity: 0.4, transparent: true }));
this.type = 'BoxMeshHelper';
this.box = box;
this.positionNumComponents = 3;
this.normalNumComponents = 3;
this.uvNumComponents = 2;
this.update();
}
update() {
const box = this.box;
if (box.isEmpty()) return;
const min = box.min;
const max = box.max;
/*
5____4
1/___0/|
| 6__|_7
2/___3/
0: max.x, max.y, max.z
1: min.x, max.y, max.z
2: min.x, min.y, max.z
3: max.x, min.y, max.z
4: max.x, max.y, min.z
5: min.x, max.y, min.z
6: min.x, min.y, min.z
7: max.x, min.y, min.z
*/
const vertices = [
// front
{ pos: [min.x, min.y, max.z], norm: [0, 0, 1], uv: [0, 1], },
{ pos: [max.x, min.y, max.z], norm: [0, 0, 1], uv: [1, 1], },
{ pos: [min.x, max.y, max.z], norm: [0, 0, 1], uv: [0, 0], },
{ pos: [min.x, max.y, max.z], norm: [0, 0, 1], uv: [0, 0], },
{ pos: [max.x, min.y, max.z], norm: [0, 0, 1], uv: [1, 1], },
{ pos: [max.x, max.y, max.z], norm: [0, 0, 1], uv: [1, 0], },
// right
{ pos: [max.x, min.y, max.z], norm: [1, 0, 0], uv: [0, 1], },
{ pos: [max.x, min.y, min.z], norm: [1, 0, 0], uv: [1, 1], },
{ pos: [max.x, max.y, max.z], norm: [1, 0, 0], uv: [0, 0], },
{ pos: [max.x, max.y, max.z], norm: [1, 0, 0], uv: [0, 0], },
{ pos: [max.x, min.y, min.z], norm: [1, 0, 0], uv: [1, 1], },
{ pos: [max.x, max.y, min.z], norm: [1, 0, 0], uv: [1, 0], },
// back
{ pos: [max.x, min.y, min.z], norm: [0, 0, -1], uv: [0, 1], },
{ pos: [min.x, min.y, min.z], norm: [0, 0, -1], uv: [1, 1], },
{ pos: [max.x, max.y, min.z], norm: [0, 0, -1], uv: [0, 0], },
{ pos: [max.x, max.y, min.z], norm: [0, 0, -1], uv: [0, 0], },
{ pos: [min.x, min.y, min.z], norm: [0, 0, -1], uv: [1, 1], },
{ pos: [min.x, max.y, min.z], norm: [0, 0, -1], uv: [1, 0], },
// left
{ pos: [min.x, min.y, min.z], norm: [-1, 0, 0], uv: [0, 1], },
{ pos: [min.x, min.y, max.z], norm: [-1, 0, 0], uv: [1, 1], },
{ pos: [min.x, max.y, min.z], norm: [-1, 0, 0], uv: [0, 0], },
{ pos: [min.x, max.y, min.z], norm: [-1, 0, 0], uv: [0, 0], },
{ pos: [min.x, min.y, max.z], norm: [-1, 0, 0], uv: [1, 1], },
{ pos: [min.x, max.y, max.z], norm: [-1, 0, 0], uv: [1, 0], },
// top
{ pos: [max.x, max.y, min.z], norm: [0, 1, 0], uv: [0, 1], },
{ pos: [min.x, max.y, min.z], norm: [0, 1, 0], uv: [1, 1], },
{ pos: [max.x, max.y, max.z], norm: [0, 1, 0], uv: [0, 0], },
{ pos: [max.x, max.y, max.z], norm: [0, 1, 0], uv: [0, 0], },
{ pos: [min.x, max.y, min.z], norm: [0, 1, 0], uv: [1, 1], },
{ pos: [min.x, max.y, max.z], norm: [0, 1, 0], uv: [1, 0], },
// bottom
{ pos: [max.x, min.y, max.z], norm: [0, -1, 0], uv: [0, 1], },
{ pos: [min.x, min.y, max.z], norm: [0, -1, 0], uv: [1, 1], },
{ pos: [max.x, min.y, min.z], norm: [0, -1, 0], uv: [0, 0], },
{ pos: [max.x, min.y, min.z], norm: [0, -1, 0], uv: [0, 0], },
{ pos: [min.x, min.y, max.z], norm: [0, -1, 0], uv: [1, 1], },
{ pos: [min.x, min.y, min.z], norm: [0, -1, 0], uv: [1, 0], },
];
const positions = [];
const normals = [];
const uvs = [];
for (const vertex of vertices) {
positions.push(...vertex.pos);
normals.push(...vertex.norm);
uvs.push(...vertex.uv);
}
this.geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(positions), this.positionNumComponents);
this.geometry.attributes.normal = new THREE.BufferAttribute(new Float32Array(normals), this.normalNumComponents);
this.geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array(uvs), this.uvNumComponents);
this.geometry.attributes.position.needsUpdate = true;
this.geometry.attributes.normal.needsUpdate = true;
this.geometry.attributes.uv.needsUpdate = true;
this.geometry.computeBoundingSphere();
}
}
const EXTENSION_NAME = 'Autodesk.ADN.DrawSectionBoxExt';
class DrawSectionBoxExt extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this.onContextMenu = this.onContextMenu.bind(this);
}
get overlayName() {
return EXTENSION_NAME;
}
onContextMenu(menu, status) {
const cutPlanes = this.viewer.getCutPlanes();
if (cutPlanes.length >= 6) {
menu.push({
title: 'Draw Section Box Mesh',
target: () => {
this.buildBoxOverlay();
}
});
}
if (!this.isOverlayEmpty()) {
menu.push({
title: 'Clear Section Box Meshes',
target: () => {
this.clearBoxOverlays();
}
});
}
}
isOverlayEmpty() {
if (!this.viewer.overlays.hasScene(this.overlayName)) return true;
return this.viewer.impl.overlayScenes[this.overlayName].scene.children.length <= 0;
}
/**
* Takes two planes and returns a Line3 which represents their intersection
* use len to specify how long the line should be
* @param {THREE.Plane} pl1
* @param {THREE.Plane} pl2
* @param {*} len How long the line should be
* @returns {THREE.Line3} Intersection Line
* // ref: https://discourse.threejs.org/t/is-it-possible-to-create-a-three-box3-from-a-three-fustum/6568/4
*/
intersectPlanes(pl1, pl2, len = 100000) {
let EXTENSION = 2000000;
let cp1 = pl1.normal.clone();
cp1.normalize();
cp1.negate();
cp1.multiplyScalar(pl1.constant);
let cp2 = pl2.normal.clone();
cp2.normalize();
cp2.negate();
cp2.multiplyScalar(pl2.constant);
// calculate cross product to derive direction of intersection line
let crPl1 = pl1.normal.clone();
let crPl2 = pl2.normal.clone();
let cr = crPl1.cross(crPl2);
cr.normalize();
let dtPl1 = pl1.normal.clone();
let dtPl2 = pl2.normal.clone();
let dot = dtPl1.dot(dtPl2);
let ang = Math.acos(dot) * (180 / Math.PI);
// return null if planes are identical or parallel
if (ang == 0 || ang == 180) return null;
// project point directly if planes are perpendicular
if (ang == 90 || ang == 270) {
let intPt = new THREE.Vector3();
pl2.projectPoint(cp1, intPt);
let intA = intPt.clone();
let intB = intPt.clone();
intA.addScaledVector(cr, len / 2);
intB.addScaledVector(cr, -len / 2);
let intLn = new THREE.Line3(intA, intB);
return intLn;
}
// project origin of first plane on to second plane
let projLnAStart = new THREE.Vector3(cp1.x, cp1.y, cp1.z).normalize();
projLnAStart.multiplyScalar(EXTENSION);
let projLnAEnd = new THREE.Vector3(-projLnAStart.x, -projLnAStart.y, -projLnAStart.z);
let int1 = new THREE.Vector3();
let projLnA = new THREE.Line3(projLnAStart, projLnAEnd);
pl2.intersectLine(projLnA, int1);
// project the intersection point back to first plane along the second plane
let projVcB = new THREE.Vector3();
projVcB.subVectors(int1, cp2).normalize();
projVcB.multiplyScalar(EXTENSION);
let projLnBStart = cp2.clone();
projLnBStart.add(projVcB);
let projLnBEnd = cp2.clone();
projLnBEnd.add(projVcB.negate());
let int2 = new THREE.Vector3();
let projLnB = new THREE.Line3(projLnBStart, projLnBEnd);
pl1.intersectLine(projLnB, int2);
// use cross vector and intersecttion point to create line
let intA = int2.clone();
let intB = int2.clone();
intA.addScaledVector(cr, len / 2);
intB.addScaledVector(cr, -len / 2);
let intLn = new THREE.Line3(intA, intB);
return intLn;
}
/**
* Get intersected points of section box planes
* @param {THREE.Frustum} frustum
* @returns {THREE.Vector3[]} Intersected points of section box planes
* // ref: https://discourse.threejs.org/t/is-it-possible-to-create-a-three-box3-from-a-three-fustum/6568/4
*/
getCorners(frustum) {
let lnLT = this.intersectPlanes(frustum.planes[0], frustum.planes[1]);
let lnRT = this.intersectPlanes(frustum.planes[1], frustum.planes[2]);
let lnRB = this.intersectPlanes(frustum.planes[2], frustum.planes[3]);
let lnLB = this.intersectPlanes(frustum.planes[3], frustum.planes[0]);
let int;
int = new THREE.Vector3();
frustum.planes[4].intersectLine(lnLT, int);
let ptLTN = int.clone();
int = new THREE.Vector3();
frustum.planes[5].intersectLine(lnLT, int);
let ptLTF = int.clone();
int = new THREE.Vector3();
frustum.planes[4].intersectLine(lnRT, int);
let ptRTN = int.clone();
int = new THREE.Vector3();
frustum.planes[5].intersectLine(lnRT, int);
let ptRTF = int.clone();
int = new THREE.Vector3();
frustum.planes[4].intersectLine(lnLB, int);
let ptLBN = int.clone();
int = new THREE.Vector3();
frustum.planes[5].intersectLine(lnLB, int);
let ptLBF = int.clone();
int = new THREE.Vector3();
frustum.planes[4].intersectLine(lnRB, int);
let ptRBN = int.clone();
int = new THREE.Vector3();
frustum.planes[5].intersectLine(lnRB, int);
let ptRBF = int.clone();
let pts = [ptLTN, ptLTF, ptRTN, ptRTF, ptLBN, ptLBF, ptRBN, ptRBF];
return pts;
}
buildBoxOverlay() {
const cutPlanes = this.viewer.getCutPlanes();
if (cutPlanes.length < 6) return;
let planes = cutPlanes.map(p => new THREE.Plane().setComponents(p.x, p.y, p.z, p.w))
let frustum = new THREE.Frustum(
planes[3],
planes[1],
planes[0],
planes[4],
planes[5],
planes[2]
);
let pts = this.getCorners(frustum);
let box = new BoxMeshHelper(new THREE.Box3().setFromPoints(pts));
this.viewer.overlays.addMesh(box, this.overlayName);
}
clearBoxOverlays() {
if (this.viewer.overlays.hasScene(this.overlayName)) {
this.viewer.overlays.clearScene(this.overlayName);
}
}
load() {
if (!this.viewer.overlays.hasScene(this.overlayName)) {
this.viewer.overlays.addScene(this.overlayName);
}
this.viewer.registerContextMenuCallback(EXTENSION_NAME, this.onContextMenu);
console.log('DrawSectionBoxExt has been loaded.');
return true;
}
async unload() {
this.viewer.registerContextMenuCallback(EXTENSION_NAME, this.onContextMenu);
this.clearBoxOverlays();
console.log('DrawSectionBoxExt has been unloaded.');
return true;
}
}
Autodesk.Viewing.theExtensionManager.registerExtension(EXTENSION_NAME, DrawSectionBoxExt);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment