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

commonMain.earth.worldwind.frame.BasicFrameController.kt Maven / Gradle / Ivy

package earth.worldwind.frame

import earth.worldwind.PickedObject.Companion.fromTerrain
import earth.worldwind.PickedObject.Companion.identifierToUniqueColor
import earth.worldwind.PickedObject.Companion.uniqueColorToIdentifier
import earth.worldwind.draw.DrawContext
import earth.worldwind.draw.DrawableSurfaceColor
import earth.worldwind.draw.DrawableSurfaceColor.Companion.obtain
import earth.worldwind.geom.*
import earth.worldwind.globe.Globe
import earth.worldwind.globe.terrain.Terrain
import earth.worldwind.render.Color
import earth.worldwind.render.RenderContext
import earth.worldwind.render.program.BasicShaderProgram
import earth.worldwind.util.Logger.ERROR
import earth.worldwind.util.Logger.logMessage
import earth.worldwind.util.kgl.GL_COLOR_BUFFER_BIT
import earth.worldwind.util.kgl.GL_DEPTH_BUFFER_BIT
import kotlin.math.roundToInt

open class BasicFrameController: FrameController {
    override val lastTerrains = mutableMapOf()
    private val pickColor = Color()
    private val pickPoint = Vec3()
    private val pickPos = Position()
    private val boundingBox = BoundingBox()
    private val fullSphere = Sector().setFullSphere()
    private val scratchPoint = Vec3()
    private val scratchRay = Line()

    override fun renderFrame(rc: RenderContext) {
        if (!rc.isPickMode) lastTerrains.clear()
        if (rc.globe.is2D && rc.globe.isContinuous) {
            // Tessellate and render all visible globe offsets of 2D continuous terrain
            renderGlobeOffset(rc, Globe.Offset.Center)
            renderGlobeOffset(rc, Globe.Offset.Right)
            renderGlobeOffset(rc, Globe.Offset.Left)
            // Reset globe offset for correct projection calculations after frame rendered
            rc.globe.offset = Globe.Offset.Center
        } else {
            // Tessellate and render single 3D terrain
            renderGlobeOffset(rc, Globe.Offset.Center)
        }
        rc.sortDrawables()
    }

    protected open fun renderGlobeOffset(rc: RenderContext, globeOffset: Globe.Offset) {
        rc.globe.offset = globeOffset

        // Check if 2D globe offset intersects current frustum
        if (rc.globe.is2D && !boundingBox.setToSector(fullSphere, rc.globe, 0f, 0f).intersectsFrustum(rc.frustum)) return

        // Prepare terrain for specified globe offset
        rc.terrain = rc.terrainTessellator.tessellate(rc)

        // Compute viewing distance and pixel size based on available terrain
        if (!rc.globe.is2D) adjustViewingParameters(rc)

        // Render the terrain picked object or remember the last terrain for future intersect operations
        if (rc.isPickMode) renderTerrainPickedObject(rc) else lastTerrains[globeOffset] = rc.terrain

        // Render all layers on specified globe offset
        rc.layers.render(rc)
    }

    protected open fun adjustViewingParameters(rc: RenderContext) {
        scratchRay.origin.copy(rc.cameraPoint)
        rc.modelview.extractForwardVector(scratchRay.direction)
        rc.viewingDistance = if (rc.terrain.intersect(scratchRay, scratchPoint)) {
            rc.lookAtPosition = rc.globe.cartesianToGeographic(scratchPoint.x, scratchPoint.y, scratchPoint.z, Position())
            scratchPoint.distanceTo(rc.cameraPoint)
        } else rc.horizonDistance
        rc.pixelSize = rc.pixelSizeAtDistance(rc.viewingDistance)
    }

    protected open fun renderTerrainPickedObject(rc: RenderContext) {
        if (rc.terrain.sector.isEmpty) return  // no terrain to pick

        // Acquire a unique picked object ID for terrain.
        val pickedObjectId = rc.nextPickedObjectId()

        // Enqueue a drawable for processing on the OpenGL thread that displays terrain in the unique pick color.
        val pool = rc.getDrawablePool()
        val drawable = obtain(pool)
        identifierToUniqueColor(pickedObjectId, drawable.color)
        drawable.opacity = 1.0f // Just to be sure to reset opacity
        drawable.program = rc.getShaderProgram { BasicShaderProgram() }
        rc.offerSurfaceDrawable(drawable, Double.NEGATIVE_INFINITY)

        // If the pick ray intersects the terrain, enqueue a picked object that associates the terrain drawable with its
        // picked object ID and the intersection position.
        val pickRay = rc.pickRay
        if (pickRay != null && rc.terrain.intersect(pickRay, pickPoint)) {
            rc.globe.cartesianToGeographic(pickPoint.x, pickPoint.y, pickPoint.z, pickPos)
            rc.offerPickedObject(fromTerrain(pickedObjectId, pickPos))
        }
    }

    override fun drawFrame(dc: DrawContext) {
        clearFrame(dc)
        drawDrawables(dc)
        if (dc.isPickMode) resolvePick(dc)
    }

    protected open fun clearFrame(dc: DrawContext) {
        dc.gl.clear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
    }

    protected open fun drawDrawables(dc: DrawContext) {
        dc.rewindDrawables()
        while (true) {
            val next = dc.pollDrawable() ?: break
            try {
                next.draw(dc)
            } catch (e: Exception) {
                logMessage(
                    ERROR, "BasicFrameController", "drawDrawables",
                    "Exception while drawing '$next'", e
                )
                // Keep going. Draw the remaining drawables.
            }
        }
    }

    protected open fun resolvePick(dc: DrawContext) {
        val pickedObjects = dc.pickedObjects ?: return
        if (pickedObjects.count == 0) return  // no eligible objects; avoid expensive calls to glReadPixels
        val pickViewport = dc.pickViewport ?: return
        val pickPointOnly = dc.pickPoint != null && pickViewport.width <= 3 && pickViewport.height <= 3
        var objectFound = false

        dc.pickPoint?.let { pickPoint ->
            // Read the fragment color at the pick point.
            dc.readPixelColor(pickPoint.x.roundToInt(), pickPoint.y.roundToInt(), pickColor)

            // Convert the fragment color to a picked object ID. It returns zero if the color cannot indicate a picked
            // object ID, in which case no objects have been drawn at the pick point.
            val topObjectId = uniqueColorToIdentifier(pickColor)
            if (topObjectId != 0) {
                val topObject = pickedObjects.pickedObjectWithId(topObjectId)
                if (topObject != null) {
                    if (!topObject.isTerrain) objectFound = true // Non-terrain object found in pick point
                    if (pickPointOnly || objectFound) {
                        topObject.markOnTop()
                        // Remove picked objects except top and terrain in case of object found or point only mode
                        // Using clearPickedObjects and two offerPickedObject is faster than keepTopAndTerrainObjects
                        val terrainObject = pickedObjects.terrainPickedObject
                        pickedObjects.clearPickedObjects()
                        pickedObjects.offerPickedObject(topObject)
                        // handles null objects and duplicate objects
                        if (terrainObject != null) pickedObjects.offerPickedObject(terrainObject)
                    }
                } else if (pickPointOnly) pickedObjects.clearPickedObjects() // no eligible objects drawn at the pick point
            } else if (pickPointOnly) pickedObjects.clearPickedObjects() // no objects drawn at the pick point
        }

        if (!pickPointOnly && !objectFound) {
            // Read the unique fragment colors in the pick rectangle.
            dc.readPixelColors(pickViewport.x, pickViewport.y, pickViewport.width, pickViewport.height).forEach { pickColor ->
                // Convert the fragment color to a picked object ID. This returns zero if the color cannot indicate a picked
                // object ID.
                val topObjectId = uniqueColorToIdentifier(pickColor)
                if (topObjectId != 0) {
                    val topObject = pickedObjects.pickedObjectWithId(topObjectId)
                    if (topObject?.isTerrain == false) topObject.markOnTop()
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy