Compare commits

...

5 Commits
master ... 0.3

Author SHA1 Message Date
212d729afb
A prototype for context receivers 2022-04-15 12:46:05 +03:00
3198bad094
Update build. 2022-04-13 17:08:25 +03:00
9648533ac8
Update build tools 2022-04-13 15:02:35 +03:00
7ee40679b9
Update light descriptor 2022-01-28 14:17:37 +03:00
f828f86e29
Replace light model for 3ds 2022-01-27 12:10:00 +03:00
47 changed files with 549 additions and 9427 deletions

2
.gitignore vendored
View File

@ -7,3 +7,5 @@ build/
data/
!gradle-wrapper.jar
/kotlin-js-store/yarn.lock

View File

@ -2,8 +2,11 @@
## [Unreleased]
### Added
- Context receivers flag
### Changed
- Naming of Canvas3D options
- Lights are added to the scene instead of 3D options
### Deprecated

View File

@ -1,6 +1,6 @@
plugins {
id("ru.mipt.npm.gradle.project")
id("org.jetbrains.kotlinx.kover") version "0.5.0-RC"
// id("org.jetbrains.kotlinx.kover") version "0.5.0"
}
val dataforgeVersion by extra("0.5.2")
@ -8,7 +8,7 @@ val fxVersion by extra("11")
allprojects{
group = "space.kscience"
version = "0.2.0"
version = "0.3.0-dev-1"
}
subprojects {

View File

@ -1,6 +1,5 @@
import kotlinx.browser.document
import kotlinx.css.*
import react.child
import react.dom.render
import ringui.SmartTabs
import ringui.Tab
@ -8,6 +7,7 @@ import space.kscience.dataforge.context.Context
import space.kscience.plotly.models.Trace
import space.kscience.plotly.scatter
import space.kscience.visionforge.Application
import space.kscience.visionforge.Colors
import space.kscience.visionforge.VisionClient
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.ring.ThreeCanvasWithControls
@ -48,7 +48,7 @@ private class JsPlaygroundApp : Application {
}
SmartTabs("gravity") {
Tab("gravity") {
GravityDemo{
GravityDemo {
attrs {
this.context = playgroundContext
}
@ -73,6 +73,9 @@ private class JsPlaygroundApp : Application {
attrs {
context = playgroundContext
solid {
ambientLight {
color(Colors.white)
}
repeat(100) {
sphere(5, name = "sphere[$it]") {
x = random.nextDouble(-300.0, 300.0)

View File

@ -7,6 +7,7 @@ import react.fc
import space.kscience.dataforge.context.Context
import space.kscience.plotly.layout
import space.kscience.plotly.models.Trace
import space.kscience.visionforge.Colors
import space.kscience.visionforge.markup.VisionOfMarkup
import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.ring.ThreeCanvasWithControls
@ -21,10 +22,10 @@ external interface DemoProps : Props {
}
val GravityDemo = fc<DemoProps> { props ->
val velocityTrace = Trace{
val velocityTrace = Trace {
name = "velocity"
}
val energyTrace = Trace{
val energyTrace = Trace {
name = "energy"
}
val markup = VisionOfMarkup()
@ -41,6 +42,11 @@ val GravityDemo = fc<DemoProps> { props ->
attrs {
context = props.context
solid {
pointLight(200, 200, 200, name = "light"){
color(Colors.white)
}
ambientLight()
sphere(5.0, "ball") {
detail = 16
color("red")
@ -91,7 +97,7 @@ val GravityDemo = fc<DemoProps> { props ->
height = 50.vh - 50.pt
}
plotly {
traces(velocityTrace,energyTrace)
traces(velocityTrace, energyTrace)
layout {
xaxis.title = "time"
}

View File

@ -45,8 +45,9 @@ kotlin {
jvmMain {
dependencies {
implementation("org.apache.commons:commons-math3:3.6.1")
implementation(npmlibs.ktor.server.cio)
implementation(npmlibs.ktor.serialization)
implementation("io.ktor:ktor-server-cio:${ktorVersion}")
implementation("io.ktor:ktor-server-content-negotiation:${ktorVersion}")
implementation("io.ktor:ktor-serialization-kotlinx-json:${ktorVersion}")
}
}
jsMain {

View File

@ -8,6 +8,12 @@ import space.kscience.visionforge.removeAll
import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.*
import kotlin.collections.HashMap
import kotlin.collections.HashSet
import kotlin.collections.filter
import kotlin.collections.forEach
import kotlin.collections.set
import kotlin.collections.toTypedArray
import kotlin.math.PI
class Model(val manager: VisionManager) {
@ -39,7 +45,6 @@ class Model(val manager: VisionManager) {
val root: SolidGroup = SolidGroup().apply {
setAsRoot(this@Model.manager)
material {
wireframe
color("darkgreen")
}
rotationX = PI / 2

View File

@ -17,12 +17,15 @@ import react.fc
import react.useMemo
import react.useState
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.invoke
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Colors
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.ring.ThreeCanvasWithControls
import space.kscience.visionforge.ring.tab
import space.kscience.visionforge.solid.specifications.Camera
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.edges
import styled.css
@ -42,17 +45,21 @@ val MMApp = fc<MMAppProps>("Muon monitor") { props ->
val mmOptions = useMemo {
Canvas3DOptions {
camera = Camera {
camera {
distance = 2100.0
latitude = PI / 6
azimuth = PI + PI / 6
}
}
}
val root = useMemo(props.model) {
props.model.root.apply {
edges()
ambientLight{
color(Colors.white)
}
}
}

View File

@ -1,24 +1,22 @@
package ru.mipt.npm.muon.monitor.server
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
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.resources
import io.ktor.http.content.static
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.Routing
import io.ktor.routing.get
import io.ktor.serialization.json
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.application.log
import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer
import io.ktor.server.http.content.resources
import io.ktor.server.http.content.static
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import org.apache.commons.math3.random.JDKRandomGenerator
import ru.mipt.npm.muon.monitor.Model
import ru.mipt.npm.muon.monitor.sim.Cos2TrackGenerator
@ -40,8 +38,6 @@ fun Application.module(context: Context = Global) {
environment.log.info("Current directory: $currentDir")
val solidManager = context.fetch(Solids)
install(DefaultHeaders)
install(CallLogging)
install(ContentNegotiation) {
json()
}

View File

@ -32,7 +32,7 @@ kotlin {
kotlinOptions {
jvmTarget = "11"
freeCompilerArgs =
freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" + "-Xlambdas=indy"
freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" + "-Xlambdas=indy" + "-Xcontext-receivers"
}
}
testRuns["test"].executionTask.configure {

View File

@ -18,7 +18,11 @@ fun VisionLayout<Solid>.demo(name: String, title: String = name, block: SolidGro
val meta = Meta {
"title" put title
}
val vision = SolidGroup(block)
val vision = SolidGroup(block).apply {
ambientLight{
color(Colors.white)
}
}
render(Name.parse(name), vision, meta)
}
@ -39,6 +43,7 @@ val canvasOptions = Canvas3DOptions {
@OptIn(DelicateCoroutinesApi::class)
fun VisionLayout<Solid>.showcase() {
demo("shapes", "Basic shapes") {
ambientLight()
box(100.0, 100.0, 100.0) {
z = -110.0
color("teal")

View File

@ -59,7 +59,7 @@ box(10, 10, 10, name = "small box"){
rotation = Point3D(0, 0, 0)
}
```
![](../docs/images/small-box.png)
![](../images/small-box.png)
The `big box` will have properties with custom values.
```kotlin
@ -72,7 +72,7 @@ box(40, 40, 40, name = "big box"){
rotation = Point3D(60, 80, 0)
}
```
![](../docs/images/big-rotated-box.png)
![](../images/big-rotated-box.png)
If we compare these boxes, we will see all differences.
Here is the function `main` with both boxes.
@ -111,8 +111,8 @@ fun main(){
}
}
```
![](../docs/images/two-boxes-1.png)
![](../docs/images/two-boxes-2.png)
![](../images/two-boxes-1.png)
![](../images/two-boxes-2.png)
***There is plenty of other properties, especially those, which you can create by yourself. Here we mention just a small part.***
@ -142,8 +142,8 @@ polyline(Point3D(30, 20, 10), Point3D(30, -100, 30), Point3D(30, -100, 30), Poin
}
```
![](../docs/images/polyline-points.png)
![](../docs/images/polyline-points-2.png)
![](../images/polyline-points.png)
![](../images/polyline-points-2.png)
### 2) Box
@ -165,7 +165,7 @@ Let's create just usual `box` with equal ribs.
color("pink")
}
```
![](../docs/images/box.png)
![](../images/box.png)
Now, let's make `box` with bigger `y` value.
```kotlin
@ -175,7 +175,7 @@ Now, let's make `box` with bigger `y` value.
```
As you can see, only the rib of `y-axis` differs from other ribs.
![](../docs/images/high-box.png)
![](../images/high-box.png)
For a final trial, let's create a `box` with a bigger `x` value.
@ -189,7 +189,7 @@ For a final trial, let's create a `box` with a bigger `x` value.
```
Predictably, only the `x-axis` rib is bigger than other ribs.
![](../docs/images/wide-box.png)
![](../images/wide-box.png)
### 3) Sphere
@ -206,7 +206,7 @@ As for `radius`, it has `Float` type, and, as you can guess, it sets the radius
color("blue")
}
```
![](../docs/images/sphere.png)
![](../images/sphere.png)
### 4) Hexagon
@ -220,7 +220,7 @@ It is solid which has six edges. It is set by eight values: `node1`,..., `node8`
5) Edge with vertices `node1`, `node5`, `node8`, `node4`
6) Edge with vertices `node8`, `node5`, `node6`, `node7`
![](../docs/images/scheme.png)
![](../images/scheme.png)
As the hexagon takes in specific points, we understand that this solid cannot be moved, it is fixed in space, and it can't make pivots.
@ -239,7 +239,7 @@ Let's make classic parallelepiped.
color("green")
}
```
![](../docs/images/classic-hexagon.png)
![](../images/classic-hexagon.png)
Now, let's make a custom hexagon.
@ -258,7 +258,7 @@ hexagon(
color("brown")
}
```
![](../docs/images/custom-hexagon.png)
![](../images/custom-hexagon.png)
### 3) Cone
It takes in six values: `bottomRadius`, `height`, `upperRadius`, `startAngle`, `angle`, and `name`.
@ -274,8 +274,8 @@ Let's build a classic cone:
color("beige")
}
```
![](../docs/images/cone-1.png)
![](../docs/images/cone-2.png)
![](../images/cone-1.png)
![](../images/cone-2.png)
First of all, we have to try to build a frustum cone:
```kotlin
@ -283,7 +283,7 @@ cone(60, 80, name = "cone") {
color(0u, 40u, 0u)
}
```
![](../docs/images/frustum-cone.png)
![](../images/frustum-cone.png)
Now, we need to make a try to build a cone segment:
@ -292,8 +292,8 @@ cone(60, 80, angle = PI, name = "cone") {
color(0u, 0u, 200u)
}
```
![](../docs/images/cone-segment-1.png)
![](../docs/images/cone-segment-2.png)
![](../images/cone-segment-1.png)
![](../images/cone-segment-2.png)
Finally, the segment of frustum cone is left for a try:
```kotlin
@ -301,7 +301,7 @@ cone(60, 100, 20, PI*3/4, angle = PI/3, name = "cone") {
color(190u, 0u, 0u)
}
```
![](../docs/images/frustum-cone-segment.png)
![](../images/frustum-cone-segment.png)
### 4) Cone Surface
This solid is set by seven values:`bottomOuterRadius`, `bottomInnerRadius`, `height`, `topOuterRadius`, `topInnerRadius`, `startAngle`, and `angle`.
@ -318,8 +318,8 @@ Let's build usual cone surface with almost all properties set:
rotation = Point3D(2, 50, -9)
}
```
![](../docs/images/cone-surface-1.png)
![](../docs/images/cone-surface-2.png)
![](../images/cone-surface-1.png)
![](../images/cone-surface-2.png)
Now, let's create a cone surface and set all it's properties:
@ -329,8 +329,8 @@ coneSurface(30, 25, 10, 10, 8,0f, pi*3/4, name = "cone surface") {
rotation = Point3D(2, 50, -9)
}
```
![](../docs/images/cone-surface-fragment.png)
![](../docs/images/cone-surface-fragment-2.png)
![](../images/cone-surface-fragment.png)
![](../images/cone-surface-fragment-2.png)
### 5) Cylinder
@ -344,8 +344,8 @@ cylinder(40, 100, "cylinder"){
color("indigo")
}
```
![](../docs/images/cylinder-1.png)
![](../docs/images/cylinder-2.png)
![](../images/cylinder-1.png)
![](../images/cylinder-2.png)
### 6) Tube
`tube` takes in `radius`, `height`, `innerRadius`, `startAngle`, `angle`, and `name`. *All values are familiar from `cone`, and `coneSurface` solids.*
@ -356,7 +356,7 @@ tube(50, 40, 20, name = "usual tube"){
opacity = 0.4
}
```
![](../docs/images/tube.png)
![](../images/tube.png)
This is an example of tube fragment:
@ -365,7 +365,7 @@ tube(50, 40, 20, 0f, PI, name = "fragmented tube"){
color("white")
}
```
![](../docs/images/tube-fragment.png)
![](../images/tube-fragment.png)
### 7) Extruded
`extruded` is set by two values: `shape`, and `layer`.

View File

@ -1,12 +1,8 @@
kotlin.code.style=official
kotlin.mpp.stability.nowarn=true
kotlin.jupyter.add.scanner=false
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1G
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4G
publishing.github=false
publishing.sonatype=false
toolsVersion=0.11.1-kotlin-1.6.10
toolsVersion=0.11.4-kotlin-1.6.20

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@ public external interface TabPaneProps : PropsWithChildren {
public val TabPane: FC<TabPaneProps> = fc("TabPane") { props ->
var activeTab: String? by useState(props.activeTab)
val children: Array<out ReactElement?> = Children.map(props.children) {
val children: Array<out ReactElement<*>?> = Children.map(props.children) {
it.asElementOrNull()
} ?: emptyArray()

View File

@ -5,7 +5,7 @@ plugins {
val dataforgeVersion: String by rootProject.extra
kotlin{
js{
js(IR){
useCommonJs()
browser {
commonWebpackConfig {

View File

@ -38,6 +38,10 @@ public fun ThreeCanvasWithControlsProps.solid(block: SolidGroup.() -> Unit) {
}
}
public fun ThreeCanvasWithControlsProps.options(block: Canvas3DOptions.() -> Unit){
options = Canvas3DOptions(block)
}
public fun ThreeCanvasWithControlsProps.tab(title: String, block: RBuilder.() -> Unit) {
additionalTabs = (additionalTabs ?: emptyMap()) + (title to block)
}

View File

@ -22,7 +22,7 @@ internal const val RENDER_FUNCTION_NAME = "renderAllVisionsById"
/**
* Render a fragment in the given consumer and return a map of extracted visions
* @param manager a VisionManager used for serialization
* @param context a context used to create a vision fragment
* @param embedData embed Vision initial state in the HTML
* @param fetchDataUrl fetch data after first render from given url
* @param fetchUpdatesUrl receive push updates from the server at given url

View File

@ -0,0 +1,96 @@
package space.kscience.visionforge.html
import kotlinx.html.*
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaSerializer
import space.kscience.dataforge.meta.isEmpty
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.visionManager
/**
* Rendering context for visions in HTML
*/
public interface HtmlVisionContext : ContextAware {
/**
* Generate div id for vision div tag
*/
public fun generateId(name: Name): String = "vision[$name]"
/**
* Render vision at given [DIV]
*/
public fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta)
}
public typealias HtmlVisionContextFragment = context(HtmlVisionContext) TagConsumer<*>.() -> Unit
context(HtmlVisionContext)
public fun HtmlVisionContextFragment(content: TagConsumer<*>.() -> Unit): HtmlVisionFragment = content
context(HtmlVisionContext)
private fun <T> TagConsumer<T>.vision(
visionManager: VisionManager,
name: Name,
vision: Vision,
outputMeta: Meta = Meta.EMPTY,
): T = div {
id = generateId(name)
classes = setOf(VisionTagConsumer.OUTPUT_CLASS)
vision.setAsRoot(visionManager)
attributes[VisionTagConsumer.OUTPUT_NAME_ATTRIBUTE] = name.toString()
if (!outputMeta.isEmpty()) {
//Hard-code output configuration
script {
attributes["class"] = VisionTagConsumer.OUTPUT_META_CLASS
unsafe {
+visionManager.jsonFormat.encodeToString(MetaSerializer, outputMeta)
}
}
}
renderVision(name, vision, outputMeta)
}
context(HtmlVisionContext)
private fun <T> TagConsumer<T>.vision(
name: Name,
vision: Vision,
outputMeta: Meta = Meta.EMPTY,
): T = vision(context.visionManager, name, vision, outputMeta)
/**
* Insert a vision in this HTML.
*/
context(HtmlVisionContext)
@DFExperimental
@VisionDSL
public fun <T> TagConsumer<T>.vision(
name: Name? = null,
visionProvider: VisionOutput.() -> Vision,
): T {
val output = VisionOutput(context, name)
val vision = output.visionProvider()
val actualName =
name ?: NameToken(VisionTagConsumer.DEFAULT_VISION_NAME, vision.hashCode().toUInt().toString()).asName()
return vision(output.buildVisionManager(), actualName, vision, output.meta)
}
/**
* Insert a vision in this HTML.
*/
context(HtmlVisionContext)
@DFExperimental
@VisionDSL
public fun <T> TagConsumer<T>.vision(
name: String?,
visionProvider: VisionOutput.() -> Vision,
): T = vision(name?.parseAsName(), visionProvider)

View File

@ -17,7 +17,7 @@ import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.sin
import space.kscience.visionforge.solid.specifications.Camera as CameraSpec
import space.kscience.visionforge.solid.specifications.CameraScheme as CameraSpec
public class OrbitControls internal constructor(camera: Camera, canvas: SubScene, spec: CameraSpec) {

View File

@ -3,7 +3,7 @@ plugins {
}
kotlin {
js{
js(IR){
binaries.library()
}
sourceSets {

View File

@ -2,9 +2,12 @@ plugins {
id("ru.mipt.npm.gradle.jvm")
}
val ktorVersion = npmlibs.versions.ktor.get()
dependencies {
api(project(":visionforge-core"))
api(npmlibs.ktor.server.cio)
api(npmlibs.ktor.html.builder)
api(npmlibs.ktor.websockets)
api("io.ktor:ktor-server-cio:${ktorVersion}")
api("io.ktor:ktor-server-html-builder:${ktorVersion}")
api("io.ktor:ktor-server-websockets:${ktorVersion}")
implementation("io.ktor:ktor-server-cors:${ktorVersion}")
}

View File

@ -1,24 +1,25 @@
package space.kscience.visionforge.server
import io.ktor.application.*
import io.ktor.features.CORS
import io.ktor.features.CallLogging
import io.ktor.html.respondHtml
import io.ktor.http.*
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.content.resources
import io.ktor.http.content.static
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.*
import io.ktor.server.application.*
import io.ktor.server.cio.CIO
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.engine.embeddedServer
import io.ktor.util.getOrFail
import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket
import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.resources
import io.ktor.server.http.content.static
import io.ktor.server.plugins.cors.CORS
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.*
import io.ktor.server.util.getOrFail
import io.ktor.server.websocket.WebSockets
import io.ktor.server.websocket.webSocket
import io.ktor.websocket.Frame
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.html.*
@ -130,13 +131,13 @@ public class VisionServer internal constructor(
try {
withContext(visionManager.context.coroutineContext) {
vision.flowChanges(updateInterval.milliseconds).collect { update ->
vision.flowChanges(updateInterval.milliseconds).onEach { update ->
val json = visionManager.jsonFormat.encodeToString(
VisionChange.serializer(),
update
)
outgoing.send(Frame.Text(json))
}
}.collect()
}
} catch (t: Throwable) {
application.log.info("WebSocket update channel for $name is closed with exception: $t")
@ -241,21 +242,16 @@ public fun Application.visionServer(
webServerUrl: Url,
path: String = DEFAULT_PAGE,
): VisionServer {
if (featureOrNull(WebSockets) == null) {
install(WebSockets)
install(WebSockets)
install(CORS) {
anyHost()
}
if (featureOrNull(CORS) == null) {
install(CORS) {
anyHost()
}
}
// if (pluginOrNull(CallLogging) == null) {
// install(CallLogging)
// }
if (featureOrNull(CallLogging) == null) {
install(CallLogging)
}
val serverRoute = (featureOrNull(Routing) ?: install(Routing)).createRouteFromPath(path)
val serverRoute = install(Routing).createRouteFromPath(path)
serverRoute {
static {
@ -263,7 +259,7 @@ public fun Application.visionServer(
}
}
return VisionServer(visionManager, webServerUrl.copy(encodedPath = path), serverRoute)
return VisionServer(visionManager, URLBuilder(webServerUrl).apply { encodedPath = path }.build(), serverRoute)
}
/**

View File

@ -1,15 +1,18 @@
package space.kscience.visionforge.solid
import space.kscience.dataforge.meta.Configurable
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.*
import space.kscience.visionforge.Colors
import space.kscience.visionforge.VisionBuilder
import kotlin.properties.ReadOnlyProperty
@VisionBuilder
public class ColorAccessor(
private val provider: MutableValueProvider,
private val colorKey: Name
private val colorKey: Name,
) : MutableValueProvider {
public var value: Value?
get() = provider.getValue(colorKey)
@ -24,8 +27,12 @@ public class ColorAccessor(
}
}
public fun Configurable.color(): ReadOnlyProperty<Configurable, ColorAccessor> = ReadOnlyProperty { _, property ->
ColorAccessor(meta, property.name.asName())
}
public var ColorAccessor?.string: String?
get() = this?.value?.let { if(it == Null) null else it.string }
get() = this?.value?.let { if (it == Null) null else it.string }
set(value) {
this?.value = value?.asValue()
}

View File

@ -0,0 +1,74 @@
package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.node
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.*
@Serializable
public abstract class LightSource : SolidBase() {
override val descriptor: MetaDescriptor get() = LightSource.descriptor
public val color: ColorAccessor by color()
public var intensity: Number by numberProperty(includeStyles = false) { 1.0 }
public companion object{
public val descriptor: MetaDescriptor by lazy {
MetaDescriptor {
value(Vision.VISIBLE_KEY, ValueType.BOOLEAN) {
inherited = false
default(true)
}
value(LightSource::color.name, ValueType.STRING, ValueType.NUMBER) {
inherited = false
widgetType = "color"
}
value(LightSource::intensity.name, ValueType.NUMBER) {
inherited = false
default(1.0)
}
value(SolidMaterial.COLOR_KEY, ValueType.STRING, ValueType.NUMBER) {
inherited = false
widgetType = "color"
}
node(Solid.POSITION_KEY) {
hide()
}
}
}
}
}
@Serializable
@SerialName("solid.light.ambient")
public class AmbientLightSource : LightSource()
@VisionBuilder
public fun VisionContainerBuilder<Solid>.ambientLight(
name: String? = "@ambientLight",
block: AmbientLightSource.() -> Unit = {},
): AmbientLightSource = AmbientLightSource().apply(block).also { set(name, it) }
@Serializable
@SerialName("solid.light.point")
public class PointLightSource : LightSource()
@VisionBuilder
public fun VisionContainerBuilder<Solid>.pointLight(
x: Number,
y: Number,
z: Number,
name: String? = null,
block: PointLightSource.() -> Unit = {},
): PointLightSource = PointLightSource().apply(block).also {
it.position = Point3D(x, y, z)
set(name, it)
}

View File

@ -72,9 +72,7 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
}
@Suppress("FunctionName")
public fun SolidGroup(block: SolidGroup.() -> Unit): SolidGroup {
return SolidGroup().apply(block)
}
public fun SolidGroup(block: SolidGroup.() -> Unit): SolidGroup = SolidGroup().apply(block)
@VisionBuilder
public fun VisionContainerBuilder<Vision>.group(

View File

@ -27,7 +27,7 @@ public class SolidMaterial : Scheme() {
*/
public val specularColor: ColorAccessor = ColorAccessor(meta, SPECULAR_COLOR_KEY)
public val emissiveColor: ColorAccessor = ColorAccessor(meta, "emissiveColor".asName())
public val emissiveColor: ColorAccessor = ColorAccessor(meta, EMISSIVE_COLOR_KEY)
/**
* Opacity
@ -43,12 +43,15 @@ public class SolidMaterial : Scheme() {
public val MATERIAL_KEY: Name = "material".asName()
public val COLOR_KEY: Name = "color".asName()
public val MATERIAL_COLOR_KEY: Name = MATERIAL_KEY + COLOR_KEY
public val TYPE_KEY: Name = "type".asName()
public val SPECULAR_COLOR_KEY: Name = "specularColor".asName()
public val MATERIAL_SPECULAR_COLOR_KEY: Name = MATERIAL_KEY + SPECULAR_COLOR_KEY
public val EMISSIVE_COLOR_KEY: Name = "emissiveColor".asName()
public val OPACITY_KEY: Name = "opacity".asName()
public val MATERIAL_OPACITY_KEY: Name = MATERIAL_KEY + OPACITY_KEY
public val WIREFRAME_KEY: Name = "wireframe".asName()
public val MATERIAL_COLOR_KEY: Name = MATERIAL_KEY + COLOR_KEY
public val MATERIAL_EMISSIVE_COLOR_KEY: Name = MATERIAL_KEY + EMISSIVE_COLOR_KEY
public val MATERIAL_SPECULAR_COLOR_KEY: Name = MATERIAL_KEY + SPECULAR_COLOR_KEY
public val MATERIAL_WIREFRAME_KEY: Name = MATERIAL_KEY + WIREFRAME_KEY
public override val descriptor: MetaDescriptor by lazy {
@ -56,6 +59,12 @@ public class SolidMaterial : Scheme() {
MetaDescriptor {
inherited = true
value(TYPE_KEY, ValueType.STRING){
inherited = true
allowedValues = listOf("default".asValue(), "simple".asValue())
default("default")
}
value(COLOR_KEY, ValueType.STRING, ValueType.NUMBER) {
inherited = true
widgetType = "color"
@ -67,6 +76,12 @@ public class SolidMaterial : Scheme() {
hide()
}
value(EMISSIVE_COLOR_KEY, ValueType.STRING, ValueType.NUMBER) {
inherited = true
widgetType = "color"
hide()
}
value(OPACITY_KEY, ValueType.NUMBER) {
inherited = true
default(1.0)

View File

@ -39,6 +39,9 @@ public class Solids(meta: Meta) : VisionPlugin(meta) {
subclass(PolyLine.serializer())
subclass(SolidLabel.serializer())
subclass(Sphere.serializer())
subclass(AmbientLightSource.serializer())
subclass(PointLightSource.serializer())
}
public val serializersModuleForSolids: SerializersModule = SerializersModule {

View File

@ -7,24 +7,24 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.meta.double
public class Axes : Scheme() {
public class AxesScheme : Scheme() {
public var visible: Boolean by boolean(false)
public var size: Double by double(AXIS_SIZE)
public var width: Double by double(AXIS_WIDTH)
public companion object : SchemeSpec<Axes>(::Axes) {
public companion object : SchemeSpec<AxesScheme>(::AxesScheme) {
public const val AXIS_SIZE: Double = 1000.0
public const val AXIS_WIDTH: Double = 3.0
override val descriptor: MetaDescriptor by lazy {
MetaDescriptor {
value(Axes::visible){
value(AxesScheme::visible){
default(false)
}
value(Axes::size){
value(AxesScheme::size){
default(AXIS_SIZE)
}
value(Axes::width){
value(AxesScheme::width){
default(AXIS_WIDTH)
}
}

View File

@ -8,7 +8,7 @@ import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.int
import kotlin.math.PI
public class Camera : Scheme() {
public class CameraScheme : Scheme() {
public var fov: Int by int(FIELD_OF_VIEW)
//var aspect by double(1.0)
@ -19,7 +19,7 @@ public class Camera : Scheme() {
public var azimuth: Double by double(INITIAL_AZIMUTH)
public var latitude: Double by double(INITIAL_LATITUDE)
public companion object : SchemeSpec<Camera>(::Camera) {
public companion object : SchemeSpec<CameraScheme>(::CameraScheme) {
public const val INITIAL_DISTANCE: Double = 300.0
public const val INITIAL_AZIMUTH: Double = 0.0
public const val INITIAL_LATITUDE: Double = PI / 6
@ -29,22 +29,22 @@ public class Camera : Scheme() {
override val descriptor: MetaDescriptor by lazy {
MetaDescriptor {
value(Camera::fov) {
value(CameraScheme::fov) {
default(FIELD_OF_VIEW)
}
value(Camera::nearClip) {
value(CameraScheme::nearClip) {
default(NEAR_CLIP)
}
value(Camera::farClip) {
value(CameraScheme::farClip) {
default(FAR_CLIP)
}
value(Camera::distance) {
value(CameraScheme::distance) {
default(INITIAL_DISTANCE)
}
value(Camera::azimuth) {
value(CameraScheme::azimuth) {
default(INITIAL_AZIMUTH)
}
value(Camera::latitude) {
value(CameraScheme::latitude) {
default(INITIAL_LATITUDE)
}
}
@ -52,4 +52,4 @@ public class Camera : Scheme() {
}
}
public val Camera.zenith: Double get() = PI / 2 - latitude
public val CameraScheme.zenith: Double get() = PI / 2 - latitude

View File

@ -8,38 +8,34 @@ import space.kscience.dataforge.names.Name
import space.kscience.visionforge.hide
import space.kscience.visionforge.widgetType
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<Clipping>(::Clipping) {
override val descriptor: MetaDescriptor = MetaDescriptor {
value(Clipping::x) {
widgetType = "range"
attributes["min"] = 0.0
attributes["max"] = 1.0
attributes["step"] = 0.01
default(1.0)
}
value(Clipping::y) {
widgetType = "range"
attributes["min"] = 0.0
attributes["max"] = 1.0
attributes["step"] = 0.01
default(1.0)
}
value(Clipping::z) {
widgetType = "range"
attributes["min"] = 0.0
attributes["max"] = 1.0
attributes["step"] = 0.01
default(1.0)
}
public object Clipping : SchemeSpec<PointScheme>(::PointScheme) {
override val descriptor: MetaDescriptor = MetaDescriptor {
value(PointScheme::x) {
widgetType = "range"
attributes["min"] = 0.0
attributes["max"] = 1.0
attributes["step"] = 0.01
default(1.0)
}
value(PointScheme::y) {
widgetType = "range"
attributes["min"] = 0.0
attributes["max"] = 1.0
attributes["step"] = 0.01
default(1.0)
}
value(PointScheme::z) {
widgetType = "range"
attributes["min"] = 0.0
attributes["max"] = 1.0
attributes["step"] = 0.01
default(1.0)
}
}
}
public class CanvasSize : Scheme() {
public var minSize: Int by int(400)
public var minWith: Number by number { minSize }
@ -62,16 +58,15 @@ public class CanvasSize : Scheme() {
}
public class Canvas3DOptions : Scheme() {
public var axes: Axes by spec(Axes)
public var light: Light by spec(Light)
public var camera: Camera by spec(Camera)
public var controls: Controls by spec(Controls)
public var axes: AxesScheme by spec(AxesScheme)
public var camera: CameraScheme by spec(CameraScheme)
public var controls: ControlsScheme by spec(ControlsScheme)
public var size: CanvasSize by spec(CanvasSize)
public var layers: List<Number> by numberList(0)
public var clipping: Clipping by spec(Clipping)
public var clipping: PointScheme by spec(Clipping)
public var onSelect: ((Name?) -> Unit)? = null
@ -79,7 +74,7 @@ public class Canvas3DOptions : Scheme() {
public companion object : SchemeSpec<Canvas3DOptions>(::Canvas3DOptions) {
override val descriptor: MetaDescriptor by lazy {
MetaDescriptor {
scheme(Canvas3DOptions::axes, Axes)
scheme(Canvas3DOptions::axes, AxesScheme)
value(Canvas3DOptions::layers) {
multiple = true
@ -90,15 +85,11 @@ public class Canvas3DOptions : Scheme() {
scheme(Canvas3DOptions::clipping, Clipping)
scheme(Canvas3DOptions::light, Light){
scheme(Canvas3DOptions::camera, CameraScheme) {
hide()
}
scheme(Canvas3DOptions::camera, Camera) {
hide()
}
scheme(Canvas3DOptions::controls, Controls) {
scheme(Canvas3DOptions::controls, ControlsScheme) {
hide()
}

View File

@ -4,6 +4,6 @@ import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
public class Controls : Scheme() {
public companion object : SchemeSpec<Controls>(::Controls)
public class ControlsScheme : Scheme() {
public companion object : SchemeSpec<ControlsScheme>(::ControlsScheme)
}

View File

@ -1,8 +0,0 @@
package space.kscience.visionforge.solid.specifications
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
public class Light : Scheme() {
public companion object : SchemeSpec<Light>(::Light)
}

View File

@ -0,0 +1,19 @@
package space.kscience.visionforge.solid.specifications
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.meta.double
public class PointScheme: Scheme(){
public var x: Double? by double()
public var y: Double? by double()
public var z: Double? by double()
public companion object: SchemeSpec<PointScheme>(::PointScheme)
}
public operator fun PointScheme.invoke(x: Number?, y: Number?, z: Number?){
this.x = x?.toDouble()
this.y = y?.toDouble()
this.z = z?.toDouble()
}

View File

@ -1,6 +1,7 @@
package space.kscience.visionforge.solid
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Colors
import space.kscience.visionforge.MutableVisionGroup
import space.kscience.visionforge.get
import kotlin.test.Test
@ -55,4 +56,18 @@ class SerializationTest {
assertEquals(group["cube"]?.meta, reconstructed["cube"]?.meta)
}
@Test
fun lightSerialization(){
val group = SolidGroup {
ambientLight {
color(Colors.white)
intensity = 100.0
}
}
val serialized = Solids.encodeToString(group)
val reconstructed = Solids.decodeFromString(serialized) as SolidGroup
assertEquals(100.0, (reconstructed["@ambientLight"] as AmbientLightSource).intensity.toDouble())
}
}

View File

@ -1,6 +1,6 @@
package space.kscience.visionforge.tables
import kotlinext.js.jso
import kotlinx.js.jso
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import space.kscience.dataforge.context.AbstractPlugin

View File

@ -10,6 +10,6 @@ kotlin{
dependencies {
api(project(":visionforge-solid"))
implementation(npm("three", "0.130.1"))
implementation(npm("three", "0.137.4"))
implementation(npm("three-csg-ts", "3.1.9"))
}

View File

@ -10,6 +10,7 @@ import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.values.boolean
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.computePropertyNode
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.setProperty
@ -75,6 +76,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
}
}
@VisionBuilder
public fun Solid.edges(enabled: Boolean = true, block: SolidMaterial.() -> Unit = {}) {
setProperty(EDGES_ENABLED_KEY, enabled)
meta.getOrCreate(EDGES_MATERIAL_KEY).updateWith(SolidMaterial, block)

View File

@ -0,0 +1,19 @@
package space.kscience.visionforge.solid.three
import info.laht.threekt.lights.AmbientLight
import info.laht.threekt.math.Color
import space.kscience.visionforge.solid.AmbientLightSource
import kotlin.reflect.KClass
public object ThreeAmbientLightFactory : ThreeFactory<AmbientLightSource> {
override val type: KClass<in AmbientLightSource> get() = AmbientLightSource::class
override fun invoke(three: ThreePlugin, obj: AmbientLightSource): AmbientLight {
val res = AmbientLight().apply {
color = obj.color.threeColor() ?: Color(0x404040)
intensity = obj.intensity.toDouble()
}
return res
}
}

View File

@ -8,12 +8,8 @@ import info.laht.threekt.external.controls.OrbitControls
import info.laht.threekt.external.controls.TrackballControls
import info.laht.threekt.geometries.EdgesGeometry
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.math.*
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh
import info.laht.threekt.scenes.Scene
@ -35,7 +31,7 @@ import kotlin.math.cos
import kotlin.math.sin
/**
*
* A canvas for three-js rendering
*/
public class ThreeCanvas(
public val three: ThreePlugin,
@ -60,19 +56,19 @@ public class ThreeCanvas(
add(axesObject)
}
//Set up light
options.useProperty(Canvas3DOptions::light, this) { lightConfig ->
//remove old light if present
getObjectByName(LIGHT_NAME)?.let { remove(it) }
//add new light
val lightObject = buildLight(lightConfig)
lightObject.name = LIGHT_NAME
add(lightObject)
}
// //Set up light
// options.useProperty(Canvas3DOptions::light, this) { lightConfig ->
// //remove old light if present
// getObjectByName(LIGHT_NAME)?.let { remove(it) }
// //add new light
// val lightObject = buildLight(lightConfig)
// lightObject.name = LIGHT_NAME
// add(lightObject)
// }
}
private fun buildCamera(spec: Camera) = PerspectiveCamera(
private fun buildCamera(spec: CameraScheme) = PerspectiveCamera(
spec.fov,
1.0,
spec.nearClip,
@ -231,9 +227,24 @@ public class ThreeCanvas(
}
}
private fun buildLight(spec: Light?): info.laht.threekt.lights.Light = AmbientLight(0x404040)
// private fun buildLight(spec: AmbientLightScheme?): info.laht.threekt.lights.Light = when (spec?.type) {
// AmbientLightScheme.Type.POINT -> PointLight().apply {
// position.x = spec.position.x ?: 0.0
// position.y = spec.position.y ?: 0.0
// position.z = spec.position.z ?: 0.0
// }
// else -> AmbientLight().apply {
//
// }
// }.apply {
// this.color = spec?.color?.threeColor() ?: Color(0x404040)
//
// spec?.intensity?.coerceIn(0.0, 1.0)?.let {
// this.intensity = it
// }
// }
private fun addControls(element: Node, controls: Controls) {
private fun addControls(element: Node, controls: ControlsScheme) {
when (controls.meta["type"].string) {
"trackball" -> TrackballControls(camera, element)
else -> OrbitControls(camera, element)
@ -311,7 +322,7 @@ 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"
//private const val CLIP_HELPER_NAME = "@clipping"
}
}

View File

@ -8,7 +8,6 @@ import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.startsWith
import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperty
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_KEY
import space.kscience.visionforge.solid.three.ThreeFactory.Companion.TYPE
@ -58,7 +57,7 @@ public fun Object3D.updatePosition(obj: Vision) {
* Update non-position non-geometry property
*/
public fun Object3D.updateProperty(source: Vision, propertyName: Name) {
console.log("$source updated $propertyName with ${source.computeProperty(propertyName)}")
// console.log("$source updated $propertyName with ${source.computeProperty(propertyName)}")
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
updateMaterialProperty(source, propertyName)
} else if (

View File

@ -4,7 +4,6 @@ package space.kscience.visionforge.solid.three
import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.TextBufferGeometry
import info.laht.threekt.objects.Mesh
import kotlinext.js.jsObject
import kotlinext.js.jso
import space.kscience.dataforge.context.logger
import space.kscience.dataforge.context.warn

View File

@ -3,39 +3,52 @@ package space.kscience.visionforge.solid.three
import info.laht.threekt.materials.LineBasicMaterial
import info.laht.threekt.materials.Material
import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.materials.MeshStandardMaterial
import info.laht.threekt.math.Color
import info.laht.threekt.objects.Mesh
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.*
import space.kscience.visionforge.Colors
import space.kscience.visionforge.Vision
import space.kscience.visionforge.computePropertyNode
import space.kscience.visionforge.getStyleNodes
import space.kscience.visionforge.solid.ColorAccessor
import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.SolidReference
public object ThreeMaterials {
public val DEFAULT_COLOR: Color = Color(Colors.darkgreen)
public val DEFAULT: MeshBasicMaterial = MeshBasicMaterial().apply {
public val DEFAULT: MeshStandardMaterial = MeshStandardMaterial().apply {
color.set(DEFAULT_COLOR)
cached = true
}
public val DEFAULT_LINE_COLOR: Color = Color(Colors.black)
public val BLACK_COLOR: Color = Color(Colors.black)
public val DEFAULT_EMISSIVE_COLOR: Color = BLACK_COLOR
public val DEFAULT_LINE_COLOR: Color get() = BLACK_COLOR
public val DEFAULT_LINE: LineBasicMaterial = LineBasicMaterial().apply {
color.set(DEFAULT_LINE_COLOR)
cached = true
}
public val SELECTED_MATERIAL: LineBasicMaterial = LineBasicMaterial().apply {
color.set(Colors.ivory)
linewidth = 8.0
cached = true
}
public val HIGHLIGHT_MATERIAL: LineBasicMaterial = LineBasicMaterial().apply {
color.set(Colors.blue)
linewidth = 8.0
cached = true
}
private val lineMaterialCache = HashMap<Meta, LineBasicMaterial>()
@ -58,34 +71,21 @@ public object ThreeMaterials {
private val materialCache = HashMap<Meta, Material>()
internal fun buildMaterial(meta: Meta): Material = MeshBasicMaterial().apply {
color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_COLOR
internal fun buildMaterial(meta: Meta): Material = when (meta[SolidMaterial.TYPE_KEY]?.string) {
"simple" -> MeshBasicMaterial().apply {
color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_COLOR
wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false
}
else -> MeshStandardMaterial().apply {
color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_COLOR
emissive = meta[SolidMaterial.EMISSIVE_COLOR_KEY]?.threeColor() ?: DEFAULT_EMISSIVE_COLOR
wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false
}
}.apply {
opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0
transparent = opacity < 1.0
wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false
needsUpdate = true
}
// val material = SolidMaterial.read(meta)
// return meta[SolidMaterial.SPECULAR_COLOR_KEY]?.let { specularColor ->
// MeshPhongMaterial().apply {
// color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_COLOR
// specular = specularColor.threeColor()
// emissive = material.emissiveColor.threeColor() ?: specular
// reflectivity = 0.5
// refractionRatio = 1.0
// shininess = 100.0
// opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0
// transparent = opacity < 1.0
// wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false
// needsUpdate = true
// }
// } ?: MeshBasicMaterial().apply {
// color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_COLOR
// opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0
// transparent = opacity < 1.0
// wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false
// needsUpdate = true
// }
internal fun cacheMaterial(meta: Meta): Material = materialCache.getOrPut(meta) {
buildMaterial(meta).apply {
@ -115,6 +115,16 @@ public fun Meta.threeColor(): Color? {
}
}
public fun ColorAccessor.threeColor(): Color? {
val value = value
return when {
value == null -> null
value === Null -> null
value.type == ValueType.NUMBER -> Color(value.int)
else -> Color(value.string)
}
}
private var Material.cached: Boolean
get() = userData["cached"] == true
set(value) {
@ -139,7 +149,11 @@ public fun Mesh.updateMaterial(vision: Vision) {
}
public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) {
if (material.cached || propertyName == SolidMaterial.MATERIAL_KEY) {
if (
material.cached
|| propertyName == SolidMaterial.MATERIAL_KEY
|| propertyName == SolidMaterial.MATERIAL_KEY + SolidMaterial.TYPE_KEY
) {
//generate a new material since cached material should not be changed
updateMaterial(vision)
} else {
@ -149,6 +163,16 @@ public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) {
?: ThreeMaterials.DEFAULT_COLOR
material.needsUpdate = true
}
SolidMaterial.SPECULAR_COLOR_KEY -> {
material.asDynamic().specular = vision.computePropertyNode(SolidMaterial.SPECULAR_COLOR_KEY)?.threeColor()
?: ThreeMaterials.DEFAULT_COLOR
material.needsUpdate = true
}
SolidMaterial.MATERIAL_EMISSIVE_COLOR_KEY -> {
material.asDynamic().emissive = vision.computePropertyNode(SolidMaterial.MATERIAL_EMISSIVE_COLOR_KEY)?.threeColor()
?: ThreeMaterials.BLACK_COLOR
material.needsUpdate = true
}
SolidMaterial.MATERIAL_OPACITY_KEY -> {
val opacity = vision.getPropertyValue(
SolidMaterial.MATERIAL_OPACITY_KEY,

View File

@ -1,7 +1,6 @@
package space.kscience.visionforge.solid.three
import info.laht.threekt.core.Object3D
import kotlinx.coroutines.CoroutineScope
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import space.kscience.dataforge.context.*
@ -27,8 +26,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
private val objectFactories = HashMap<KClass<out Solid>, ThreeFactory<*>>()
private val compositeFactory = ThreeCompositeFactory(this)
//TODO generate a separate supervisor update scope
internal val updateScope: CoroutineScope get() = context
// internal val updateScope: CoroutineScope get() = context
init {
//Add specialized factories here
@ -38,6 +36,8 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
objectFactories[ConeSegment::class] = ThreeConeFactory
objectFactories[PolyLine::class] = ThreeLineFactory
objectFactories[SolidLabel::class] = ThreeCanvasLabelFactory
objectFactories[AmbientLightSource::class] = ThreeAmbientLightFactory
objectFactories[PointLightSource::class] = ThreePointLightFactory
}
@Suppress("UNCHECKED_CAST")

View File

@ -0,0 +1,32 @@
package space.kscience.visionforge.solid.three
import info.laht.threekt.lights.PointLight
import info.laht.threekt.math.Color
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.solid.LightSource
import space.kscience.visionforge.solid.PointLightSource
import kotlin.reflect.KClass
public object ThreePointLightFactory : ThreeFactory<PointLightSource> {
override val type: KClass<in PointLightSource> get() = PointLightSource::class
override fun invoke(three: ThreePlugin, obj: PointLightSource): PointLight {
val res = PointLight().apply {
matrixAutoUpdate = false
color = obj.color.threeColor() ?: Color(0x404040)
intensity = obj.intensity.toDouble()
updatePosition(obj)
}
obj.onPropertyChange { name ->
when (name) {
LightSource::color.name.asName() -> res.color = obj.color.threeColor() ?: Color(0x404040)
LightSource::intensity.name.asName() -> res.intensity = obj.intensity.toDouble()
else -> res.updateProperty(obj, name)
}
}
return res
}
}

View File

@ -5,7 +5,7 @@ plugins {
val ktorVersion: String by rootProject.extra
kotlin {
js{
js(IR){
browser {
webpackTask {
this.outputFileName = "js/visionforge-three.js"