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 23998b78..ad139154 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,7 +1,6 @@ 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 @@ -56,12 +55,7 @@ class ThreeDemoApp : ApplicationBase() { // } gdml { y = 110.0 - shape = buildMeta { - "_typename" to "TGeoBBox" - "fDX" to 50.0 - "fDY" to 50.0 - "fDZ" to 50.0 - } + shape = box(50, 50, 50) } } 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 index a21abb3b..de85ec51 100644 --- 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 @@ -3,6 +3,7 @@ package hep.dataforge.vis.spatial.gdml import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.buildMeta import hep.dataforge.vis.* import hep.dataforge.vis.spatial.GenericThreeBuilder import hep.dataforge.vis.spatial.Materials @@ -19,6 +20,37 @@ class GDMLObject(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, TYPE, var facesLimit by int(0) + fun box(xSize: Number, ySize: Number, zSize: Number) = buildMeta { + "_typename" to "TGeoBBox" + "fDX" to xSize + "fDY" to ySize + "fDZ" to zSize + } + + /** + * Create a GDML union + */ + operator fun Meta.plus(other: Meta) = buildMeta { + "fNode.fLeft" to this + "fNode.fRight" to other + "fNode._typename" to "TGeoUnion" + } + + /** + * Create a GDML subtraction + */ + operator fun Meta.minus(other: Meta) = buildMeta { + "fNode.fLeft" to this + "fNode.fRight" to other + "fNode._typename" to "TGeoSubtraction" + } + + infix fun Meta.intersect(other: Meta) = buildMeta { + "fNode.fLeft" to this + "fNode.fRight" to other + "fNode._typename" to "TGeoIntersection" + } + companion object { const val TYPE = "geometry.spatial.gdml" } diff --git a/dataforge-vis-spatial-js/src/main/resources/JSRootGeoBase.js b/dataforge-vis-spatial-js/src/main/resources/JSRootGeoBase.js index 083dfdc2..7fb5f061 100644 --- a/dataforge-vis-spatial-js/src/main/resources/JSRootGeoBase.js +++ b/dataforge-vis-spatial-js/src/main/resources/JSRootGeoBase.js @@ -1,1672 +1,1724 @@ /** @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'); +(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'); + if (typeof ThreeBSP == 'undefined') + throw new Error('ThreeBSP is not defined', 'JSRootGeoBase.js'); - factory(THREE, ThreeBSP); - } -} (function( THREE, ThreeBSP ) { + factory(THREE, ThreeBSP); + } +}(function (THREE, ThreeBSP) { - "use strict"; + "use strict"; - var JSROOT = {}; + 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 + /** Generate mask for given bit + * + * @param {number} n bit number + * @returns {Number} produced make + * @private */ + JSROOT.BIT = function (n) { + return 1 << (n); }; - /** @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; - - } + /** + * @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; n < this.fNodes.arr.length; ++n) { + var sub = this.fNodes.arr[n].fVolume; + GEO.SetBit(sub, GEO.BITS.kVisThis, !flag); + // GEO.SetBit(sub, GEO.BITS.kVisDaughters, !flag); + //GEO.SetBit(sub, GEO.BITS.kVisOneLevel, false); + } + }; + + /** method used to avoid duplication of warnings + * @memberOf GEO */ + GEO.warn = function (msg) { + if (GEO._warn_msgs === undefined) GEO._warn_msgs = {}; + if (GEO._warn_msgs[msg] !== undefined) return; + GEO._warn_msgs[msg] = true; + console.warn(msg); + }; + + /** @memberOf GEO */ + GEO.NodeKind = function (obj) { + // return kind of the geo nodes + // 0 - TGeoNode + // 1 - TEveGeoNode + // -1 - unsupported type + + if ((obj === undefined) || (obj === null) || (typeof obj !== 'object')) return -1; + + return ('fShape' in obj) && ('fTrans' in obj) ? 1 : 0; + }; + + GEO.CountNumShapes = function (shape) { + if (!shape) return 0; + if (shape._typename !== 'TGeoCompositeShape') return 1; + return GEO.CountNumShapes(shape.fNode.fLeft) + GEO.CountNumShapes(shape.fNode.fRight); + }; + + // ========================================================================== + + GEO.GeometryCreator = function (numfaces) { + this.nfaces = numfaces; + this.indx = 0; + this.pos = new Float32Array(numfaces * 9); + this.norm = new Float32Array(numfaces * 9); + + return this; + }; + + GEO.GeometryCreator.prototype.AddFace3 = function (x1, y1, z1, + x2, y2, z2, + x3, y3, z3) { + var indx = this.indx, pos = this.pos; + pos[indx] = x1; + pos[indx + 1] = y1; + pos[indx + 2] = z1; + pos[indx + 3] = x2; + pos[indx + 4] = y2; + pos[indx + 5] = z2; + pos[indx + 6] = x3; + pos[indx + 7] = y3; + pos[indx + 8] = z3; + this.last4 = false; + this.indx = indx + 9; + }; + + GEO.GeometryCreator.prototype.StartPolygon = function () { + }; + GEO.GeometryCreator.prototype.StopPolygon = function () { + }; + + GEO.GeometryCreator.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 + + var indx = this.indx, pos = this.pos; + + if (reduce !== 1) { + pos[indx] = x1; + pos[indx + 1] = y1; + pos[indx + 2] = z1; + pos[indx + 3] = x2; + pos[indx + 4] = y2; + pos[indx + 5] = z2; + pos[indx + 6] = x3; + pos[indx + 7] = y3; + pos[indx + 8] = z3; + indx += 9; + } + + if (reduce !== 2) { + pos[indx] = x1; + pos[indx + 1] = y1; + pos[indx + 2] = z1; + pos[indx + 3] = x3; + pos[indx + 4] = y3; + pos[indx + 5] = z3; + pos[indx + 6] = x4; + pos[indx + 7] = y4; + pos[indx + 8] = z4; + indx += 9; + } + + this.last4 = (indx !== this.indx + 9); + this.indx = indx; + }; + + GEO.GeometryCreator.prototype.SetNormal4 = function (nx1, ny1, nz1, + nx2, ny2, nz2, + nx3, ny3, nz3, + nx4, ny4, nz4, + reduce) { + // same as AddFace4, assign normals for each individual vertex + // reduce has same meaning and should be the same + + if (this.last4 && reduce) + return console.error('missmatch between AddFace4 and SetNormal4 calls'); + + var indx = this.indx - (this.last4 ? 18 : 9), norm = this.norm; + + if (reduce !== 1) { + norm[indx] = nx1; + norm[indx + 1] = ny1; + norm[indx + 2] = nz1; + norm[indx + 3] = nx2; + norm[indx + 4] = ny2; + norm[indx + 5] = nz2; + norm[indx + 6] = nx3; + norm[indx + 7] = ny3; + norm[indx + 8] = nz3; + indx += 9; + } + + if (reduce !== 2) { + norm[indx] = nx1; + norm[indx + 1] = ny1; + norm[indx + 2] = nz1; + norm[indx + 3] = nx3; + norm[indx + 4] = ny3; + norm[indx + 5] = nz3; + norm[indx + 6] = nx4; + norm[indx + 7] = ny4; + norm[indx + 8] = nz4; + } + }; + + GEO.GeometryCreator.prototype.RecalcZ = function (func) { + var pos = this.pos, + last = this.indx, + indx = last - (this.last4 ? 18 : 9); + + while (indx < last) { + pos[indx + 2] = func(pos[indx], pos[indx + 1], pos[indx + 2]); + indx += 3; + } + }; + + GEO.GetNormal = function (x1, y1, z1, x2, y2, z2, x3, y3, z3) { + + var pA = new THREE.Vector3(x1, y1, z1), + pB = new THREE.Vector3(x2, y2, z2), + pC = new THREE.Vector3(x3, y3, z3), + cb = new THREE.Vector3(), + ab = new THREE.Vector3(); + + cb.subVectors(pC, pB); + ab.subVectors(pA, pB); + cb.cross(ab); + + return cb; + }; + + GEO.GeometryCreator.prototype.CalcNormal = function () { + var indx = this.indx, norm = this.norm; + + 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.fromArray(this.pos, this.indx - 9); + this.pB.fromArray(this.pos, this.indx - 6); + this.pC.fromArray(this.pos, this.indx - 3); + + 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.GeometryCreator.prototype.SetNormal = function (nx, ny, nz) { + var indx = this.indx - 9, norm = this.norm; + + norm[indx] = norm[indx + 3] = norm[indx + 6] = nx; + norm[indx + 1] = norm[indx + 4] = norm[indx + 7] = ny; + norm[indx + 2] = norm[indx + 5] = norm[indx + 8] = nz; + + if (this.last4) { + indx -= 9; + norm[indx] = norm[indx + 3] = norm[indx + 6] = nx; + norm[indx + 1] = norm[indx + 4] = norm[indx + 7] = ny; + norm[indx + 2] = norm[indx + 5] = norm[indx + 8] = nz; + } + }; + + GEO.GeometryCreator.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 + if (reduce === undefined) reduce = 0; + + var indx = this.indx - ((reduce > 0) ? 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); + }; - var polygon = new ThreeBSP.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 () { - 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; - } + 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.polygons.push(polygon); - }; + this.cb.subVectors(this.pC, this.pB); + this.ab.subVectors(this.pA, this.pB); + this.cb.cross(this.ab); - 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); - }; + this.SetNormal(this.cb.x, this.cb.y, this.cb.z); + }; - 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() { + 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) { - 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(); - } + 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); - this.pA.set( this.v1.x, this.v1.y, this.v1.z); + for (var n = 0; n < indicies.length; n += 4) { + var i1 = indicies[n] * 3, + i2 = indicies[n + 1] * 3, + i3 = indicies[n + 2] * 3, + i4 = indicies[n + 3] * 3; + creator.AddFace4(v[i1], v[i1 + 1], v[i1 + 2], v[i2], v[i2 + 1], v[i2 + 2], + v[i3], v[i3 + 1], v[i3 + 2], v[i4], v[i4 + 1], v[i4 + 2]); + if (n === 0) creator.SetNormal(0, 0, 1); else if (n === 20) creator.SetNormal(0, 0, -1); else creator.CalcNormal(); + } - 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); - } + return creator.Create(); + }; - this.cb.subVectors( this.pC, this.pB ); - this.ab.subVectors( this.pA, this.pB ); - this.cb.cross( this.ab ); + /** @memberOf GEO */ + GEO.createParaBuffer = function (shape, faces_limit) { - this.SetNormal(this.cb.x, this.cb.y, this.cb.z); - }; + if (faces_limit < 0) return 12; + var txy = shape.fTxy, txz = shape.fTxz, tyz = shape.fTyz; - 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); - }; + var v = [ + -shape.fZ * txz - txy * shape.fY - shape.fX, -shape.fY - shape.fZ * tyz, -shape.fZ, + -shape.fZ * txz + txy * shape.fY - shape.fX, shape.fY - shape.fZ * tyz, -shape.fZ, + -shape.fZ * txz + txy * shape.fY + shape.fX, shape.fY - shape.fZ * tyz, -shape.fZ, + -shape.fZ * txz - txy * shape.fY + shape.fX, -shape.fY - shape.fZ * tyz, -shape.fZ, + shape.fZ * txz - txy * shape.fY - shape.fX, -shape.fY + shape.fZ * tyz, shape.fZ, + shape.fZ * txz + txy * shape.fY - shape.fX, shape.fY + shape.fZ * tyz, shape.fZ, + shape.fZ * txz + txy * shape.fY + shape.fX, shape.fY + shape.fZ * tyz, shape.fZ, + shape.fZ * txz - txy * shape.fY + shape.fX, -shape.fY + shape.fZ * tyz, shape.fZ]; - 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 }; - }; + return GEO.create8edgesBuffer(v, faces_limit); + }; - // ================= all functions to create geometry =================================== + /** @memberOf GEO */ + GEO.createTrapezoidBuffer = function (shape, faces_limit) { - /** @memberOf GEO */ - GEO.createCubeBuffer = function(shape, faces_limit) { + if (faces_limit < 0) return 12; - if (faces_limit < 0) return 12; + var y1, y2; + if (shape._typename == "TGeoTrd1") { + y1 = y2 = shape.fDY; + } else { + y1 = shape.fDy1; + y2 = shape.fDy2; + } - 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 ((indicies[k] == indicies[k + 1]) || (indicies[k] == indicies[k + 2]) || (indicies[k + 1] == indicies[k + 2]) || + (map.indexOf(id1) >= 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++; } - } + } - 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(); + 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 (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(); + + 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(); - }; + 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); + /** @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; + // 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 (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 1.) { + widthSegments = Math.max(4, Math.floor(widthSegments / Math.sqrt(fact))); + heightSegments = Math.max(4, Math.floor(heightSegments / Math.sqrt(fact))); } - } - } + } - // top/bottom - for (var side=0; side<=heightSegments; side+=heightSegments) - if (Math.abs(_sint[side]) >= epsilon) { - var ss = _sint[side], cc = _cost[side], - d1 = (side===0) ? 0 : 1, d2 = 1 - d1; - for (var n=0;n= epsilon) { + var ss = _sint[side], cc = _cost[side], + d1 = (side === 0) ? 0 : 1, d2 = 1 - d1; + for (var n = 0; n < widthSegments; ++n) { + creator.AddFace4( + radius[1] * ss * _cosp[n + d1], radius[1] * ss * _sinp[n + d1], radius[1] * cc, + radius[0] * ss * _cosp[n + d1], radius[0] * ss * _sinp[n + d1], radius[0] * cc, + radius[0] * ss * _cosp[n + d2], radius[0] * ss * _sinp[n + d2], radius[0] * cc, + radius[1] * ss * _cosp[n + d2], radius[1] * ss * _sinp[n + d2], radius[1] * cc, + noInside ? 2 : 0); + creator.CalcNormal(); + } } - } - } - return creator.Create(); - }; + // cut left/right sides + if (phiLength < 360) { + for (var side = 0; side <= widthSegments; side += widthSegments) { + var ss = _sinp[side], cc = _cosp[side], + d1 = (side === 0) ? 1 : 0, d2 = 1 - d1; - /** @memberOf GEO */ - GEO.createTubeBuffer = function( shape, faces_limit) { - var outerR, innerR; // inner/outer tube radius - if ((shape._typename == "TGeoCone") || (shape._typename == "TGeoConeSeg")) { - outerR = [ shape.fRmax2, shape.fRmax1 ]; - innerR = [ shape.fRmin2, shape.fRmin1 ]; - } else { - outerR = [ shape.fRmax, shape.fRmax ]; - innerR = [ shape.fRmin, shape.fRmin ]; - } + for (var k = 0; k < heightSegments; ++k) { + creator.AddFace4( + radius[1] * _sint[k + d1] * cc, radius[1] * _sint[k + d1] * ss, radius[1] * _cost[k + d1], + radius[0] * _sint[k + d1] * cc, radius[0] * _sint[k + d1] * ss, radius[0] * _cost[k + d1], + radius[0] * _sint[k + d2] * cc, radius[0] * _sint[k + d2] * ss, radius[0] * _cost[k + d2], + radius[1] * _sint[k + d2] * cc, radius[1] * _sint[k + d2] * ss, radius[1] * _cost[k + d2], + noInside ? 2 : 0); + creator.CalcNormal(); + } + } + } - var hasrmin = (innerR[0] > 0) || (innerR[1] > 0), - thetaStart = 0, thetaLength = 360; + return creator.Create(); + }; - if ((shape._typename == "TGeoConeSeg") || (shape._typename == "TGeoTubeSeg") || (shape._typename == "TGeoCtub")) { - thetaStart = shape.fPhi1; - thetaLength = shape.fPhi2 - shape.fPhi1; - } + /** @memberOf GEO */ + GEO.createTubeBuffer = function (shape, faces_limit) { + var outerR, innerR; // inner/outer tube radius + if ((shape._typename == "TGeoCone") || (shape._typename == "TGeoConeSeg")) { + outerR = [shape.fRmax2, shape.fRmax1]; + innerR = [shape.fRmin2, shape.fRmin1]; + } else { + outerR = [shape.fRmax, shape.fRmax]; + innerR = [shape.fRmin, shape.fRmin]; + } - var radiusSegments = Math.max(4, Math.round(thetaLength/GEO.GradPerSegm)); + var hasrmin = (innerR[0] > 0) || (innerR[1] > 0), + thetaStart = 0, thetaLength = 360; - // external surface - var numfaces = radiusSegments * (((outerR[0] <= 0) || (outerR[1] <= 0)) ? 1 : 2); + if ((shape._typename == "TGeoConeSeg") || (shape._typename == "TGeoTubeSeg") || (shape._typename == "TGeoCtub")) { + thetaStart = shape.fPhi1; + thetaLength = shape.fPhi2 - shape.fPhi1; + } - // internal surface - if (hasrmin) - numfaces += radiusSegments * (((innerR[0] <= 0) || (innerR[1] <= 0)) ? 1 : 2); + var radiusSegments = Math.max(4, Math.round(thetaLength / GEO.GradPerSegm)); - // 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); + // external surface + var numfaces = radiusSegments * (((outerR[0] <= 0) || (outerR[1] <= 0)) ? 1 : 2); - if (thetaLength < 360) - numfaces += ((outerR[0] > innerR[0]) ? 2 : 0) + ((outerR[1] > innerR[1]) ? 2 : 0); + // internal surface + if (hasrmin) + numfaces += radiusSegments * (((innerR[0] <= 0) || (innerR[1] <= 0)) ? 1 : 2); - if (faces_limit < 0) return numfaces; + // 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); - var phi0 = thetaStart*Math.PI/180, - dphi = thetaLength/radiusSegments*Math.PI/180, - _sin = new Float32Array(radiusSegments+1), - _cos = new Float32Array(radiusSegments+1); + if (thetaLength < 360) + numfaces += ((outerR[0] > innerR[0]) ? 2 : 0) + ((outerR[1] > innerR[1]) ? 2 : 0); - for (var seg=0; seg<=radiusSegments; ++seg) { - _cos[seg] = Math.cos(phi0+seg*dphi); - _sin[seg] = Math.sin(phi0+seg*dphi); - } + if (faces_limit < 0) return numfaces; - var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); + var phi0 = thetaStart * Math.PI / 180, + dphi = thetaLength / radiusSegments * Math.PI / 180, + _sin = new Float32Array(radiusSegments + 1), + _cos = new Float32Array(radiusSegments + 1); - // var creator = new GEO.GeometryCreator(numfaces); + for (var seg = 0; seg <= radiusSegments; ++seg) { + _cos[seg] = Math.cos(phi0 + seg * dphi); + _sin[seg] = Math.sin(phi0 + seg * dphi); + } - var calcZ; + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); - 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]; - }; + // var creator = new GEO.GeometryCreator(numfaces); - // create outer/inner tube - for (var side = 0; side<2; ++side) { - if ((side === 1) && !hasrmin) break; + var calcZ; - var R = (side === 0) ? outerR : innerR, - d1 = side, d2 = 1 - side, nxy = 1., nz = 0; + 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]; + }; - if (R[0] !== R[1]) { - var angle = Math.atan2((R[1]-R[0]), 2*shape.fDZ); - nxy = Math.cos(angle); - nz = Math.sin(angle); - } + // create outer/inner tube + for (var side = 0; side < 2; ++side) { + if ((side === 1) && !hasrmin) break; - if (side === 1) { - nxy *= -1; - nz *= -1; - } - var reduce = 0; - if (R[0] <= 0) reduce = 2; else - if (R[1] <= 0) reduce = 1; + var R = (side === 0) ? outerR : innerR, + d1 = side, d2 = 1 - side, nxy = 1., nz = 0; - 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))); - } - } + // calculate normals ourself + nx1 = nx2; + ny1 = ny2; + nx2 = x[seg + 1] * shape.fRmax / shape.fRmin; + ny2 = y[seg + 1] * shape.fRmin / shape.fRmax; + var dist = Math.sqrt(nx2 * nx2 + ny2 * ny2); + nx2 = nx2 / dist; + ny2 = ny2 / dist; - var zmin = -shape.fDZ, zmax = shape.fDZ, rmin = shape.fRlo, rmax = shape.fRhi; + creator.SetNormal_12_34(nx1, ny1, 0, nx2, ny2, 0); + } - // 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; + // create top/bottom sides + for (var side = 0; side < 2; ++side) { + var sign = (side === 0) ? 1 : -1, d1 = side, d2 = 1 - side; + for (var seg = 0; seg < radiusSegments; ++seg) { + creator.AddFace3(0, 0, sign * shape.fDZ, + x[seg + d1], y[seg + d1], sign * shape.fDZ, + x[seg + d2], y[seg + d2], sign * shape.fDZ); + creator.SetNormal(0, 0, sign); } - } + } - nxy = shape.fA * radius; - nz = (shape.fA > 0) ? -1 : 1; + return creator.Create(); + }; - var skip = 0; - if (lastr === 0) skip = 1; else - if (radius === 0) skip = 2; + /** @memberOf GEO */ + GEO.createTorusBuffer = function (shape, faces_limit) { + var radius = shape.fR, + radialSegments = Math.max(6, Math.round(360 / GEO.GradPerSegm)), + tubularSegments = Math.max(8, Math.round(shape.fDphi / GEO.GradPerSegm)); - for (var seg=0; seg 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0)); - // use analytic normal values when open/closing paraboloid around 0 - // cut faces (top or bottom) set with simple normal - if ((skip===0) || ((layer===1) && (rmin===0)) || ((layer===heightSegments+1) && (rmax===0))) - creator.SetNormal4(nxy*_cos[seg], nxy*_sin[seg], nz, - lastnxy*_cos[seg], lastnxy*_sin[seg], lastnz, - lastnxy*_cos[seg+1], lastnxy*_sin[seg+1], lastnz, - nxy*_cos[seg+1], nxy*_sin[seg+1], nz, skip); - else - creator.SetNormal(0, 0, (layer < heightSegments) ? -1 : 1); - } + if (faces_limit < 0) return numfaces; - lastz = layerz; lastr = radius; - lastnxy = nxy; lastnz = nz; - } + 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)); + } - return creator.Create(); - }; + var _sinr = new Float32Array(radialSegments + 1), + _cosr = new Float32Array(radialSegments + 1), + _sint = new Float32Array(tubularSegments + 1), + _cost = new Float32Array(tubularSegments + 1); - /** @memberOf GEO */ - GEO.createHypeBuffer = function( shape, faces_limit ) { + 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); + } - if ((shape.fTin===0) && (shape.fTout===0)) - return GEO.createTubeBuffer(shape, faces_limit); + 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 radiusSegments = Math.max(4, Math.round(360/GEO.GradPerSegm)), - heightSegments = 30; + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); - var numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2); + // 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(); - if (faces_limit < 0) return numfaces; + 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; - 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); - } + for (var t = 0; t < tubularSegments; ++t) { + var t1 = t + d1, t2 = t + d2; + center1.x = radius * _cost[t1]; + center1.y = radius * _sint[t1]; + center2.x = radius * _cost[t2]; + center2.y = radius * _sint[t2]; - // 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); - } + for (var n = 0; n < radialSegments; ++n) { + p1.x = (radius + tube * _cosr[n]) * _cost[t1]; + p1.y = (radius + tube * _cosr[n]) * _sint[t1]; + p1.z = tube * _sinr[n]; + p2.x = (radius + tube * _cosr[n + 1]) * _cost[t1]; + p2.y = (radius + tube * _cosr[n + 1]) * _sint[t1]; + p2.z = tube * _sinr[n + 1]; + p3.x = (radius + tube * _cosr[n + 1]) * _cost[t2]; + p3.y = (radius + tube * _cosr[n + 1]) * _sint[t2]; + p3.z = tube * _sinr[n + 1]; + p4.x = (radius + tube * _cosr[n]) * _cost[t2]; + p4.y = (radius + tube * _cosr[n]) * _sint[t2]; + p4.z = tube * _sinr[n]; - var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); + creator.AddFace4(p1.x, p1.y, p1.z, + p2.x, p2.y, p2.z, + p3.x, p3.y, p3.z, + p4.x, p4.y, p4.z); - // in-out side - for (var side=0;side<2;++side) { - if ((side > 0) && (shape.fRmin <= 0)) break; + n1.subVectors(p1, center1).normalize(); + n2.subVectors(p2, center1).normalize(); + n3.subVectors(p3, center2).normalize(); + n4.subVectors(p4, center2).normalize(); - 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 0) ? 0 : 1, d2 = 1 - d1, + skip = (shape.fRmin) > 0 ? 0 : 1, + nsign = t > 0 ? 1 : -1; + for (var n = 0; n < radialSegments; ++n) { + creator.AddFace4((radius + tube1 * _cosr[n + d1]) * _cost[t], (radius + tube1 * _cosr[n + d1]) * _sint[t], tube1 * _sinr[n + d1], + (radius + tube2 * _cosr[n + d1]) * _cost[t], (radius + tube2 * _cosr[n + d1]) * _sint[t], tube2 * _sinr[n + d1], + (radius + tube2 * _cosr[n + d2]) * _cost[t], (radius + tube2 * _cosr[n + d2]) * _sint[t], tube2 * _sinr[n + d2], + (radius + tube1 * _cosr[n + d2]) * _cost[t], (radius + tube1 * _cosr[n + d2]) * _sint[t], tube1 * _sinr[n + d2], skip); + creator.SetNormal(-nsign * _sint[t], nsign * _cost[t], 0); + } + } - } - - return creator.Create(); - }; + return creator.Create(); + }; - /** @memberOf GEO */ - GEO.createMatrix = function(matrix) { + /** @memberOf GEO */ + GEO.createPolygonBuffer = function (shape, faces_limit) { + var thetaStart = shape.fPhi1, + thetaLength = shape.fDphi, + radiusSegments = 60; - if (!matrix) return null; + if (shape._typename == "TGeoPgon") + radiusSegments = shape.fNedges; + else + radiusSegments = Math.max(5, Math.round(thetaLength / GEO.GradPerSegm)); - var translation = null, rotation = null, scale = null; + var usage = new Int16Array(2 * shape.fNz), numusedlayers = 0, hasrmin = false; - switch (matrix._typename) { - case 'TGeoTranslation': translation = matrix.fTranslation; break; - case 'TGeoRotation': rotation = matrix.fRotationMatrix; break; - case 'TGeoScale': scale = matrix.fScale; break; - case 'TGeoGenTrans': - scale = matrix.fScale; // no break, translation and rotation follows - case 'TGeoCombiTrans': - translation = matrix.fTranslation; - if (matrix.fRotation) rotation = matrix.fRotation.fRotationMatrix; - break; - case 'TGeoHMatrix': - translation = matrix.fTranslation; - rotation = matrix.fRotationMatrix; - scale = matrix.fScale; - break; - case 'TGeoIdentity': - break; - default: - console.warn('unsupported matrix ' + matrix._typename); - } + for (var layer = 0; layer < shape.fNz; ++layer) + if (shape.fRmin[layer] > 0) hasrmin = true; - if (!translation && !rotation && !scale) return null; + // return very rough estimation, number of faces may be much less + if (faces_limit < 0) return (hasrmin ? 4 : 2) * radiusSegments * (shape.fNz - 1); - var res = new THREE.Matrix4(); + // coordinate of point on cut edge (x,z) + var pnts = (thetaLength === 360) ? null : []; - if (rotation) - res.set(rotation[0], rotation[1], rotation[2], 0, - rotation[3], rotation[4], rotation[5], 0, - rotation[6], rotation[7], rotation[8], 0, - 0, 0, 0, 1); + // first analyse levels - if we need to create all of them + for (var side = 0; side < 2; ++side) { + var rside = (side === 0) ? 'fRmax' : 'fRmin'; - if (translation) - res.setPosition(new THREE.Vector3(translation[0], translation[1], translation[2])); + for (var layer = 0; layer < shape.fNz; ++layer) { - if (scale) - res.scale(new THREE.Vector3(scale[0], scale[1], scale[2])); + // first create points for the layer + var layerz = shape.fZ[layer], rad = shape[rside][layer]; - return res; - }; + usage[layer * 2 + side] = 0; - /** @memberOf GEO */ - GEO.getNodeMatrix = function(kind, node) { - // returns transformation matrix for the node - // created after node visibility flag is checked and volume cut is performed + 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))) { - var matrix = null; + // same Z and R as before - ignore + // or same R before and after - if (kind === 1) { - // special handling for EVE nodes + continue; + } - matrix = new THREE.Matrix4(); + if ((layer > 0) && ((side === 0) || hasrmin)) { + usage[layer * 2 + side] = 1; + numusedlayers++; + } - if (node.fTrans!==null) { - matrix.set(node.fTrans[0], node.fTrans[4], node.fTrans[8], 0, - node.fTrans[1], node.fTrans[5], node.fTrans[9], 0, - node.fTrans[2], node.fTrans[6], node.fTrans[10], 0, - 0, 0, 0, 1); - // second - set position with proper sign - matrix.setPosition({ x: node.fTrans[12], y: node.fTrans[13], z: node.fTrans[14] }); - } - } else - if (('fMatrix' in node) && (node.fMatrix !== null)) - matrix = GEO.createMatrix(node.fMatrix); - else - if ((node._typename == "TGeoNodeOffset") && (node.fFinder !== null)) { - var kPatternReflected = GEO.BITS.kVisBranch; - if ((node.fFinder.fBits & kPatternReflected) !== 0) - GEO.warn('Unsupported reflected pattern ' + node.fFinder._typename); + 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)); + } + } + } + } - // if (node.fFinder._typename === 'TGeoPatternCylR') { } - // if (node.fFinder._typename === 'TGeoPatternSphR') { } - // if (node.fFinder._typename === 'TGeoPatternSphTheta') { } - // if (node.fFinder._typename === 'TGeoPatternSphPhi') { } - // if (node.fFinder._typename === 'TGeoPatternHoneycomb') { } - switch(node.fFinder._typename) { - case 'TGeoPatternX': - case 'TGeoPatternY': - case 'TGeoPatternZ': - case 'TGeoPatternParaX': - case 'TGeoPatternParaY': - case 'TGeoPatternParaZ': - var _shift = node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep; + 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); - matrix = new THREE.Matrix4(); + var cut_faces = null; - switch (node.fFinder._typename[node.fFinder._typename.length-1]) { - case 'X': matrix.setPosition(new THREE.Vector3(_shift, 0, 0)); break; - case 'Y': matrix.setPosition(new THREE.Vector3(0, _shift, 0)); break; - case 'Z': matrix.setPosition(new THREE.Vector3(0, 0, _shift)); break; - } - break; + 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]); + } - case 'TGeoPatternCylPhi': - var phi = (Math.PI/180)*(node.fFinder.fStart+(node.fIndex+0.5)*node.fFinder.fStep), - _cos = Math.cos(phi), _sin = Math.sin(phi); + } else { + // let three.js calculate our faces + // console.log('triangulate polygon ' + shape.fShapeId); + cut_faces = THREE.ShapeUtils.triangulateShape(pnts, []); + } + numfaces += cut_faces.length * 2; + } - matrix = new THREE.Matrix4(); + var phi0 = thetaStart * Math.PI / 180, dphi = thetaLength / radiusSegments * Math.PI / 180; - matrix.set(_cos, -_sin, 0, 0, - _sin, _cos, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - break; + // 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); + } - case 'TGeoPatternCylR': - // seems to be, require no transformation - matrix = new THREE.Matrix4(); - break; + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(numfaces); - case 'TGeoPatternTrapZ': - var dz = node.fFinder.fStart + (node.fIndex+0.5)*node.fFinder.fStep; - matrix = new THREE.Matrix4(); - matrix.setPosition(new THREE.Vector3(node.fFinder.fTxz*dz, node.fFinder.fTyz*dz, dz)); - break; + // 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; - default: - GEO.warn('Unsupported pattern type ' + node.fFinder._typename); - break; - } - } + for (var layer = 0; layer < shape.fNz; ++layer) { - return matrix; - }; + if (usage[layer * 2 + side] === 0) continue; - /** @memberOf GEO */ - GEO.createComposite = function ( shape, faces_limit ) { + 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 < cut_faces.length; ++n) { + var a = pnts[cut_faces[n][0]], + b = pnts[cut_faces[n][d1]], + c = pnts[cut_faces[n][d2]]; + + creator.AddFace3(a.x * _cos[seg], a.x * _sin[seg], a.y, + b.x * _cos[seg], b.x * _sin[seg], b.y, + c.x * _cos[seg], c.x * _sin[seg], c.y); + + creator.CalcNormal(); + } + } + + return creator.Create(); + }; + + /** @memberOf GEO */ + GEO.createXtruBuffer = function (shape, faces_limit) { + var nfaces = (shape.fNz - 1) * shape.fNvert * 2; + + if (faces_limit < 0) return nfaces + shape.fNvert * 3; + + // create points + var pnts = []; + for (var vert = 0; vert < shape.fNvert; ++vert) + pnts.push(new THREE.Vector2(shape.fX[vert], shape.fY[vert])); + + // console.log('triangulate Xtru ' + shape.fShapeId); + var faces = THREE.ShapeUtils.triangulateShape(pnts, []); + if (faces.length < pnts.length - 2) { + GEO.warn('Problem with XTRU shape ' + shape.fName + ' with ' + pnts.length + ' vertices'); + faces = []; + } else { + nfaces += faces.length * 2; + } + + var creator = faces_limit ? new GEO.PolygonsCreator : new GEO.GeometryCreator(nfaces); + + for (var layer = 0; layer < shape.fNz - 1; ++layer) { + var z1 = shape.fZ[layer], scale1 = shape.fScale[layer], + z2 = shape.fZ[layer + 1], scale2 = shape.fScale[layer + 1]; + + for (var vert1 = 0; vert1 < shape.fNvert; ++vert1) { + var vert2 = (vert1 + 1) % shape.fNvert; + creator.AddFace4(scale1 * shape.fX[vert1], scale1 * shape.fY[vert1], z1, + scale2 * shape.fX[vert1], scale2 * shape.fY[vert1], z2, + scale2 * shape.fX[vert2], scale2 * shape.fY[vert2], z2, + scale1 * shape.fX[vert2], scale1 * shape.fY[vert2], z1); + creator.CalcNormal(); + } + } + + for (layer = 0; layer <= shape.fNz - 1; layer += (shape.fNz - 1)) { + var z = shape.fZ[layer], scale = shape.fScale[layer]; + + for (var n = 0; n < faces.length; ++n) { + var face = faces[n], + pnt1 = pnts[face[0]], + pnt2 = pnts[face[(layer === 0) ? 2 : 1]], + pnt3 = pnts[face[(layer === 0) ? 1 : 2]]; + + creator.AddFace3(scale * pnt1.x, scale * pnt1.y, z, + scale * pnt2.x, scale * pnt2.y, z, + scale * pnt3.x, scale * pnt3.y, z); + creator.SetNormal(0, 0, layer === 0 ? -1 : 1); + } + } + + return creator.Create(); + }; + + /** @memberOf GEO */ + GEO.createParaboloidBuffer = function (shape, faces_limit) { + + var radiusSegments = Math.max(4, Math.round(360 / GEO.GradPerSegm)), + heightSegments = 30; + + if (faces_limit > 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 < radiusSegments; ++seg) { + creator.AddFace4(radius * _cos[seg], radius * _sin[seg], layerz, + lastr * _cos[seg], lastr * _sin[seg], lastz, + lastr * _cos[seg + 1], lastr * _sin[seg + 1], lastz, + radius * _cos[seg + 1], radius * _sin[seg + 1], layerz, skip); + + // use analytic normal values when open/closing paraboloid around 0 + // cut faces (top or bottom) set with simple normal + if ((skip === 0) || ((layer === 1) && (rmin === 0)) || ((layer === heightSegments + 1) && (rmax === 0))) + creator.SetNormal4(nxy * _cos[seg], nxy * _sin[seg], nz, + lastnxy * _cos[seg], lastnxy * _sin[seg], lastnz, + lastnxy * _cos[seg + 1], lastnxy * _sin[seg + 1], lastnz, + nxy * _cos[seg + 1], nxy * _sin[seg + 1], nz, skip); + else + creator.SetNormal(0, 0, (layer < heightSegments) ? -1 : 1); + } + + lastz = layerz; + lastr = radius; + lastnxy = nxy; + lastnz = nz; + } + + return creator.Create(); + }; + + /** @memberOf GEO */ + GEO.createHypeBuffer = function (shape, faces_limit) { + + if ((shape.fTin === 0) && (shape.fTout === 0)) + return GEO.createTubeBuffer(shape, faces_limit); + + var radiusSegments = Math.max(4, Math.round(360 / GEO.GradPerSegm)), + heightSegments = 30; + + var numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 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 < heightSegments; ++layer) { + var z1 = -shape.fDz + layer / heightSegments * 2 * shape.fDz, + z2 = -shape.fDz + (layer + 1) / heightSegments * 2 * shape.fDz, + r1 = Math.sqrt(r0 * r0 + tsq * z1 * z1), + r2 = Math.sqrt(r0 * r0 + tsq * z2 * z2); + + 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.CalcNormal(); + } + } + } + + // add caps + for (var layer = 0; layer < 2; ++layer) { + var z = (layer === 0) ? shape.fDz : -shape.fDz, + r1 = Math.sqrt(shape.fRmax * shape.fRmax + shape.fToutsq * z * z), + r2 = (shape.fRmin > 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 < radiusSegments; ++seg) { + creator.AddFace4(r1 * _cos[seg + d1], r1 * _sin[seg + d1], z, + r2 * _cos[seg + d1], r2 * _sin[seg + d1], z, + r2 * _cos[seg + d2], r2 * _sin[seg + d2], z, + r1 * _cos[seg + d2], r1 * _sin[seg + d2], z, skip); + creator.SetNormal(0, 0, (layer === 0) ? 1 : -1) + } + + } + + return creator.Create(); + }; + + + /** @memberOf GEO */ + GEO.createMatrix = function (matrix) { + + if (!matrix) return null; + + var translation = null, rotation = null, scale = null; + + switch (matrix._typename) { + case 'TGeoTranslation': + translation = matrix.fTranslation; + break; + case 'TGeoRotation': + rotation = matrix.fRotationMatrix; + break; + case 'TGeoScale': + scale = matrix.fScale; + break; + case 'TGeoGenTrans': + scale = matrix.fScale; // no break, translation and rotation follows + case 'TGeoCombiTrans': + translation = matrix.fTranslation; + if (matrix.fRotation) rotation = matrix.fRotation.fRotationMatrix; + break; + case 'TGeoHMatrix': + translation = matrix.fTranslation; + rotation = matrix.fRotationMatrix; + scale = matrix.fScale; + break; + case 'TGeoIdentity': + break; + default: + console.warn('unsupported matrix ' + matrix._typename); + } + + if (!translation && !rotation && !scale) return null; + + var res = new THREE.Matrix4(); + + if (rotation) + res.set(rotation[0], rotation[1], rotation[2], 0, + rotation[3], rotation[4], rotation[5], 0, + rotation[6], rotation[7], rotation[8], 0, + 0, 0, 0, 1); + + if (translation) + res.setPosition(new THREE.Vector3(translation[0], translation[1], translation[2])); + + if (scale) + res.scale(new THREE.Vector3(scale[0], scale[1], scale[2])); + + return res; + }; + + /** @memberOf GEO */ + GEO.getNodeMatrix = function (kind, node) { + // returns transformation matrix for the node + // created after node visibility flag is checked and volume cut is performed + + var matrix = null; + + if (kind === 1) { + // special handling for EVE nodes + + matrix = new THREE.Matrix4(); + + if (node.fTrans !== null) { + matrix.set(node.fTrans[0], node.fTrans[4], node.fTrans[8], 0, + node.fTrans[1], node.fTrans[5], node.fTrans[9], 0, + node.fTrans[2], node.fTrans[6], node.fTrans[10], 0, + 0, 0, 0, 1); + // second - set position with proper sign + matrix.setPosition({x: node.fTrans[12], y: node.fTrans[13], z: node.fTrans[14]}); + } + } else if (('fMatrix' in node) && (node.fMatrix !== null)) + matrix = GEO.createMatrix(node.fMatrix); + else if ((node._typename == "TGeoNodeOffset") && (node.fFinder !== null)) { + var kPatternReflected = GEO.BITS.kVisBranch; + if ((node.fFinder.fBits & kPatternReflected) !== 0) + GEO.warn('Unsupported reflected pattern ' + node.fFinder._typename); + + // if (node.fFinder._typename === 'TGeoPatternCylR') { } + // if (node.fFinder._typename === 'TGeoPatternSphR') { } + // if (node.fFinder._typename === 'TGeoPatternSphTheta') { } + // if (node.fFinder._typename === 'TGeoPatternSphPhi') { } + // if (node.fFinder._typename === 'TGeoPatternHoneycomb') { } + switch (node.fFinder._typename) { + case 'TGeoPatternX': + case 'TGeoPatternY': + case 'TGeoPatternZ': + case 'TGeoPatternParaX': + case 'TGeoPatternParaY': + case 'TGeoPatternParaZ': + var _shift = node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep; + + matrix = new THREE.Matrix4(); + + switch (node.fFinder._typename[node.fFinder._typename.length - 1]) { + case 'X': + matrix.setPosition(new THREE.Vector3(_shift, 0, 0)); + break; + case 'Y': + matrix.setPosition(new THREE.Vector3(0, _shift, 0)); + break; + case 'Z': + matrix.setPosition(new THREE.Vector3(0, 0, _shift)); + break; + } + break; + + case 'TGeoPatternCylPhi': + var phi = (Math.PI / 180) * (node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep), + _cos = Math.cos(phi), _sin = Math.sin(phi); + + matrix = new THREE.Matrix4(); + + matrix.set(_cos, -_sin, 0, 0, + _sin, _cos, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + break; + + case 'TGeoPatternCylR': + // seems to be, require no transformation + matrix = new THREE.Matrix4(); + break; + + case 'TGeoPatternTrapZ': + var dz = node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep; + matrix = new THREE.Matrix4(); + matrix.setPosition(new THREE.Vector3(node.fFinder.fTxz * dz, node.fFinder.fTyz * dz, dz)); + break; + + default: + GEO.warn('Unsupported pattern type ' + node.fFinder._typename); + break; + } + } + + return matrix; + }; + + /** @memberOf GEO */ + GEO.createComposite = function (shape, faces_limit) { + + /* if ((faces_limit === -1) || (faces_limit === 0)) { var cnt = GEO.CountNumShapes(shape); @@ -1685,1742 +1737,1824 @@ } */ - if (faces_limit < 0) - return GEO.createGeometry(shape.fNode.fLeft, -10) + + 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); + 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; + // 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 (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'); + 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; + 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; + 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 < 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; + if ((n1 + n2 >= faces_limit) || !geom2) { + if (geom1.polygons) { + geom1 = ThreeBSP.CreateBufferGeometry(geom1.polygons); + n1 = GEO.numGeometryFaces(geom1); } - 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); - } + 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; + } - return limit < 0 ? 0 : null; - }; + bsp1 = new ThreeBSP.Geometry(geom1, matrix1, GEO.CompressComp ? 0 : undefined); - /** Provides info about geo object, used for tooltip info */ - GEO.provideInfo = function(obj) { - var info = [], shape = null; + bsp2 = new ThreeBSP.Geometry(geom2, matrix2, bsp1.maxid); - 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; + // take over maxid from both geometries + bsp1.maxid = bsp2.maxid; - if (!shape) { - info.push(obj._typename); - return info; - } + 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'); + } - var sz = Math.max(shape.fDX, shape.fDY, shape.fDZ); - var useexp = (sz>1e7) || (sz<1e-7); + 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); + } - 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); - } + return return_bsp ? {polygons: bsp1.toPolygons()} : bsp1.toBufferGeometry(); + }; - info.push(shape._typename); + /** @memberOf GEO */ + GEO.projectGeometry = function (geom, matrix, projection, position, flippedMesh) { - info.push("DX="+conv(shape.fDX) + " DY="+conv(shape.fDY) + " DZ="+conv(shape.fDZ)); + if (!geom.boundingBox) geom.computeBoundingBox(); - 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; - } + var box = geom.boundingBox.clone(); - return info; - }; + box.applyMatrix4(matrix); - /** @memberOf GEO */ - GEO.CreateProjectionMatrix = function(camera) { - var cameraProjectionMatrix = new THREE.Matrix4(); + if (!position) position = 0; - camera.updateMatrixWorld(); - camera.matrixWorldInverse.getInverse( camera.matrixWorld ); - cameraProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse); + if (((box.min[projection] >= position) && (box.max[projection] >= position)) || + ((box.min[projection] <= position) && (box.max[projection] <= position))) { + return null; // not interesting + } - return cameraProjectionMatrix; - }; + 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; - /** @memberOf GEO */ - GEO.CreateFrustum = function(source) { - if (!source) return null; + 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; + } - if (source instanceof THREE.PerspectiveCamera) - source = GEO.CreateProjectionMatrix(source); + var bsp2 = ThreeBSP.CreateNormal(projection, position, size); - var frustum = new THREE.Frustum(); - frustum.setFromMatrix(source); + bsp1.cut_from_plane(bsp2); - 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 - ]); + return bsp2.toBufferGeometry(); + }; - frustum.test = new THREE.Vector3(0,0,0); + /** + * 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; - frustum.CheckShape = function(matrix, shape) { - var pnt = this.test, len = this.corners.length, corners = this.corners, i; + 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); + } - 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 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; - }; + }; - 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 - }; + /** @memberOf GEO */ + GEO.numGeometryFaces = function (geom) { + if (!geom) return 0; - return frustum; - }; + if (geom instanceof ThreeBSP.Geometry) + return geom.tree.numPolygons(); - /** @memberOf GEO */ - GEO.VisibleByCamera = function(camera, matrix, shape) { - var frustum = new THREE.Frustum(); - var cameraProjectionMatrix = new THREE.Matrix4(); + if (geom.type == 'BufferGeometry') { + var attr = geom.getAttribute('position'); + return attr ? attr.count / 3 : 0; + } - camera.updateMatrixWorld(); - camera.matrixWorldInverse.getInverse( camera.matrixWorld ); - cameraProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse); - frustum.setFromMatrix( cameraProjectionMatrix ); + // special array of polygons + if (geom && geom.polygons) return geom.polygons.length; - 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 geom.faces.length; + }; - return false; - }; + /** @memberOf GEO */ + GEO.numGeometryVertices = function (geom) { + if (!geom) return 0; - /** @memberOf GEO */ - GEO.numGeometryFaces = function(geom) { - if (!geom) return 0; + if (geom instanceof ThreeBSP.Geometry) + return geom.tree.numPolygons() * 3; - if (geom instanceof ThreeBSP.Geometry) - return geom.tree.numPolygons(); + if (geom.type == 'BufferGeometry') { + var attr = geom.getAttribute('position'); + return attr ? attr.count : 0; + } - if (geom.type == 'BufferGeometry') { - var attr = geom.getAttribute('position'); - return attr ? attr.count / 3 : 0; - } + if (geom && geom.polygons) return geom.polygons.length * 4; - // special array of polygons - if (geom && geom.polygons) return geom.polygons.length; + return geom.vertices.length; + }; - return geom.faces.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; k < len; ++k) + if (stack1[k] !== stack2[k]) return k; + return len; + }; - /** @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; + obj._refid = this.origin.length; + this.origin.push(obj); + if (sublevel > this.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; + 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 (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; + if (sublevel > 1) return; - this.nodes = []; + this.nodes = []; - var sortarr = []; + var sortarr = []; - // first create nodes objects - for (var n=0; n 10) ? 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 < this.last; ++n) entry.stack[n] = this.stack[n + 1]; // copy stack + return entry; + }; + + if (arg.domatrix) { + arg.matrices = []; + arg.mpool = [new THREE.Matrix4()]; // pool of Matrix objects to avoid permanent creation + arg.getmatrix = function () { + return this.matrices[this.last]; + } } - } else { - clone.vis = obj.fRnrSelf; + } - // when the only node is selected, draw it - if ((n===0) && (this.nodes.length===1)) clone.vis = true; - } + var res = 0, node = this.nodes[arg.nodeid]; - // shape with zero volume or without faces will not be observed - if ((clone.vol <= 0) || (clone.nfaces <= 0)) clone.vis = false; + if (arg.domatrix) { + if (!arg.mpool[arg.last + 1]) + arg.mpool[arg.last + 1] = new THREE.Matrix4(); - if (clone.vis) res++; - } + var prnt = (arg.last > 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; + } + } - return res; - }; + if (node.vis && (vislvl >= 0)) { + if (!arg.func || arg.func(node)) res++; + } - GEO.ClonedNodes.prototype.GetVisibleFlags = function() { - // function extract only visibility flags, used to transfer them to the worker - var res = []; - for (var n=0;n node.depth)) vislvl = node.depth; - if (!this.nodes) return 0; + //if (arg.last > arg.stack.length - 2) + // throw 'ScanVisible: stack capacity ' + arg.stack.length + ' is not enough'; - if (vislvl === undefined) { - vislvl = 99999; - if (!arg) arg = {}; - arg.stack = new Array(100); // current stack - arg.nodeid = 0; - arg.counter = 0; // sequence ID of the node, used to identify it later - arg.last = 0; - arg.CopyStack = function(factor) { - var entry = { nodeid: this.nodeid, seqid: this.counter, stack: (this.last>10) ? 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)) { + 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.domatrix) { - arg.matrices = []; - arg.mpool = [ new THREE.Matrix4() ]; // pool of Matrix objects to avoid permanent creation - arg.getmatrix = function() { return this.matrices[this.last]; } - } - } + 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; + } - var res = 0, node = this.nodes[arg.nodeid]; + return res; + }; - if (arg.domatrix) { - if (!arg.mpool[arg.last+1]) - arg.mpool[arg.last+1] = new THREE.Matrix4(); + /** 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 : ""; + }; - var prnt = (arg.last > 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; - } - } + GEO.ClonedNodes.prototype.ResolveStack = function (stack, withmatrix) { - if (node.vis && (vislvl>=0)) { - if (!arg.func || arg.func(node)) res++; - } + var res = {id: 0, obj: null, node: this.nodes[0], name: this.name_prefix}; - arg.counter++; + // if (!this.toplevel || (this.nodes.length === 1) || (res.node.kind === 1)) res.name = ""; - if ((node.depth !== undefined) && (vislvl > node.depth)) vislvl = node.depth; + if (withmatrix) { + res.matrix = new THREE.Matrix4(); + if (res.node.matrix) res.matrix.fromArray(res.node.matrix); + } - //if (arg.last > arg.stack.length - 2) - // throw 'ScanVisible: stack capacity ' + arg.stack.length + ' is not enough'; + if (this.origin) + res.obj = this.origin[0]; - 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 (!res.name) + // res.name = this.GetNodeName(0); - 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; - } + if (stack) + for (var lvl = 0; lvl < stack.length; ++lvl) { + res.id = res.node.chlds[stack[lvl]]; + res.node = this.nodes[res.id]; - return res; - }; + if (this.origin) + res.obj = this.origin[res.id]; - /** 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 : ""; - }; + var subname = this.GetNodeName(res.id); + if (subname) { + if (res.name) res.name += "/"; + res.name += subname; + } - 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 (node.fElements !== null) prop.chlds = node.fElements.arr; - 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 (visible) { + var _opacity = Math.min(1, node.fRGBA[3]); + prop.fillcolor = new THREE.Color(node.fRGBA[0], node.fRGBA[1], node.fRGBA[2]); + 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; + } + + var volume = node.fVolume; + + var prop = { + name: GEO.ObjectName(volume), + nname: GEO.ObjectName(node), + volume: node.fVolume, + shape: volume.fShape, + material: null, + chlds: null + }; + + if (node.fVolume.fNodes !== null) prop.chlds = node.fVolume.fNodes.arr; + + if (volume) prop.linewidth = volume.fLineWidth; + + if (visible) { + var _opacity = 1.0; + if ((volume.fFillColor > 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 = JSROOT.Painter.root_colors[volume.fMedium.fMaterial.fFillColor]; - } - if (prop.fillcolor === undefined) - prop.fillcolor = "lightgrey"; + 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; + 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; - }; + 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 + 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'); + 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]]; + 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; + 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; - } + if (!maxnumnodes) maxnumnodes = maxnumfaces / 100; + var arg = { + facecnt: 0, + viscnt: new Int32Array(this.nodes.length), // counter for each node + // nodes: this.nodes, + func: function (node) { + this.facecnt += node.nfaces; + this.viscnt[node.id]++; 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); + for (var n = 0; n < arg.viscnt.length; ++n) arg.viscnt[n] = 0; - return { lst: arg.items, complete: minVol === 0 }; - }; + var total = this.ScanVisible(arg), minVol = 0, maxVol = 0, camVol = -1, camFact = 10, + sortidcut = this.nodes.length + 1; - 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 + // console.log('Total visible nodes ' + total + ' numfaces ' + arg.facecnt); - var indx2 = 0, del = []; - for (var indx1=0; (indx1 maxnumfaces) { - while ((indx2 < prev.length) && (prev[indx2].seqid < current[indx1].seqid)) { - del.push(prev[indx2++]); // this entry should be removed - } + var bignumfaces = maxnumfaces * (frustum ? 0.8 : 1.0), + bignumnodes = maxnumnodes * (frustum ? 0.8 : 1.0); - if ((indx2 < prev.length) && (prev[indx2].seqid === current[indx1].seqid)) { - if (prev[indx2].done) current[indx1].done = true; // copy ready flag - indx2++; - } - } + // define minimal volume, which always to shown + var boundary = this.GetVolumeBoundary(arg.viscnt, bignumfaces, bignumnodes); - // remove rest - while (indx2 maxnumfaces * 0.2) + camVol = this.GetVolumeBoundary(arg.viscnt, maxnumfaces * 0.2, maxnumnodes * 0.2).min; + else + camVol = 0; - if (shape._id === undefined) { - shape._id = shapes.length; + camFact = maxVol / ((camVol > 0) ? (camVol > 0) : minVol); - shapes.push({ id: shape._id, shape: shape, vol: this.nodes[entry.nodeid].vol, refcnt: 1, factor: 1, ready: false }); + // console.log('Limit for camera ' + camVol + ' faces in camera view ' + arg.totalcam); + } + } - // shapes.push( { obj: shape, vol: this.nodes[entry.nodeid].vol }); - } else { - shapes[shape._id].refcnt++; - } + arg.items = []; - entry.shape = shapes[shape._id]; // remember shape used + 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; + }; - // use maximal importance factor to push element to the front - if (entry.factor && (entry.factor>entry.shape.factor)) - entry.shape.factor = entry.factor; - } + this.ScanVisible(arg); - // now sort shapes in volume decrease order - shapes.sort(function(a,b) { return b.vol*b.factor - a.vol*a.factor; }); + return {lst: arg.items, complete: minVol === 0}; + }; - // now set new shape ids according to the sorted order and delete temporary field - for (var n=0;n entry.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 < shapes.length; ++n) { + var item = shapes[n]; + item.id = n; // set new ID + delete item.shape._id; // remove temporary field + } + + // as last action set current shape id to each entry + for (var i = 0; i < lst.length; ++i) { + var entry = lst[i]; + if (entry.shape) { + entry.shapeid = entry.shape.id; // keep only id for the entry + delete entry.shape; // remove direct references + } + } + + return shapes; + }; + + GEO.ClonedNodes.prototype.MergeShapesLists = function (oldlst, newlst) { + + if (!oldlst) return newlst; + + // set geometry to shape object itself + for (var n = 0; n < oldlst.length; ++n) { + var item = oldlst[n]; + + item.shape._geom = item.geom; + delete item.geom; + + if (item.geomZ !== undefined) { + item.shape._geomZ = item.geomZ; + delete item.geomZ; + } + } + + // take from shape (if match) + for (var n = 0; n < newlst.length; ++n) { + var item = newlst[n]; + + if (item.shape._geom !== undefined) { + item.geom = item.shape._geom; + delete item.shape._geom; + } + + if (item.shape._geomZ !== undefined) { + item.geomZ = item.shape._geomZ; + delete item.shape._geomZ; + } + } + + // now delete all unused geometries + for (var n = 0; n < oldlst.length; ++n) { + var item = oldlst[n]; delete item.shape._geom; - } - - if (item.shape._geomZ !== undefined) { - item.geomZ = item.shape._geomZ; delete item.shape._geomZ; - } - } + } - // now delete all unused geometries - 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; + // if enough faces are produced, nothing else is required + if (res.done) { + item.ready = true; + continue; } - 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; 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; + } + } - shape.geomZ = shape.geom.clone(); + res.done = true; - shape.geomZ.scale(flip.x, flip.y, flip.z); + return res; + }; - var face, d, n = 0; - while(n < shape.geomZ.faces.length) { - face = geom.faces[n++]; - d = face.b; face.b = face.c; face.c = d; + 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 < chlds.length; ++k) { + var chld = chlds[k]; + if (!chld || !chld.fName) continue; + if (!chld.$geo_suffix) { + var indx = names.indexOf(chld.fName); + if (indx >= 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; n < len; n += 3) { + newpos[n] = pos[n + shift]; + newpos[n + 1] = pos[n + 1 + shift]; + newpos[n + 2] = -pos[n + 2 + shift]; + + newnorm[n] = norm[n + shift]; + newnorm[n + 1] = norm[n + 1 + shift]; + newnorm[n + 2] = -norm[n + 2 + shift]; + + shift += 3; + if (shift === 6) shift = -3; // values 0,3,-3 + } + + shape.geomZ = new THREE.BufferGeometry(); + shape.geomZ.addAttribute('position', new THREE.BufferAttribute(newpos, 3)); + shape.geomZ.addAttribute('normal', new THREE.BufferAttribute(newnorm, 3)); + // normals are calculated with normal geometry and correctly scaled + // geom.computeVertexNormals(); + + } else { + + shape.geomZ = shape.geom.clone(); + + shape.geomZ.scale(flip.x, flip.y, flip.z); + + var face, d, n = 0; + while (n < shape.geomZ.faces.length) { + face = geom.faces[n++]; + d = face.b; + face.b = face.c; + face.c = d; + } + + // normals are calculated with normal geometry and correctly scaled + // geom.computeFaceNormals(); + } + } + + var mesh = new THREE.Mesh(shape.geomZ, material); + mesh.scale.copy(flip); + mesh.updateMatrix(); + + mesh._flippedMesh = true; + + return mesh; + }; + + /** Cleanup shape entity + * @private */ + GEO.cleanupShape = function (shape) { + if (!shape) return; + + if (shape.geom && (typeof shape.geom.dispose == 'funciton')) + shape.geom.dispose(); + + if (shape.geomZ && (typeof shape.geomZ.dispose == 'funciton')) + shape.geomZ.dispose(); + + delete shape.geom; + delete shape.geomZ; + }; + + GEO.produceRenderOrder = function (toplevel, origin, method, clones) { + // function scans throug hierarchy of objects and try to set renderOrder + // algorithm is not perfect, but better then nothing + + var raycast = new THREE.Raycaster(); + + function setdefaults(top) { + if (!top) return; + top.traverse(function (obj) { + obj.renderOrder = 0; + if (obj.material) obj.material.depthWrite = true; // by default depthWriting enabled + }); + } + + function traverse(obj, lvl, arr) { + // traverse hierarchy and extract all children of given level + // if (obj.$jsroot_depth===undefined) return; + + if (!obj.children) return; + + for (var k = 0; k < obj.children.length; ++k) { + var chld = obj.children[k]; + if (chld.$jsroot_order === lvl) { + if (chld.material) { + if (chld.material.transparent) { + chld.material.depthWrite = false; // disable depth writing for transparent + arr.push(chld); + } else { + setdefaults(chld); + } + } + } else if ((obj.$jsroot_depth === undefined) || (obj.$jsroot_depth < lvl)) traverse(chld, lvl, arr); + } + } + + function sort(arr, minorder, maxorder) { + // resort meshes using ray caster and camera position + // idea to identify meshes which are in front or behind + + if (arr.length > 300) { + // too many of them, just set basic level and exit + for (var i = 0; i < arr.length; ++i) arr[i].renderOrder = (minorder + maxorder) / 2; + return false; } - // normals are calculated with normal geometry and correctly scaled - // geom.computeFaceNormals(); - } - } + // first calculate distance to the camera + // it gives preliminary order of volumes - var mesh = new THREE.Mesh( shape.geomZ, material ); - mesh.scale.copy(flip); - mesh.updateMatrix(); + for (var i = 0; i < arr.length; ++i) { + var mesh = arr[i], + box3 = mesh.$jsroot_box3; - mesh._flippedMesh = true; + if (!box3) + mesh.$jsroot_box3 = box3 = GEO.getBoundingBox(mesh); - return mesh; - }; + if (method === 'size') { + mesh.$jsroot_distance = box3.getSize(new THREE.Vector3()); + continue; + } - /** Cleanup shape entity - * @private */ - GEO.cleanupShape = function(shape) { - if (!shape) return; + if (method === "pnt") { + mesh.$jsroot_distance = origin.distanceTo(box3.getCenter()); + continue; + } - if (shape.geom && (typeof shape.geom.dispose == 'funciton')) - shape.geom.dispose(); + var dist = Math.min(dist, origin.distanceTo(box3.min), origin.distanceTo(box3.max)); - if (shape.geomZ && (typeof shape.geomZ.dispose == 'funciton')) - shape.geomZ.dispose(); + var pnt = new THREE.Vector3(box3.min.x, box3.min.y, box3.max.z); + dist = Math.min(dist, origin.distanceTo(pnt)); + pnt.set(box3.min.x, box3.max.y, box3.min.z); + dist = Math.min(dist, origin.distanceTo(pnt)); + pnt.set(box3.max.x, box3.min.y, box3.min.z); + dist = Math.min(dist, origin.distanceTo(pnt)); - delete shape.geom; - delete shape.geomZ; - }; + pnt.set(box3.max.x, box3.max.y, box3.min.z); + dist = Math.min(dist, origin.distanceTo(pnt)); - GEO.produceRenderOrder = function(toplevel, origin, method, clones) { - // function scans throug hierarchy of objects and try to set renderOrder - // algorithm is not perfect, but better then nothing + pnt.set(box3.max.x, box3.min.y, box3.max.z); + dist = Math.min(dist, origin.distanceTo(pnt)); - var raycast = new THREE.Raycaster(); + pnt.set(box3.min.x, box3.max.y, box3.max.z); + dist = Math.min(dist, origin.distanceTo(pnt)); - function setdefaults(top) { - if (!top) return; - top.traverse(function(obj) { - obj.renderOrder = 0; - if (obj.material) obj.material.depthWrite = true; // by default depthWriting enabled - }); - } - - function traverse(obj, lvl, arr) { - // traverse hierarchy and extract all children of given level - // if (obj.$jsroot_depth===undefined) return; - - if (!obj.children) return; - - for (var k=0;k300) { - // 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(); - var pnt = new THREE.Vector3(box3.min.x, box3.min.y, box3.max.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - pnt.set(box3.min.x, box3.max.y, box3.min.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - pnt.set(box3.max.x, box3.min.y, box3.min.z); - dist = Math.min(dist, origin.distanceTo(pnt)); + for (var ntry = 0; ntry < 2; ++ntry) { - pnt.set(box3.max.x, box3.max.y, box3.min.z); - dist = Math.min(dist, origin.distanceTo(pnt)); + direction.sub(origin).normalize(); - pnt.set(box3.max.x, box3.min.y, box3.max.z); - dist = Math.min(dist, origin.distanceTo(pnt)); + raycast.set(origin, direction); - pnt.set(box3.min.x, box3.max.y, box3.max.z); - dist = Math.min(dist, origin.distanceTo(pnt)); + var intersects = raycast.intersectObjects(arr, false); // only plain array - mesh.$jsroot_distance = dist; - } + var unique = []; - arr.sort(function(a,b) { return a.$jsroot_distance - b.$jsroot_distance; }); + for (var k1 = 0; k1 < intersects.length; ++k1) { + if (unique.indexOf(intersects[k1].object) < 0) unique.push(intersects[k1].object); + // if (intersects[k1].object === mesh) break; // trace until object itself + } - var resort = new Array(arr.length); + intersects = unique; - for (var i=0;i 0)) + console.log('MISS', clones ? clones.ResolveStack(mesh.stack).name : "???"); - if (method==="ray") - for (var i=arr.length-1;i>=0;--i) { - var mesh = arr[i], - box3 = mesh.$jsroot_box3, - direction = box3.getCenter(); + if ((intersects.indexOf(mesh) >= 0) || (ntry > 0)) break; - for(var ntry=0; ntry<2;++ntry) { + var pos = mesh.geometry.attributes.position.array; - direction.sub(origin).normalize(); + 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); - raycast.set( origin, direction ); + direction.applyMatrix4(mesh.matrixWorld); + } - var intersects = raycast.intersectObjects(arr, false); // only plain array + // now push first object in intersects to the front + for (var k1 = 0; k1 < intersects.length - 1; ++k1) { + var mesh1 = intersects[k1], mesh2 = intersects[k1 + 1], + i1 = mesh1.$jsroot_index, i2 = mesh2.$jsroot_index; + if (i1 < i2) continue; + for (var ii = i2; ii < i1; ++ii) { + resort[ii] = resort[ii + 1]; + resort[ii].$jsroot_index = ii; + } + resort[i1] = mesh2; + mesh2.$jsroot_index = i1; + } - 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); + for (var i = 0; i < resort.length; ++i) { + resort[i].renderOrder = maxorder - (i + 1) / (resort.length + 1) * (maxorder - minorder); + delete resort[i].$jsroot_index; + delete resort[i].$jsroot_distance; } - // 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 ); + if (obj3d.matrixWorld.determinant() > -0.9) { + mesh = new THREE.Mesh(shape.geom, prop.material); + } else { + mesh = GEO.createFlippedMesh(obj3d, shape, prop.material); } - } - } - return box3; - }; + 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; + return GEO; })); diff --git a/dataforge-vis-spatial-js/src/main/resources/ThreeCSG.js b/dataforge-vis-spatial-js/src/main/resources/ThreeCSG.js index f7afe9e9..541c6429 100644 --- a/dataforge-vis-spatial-js/src/main/resources/ThreeCSG.js +++ b/dataforge-vis-spatial-js/src/main/resources/ThreeCSG.js @@ -1,905 +1,908 @@ -(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 { +(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'); + if (typeof THREE == 'undefined') + throw new Error('THREE is not defined', 'ThreeCSG.js'); - ThreeBSP = factory(THREE); - } -} (function(THREE, ThreeBSP) { + ThreeBSP = factory(THREE); + } +}(function (THREE, ThreeBSP) { - "use strict"; + "use strict"; - if (!ThreeBSP) ThreeBSP = {}; + if (!ThreeBSP) ThreeBSP = {}; - var EPSILON = 1e-5, - COPLANAR = 0, - FRONT = 1, - BACK = 2, - SPANNING = 3; + 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 + 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; + 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) { + 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 < polygons.length; ++i) { + var polygon = polygons[i]; + if (transfer_matrix) { + for (var n = 0; n < polygon.vertices.length; ++n) + polygon.vertices[n].applyMatrix4(transfer_matrix); + } + + polygon.calculateProperties(); + } + + this.tree = new ThreeBSP.Node(polygons, nodeid); + if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; + return this; + + } else { + throw 'ThreeBSP: Given geometry is unsupported'; + } + + var polygons = [], + nfaces = geometry.faces.length, + face, polygon, vertex; + + for (var i = 0; i < nfaces; ++i) { + face = geometry.faces[i]; + // faceVertexUvs = geometry.faceVertexUvs[0][i]; 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); + if (face instanceof THREE.Face3) { + vertex = geometry.vertices[face.a]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; + vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[0].x, face.vertexNormals[0].y, face.vertexNormals[0].z /*face.normal , uvs */); + if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); - 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); + vertex = geometry.vertices[face.b]; + //uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; + vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[1].x, face.vertexNormals[1].y, face.vertexNormals[1].z/*face.normal , uvs */); + if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); - 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); + vertex = geometry.vertices[face.c]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; + vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[2].x, face.vertexNormals[2].y, face.vertexNormals[2].z /*face.normal, uvs */); + if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); + } else if (typeof THREE.Face4) { + vertex = geometry.vertices[face.a]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; + vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[0].x, face.vertexNormals[0].y, face.vertexNormals[0].z /*, uvs */); + if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); - if (flippedMesh) polygon.vertices.push( vert1, vert3, vert2 ); - else polygon.vertices.push( vert1, vert2, vert3 ); + vertex = geometry.vertices[face.b]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; + vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[1].x, face.vertexNormals[1].y, face.vertexNormals[1].z /*, uvs */); + if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); - polygon.calculateProperties(); - polygons.push( polygon ); - } + vertex = geometry.vertices[face.c]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; + vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[2].x, face.vertexNormals[2].y, face.vertexNormals[2].z /*, uvs */); + if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); - 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 1); - ThreeBSP.Geometry.prototype.direct_union = function( other_tree ) { - var a = this.tree, - b = other_tree.tree; + while (foundpair) { + foundpair = false; - a.clipTo( b ); - b.clipTo( a ); - b.invert(); - b.clipTo( a ); - b.invert(); - a.build( b.collectPolygons([]) ); - return this; - }; + for (i1 = 0; i1 < len - 1; ++i1) { + p1 = parts[i1]; + if (!p1 || !p1.parent) continue; + for (i2 = i1 + 1; i2 < len; ++i2) { + p2 = parts[i2]; + if (p2 && (p1.parent === p2.parent) && (p1.nsign === p2.nsign)) { - ThreeBSP.Geometry.prototype.direct_intersect = function( other_tree ) { - var a = this.tree, - b = other_tree.tree; + if (p1.nsign !== p1.parent.nsign) p1.parent.flip(); - a.invert(); - b.clipTo( a ); - b.invert(); - a.clipTo( b ); - b.clipTo( a ); - a.build( b.collectPolygons([]) ); - a.invert(); - return this; - }; + nreduce++; + parts[i1] = p1.parent; + parts[i2] = null; + if (p1.parent.vertices.length < 3) console.log('something wrong with parent'); + foundpair = true; + break; + } + } + } + } + } - ThreeBSP.CreateNormal = function(axis_name, pos, size) { - // create geometry to make cut on specified axis + if (nreduce > 0) { + polygons.splice(0, polygons.length); - var vert1, vert2, vert3; - - if (!size || (size<10000)) size = 10000; - - switch(axis_name) { - case "x": - vert1 = new ThreeBSP.Vertex(pos, -3*size, size, 1, 0, 0), - vert3 = new ThreeBSP.Vertex(pos, size, size, 1, 0, 0), - vert2 = new ThreeBSP.Vertex(pos, size, -3*size, 1, 0, 0); - break; - case "y": - vert1 = new ThreeBSP.Vertex(-3*size, pos, size, 0, 1, 0), - vert2 = new ThreeBSP.Vertex( size, pos, size, 0, 1, 0), - vert3 = new ThreeBSP.Vertex( size, pos, -3*size, 0, 1, 0); - break; - case "z": - vert1 = new ThreeBSP.Vertex(-3*size, size, pos, 0, 0, 1), - vert3 = new ThreeBSP.Vertex( size, size, pos, 0, 0, 1), - vert2 = new ThreeBSP.Vertex( size, -3*size, pos, 0, 0, 1); - break; - } - - var polygon = new ThreeBSP.Polygon([vert1, vert2, vert3]); - polygon.calculateProperties(); - - var node = new ThreeBSP.Node([polygon]); - - return new ThreeBSP.Geometry(node); - }; - - - ThreeBSP.Geometry.prototype.cut_from_plane = function( other_tree) { - // just cut peaces from second geometry, which just simple plane - - var a = this.tree, - b = other_tree.tree; - - a.invert(); - b.clipTo( a ); - - return this; - }; - - - ThreeBSP.Geometry.prototype.toGeometry = function() { - var i, j, - matrix = this.matrix ? new THREE.Matrix4().getInverse( this.matrix ) : null, - geometry = new THREE.Geometry(), - polygons = this.tree.collectPolygons([]), - polygon_count = polygons.length, - polygon, polygon_vertice_count, - vertice_dict = {}, - vertex_idx_a, vertex_idx_b, vertex_idx_c, - vertex, face; - - for ( i = 0; i < polygon_count; ++i ) { - polygon = polygons[i]; - polygon_vertice_count = polygon.vertices.length; - - for ( j = 2; j < polygon_vertice_count; ++j ) { - // verticeUvs = []; - - vertex = polygon.vertices[0]; - // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); - vertex = new THREE.Vector3( vertex.x, vertex.y, vertex.z ); - if (matrix) vertex.applyMatrix4(matrix); - - if ( typeof vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] !== 'undefined' ) { - vertex_idx_a = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ]; - } else { - geometry.vertices.push( vertex ); - vertex_idx_a = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] = geometry.vertices.length - 1; + for (n = 0; n < arr.length; ++n) { + parts = arr[n]; + if (parts !== undefined) + for (i1 = 0, len = parts.length; i1 < len; ++i1) + if (parts[i1]) polygons.push(parts[i1]); } - vertex = polygon.vertices[j-1]; - // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); - vertex = new THREE.Vector3( vertex.x, vertex.y, vertex.z ); - if (matrix) vertex.applyMatrix4(matrix); - if ( typeof vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] !== 'undefined' ) { - vertex_idx_b = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ]; - } else { - geometry.vertices.push( vertex ); - vertex_idx_b = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] = geometry.vertices.length - 1; + } + }; + + ThreeBSP.Geometry.prototype.direct_subtract = function (other_tree) { + var a = this.tree, + b = other_tree.tree; + a.invert(); + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.collectPolygons([])); + a.invert(); + return this; + }; + + ThreeBSP.Geometry.prototype.direct_union = function (other_tree) { + var a = this.tree, + b = other_tree.tree; + + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.collectPolygons([])); + return this; + }; + + ThreeBSP.Geometry.prototype.direct_intersect = function (other_tree) { + var a = this.tree, + b = other_tree.tree; + + a.invert(); + b.clipTo(a); + b.invert(); + a.clipTo(b); + b.clipTo(a); + a.build(b.collectPolygons([])); + a.invert(); + return this; + }; + + ThreeBSP.CreateNormal = function (axis_name, pos, size) { + // create geometry to make cut on specified axis + + var vert1, vert2, vert3; + + if (!size || (size < 10000)) size = 10000; + + switch (axis_name) { + case "x": + vert1 = new ThreeBSP.Vertex(pos, -3 * size, size, 1, 0, 0), + vert3 = new ThreeBSP.Vertex(pos, size, size, 1, 0, 0), + vert2 = new ThreeBSP.Vertex(pos, size, -3 * size, 1, 0, 0); + break; + case "y": + vert1 = new ThreeBSP.Vertex(-3 * size, pos, size, 0, 1, 0), + vert2 = new ThreeBSP.Vertex(size, pos, size, 0, 1, 0), + vert3 = new ThreeBSP.Vertex(size, pos, -3 * size, 0, 1, 0); + break; + case "z": + vert1 = new ThreeBSP.Vertex(-3 * size, size, pos, 0, 0, 1), + vert3 = new ThreeBSP.Vertex(size, size, pos, 0, 0, 1), + vert2 = new ThreeBSP.Vertex(size, -3 * size, pos, 0, 0, 1); + break; + } + + var polygon = new ThreeBSP.Polygon([vert1, vert2, vert3]); + polygon.calculateProperties(); + + var node = new ThreeBSP.Node([polygon]); + + return new ThreeBSP.Geometry(node); + }; + + + ThreeBSP.Geometry.prototype.cut_from_plane = function (other_tree) { + // just cut peaces from second geometry, which just simple plane + + var a = this.tree, + b = other_tree.tree; + + a.invert(); + b.clipTo(a); + + return this; + }; + + + ThreeBSP.Geometry.prototype.toGeometry = function () { + var i, j, + matrix = this.matrix ? new THREE.Matrix4().getInverse(this.matrix) : null, + geometry = new THREE.Geometry(), + polygons = this.tree.collectPolygons([]), + polygon_count = polygons.length, + polygon, polygon_vertice_count, + vertice_dict = {}, + vertex_idx_a, vertex_idx_b, vertex_idx_c, + vertex, face; + + for (i = 0; i < polygon_count; ++i) { + polygon = polygons[i]; + polygon_vertice_count = polygon.vertices.length; + + for (j = 2; j < polygon_vertice_count; ++j) { + // verticeUvs = []; + + vertex = polygon.vertices[0]; + // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); + vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); + if (matrix) vertex.applyMatrix4(matrix); + + if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { + vertex_idx_a = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; + } else { + geometry.vertices.push(vertex); + vertex_idx_a = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; + } + + vertex = polygon.vertices[j - 1]; + // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); + vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); + if (matrix) vertex.applyMatrix4(matrix); + if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { + vertex_idx_b = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; + } else { + geometry.vertices.push(vertex); + vertex_idx_b = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; + } + + vertex = polygon.vertices[j]; + // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); + vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); + if (matrix) vertex.applyMatrix4(matrix); + if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { + vertex_idx_c = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; + } else { + geometry.vertices.push(vertex); + vertex_idx_c = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; + } + + face = new THREE.Face3( + vertex_idx_a, + vertex_idx_b, + vertex_idx_c, + new THREE.Vector3(polygon.normal.x, polygon.normal.y, polygon.normal.z) + ); + + geometry.faces.push(face); + // geometry.faceVertexUvs[0].push( verticeUvs ); } - vertex = polygon.vertices[j]; - // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); - vertex = new THREE.Vector3( vertex.x, vertex.y, vertex.z ); - if (matrix) vertex.applyMatrix4(matrix); - if ( typeof vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] !== 'undefined' ) { - vertex_idx_c = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ]; - } else { - geometry.vertices.push( vertex ); - vertex_idx_c = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] = geometry.vertices.length - 1; + } + return geometry; + }; + + ThreeBSP.Geometry.prototype.scale = function (x, y, z) { + // try to scale as THREE.BufferGeometry + var polygons = this.tree.collectPolygons([]); + + for (var i = 0; i < polygons.length; ++i) { + var polygon = polygons[i]; + for (var k = 0; k < polygon.vertices.length; ++k) { + var v = polygon.vertices[k]; + v.x *= x; + v.y *= y; + v.z *= z; } + delete polygon.normal; + polygon.calculateProperties(); + } + }; - face = new THREE.Face3( - vertex_idx_a, - vertex_idx_b, - vertex_idx_c, - new THREE.Vector3( polygon.normal.x, polygon.normal.y, polygon.normal.z ) - ); + ThreeBSP.Geometry.prototype.toPolygons = function () { + var polygons = this.tree.collectPolygons([]); - geometry.faces.push( face ); - // geometry.faceVertexUvs[0].push( verticeUvs ); - } + this.tryToCompress(polygons); - } - return geometry; - }; - - ThreeBSP.Geometry.prototype.scale = function(x,y,z) { - // try to scale as THREE.BufferGeometry - var polygons = this.tree.collectPolygons([]); - - for (var i = 0; i < polygons.length; ++i) { - var polygon = polygons[i]; - for (var k=0; k < polygon.vertices.length; ++k) { - var v = polygon.vertices[k]; - v.x *= x; - v.y *= y; - v.z *= z; - } - delete polygon.normal; - polygon.calculateProperties(); - } - }; - - ThreeBSP.Geometry.prototype.toPolygons = function() { - var polygons = this.tree.collectPolygons([]); - - this.tryToCompress(polygons); - - for (var i = 0; i < polygons.length; ++i ) { - delete polygons[i].id; - delete polygons[i].parent; - } - - return polygons; - }; - - ThreeBSP.Geometry.prototype.toBufferGeometry = function() { - return ThreeBSP.CreateBufferGeometry(this.toPolygons()); - }; - - ThreeBSP.CreateBufferGeometry = function(polygons) { - var i, j, polygon_count = polygons.length, buf_size = 0; - - for ( i = 0; i < polygon_count; ++i ) - buf_size += (polygons[i].vertices.length - 2) * 9; - - var positions_buf = new Float32Array(buf_size), - normals_buf = new Float32Array(buf_size), - iii = 0, polygon; - - function CopyVertex(vertex) { - - positions_buf[iii] = vertex.x; - positions_buf[iii+1] = vertex.y; - positions_buf[iii+2] = vertex.z; - - normals_buf[iii] = polygon.nsign * vertex.nx; - normals_buf[iii+1] = polygon.nsign * vertex.ny; - normals_buf[iii+2] = polygon.nsign * vertex.nz; - iii+=3; - } - - for ( i = 0; i < polygon_count; ++i ) { - polygon = polygons[i]; - for ( j = 2; j < polygon.vertices.length; ++j ) { - CopyVertex(polygon.vertices[0]); - CopyVertex(polygon.vertices[j-1]); - CopyVertex(polygon.vertices[j]); - } - } - - var geometry = new THREE.BufferGeometry(); - geometry.addAttribute( 'position', new THREE.BufferAttribute( positions_buf, 3 ) ); - geometry.addAttribute( 'normal', new THREE.BufferAttribute( normals_buf, 3 ) ); - - // geometry.computeVertexNormals(); - return geometry; - }; - - ThreeBSP.Geometry.prototype.toMesh = function( material ) { - var geometry = this.toGeometry(), - mesh = new THREE.Mesh( geometry, material ); - - if (this.matrix) { - mesh.position.setFromMatrixPosition( this.matrix ); - mesh.rotation.setFromRotationMatrix( this.matrix ); - } - - return mesh; - }; - - ThreeBSP.Polygon = function( vertices, normal, w ) { - if ( !( vertices instanceof Array ) ) { - vertices = []; - } - - this.vertices = vertices; - this.nsign = 1; - if ( vertices.length > 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++; + for (var i = 0; i < polygons.length; ++i) { + delete polygons[i].id; delete polygons[i].parent; - } + } - this.divider.splitPolygon( polygons[i], this.polygons, this.polygons, front, back ); - } + return polygons; + }; - if (nodeid !== undefined) this.maxnodeid = nodeid; + ThreeBSP.Geometry.prototype.toBufferGeometry = function () { + return ThreeBSP.CreateBufferGeometry(this.toPolygons()); + }; - if ( front.length > 0 ) - this.front = new ThreeBSP.Node( front ); + ThreeBSP.CreateBufferGeometry = function (polygons) { + var i, j, polygon_count = polygons.length, buf_size = 0; - if ( back.length > 0 ) - this.back = new ThreeBSP.Node( back ); - }; + for (i = 0; i < polygon_count; ++i) + buf_size += (polygons[i].vertices.length - 2) * 9; - 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; - }; + var positions_buf = new Float32Array(buf_size), + normals_buf = new Float32Array(buf_size), + iii = 0, polygon; - ThreeBSP.Node.prototype.build = function( polygons ) { - var polygon_count = polygons.length, - front = [], back = []; + function CopyVertex(vertex) { - if ( !this.divider ) - this.divider = polygons[0].clone(); + positions_buf[iii] = vertex.x; + positions_buf[iii + 1] = vertex.y; + positions_buf[iii + 2] = vertex.z; - for (var i = 0; i < polygon_count; ++i ) - this.divider.splitPolygon( polygons[i], this.polygons, this.polygons, front, back ); + normals_buf[iii] = polygon.nsign * vertex.nx; + normals_buf[iii + 1] = polygon.nsign * vertex.ny; + normals_buf[iii + 2] = polygon.nsign * vertex.nz; + iii += 3; + } - if ( front.length > 0 ) { - if ( !this.front ) this.front = new ThreeBSP.Node(); - this.front.build( front ); - } + for (i = 0; i < polygon_count; ++i) { + polygon = polygons[i]; + for (j = 2; j < polygon.vertices.length; ++j) { + CopyVertex(polygon.vertices[0]); + CopyVertex(polygon.vertices[j - 1]); + CopyVertex(polygon.vertices[j]); + } + } - if ( back.length > 0 ) { - if ( !this.back ) this.back = new ThreeBSP.Node(); - this.back.build( back ); - } - }; + var geometry = new THREE.BufferGeometry(); + geometry.addAttribute('position', new THREE.BufferAttribute(positions_buf, 3)); + geometry.addAttribute('normal', new THREE.BufferAttribute(normals_buf, 3)); - ThreeBSP.Node.prototype.collectPolygons = function(arr) { - var len = this.polygons.length; - for (var i=0;i 0) { + this.calculateProperties(); + } else { + this.normal = this.w = undefined; + } + }; - ThreeBSP.Node.prototype.invert = function() { - var polygon_count = this.polygons.length; + 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; + }; - for (var i = 0; i < polygon_count; ++i ) - this.polygons[i].flip(); + ThreeBSP.Polygon.prototype.calculateProperties = function () { + if (this.normal) return; - this.divider.flip(); - if ( this.front ) this.front.invert(); - if ( this.back ) this.back.invert(); + var a = this.vertices[0], + b = this.vertices[1], + c = this.vertices[2]; - var temp = this.front; - this.front = this.back; - this.back = temp; + this.nsign = 1; - return this; - }; + this.normal = b.clone().subtract(a).cross( + c.clone().subtract(a) + ).normalize(); - ThreeBSP.Node.prototype.clipPolygons = function( polygons ) { + this.w = this.normal.clone().dot(a); + return this; + }; - if ( !this.divider ) return polygons.slice(); + ThreeBSP.Polygon.prototype.clone = function () { + var vertice_count = this.vertices.length, + polygon = new ThreeBSP.Polygon; - var polygon_count = polygons.length, front = [], back = []; + for (var i = 0; i < vertice_count; ++i) + polygon.vertices.push(this.vertices[i].clone()); - for (var i = 0; i < polygon_count; ++i ) - this.divider.splitPolygon( polygons[i], front, back, front, back ); + return polygon.copyProperties(this); + }; - if ( this.front ) front = this.front.clipPolygons( front ); - if ( this.back ) back = this.back.clipPolygons( back ); - else back = []; + ThreeBSP.Polygon.prototype.flip = function () { - return front.concat( back ); - }; + /// normal is not changed, only sign variable + //this.normal.multiplyScalar( -1 ); + //this.w *= -1; - ThreeBSP.Node.prototype.clipTo = function( node ) { - this.polygons = node.clipPolygons( this.polygons ); - if ( this.front ) this.front.clipTo( node ); - if ( this.back ) this.back.clipTo( node ); - }; + this.nsign *= -1; - return ThreeBSP; + 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 < len; ++i) arr.push(this.polygons[i]); + if (this.front) this.front.collectPolygons(arr); + if (this.back) this.back.collectPolygons(arr); + return arr; + }; + + ThreeBSP.Node.prototype.allPolygons = function () { + var polygons = this.polygons.slice(); + if (this.front) polygons = polygons.concat(this.front.allPolygons()); + if (this.back) polygons = polygons.concat(this.back.allPolygons()); + return polygons; + }; + + ThreeBSP.Node.prototype.numPolygons = function () { + var res = this.polygons.length; + if (this.front) res += this.front.numPolygons(); + if (this.back) res += this.back.numPolygons(); + return res; + }; + + ThreeBSP.Node.prototype.clone = function () { + var node = new ThreeBSP.Node(); + + node.divider = this.divider.clone(); + node.polygons = this.polygons.map(function (polygon) { + return polygon.clone(); + }); + node.front = this.front && this.front.clone(); + node.back = this.back && this.back.clone(); + + return node; + }; + + ThreeBSP.Node.prototype.invert = function () { + var polygon_count = this.polygons.length; + + for (var i = 0; i < polygon_count; ++i) + this.polygons[i].flip(); + + this.divider.flip(); + if (this.front) this.front.invert(); + if (this.back) this.back.invert(); + + var temp = this.front; + this.front = this.back; + this.back = temp; + + return this; + }; + + ThreeBSP.Node.prototype.clipPolygons = function (polygons) { + + if (!this.divider) return polygons.slice(); + + var polygon_count = polygons.length, front = [], back = []; + + for (var i = 0; i < polygon_count; ++i) + this.divider.splitPolygon(polygons[i], front, back, front, back); + + if (this.front) front = this.front.clipPolygons(front); + if (this.back) back = this.back.clipPolygons(back); + else back = []; + + return front.concat(back); + }; + + ThreeBSP.Node.prototype.clipTo = function (node) { + this.polygons = node.clipPolygons(this.polygons); + if (this.front) this.front.clipTo(node); + if (this.back) this.back.clipTo(node); + }; + + return ThreeBSP; }));