diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt index ba3e4616..2c501fd7 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt @@ -276,6 +276,44 @@ private fun SolidGroup.addShape( } } + "TGeoCone" -> { + val fDz by shape.meta.double(0.0) + val fRmin1 by shape.meta.double(0.0) + val fRmax1 by shape.meta.double(0.0) + val fRmin2 by shape.meta.double(0.0) + val fRmax2 by shape.meta.double(0.0) + + coneSurface( + bottomOuterRadius = fRmax1, + bottomInnerRadius = fRmin1, + height = fDz * 2.0, + topOuterRadius = fRmax2, + topInnerRadius = fRmin2, + name = name, + ) + } + + "TGeoCtub" -> { + val fRmin by shape.meta.double(0.0) + val fRmax by shape.meta.double(0.0) + val fDz by shape.meta.double(0.0) + val fPhi1 by shape.meta.double(0.0) + val fPhi2 by shape.meta.double(PI2.toDouble()) + val fNlow by shape.meta.doubleArray() + val fNhigh by shape.meta.doubleArray() + + cutTube( + outerRadius = fRmax, + innerRadius = fRmin, + height = fDz * 2.0, + startAngle = degToRad(fPhi1), + angle = degToRad(fPhi2 - fPhi1), + topNormal = Float32Vector3D(fNhigh[0], fNhigh[1], fNhigh[2]), + bottomNormal = Float32Vector3D(fNlow[0], fNlow[1], fNlow[2]), + name = name, + ) + } + else -> { TODO("A shape with type ${shape.typename} not implemented") } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/CutTube.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/CutTube.kt new file mode 100644 index 00000000..cc0ef614 --- /dev/null +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/CutTube.kt @@ -0,0 +1,168 @@ +package space.kscience.visionforge.solid + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import space.kscience.visionforge.MutableVisionContainer +import space.kscience.visionforge.VisionBuilder +import space.kscience.visionforge.setChild +import kotlin.math.abs +import kotlin.math.cos +import kotlin.math.sin + +/** + * A tube section cut with two planes. + * The centers of the two sections are positioned + * along the central axis of the tube, at the top + * and at the bottom. + * + * The default segment number is 32. + */ +@Serializable +@SerialName("solid.cutTube") +public class CutTube( + public val outerRadius: Float, + public val innerRadius: Float, + public val height: Float, + public val phiStart: Float = 0f, + public val phi: Float = PI2, + public val nTop: Float32Vector3D, + public val nBottom: Float32Vector3D, +) : SolidBase(), GeometrySolid { + + init { + require(innerRadius >= 0) { "Tube inner radius must be non-negative" } + require(outerRadius >= 0) { "Tube outer radius must not be less than its inner radius" } + require(height >= 0) { "Tube height must be non-negative" } + //TODO implement more "subtle" section checks + require(abs(nTop.z) > 1e-5) { "Top section is almost vertical" } + require(abs(nBottom.z) > 1e-5) { "Bottom section is almost vertical" } + //require(phiStart >= 0) + require(phi in (0f..(PI2))) + } + + override fun toGeometry(geometryBuilder: GeometryBuilder) { + val segments = detail ?: 32 + require(segments >= 4) { "The number of segments in the tube is too small" } + val angleStep = phi / (segments - 1) + + fun section(r: Float, z: Float, n: Float32Vector3D): List = (0 until segments).map { i -> + val x = r * cos(phiStart + angleStep * i) + val y = r * sin(phiStart + angleStep * i) + Float32Vector3D(x, y, (n.z * z - n.x * x - n.y * y) / n.z) + } + + geometryBuilder.apply { + //creating top and bottom sections of the tube: + //shapes in planes defined with (0, 0, +-z) points and normal vectors + //TODO check for an intersection of the sections + val topOuterPoints = section(outerRadius, height / 2, nTop) + val bottomOuterPoints = section(outerRadius, -height / 2, nBottom) + + + //outer face + for (it in 1 until segments) { + face4(bottomOuterPoints[it - 1], bottomOuterPoints[it], topOuterPoints[it], topOuterPoints[it - 1]) + } + if (phi == PI2) { + face4(bottomOuterPoints.last(), bottomOuterPoints[0], topOuterPoints[0], topOuterPoints.last()) + } + + if (innerRadius == 0f) { + val zeroBottom = Float32Vector3D(0f, 0f, -height / 2) + val zeroTop = Float32Vector3D(0f, 0f, height / 2) + (1 until segments).forEach { + face(bottomOuterPoints[it - 1], zeroBottom, bottomOuterPoints[it]) + face(topOuterPoints[it - 1], topOuterPoints[it], zeroTop) + } + if (phi == PI2) { + face(bottomOuterPoints.last(), zeroBottom, bottomOuterPoints[0]) + face(topOuterPoints.last(), topOuterPoints[0], zeroTop) + } else { + face4(zeroTop, zeroBottom, bottomOuterPoints[0], topOuterPoints[0]) + face4(zeroTop, zeroBottom, bottomOuterPoints.last(), topOuterPoints.last()) + } + } else { + val topInnerPoints = section(innerRadius, height / 2, nTop) + val bottomInnerPoints = section(innerRadius, -height / 2, nBottom) + // inner face + (1 until segments).forEach { + // inner surface + face4( + bottomInnerPoints[it], + bottomInnerPoints[it - 1], + topInnerPoints[it - 1], + topInnerPoints[it] + ) + //bottom cup + face4( + bottomInnerPoints[it - 1], + bottomInnerPoints[it], + bottomOuterPoints[it], + bottomOuterPoints[it - 1] + ) + //upper cup + face4( + topInnerPoints[it], + topInnerPoints[it - 1], + topOuterPoints[it - 1], + topOuterPoints[it] + ) + } + if (phi == PI2) { + face4(bottomInnerPoints[0], bottomInnerPoints.last(), topInnerPoints.last(), topInnerPoints[0]) + face4( + bottomInnerPoints.last(), + bottomInnerPoints[0], + bottomOuterPoints[0], + bottomOuterPoints.last() + ) + face4(topInnerPoints[0], topInnerPoints.last(), topOuterPoints.last(), topOuterPoints[0]) + } else { + face4(bottomInnerPoints[0], bottomOuterPoints[0], topOuterPoints[0], topInnerPoints[0]) + face4( + bottomOuterPoints.last(), + bottomInnerPoints.last(), + topInnerPoints.last(), + topOuterPoints.last() + ) + } + } + } + } +} + + +/** + * Create a cut tube - a hollow cylinder (or a segment) cut with two planes. The axis of the cylinder + * is the Z axis. Each section is define by a center and a normal vector. The centers of the two sections + * are the points (0, 0, height / 2) and (0, 0, -height / 2). + * + * @param outerRadius outer radius of the tube + * @param innerRadius inner radius if the tube + * @param height height (length) of the tube. Essentially this is the length of the Z axis segment, + * contained between the top and bottom sections of the tube + * @param startAngle start angle of the segment (if this is a segment) + * @param angle angular magnitude of the segment + * @param topNormal normal vector of the top section + * @param bottomNormal normal vector of the bottom section + */ +@VisionBuilder +public inline fun MutableVisionContainer.cutTube( + outerRadius: Number, + innerRadius: Number, + height: Number, + startAngle: Number = 0f, + angle: Number = PI2, + topNormal: Float32Vector3D, + bottomNormal: Float32Vector3D, + name: String? = null, + block: CutTube.() -> Unit = {}, +): CutTube = CutTube( + outerRadius = outerRadius.toFloat(), + innerRadius = innerRadius.toFloat(), + height = height.toFloat(), + phiStart = startAngle.toFloat(), + phi = angle.toFloat(), + nTop = topNormal, + nBottom = bottomNormal +).apply(block).also { setChild(name, it) } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt index 2a2e86fc..d6f992ec 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt @@ -46,6 +46,7 @@ public class Solids(meta: Meta) : VisionPlugin(meta), MutableVisionContainer