diff --git a/CHANGELOG.md b/CHANGELOG.md index 91d20f96..8ea6fc9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - SphereLayer solid - Hexagon interface and GenericHexagon implementation (Box inherits Hexagon) - Increased the default detail level for spheres and cones to 32 +- Simple clipping for Solids in ThreeJs ### Changed - Vision does not implement ItemProvider anymore. Property changes are done via `getProperty`/`setProperty` and `property` delegate. diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/RangeValueChooser.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/RangeValueChooser.kt new file mode 100644 index 00000000..5ad257c8 --- /dev/null +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/RangeValueChooser.kt @@ -0,0 +1,43 @@ +package space.kscience.visionforge.react + +import kotlinx.html.InputType +import kotlinx.html.js.onChangeFunction +import org.w3c.dom.HTMLInputElement +import org.w3c.dom.events.Event +import react.FunctionalComponent +import react.functionalComponent +import react.useState +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.string +import space.kscience.dataforge.values.asValue +import styled.styledInput + +@JsExport +public val RangeValueChooser: FunctionalComponent = + functionalComponent("RangeValueChooser") { props -> + var innerValue by useState(props.item.string) + + val handleChange: (Event) -> Unit = { + val newValue = (it.target as HTMLInputElement).value + props.valueChanged?.invoke(newValue.toDoubleOrNull()?.asValue()) + innerValue = newValue + } + + styledInput(type = InputType.range) { + attrs { + value = innerValue ?: "" + onChangeFunction = handleChange + val minValue = props.descriptor?.attributes?.get("min").string + minValue?.let { + min = it + } + val maxValue = props.descriptor?.attributes?.get("max").string + maxValue?.let { + max = it + } + props.descriptor?.attributes?.get("step").string?.let { + step = it + } + } + } + } \ No newline at end of file diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt index b0b448bb..7a4960c1 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt @@ -153,6 +153,7 @@ public val ValueChooser: FunctionalComponent = functionalComp rawInput -> child(StringValueChooser, props) descriptor?.widgetType == "color" -> child(ColorValueChooser, props) descriptor?.widgetType == "multiSelect" -> child(MultiSelectChooser, props) + descriptor?.widgetType == "range" -> child(RangeValueChooser, props) type == ValueType.BOOLEAN -> child(BooleanValueChooser, props) type == ValueType.NUMBER -> child(NumberValueChooser, props) descriptor?.allowedValues?.isNotEmpty() ?: false -> child(ComboValueChooser, props) diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/schemeDesctiptors.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/schemeDesctiptors.kt index 8c43d0e0..8f342b52 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/schemeDesctiptors.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/schemeDesctiptors.kt @@ -23,6 +23,11 @@ public inline fun NodeDescriptorBuilder.value( type(ValueType.NUMBER) block() } + typeOf(), typeOf(), typeOf(), typeOf(), typeOf(), typeOf() -> + value(property.name) { + type(ValueType.NUMBER) + block() + } typeOf() -> value(property.name) { type(ValueType.BOOLEAN) block() diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt index 1b9ecd0f..35cf7ab6 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt @@ -17,16 +17,7 @@ public class Composite( public val compositeType: CompositeType, public val first: Solid, public val second: Solid, -) : SolidBase(), Solid { -// -// init { -// first.parent = this -// second.parent = this -// } -// -// override val children: Map -// get() = mapOf(NameToken("first") to first, NameToken("second") to second) -} +) : SolidBase(), Solid @VisionBuilder public inline fun VisionContainerBuilder.composite( diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt index 4a7686b4..1e23d1ad 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt @@ -2,6 +2,7 @@ package space.kscience.visionforge.solid.specifications import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.descriptors.attributes import space.kscience.dataforge.names.Name import space.kscience.dataforge.values.ValueType import space.kscience.visionforge.hide @@ -9,15 +10,41 @@ import space.kscience.visionforge.scheme import space.kscience.visionforge.value import space.kscience.visionforge.widgetType -public class ClippingPlane: Scheme(){ - public var x: Double by double(0.0) - public var y: Double by double(0.0) - public var z: Double by double(0.0) +public class Clipping : Scheme() { + public var x: Double? by double() + public var y: Double? by double() + public var z: Double? by double() - public companion object: SchemeSpec(::ClippingPlane) + public companion object : SchemeSpec(::Clipping) { + override val descriptor: NodeDescriptor = NodeDescriptor { + value(Clipping::x) { + widgetType = "range" + attributes { + set("min", 0.0) + set("max", 1.0) + set("step", 0.01) + } + } + value(Clipping::y) { + widgetType = "range" + attributes { + set("min", 0.0) + set("max", 1.0) + set("step", 0.01) + } + } + value(Clipping::z) { + widgetType = "range" + attributes { + set("min", 0.0) + set("max", 1.0) + set("step", 0.01) + } + } + } + } } - public class Canvas3DOptions : Scheme() { public var axes: Axes by spec(Axes) public var light: Light by spec(Light) @@ -34,7 +61,7 @@ public class Canvas3DOptions : Scheme() { public var layers: List by numberList(0) - //public var clippingPlanes: List by list + public var clipping: Clipping by spec(Clipping) public var onSelect: ((Name?) -> Unit)? = null @@ -75,6 +102,7 @@ public class Canvas3DOptions : Scheme() { widgetType = "multiSelect" allow(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) } + scheme(Canvas3DOptions::clipping, Clipping) } } } diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/helpers/PlaneHelper.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/helpers/PlaneHelper.kt new file mode 100644 index 00000000..dbe6dc8d --- /dev/null +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/helpers/PlaneHelper.kt @@ -0,0 +1,15 @@ +@file:JsModule("three") +@file:JsNonModule +package info.laht.threekt.helpers + +import info.laht.threekt.math.Color +import info.laht.threekt.math.Plane +import info.laht.threekt.objects.LineSegments + +/** + * Helper object to visualize a [Plane]. + */ +external class PlaneHelper(plane : Plane, size : Float, hex : Color): LineSegments{ + var plane: Plane + var size: Float +} \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/math/Plane.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/math/Plane.kt index f91c0fef..2848938d 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/math/Plane.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/math/Plane.kt @@ -27,9 +27,7 @@ package info.laht.threekt.math -external class Plane { - - constructor() +external class Plane() { constructor(normal: Vector3, constant: Double) var normal: Vector3 diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt index 306bdbaf..f5629cbd 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt @@ -12,7 +12,9 @@ import info.laht.threekt.helpers.AxesHelper import info.laht.threekt.lights.AmbientLight import info.laht.threekt.materials.LineBasicMaterial import info.laht.threekt.math.Box3 +import info.laht.threekt.math.Plane import info.laht.threekt.math.Vector2 +import info.laht.threekt.math.Vector3 import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.Mesh import info.laht.threekt.scenes.Scene @@ -25,10 +27,7 @@ import space.kscience.dataforge.context.logger import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.useProperty -import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.asName -import space.kscience.dataforge.names.plus -import space.kscience.dataforge.names.toName +import space.kscience.dataforge.names.* import space.kscience.visionforge.Colors import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.specifications.* @@ -59,7 +58,7 @@ public class ThreeCanvas( private set private val scene: Scene = Scene().apply { - options.useProperty(Canvas3DOptions::axes) { axesConfig -> + options.useProperty(Canvas3DOptions::axes, this) { axesConfig -> getObjectByName(AXES_NAME)?.let { remove(it) } val axesObject = AxesHelper(axes.size.toInt()).apply { visible = axes.visible } axesObject.name = AXES_NAME @@ -67,7 +66,7 @@ public class ThreeCanvas( } //Set up light - options.useProperty(Canvas3DOptions::light) { lightConfig -> + options.useProperty(Canvas3DOptions::light, this) { lightConfig -> //remove old light if present getObjectByName(LIGHT_NAME)?.let { remove(it) } //add new light @@ -87,7 +86,7 @@ public class ThreeCanvas( translateX(spec.distance * sin(spec.zenith) * sin(spec.azimuth)) translateY(spec.distance * cos(spec.zenith)) translateZ(spec.distance * sin(spec.zenith) * cos(spec.azimuth)) - options.useProperty(Canvas3DOptions::layers) { selectedLayers -> + options.useProperty(Canvas3DOptions::layers, this) { selectedLayers -> (0..31).forEach { if (it in selectedLayers) { this@apply.layers.enable(it) @@ -98,7 +97,6 @@ public class ThreeCanvas( } } - public val camera: PerspectiveCamera = buildCamera(options.camera) private var picked: Object3D? = null @@ -107,6 +105,30 @@ public class ThreeCanvas( antialias = true }.apply { setClearColor(Colors.skyblue, 1) + //Clipping planes + localClippingEnabled = true + options.onChange(this@ThreeCanvas) { name, _, _ -> + if (name.startsWith(Canvas3DOptions::clipping.name.asName())) { + val clipping = options.clipping + boundingBox?.let { boundingBox -> + val xClippingPlane = clipping.x?.let { + val absoluteValue = boundingBox.min.x + (boundingBox.max.x - boundingBox.min.x) * it + Plane(Vector3(-1.0, 0.0, 0.0), absoluteValue) + + } + val yClippingPlane = clipping.y?.let { + val absoluteValue = boundingBox.min.y + (boundingBox.max.y - boundingBox.min.y) * it + Plane(Vector3(0.0, -1.0, 0.0), absoluteValue) + } + + val zClippingPlane = clipping.z?.let { + val absoluteValue = boundingBox.min.z + (boundingBox.max.z - boundingBox.min.z) * it + Plane(Vector3(0.0, 0.0, -1.0), absoluteValue) + } + clippingPlanes = listOfNotNull(xClippingPlane, yClippingPlane, zClippingPlane).toTypedArray() + } + } + } } private val canvas = (renderer.domElement as HTMLCanvasElement).apply { @@ -291,5 +313,6 @@ public class ThreeCanvas( private const val SELECT_NAME = "@select" private const val LIGHT_NAME = "@light" private const val AXES_NAME = "@axes" + private const val CLIP_HELPER_NAME = "@clipping" } } \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt index cae984d9..bbce8f19 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt @@ -26,7 +26,7 @@ public object ThreeCanvasLabelFactory : ThreeFactory { val canvas = document.createElement("canvas") as HTMLCanvasElement val context = canvas.getContext("2d") as CanvasRenderingContext2D context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}" - context.fillStyle = obj.color ?: "black" + context.fillStyle = obj.color.value ?: "black" context.textBaseline = CanvasTextBaseline.MIDDLE val metrics = context.measureText(obj.text) //canvas.width = metrics.width.toInt()