Updated build and used new core version

This commit is contained in:
Alexander Nozik 2019-03-27 22:14:42 +03:00
parent d874c3cdd6
commit 9a7fcfe9c6
18 changed files with 367 additions and 117 deletions

View File

@ -1,4 +1,8 @@
import com.moowork.gradle.node.NodeExtension
import com.moowork.gradle.node.npm.NpmTask
import com.moowork.gradle.node.task.NodeTask
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
@ -9,7 +13,7 @@ buildscript {
val dokkaVersion: String by rootProject.extra("0.9.17")
val serializationVersion: String by rootProject.extra("0.10.0")
val dataforgeVersion: String by rootProject.extra("0.1.2-dev-1")
val dataforgeVersion: String by rootProject.extra("0.1.2-dev-2")
repositories {
jcenter()
@ -28,6 +32,7 @@ buildscript {
plugins {
id("com.jfrog.artifactory") version "4.8.1" apply false
id("com.moowork.node") version "1.3.1" apply false
// id("org.jetbrains.kotlin.multiplatform") apply false
}
@ -86,6 +91,7 @@ subprojects {
afterEvaluate {
extensions.findByType<KotlinMultiplatformExtension>()?.apply {
apply(plugin = "com.moowork.node")
jvm {
compilations.all {
kotlinOptions {
@ -113,6 +119,75 @@ subprojects {
}
}
}
configure<NodeExtension>{
nodeModulesDir = file("$buildDir/node_modules")
}
val compileKotlinJs by tasks.getting(Kotlin2JsCompile::class)
val compileTestKotlinJs by tasks.getting(Kotlin2JsCompile::class)
val populateNodeModules by tasks.registering(Copy::class) {
dependsOn(compileKotlinJs)
from(compileKotlinJs.destinationDir)
compilations["test"].runtimeDependencyFiles.forEach {
if (it.exists() && !it.isDirectory) {
from(zipTree(it.absolutePath).matching { include("*.js") })
}
}
into("$buildDir/node_modules")
}
val installMocha by tasks.registering(NpmTask::class) {
setWorkingDir(buildDir)
setArgs(listOf("install", "mocha"))
}
val runMocha by tasks.registering(NodeTask::class) {
dependsOn(compileTestKotlinJs, populateNodeModules, installMocha)
setScript(file("$buildDir/node_modules/mocha/bin/mocha"))
setArgs(listOf(compileTestKotlinJs.outputFile))
}
tasks["jsTest"].dependsOn(runMocha)
}
sourceSets {
val commonMain by getting {
dependencies {
api(kotlin("stdlib"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val jvmMain by getting {
dependencies {
api(kotlin("stdlib-jdk8"))
}
}
val jvmTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
}
}
val jsMain by getting {
dependencies {
api(kotlin("stdlib-js"))
}
}
val jsTest by getting {
dependencies {
implementation(kotlin("test-js"))
}
}
}
targets.all {

View File

@ -8,10 +8,12 @@ plugins {
id("org.jetbrains.kotlin.frontend")
}
val kotlinVersion: String by rootProject.extra
dependencies {
api(project(":dataforge-vis-spatial"))
implementation("info.laht.threekt:threejs-wrapper:0.88-npm-2")
testCompile("org.jetbrains.kotlin:kotlin-test-js:$kotlinVersion")
}
configure<KotlinFrontendExtension> {

View File

@ -1,6 +1,6 @@
package hep.dataforge.vis
import hep.dataforge.vis.spatial.ThreeDemoApp
import hep.dataforge.vis.spatial.jsroot.JSRootDemoApp
import kotlin.browser.document
import kotlin.dom.hasClass
@ -37,7 +37,7 @@ fun main() {
fun start(state: dynamic): ApplicationBase? {
return if (document.body?.hasClass("testApp") == true) {
val application = ThreeDemoApp()
val application = JSRootDemoApp()
@Suppress("UnsafeCastFromDynamic")
application.start(state?.appState ?: emptyMap())

View File

@ -1,9 +1,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.context.Global
import hep.dataforge.context.members
import hep.dataforge.meta.number
import hep.dataforge.meta.set
import hep.dataforge.vis.ApplicationBase
import hep.dataforge.vis.DisplayGroup
import hep.dataforge.vis.require
@ -26,20 +24,10 @@ class ThreeDemoApp : ApplicationBase() {
//TODO remove after DI fix
Global.plugins.load(ThreePlugin())
Global.plugins.load(JSRootPlugin())
// Global.plugins.load(ThreePlugin())
// Global.plugins.load(JSRootPlugin())
// Global.plugins.load(JSRootPlugin)
println(Global.plugins.count())
Global.plugins.forEach {
println(it)
}
Global.members<ThreeFactory<*>>(ThreeFactory.TYPE).forEach {
println(it)
}
Global.plugins.load(JSRootPlugin)
val renderer = ThreeOutput(Global)
renderer.start(document.getElementById("canvas")!!)
@ -50,31 +38,37 @@ class ThreeDemoApp : ApplicationBase() {
renderer.render {
group = group {
box {
z = 110.0
xSize = 100.0
ySize = 100.0
zSize = 100.0
}
box {
visible = false
x = 110.0
xSize = 100.0
ySize = 100.0
zSize = 100.0
properties.style["color"] = 1530
color(1530)
GlobalScope.launch {
while (isActive) {
delay(500)
visible = !visible
}
}
}
}
// convex {
// point(50, 50, -50)
// point(50, -50, -50)
// point(-50, -50, -50)
// point(-50, 50, -50)
// point(50, 50, 50)
// point(50, -50, 50)
// point(-50, -50, 50)
// point(-50, 50, 50)
// }
convex {
point(50,50,50)
point(-50,-50,50)
point(-50,50,-50)
point(50,-50,-50)
}
jsRoot {
y = 110.0
shape = box(50, 50, 50)
color(12285)
}
}

View File

@ -8,7 +8,7 @@ import hep.dataforge.vis.onChange
import hep.dataforge.vis.spatial.ThreeFactory.Companion.TYPE
import hep.dataforge.vis.spatial.ThreeFactory.Companion.buildMesh
import hep.dataforge.vis.spatial.ThreeFactory.Companion.updateMesh
import hep.dataforge.vis.spatial.three.ConvexGeometry
import hep.dataforge.vis.spatial.three.ConvexBufferGeometry
import hep.dataforge.vis.spatial.three.EdgesGeometry
import hep.dataforge.vis.spatial.three.euler
import info.laht.threekt.core.BufferGeometry
@ -77,6 +77,9 @@ abstract class MeshThreeFactory<T : DisplayObject>(override val type: KClass<out
override fun invoke(obj: T): Mesh {
val geometry = buildGeometry(obj)
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
val mesh = buildMesh(obj, geometry)
ThreeFactory.updatePosition(obj, mesh)
obj.onChange(this) { _, _, _ ->
@ -92,9 +95,11 @@ object ThreeBoxFactory : MeshThreeFactory<Box>(Box::class) {
BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize)
}
fun Point3D.asVector(): Vector3 = this.asDynamic() as Vector3
fun Point3D.asVector(): Vector3 = Vector3(this.x, this.y, this.z)
object ThreeConvexFactory : MeshThreeFactory<Convex>(Convex::class) {
override fun buildGeometry(obj: Convex) =
ConvexGeometry(obj.points.map { it.asVector() }.toTypedArray())
override fun buildGeometry(obj: Convex): ConvexBufferGeometry {
val vectors = obj.points.map { it.asVector() }.toTypedArray()
return ConvexBufferGeometry(vectors)
}
}

View File

@ -2,7 +2,7 @@ package hep.dataforge.vis.spatial
import hep.dataforge.context.Context
import hep.dataforge.context.members
import hep.dataforge.meta.Meta
import hep.dataforge.meta.*
import hep.dataforge.output.Output
import hep.dataforge.vis.DisplayGroup
import hep.dataforge.vis.DisplayObject
@ -18,7 +18,7 @@ import org.w3c.dom.Element
import kotlin.browser.window
import kotlin.reflect.KClass
class ThreeOutput(override val context: Context) : Output<DisplayObject> {
class ThreeOutput(override val context: Context, val meta: Meta = EmptyMeta) : Output<DisplayObject> {
private val renderer = WebGLRenderer { antialias = true }.apply {
setClearColor(ColorConstants.skyblue, 1)
@ -30,10 +30,10 @@ class ThreeOutput(override val context: Context) : Output<DisplayObject> {
}
val camera = PerspectiveCamera(
75,
meta["fov"].int ?: 75,
window.innerWidth.toDouble() / window.innerHeight,
World.CAMERA_NEAR_CLIP,
World.CAMERA_FAR_CLIP
meta["camera.nearClip"].double?: World.CAMERA_NEAR_CLIP,
meta["camera.farClip"].double?: World.CAMERA_FAR_CLIP
).apply {
position.setZ(World.CAMERA_INITIAL_DISTANCE)
rotation.set(World.CAMERA_INITIAL_X_ANGLE, World.CAMERA_INITIAL_Y_ANGLE, World.CAMERA_INITIAL_Z_ANGLE)
@ -70,7 +70,7 @@ class ThreeOutput(override val context: Context) : Output<DisplayObject> {
//is Box -> ThreeBoxFactory(obj)
//is JSRootObject -> ThreeJSRootFactory(obj)
//is Convex -> ThreeConvexFactory(obj)
else -> findFactory(obj::class)?.invoke(obj)?: error("Factory not found")
else -> findFactory(obj::class)?.invoke(obj) ?: error("Factory not found")
}
}

View File

@ -33,6 +33,6 @@ class ThreePlugin : AbstractPlugin() {
companion object : PluginFactory<ThreePlugin> {
override val tag = PluginTag("vis.three", "hep.dataforge")
override val type = ThreePlugin::class
override fun build() = ThreePlugin()
override fun invoke() = ThreePlugin()
}
}

View File

@ -0,0 +1,39 @@
package hep.dataforge.vis.spatial.jsroot
import hep.dataforge.context.Global
import hep.dataforge.vis.ApplicationBase
import hep.dataforge.vis.DisplayGroup
import hep.dataforge.vis.require
import hep.dataforge.vis.spatial.ThreeOutput
import hep.dataforge.vis.spatial.render
import kotlin.browser.document
class JSRootDemoApp : ApplicationBase() {
override val stateKeys: List<String> = emptyList()
override fun start(state: Map<String, Any>) {
require("JSRootGeoBase.js")
//TODO remove after DI fix
// Global.plugins.load(ThreePlugin())
// Global.plugins.load(JSRootPlugin())
Global.plugins.load(JSRootPlugin)
val renderer = ThreeOutput(Global)
renderer.start(document.getElementById("canvas")!!)
println("started")
lateinit var group: DisplayGroup
renderer.render {
jsRoot ("./geofile_full.json")
}
}
override fun dispose() = emptyMap<String, Any>()//mapOf("lines" to presenter.dispose())
}

View File

@ -4,5 +4,8 @@
package hep.dataforge.vis.spatial.jsroot
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
external fun createGeometry(shape: dynamic, limit: Int): BufferGeometry
external fun build(obj: dynamic, opt: dynamic): Object3D

View File

@ -0,0 +1,82 @@
package hep.dataforge.vis.spatial.jsroot
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.toDynamic
import hep.dataforge.vis.*
import hep.dataforge.vis.spatial.MeshThreeFactory
import info.laht.threekt.core.BufferGeometry
class JSRootGeometry(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, TYPE, meta) {
var shape by node()
var facesLimit by int(0)
fun box(xSize: Number, ySize: Number, zSize: Number) = buildMeta {
"_typename" to "TGeoBBox"
"fDX" to xSize
"fDY" to ySize
"fDZ" to zSize
}
/**
* Create a GDML union
*/
operator fun Meta.plus(other: Meta) = buildMeta {
"fNode.fLeft" to this
"fNode.fRight" to other
"fNode._typename" to "TGeoUnion"
}
/**
* Create a GDML subtraction
*/
operator fun Meta.minus(other: Meta) = buildMeta {
"fNode.fLeft" to this
"fNode.fRight" to other
"fNode._typename" to "TGeoSubtraction"
}
/**
* Intersect two GDML geometries
*/
infix fun Meta.intersect(other: Meta) = buildMeta {
"fNode.fLeft" to this
"fNode.fRight" to other
"fNode._typename" to "TGeoIntersection"
}
companion object {
const val TYPE = "geometry.spatial.jsRoot.geometry"
}
}
fun DisplayGroup.jsRoot(meta: Meta = EmptyMeta, action: JSRootGeometry.() -> Unit = {}) =
JSRootGeometry(this, meta).apply(action).also { addChild(it) }
//fun Meta.toDynamic(): dynamic {
// fun MetaItem<*>.toDynamic(): dynamic = when (this) {
// is MetaItem.ValueItem -> this.value.value.asDynamic()
// is MetaItem.NodeItem -> this.node.toDynamic()
// }
//
// val res = js("{}")
// this.items.entries.groupBy { it.key.body }.forEach { (key, value) ->
// val list = value.map { it.value }
// res[key] = when (list.size) {
// 1 -> list.first().toDynamic()
// else -> list.map { it.toDynamic() }
// }
// }
// return res
//}
object ThreeJSRootFactory : MeshThreeFactory<JSRootGeometry>(JSRootGeometry::class) {
override fun buildGeometry(obj: JSRootGeometry): BufferGeometry {
val shapeMeta = obj.shape?.toDynamic() ?: error("The shape not defined")
return createGeometry(shapeMeta, obj.facesLimit)
}
}

View File

@ -1,84 +1,37 @@
package hep.dataforge.vis.spatial.jsroot
import hep.dataforge.meta.DynamicMeta
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.buildMeta
import hep.dataforge.vis.*
import hep.dataforge.vis.spatial.MeshThreeFactory
import info.laht.threekt.core.BufferGeometry
import hep.dataforge.meta.toDynamic
import hep.dataforge.vis.DisplayGroup
import hep.dataforge.vis.DisplayLeaf
import hep.dataforge.vis.DisplayObject
import hep.dataforge.vis.node
import hep.dataforge.vis.spatial.ThreeFactory
import info.laht.threekt.core.Object3D
class JSRootObject(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, TYPE, meta) {
var shape by node()
var color by item()
var facesLimit by int(0)
fun box(xSize: Number, ySize: Number, zSize: Number) = buildMeta {
"_typename" to "TGeoBBox"
"fDX" to xSize
"fDY" to ySize
"fDZ" to zSize
}
/**
* Create a GDML union
*/
operator fun Meta.plus(other: Meta) = buildMeta {
"fNode.fLeft" to this
"fNode.fRight" to other
"fNode._typename" to "TGeoUnion"
}
/**
* Create a GDML subtraction
*/
operator fun Meta.minus(other: Meta) = buildMeta {
"fNode.fLeft" to this
"fNode.fRight" to other
"fNode._typename" to "TGeoSubtraction"
}
/**
* Intersect two GDML geometries
*/
infix fun Meta.intersect(other: Meta) = buildMeta {
"fNode.fLeft" to this
"fNode.fRight" to other
"fNode._typename" to "TGeoIntersection"
}
var data by node()
var options by node()
companion object {
const val TYPE = "geometry.spatial.jsRoot"
const val TYPE = "geometry.spatial.jsRoot.object"
}
}
fun DisplayGroup.jsRoot(meta: Meta = EmptyMeta, action: JSRootObject.() -> Unit = {}) =
JSRootObject(this, meta).apply(action).also { addChild(it) }
object JSRootObjectFactory : ThreeFactory<JSRootObject> {
fun Meta.toDynamic(): dynamic {
fun MetaItem<*>.toDynamic(): dynamic = when (this) {
is MetaItem.ValueItem -> this.value.value.asDynamic()
is MetaItem.NodeItem -> this.node.toDynamic()
}
override val type = JSRootObject::class
val res = js("{}")
this.items.entries.groupBy { it.key.body }.forEach { (key, value) ->
val list = value.map { it.value }
res[key] = when (list.size) {
1 -> list.first().toDynamic()
else -> list.map { it.toDynamic() }
}
}
return res
}
object ThreeJSRootFactory : MeshThreeFactory<JSRootObject>(JSRootObject::class) {
override fun buildGeometry(obj: JSRootObject): BufferGeometry {
val shapeMeta = obj.shape?.toDynamic() ?: error("The shape not defined")
return createGeometry(shapeMeta, obj.facesLimit)
override fun invoke(obj: JSRootObject): Object3D {
return build(obj.data?.toDynamic(), obj.options?.toDynamic())
}
}
fun DisplayGroup.jsRoot(path: String) {
JSRootObject(this, EmptyMeta).apply{
data = DynamicMeta(hep.dataforge.vis.require(path))
}.also { addChild(it) }
}

View File

@ -20,6 +20,6 @@ class JSRootPlugin : AbstractPlugin() {
companion object: PluginFactory<JSRootPlugin> {
override val tag = PluginTag("vis.jsroot", "hep.dataforge")
override val type = JSRootPlugin::class
override fun build() = JSRootPlugin()
override fun invoke() = JSRootPlugin()
}
}

View File

@ -3,6 +3,9 @@
package hep.dataforge.vis.spatial.three
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
import info.laht.threekt.math.Vector3
external class ConvexGeometry(points: Array<Vector3>) : BufferGeometry
external class ConvexGeometry(points: Array<Vector3>) : Geometry
external class ConvexBufferGeometry(points: Array<Vector3>) : BufferGeometry

View File

@ -9,6 +9,8 @@ import hep.dataforge.vis.spatial.rotationOrder
import hep.dataforge.vis.spatial.rotationX
import hep.dataforge.vis.spatial.rotationY
import hep.dataforge.vis.spatial.rotationZ
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.math.Euler
import info.laht.threekt.math.Vector3
@ -26,3 +28,5 @@ fun Group(children: Collection<Object3D>) = info.laht.threekt.objects.Group().ap
val DisplayObject.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)
fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }

View File

@ -3398,7 +3398,7 @@
process(toplevel, 0, 1, 1000000);
};
GEO.build = function (obj, opt, call_back) {
GEO.build = function (obj, opt) {
// function can be used to build three.js model for TGeo object
if (!obj) return;
@ -3510,7 +3510,7 @@
// mesh.renderOrder = clones.maxdepth - entry.stack.length;
}
JSROOT.CallBack(call_back, toplevel);
//JSROOT.CallBack(call_back, toplevel);
return toplevel;
};

View File

@ -32,10 +32,12 @@ class ConvexBuilder {
}
fun build(parent: DisplayObject?, meta: Meta): Convex {
val builder = meta.builder()
points.forEachIndexed { index, value ->
builder["points.point[$index]"] = value.toMeta()
}
return Convex(parent, builder.seal())
val points = buildMeta {
points.forEachIndexed { index, value ->
"points.point[$index]" to value.toMeta()
}
}.seal()
return Convex(parent, points)
}
}

View File

@ -6,6 +6,7 @@ import hep.dataforge.vis.DisplayGroup
import hep.dataforge.vis.DisplayNode
import hep.dataforge.vis.DisplayObject
import hep.dataforge.vis.DisplayObject.Companion.DEFAULT_TYPE
import hep.dataforge.vis.get
fun DisplayGroup.group(meta: Meta = EmptyMeta, action: DisplayGroup.() -> Unit = {}) =
DisplayNode(this, DEFAULT_TYPE, meta).apply(action).also { addChild(it) }
@ -18,26 +19,38 @@ fun Output<DisplayObject>.render(meta: Meta = EmptyMeta, action: DisplayGroup.()
// Common properties
/**
* Visibility property. Inherited from parent
*/
var DisplayObject.visible
get() = properties["visible"].boolean ?: true
get() = this["visible"].boolean ?: true
set(value) {
properties.style["visible"] = value
}
// 3D Object position
/**
* x position property relative to parent. Not inherited
*/
var DisplayObject.x
get() = properties["pos.x"].number ?: 0.0
set(value) {
properties.style["pos.x"] = value
}
/**
* y position property. Not inherited
*/
var DisplayObject.y
get() = properties["pos.y"].number ?: 0.0
set(value) {
properties.style["pos.y"] = value
}
/**
* z position property. Not inherited
*/
var DisplayObject.z
get() = properties["pos.z"].number ?: 0.0
set(value) {
@ -46,18 +59,27 @@ var DisplayObject.z
// 3D Object rotation
/**
* x rotation relative to parent. Not inherited
*/
var DisplayObject.rotationX
get() = properties["rotation.x"].number ?: 0.0
set(value) {
properties.style["rotation.x"] = value
}
/**
* y rotation relative to parent. Not inherited
*/
var DisplayObject.rotationY
get() = properties["rotation.y"].number ?: 0.0
set(value) {
properties.style["rotation.y"] = value
}
/**
* z rotation relative to parent. Not inherited
*/
var DisplayObject.rotationZ
get() = properties["rotation.z"].number ?: 0.0
set(value) {
@ -73,6 +95,9 @@ enum class RotationOrder {
ZYX
}
/**
* Rotation order. Not inherited
*/
var DisplayObject.rotationOrder: RotationOrder
get() = properties["rotation.order"].enum<RotationOrder>() ?: RotationOrder.XYZ
set(value) {
@ -81,24 +106,50 @@ var DisplayObject.rotationOrder: RotationOrder
// 3D object scale
/**
* X scale. Not inherited
*/
var DisplayObject.scaleX
get() = properties["scale.x"].number ?: 1.0
set(value) {
properties.style["scale.x"] = value
}
/**
* Y scale. Not inherited
*/
var DisplayObject.scaleY
get() = properties["scale.y"].number ?: 1.0
set(value) {
properties.style["scale.y"] = value
}
/**
* Z scale. Not inherited
*/
var DisplayObject.scaleZ
get() = properties["scale.z"].number ?: 1.0
set(value) {
properties.style["scale.z"] = value
}
fun DisplayObject.color(rgb: Int){
this.properties.style["color"] = rgb
}
fun DisplayObject.color(meta: Meta){
this.properties.style["color"] = meta
}
fun DisplayObject.color(r: Int, g: Int, b: Int) = color(buildMeta {
"red" to r
"green" to g
"blue" to b
})
//TODO add inherited scale
object World {
const val CAMERA_INITIAL_DISTANCE = -500.0
const val CAMERA_INITIAL_X_ANGLE = -50.0

View File

@ -0,0 +1,37 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.get
import hep.dataforge.meta.getAll
import hep.dataforge.meta.node
import hep.dataforge.names.toName
import hep.dataforge.vis.DisplayNode
import kotlin.test.Test
import kotlin.test.assertEquals
class ConvexTest {
@Test
fun testConvexBuilder() {
val group = DisplayNode().apply {
convex {
point(50, 50, -50)
point(50, -50, -50)
point(-50, -50, -50)
point(-50, 50, -50)
point(50, 50, 50)
point(50, -50, 50)
point(-50, -50, 50)
point(-50, 50, 50)
}
}
val convex = group.children.first() as Convex
val pointsNode = convex.properties["points"].node
assertEquals(8, pointsNode?.items?.count())
val points = pointsNode?.getAll("point".toName())
assertEquals(8, convex.points.size)
}
}