All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.earth.worldwind.shape.milstd2525.AbstractMilStd2525TacticalGraphic.kt Maven / Gradle / Ivy

package earth.worldwind.shape.milstd2525

import earth.worldwind.geom.AltitudeMode
import earth.worldwind.geom.Angle
import earth.worldwind.geom.Location
import earth.worldwind.geom.Sector
import earth.worldwind.render.AbstractSurfaceRenderable
import earth.worldwind.render.RenderContext
import earth.worldwind.render.Renderable
import earth.worldwind.shape.*
import earth.worldwind.shape.milstd2525.MilStd2525.labelScaleThreshold
import kotlin.jvm.JvmStatic
import kotlin.math.PI
import kotlin.math.ln
import kotlin.math.roundToInt

abstract class AbstractMilStd2525TacticalGraphic(
    protected val sidc: String, protected val boundingSector: Sector,
    modifiers: Map?, attributes: Map?,
) : AbstractSurfaceRenderable(boundingSector), Highlightable {
    override var isHighlighted = false
    var modifiers = modifiers
        set(value) {
            field = value
            reset()
        }
    var attributes = attributes
        set(value) {
            field = value
            reset()
        }
    private var minScale = Double.MIN_VALUE
    private var maxScale = Double.MAX_VALUE
    private val lodBuffer = mutableMapOf>()
    private val lodSector = mutableMapOf()

    protected companion object {
        const val MAX_WIDTH_DP = 1e-3
        const val MIN_WIDTH_DP = 1e-5
        const val HIGHLIGHT_FACTOR = 2f

        private const val ZERO_LEVEL_PX = 256

        @JvmStatic
        fun defaultBoundingSector(locations: List) = Sector().apply { locations.forEach { l -> union(l) } }

        private fun computeNearestLoD(equatorialRadius: Double, scale: Double) =
            (ln(2 * PI * equatorialRadius / ZERO_LEVEL_PX / scale) / ln(2.0)).roundToInt()

        private fun computeLoDScale(equatorialRadius: Double, lod: Int) =
            2 * PI * equatorialRadius / ZERO_LEVEL_PX / (1 shl lod)
    }

    init {
        recalculateScaleLimits()
    }

    fun setBoundingSector(sector: Sector) {
        boundingSector.copy(sector)
        recalculateScaleLimits()
    }

    override fun doRender(rc: RenderContext) {
        // Get the current map scale based on observation range.
        val currentScale = rc.pixelSize * rc.densityFactor
        // Limit scale based on clipping sector diagonal size
        val limitedScale = currentScale.coerceIn(minScale, maxScale)
        // Get renderables for current LoD
        val equatorialRadius = rc.globe.equatorialRadius
        val lod = computeNearestLoD(equatorialRadius, limitedScale)
        // Set sector based on selected lod
        sector.copy(lodSector[lod] ?: boundingSector)
        // Check if tactical graphics visible
        val terrainSector = rc.terrain.sector
        if (!terrainSector.isEmpty && terrainSector.intersects(sector) && getExtent(rc).intersectsFrustum(rc.frustum)) {
            val shapes = lodBuffer[lod] ?: run {
                sector.setEmpty() // Prepare bounding box to be extended by real graphics measures
                makeRenderables(computeLoDScale(equatorialRadius, lod)).also {
                    lodBuffer[lod] = it
                    lodSector[lod] = Sector(sector) // Remember real bounding box based on LoD
                }
            }
            // Draw available shapes
            for (renderable in shapes) {
                if (renderable is Highlightable) renderable.isHighlighted = isHighlighted
                if (renderable !is Label || isHighlighted || currentScale <= labelScaleThreshold) renderable.render(rc)
            }
        }
    }

    protected abstract fun makeRenderables(scale: Double): List // Platform dependent implementation

    protected fun reset() {
        lodBuffer.clear()
        lodSector.clear()
    }

    protected fun applyShapeAttributes(shape: AbstractShape) = shape.apply {
        altitudeMode = AltitudeMode.CLAMP_TO_GROUND
        isFollowTerrain = true
        maximumIntermediatePoints = 0 // Do not draw intermediate vertices for tactical graphics
        highlightAttributes = ShapeAttributes(attributes).apply {
            outlineWidth *= HIGHLIGHT_FACTOR
        }
        pickDelegate = this@AbstractMilStd2525TacticalGraphic
    }

    protected fun applyLabelAttributes(label: Label, angle: Angle) = label.apply {
        altitudeMode = AltitudeMode.CLAMP_TO_GROUND
        rotation = angle
        rotationMode = OrientationMode.RELATIVE_TO_GLOBE
        pickDelegate = this@AbstractMilStd2525TacticalGraphic
    }

    private fun recalculateScaleLimits() {
        val diagonalDistance = Location(boundingSector.minLatitude, boundingSector.minLongitude)
            .greatCircleDistance(Location(boundingSector.maxLatitude, boundingSector.maxLongitude))
        minScale = diagonalDistance / MAX_WIDTH_DP
        maxScale = diagonalDistance / MIN_WIDTH_DP
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy