Last active
August 23, 2022 03:21
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///////////////////////////////////////////////////////////////////// | |
// 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