diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt index 14663acc..5b11d5e4 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt @@ -77,6 +77,9 @@ fun DisplayObject.int(default: Int? = null, key: String? = null, inherited: Bool fun DisplayObject.node(key: String? = null, inherited: Boolean = true) = DisplayObjectDelegateWrapper(key?.toName(), null, inherited) { it.node } +fun DisplayObject.item(key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), null, inherited) { it } + //fun Configurable.spec(spec: Specification, key: String? = null) = ChildConfigDelegate(key) { spec.wrap(this) } @JvmName("safeString") @@ -95,6 +98,10 @@ fun DisplayObject.number(default: Number, key: String? = null, inherited: Boolea fun DisplayObject.double(default: Double, key: String? = null, inherited: Boolean = true) = DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.double } +@JvmName("safeInt") +fun DisplayObject.int(default: Int, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.int } + inline fun > DisplayObject.enum(default: E, key: String? = null, inherited: Boolean = true) = DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { item -> item.string?.let { enumValueOf(it) } } diff --git a/dataforge-vis-spatial-js/build.gradle.kts b/dataforge-vis-spatial-js/build.gradle.kts index ec5f9fec..65f47fba 100644 --- a/dataforge-vis-spatial-js/build.gradle.kts +++ b/dataforge-vis-spatial-js/build.gradle.kts @@ -11,7 +11,7 @@ plugins { dependencies { api(project(":dataforge-vis-spatial")) - implementation("info.laht.threekt:threejs-wrapper:0.88-npm-1") + implementation("info.laht.threekt:threejs-wrapper:0.88-npm-2") } configure { @@ -19,7 +19,6 @@ configure { configure { dependency("three-full") - //dependency("three-orbitcontrols") dependency("style-loader") devDependency("karma") } @@ -45,6 +44,7 @@ tasks{ sourceMap = true moduleKind = "umd" main = "call" + kotlinOptions.sourceMapEmbedSources = "always" } } @@ -54,6 +54,7 @@ tasks{ outputFile = "${project.buildDir.path}/js/${project.name}-test.js" sourceMap = true moduleKind = "umd" + kotlinOptions.sourceMapEmbedSources = "always" } } } \ No newline at end of file diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/HMR.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/HMR.kt index a77b9cf6..e65d1cf2 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/HMR.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/HMR.kt @@ -1,4 +1,4 @@ -package hep.dataforge.vis.hmr +package hep.dataforge.vis external val module: Module diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/main.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/main.kt index f4359987..e00d3a71 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/main.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/main.kt @@ -1,6 +1,5 @@ package hep.dataforge.vis -import hep.dataforge.vis.hmr.module import hep.dataforge.vis.spatial.ThreeDemoApp import kotlin.browser.document import kotlin.dom.hasClass diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeDemoApp.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeDemoApp.kt index c0b4dd46..23998b78 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeDemoApp.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeDemoApp.kt @@ -1,10 +1,13 @@ package hep.dataforge.vis.spatial import hep.dataforge.context.Global +import hep.dataforge.meta.buildMeta import hep.dataforge.meta.number import hep.dataforge.meta.set import hep.dataforge.vis.ApplicationBase import hep.dataforge.vis.DisplayGroup +import hep.dataforge.vis.require +import hep.dataforge.vis.spatial.gdml.gdml import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.isActive @@ -18,8 +21,7 @@ class ThreeDemoApp : ApplicationBase() { override val stateKeys: List = emptyList() override fun start(state: Map) { - //require("three-full") - //require("three/examples/js/geometries/ConvexGeometry") + require("JSRootGeoBase.js") val renderer = ThreeOutput(Global) renderer.start(document.getElementById("canvas")!!) @@ -42,16 +44,24 @@ class ThreeDemoApp : ApplicationBase() { properties.style["color"] = 1530 } } - convex { - point(50,50,-50) - point(50,-50,-50) - point(-50,-50,-50) - point(-50,50,-50) - point(50,50,50) - point(50,-50,50) - point(-50,-50,50) - point(-50,50,50) - +// convex { +// point(50, 50, -50) +// point(50, -50, -50) +// point(-50, -50, -50) +// point(-50, 50, -50) +// point(50, 50, 50) +// point(50, -50, 50) +// point(-50, -50, 50) +// point(-50, 50, 50) +// } + gdml { + y = 110.0 + shape = buildMeta { + "_typename" to "TGeoBBox" + "fDX" to 50.0 + "fDY" to 50.0 + "fDZ" to 50.0 + } } } @@ -76,5 +86,5 @@ class ThreeDemoApp : ApplicationBase() { // } } - override fun dispose() = emptyMap()//mapOf("lines" to presenter.dispose()) + override fun dispose() = emptyMap()//mapOf("lines" to presenter.dispose()) } \ No newline at end of file diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeOutput.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeOutput.kt index f06a920d..8084b57e 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeOutput.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeOutput.kt @@ -5,6 +5,8 @@ import hep.dataforge.io.Output import hep.dataforge.meta.Meta import hep.dataforge.vis.DisplayGroup import hep.dataforge.vis.DisplayObject +import hep.dataforge.vis.spatial.gdml.GDMLObject +import hep.dataforge.vis.spatial.gdml.ThreeGDMLBuilder import hep.dataforge.vis.spatial.three.Group import info.laht.threekt.WebGLRenderer import info.laht.threekt.cameras.PerspectiveCamera @@ -66,6 +68,7 @@ class ThreeOutput(override val context: Context) : Output { ThreeObjectBuilder.updatePosition(obj, this) } is Box -> ThreeBoxBuilder(obj) + is GDMLObject -> ThreeGDMLBuilder(obj) //is Convex -> ThreeConvexBuilder(obj) else -> null } diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/gdml/GDMLObject.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/gdml/GDMLObject.kt new file mode 100644 index 00000000..a21abb3b --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/gdml/GDMLObject.kt @@ -0,0 +1,64 @@ +package hep.dataforge.vis.spatial.gdml + +import hep.dataforge.meta.EmptyMeta +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaItem +import hep.dataforge.vis.* +import hep.dataforge.vis.spatial.GenericThreeBuilder +import hep.dataforge.vis.spatial.Materials +import hep.dataforge.vis.spatial.material +import hep.dataforge.vis.spatial.three.EdgesGeometry +import info.laht.threekt.objects.LineSegments +import info.laht.threekt.objects.Mesh + +class GDMLObject(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, TYPE, meta) { + + var shape by node() + + var color by item() + + var facesLimit by int(0) + + companion object { + const val TYPE = "geometry.spatial.gdml" + } +} + +//TODO add Zelenyy GDML builder here +fun DisplayGroup.gdml(meta: Meta = EmptyMeta, action: GDMLObject.() -> Unit = {}) = + GDMLObject(this, meta).apply(action).also { addChild(it) } + +fun Meta.toDynamic(): dynamic { + fun MetaItem<*>.toDynamic(): dynamic = when (this) { + is MetaItem.ValueItem -> this.value.value.asDynamic() + is MetaItem.NodeItem -> this.node.toDynamic() + } + + val res = js("{}") + this.items.entries.groupBy { it.key.body }.forEach { (key, value) -> + val list = value.map { it.value } + res[key] = when (list.size) { + 1 -> list.first().toDynamic() + else -> list.map { it.toDynamic() } + } + } + return res +} + + +object ThreeGDMLBuilder : GenericThreeBuilder() { + override fun build(obj: GDMLObject): Mesh { + val shapeMeta = obj.shape?.toDynamic() ?: error("The shape not defined") + println(shapeMeta) + val geometry = createGeometry(shapeMeta, obj.facesLimit) + return Mesh(geometry, obj.color.material()).also { mesh -> + mesh.add(LineSegments(EdgesGeometry(geometry), Materials.DEFAULT)) + } + } + + override fun update(obj: GDMLObject, target: Mesh) { + val shapeMeta: dynamic = obj.shape?.toDynamic() + target.geometry = createGeometry(shapeMeta, obj.facesLimit) + target.material = obj.color.material() + } +} \ No newline at end of file diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/gdml/GEO.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/gdml/GEO.kt new file mode 100644 index 00000000..28cf0264 --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/gdml/GEO.kt @@ -0,0 +1,7 @@ +@file:JsModule("JSRootGeoBase.js") +@file:JsNonModule +package hep.dataforge.vis.spatial.gdml + +import info.laht.threekt.core.Geometry + +external fun createGeometry(shape: dynamic, limit: Int): Geometry diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ConvexGeometry.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ConvexGeometry.kt index 5d1c1f44..3949958d 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ConvexGeometry.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ConvexGeometry.kt @@ -1,4 +1,5 @@ -@file:JsQualifier("THREE") +@file:JsModule("three-full") +@file:JsNonModule package hep.dataforge.vis.spatial.three import info.laht.threekt.core.BufferGeometry diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/EdgesGeometry.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/EdgesGeometry.kt index fb85241d..48a493c7 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/EdgesGeometry.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/EdgesGeometry.kt @@ -1,4 +1,4 @@ -@file:JsModule("three") +@file:JsModule("three-full") @file:JsNonModule package hep.dataforge.vis.spatial.three diff --git a/dataforge-vis-spatial-js/src/main/resources/JSRootGeoBase.js b/dataforge-vis-spatial-js/src/main/resources/JSRootGeoBase.js new file mode 100644 index 00000000..083dfdc2 --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/resources/JSRootGeoBase.js @@ -0,0 +1,3426 @@ +/** @file JSRootGeoBase.js */ +/// Basic functions for work with TGeo classes + +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + define( [ 'three-full', 'ThreeCSG' ], factory ); + } else + if (typeof exports === 'object' && typeof module !== 'undefined') { + factory(require("three-full"), require("./ThreeCSG.js")); + } else { + if (typeof THREE == 'undefined') + throw new Error('THREE is not defined', 'JSRootGeoBase.js'); + + if (typeof ThreeBSP == 'undefined') + throw new Error('ThreeBSP is not defined', 'JSRootGeoBase.js'); + + factory(THREE, ThreeBSP); + } +} (function( THREE, ThreeBSP ) { + + "use strict"; + + var JSROOT = {}; + + /** Generate mask for given bit + * + * @param {number} n bit number + * @returns {Number} produced make + * @private */ + JSROOT.BIT = function(n) { return 1 << (n); }; + + /** + * @summary Generic method to invoke callback function. + * + * @param {object|function} func either normal function or container like + * { obj: object_pointer, func: name of method to call } + * @param arg1 first optional argument of callback + * @param arg2 second optional argument of callback + * + * @private + */ + JSROOT.CallBack = function(func, arg1, arg2) { + + if (typeof func == 'string') func = JSROOT.findFunction(func); + + if (!func) return; + + if (typeof func == 'function') return func(arg1,arg2); + + if (typeof func != 'object') return; + + if (('obj' in func) && ('func' in func) && + (typeof func.obj == 'object') && (typeof func.func == 'string') && + (typeof func.obj[func.func] == 'function')) { + return func.obj[func.func](arg1, arg2); + } + }; + + /** @namespace GEO */ + /// Holder of all TGeo-related functions and classes + var GEO = { + GradPerSegm: 6, // grad per segment in cylinder/spherical symmetry shapes + CompressComp: true, // use faces compression in composite shapes + CompLimit: 20 // maximal number of components in composite shape + }; + + /** @memberOf GEO */ + GEO.BITS = { + kVisOverride : JSROOT.BIT(0), // volume's vis. attributes are overwritten + kVisNone : JSROOT.BIT(1), // the volume/node is invisible, as well as daughters + kVisThis : JSROOT.BIT(2), // this volume/node is visible + kVisDaughters : JSROOT.BIT(3), // all leaves are visible + kVisOneLevel : JSROOT.BIT(4), // first level daughters are visible + kVisStreamed : JSROOT.BIT(5), // true if attributes have been streamed + kVisTouched : JSROOT.BIT(6), // true if attributes are changed after closing geom + kVisOnScreen : JSROOT.BIT(7), // true if volume is visible on screen + kVisContainers : JSROOT.BIT(12), // all containers visible + kVisOnly : JSROOT.BIT(13), // just this visible + kVisBranch : JSROOT.BIT(14), // only a given branch visible + kVisRaytrace : JSROOT.BIT(15) // raytracing flag + }; + + /** @memberOf GEO */ + GEO.TestBit = function(volume, f) { + var att = volume.fGeoAtt; + return att === undefined ? false : ((att & f) !== 0); + }; + + /** @memberOf GEO */ + GEO.SetBit = function(volume, f, value) { + if (volume.fGeoAtt === undefined) return; + volume.fGeoAtt = value ? (volume.fGeoAtt | f) : (volume.fGeoAtt & ~f); + }; + + /** @memberOf GEO */ + GEO.ToggleBit = function(volume, f) { + if (volume.fGeoAtt !== undefined) + volume.fGeoAtt = volume.fGeoAtt ^ (f & 0xffffff); + }; + + /** @memberOf GEO + * implementation of TGeoVolume::InvisibleAll */ + GEO.InvisibleAll = function(flag) { + if (flag===undefined) flag = true; + + GEO.SetBit(this, GEO.BITS.kVisThis, !flag); + GEO.SetBit(this, GEO.BITS.kVisDaughters, !flag); + GEO.SetBit(this, GEO.BITS.kVisOneLevel, false); + + if (this.fNodes) + for (var n=0;n0) ? 9 : 18), norm = this.norm; + + if (reduce!==1) { + norm[indx] = nx12; + norm[indx+1] = ny12; + norm[indx+2] = nz12; + norm[indx+3] = nx12; + norm[indx+4] = ny12; + norm[indx+5] = nz12; + norm[indx+6] = nx34; + norm[indx+7] = ny34; + norm[indx+8] = nz34; + indx+=9; + } + + if (reduce!==2) { + norm[indx] = nx12; + norm[indx+1] = ny12; + norm[indx+2] = nz12; + norm[indx+3] = nx34; + norm[indx+4] = ny34; + norm[indx+5] = nz34; + norm[indx+6] = nx34; + norm[indx+7] = ny34; + norm[indx+8] = nz34; + indx+=9; + } + }; + + GEO.GeometryCreator.prototype.Create = function() { + if (this.nfaces !== this.indx/9) + console.error('Mismatch with created ' + this.nfaces + ' and filled ' + this.indx/9 + ' number of faces'); + + var geometry = new THREE.BufferGeometry(); + geometry.addAttribute( 'position', new THREE.BufferAttribute( this.pos, 3 ) ); + geometry.addAttribute( 'normal', new THREE.BufferAttribute( this.norm, 3 ) ); + return geometry; + }; + + // ================================================================================ + + // same methods as GeometryCreator, but with different implementation + + GEO.PolygonsCreator = function() { + this.polygons = []; + }; + + GEO.PolygonsCreator.prototype.StartPolygon = function(normal) { + this.multi = 1; + this.mnormal = normal; + }; + + GEO.PolygonsCreator.prototype.StopPolygon = function() { + if (!this.multi) return; + this.multi = 0; + console.error('Polygon should be already closed at this moment'); + }; + + GEO.PolygonsCreator.prototype.AddFace3 = function(x1,y1,z1, + x2,y2,z2, + x3,y3,z3) { + this.AddFace4(x1,y1,z1,x2,y2,z2,x3,y3,z3,x3,y3,z3,2); + }; + + + GEO.PolygonsCreator.prototype.AddFace4 = function(x1,y1,z1, + x2,y2,z2, + x3,y3,z3, + x4,y4,z4, + reduce) { + // from four vertices one normally creates two faces (1,2,3) and (1,3,4) + // if (reduce==1), first face is reduced + // if (reduce==2), second face is reduced + + if (reduce === undefined) reduce = 0; + + this.v1 = new ThreeBSP.Vertex( x1, y1, z1, 0, 0, 0 ); + this.v2 = (reduce===1) ? null : new ThreeBSP.Vertex( x2, y2, z2, 0, 0, 0 ); + this.v3 = new ThreeBSP.Vertex( x3, y3, z3, 0, 0, 0 ); + this.v4 = (reduce===2) ? null : new ThreeBSP.Vertex( x4, y4, z4, 0, 0, 0 ); + + this.reduce = reduce; + + if (this.multi) { + + if (reduce!==2) console.error('polygon not supported for not-reduced faces'); + + var polygon; + + if (this.multi++ === 1) { + polygon = new ThreeBSP.Polygon; + + polygon.vertices.push(this.mnormal ? this.v2 : this.v3); + this.polygons.push(polygon); + } else { + polygon = this.polygons[this.polygons.length-1]; + // check that last vertice equals to v2 + var last = this.mnormal ? polygon.vertices[polygon.vertices.length-1] : polygon.vertices[0], + comp = this.mnormal ? this.v2 : this.v3; + + if (comp.diff(last) > 1e-12) + console.error('vertex missmatch when building polygon'); + } + + var first = this.mnormal ? polygon.vertices[0] : polygon.vertices[polygon.vertices.length-1], + next = this.mnormal ? this.v3 : this.v2; + + if (next.diff(first) < 1e-12) { + //console.log('polygon closed!!!', polygon.vertices.length); + this.multi = 0; + } else + if (this.mnormal) { + polygon.vertices.push(this.v3); + } else { + polygon.vertices.unshift(this.v2); + } + + return; + + } + + var polygon = new ThreeBSP.Polygon; + + switch (reduce) { + case 0: polygon.vertices.push(this.v1, this.v2, this.v3, this.v4); break; + case 1: polygon.vertices.push(this.v1, this.v3, this.v4); break; + case 2: polygon.vertices.push(this.v1, this.v2, this.v3); break; + } + + this.polygons.push(polygon); + }; + + GEO.PolygonsCreator.prototype.SetNormal4 = function(nx1,ny1,nz1, + nx2,ny2,nz2, + nx3,ny3,nz3, + nx4,ny4,nz4, + reduce) { + this.v1.setnormal(nx1,ny1,nz1); + if (this.v2) this.v2.setnormal(nx2,ny2,nz2); + this.v3.setnormal(nx3,ny3,nz3); + if (this.v4) this.v4.setnormal(nx4,ny4,nz4); + }; + + GEO.PolygonsCreator.prototype.SetNormal_12_34 = function(nx12,ny12,nz12,nx34,ny34,nz34,reduce) { + // special shortcut, when same normals can be applied for 1-2 point and 3-4 point + this.v1.setnormal(nx12,ny12,nz12); + if (this.v2) this.v2.setnormal(nx12,ny12,nz12); + this.v3.setnormal(nx34,ny34,nz34); + if (this.v4) this.v4.setnormal(nx34,ny34,nz34); + }; + + GEO.PolygonsCreator.prototype.CalcNormal = function() { + + if (!this.cb) { + this.pA = new THREE.Vector3(); + this.pB = new THREE.Vector3(); + this.pC = new THREE.Vector3(); + this.cb = new THREE.Vector3(); + this.ab = new THREE.Vector3(); + } + + this.pA.set( this.v1.x, this.v1.y, this.v1.z); + + if (this.reduce!==1) { + this.pB.set( this.v2.x, this.v2.y, this.v2.z); + this.pC.set( this.v3.x, this.v3.y, this.v3.z); + } else { + this.pB.set( this.v3.x, this.v3.y, this.v3.z); + this.pC.set( this.v4.x, this.v4.y, this.v4.z); + } + + this.cb.subVectors( this.pC, this.pB ); + this.ab.subVectors( this.pA, this.pB ); + this.cb.cross( this.ab ); + + this.SetNormal(this.cb.x, this.cb.y, this.cb.z); + }; + + + GEO.PolygonsCreator.prototype.SetNormal = function(nx,ny,nz) { + this.v1.setnormal(nx,ny,nz); + if (this.v2) this.v2.setnormal(nx,ny,nz); + this.v3.setnormal(nx,ny,nz); + if (this.v4) this.v4.setnormal(nx,ny,nz); + }; + + GEO.PolygonsCreator.prototype.RecalcZ = function(func) { + this.v1.z = func(this.v1.x, this.v1.y, this.v1.z); + if (this.v2) this.v2.z = func(this.v2.x, this.v2.y, this.v2.z); + this.v3.z = func(this.v3.x, this.v3.y, this.v3.z); + if (this.v4) this.v4.z = func(this.v4.x, this.v4.y, this.v4.z); + }; + + GEO.PolygonsCreator.prototype.Create = function() { + return { polygons: this.polygons }; + }; + + // ================= all functions to create geometry =================================== + + /** @memberOf GEO */ + GEO.createCubeBuffer = function(shape, faces_limit) { + + if (faces_limit < 0) return 12; + + var dx = shape.fDX, dy = shape.fDY, dz = shape.fDZ; + + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(12); + + creator.AddFace4(dx,dy,dz, dx,-dy,dz, dx,-dy,-dz, dx,dy,-dz); creator.SetNormal(1,0,0); + + creator.AddFace4(-dx,dy,-dz, -dx,-dy,-dz, -dx,-dy,dz, -dx,dy,dz); creator.SetNormal(-1,0,0); + + creator.AddFace4(-dx,dy,-dz, -dx,dy,dz, dx,dy,dz, dx,dy,-dz); creator.SetNormal(0,1,0); + + creator.AddFace4(-dx,-dy,dz, -dx,-dy,-dz, dx,-dy,-dz, dx,-dy,dz); creator.SetNormal(0,-1,0); + + creator.AddFace4(-dx,dy,dz, -dx,-dy,dz, dx,-dy,dz, dx,dy,dz); creator.SetNormal(0,0,1); + + creator.AddFace4(dx,dy,-dz, dx,-dy,-dz, -dx,-dy,-dz, -dx,dy,-dz); creator.SetNormal(0,0,-1); + + return creator.Create(); + }; + + /** @memberOf GEO */ + GEO.create8edgesBuffer = function( v, faces_limit ) { + + var indicies = [ 4,7,6,5, 0,3,7,4, 4,5,1,0, 6,2,1,5, 7,3,2,6, 1,2,3,0 ]; + + var creator = (faces_limit > 0) ? new GEO.PolygonsCreator : new GEO.GeometryCreator(12); + + for (var n=0;n=0) || (map.indexOf(id2)>=0) || (map.indexOf(id3)>=0)) { + indicies[k] = indicies[k+1] = indicies[k+2] = -1; + } else { + map.push(id1,id2,id3); + numfaces++; + } + } + + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); + + // var creator = new GEO.GeometryCreator(numfaces); + + for (var n=0; n < indicies.length; n+=6) { + var i1 = indicies[n] * 3, + i2 = indicies[n+1] * 3, + i3 = indicies[n+2] * 3, + i4 = indicies[n+3] * 3, + i5 = indicies[n+4] * 3, + i6 = indicies[n+5] * 3, + norm = null; + + if ((i1>=0) && (i4>=0) && faces_limit) { + // try to identify two faces with same normal - very useful if one can create face4 + if (n===0) norm = new THREE.Vector3(0,0,1); else + if (n===30) norm = new THREE.Vector3(0,0,-1); else { + var norm1 = GEO.GetNormal(vertices[i1], vertices[i1+1], vertices[i1+2], + vertices[i2], vertices[i2+1], vertices[i2+2], + vertices[i3], vertices[i3+1], vertices[i3+2]); + + norm1.normalize(); + + var norm2 = GEO.GetNormal(vertices[i4], vertices[i4+1], vertices[i4+2], + vertices[i5], vertices[i5+1], vertices[i5+2], + vertices[i6], vertices[i6+1], vertices[i6+2]); + + norm2.normalize(); + + if (norm1.distanceToSquared(norm2) < 1e-12) norm = norm1; + } + } + + if (norm !== null) { + creator.AddFace4(vertices[i1], vertices[i1+1], vertices[i1+2], + vertices[i2], vertices[i2+1], vertices[i2+2], + vertices[i3], vertices[i3+1], vertices[i3+2], + vertices[i5], vertices[i5+1], vertices[i5+2]); + creator.SetNormal(norm.x, norm.y, norm.z); + } else { + if (i1>=0) { + creator.AddFace3(vertices[i1], vertices[i1+1], vertices[i1+2], + vertices[i2], vertices[i2+1], vertices[i2+2], + vertices[i3], vertices[i3+1], vertices[i3+2]); + creator.CalcNormal(); + } + if (i4>=0) { + creator.AddFace3(vertices[i4], vertices[i4+1], vertices[i4+2], + vertices[i5], vertices[i5+1], vertices[i5+2], + vertices[i6], vertices[i6+1], vertices[i6+2]); + creator.CalcNormal(); + } + } + } + + return creator.Create(); + }; + + /** @memberOf GEO */ + GEO.createSphereBuffer = function( shape, faces_limit ) { + var radius = [shape.fRmax, shape.fRmin], + phiStart = shape.fPhi1, + phiLength = shape.fPhi2 - shape.fPhi1, + thetaStart = shape.fTheta1, + thetaLength = shape.fTheta2 - shape.fTheta1, + widthSegments = shape.fNseg, + heightSegments = shape.fNz, + noInside = (radius[1] <= 0); + + // widthSegments = 20; heightSegments = 10; + // phiStart = 0; phiLength = 360; thetaStart = 0; thetaLength = 180; + + if (faces_limit > 0) { + var fact = (noInside ? 2 : 4) * widthSegments * heightSegments / faces_limit; + + if (fact > 1.) { + widthSegments = Math.max(4, Math.floor(widthSegments/Math.sqrt(fact))); + heightSegments = Math.max(4, Math.floor(heightSegments/Math.sqrt(fact))); + } + } + + var numoutside = widthSegments * heightSegments * 2, + numtop = widthSegments * 2, + numbottom = widthSegments * 2, + numcut = phiLength === 360 ? 0 : heightSegments * (noInside ? 2 : 4), + epsilon = 1e-10; + + if (noInside) numbottom = numtop = widthSegments; + + if (faces_limit < 0) return numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut; + + var _sinp = new Float32Array(widthSegments+1), + _cosp = new Float32Array(widthSegments+1), + _sint = new Float32Array(heightSegments+1), + _cost = new Float32Array(heightSegments+1); + + for (var n=0;n<=heightSegments;++n) { + var theta = (thetaStart + thetaLength/heightSegments*n)*Math.PI/180; + _sint[n] = Math.sin(theta); + _cost[n] = Math.cos(theta); + } + + for (var n=0;n<=widthSegments;++n) { + var phi = (phiStart + phiLength/widthSegments*n)*Math.PI/180; + _sinp[n] = Math.sin(phi); + _cosp[n] = Math.cos(phi); + } + + if (Math.abs(_sint[0]) <= epsilon) { numoutside -= widthSegments; numtop = 0; } + if (Math.abs(_sint[heightSegments]) <= epsilon) { numoutside -= widthSegments; numbottom = 0; } + + var numfaces = numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut; + + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); + + // var creator = new GEO.GeometryCreator(numfaces); + + for (var side=0;side<2;++side) { + if ((side===1) && noInside) break; + + var r = radius[side], + s = (side===0) ? 1 : -1, + d1 = 1 - side, d2 = 1 - d1; + + // use direct algorithm for the sphere - here normals and position can be calculated directly + for (var k=0;k= epsilon) { + var ss = _sint[side], cc = _cost[side], + d1 = (side===0) ? 0 : 1, d2 = 1 - d1; + for (var n=0;n 0) || (innerR[1] > 0), + thetaStart = 0, thetaLength = 360; + + if ((shape._typename == "TGeoConeSeg") || (shape._typename == "TGeoTubeSeg") || (shape._typename == "TGeoCtub")) { + thetaStart = shape.fPhi1; + thetaLength = shape.fPhi2 - shape.fPhi1; + } + + var radiusSegments = Math.max(4, Math.round(thetaLength/GEO.GradPerSegm)); + + // external surface + var numfaces = radiusSegments * (((outerR[0] <= 0) || (outerR[1] <= 0)) ? 1 : 2); + + // internal surface + if (hasrmin) + numfaces += radiusSegments * (((innerR[0] <= 0) || (innerR[1] <= 0)) ? 1 : 2); + + // upper cap + if (outerR[0] > 0) numfaces += radiusSegments * ((innerR[0]>0) ? 2 : 1); + // bottom cup + if (outerR[1] > 0) numfaces += radiusSegments * ((innerR[1]>0) ? 2 : 1); + + if (thetaLength < 360) + numfaces += ((outerR[0] > innerR[0]) ? 2 : 0) + ((outerR[1] > innerR[1]) ? 2 : 0); + + if (faces_limit < 0) return numfaces; + + var phi0 = thetaStart*Math.PI/180, + dphi = thetaLength/radiusSegments*Math.PI/180, + _sin = new Float32Array(radiusSegments+1), + _cos = new Float32Array(radiusSegments+1); + + for (var seg=0; seg<=radiusSegments; ++seg) { + _cos[seg] = Math.cos(phi0+seg*dphi); + _sin[seg] = Math.sin(phi0+seg*dphi); + } + + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); + + // var creator = new GEO.GeometryCreator(numfaces); + + var calcZ; + + if (shape._typename == "TGeoCtub") + calcZ = function(x,y,z) { + var arr = (z<0) ? shape.fNlow : shape.fNhigh; + return ((z<0) ? -shape.fDz : shape.fDz) - (x*arr[0] + y*arr[1]) / arr[2]; + }; + + // create outer/inner tube + for (var side = 0; side<2; ++side) { + if ((side === 1) && !hasrmin) break; + + var R = (side === 0) ? outerR : innerR, + d1 = side, d2 = 1 - side, nxy = 1., nz = 0; + + if (R[0] !== R[1]) { + var angle = Math.atan2((R[1]-R[0]), 2*shape.fDZ); + nxy = Math.cos(angle); + nz = Math.sin(angle); + } + + if (side === 1) { + nxy *= -1; + nz *= -1; + } + var reduce = 0; + if (R[0] <= 0) reduce = 2; else + if (R[1] <= 0) reduce = 1; + + for (var seg=0;seg 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0)); + + if (faces_limit < 0) return numfaces; + + if ((faces_limit > 0) && (numfaces > faces_limit)) { + radialSegments = Math.floor(radialSegments/Math.sqrt(numfaces / faces_limit)); + tubularSegments = Math.floor(tubularSegments/Math.sqrt(numfaces / faces_limit)); + numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0)); + } + + var _sinr = new Float32Array(radialSegments+1), + _cosr = new Float32Array(radialSegments+1), + _sint = new Float32Array(tubularSegments+1), + _cost = new Float32Array(tubularSegments+1); + + for (var n=0;n<=radialSegments;++n) { + _sinr[n] = Math.sin(n/radialSegments*2*Math.PI); + _cosr[n] = Math.cos(n/radialSegments*2*Math.PI); + } + + for (var t=0;t<=tubularSegments;++t) { + var angle = (shape.fPhi1 + shape.fDphi*t/tubularSegments)/180*Math.PI; + _sint[t] = Math.sin(angle); + _cost[t] = Math.cos(angle); + } + + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); + + // use vectors for normals calculation + var p1 = new THREE.Vector3(), p2 = new THREE.Vector3(), p3 = new THREE.Vector3(), p4 = new THREE.Vector3(), + n1 = new THREE.Vector3(), n2 = new THREE.Vector3(), n3 = new THREE.Vector3(), n4 = new THREE.Vector3(), + center1 = new THREE.Vector3(), center2 = new THREE.Vector3(); + + for (var side=0;side<2;++side) { + if ((side > 0) && (shape.fRmin <= 0)) break; + var tube = (side > 0) ? shape.fRmin : shape.fRmax, + d1 = 1 - side, d2 = 1 - d1, ns = side>0 ? -1 : 1; + + for (var t=0;t0) ? 0 : 1, d2 = 1 - d1, + skip = (shape.fRmin) > 0 ? 0 : 1, + nsign = t>0 ? 1 : -1; + for (var n=0;n 0) hasrmin = true; + + // return very rough estimation, number of faces may be much less + if (faces_limit < 0) return (hasrmin ? 4 : 2) * radiusSegments * (shape.fNz-1); + + // coordinate of point on cut edge (x,z) + var pnts = (thetaLength === 360) ? null : []; + + // first analyse levels - if we need to create all of them + for (var side = 0; side < 2; ++side) { + var rside = (side === 0) ? 'fRmax' : 'fRmin'; + + for (var layer=0; layer < shape.fNz; ++layer) { + + // first create points for the layer + var layerz = shape.fZ[layer], rad = shape[rside][layer]; + + usage[layer*2+side] = 0; + + if ((layer > 0) && (layer < shape.fNz-1)) + if (((shape.fZ[layer-1] === layerz) && (shape[rside][layer-1] === rad)) || + ((shape[rside][layer+1] === rad) && (shape[rside][layer-1] === rad))) { + + // same Z and R as before - ignore + // or same R before and after + + continue; + } + + if ((layer>0) && ((side === 0) || hasrmin)) { + usage[layer*2+side] = 1; + numusedlayers++; + } + + if (pnts !== null) { + if (side === 0) { + pnts.push(new THREE.Vector2(rad, layerz)); + } else + if (rad < shape.fRmax[layer]) { + pnts.unshift(new THREE.Vector2(rad, layerz)); + } + } + } + } + + var numfaces = numusedlayers*radiusSegments*2; + if (shape.fRmin[0] !== shape.fRmax[0]) numfaces += radiusSegments * (hasrmin ? 2 : 1); + if (shape.fRmin[shape.fNz-1] !== shape.fRmax[shape.fNz-1]) numfaces += radiusSegments * (hasrmin ? 2 : 1); + + var cut_faces = null; + + if (pnts!==null) { + if (pnts.length === shape.fNz * 2) { + // special case - all layers are there, create faces ourself + cut_faces = []; + for (var layer = shape.fNz-1; layer>0; --layer) { + if (shape.fZ[layer] === shape.fZ[layer-1]) continue; + var right = 2*shape.fNz - 1 - layer; + cut_faces.push([right, layer - 1, layer]); + cut_faces.push([right, right + 1, layer-1]); + } + + } else { + // let three.js calculate our faces + // console.log('triangulate polygon ' + shape.fShapeId); + cut_faces = THREE.ShapeUtils.triangulateShape(pnts, []); + } + numfaces += cut_faces.length*2; + } + + var phi0 = thetaStart*Math.PI/180, dphi = thetaLength/radiusSegments*Math.PI/180; + + // calculate all sin/cos tables in advance + var _sin = new Float32Array(radiusSegments+1), + _cos = new Float32Array(radiusSegments+1); + for (var seg=0;seg<=radiusSegments;++seg) { + _cos[seg] = Math.cos(phi0+seg*dphi); + _sin[seg] = Math.sin(phi0+seg*dphi); + } + + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); + + // add sides + for (var side = 0; side < 2; ++side) { + var rside = (side === 0) ? 'fRmax' : 'fRmin', + z1 = shape.fZ[0], r1 = shape[rside][0], + d1 = 1 - side, d2 = side; + + for (var layer=0; layer < shape.fNz; ++layer) { + + if (usage[layer*2+side] === 0) continue; + + var z2 = shape.fZ[layer], r2 = shape[rside][layer], + nxy = 1, nz = 0; + + if ((r2 !== r1)) { + var angle = Math.atan2((r2-r1), (z2-z1)); + nxy = Math.cos(angle); + nz = Math.sin(angle); + } + + if (side>0) { nxy*=-1; nz*=-1; } + + for (var seg=0;seg < radiusSegments;++seg) { + creator.AddFace4(r1 * _cos[seg+d1], r1 * _sin[seg+d1], z1, + r2 * _cos[seg+d1], r2 * _sin[seg+d1], z2, + r2 * _cos[seg+d2], r2 * _sin[seg+d2], z2, + r1 * _cos[seg+d2], r1 * _sin[seg+d2], z1); + creator.SetNormal_12_34(nxy*_cos[seg+d1], nxy*_sin[seg+d1], nz, nxy*_cos[seg+d2], nxy*_sin[seg+d2], nz); + } + + z1 = z2; r1 = r2; + } + } + + // add top/bottom + for (var layer=0; layer < shape.fNz; layer += (shape.fNz-1)) { + + var rmin = shape.fRmin[layer], rmax = shape.fRmax[layer]; + + if (rmin === rmax) continue; + + var layerz = shape.fZ[layer], + d1 = (layer===0) ? 1 : 0, d2 = 1 - d1, + normalz = (layer===0) ? -1: 1; + + if (!hasrmin && !cut_faces) creator.StartPolygon(layer>0); + + for (var seg=0;seg < radiusSegments;++seg) { + creator.AddFace4(rmin * _cos[seg+d1], rmin * _sin[seg+d1], layerz, + rmax * _cos[seg+d1], rmax * _sin[seg+d1], layerz, + rmax * _cos[seg+d2], rmax * _sin[seg+d2], layerz, + rmin * _cos[seg+d2], rmin * _sin[seg+d2], layerz, + hasrmin ? 0 : 2); + creator.SetNormal(0, 0, normalz); + } + + creator.StopPolygon(); + } + + if (cut_faces) + for (var seg = 0; seg <= radiusSegments; seg += radiusSegments) { + var d1 = (seg === 0) ? 1 : 2, d2 = 3 - d1; + for (var n=0;n 0) { + var fact = 2*radiusSegments*(heightSegments+1) / faces_limit; + if (fact > 1.) { + radiusSegments = Math.max(5, Math.floor(radiusSegments/Math.sqrt(fact))); + heightSegments = Math.max(5, Math.floor(heightSegments/Math.sqrt(fact))); + } + } + + var zmin = -shape.fDZ, zmax = shape.fDZ, rmin = shape.fRlo, rmax = shape.fRhi; + + // if no radius at -z, find intersection + if (shape.fA >= 0) { + if (shape.fB > zmin) zmin = shape.fB; + } else { + if (shape.fB < zmax) zmax = shape.fB; + } + + var ttmin = Math.atan2(zmin, rmin), ttmax = Math.atan2(zmax, rmax); + + var numfaces = (heightSegments+1)*radiusSegments*2; + if (rmin===0) numfaces -= radiusSegments*2; // complete layer + if (rmax===0) numfaces -= radiusSegments*2; // complete layer + + if (faces_limit < 0) return numfaces; + + // calculate all sin/cos tables in advance + var _sin = new Float32Array(radiusSegments+1), + _cos = new Float32Array(radiusSegments+1); + for (var seg=0;seg<=radiusSegments;++seg) { + _cos[seg] = Math.cos(seg/radiusSegments*2*Math.PI); + _sin[seg] = Math.sin(seg/radiusSegments*2*Math.PI); + } + + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); + + var lastz = zmin, lastr = 0, lastnxy = 0, lastnz = -1; + + for (var layer = 0; layer <= heightSegments + 1; ++layer) { + + var layerz = 0, radius = 0, nxy = 0, nz = -1; + + if ((layer === 0) && (rmin===0)) continue; + + if ((layer === heightSegments + 1) && (lastr === 0)) break; + + switch (layer) { + case 0: layerz = zmin; radius = rmin; break; + case heightSegments: layerz = zmax; radius = rmax; break; + case heightSegments + 1: layerz = zmax; radius = 0; break; + default: { + var tt = Math.tan(ttmin + (ttmax-ttmin) * layer / heightSegments); + var delta = tt*tt - 4*shape.fA*shape.fB; // should be always positive (a*b<0) + radius = 0.5*(tt+Math.sqrt(delta))/shape.fA; + if (radius < 1e-6) radius = 0; + layerz = radius*tt; + } + } + + nxy = shape.fA * radius; + nz = (shape.fA > 0) ? -1 : 1; + + var skip = 0; + if (lastr === 0) skip = 1; else + if (radius === 0) skip = 2; + + for (var seg=0; seg 0) ? 4 : 2); + + if (faces_limit < 0) return numfaces; + + if ((faces_limit > 0) && (faces_limit > numfaces)) { + radiusSegments = Math.max(4, Math.floor(radiusSegments/Math.sqrt(numfaces/faces_limit))); + heightSegments = Math.max(4, Math.floor(heightSegments/Math.sqrt(numfaces/faces_limit))); + numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2); + } + + // calculate all sin/cos tables in advance + var _sin = new Float32Array(radiusSegments+1), _cos = new Float32Array(radiusSegments+1); + for (var seg=0;seg<=radiusSegments;++seg) { + _cos[seg] = Math.cos(seg/radiusSegments*2*Math.PI); + _sin[seg] = Math.sin(seg/radiusSegments*2*Math.PI); + } + + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); + + // in-out side + for (var side=0;side<2;++side) { + if ((side > 0) && (shape.fRmin <= 0)) break; + + var r0 = (side > 0) ? shape.fRmin : shape.fRmax, + tsq = (side > 0) ? shape.fTinsq : shape.fToutsq, + d1 = 1- side, d2 = 1 - d1; + + // vertical layers + for (var layer=0;layer 0) ? Math.sqrt(shape.fRmin*shape.fRmin + shape.fTinsq*z*z) : 0, + skip = (shape.fRmin > 0) ? 0 : 1, + d1 = 1 - layer, d2 = 1 - d1; + for (var seg=0; seg GEO.CompLimit) { + GEO.warn("composite shape " + shape.fShapeId + " has " + cnt + " components, replace by most left"); + var matrix = new THREE.Matrix4(); + while (shape.fNode && shape.fNode.fLeft) { + var m1 = GEO.createMatrix(shape.fNode.fLeftMat); + if (m1) matrix.multiply(m1); + shape = shape.fNode.fLeft; + } + var res = GEO.createGeometry(shape, faces_limit); + if (res && (faces_limit===0)) res.applyMatrix(matrix); + return res; + } + } + */ + + if (faces_limit < 0) + return GEO.createGeometry(shape.fNode.fLeft, -10) + + GEO.createGeometry(shape.fNode.fRight, -10); + + var geom1, geom2, bsp1, bsp2, return_bsp = false, + matrix1 = GEO.createMatrix(shape.fNode.fLeftMat), + matrix2 = GEO.createMatrix(shape.fNode.fRightMat); + + // seems to be, IE has smaller stack for functions calls and ThreeCSG fails with large shapes + if (faces_limit === 0) faces_limit = (JSROOT.browser && JSROOT.browser.isIE) ? 2000 : 4000; + else return_bsp = true; + + if (matrix1 && (matrix1.determinant() < -0.9)) + GEO.warn('Axis reflection in left composite shape - not supported'); + + if (matrix2 && (matrix2.determinant() < -0.9)) + GEO.warn('Axis reflections in right composite shape - not supported'); + + geom1 = GEO.createGeometry(shape.fNode.fLeft, faces_limit); + if (!geom1) return null; + + var n1 = GEO.numGeometryFaces(geom1), n2 = 0; + if (geom1._exceed_limit) n1 += faces_limit; + + if (n1 < faces_limit) { + geom2 = GEO.createGeometry(shape.fNode.fRight, faces_limit); + n2 = GEO.numGeometryFaces(geom2); + } + + if ((n1 + n2 >= faces_limit) || !geom2) { + if (geom1.polygons) { + geom1 = ThreeBSP.CreateBufferGeometry(geom1.polygons); + n1 = GEO.numGeometryFaces(geom1); + } + if (matrix1) geom1.applyMatrix(matrix1); + // if (!geom1._exceed_limit) console.log('reach faces limit', faces_limit, 'got', n1, n2); + geom1._exceed_limit = true; + return geom1; + } + + bsp1 = new ThreeBSP.Geometry(geom1, matrix1, GEO.CompressComp ? 0 : undefined); + + bsp2 = new ThreeBSP.Geometry(geom2, matrix2, bsp1.maxid); + + // take over maxid from both geometries + bsp1.maxid = bsp2.maxid; + + switch(shape.fNode._typename) { + case 'TGeoIntersection': bsp1.direct_intersect(bsp2); break; // "*" + case 'TGeoUnion': bsp1.direct_union(bsp2); break; // "+" + case 'TGeoSubtraction': bsp1.direct_subtract(bsp2); break; // "/" + default: + GEO.warn('unsupported bool operation ' + shape.fNode._typename + ', use first geom'); + } + + if (GEO.numGeometryFaces(bsp1) === 0) { + GEO.warn('Zero faces in comp shape' + + ' left: ' + shape.fNode.fLeft._typename + ' ' + GEO.numGeometryFaces(geom1) + ' faces' + + ' right: ' + shape.fNode.fRight._typename + ' ' + GEO.numGeometryFaces(geom2) + ' faces' + + ' use first'); + bsp1 = new ThreeBSP.Geometry(geom1, matrix1); + } + + return return_bsp ? { polygons: bsp1.toPolygons() } : bsp1.toBufferGeometry(); + }; + + /** @memberOf GEO */ + GEO.projectGeometry = function(geom, matrix, projection, position, flippedMesh) { + + if (!geom.boundingBox) geom.computeBoundingBox(); + + var box = geom.boundingBox.clone(); + + box.applyMatrix4(matrix); + + if (!position) position = 0; + + if (((box.min[projection]>=position) && (box.max[projection]>=position)) || + ((box.min[projection]<=position) && (box.max[projection]<=position))) { + return null; // not interesting + } + + var bsp1 = new ThreeBSP.Geometry(geom, matrix, 0, flippedMesh), + sizex = 2*Math.max(Math.abs(box.min.x), Math.abs(box.max.x)), + sizey = 2*Math.max(Math.abs(box.min.y), Math.abs(box.max.y)), + sizez = 2*Math.max(Math.abs(box.min.z), Math.abs(box.max.z)), + size = 10000; + + switch (projection) { + case "x": size = Math.max(sizey,sizez); break; + case "y": size = Math.max(sizex,sizez); break; + case "z": size = Math.max(sizex,sizey); break; + } + + var bsp2 = ThreeBSP.CreateNormal(projection, position, size); + + bsp1.cut_from_plane(bsp2); + + return bsp2.toBufferGeometry(); + }; + + /** + * Creates geometry model for the provided shape + * @memberOf GEO + * + * If @par limit === 0 (or undefined) returns THREE.BufferGeometry + * If @par limit < 0 just returns estimated number of faces + * If @par limit > 0 return list of ThreeBSP polygons (used only for composite shapes) + * */ + GEO.createGeometry = function( shape, limit ) { + if (limit === undefined) limit = 0; + + try { + switch (shape._typename) { + case "TGeoBBox": return GEO.createCubeBuffer( shape, limit ); + case "TGeoPara": return GEO.createParaBuffer( shape, limit ); + case "TGeoTrd1": + case "TGeoTrd2": return GEO.createTrapezoidBuffer( shape, limit ); + case "TGeoArb8": + case "TGeoTrap": + case "TGeoGtra": return GEO.createArb8Buffer( shape, limit ); + case "TGeoSphere": return GEO.createSphereBuffer( shape , limit ); + case "TGeoCone": + case "TGeoConeSeg": + case "TGeoTube": + case "TGeoTubeSeg": + case "TGeoCtub": return GEO.createTubeBuffer( shape, limit ); + case "TGeoEltu": return GEO.createEltuBuffer( shape, limit ); + case "TGeoTorus": return GEO.createTorusBuffer( shape, limit ); + case "TGeoPcon": + case "TGeoPgon": return GEO.createPolygonBuffer( shape, limit ); + case "TGeoXtru": return GEO.createXtruBuffer( shape, limit ); + case "TGeoParaboloid": return GEO.createParaboloidBuffer( shape, limit ); + case "TGeoHype": return GEO.createHypeBuffer( shape, limit ); + case "TGeoCompositeShape": return GEO.createComposite( shape, limit ); + case "TGeoShapeAssembly": break; + case "TGeoScaledShape": { + var res = GEO.createGeometry(shape.fShape, limit); + if (shape.fScale && (limit>=0) && (typeof res === 'object') && (typeof res.scale === 'function')) + res.scale(shape.fScale.fScale[0],shape.fScale.fScale[1],shape.fScale.fScale[2]); + return res; + } + default: GEO.warn('unsupported shape type ' + shape._typename); + } + } catch(e) { + var place = ""; + if (e.stack !== undefined) { + place = e.stack.split("\n")[0]; + if (place.indexOf(e.message) >= 0) place = e.stack.split("\n")[1]; + else place = " at: " + place; + } + GEO.warn(shape._typename + " err: " + e.message + place); + } + + return limit < 0 ? 0 : null; + }; + + /** Provides info about geo object, used for tooltip info */ + GEO.provideInfo = function(obj) { + var info = [], shape = null; + + if (obj.fVolume !== undefined) shape = obj.fVolume.fShape; else + if (obj.fShape !== undefined) shape = obj.fShape; else + if ((obj.fShapeBits !== undefined) && (obj.fShapeId !== undefined)) shape = obj; + + if (!shape) { + info.push(obj._typename); + return info; + } + + var sz = Math.max(shape.fDX, shape.fDY, shape.fDZ); + var useexp = (sz>1e7) || (sz<1e-7); + + function conv(v) { + if (v===undefined) return "???"; + if ((v==Math.round(v) && v<1e7)) return Math.round(v); + return useexp ? v.toExponential(4) : v.toPrecision(7); + } + + info.push(shape._typename); + + info.push("DX="+conv(shape.fDX) + " DY="+conv(shape.fDY) + " DZ="+conv(shape.fDZ)); + + switch (shape._typename) { + case "TGeoBBox": break; + case "TGeoPara": info.push("Alpha=" + shape.fAlpha + " Phi=" + shape.fPhi + " Theta=" + shape.fTheta); break; + case "TGeoTrd2": info.push("Dy1=" + conv(shape.fDy1) + " Dy2=" + conv(shape.fDy1)); + case "TGeoTrd1": info.push("Dx1=" + conv(shape.fDx1) + " Dx2=" + conv(shape.fDx1)); break; + case "TGeoArb8": break; + case "TGeoTrap": break; + case "TGeoGtra": break; + case "TGeoSphere": + info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); + info.push("Phi1=" + shape.fPhi1 + " Phi2=" + shape.fPhi2); + info.push("Theta1=" + shape.fTheta1 + " Theta2=" + shape.fTheta2); + break; + case "TGeoConeSeg": + info.push("Phi1=" + shape.fPhi1 + " Phi2=" + shape.fPhi2); + case "TGeoCone": + info.push("Rmin1=" + conv(shape.fRmin1) + " Rmax1=" + conv(shape.fRmax1)); + info.push("Rmin2=" + conv(shape.fRmin2) + " Rmax2=" + conv(shape.fRmax2)); + break; + case "TGeoCtub": + case "TGeoTubeSeg": + info.push("Phi1=" + shape.fPhi1 + " Phi2=" + shape.fPhi2); + case "TGeoEltu": + case "TGeoTube": + info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); + break; + case "TGeoTorus": + info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); + info.push("Phi1=" + shape.fPhi1 + " Dphi=" + shape.fDphi); + break; + case "TGeoPcon": + case "TGeoPgon": break; + case "TGeoXtru": break; + case "TGeoParaboloid": + info.push("Rlo=" + conv(shape.fRlo) + " Rhi=" + conv(shape.fRhi)); + info.push("A=" + conv(shape.fA) + " B=" + conv(shape.fB)); + break; + case "TGeoHype": + info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); + info.push("StIn=" + conv(shape.fStIn) + " StOut=" + conv(shape.fStOut)); + break; + case "TGeoCompositeShape": break; + case "TGeoShapeAssembly": break; + case "TGeoScaledShape": + info = GEO.provideInfo(shape.fShape); + if (shape.fScale) + info.unshift('Scale X=' + shape.fScale.fScale[0] + " Y=" + shape.fScale.fScale[1] + " Z=" + shape.fScale.fScale[2]); + break; + } + + return info; + }; + + /** @memberOf GEO */ + GEO.CreateProjectionMatrix = function(camera) { + var cameraProjectionMatrix = new THREE.Matrix4(); + + camera.updateMatrixWorld(); + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + cameraProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse); + + return cameraProjectionMatrix; + }; + + /** @memberOf GEO */ + GEO.CreateFrustum = function(source) { + if (!source) return null; + + if (source instanceof THREE.PerspectiveCamera) + source = GEO.CreateProjectionMatrix(source); + + var frustum = new THREE.Frustum(); + frustum.setFromMatrix(source); + + frustum.corners = new Float32Array([ + 1, 1, 1, + 1, 1, -1, + 1, -1, 1, + 1, -1, -1, + -1, 1, 1, + -1, 1, -1, + -1, -1, 1, + -1, -1, -1, + 0, 0, 0 // also check center of the shape + ]); + + frustum.test = new THREE.Vector3(0,0,0); + + frustum.CheckShape = function(matrix, shape) { + var pnt = this.test, len = this.corners.length, corners = this.corners, i; + + for (i = 0; i < len; i+=3) { + pnt.x = corners[i] * shape.fDX; + pnt.y = corners[i+1] * shape.fDY; + pnt.z = corners[i+2] * shape.fDZ; + if (this.containsPoint(pnt.applyMatrix4(matrix))) return true; + } + + return false; + }; + + frustum.CheckBox = function(box) { + var pnt = this.test, cnt = 0; + pnt.set(box.min.x, box.min.y, box.min.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.min.x, box.min.y, box.max.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.min.x, box.max.y, box.min.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.min.x, box.max.y, box.max.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.max.x, box.max.y, box.max.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.max.x, box.min.y, box.max.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.max.x, box.max.y, box.min.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.max.x, box.max.y, box.max.z); + if (this.containsPoint(pnt)) cnt++; + return cnt>5; // only if 6 edges and more are seen, we think that box is fully visible + }; + + return frustum; + }; + + /** @memberOf GEO */ + GEO.VisibleByCamera = function(camera, matrix, shape) { + var frustum = new THREE.Frustum(); + var cameraProjectionMatrix = new THREE.Matrix4(); + + camera.updateMatrixWorld(); + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + cameraProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse); + frustum.setFromMatrix( cameraProjectionMatrix ); + + var corners = [ + new THREE.Vector3( shape.fDX/2.0, shape.fDY/2.0, shape.fDZ/2.0 ), + new THREE.Vector3( shape.fDX/2.0, shape.fDY/2.0, -shape.fDZ/2.0 ), + new THREE.Vector3( shape.fDX/2.0, -shape.fDY/2.0, shape.fDZ/2.0 ), + new THREE.Vector3( shape.fDX/2.0, -shape.fDY/2.0, -shape.fDZ/2.0 ), + new THREE.Vector3( -shape.fDX/2.0, shape.fDY/2.0, shape.fDZ/2.0 ), + new THREE.Vector3( -shape.fDX/2.0, shape.fDY/2.0, -shape.fDZ/2.0 ), + new THREE.Vector3( -shape.fDX/2.0, -shape.fDY/2.0, shape.fDZ/2.0 ), + new THREE.Vector3( -shape.fDX/2.0, -shape.fDY/2.0, -shape.fDZ/2.0 ) + ]; + for (var i = 0; i < corners.length; i++) { + if (frustum.containsPoint(corners[i].applyMatrix4(matrix))) return true; + } + + return false; + }; + + /** @memberOf GEO */ + GEO.numGeometryFaces = function(geom) { + if (!geom) return 0; + + if (geom instanceof ThreeBSP.Geometry) + return geom.tree.numPolygons(); + + if (geom.type == 'BufferGeometry') { + var attr = geom.getAttribute('position'); + return attr ? attr.count / 3 : 0; + } + + // special array of polygons + if (geom && geom.polygons) return geom.polygons.length; + + return geom.faces.length; + }; + + /** @memberOf GEO */ + GEO.numGeometryVertices = function(geom) { + if (!geom) return 0; + + if (geom instanceof ThreeBSP.Geometry) + return geom.tree.numPolygons() * 3; + + if (geom.type == 'BufferGeometry') { + var attr = geom.getAttribute('position'); + return attr ? attr.count : 0; + } + + if (geom && geom.polygons) return geom.polygons.length * 4; + + return geom.vertices.length; + }; + + /** Compares two stacks. Returns length where stacks are the same + * @memberOf GEO + * @private */ + GEO.CompareStacks = function(stack1, stack2) { + if (!stack1 || !stack2) return 0; + if (stack1 === stack2) return stack1.length; + var len = Math.min(stack1.length, stack2.length); + for (var k=0;kthis.maxdepth) this.maxdepth = sublevel; + + var chlds = null; + if (kind===0) + chlds = (obj.fVolume && obj.fVolume.fNodes) ? obj.fVolume.fNodes.arr : null; + else + chlds = obj.fElements ? obj.fElements.arr : null; + + if (chlds !== null) { + GEO.CheckDuplicates(obj, chlds); + for (var i = 0; i < chlds.length; ++i) + this.CreateClones(chlds[i], sublevel+1, kind); + } + + if (sublevel > 1) return; + + this.nodes = []; + + var sortarr = []; + + // first create nodes objects + for (var n=0; n10) ? new Int32Array(this.last) : new Array(this.last) }; + if (factor) entry.factor = factor; // factor used to indicate importance of entry, will be build as first + for (var n=0;n 0) ? arg.matrices[arg.last-1] : new THREE.Matrix4(); + if (node.matrix) { + arg.matrices[arg.last] = arg.mpool[arg.last].fromArray(prnt.elements); + arg.matrices[arg.last].multiply(arg.mpool[arg.last+1].fromArray(node.matrix)); + } else { + arg.matrices[arg.last] = prnt; + } + } + + if (node.vis && (vislvl>=0)) { + if (!arg.func || arg.func(node)) res++; + } + + arg.counter++; + + if ((node.depth !== undefined) && (vislvl > node.depth)) vislvl = node.depth; + + //if (arg.last > arg.stack.length - 2) + // throw 'ScanVisible: stack capacity ' + arg.stack.length + ' is not enough'; + + if (node.chlds && (node.numvischld > 0)) { + var currid = arg.counter, numvischld = 0; + arg.last++; + for (var i = 0; i < node.chlds.length; ++i) { + arg.nodeid = node.chlds[i]; + arg.stack[arg.last] = i; // in the stack one store index of child, it is path in the hierarchy + numvischld += this.ScanVisible(arg, vislvl-1); + } + arg.last--; + res += numvischld; + if (numvischld === 0) { + node.numvischld = 0; + node.idshift = arg.counter - currid; + } + } else { + arg.counter += node.idshift; + } + + if (arg.last === 0) { + delete arg.last; + delete arg.stack; + delete arg.CopyStack; + delete arg.counter; + delete arg.matrices; + delete arg.mpool; + delete arg.getmatrix; + } + + return res; + }; + + /** Return node name with given id. + * Either original object or description is used + * @private */ + GEO.ClonedNodes.prototype.GetNodeName = function(nodeid) { + if (this.origin) { + var obj = this.origin[nodeid]; + return obj ? GEO.ObjectName(obj) : ""; + } + var node = this.nodes[nodeid]; + return node ? node.name : ""; + }; + + GEO.ClonedNodes.prototype.ResolveStack = function(stack, withmatrix) { + + var res = { id: 0, obj: null, node: this.nodes[0], name: this.name_prefix }; + + // if (!this.toplevel || (this.nodes.length === 1) || (res.node.kind === 1)) res.name = ""; + + if (withmatrix) { + res.matrix = new THREE.Matrix4(); + if (res.node.matrix) res.matrix.fromArray(res.node.matrix); + } + + if (this.origin) + res.obj = this.origin[0]; + + //if (!res.name) + // res.name = this.GetNodeName(0); + + if (stack) + for(var lvl=0;lvl 1) && (volume.fLineColor == 1)) + prop.fillcolor = JSROOT.Painter.root_colors[volume.fFillColor]; + else + if (volume.fLineColor >= 0) + prop.fillcolor = JSROOT.Painter.root_colors[volume.fLineColor]; + + if (volume.fMedium && volume.fMedium.fMaterial) { + var fillstyle = volume.fMedium.fMaterial.fFillStyle; + var transparency = (fillstyle < 3000 || fillstyle > 3100) ? 0 : fillstyle - 3000; + if (transparency > 0) + _opacity = (100.0 - transparency) / 100.0; + if (prop.fillcolor === undefined) + prop.fillcolor = JSROOT.Painter.root_colors[volume.fMedium.fMaterial.fFillColor]; + } + if (prop.fillcolor === undefined) + prop.fillcolor = "lightgrey"; + + prop.material = new THREE.MeshLambertMaterial( { transparent: _opacity < 1, + opacity: _opacity, wireframe: false, color: prop.fillcolor, + side: THREE.FrontSide /* THREE.DoubleSide */, vertexColors: THREE.NoColors /*THREE.VertexColors*/, + overdraw: 0., depthWrite: _opacity == 1 } ); + prop.material.inherentOpacity = _opacity; + + } + + return prop; + }; + + GEO.ClonedNodes.prototype.CreateObject3D = function(stack, toplevel, options) { + // create hierarchy of Object3D for given stack entry + // such hierarchy repeats hierarchy of TGeoNodes and set matrix for the objects drawing + // also set renderOrder, required to handle transparency + + var node = this.nodes[0], three_prnt = toplevel, draw_depth = 0, + force = (typeof options == 'object') || (options==='force'); + + for(var lvl=0; lvl<=stack.length; ++lvl) { + var nchld = (lvl > 0) ? stack[lvl-1] : 0; + // extract current node + if (lvl>0) node = this.nodes[node.chlds[nchld]]; + + var obj3d = undefined; + + if (three_prnt.children) + for (var i=0;i maxnumfaces) { + + var bignumfaces = maxnumfaces * (frustum ? 0.8 : 1.0), + bignumnodes = maxnumnodes * (frustum ? 0.8 : 1.0); + + // define minimal volume, which always to shown + var boundary = this.GetVolumeBoundary(arg.viscnt, bignumfaces, bignumnodes); + + minVol = boundary.min; + maxVol = boundary.max; + sortidcut = boundary.sortidcut; + + if (frustum) { + arg.domatrix = true; + arg.frustum = frustum; + arg.totalcam = 0; + arg.func = function(node) { + if (node.vol <= minVol) // only small volumes are interesting + if (this.frustum.CheckShape(this.getmatrix(), node)) { + this.viscnt[node.id]++; + this.totalcam += node.nfaces; + } + + return true; + }; + + for (var n=0;n maxnumfaces*0.2) + camVol = this.GetVolumeBoundary(arg.viscnt, maxnumfaces*0.2, maxnumnodes*0.2).min; + else + camVol = 0; + + camFact = maxVol / ((camVol>0) ? (camVol>0) : minVol); + + // console.log('Limit for camera ' + camVol + ' faces in camera view ' + arg.totalcam); + } + } + + arg.items = []; + + arg.func = function(node) { + if (node.sortid < sortidcut) { + this.items.push(this.CopyStack()); + } else + if ((camVol >= 0) && (node.vol > camVol)) + if (this.frustum.CheckShape(this.getmatrix(), node)) { + this.items.push(this.CopyStack(camFact)); + } + return true; + }; + + this.ScanVisible(arg); + + return { lst: arg.items, complete: minVol === 0 }; + }; + + GEO.ClonedNodes.prototype.MergeVisibles = function(current, prev) { + // merge list of drawn objects + // in current list we should mark if object already exists + // from previous list we should collect objects which are not there + + var indx2 = 0, del = []; + for (var indx1=0; (indx1entry.shape.factor)) + entry.shape.factor = entry.factor; + } + + // now sort shapes in volume decrease order + shapes.sort(function(a,b) { return b.vol*b.factor - a.vol*a.factor; }); + + // now set new shape ids according to the sorted order and delete temporary field + for (var n=0;n= limit) { + res.done = true; + } else + if ((created > 0.01*lst.length) && (timelimit!==undefined)) { + var tm2 = new Date().getTime(); + if (tm2-tm1 > timelimit) return res; + } + } + + res.done = true; + + return res; + }; + + GEO.ObjectName = function(obj) { + if (!obj || !obj.fName) return ""; + return obj.fName + (obj.$geo_suffix ? obj.$geo_suffix : ""); + }; + + GEO.CheckDuplicates = function(parent, chlds) { + if (parent) { + if (parent.$geo_checked) return; + parent.$geo_checked = true; + } + + var names = [], cnts = [], obj = null; + for (var k=0;k=0) { + var cnt = cnts[indx] || 1; + while(names.indexOf(chld.fName+"#"+cnt)>=0) ++cnt; + chld.$geo_suffix = "#" + cnt; + cnts[indx] = cnt+1; + } + } + names.push(GEO.ObjectName(chld)); + } + }; + + GEO.createFlippedMesh = function(parent, shape, material) { + // when transformation matrix includes one or several inversion of axis, + // one should inverse geometry object, otherwise THREE.js cannot correctly draw it + + var flip = new THREE.Vector3(1,1,-1); + + if (shape.geomZ === undefined) { + + if (shape.geom.type == 'BufferGeometry') { + + var pos = shape.geom.getAttribute('position').array, + norm = shape.geom.getAttribute('normal').array; + + var index = shape.geom.getIndex(); + + if (index) { + // we need to unfold all points to + var arr = index.array, + i0 = shape.geom.drawRange.start, + ilen = shape.geom.drawRange.count; + if (i0 + ilen > arr.length) ilen = arr.length - i0; + + var dpos = new Float32Array(ilen*3), dnorm = new Float32Array(ilen*3); + for (var ii = 0; ii < ilen; ++ii) { + var k = arr[i0 + ii]; + if ((k<0) || (k*3>=pos.length)) console.log('strange index', k*3, pos.length); + dpos[ii*3] = pos[k*3]; + dpos[ii*3+1] = pos[k*3+1]; + dpos[ii*3+2] = pos[k*3+2]; + dnorm[ii*3] = norm[k*3]; + dnorm[ii*3+1] = norm[k*3+1]; + dnorm[ii*3+2] = norm[k*3+2]; + } + + pos = dpos; norm = dnorm; + } + + var len = pos.length, n, shift = 0, + newpos = new Float32Array(len), + newnorm = new Float32Array(len); + + // we should swap second and third point in each face + for (n=0; n300) { + // too many of them, just set basic level and exit + for (var i=0;i=0;--i) { + var mesh = arr[i], + box3 = mesh.$jsroot_box3, + direction = box3.getCenter(); + + for(var ntry=0; ntry<2;++ntry) { + + direction.sub(origin).normalize(); + + raycast.set( origin, direction ); + + var intersects = raycast.intersectObjects(arr, false); // only plain array + + var unique = []; + + for (var k1=0;k10)) + console.log('MISS', clones ? clones.ResolveStack(mesh.stack).name : "???"); + + if ((intersects.indexOf(mesh)>=0) || (ntry>0)) break; + + var pos = mesh.geometry.attributes.position.array; + + direction = new THREE.Vector3((pos[0]+pos[3]+pos[6])/3, (pos[1]+pos[4]+pos[7])/3, (pos[2]+pos[5]+pos[8])/3); + + direction.applyMatrix4(mesh.matrixWorld); + } + + // now push first object in intersects to the front + for (var k1=0;k1 -0.9) { + mesh = new THREE.Mesh( shape.geom, prop.material ); + } else { + mesh = GEO.createFlippedMesh(obj3d, shape, prop.material); + } + + obj3d.add(mesh); + // specify rendering order, required for transparency handling + //if (obj3d.$jsroot_depth !== undefined) + // mesh.renderOrder = clones.maxdepth - obj3d.$jsroot_depth; + //else + // mesh.renderOrder = clones.maxdepth - entry.stack.length; + } + + JSROOT.CallBack(call_back, toplevel); + + return toplevel; + }; + + GEO.getBoundingBox = function(node, box3) { + + // extract code of Box3.expandByObject + // Major difference - do not traverse hierarchy + + if (!node || !node.geometry) return box3; + + if (!box3) { box3 = new THREE.Box3(); box3.makeEmpty(); } + + node.updateMatrixWorld(); + + var v1 = new THREE.Vector3(), + geometry = node.geometry; + + if ( geometry.isGeometry ) { + var vertices = geometry.vertices; + for (var i = 0, l = vertices.length; i < l; i ++ ) { + v1.copy( vertices[ i ] ); + v1.applyMatrix4( node.matrixWorld ); + box3.expandByPoint( v1 ); + } + } else if ( geometry.isBufferGeometry ) { + var attribute = geometry.attributes.position; + if ( attribute !== undefined ) { + for (var i = 0, l = attribute.count; i < l; i ++ ) { + // v1.fromAttribute( attribute, i ).applyMatrix4( node.matrixWorld ); + v1.fromBufferAttribute( attribute, i ).applyMatrix4( node.matrixWorld ); + box3.expandByPoint( v1 ); + } + } + } + + return box3; + }; + + + return GEO; + +})); + diff --git a/dataforge-vis-spatial-js/src/main/resources/ThreeCSG.js b/dataforge-vis-spatial-js/src/main/resources/ThreeCSG.js new file mode 100644 index 00000000..f7afe9e9 --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/resources/ThreeCSG.js @@ -0,0 +1,905 @@ +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + define( [ 'three-full' ], factory ); + } else + if (typeof exports === 'object' && typeof module !== 'undefined') { + factory(require("three-full"), exports); + } else { + + if (typeof THREE == 'undefined') + throw new Error('THREE is not defined', 'ThreeCSG.js'); + + ThreeBSP = factory(THREE); + } +} (function(THREE, ThreeBSP) { + + "use strict"; + + if (!ThreeBSP) ThreeBSP = {}; + + var EPSILON = 1e-5, + COPLANAR = 0, + FRONT = 1, + BACK = 2, + SPANNING = 3; + + ThreeBSP.Geometry = function( geometry, transfer_matrix, nodeid, flippedMesh ) { + // Convert THREE.Geometry to ThreeBSP + + if ( geometry instanceof THREE.Geometry ) { + this.matrix = null; // new THREE.Matrix4; not create matrix when do not needed + } else if ( geometry instanceof THREE.Mesh ) { + // #todo: add hierarchy support + geometry.updateMatrix(); + transfer_matrix = this.matrix = geometry.matrix.clone(); + geometry = geometry.geometry; + } else if ( geometry instanceof ThreeBSP.Node ) { + this.tree = geometry; + this.matrix = null; // new THREE.Matrix4; + return this; + } else if ( geometry instanceof THREE.BufferGeometry ) { + var pos_buf = geometry.getAttribute('position').array, + norm_buf = geometry.getAttribute('normal').array, + polygons = [], polygon, vert1, vert2, vert3; + + for (var i=0; i < pos_buf.length; i+=9) { + polygon = new ThreeBSP.Polygon; + + vert1 = new ThreeBSP.Vertex( pos_buf[i], pos_buf[i+1], pos_buf[i+2], norm_buf[i], norm_buf[i+1], norm_buf[i+2]); + if (transfer_matrix) vert1.applyMatrix4(transfer_matrix); + + vert2 = new ThreeBSP.Vertex( pos_buf[i+3], pos_buf[i+4], pos_buf[i+5], norm_buf[i+3], norm_buf[i+4], norm_buf[i+5]); + if (transfer_matrix) vert2.applyMatrix4(transfer_matrix); + + vert3 = new ThreeBSP.Vertex( pos_buf[i+6], pos_buf[i+7], pos_buf[i+8], norm_buf[i+6], norm_buf[i+7], norm_buf[i+8]); + if (transfer_matrix) vert3.applyMatrix4(transfer_matrix); + + if (flippedMesh) polygon.vertices.push( vert1, vert3, vert2 ); + else polygon.vertices.push( vert1, vert2, vert3 ); + + polygon.calculateProperties(); + polygons.push( polygon ); + } + + this.tree = new ThreeBSP.Node( polygons, nodeid ); + if (nodeid!==undefined) this.maxid = this.tree.maxnodeid; + return this; + + } else if (geometry.polygons && (geometry.polygons[0] instanceof ThreeBSP.Polygon)) { + var polygons = geometry.polygons; + + for (var i=0;i 1); + + while (foundpair) { + foundpair = false; + + for (i1 = 0; i10) { + polygons.splice(0, polygons.length); + + for(n=0;n 0 ) { + this.calculateProperties(); + } else { + this.normal = this.w = undefined; + } + }; + + ThreeBSP.Polygon.prototype.copyProperties = function(parent, more) { + this.normal = parent.normal; // .clone(); + this.w = parent.w; + this.nsign = parent.nsign; + if (more && (parent.id !== undefined)) { + this.id = parent.id; + this.parent = parent; + } + return this; + }; + + ThreeBSP.Polygon.prototype.calculateProperties = function() { + if (this.normal) return; + + var a = this.vertices[0], + b = this.vertices[1], + c = this.vertices[2]; + + this.nsign = 1; + + this.normal = b.clone().subtract( a ).cross( + c.clone().subtract( a ) + ).normalize(); + + this.w = this.normal.clone().dot( a ); + return this; + }; + + ThreeBSP.Polygon.prototype.clone = function() { + var vertice_count = this.vertices.length, + polygon = new ThreeBSP.Polygon; + + for (var i = 0; i < vertice_count; ++i ) + polygon.vertices.push( this.vertices[i].clone() ); + + return polygon.copyProperties(this); + }; + + ThreeBSP.Polygon.prototype.flip = function() { + + /// normal is not changed, only sign variable + //this.normal.multiplyScalar( -1 ); + //this.w *= -1; + + this.nsign *= -1; + + this.vertices.reverse(); + + return this; + }; + + ThreeBSP.Polygon.prototype.classifyVertex = function( vertex ) { + var side_value = this.nsign * (this.normal.dot( vertex ) - this.w); + + if ( side_value < -EPSILON ) return BACK; + if ( side_value > EPSILON ) return FRONT; + return COPLANAR; + }; + + ThreeBSP.Polygon.prototype.classifySide = function( polygon ) { + var i, classification, + num_positive = 0, num_negative = 0, + vertice_count = polygon.vertices.length; + + for ( i = 0; i < vertice_count; ++i ) { + classification = this.classifyVertex( polygon.vertices[i] ); + if ( classification === FRONT ) { + ++num_positive; + } else if ( classification === BACK ) { + ++num_negative; + } + } + + if ( num_positive > 0 && num_negative === 0 ) return FRONT; + if ( num_positive === 0 && num_negative > 0 ) return BACK; + if ( num_positive === 0 && num_negative === 0 ) return COPLANAR; + return SPANNING; + }; + + ThreeBSP.Polygon.prototype.splitPolygon = function( polygon, coplanar_front, coplanar_back, front, back ) { + var classification = this.classifySide( polygon ); + + if ( classification === COPLANAR ) { + + ( (this.nsign * polygon.nsign * this.normal.dot( polygon.normal ) > 0) ? coplanar_front : coplanar_back ).push( polygon ); + + } else if ( classification === FRONT ) { + + front.push( polygon ); + + } else if ( classification === BACK ) { + + back.push( polygon ); + + } else { + + var vertice_count = polygon.vertices.length, + nnx = this.normal.x, + nny = this.normal.y, + nnz = this.normal.z, + i, j, ti, tj, vi, vj, + t, v, + f = [], b = []; + + for ( i = 0; i < vertice_count; ++i ) { + + j = (i + 1) % vertice_count; + vi = polygon.vertices[i]; + vj = polygon.vertices[j]; + ti = this.classifyVertex( vi ); + tj = this.classifyVertex( vj ); + + if ( ti != BACK ) f.push( vi ); + if ( ti != FRONT ) b.push( vi ); + if ( (ti | tj) === SPANNING ) { + // t = ( this.w - this.normal.dot( vi ) ) / this.normal.dot( vj.clone().subtract( vi ) ); + //v = vi.clone().lerp( vj, t ); + + t = (this.w - (nnx*vi.x + nny*vi.y + nnz*vi.z)) / (nnx*(vj.x-vi.x) + nny*(vj.y-vi.y) + nnz*(vj.z-vi.z)); + + v = vi.interpolate( vj, t ); + f.push( v ); + b.push( v ); + } + } + + //if ( f.length >= 3 ) front.push( new ThreeBSP.Polygon( f ).calculateProperties() ); + //if ( b.length >= 3 ) back.push( new ThreeBSP.Polygon( b ).calculateProperties() ); + if ( f.length >= 3 ) front.push( new ThreeBSP.Polygon( f ).copyProperties(polygon, true) ); + if ( b.length >= 3 ) back.push( new ThreeBSP.Polygon( b ).copyProperties(polygon, true) ); + } + }; + + ThreeBSP.Vertex = function(x, y, z, nx, ny, nz) { + this.x = x; + this.y = y; + this.z = z; + this.nx = nx; + this.ny = ny; + this.nz = nz; + }; + + ThreeBSP.Vertex.prototype.setnormal = function ( nx, ny, nz ) { + this.nx = nx; + this.ny = ny; + this.nz = nz; + }; + + ThreeBSP.Vertex.prototype.clone = function() { + return new ThreeBSP.Vertex( this.x, this.y, this.z, this.nx, this.ny, this.nz); + }; + + ThreeBSP.Vertex.prototype.add = function( vertex ) { + this.x += vertex.x; + this.y += vertex.y; + this.z += vertex.z; + return this; + }; + + ThreeBSP.Vertex.prototype.subtract = function( vertex ) { + this.x -= vertex.x; + this.y -= vertex.y; + this.z -= vertex.z; + return this; + }; + + ThreeBSP.Vertex.prototype.multiplyScalar = function( scalar ) { + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + return this; + }; + + ThreeBSP.Vertex.prototype.cross = function( vertex ) { + var x = this.x, + y = this.y, + z = this.z; + + this.x = y * vertex.z - z * vertex.y; + this.y = z * vertex.x - x * vertex.z; + this.z = x * vertex.y - y * vertex.x; + + return this; + }; + + ThreeBSP.Vertex.prototype.normalize = function() { + var length = Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); + + this.x /= length; + this.y /= length; + this.z /= length; + + return this; + }; + + ThreeBSP.Vertex.prototype.dot = function( vertex ) { + return this.x*vertex.x + this.y*vertex.y + this.z*vertex.z; + }; + + ThreeBSP.Vertex.prototype.diff = function( vertex ) { + var dx = (this.x - vertex.x), + dy = (this.y - vertex.y), + dz = (this.z - vertex.z), + len2 = this.x*this.x + this.y*this.y + this.z*this.z; + + return (dx*dx + dy*dy + dz*dz) / (len2>0 ? len2 : 1e-10); + }; + +/* + ThreeBSP.Vertex.prototype.lerp = function( a, t ) { + this.add( + a.clone().subtract( this ).multiplyScalar( t ) + ); + + this.normal.add( + a.normal.clone().sub( this.normal ).multiplyScalar( t ) + ); + + //this.uv.add( + // a.uv.clone().sub( this.uv ).multiplyScalar( t ) + //); + + return this; + }; + ThreeBSP.Vertex.prototype.interpolate = function( other, t ) { + return this.clone().lerp( other, t ); + }; +*/ + + ThreeBSP.Vertex.prototype.interpolate = function( a, t ) { + var t1 = 1-t; + return new ThreeBSP.Vertex(this.x*t1 + a.x*t, this.y*t1 + a.y*t, this.z*t1 + a.z*t, + this.nx*t1 + a.nx*t, this.ny*t1 + a.ny*t, this.nz*t1 + a.nz*t); + }; + + ThreeBSP.Vertex.prototype.applyMatrix4 = function ( m ) { + + // input: THREE.Matrix4 affine matrix + + var x = this.x, y = this.y, z = this.z, e = m.elements; + + this.x = e[0] * x + e[4] * y + e[8] * z + e[12]; + this.y = e[1] * x + e[5] * y + e[9] * z + e[13]; + this.z = e[2] * x + e[6] * y + e[10] * z + e[14]; + + x = this.nx; y = this.ny; z = this.nz; + + this.nx = e[0] * x + e[4] * y + e[8] * z; + this.ny = e[1] * x + e[5] * y + e[9] * z; + this.nz = e[2] * x + e[6] * y + e[10] * z; + + return this; + }; + + // ================================================================================================ + + ThreeBSP.Node = function( polygons, nodeid ) { + this.polygons = []; + this.front = this.back = undefined; + + if ( !(polygons instanceof Array) || polygons.length === 0 ) return; + + this.divider = polygons[0].clone(); + + var polygon_count = polygons.length, + front = [], back = []; + + for (var i = 0; i < polygon_count; ++i ) { + if (nodeid!==undefined) { + polygons[i].id = nodeid++; + delete polygons[i].parent; + } + + this.divider.splitPolygon( polygons[i], this.polygons, this.polygons, front, back ); + } + + if (nodeid !== undefined) this.maxnodeid = nodeid; + + if ( front.length > 0 ) + this.front = new ThreeBSP.Node( front ); + + if ( back.length > 0 ) + this.back = new ThreeBSP.Node( back ); + }; + + ThreeBSP.Node.isConvex = function( polygons ) { + var i, j, len = polygons.length; + for ( i = 0; i < len; ++i ) + for ( j = 0; j < len; ++j ) + if ( i !== j && polygons[i].classifySide( polygons[j] ) !== BACK ) return false; + return true; + }; + + ThreeBSP.Node.prototype.build = function( polygons ) { + var polygon_count = polygons.length, + front = [], back = []; + + if ( !this.divider ) + this.divider = polygons[0].clone(); + + for (var i = 0; i < polygon_count; ++i ) + this.divider.splitPolygon( polygons[i], this.polygons, this.polygons, front, back ); + + if ( front.length > 0 ) { + if ( !this.front ) this.front = new ThreeBSP.Node(); + this.front.build( front ); + } + + if ( back.length > 0 ) { + if ( !this.back ) this.back = new ThreeBSP.Node(); + this.back.build( back ); + } + }; + + ThreeBSP.Node.prototype.collectPolygons = function(arr) { + var len = this.polygons.length; + for (var i=0;i + + + +