
commonMain.earth.worldwind.draw.DrawContext.kt Maven / Gradle / Ivy
package earth.worldwind.draw
import earth.worldwind.PickedObjectList
import earth.worldwind.geom.Matrix4
import earth.worldwind.geom.Vec2
import earth.worldwind.geom.Vec3
import earth.worldwind.geom.Viewport
import earth.worldwind.render.Color
import earth.worldwind.render.Framebuffer
import earth.worldwind.render.Texture
import earth.worldwind.render.buffer.BufferPool
import earth.worldwind.render.buffer.FloatBufferObject
import earth.worldwind.render.buffer.IntBufferObject
import earth.worldwind.util.kgl.*
open class DrawContext(val gl: Kgl) {
val eyePoint = Vec3()
val viewport = Viewport()
val projection = Matrix4()
val modelview = Matrix4()
val modelviewProjection = Matrix4()
// val infiniteProjection = Matrix4()
val screenProjection = Matrix4()
var drawableQueue: DrawableQueue? = null
var drawableTerrain: DrawableQueue? = null
var pickedObjects: PickedObjectList? = null
var pickViewport: Viewport? = null
var pickPoint: Vec2? = null
var isPickMode = false
private var framebuffer = KglFramebuffer.NONE
private var program = KglProgram.NONE
private var textureUnit = GL_TEXTURE0
private val textures = Array(32) { KglTexture.NONE }
private var arrayBuffer = KglBuffer.NONE
private var elementArrayBuffer = KglBuffer.NONE
private var scratchFramebufferCache: Framebuffer? = null
private var unitSquareBufferCache: FloatBufferObject? = null
private var rectangleElementsBufferCache: IntBufferObject? = null
private var scratchBuffer = ByteArray(4)
private val pixelArray = ByteArray(4)
private var bufferPool = BufferPool(GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW)
/**
* Returns count of terrain drawables in queue
*/
val drawableTerrainCount get() = drawableTerrain?.count?:0
/**
* Returns the name of the OpenGL framebuffer object that is currently active.
*/
val currentFramebuffer get() = framebuffer
/**
* Returns the name of the OpenGL program object that is currently active.
*/
val currentProgram get() = program
/**
* Returns the OpenGL multitexture unit that is currently active. Returns a value from the GL_TEXTUREi enumeration,
* where i ranges from 0 to 32.
*/
val currentTextureUnit get() = textureUnit
/**
* Returns the name of the OpenGL texture 2D object currently bound to the active multitexture unit. The active
* multitexture unit may be determined by calling currentTextureUnit.
*/
val currentTexture get() = currentTexture(textureUnit)
/**
* Returns an OpenGL framebuffer object suitable for offscreen drawing. The framebuffer has a 32-bit color buffer
* and a 32-bit depth buffer, both attached as OpenGL texture 2D objects.
*
* The framebuffer may be used by any drawable and for any purpose. However, the draw context makes no guarantees
* about the framebuffer's contents. Drawables must clear the framebuffer before use, and must assume its contents
* may be modified by another drawable, either during the current frame or in a subsequent frame.
*
* The OpenGL framebuffer object is created on first use and cached. Subsequent calls to this method return the
* cached buffer object.
*/
val scratchFramebuffer get() = scratchFramebufferCache ?: Framebuffer().apply {
val colorAttachment = Texture(1024, 1024, GL_RGBA, GL_UNSIGNED_BYTE, true)
val depthAttachment = Texture(1024, 1024, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, true)
// TODO consider modifying Texture's tex parameter behavior in order to make this unnecessary
depthAttachment.setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST)
depthAttachment.setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST)
attachTexture(this@DrawContext, colorAttachment, GL_COLOR_ATTACHMENT0)
attachTexture(this@DrawContext, depthAttachment, GL_DEPTH_ATTACHMENT)
}.also { scratchFramebufferCache = it }
/**
* Returns an OpenGL buffer object containing a unit square expressed as four vertices at (0, 1), (0, 0), (1, 1) and
* (1, 0). Each vertex is stored as two 32-bit floating point coordinates. The four vertices are in the order
* required by a triangle strip.
*
* The OpenGL buffer object is created on first use and cached. Subsequent calls to this method return the cached
* buffer object.
*/
val unitSquareBuffer get() = unitSquareBufferCache ?: FloatBufferObject(
GL_ARRAY_BUFFER, floatArrayOf(0f, 1f, 0f, 0f, 1f, 1f, 1f, 0f)
).also { unitSquareBufferCache = it }
/**
* Returns an OpenGL buffer object containing indices needed to render triangle
* Expected vertex data layout for this buffer is something like this
* 1 ---- 0
* | /|
* | / |
* | / |
* | / |
* | / |
* 3 ---- 2
*
* The OpenGL buffer object is created on first use and cached. Subsequent calls to this method return the cached
* buffer object.
*/
val rectangleElementsBuffer get() = rectangleElementsBufferCache ?: IntBufferObject(
GL_ELEMENT_ARRAY_BUFFER, intArrayOf(0, 1, 2, 2, 1, 3)
).also { rectangleElementsBufferCache = it }
/**
* Returns a scratch list suitable for accumulating entries during drawing. The list is cleared before each frame,
* otherwise its contents are undefined.
*/
val scratchList = mutableListOf()
fun reset() {
eyePoint.set(0.0, 0.0, 0.0)
viewport.setEmpty()
projection.setToIdentity()
modelview.setToIdentity()
modelviewProjection.setToIdentity()
screenProjection.setToIdentity()
// infiniteProjection.setToIdentity()
drawableQueue = null
drawableTerrain = null
pickedObjects = null
pickViewport = null
pickPoint = null
isPickMode = false
scratchBuffer.fill(0)
scratchList.clear()
bufferPool.reset()
}
fun contextLost() {
// Clear objects and values associated with the current OpenGL context.
framebuffer = KglFramebuffer.NONE
program = KglProgram.NONE
textureUnit = GL_TEXTURE0
arrayBuffer = KglBuffer.NONE
elementArrayBuffer = KglBuffer.NONE
scratchFramebufferCache = null
unitSquareBufferCache = null
rectangleElementsBufferCache = null
textures.fill(KglTexture.NONE)
bufferPool.contextLost()
}
fun peekDrawable() = drawableQueue?.peekDrawable()
fun pollDrawable() = drawableQueue?.pollDrawable()
fun rewindDrawables() { drawableQueue?.rewindDrawables() }
fun getDrawableTerrain(index: Int) = drawableTerrain?.getDrawable(index) as DrawableTerrain? ?: error("Invalid index")
/**
* Makes an OpenGL framebuffer object active. The active framebuffer becomes the target of all OpenGL commands that
* render to the framebuffer or read from the framebuffer. This has no effect if the specified framebuffer object is
* already active. The default is framebuffer 0, indicating that the default framebuffer provided by the windowing
* system is active.
*
* @param framebuffer the name of the OpenGL framebuffer object to make active, or 0 to make the default
* framebuffer provided by the windowing system active
*/
fun bindFramebuffer(framebuffer: KglFramebuffer) {
if (this.framebuffer != framebuffer) {
this.framebuffer = framebuffer
gl.bindFramebuffer(GL_FRAMEBUFFER, framebuffer)
}
}
/**
* Makes an OpenGL program object active as part of current rendering state. This has no effect if the specified
* program object is already active. The default is program 0, indicating that no program is active.
*
* @param program the name of the OpenGL program object to make active, or 0 to make no program active
*/
fun useProgram(program: KglProgram) {
if (this.program != program) {
this.program = program
gl.useProgram(program)
}
}
/**
* Specifies the OpenGL multitexture unit to make active. This has no effect if the specified multitexture unit is
* already active. The default is GL_TEXTURE0.
*
* @param textureUnit the multitexture unit, one of GL_TEXTUREi, where i ranges from 0 to 32.
*/
fun activeTextureUnit(textureUnit: Int) {
if (this.textureUnit != textureUnit) {
this.textureUnit = textureUnit
gl.activeTexture(textureUnit)
}
}
/**
* Returns the name of the OpenGL texture 2D object currently bound to the specified multitexture unit.
*
* @param textureUnit the multitexture unit, one of GL_TEXTUREi, where i ranges from 0 to 32.
*
* @return the currently bound texture 2D object, or 0 if no texture object is bound
*/
fun currentTexture(textureUnit: Int) = textures[textureUnit - GL_TEXTURE0]
/**
* Makes an OpenGL texture 2D object bound to the current multitexture unit. This has no effect if the specified
* texture object is already bound. The default is texture 0, indicating that no texture is bound.
*
* @param texture the name of the OpenGL texture 2D object to make active, or 0 to make no texture active
*/
fun bindTexture(texture: KglTexture) {
val textureUnitIndex = textureUnit - GL_TEXTURE0
if (textures[textureUnitIndex] != texture) {
textures[textureUnitIndex] = texture
gl.bindTexture(GL_TEXTURE_2D, texture)
}
}
/**
* Returns the name of the OpenGL buffer object bound to the specified target buffer.
*
* @param target the target buffer, either GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER
*
* @return the currently bound buffer object, or 0 if no buffer object is bound
*/
fun currentBuffer(target: Int): KglBuffer {
return when (target) {
GL_ARRAY_BUFFER -> arrayBuffer
GL_ELEMENT_ARRAY_BUFFER -> elementArrayBuffer
else -> KglBuffer.NONE
}
}
/**
* Makes an OpenGL buffer object bound to a specified target buffer. This has no effect if the specified buffer
* object is already bound. The default is buffer 0, indicating that no buffer object is bound.
*
* @param target the target buffer, either GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER
* @param buffer the name of the OpenGL buffer object to make active
*/
fun bindBuffer(target: Int, buffer: KglBuffer) {
if (target == GL_ARRAY_BUFFER && arrayBuffer != buffer) {
arrayBuffer = buffer
gl.bindBuffer(target, buffer)
} else if (target == GL_ELEMENT_ARRAY_BUFFER && elementArrayBuffer != buffer) {
elementArrayBuffer = buffer
gl.bindBuffer(target, buffer)
} else {
gl.bindBuffer(target, buffer)
}
}
/**
* Puts dynamic vertex data into the buffer pool and returns offset.
*
* @param vertexData dynamic vertex data to be added in the buffer pool
* @return offset in the buffer pool
*/
fun bindBufferPool(vertexData: FloatArray) = bufferPool.bindBuffer(this, vertexData)
/**
* Reads the fragment color at a screen point in the currently active OpenGL frame buffer. The X and Y components
* indicate OpenGL screen coordinates, which originate in the frame buffer's lower left corner.
*
* @param x the screen point's X component
* @param y the screen point's Y component
* @param result an optional pre-allocated Color in which to return the fragment color, or null to return a new
* color
*
* @return the result argument set to the fragment color, or a new color if the result is null
*/
fun readPixelColor(x: Int, y: Int, result: Color): Color {
// Read the fragment pixel as an RGBA 8888 color.
gl.readPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixelArray)
// Convert the RGBA 8888 color to a WorldWind color.
result.red = (pixelArray[0].toInt() and 0xFF) / 0xFF.toFloat()
result.green = (pixelArray[1].toInt() and 0xFF) / 0xFF.toFloat()
result.blue = (pixelArray[2].toInt() and 0xFF) / 0xFF.toFloat()
result.alpha = (pixelArray[3].toInt() and 0xFF) / 0xFF.toFloat()
return result
}
/**
* Reads the unique fragment colors within a screen rectangle in the currently active OpenGL frame buffer. The
* components indicate OpenGL screen coordinates, which originate in the frame buffer's lower left corner.
*
* @param x the screen rectangle's X component
* @param y the screen rectangle's Y component
* @param width the screen rectangle's width
* @param height the screen rectangle's height
*
* @return a set containing the unique fragment colors
*/
fun readPixelColors(x: Int, y: Int, width: Int, height: Int): Set {
// Read the fragment pixels as a tightly packed array of RGBA 8888 colors.
val pixelCount = width * height
val pixelBuffer = scratchBuffer(pixelCount * 4)
val packAlignment = gl.getParameteri(GL_PACK_ALIGNMENT)
gl.pixelStorei(GL_PACK_ALIGNMENT, 1) // read byte aligned
gl.readPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixelBuffer)
gl.pixelStorei(GL_PACK_ALIGNMENT, packAlignment) // restore the pack alignment
val resultSet = mutableSetOf()
var result = Color()
for (i in 0 until pixelCount) {
val idx = i * 4
// Convert the RGBA 8888 color to a WorldWind color.
result.red = (pixelBuffer[idx + 0].toInt() and 0xFF) / 0xFF.toFloat()
result.green = (pixelBuffer[idx + 1].toInt() and 0xFF) / 0xFF.toFloat()
result.blue = (pixelBuffer[idx + 2].toInt() and 0xFF) / 0xFF.toFloat()
result.alpha = (pixelBuffer[idx + 3].toInt() and 0xFF) / 0xFF.toFloat()
// Accumulate the unique colors in a set.
if (resultSet.add(result)) result = Color()
}
return resultSet
}
/**
* Returns a scratch NIO buffer suitable for use during drawing. The returned buffer has capacity at least equal to
* the specified capacity. The buffer is cleared before each frame, otherwise its contents, position, limit and mark
* are undefined.
*
* @param capacity the buffer's minimum capacity in bytes
*
* @return the draw context's scratch buffer
*/
fun scratchBuffer(capacity: Int): ByteArray {
if (scratchBuffer.size < capacity) scratchBuffer = ByteArray(capacity)
return scratchBuffer
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy