GDML demo renders something

This commit is contained in:
Alexander Nozik 2019-07-28 22:00:11 +03:00
parent d3500c3a57
commit e31ad5ece1
12 changed files with 268 additions and 100 deletions

View File

@ -0,0 +1,76 @@
package hep.dataforge.vis.spatial.gdml
import scientifik.gdml.GDMLPosition
import scientifik.gdml.GDMLRotation
import scientifik.gdml.GDMLSolid
import kotlin.math.PI
enum class LUnit(val value: Double) {
MM(1.0),
CM(10.0),
M(1000.0)
}
enum class AUnit(val value: Double) {
DEG(PI / 180),
RAD(1.0),
RADIAN(1.0)
}
fun GDMLPosition.unit(): LUnit = LUnit.valueOf(unit.toUpperCase())
fun GDMLPosition.x(unit: LUnit): Double = if (unit.name == this.unit) {
x.toDouble()
} else {
x.toDouble() / unit.value * unit().value
}
fun GDMLPosition.y(unit: LUnit): Double = if (unit.name == this.unit) {
y.toDouble()
} else {
y.toDouble() / unit.value * unit().value
}
fun GDMLPosition.z(unit: LUnit): Double = if (unit.name == this.unit) {
z.toDouble()
} else {
z.toDouble() / unit.value * unit().value
}
fun GDMLRotation.unit(): AUnit = AUnit.valueOf(unit.toUpperCase())
fun GDMLRotation.x(unit: AUnit = AUnit.RAD): Double = if (unit.name == this.unit) {
x.toDouble()
} else {
x.toDouble() / unit.value * unit().value
}
fun GDMLRotation.y(unit: AUnit = AUnit.RAD): Double = if (unit.name == this.unit) {
y.toDouble()
} else {
y.toDouble() / unit.value * unit().value
}
fun GDMLRotation.z(unit: AUnit = AUnit.RAD): Double = if (unit.name == this.unit) {
z.toDouble()
} else {
z.toDouble() / unit.value * unit().value
}
fun GDMLSolid.lscale(unit: LUnit): Double {
val solidUnit = lunit?.let { LUnit.valueOf(it.toUpperCase()) } ?: return 1.0
return if (solidUnit == unit) {
1.0
} else {
solidUnit.value / unit.value
}
}
fun GDMLSolid.ascale(unit: AUnit = AUnit.RAD): Double {
val solidUnit = aunit?.let { AUnit.valueOf(it.toUpperCase()) } ?: return 1.0
return if (solidUnit == unit) {
1.0
} else {
solidUnit.value / unit.value
}
}

View File

@ -1,7 +1,7 @@
package hep.dataforge.vis.spatial.gdml
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.color
@ -9,66 +9,64 @@ import hep.dataforge.vis.spatial.*
import scientifik.gdml.*
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
private fun VisualObject.withPosition(
lUnit: LUnit,
pos: GDMLPosition? = null,
rotation: GDMLRotation? = null,
scale: GDMLScale? = null
): VisualObject =
apply {
// if( this is VisualObject3D){
// pos?.let {
// x = pos.x
// y = pos.y
// z = pos.z
// }
// rotation?.let {
// rotationX = rotation.x
// rotationY = rotation.y
// rotationZ = rotation.z
// }
// } else {
pos?.let {
x = pos.x
y = pos.y
z = pos.z
}
rotation?.let {
rotationX = rotation.x
rotationY = rotation.y
rotationZ = rotation.z
}
//}
scale?.let {
scaleX = scale.x
scaleY = scale.y
scaleZ = scale.z
}
//TODO convert units if needed
): VisualObject = apply {
pos?.let {
x = pos.x(lUnit)
y = pos.y(lUnit)
z = pos.z(lUnit)
}
rotation?.let {
rotationX = rotation.x()
rotationY = rotation.y()
rotationZ = rotation.z()
}
scale?.let {
scaleX = scale.x
scaleY = scale.y
scaleZ = scale.z
}
//TODO convert units if needed
}
private inline operator fun Number.times(d: Double) = toDouble() * d
private fun VisualGroup.addSolid(
root: GDML,
solid: GDMLSolid,
lUnit: LUnit,
name: String? = null,
block: VisualObject.() -> Unit = {}
): VisualObject {
val lScale = solid.lscale(lUnit)
val aScale = solid.ascale()
return when (solid) {
is GDMLBox -> box(solid.x, solid.y, solid.z, name)
is GDMLTube -> cylinder(solid.rmax, solid.z, name) {
startAngle = solid.startphi
angle = solid.deltaphi
is GDMLBox -> box(solid.x * lScale, solid.y * lScale, solid.z * lScale, name)
is GDMLTube -> cylinder(solid.rmax * lScale, solid.z * lScale, name) {
startAngle = solid.startphi * aScale
angle = solid.deltaphi * aScale
}
is GDMLXtru -> extrude(name) {
shape {
solid.vertices.forEach {
point(it.x, it.y)
point(it.x * lScale, it.y * lScale)
}
}
solid.sections.sortedBy { it.zOrder }.forEach { section ->
layer(section.zPosition, section.xOffset, section.yOffset, section.scalingFactor)
layer(
section.zPosition * lScale,
section.xOffset * lScale,
section.yOffset * lScale,
section.scalingFactor
)
}
}
is GDMLScaledSolid -> {
@ -76,31 +74,31 @@ private fun VisualGroup.addSolid(
val innerSolid = solid.solidref.resolve(root)
?: error("Solid with tag ${solid.solidref.ref} for scaled solid ${solid.name} not defined")
addSolid(root, innerSolid) {
addSolid(root, innerSolid, lUnit) {
block()
scaleX = scaleX.toDouble() * solid.scale.x.toDouble()
scaleY = scaleY.toDouble() * solid.scale.y.toDouble()
scaleZ = scaleZ.toDouble() * solid.scale.z.toDouble()
}
}
is GDMLSphere -> sphere(solid.rmax, solid.deltaphi, solid.deltatheta, name) {
phiStart = solid.startphi.toDouble()
thetaStart = solid.starttheta.toDouble()
is GDMLSphere -> sphere(solid.rmax * lScale, solid.deltaphi * aScale, solid.deltatheta * aScale, name) {
phiStart = solid.startphi * aScale
thetaStart = solid.starttheta * aScale
}
is GDMLOrb -> sphere(solid.r, name = name)
is GDMLOrb -> sphere(solid.r * lScale, name = name)
is GDMLPolyhedra -> extrude(name) {
//getting the radius of first
require(solid.planes.size > 1) { "The polyhedron geometry requires at least two planes" }
val baseRadius = solid.planes.first().rmax.toDouble()
val baseRadius = solid.planes.first().rmax * lScale
shape {
(0..solid.numsides).forEach {
val phi = solid.deltaphi.toDouble() / solid.numsides * it + solid.startphi.toDouble()
baseRadius * cos(phi) to baseRadius * sin(phi)
val phi = solid.deltaphi * aScale / solid.numsides * it + solid.startphi * aScale
(baseRadius * cos(phi) to baseRadius * sin(phi))
}
}
solid.planes.forEach { plane ->
//scaling all radii relative to first layer radius
layer(plane.z, scale = plane.rmax.toDouble() / baseRadius)
layer(plane.z * lScale, scale = plane.rmax * lScale / baseRadius)
}
}
is GDMLBoolSolid -> {
@ -113,19 +111,59 @@ private fun VisualGroup.addSolid(
}
return composite(type, name) {
addSolid(root, first) {
withPosition(solid.resolveFirstPosition(root), solid.resolveFirstRotation(root), null)
addSolid(root, first, lUnit) {
withPosition(lUnit, solid.resolveFirstPosition(root), solid.resolveFirstRotation(root), null)
}
addSolid(root, second, lUnit) {
withPosition(lUnit, solid.resolvePosition(root), solid.resolveRotation(root), null)
}
addSolid(root, second)
withPosition(solid.resolvePosition(root), solid.resolveRotation(root), null)
}
}
}.apply(block)
}
private fun VisualGroup.addPhysicalVolume(
root: GDML,
physVolume: GDMLPhysVolume,
lUnit: LUnit,
resolveColor: GDMLMaterial.() -> Meta
) {
val volume: GDMLGroup = physVolume.volumeref.resolve(root)
?: error("Volume with ref ${physVolume.volumeref.ref} could not be resolved")
addVolume(
root,
volume,
lUnit,
physVolume.resolvePosition(root),
physVolume.resolveRotation(root),
physVolume.resolveScale(root),
resolveColor
)
}
private fun VisualGroup.addDivisionVolume(
root: GDML,
divisionVolume: GDMLDivisionVolume,
lUnit: LUnit,
resolveColor: GDMLMaterial.() -> Meta
) {
val volume: GDMLGroup = divisionVolume.volumeref.resolve(root)
?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved")
//TODO add divisions
addVolume(
root,
volume,
lUnit,
resolveColor = resolveColor
)
}
private fun VisualGroup.addVolume(
root: GDML,
group: GDMLGroup,
lUnit: LUnit,
position: GDMLPosition? = null,
rotation: GDMLRotation? = null,
scale: GDMLScale? = null,
@ -133,40 +171,37 @@ private fun VisualGroup.addVolume(
) {
group(group.name) {
withPosition(position, rotation, scale)
withPosition(lUnit, position, rotation, scale)
if (group is GDMLVolume) {
val solid = group.solidref.resolve(root)
?: error("Solid with tag ${group.solidref.ref} for volume ${group.name} not defined")
val material = group.materialref.resolve(root)
?: error("Material with tag ${group.materialref.ref} for volume ${group.name} not defined")
val material = group.materialref.resolve(root) ?: GDMLElement(group.materialref.ref)
//?: error("Material with tag ${group.materialref.ref} for volume ${group.name} not defined")
addSolid(root, solid, solid.name) {
addSolid(root, solid, lUnit, solid.name) {
color(material.resolveColor())
}
//TODO render placements
when (val vol = group.placement) {
is GDMLPhysVolume -> addPhysicalVolume(root, vol, lUnit, resolveColor)
is GDMLDivisionVolume -> addDivisionVolume(root, vol, lUnit, resolveColor)
}
}
group.physVolumes.forEach { physVolume ->
val volume: GDMLGroup = physVolume.volumeref.resolve(root)
?: error("Volume with ref ${physVolume.volumeref.ref} could not be resolved")
addVolume(
root,
volume,
physVolume.resolvePosition(root),
physVolume.resolveRotation(root),
physVolume.resolveScale(root),
resolveColor
)
addPhysicalVolume(root, physVolume, lUnit, resolveColor)
}
}
}
fun GDML.toVisual(lUnit: LUnit = LUnit.MM): VisualGroup {
val cache = HashMap<GDMLMaterial, Meta>()
val random = Random(111)
fun GDML.toVisual(): VisualGroup {
//TODO add materials cache
fun GDMLMaterial.color(): Meta = EmptyMeta
return VisualGroup().also { it.addVolume(this, world) { color() } }
fun GDMLMaterial.color(): Meta = cache.getOrPut(this) {
buildMeta { "color" to random.nextInt(0, Int.MAX_VALUE) }
}
return VisualGroup().also { it.addVolume(this, world, lUnit) { color() } }
}

View File

@ -3,9 +3,11 @@ package hep.dataforge.vis.spatial.gdml.demo
import hep.dataforge.context.Global
import hep.dataforge.vis.hmr.ApplicationBase
import hep.dataforge.vis.hmr.startApplication
import hep.dataforge.vis.spatial.gdml.LUnit
import hep.dataforge.vis.spatial.gdml.toVisual
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.w3c.dom.HTMLDivElement
@ -32,7 +34,7 @@ private class GDMLDemoApp : ApplicationBase() {
/**
* Load data from text file
*/
private fun loadData(event: Event, block: suspend (String) -> Unit) {
private fun loadData(event: Event, block: suspend CoroutineScope.(String) -> Unit) {
event.stopPropagation()
event.preventDefault()
@ -51,6 +53,25 @@ private class GDMLDemoApp : ApplicationBase() {
}
}
private fun spinner(show: Boolean) {
val style = if (show) {
"display:block;"
} else {
"display:none;"
}
document.getElementById("loader")?.setAttribute("style", style)
}
private fun message(message: String?) {
val element = document.getElementById("message")
if (message == null) {
element?.setAttribute("style", "display:none;")
} else {
element?.textContent = message
element?.setAttribute("style", "display:block;")
}
}
override fun start(state: Map<String, Any>) {
@ -59,13 +80,22 @@ private class GDMLDemoApp : ApplicationBase() {
//val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
val canvas = document.getElementById("canvas") ?: error("Element with id canvas not found on page")
canvas.clear()
val action: suspend (String) -> Unit = {
val action: suspend CoroutineScope.(String) -> Unit = {
canvas.clear()
val output = three.output(canvas)
launch { spinner(true) }
launch { message("Loading GDML") }
val gdml = GDML.format.parse(GDML.serializer(), it)
val visual = gdml.toVisual()
launch { message("Converting GDML into DF-VIS format") }
val visual = gdml.toVisual(LUnit.CM)
launch { message("Rendering") }
val output = three.output(canvas)
output.render(visual)
launch {
message(null)
spinner(false)
}
}
(document.getElementById("drop_zone") as? HTMLDivElement)?.apply {

View File

@ -6,6 +6,7 @@
<title>Three js demo for particle physics</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="main.css">
<script type="text/javascript" src="main.bundle.js"></script>
</head>
<body class="testApp">
@ -18,6 +19,8 @@
<div class="container">
<h1>GDML demo</h1>
</div>
<div class="container loader" id="loader" style="display:none;"></div>
<div class="container animate-bottom" id="message" style="display:none;"></div>
<div class="container" id="canvas"></div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"

View File

@ -0,0 +1,13 @@
.loader {
border: 16px solid #f3f3f3; /* Light grey */
border-top: 16px solid #3498db; /* Blue */
border-radius: 50%;
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View File

@ -4,6 +4,7 @@ import hep.dataforge.meta.*
import hep.dataforge.values.ValueType
import hep.dataforge.vis.common.Colors
import info.laht.threekt.materials.Material
import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.materials.MeshPhongMaterial
import info.laht.threekt.math.Color
@ -39,19 +40,20 @@ fun MetaItem<*>.color(): Color {
}
/**
* Infer FX material based on meta item
* Infer Three material based on meta item
*/
fun MetaItem<*>?.material(): Material {
return when (this) {
null -> Materials.DEFAULT
is MetaItem.ValueItem -> MeshPhongMaterial().apply {
is MetaItem.ValueItem -> MeshBasicMaterial().apply {
color = this@material.color()
}
is MetaItem.NodeItem -> MeshPhongMaterial().apply {
is MetaItem.NodeItem -> MeshBasicMaterial().apply {
(node["color"] ?: this@material).let { color = it.color() }
opacity = node["opacity"]?.double ?: 1.0
transparent = node["transparent"].boolean ?: (opacity < 1.0)
node["specularColor"]?.let { specular = it.color() }
//node["specularColor"]?.let { specular = it.color() }
side = 2
}
}
}

View File

@ -4,15 +4,19 @@ import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.int
import hep.dataforge.vis.spatial.GeometryBuilder
import hep.dataforge.vis.spatial.Point2D
import hep.dataforge.vis.spatial.Point3D
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Face3
import info.laht.threekt.core.Geometry
import info.laht.threekt.math.Vector2
import info.laht.threekt.math.Vector3
// TODO use unsafe cast instead
fun Point3D.asVector(): Vector3 = Vector3(this.x, this.y, this.z)
fun Point2D.asVector(): Vector2 = Vector2(this.x, this.y)
class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
private val vertices = ArrayList<Point3D>()
@ -38,6 +42,7 @@ class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
faces.add(face)
}
override fun build(): BufferGeometry {
return Geometry().apply {
vertices = this@ThreeGeometryBuilder.vertices.map { it.asVector() }.toTypedArray()

View File

@ -41,11 +41,12 @@ class ThreePlugin : AbstractPlugin() {
is VisualGroup -> Group(obj.mapNotNull {
try {
buildObject3D(it)
} catch (ex: Throwable){
logger.error(ex){"Failed to render $it"}
} catch (ex: Throwable) {
console.error(ex)
logger.error(ex) { "Failed to render $it" }
null
}
}).apply {
}).apply {
updatePosition(obj)
}
is Composite -> compositeFactory(obj)

View File

@ -2,10 +2,11 @@ package hep.dataforge.vis.spatial
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualLeaf
import hep.dataforge.vis.common.VisualObject
class Box(parent: VisualObject?, val xSize: Number, val ySize: Number, val zSize: Number, meta: Array<out Meta>) :
VisualObject3D(parent, meta), Shape {
VisualLeaf(parent, meta), Shape {
//TODO add helper for color configuration

View File

@ -4,6 +4,7 @@ import hep.dataforge.meta.Meta
import hep.dataforge.meta.seal
import hep.dataforge.meta.update
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualLeaf
import hep.dataforge.vis.common.VisualObject
enum class CompositeType {
@ -18,7 +19,7 @@ open class Composite(
val second: VisualObject,
val type: CompositeType = CompositeType.UNION,
meta: Array<out Meta>
) : VisualObject3D(parent, meta)
) : VisualLeaf(parent, meta)
fun VisualGroup.composite(
type: CompositeType,

View File

@ -45,10 +45,23 @@ class Extruded(parent: VisualObject?, meta: Array<out Meta>) : VisualLeaf(parent
val layers: MutableList<Layer> = ArrayList()
fun layer(z: Number, x: Number = 0.0, y: Number = 0.0, scale: Number = 1.0) {
layers.add(Layer(x,y,z,scale))
layers.add(Layer(x, y, z, scale))
//TODO send invalidation signal
}
private fun <T : Any> GeometryBuilder<T>.cap(shape: List<Point3D>) {
//FIXME won't work for non-convex shapes
val center = Point3D(
shape.map { it.x.toDouble() }.average(),
shape.map { it.y.toDouble() }.average(),
shape.map { it.z.toDouble() }.average()
)
for(i in 0 until (shape.size - 1)){
face(shape[i], shape[i+1], center, null)
}
face(shape.last(), shape.first(),center,null)
}
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
val shape: Shape2D = shape
@ -91,6 +104,8 @@ class Extruded(parent: VisualObject?, meta: Array<out Meta>) : VisualLeaf(parent
)
lowerLayer = upperLayer
}
geometryBuilder.cap(layers.first().reversed())
geometryBuilder.cap(layers.last())
}
companion object {

View File

@ -4,23 +4,9 @@ import hep.dataforge.meta.*
import hep.dataforge.names.plus
import hep.dataforge.output.Output
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualLeaf
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.asName
/**
* Performance optimized version of visual object
*/
open class VisualObject3D(parent: VisualObject?, tagRefs: Array<out Meta>) : VisualLeaf(parent, tagRefs) {
var x: Number? = null; get() = field ?: (this as VisualLeaf).x
var y: Number? = null; get() = field ?: (this as VisualLeaf).y
var z: Number? = null; get() = field ?: (this as VisualLeaf).z
var rotationX: Number? = null; get() = field ?: (this as VisualLeaf).rotationX
var rotationY: Number? = null; get() = field ?: (this as VisualLeaf).rotationY
var rotationZ: Number? = null; get() = field ?: (this as VisualLeaf).rotationZ
}
fun VisualGroup.group(key: String? = null, vararg meta: Meta, action: VisualGroup.() -> Unit = {}): VisualGroup =
VisualGroup(this, meta).apply(action).also { set(key, it) }