Skip to content

Commit

Permalink
Fix Tactical Graphics sector calculation based on level of details.
Browse files Browse the repository at this point in the history
  • Loading branch information
ComBatVision committed Jun 26, 2024
1 parent 3a423d2 commit 01a54aa
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 95 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ repositories {
}
dependencies {
implementation 'earth.worldwind:worldwind:1.5.17'
implementation 'earth.worldwind:worldwind:1.5.19'
}
```

Expand Down
8 changes: 4 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
plugins {
val kotlinVersion = "1.9.23"
val kotlinVersion = "1.9.24"
kotlin("multiplatform") version kotlinVersion apply false
kotlin("plugin.serialization") version kotlinVersion apply false
kotlin("android") version kotlinVersion apply false
id("org.jetbrains.dokka") version "1.9.10" apply false
id("org.jetbrains.dokka") version "1.9.20" apply false
id("com.android.library") apply false
id("com.android.application") apply false
}

buildscript {
dependencies {
classpath("dev.icerock.moko:resources-generator:0.24.0-beta-2")
classpath("dev.icerock.moko:resources-generator:0.24.1")
}
}

allprojects {
group = "earth.worldwind"
version = "1.5.17"
version = "1.5.19"

extra.apply {
set("minSdk", 21)
Expand Down
2 changes: 1 addition & 1 deletion worldwind/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ kotlin {
}
sourceSets {
val mockkVersion = "1.13.10"
val mokoVersion = "0.24.0-beta-2"
val mokoVersion = "0.24.1"
val ktorVersion = "2.3.10"
val ormliteVersion = "6.1"
val commonMain by getting {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import kotlin.math.roundToInt
actual open class MilStd2525TacticalGraphic @JvmOverloads actual constructor(
sidc: String, locations: List<Location>,
boundingSector: Sector, modifiers: Map<String, String>?, attributes: Map<String, String>?
) : AbstractMilStd2525TacticalGraphic(sidc, locations, boundingSector, modifiers, attributes) {
) : AbstractMilStd2525TacticalGraphic(sidc, boundingSector, modifiers, attributes) {
protected lateinit var controlPoints: ArrayList<Point2D>
protected lateinit var pointUL: Point2D.Double

Expand All @@ -34,7 +34,11 @@ actual open class MilStd2525TacticalGraphic @JvmOverloads actual constructor(
MilStd2525.graphicModifiersFromSparseArray(modifiers), MilStd2525.attributesFromSparseArray(attributes)
)

override fun transformLocations(locations: List<Location>) {
init {
setAnchorLocations(locations)
}

fun setAnchorLocations(locations: List<Location>) {
if (this::controlPoints.isInitialized) controlPoints.clear() else controlPoints = ArrayList()
for (location in locations) controlPoints.add(Point2D.Double(location.longitude.inDegrees, location.latitude.inDegrees))
var left = controlPoints[0].x
Expand All @@ -57,6 +61,7 @@ actual open class MilStd2525TacticalGraphic @JvmOverloads actual constructor(
}
}
if (this::pointUL.isInitialized) pointUL.setLocation(left, top) else pointUL = Point2D.Double(left, top)
reset()
}

override fun makeRenderables(scale: Double): List<Renderable> {
Expand Down Expand Up @@ -92,7 +97,6 @@ actual open class MilStd2525TacticalGraphic @JvmOverloads actual constructor(
val outlines = mutableListOf<Renderable>()
for (i in mss.symbolShapes.indices) convertShapeToRenderables(mss.symbolShapes[i], mss, ipc, shapes, outlines)
for (i in mss.modifierShapes.indices) convertShapeToRenderables(mss.modifierShapes[i], mss, ipc, shapes, outlines)
invalidateExtent() // Regenerate extent in next frame due to sector may be extended by real shape measures
return outlines + shapes
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ import earth.worldwind.geom.Sector
import earth.worldwind.globe.Globe

abstract class AbstractSurfaceRenderable(sector: Sector, displayName: String? = null) : AbstractRenderable(displayName) {
var sector = Sector(sector)
set(value) {
field.copy(value)
invalidateExtent()
}
val sector = Sector(sector)
protected val extent by lazy { BoundingBox() }
protected val heightLimits by lazy { FloatArray(2) }
protected var heightLimitsTimestamp = 0L
protected var extentExaggeration = 0.0f
protected var extentGlobeState: Globe.State? = null
protected var extentGlobeOffset: Globe.Offset? = null
protected val extentSector = Sector()

protected open fun getExtent(rc: RenderContext): BoundingBox {
val globe = rc.globe
Expand All @@ -27,15 +24,16 @@ abstract class AbstractSurfaceRenderable(sector: Sector, displayName: String? =
val state = rc.globeState
val offset = rc.globe.offset
if (timestamp != heightLimitsTimestamp || ve != extentExaggeration
|| state != extentGlobeState || offset != extentGlobeOffset ) {
|| state != extentGlobeState || offset != extentGlobeOffset || extentSector != sector) {
val minHeight = heightLimits[0] * ve
val maxHeight = heightLimits[1] * ve
extent.setToSector(sector, globe, minHeight, maxHeight)
heightLimitsTimestamp = timestamp
extentExaggeration = ve
extentGlobeState = state
extentGlobeOffset = offset
extentSector.copy(sector)
}
heightLimitsTimestamp = timestamp
extentExaggeration = ve
extentGlobeState = state
extentGlobeOffset = offset
return extent
}

Expand All @@ -47,9 +45,4 @@ abstract class AbstractSurfaceRenderable(sector: Sector, displayName: String? =
// check for valid height limits
if (heightLimits[0] > heightLimits[1]) heightLimits.fill(0f)
}

protected open fun invalidateExtent() {
heightLimitsTimestamp = 0L
extentExaggeration = 0.0f
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ open class TriangleShaderProgram : AbstractShaderProgram() {
protected val color = Color()
protected var opacity = 1.0f
protected var lineWidth = 1.0f
protected var invMiterLengthCutoff = 1 / 5.0f // shouldn't be greater than 1.0 or equal 0.0
protected var invMiterLengthCutoff = 1.0f
protected var screenX = 0.0f
protected var screenY = 0.0f

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package earth.worldwind.shape.milstd2525

import earth.worldwind.geom.*
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 earth.worldwind.util.Logger
import kotlin.jvm.JvmStatic
import kotlin.math.PI
import kotlin.math.ln
import kotlin.math.roundToInt

abstract class AbstractMilStd2525TacticalGraphic(
protected val sidc: String, locations: List<Location>, boundingSector: Sector,
protected val sidc: String, protected val boundingSector: Sector,
modifiers: Map<String, String>?, attributes: Map<String, String>?,
) : AbstractSurfaceRenderable(boundingSector), Highlightable {
override var isHighlighted = false
Expand All @@ -27,18 +29,17 @@ abstract class AbstractMilStd2525TacticalGraphic(
field = value
reset()
}
private var minScale = 0.0
private var maxScale = 0.0
private var minScale = Double.MIN_VALUE
private var maxScale = Double.MAX_VALUE
private val lodBuffer = mutableMapOf<Int, List<Renderable>>()
private val lodSector = mutableMapOf<Int, Sector>()

protected companion object {
const val MAX_WIDTH_DP = 0.0005
const val MIN_WIDTH_DP = 0.000015
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
private val forwardRay = Line()
private val lookAtPoint = Vec3()

@JvmStatic
fun defaultBoundingSector(locations: List<Location>) = Sector().apply { locations.forEach { l -> union(l) } }
Expand All @@ -50,28 +51,35 @@ abstract class AbstractMilStd2525TacticalGraphic(
2 * PI * equatorialRadius / ZERO_LEVEL_PX / (1 shl lod)
}

init { setGeometry(locations, boundingSector) }
init {
recalculateScaleLimits()
}

fun setGeometry(locations: List<Location>, boundingSector: Sector = defaultBoundingSector(locations)) {
require(locations.isNotEmpty()) {
Logger.logMessage(Logger.ERROR, "MilStd2525TacticalGraphic", "constructor", "missingList")
}
transformLocations(locations)
sector = boundingSector
reset()
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)) {
// 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)
val shapes = lodBuffer[lod] ?: makeRenderables(computeLoDScale(equatorialRadius, lod)).also { lodBuffer[lod] = it }
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
Expand All @@ -80,19 +88,12 @@ abstract class AbstractMilStd2525TacticalGraphic(
}
}

override fun invalidateExtent() {
super.invalidateExtent()
// Recalculate scale limits according to new sector
val diagonalDistance = Location(sector.maxLatitude, sector.minLongitude)
.greatCircleDistance(Location(sector.minLatitude, sector.maxLongitude))
minScale = diagonalDistance / MAX_WIDTH_DP
maxScale = diagonalDistance / MIN_WIDTH_DP
}

protected open fun reset() = lodBuffer.clear()
protected abstract fun makeRenderables(scale: Double): List<Renderable> // Platform dependent implementation

abstract fun transformLocations(locations: List<Location>)
abstract fun makeRenderables(scale: Double): List<Renderable>
protected fun reset() {
lodBuffer.clear()
lodSector.clear()
}

protected fun applyShapeAttributes(shape: AbstractShape) = shape.apply {
altitudeMode = AltitudeMode.CLAMP_TO_GROUND
Expand All @@ -110,4 +111,11 @@ abstract class AbstractMilStd2525TacticalGraphic(
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,23 @@ import earth.worldwind.geom.Position
import earth.worldwind.geom.Sector
import earth.worldwind.render.Font
import earth.worldwind.render.Renderable
import earth.worldwind.render.image.ImageSource
import earth.worldwind.shape.*
import earth.worldwind.util.Logger
import kotlin.math.roundToInt

actual open class MilStd2525TacticalGraphic actual constructor(
sidc: String, locations: List<Location>,
boundingSector: Sector, modifiers: Map<String, String>?, attributes: Map<String, String>?
) : AbstractMilStd2525TacticalGraphic(sidc, locations, boundingSector, modifiers, attributes) {
) : AbstractMilStd2525TacticalGraphic(sidc, boundingSector, modifiers, attributes) {
protected lateinit var controlPoints: java.util.ArrayList<armyc2.c2sd.graphics2d.Point2D>
protected lateinit var pointUL: armyc2.c2sd.graphics2d.Point2D

protected companion object {
fun convertColor(color: Color) = earth.worldwind.render.Color(
color.getRed().toInt(),
color.getGreen().toInt(),
color.getBlue().toInt(),
color.getAlpha().toInt()
)
init {
setAnchorLocations(locations)
}

override fun transformLocations(locations: List<Location>) {
fun setAnchorLocations(locations: List<Location>) {
if (this::controlPoints.isInitialized) controlPoints.clear() else controlPoints = java.util.ArrayList()
for (location in locations) controlPoints.add(armyc2.c2sd.graphics2d.Point2D(location.longitude.inDegrees, location.latitude.inDegrees))
val point0 = controlPoints.get(0) ?: return
Expand All @@ -49,7 +46,7 @@ actual open class MilStd2525TacticalGraphic actual constructor(
}
}
if (this::pointUL.isInitialized) pointUL.setLocation(left, top) else pointUL = armyc2.c2sd.graphics2d.Point2D(left, top)
pointUL.setLocation(left, top)
reset()
}

override fun makeRenderables(scale: Double): List<Renderable> {
Expand Down Expand Up @@ -85,7 +82,6 @@ actual open class MilStd2525TacticalGraphic actual constructor(
val outlines = mutableListOf<Renderable>()
for (i in 0 until mss.getSymbolShapes().size()) convertShapeToRenderables(mss.getSymbolShapes().get(i)!!, mss, ipc, shapes, outlines)
for (i in 0 until mss.getModifierShapes().size()) convertShapeToRenderables(mss.getModifierShapes().get(i)!!, mss, ipc, shapes, outlines)
invalidateExtent() // Regenerate extent in next frame due to sector may be extended by real shape measures
return outlines + shapes
}

Expand All @@ -98,15 +94,14 @@ actual open class MilStd2525TacticalGraphic actual constructor(
outlineWidth = MilStd2525.graphicsLineWidth
(si.getLineColor() ?: si.getFillColor())?.let { outlineColor = convertColor(it) } ?: return
(si.getFillColor() ?: si.getLineColor())?.let { interiorColor = convertColor(it) } ?: return
// TODO Fill dash pattern
// val stroke = shape.getStroke()
// if (stroke is armyc2.c2sd.graphics2d.BasicStroke) {
// val dash = stroke.getDashArray()
// if (!dash.isNullOrEmpty()) outlineImageSource = ImageSource.fromLineStipple(
// // TODO How to correctly interpret dash array?
// factor = dash[0].roundToInt(), pattern = 0xF0F0.toShort()
// )
// }
val stroke = si.getStroke()
if (stroke is armyc2.c2sd.graphics2d.BasicStroke) {
val dash = stroke.getDashArray()
if (!dash.isNullOrEmpty()) outlineImageSource = ImageSource.fromLineStipple(
// TODO How to correctly interpret dash array?
factor = dash[0].roundToInt(), pattern = 0xF0F0.toShort()
)
}
}
val hasOutline = MilStd2525.graphicsOutlineWidth != 0f
val outlineAttributes = if (hasOutline) ShapeAttributes(shapeAttributes).apply {
Expand Down Expand Up @@ -160,4 +155,13 @@ actual open class MilStd2525TacticalGraphic actual constructor(
else -> Logger.logMessage(Logger.ERROR, "MilStd2525TacticalGraphic", "convertShapeToRenderables", "unknownShapeType")
}
}

protected companion object {
fun convertColor(color: Color) = earth.worldwind.render.Color(
color.getRed().toInt(),
color.getGreen().toInt(),
color.getBlue().toInt(),
color.getAlpha().toInt()
)
}
}
Loading

0 comments on commit 01a54aa

Please sign in to comment.