This commit is contained in:
Alexander Nozik 2019-12-31 16:06:38 +03:00
parent dac794b8ca
commit d36542ac5b
17 changed files with 196 additions and 122 deletions

View File

@ -1,10 +1,7 @@
package hep.dataforge.vis.common
import hep.dataforge.meta.MetaItem
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.asName
import hep.dataforge.names.isEmpty
import hep.dataforge.names.*
import kotlinx.serialization.Transient
@ -75,7 +72,7 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
protected abstract fun createGroup(name: Name): MutableVisualGroup
/**
* Add named or unnamed child to the group. If key is [null] the child is considered unnamed. Both key and value are not
* Add named or unnamed child to the group. If key is null the child is considered unnamed. Both key and value are not
* allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed.
*/
override fun set(name: Name, child: VisualObject?) {
@ -102,9 +99,13 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
structureChangeListeners.forEach { it.callback(name, child) }
}
operator fun set(key: String, child: VisualObject?) = if (key.isBlank()) {
child?.let { addStatic(child) }
} else {
set(key.asName(), child)
operator fun set(key: String, child: VisualObject?): Unit {
if (key.isBlank()) {
if(child!= null) {
addStatic(child)
}
} else {
set(key.toName(), child)
}
}
}

View File

@ -92,7 +92,8 @@ class ThreeCanvas(val three: ThreePlugin, val spec: CanvasSpec) : Renderer<Visua
override fun render(obj: VisualObject3D, meta: Meta) {
content = obj
scene.add(three.buildObject3D(obj))
val object3D = three.buildObject3D(obj)
scene.add(object3D)
}
}

View File

@ -26,7 +26,6 @@ object ThreeLabelFactory : ThreeFactory<Label3D> {
val context = canvas.getContext("2d") as CanvasRenderingContext2D
context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}"
context.fillStyle = obj.color ?: "black"
//context.textAlign = CanvasTextAlign.CENTER
context.textBaseline = CanvasTextBaseline.MIDDLE
val metrics = context.measureText(obj.text)
//canvas.width = metrics.width.toInt()

View File

@ -2,9 +2,11 @@ package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.node
import hep.dataforge.vis.spatial.PolyLine
import hep.dataforge.vis.spatial.layer
import hep.dataforge.vis.spatial.color
import hep.dataforge.vis.spatial.three.ThreeMaterials.DEFAULT_LINE_COLOR
import info.laht.threekt.core.Geometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.math.Color
import info.laht.threekt.objects.LineSegments
import kotlin.reflect.KClass
@ -18,11 +20,13 @@ object ThreeLineFactory : ThreeFactory<PolyLine> {
val material =
ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node)
material.linewidth = obj.thickness.toDouble()
material.color = obj.color?.let { Color(it) }?: DEFAULT_LINE_COLOR
return LineSegments(geometry, material).apply {
updatePosition(obj)
layers.enable(obj.layer)
//layers.enable(obj.layer)
//add listener to object properties
obj.onPropertyChange(this) { propertyName, _, _ ->
updateProperty(obj, propertyName)

View File

@ -6,7 +6,6 @@ import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.Material3D
import info.laht.threekt.materials.LineBasicMaterial
import info.laht.threekt.materials.Material
import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.materials.MeshPhongMaterial
import info.laht.threekt.math.Color
@ -25,7 +24,7 @@ object ThreeMaterials {
// private val materialCache = HashMap<Meta, Material>()
private val lineMaterialCache = HashMap<Meta?, Material>()
private val lineMaterialCache = HashMap<Meta?, LineBasicMaterial>()
// fun buildMaterial(meta: Meta): Material =
@ -37,11 +36,11 @@ object ThreeMaterials {
// //side = 2
// }
fun getLineMaterial(meta: Meta?): Material = lineMaterialCache.getOrPut(meta) {
fun getLineMaterial(meta: Meta?): LineBasicMaterial = lineMaterialCache.getOrPut(meta) {
LineBasicMaterial().apply {
color = meta["color"]?.color() ?: DEFAULT_LINE_COLOR
opacity = meta["opacity"].double ?: 1.0
transparent = meta["transparent"].boolean ?: (opacity < 1.0)
color = meta[Material3D.COLOR_KEY]?.color() ?: DEFAULT_LINE_COLOR
opacity = meta[Material3D.OPACITY_KEY].double ?: 1.0
transparent = opacity < 1.0
linewidth = meta["thickness"].double ?: 1.0
}
}
@ -98,7 +97,7 @@ fun Mesh.updateMaterial(obj: VisualObject) {
color = meta[Material3D.COLOR_KEY]?.color() ?: ThreeMaterials.DEFAULT_COLOR
specular = meta[Material3D.SPECULAR_COLOR]!!.color()
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
transparent = opacity <= 0.0
transparent = opacity < 1.0
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
needsUpdate = true
}
@ -106,7 +105,7 @@ fun Mesh.updateMaterial(obj: VisualObject) {
MeshBasicMaterial().apply {
color = meta[Material3D.COLOR_KEY]?.color() ?: ThreeMaterials.DEFAULT_COLOR
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
transparent = opacity <= 0.0
transparent = opacity < 1.0
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
needsUpdate = true
}

View File

@ -2,10 +2,7 @@ package hep.dataforge.vis.spatial.three
import hep.dataforge.context.*
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.names.isEmpty
import hep.dataforge.names.startsWith
import hep.dataforge.names.*
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.*
import info.laht.threekt.core.Object3D
@ -43,14 +40,13 @@ class ThreePlugin : AbstractPlugin() {
is Proxy -> proxyFactory(obj)
is VisualGroup3D -> {
val group = ThreeGroup()
obj.children.forEach { (name, child) ->
obj.children.forEach { (token, child) ->
if (child is VisualObject3D && child.ignore != true) {
try {
val object3D = buildObject3D(child)
object3D.name = name.toString()
group.add(object3D)
group[token] = object3D
} catch (ex: Throwable) {
logger.error(ex) { "Failed to render $name" }
logger.error(ex) { "Failed to render $child" }
}
}
}
@ -71,6 +67,31 @@ class ThreePlugin : AbstractPlugin() {
visible = obj.visible ?: true
}
}
obj.onChildrenChange(this) { name, child ->
if (name.isEmpty()) {
logger.error { "Children change with empty namr on $group" }
return@onChildrenChange
}
val parentName = name.cutLast()
val childName = name.last()!!
//removing old object
findChild(name)?.let { oldChild ->
oldChild.parent?.remove(oldChild)
}
//adding new object
if (child != null && child is VisualObject3D) {
try {
val object3D = buildObject3D(child)
set(name, object3D)
} catch (ex: Throwable) {
logger.error(ex) { "Failed to render $child" }
}
}
}
}
}
is Composite -> compositeFactory(obj)
@ -93,7 +114,34 @@ class ThreePlugin : AbstractPlugin() {
}
}
fun Object3D.findChild(name: Name): Object3D? {
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
object3D.name = token.toString()
add(object3D)
}
internal fun Object3D.getOrCreateGroup(name: Name): Object3D {
return when {
name.isEmpty() -> this
name.length == 1 -> {
val token = name.first()!!
children.find { it.name == token.toString() } ?: info.laht.threekt.objects.Group().also { group ->
group.name = token.toString()
this.add(group)
}
}
else -> getOrCreateGroup(name.first()!!.asName()).getOrCreateGroup(name.cutFirst())
}
}
internal operator fun Object3D.set(name: Name, obj: Object3D) {
when (name.length) {
0 -> error("Can't set object with an empty name")
1 -> set(name.first()!!, obj)
else -> getOrCreateGroup(name.cutLast())[name.last()!!] = obj
}
}
internal fun Object3D.findChild(name: Name): Object3D? {
return when {
name.isEmpty() -> this
name.length == 1 -> this.children.find { it.name == name.first()!!.toString() }

View File

@ -5,7 +5,10 @@ import hep.dataforge.meta.float
import hep.dataforge.meta.get
import hep.dataforge.meta.node
import hep.dataforge.vis.spatial.*
import info.laht.threekt.core.*
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.DirectGeometry
import info.laht.threekt.core.Face3
import info.laht.threekt.core.Geometry
import info.laht.threekt.external.controls.OrbitControls
import info.laht.threekt.materials.Material
import info.laht.threekt.math.Euler
@ -14,16 +17,6 @@ import info.laht.threekt.objects.Mesh
import info.laht.threekt.textures.Texture
import kotlin.math.PI
/**
* Utility methods for three.kt.
* TODO move to three project
*/
@Suppress("FunctionName")
fun Group(children: Collection<Object3D>) = info.laht.threekt.objects.Group().apply {
children.forEach { this.add(it) }
}
val VisualObject3D.euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name)
val MetaItem<*>.vector get() = Vector3(node["x"].float ?: 0f, node["y"].float ?: 0f, node["z"].float ?: 0f)

View File

@ -1,9 +1,8 @@
import org.openjfx.gradle.JavaFXOptions
import scientifik.useSerialization
plugins {
id("scientifik.mpp")
id("org.openjfx.javafxplugin")
//id("org.openjfx.javafxplugin")
id("application")
}
@ -50,6 +49,14 @@ kotlin {
implementation("io.ktor:ktor-serialization:$ktor_version")
}
}
jsMain{
dependencies{
implementation("io.ktor:ktor-client-js:$ktor_version")
implementation("io.ktor:ktor-client-serialization-js:$ktor_version")
implementation(npm("text-encoding"))
implementation(npm("abort-controller"))
}
}
}
}
@ -57,6 +64,6 @@ application {
mainClassName = "ru.mipt.npm.muon.monitor.server/MMServerKt"
}
configure<JavaFXOptions> {
modules("javafx.controls")
}
//configure<JavaFXOptions> {
// modules("javafx.controls")
//}

View File

@ -12,9 +12,9 @@ typealias Track = List<Point3D>
*
*/
@Serializable
data class Event(val track: Track?, val hits: Collection<String>) {
/**
* The unique identity for given set of hits. One identity could correspond to different tracks
*/
val id get() = hits.sorted().joinToString(separator = ", ", prefix = "[", postfix = "]");
data class Event(val id: Int, val track: Track?, val hits: Collection<String>) {
// /**
// * The unique identity for given set of hits. One identity could correspond to different tracks
// */
// val id get() = hits.sorted().joinToString(separator = ", ", prefix = "[", postfix = "]")
}

View File

@ -60,7 +60,7 @@ class Model {
map[pixel]?.color("blue")
}
private fun reset() {
fun reset() {
map.values.forEach {
it.setProperty(Material3D.MATERIAL_COLOR_KEY, null)
}
@ -73,7 +73,9 @@ class Model {
highlight(it)
}
event.track?.let {
tracks.polyline(*it.toTypedArray())
tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]"){
thickness = 4
}
}
}

View File

@ -125,8 +125,8 @@ object Monitor {
const val PIXEL_XY_SPACING = 123.2
const val PIXEL_Z_SIZE = 30.0
const val CENTRAL_LAYER_Z = 0.0
const val UPPER_LAYER_Z = 166.0
const val LOWER_LAYER_Z = -180.0
const val UPPER_LAYER_Z = -166.0
const val LOWER_LAYER_Z = 180.0
/**
* Build map for the whole monitor
@ -136,11 +136,11 @@ object Monitor {
.lineSequence()
.mapNotNull { line ->
if (line.startsWith(" ")) {
val split = line.trim().split("\\s+".toRegex());
val split = line.trim().split("\\s+".toRegex())
val detectorName = split[1];
val x = split[4].toDouble() - 500;
val y = split[5].toDouble() - 500;
val z = split[6].toDouble() - 180;
val x = split[4].toDouble() - 500
val y = split[5].toDouble() - 500
val z = 180 - split[6].toDouble()
SC16(detectorName, Point3D(x, y, z))
} else {
null

View File

@ -6,53 +6,41 @@ import hep.dataforge.js.startApplication
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.withBottom
import hep.dataforge.names.NameToken
import hep.dataforge.vis.js.editor.card
import hep.dataforge.vis.js.editor.objectTree
import hep.dataforge.vis.js.editor.propertyEditor
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_WIREFRAME_KEY
import hep.dataforge.vis.spatial.Visual3DPlugin
import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output
import hep.dataforge.vis.spatial.three.threeSettings
import hep.dataforge.vis.spatial.visible
import io.ktor.client.HttpClient
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.client.request.get
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.html.js.button
import kotlinx.html.js.onClickFunction
import org.w3c.dom.HTMLElement
import kotlin.browser.document
import kotlin.dom.clear
import kotlin.math.PI
private class GDMLDemoApp : Application {
// /**
// * Handle mouse drag according to https://www.html5rocks.com/en/tutorials/file/dndfiles/
// */
// private fun handleDragOver(event: DragEvent) {
// event.stopPropagation()
// event.preventDefault()
// event.dataTransfer?.dropEffect = "copy"
// }
//
// /**
// * Load data from text file
// */
// private fun loadData(event: DragEvent, block: (name: String, data: String) -> Unit) {
// event.stopPropagation()
// event.preventDefault()
//
// val file = (event.dataTransfer?.files as FileList)[0]
// ?: throw RuntimeException("Failed to load file")
//
// FileReader().apply {
// onload = {
// val string = result as String
// block(file.name, string)
// }
// readAsText(file)
// }
// }
private val model = Model()
private val connection = HttpClient{
install(JsonFeature) {
serializer = KotlinxSerializer(Visual3DPlugin.json)
}
}
override fun start(state: Map<String, Any>) {
val context = Global.context("demo") {}
@ -75,7 +63,25 @@ private class GDMLDemoApp : Application {
output.camera.layers.set(0)
output.camera.position.z = -2000.0
output.camera.position.y = 500.0
settingsElement.threeSettings(output)
settingsElement.threeSettings(output){
card("Events") {
button {
+"Next"
onClickFunction = {
GlobalScope.launch {
val event = connection.get<Event>("http://localhost:8080/event")
model.displayEvent(event)
}
}
}
button {
+"Clear"
onClickFunction = {
model.reset()
}
}
}
}
//tree.visualObjectTree(visual, editor::propertyEditor)
treeElement.objectTree(NameToken("World"), visual) {
editorElement.propertyEditor(it) { item ->

View File

@ -11,24 +11,19 @@
<script type="text/javascript" src="main.bundle.js"></script>
</head>
<body class="testApp">
<!--
<div class="container" id="drop_zone" data-toggle="tooltip" data-placement="right"
title="Для загрузки данных в текстовом формате, надо перетащить файл сюда">
Загрузить данные
<br/>
(перетащить файл сюда)
</div>
-->
<div class="container">
<h1>Muon monitor demo</h1>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-lg-3" id="tree"></div>
<div class="col-lg-3">
<div class="row" id ="settings"></div>
<div class="row" id ="tree"></div>
</div>
<div class="col-lg-6">
<div class="row" id="settings"></div>
<div class="row" id="canvas"></div>
<div class="container" id = "canvas"></div>
</div>
<div class="col-lg-3" id="editor"></div>
</div>

View File

@ -5,6 +5,7 @@ import hep.dataforge.vis.spatial.Visual3DPlugin
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.application.log
import io.ktor.features.CallLogging
import io.ktor.features.ContentNegotiation
import io.ktor.features.DefaultHeaders
@ -18,34 +19,43 @@ import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer
import org.apache.commons.math3.random.JDKRandomGenerator
import ru.mipt.npm.muon.monitor.Model
import ru.mipt.npm.muon.monitor.sim.UniformTrackGenerator
import ru.mipt.npm.muon.monitor.sim.Cos2TrackGenerator
import ru.mipt.npm.muon.monitor.sim.simulateOne
import java.awt.Desktop
import java.io.File
import java.net.URI
private val generator = Cos2TrackGenerator(JDKRandomGenerator(223))
fun Application.module() {
val currentDir = File(".").absoluteFile
environment.log.info("Current directory: $currentDir")
val generator = UniformTrackGenerator(JDKRandomGenerator(223))
install(DefaultHeaders)
install(CallLogging)
install(ContentNegotiation){
install(ContentNegotiation) {
serialization(json = Visual3DPlugin.json)
}
install(Routing) {
get("/next") {
call.respond(generator.simulateOne())
get("/event") {
val event = generator.simulateOne()
call.respond(event)
}
get("/geometry"){
get("/geometry") {
call.respond(Model.buildGeometry())
}
static("/") {
resources()
}
}
try {
Desktop.getDesktop().browse(URI("http://localhost:8080/index.html"))
} catch (ex: Exception) {
log.error("Failed to launch browser", ex)
}
}
fun main() {
embeddedServer(CIO, 8080,host = "localhost", module = Application::module).start(wait = true)
embeddedServer(CIO, 8080, host = "localhost", module = Application::module).start(wait = true)
}

View File

@ -54,8 +54,8 @@ fun Vector3D.toPoint() = Point3D(x, y, z)
fun Line.toPoints(): List<Point3D> {
val basePoint = basePlane.intersection(this)
val bottom = basePoint.subtract(300.0, direction)
val top = basePoint.add(300.0, direction)
val bottom = basePoint.subtract(2000.0, direction)
val top = basePoint.add(2000.0, direction)
return listOf(bottom.toPoint(), top.toPoint())
}

View File

@ -11,13 +11,14 @@ import java.util.*
// minimal track length in detector
val MINIMAL_TRACK_LENGTH = 10.0
internal const val MINIMAL_TRACK_LENGTH = 10.0
private val layerCache = HashMap<Double, Plane>()
fun findLayer(z: Double): Plane = layerCache.getOrPut(z){
Plane(Vector3D(0.0, 0.0, z), Vector3D(0.0, 0.0, 1.0),
fun findLayer(z: Double): Plane = layerCache.getOrPut(z) {
Plane(
Vector3D(0.0, 0.0, z), Vector3D(0.0, 0.0, 1.0),
Monitor.GEOMETRY_TOLERANCE
)
}
@ -34,7 +35,7 @@ fun readEffs(): Map<String, Double> {
index = 0
} else if (trimmed.isNotEmpty()) {
val eff = trimmed.split(Regex("\\s+"))[1].toDouble()
effMap.put("SC${detectorName}_${index}", eff)
effMap["SC${detectorName}_${index}"] = eff
index++
}
}
@ -42,8 +43,8 @@ fun readEffs(): Map<String, Double> {
}
fun buildEventByTrack(track: Line, hitResolver: (Line) -> Collection<SC1> = defaultHitResolver): Event {
return Event(track.toPoints(), hitResolver(track).map { it.name })
fun buildEventByTrack(index: Int, track: Line, hitResolver: (Line) -> Collection<SC1> = defaultHitResolver): Event {
return Event(index, track.toPoints(), hitResolver(track).map { it.name })
}
val defaultHitResolver: (Line) -> Collection<SC1> = { track: Line ->

View File

@ -4,14 +4,18 @@ import org.apache.commons.math3.geometry.euclidean.threed.Line
import org.apache.commons.math3.random.RandomGenerator
import ru.mipt.npm.muon.monitor.Event
import ru.mipt.npm.muon.monitor.Monitor.PIXEL_XY_SIZE
import kotlin.math.acos
import kotlin.math.pow
import kotlin.math.sin
private var counter = 0
/**
* Simulate single track and returns corresponding event
*/
fun TrackGenerator.simulateOne(): Event {
val track = generate()
return buildEventByTrack(track)
return buildEventByTrack(counter++, track)
}
interface TrackGenerator {
@ -22,13 +26,17 @@ interface TrackGenerator {
/**
* A uniform generator with track bases distributed in square in central plane, uniform phi and cos theta
*/
class UniformTrackGenerator(override val rnd: RandomGenerator, val maxX: Double = 4 * PIXEL_XY_SIZE, val maxY: Double = 4 * PIXEL_XY_SIZE) :
class UniformTrackGenerator(
override val rnd: RandomGenerator,
val maxX: Double = 4 * PIXEL_XY_SIZE,
val maxY: Double = 4 * PIXEL_XY_SIZE
) :
TrackGenerator {
override fun generate(): Line {
val x = (1 - rnd.nextDouble() * 2.0) * maxX
val y = (1 - rnd.nextDouble() * 2.0) * maxY
val phi = (1 - rnd.nextDouble() * 2.0) * Math.PI
val theta = Math.PI / 2 - Math.acos(rnd.nextDouble())
val theta = Math.PI / 2 - acos(rnd.nextDouble())
return makeTrack(x, y, theta, phi)
}
}
@ -63,10 +71,10 @@ class Cos2TrackGenerator(
for (i in 0..500) {
val thetaCandidate = Math.acos(rnd.nextDouble())
val thetaCandidate = acos(rnd.nextDouble())
val u = rnd.nextDouble()
val sin = Math.sin(thetaCandidate)
if (u < Math.pow(sin, power) / sin) {
val sin = sin(thetaCandidate)
if (u < sin.pow(power) / sin) {
return makeTrack(x, y, thetaCandidate, phi)
}
}