2021-12-29 21:52:24 -05:00
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
"use strict" ;
( function ( ) {
// dep: ext.three
// dep: ext.three-bgu
2022-01-01 22:39:49 -05:00
gapp . register ( "mesh.api" , [
2021-12-29 21:52:24 -05:00
"moto.license" , // dep: moto.license
"moto.client" , // dep: moto.client
2022-01-01 12:12:57 -05:00
"moto.broker" , // dep: moto.broker
2021-12-29 21:52:24 -05:00
"moto.space" , // dep: moto.space
"mesh.tool" , // dep: mesh.tool
"add.array" , // dep: add.array
] ) ;
let mesh = self . mesh = self . mesh || { } ;
if ( mesh . api ) return ;
let space = moto . Space ;
2021-12-30 22:59:46 -05:00
let groups = [ ] ;
2021-12-31 14:12:42 -05:00
let selected = [ ] ;
2021-12-29 21:52:24 -05:00
2021-12-31 14:12:42 -05:00
let selection = {
2021-12-31 17:14:55 -05:00
// @returns {MeshObject[]}
2021-12-31 14:12:42 -05:00
list ( ) {
2022-01-01 00:04:35 -05:00
return selected . slice ( ) ;
2021-12-31 14:12:42 -05:00
} ,
2021-12-29 21:52:24 -05:00
2022-01-01 13:43:35 -05:00
groups ( ) {
let all = selection . list ( ) ;
let grp = all . filter ( s => s instanceof mesh . group ) ;
let mdl = all . filter ( s => s instanceof mesh . model ) ;
for ( let m of mdl ) {
grp . addOnce ( m . group ) ;
}
return grp ;
} ,
models ( ) {
let all = selection . list ( ) ;
let grp = all . filter ( s => s instanceof mesh . group ) ;
let mdl = all . filter ( s => s instanceof mesh . model ) ;
for ( let g of grp ) {
for ( let m of g . models ) {
mdl . addOnce ( m ) ;
}
}
return mdl ;
} ,
2022-01-02 16:30:05 -05:00
contains ( object ) {
return selected . contains ( object ) ;
} ,
2021-12-31 17:14:55 -05:00
// @param group {MeshObject[]}
2021-12-31 14:12:42 -05:00
set ( objects ) {
selected = objects ;
2022-01-01 23:48:36 -05:00
selection . update ( ) ;
2021-12-31 14:12:42 -05:00
} ,
2021-12-31 17:14:55 -05:00
// @param group {MeshObject}
2021-12-31 14:12:42 -05:00
add ( object ) {
2022-01-02 19:52:56 -05:00
// pendantic code necessary to minimize re-entrant api calls
if ( object . models ) {
// if group, remove discrete selected members
for ( let m of object . models ) {
if ( selected . contains ( m ) ) {
selection . remove ( m ) ;
}
}
} else {
// if model, remove selcted group
if ( selected . contains ( object . group ) ) {
selection . remove ( object . group ) ;
}
}
2021-12-31 14:12:42 -05:00
selected . addOnce ( object ) ;
2022-01-01 23:48:36 -05:00
selection . update ( ) ;
2021-12-31 14:12:42 -05:00
} ,
2021-12-31 17:14:55 -05:00
// @param group {MeshObject}
2021-12-31 14:12:42 -05:00
remove ( object ) {
selected . remove ( object ) ;
2022-01-01 23:48:36 -05:00
selection . update ( ) ;
2021-12-31 14:12:42 -05:00
} ,
2022-01-02 19:52:56 -05:00
// @param group {MeshObject}
toggle ( object ) {
if ( selected . contains ( object ) ) {
selection . remove ( object ) ;
} else {
selection . add ( object ) ;
}
} ,
2021-12-31 14:12:42 -05:00
clear ( ) {
selected = [ ] ;
2022-01-01 23:48:36 -05:00
selection . update ( ) ;
2021-12-31 17:14:55 -05:00
} ,
update ( ) {
for ( let group of groups ) {
group . material ( mesh . material . unselected ) ;
}
2022-01-02 16:30:05 -05:00
// prevent selection of model and its group
let mgsel = selected . filter ( s => s instanceof mesh . model ) . map ( m => m . group ) ;
selected = selected . filter ( sel => ! mgsel . contains ( sel ) ) ;
2021-12-31 17:14:55 -05:00
for ( let object of selected ) {
object . material ( mesh . material . selected ) ;
}
2022-01-01 12:12:57 -05:00
return selection ;
2021-12-31 14:12:42 -05:00
} ,
move ( dx = 0 , dy = 0 , dz = 0 ) {
for ( let s of selected ) {
s . move ( dx , dy , dz ) ;
2021-12-30 22:59:46 -05:00
}
2021-12-31 14:12:42 -05:00
return selection ;
2021-12-30 22:59:46 -05:00
} ,
2021-12-31 14:12:42 -05:00
rotate ( dx = 0 , dy = 0 , dz = 0 ) {
for ( let s of selected ) {
2021-12-31 16:12:37 -05:00
s . rotate ( dx , dy , dz ) ;
2021-12-31 14:12:42 -05:00
}
return selection ;
} ,
2022-01-01 10:44:06 -05:00
qrotate ( q ) {
for ( let s of selected ) {
s . qrotate ( q ) ;
}
return selection ;
} ,
2021-12-31 14:12:42 -05:00
scale ( dx = 0 , dy = 0 , dz = 0 ) {
for ( let s of selected ) {
let { x , y , z } = s . scale ( ) ;
s . scale ( x + dx , y + dy , z + dz ) ;
}
return selection ;
} ,
floor ( ) {
for ( let s of selected ) {
s . floor ( ... arguments ) ;
}
return selection ;
} ,
centerXY ( ) {
for ( let s of selected ) {
2021-12-31 16:59:38 -05:00
s . centerXY ( ... arguments ) ;
2021-12-31 14:12:42 -05:00
}
return selection ;
} ,
2022-01-01 13:43:35 -05:00
wireframe ( ) {
for ( let m of selection . models ( ) ) {
m . wireframe ( ... arguments ) ;
}
return selection ;
} ,
boundsBox ( ) {
for ( let m of selection . groups ( ) ) {
m . showBounds ( ... arguments ) ;
}
return selection ;
} ,
2021-12-31 14:12:42 -05:00
home ( ) {
return selection . centerXY ( ) . floor ( ) ;
2021-12-31 16:59:38 -05:00
} ,
focus ( ) {
api . focus ( selected ) ;
} ,
bounds ( ) {
return util . bounds ( selected . map ( s => s . object ( ) ) ) ;
2021-12-31 14:12:42 -05:00
}
} ;
let group = {
// @returns {MeshGroup[]}
list ( ) {
return groups . slice ( ) ;
} ,
// @param group {MeshModel[]}
2022-01-01 23:48:36 -05:00
new ( models ) {
2021-12-31 14:12:42 -05:00
return group . add ( new mesh . group ( models ) ) ;
} ,
// @param group {MeshGroup}
2022-01-01 23:48:36 -05:00
add ( group ) {
2021-12-31 14:12:42 -05:00
groups . addOnce ( group ) ;
space . world . add ( group . group ) ;
space . update ( ) ;
return group ;
} ,
// @param group {MeshGroup}
2022-01-01 23:48:36 -05:00
remove ( group ) {
2021-12-31 14:12:42 -05:00
groups . remove ( group ) ;
space . world . remove ( group . group ) ;
space . update ( ) ;
}
} ;
2022-01-01 22:39:49 -05:00
let model = {
// @returns {MeshModel[]}
list ( ) {
return groups . map ( g => g . models ) . flat ( ) ;
}
} ;
2021-12-31 14:12:42 -05:00
let api = mesh . api = {
2022-01-01 23:48:36 -05:00
clear ( ) {
2021-12-31 14:12:42 -05:00
for ( let group of group . list ( ) ) {
group . remove ( group ) ;
}
} ,
2021-12-31 16:59:38 -05:00
// @param object {THREE.Object3D | THREE.Object3D[]}
2022-01-01 23:48:36 -05:00
focus ( object ) {
2021-12-31 16:59:38 -05:00
let { center } = util . bounds ( object ) ;
2021-12-31 17:20:41 -05:00
// when no valid objects supplied, set origin
2021-12-31 16:59:38 -05:00
if ( isNaN ( center . x * center . y * center . z ) ) {
2021-12-31 17:20:41 -05:00
center = { x : 0 , y : 0 , z : 0 } ;
2021-12-31 16:59:38 -05:00
}
2021-12-31 17:20:41 -05:00
// sets "home" views (front, back, home, reset)
2022-01-01 13:43:35 -05:00
space . platform . setCenter ( center . x , - center . y , center . z ) ;
2021-12-31 17:20:41 -05:00
// sets camera focus
2021-12-31 16:59:38 -05:00
space . view . setFocus ( new THREE . Vector3 (
2022-01-01 13:43:35 -05:00
center . x , center . z , - center . y
2021-12-31 16:59:38 -05:00
) ) ;
} ,
2021-12-31 14:12:42 -05:00
selection ,
group ,
2022-01-01 22:39:49 -05:00
model ,
2022-01-01 00:04:35 -05:00
2022-01-01 23:48:36 -05:00
objects ( ) {
2022-01-02 14:25:28 -05:00
// return model objects suitable for finding ray intersections
2022-01-01 00:04:35 -05:00
return group . list ( ) . map ( o => o . models ) . flat ( ) . map ( o => o . object ( ) ) ;
2021-12-29 21:52:24 -05:00
}
} ;
2021-12-31 00:35:45 -05:00
let util = mesh . util = {
2022-01-01 23:48:36 -05:00
uuid ( segs = 1 ) {
2022-01-01 22:39:49 -05:00
let uid = [ ] ;
while ( segs -- > 0 ) {
uid . push ( Math . round ( Math . random ( ) * 0xffffffff ) . toString ( 36 ) ) ;
}
return uid . join ( '-' ) ;
} ,
2021-12-31 17:14:55 -05:00
// @param object {THREE.Object3D | THREE.Object3D[] | MeshObject | MeshObject[]}
2021-12-31 00:35:45 -05:00
// @returns bounds modified for moto.Space
2022-01-01 23:48:36 -05:00
bounds ( object ) {
2021-12-31 16:59:38 -05:00
let box = new THREE . Box3 ( ) ;
if ( Array . isArray ( object ) ) {
for ( let o of object ) {
2022-01-01 20:07:05 -05:00
util . box3expand ( box , o instanceof mesh . object ? o . object ( ) : o ) ;
2021-12-31 16:59:38 -05:00
}
} else if ( object ) {
2022-01-01 20:07:05 -05:00
util . box3expand ( box , object instanceof mesh . object ? object . object ( ) : object ) ;
2021-12-31 16:59:38 -05:00
} else {
return box ;
}
2021-12-31 00:35:45 -05:00
let bnd = {
min : {
x : box . min . x ,
y : box . min . z ,
z : box . min . y
} ,
max : {
x : box . max . x ,
y : box . max . z ,
z : box . max . y
}
} ;
bnd . size = {
x : bnd . max . x - bnd . min . x ,
y : bnd . max . y - bnd . min . y ,
z : bnd . max . z - bnd . min . z
} ;
bnd . center = {
x : ( bnd . max . x + bnd . min . x ) / 2 ,
2022-01-01 12:12:57 -05:00
y : - ( bnd . max . y + bnd . min . y ) / 2 ,
2021-12-31 00:35:45 -05:00
z : ( bnd . max . z + bnd . min . z ) / 2
} ;
return bnd ;
2022-01-01 20:07:05 -05:00
} ,
// bounding box workaround adapted from:
// https://discourse.threejs.org/t/bounding-box-bigger-than-concave-object-extrudegeometry/26073/2
2022-01-01 23:48:36 -05:00
box3expand ( box3 , object ) {
2022-01-01 20:07:05 -05:00
const geometry = object . geometry ;
object . updateWorldMatrix ( geometry ? true : false , false ) ;
if ( geometry ) {
let bounds = util . geoBounds ( geometry , object . matrixWorld ) ;
2022-01-01 20:19:45 -05:00
let bt = new THREE . Box3 ( ) . copy ( bounds ) ;
let m4 = new THREE . Matrix4 ( ) ;
m4 . setPosition ( new THREE . Vector3 ( ) . setFromMatrixPosition ( object . matrixWorld ) ) ;
bt . applyMatrix4 ( m4 ) ;
box3 . union ( bt ) ;
2022-01-01 20:07:05 -05:00
}
const children = object . children ;
for ( let i = 0 , l = children . length ; i < l ; i ++ ) {
util . box3expand ( box3 , children [ i ] ) ;
}
} ,
// second half of bound box workaround (see above)
2022-01-01 23:48:36 -05:00
// todo: cache, distribute in workers, or other mem/cpu optimization
geoBounds ( geometry , matrix ) {
2022-01-01 20:07:05 -05:00
const boundingBox = new THREE . Box3 ( ) ;
const position = geometry . attributes . position . clone ( ) ;
if ( matrix ) {
position . applyMatrix4 ( new THREE . Matrix4 ( ) . extractRotation ( matrix ) ) ;
}
if ( position . isGLBufferAttribute ) {
2022-01-01 23:48:36 -05:00
console . error ( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box. Alternatively set "mesh.frustumCulled" to "false".' , geometry ) ;
2022-01-01 20:07:05 -05:00
}
boundingBox . setFromBufferAttribute ( position ) ;
const morphAttributesPosition = geometry . morphAttributes . position ;
if ( morphAttributesPosition ) {
for ( let i = 0 , il = morphAttributesPosition . length ; i < il ; i ++ ) {
2022-01-01 20:19:45 -05:00
const box3 = new THREE . Box3 ( ) . setFromBufferAttribute ( morphAttributesPosition [ i ] ) ;
2022-01-01 20:07:05 -05:00
if ( geometry . morphTargetsRelative ) {
2022-01-01 20:19:45 -05:00
let vector = new THREE . Vector3 ( ) ;
vector . addVectors ( boundingBox . min , box3 . min ) ;
boundingBox . expandByPoint ( vector ) ;
vector . addVectors ( boundingBox . max , box3 . max ) ;
boundingBox . expandByPoint ( vector ) ;
2022-01-01 20:07:05 -05:00
} else {
2022-01-01 20:19:45 -05:00
boundingBox . expandByPoint ( box3 . min ) ;
boundingBox . expandByPoint ( box3 . max ) ;
2022-01-01 20:07:05 -05:00
}
}
}
if ( isNaN ( boundingBox . min . x ) || isNaN ( boundingBox . min . y ) || isNaN ( boundingBox . min . z ) ) {
2022-01-01 23:48:36 -05:00
console . error ( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.' , geometry ) ;
2022-01-01 20:07:05 -05:00
}
return boundingBox ;
2021-12-31 00:35:45 -05:00
}
} ;
2022-01-01 22:39:49 -05:00
let broker = gapp . broker ;
2022-01-01 23:48:36 -05:00
// publish messages when api functions are called
broker . wrapObject ( selection , 'selection' ) ;
broker . wrapObject ( model , 'model' ) ;
broker . wrapObject ( group , 'group' ) ;
2022-01-01 22:39:49 -05:00
2021-12-29 21:52:24 -05:00
} ) ( ) ;