Skip to content

Commit 6e3d7b7

Browse files
TextureQuad base implementation
1 parent 9520a4b commit 6e3d7b7

File tree

4 files changed

+866
-0
lines changed

4 files changed

+866
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package earth.worldwind.draw
2+
3+
import earth.worldwind.geom.Matrix3
4+
import earth.worldwind.geom.Matrix4
5+
import earth.worldwind.geom.Vec2
6+
import earth.worldwind.geom.Vec3
7+
import earth.worldwind.render.Color
8+
import earth.worldwind.render.Texture
9+
import earth.worldwind.render.buffer.BufferObject
10+
import earth.worldwind.render.program.AbstractShaderProgram
11+
12+
open class DrawQuadState internal constructor() {
13+
companion object {
14+
const val MAX_DRAW_ELEMENTS = 5
15+
}
16+
17+
var programDrawToTexture: AbstractShaderProgram? = null
18+
var programDrawTextureToTerrain: AbstractShaderProgram? = null
19+
var vertexBuffer: BufferObject? = null
20+
var elementBuffer: BufferObject? = null
21+
val vertexOrigin = Vec3()
22+
23+
var A = Vec2()
24+
var B = Vec2()
25+
var C = Vec2()
26+
var D = Vec2()
27+
var vertexStride = 0
28+
var enableCullFace = true
29+
var enableDepthTest = true
30+
var enableLighting = false
31+
var depthOffset = 0.0
32+
val color = Color()
33+
var opacity = 1.0f
34+
var texture: Texture? = null
35+
var textureLod = 0
36+
val texCoordMatrix = Matrix3()
37+
val texCoordAttrib = VertexAttrib()
38+
internal var primCount = 0
39+
internal val prims = Array(MAX_DRAW_ELEMENTS) { DrawElements() }
40+
41+
open fun reset() {
42+
programDrawToTexture = null
43+
programDrawTextureToTerrain = null
44+
vertexBuffer = null
45+
elementBuffer = null
46+
vertexOrigin.set(0.0, 0.0, 0.0)
47+
vertexStride = 0
48+
enableCullFace = true
49+
enableDepthTest = true
50+
enableLighting = false
51+
depthOffset = 0.0
52+
color.set(1f, 1f, 1f, 1f)
53+
opacity = 1.0f
54+
texture = null
55+
textureLod = 0
56+
texCoordMatrix.setToIdentity()
57+
texCoordAttrib.size = 0
58+
texCoordAttrib.offset = 0
59+
primCount = 0
60+
A.set(0.0, 0.0)
61+
B.set(0.0, 0.0)
62+
C.set(0.0, 0.0)
63+
D.set(0.0, 0.0)
64+
65+
for (idx in 0 until MAX_DRAW_ELEMENTS) prims[idx].texture = null
66+
}
67+
68+
open fun drawElements(mode: Int, count: Int, type: Int, offset: Int) {
69+
val prim = prims[primCount++]
70+
prim.mode = mode
71+
prim.count = count
72+
prim.type = type
73+
prim.offset = offset
74+
prim.color.copy(color)
75+
prim.opacity = opacity
76+
prim.depthOffset = depthOffset
77+
prim.texture = texture
78+
prim.textureLod = textureLod
79+
prim.texCoordMatrix.copy(texCoordMatrix)
80+
prim.texCoordAttrib.copy(texCoordAttrib)
81+
prim.A.copy(A)
82+
prim.B.copy(B)
83+
prim.C.copy(C)
84+
prim.D.copy(D)
85+
}
86+
87+
internal open class DrawElements {
88+
var mode = 0
89+
var count = 0
90+
var type = 0
91+
var offset = 0
92+
val color = Color()
93+
var opacity = 1.0f
94+
var depthOffset = 0.0
95+
var texture: Texture? = null
96+
var textureLod = 0
97+
val texCoordMatrix = Matrix3()
98+
val texCoordAttrib = VertexAttrib()
99+
100+
var A = Vec2()
101+
var B = Vec2()
102+
var C = Vec2()
103+
var D = Vec2()
104+
}
105+
106+
open class VertexAttrib {
107+
var size = 0
108+
var offset = 0
109+
110+
fun copy(attrib: VertexAttrib) {
111+
size = attrib.size
112+
offset = attrib.offset
113+
}
114+
}
115+
}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package earth.worldwind.draw
2+
3+
import earth.worldwind.geom.Matrix3
4+
import earth.worldwind.geom.Matrix4
5+
import earth.worldwind.geom.Sector
6+
import earth.worldwind.geom.Vec2
7+
import earth.worldwind.globe.Globe
8+
import earth.worldwind.render.Color
9+
import earth.worldwind.render.Texture
10+
import earth.worldwind.render.program.SurfaceQuadShaderProgram
11+
import earth.worldwind.render.program.TriangleShaderProgram
12+
import earth.worldwind.util.Pool
13+
import earth.worldwind.util.kgl.*
14+
import kotlin.jvm.JvmStatic
15+
import kotlin.math.cos
16+
17+
open class DrawableSurfaceQuad protected constructor(): Drawable {
18+
var offset = Globe.Offset.Center
19+
val sector = Sector()
20+
val drawState = DrawQuadState()
21+
var version = 0
22+
var isDynamic = false
23+
private var hash = 0
24+
private var pool: Pool<DrawableSurfaceQuad>? = null
25+
private val mvpMatrix = Matrix4()
26+
private val textureMvpMatrix = Matrix4()
27+
28+
companion object {
29+
val KEY = DrawableSurfaceQuad::class
30+
private val identityMatrix3 = Matrix3()
31+
private val color = Color()
32+
private var opacity = 1.0f
33+
34+
@JvmStatic
35+
fun obtain(pool: Pool<DrawableSurfaceQuad>): DrawableSurfaceQuad {
36+
val instance = pool.acquire() ?: DrawableSurfaceQuad()
37+
instance.pool = pool
38+
return instance
39+
}
40+
}
41+
42+
override fun recycle() {
43+
drawState.reset()
44+
pool?.release(this)
45+
pool = null
46+
hash = 0
47+
}
48+
49+
override fun draw(dc: DrawContext) {
50+
// Make multi-texture unit 0 active.
51+
dc.activeTextureUnit(GL_TEXTURE0)
52+
53+
// Set up to use vertex tex coord attributes.
54+
dc.gl.enableVertexAttribArray(1 /*vertexTexCoord*/)
55+
dc.gl.enableVertexAttribArray(2 /*vertexTexCoord*/)
56+
dc.gl.enableVertexAttribArray(3 /*vertexTexCoord*/)
57+
58+
// Accumulate shapes in the draw context's scratch list.
59+
// TODO accumulate in a geospatial quadtree
60+
val scratchList = dc.scratchList
61+
try {
62+
// Add this shape.
63+
scratchList.add(this)
64+
65+
// Add all shapes that are contiguous in the drawable queue.
66+
while (true) {
67+
val next = dc.peekDrawable() ?: break
68+
// check if the drawable at the front of the queue can be batched
69+
if (next !is DrawableSurfaceQuad || next.isDynamic != isDynamic) break
70+
dc.pollDrawable() // take it off the queue
71+
scratchList.add(next)
72+
}
73+
74+
// Draw the accumulated shapes on each drawable terrain.
75+
for (idx in 0 until dc.drawableTerrainCount) {
76+
// Get the drawable terrain associated with the draw context.
77+
val terrain = dc.getDrawableTerrain(idx)
78+
// Draw the accumulated surface shapes to a texture representing the terrain's sector.
79+
drawShapesToTexture(dc, terrain)?.let { texture ->
80+
// Draw the texture containing the rasterized shapes onto the terrain geometry.
81+
drawTextureToTerrain(dc, terrain, texture)
82+
}
83+
}
84+
} finally {
85+
// Clear the accumulated shapes.
86+
scratchList.clear()
87+
// Restore the default WorldWind OpenGL state.
88+
dc.gl.disableVertexAttribArray(1 /*vertexTexCoord*/)
89+
dc.gl.disableVertexAttribArray(2 /*vertexTexCoord*/)
90+
dc.gl.disableVertexAttribArray(3 /*vertexTexCoord*/)
91+
}
92+
}
93+
94+
protected open fun textureHash(): Int {
95+
if (hash == 0) {
96+
hash = 31 * version
97+
for (i in 0 until drawState.primCount) {
98+
val prim = drawState.prims[i]
99+
hash = 31 * hash + prim.color.hashCode()
100+
hash = 31 * hash + prim.opacity.hashCode()
101+
hash = 31 * hash + prim.texture.hashCode()
102+
hash = 31 * hash + prim.textureLod
103+
}
104+
}
105+
return hash
106+
}
107+
108+
protected open fun drawShapesToTexture(dc: DrawContext, terrain: DrawableTerrain): Texture? {
109+
// The terrain's sector defines the geographic region in which to draw.
110+
val terrainSector = terrain.sector
111+
112+
// Filter shapes that intersect current terrain tile and globe offset
113+
val scratchList = mutableListOf<DrawableSurfaceQuad>()
114+
for (idx in dc.scratchList.indices) {
115+
val shape = dc.scratchList[idx] as DrawableSurfaceQuad
116+
if (shape.offset == terrain.offset && shape.sector.intersectsOrNextTo(terrainSector)) scratchList.add(shape)
117+
}
118+
if (scratchList.isEmpty()) return null // Nothing to draw
119+
120+
var hash = 0
121+
val useCache = !dc.isPickMode && !isDynamic // Use single color attachment in pick mode and for dynamic shapes
122+
if (useCache) {
123+
// Calculate a texture cache key for this terrain tile and shapes batch
124+
hash = terrainSector.hashCode()
125+
for (idx in scratchList.indices) hash = 31 * hash + scratchList[idx].textureHash()
126+
127+
// Use cached texture
128+
val cachedTexture = dc.texturesCache[hash]
129+
if (cachedTexture != null) return cachedTexture
130+
}
131+
132+
val program = drawState.programDrawToTexture as? SurfaceQuadShaderProgram ?: return null
133+
if (!program.useProgram(dc)) return null
134+
135+
// Redraw shapes to texture and put in cache if required
136+
val framebuffer = dc.scratchFramebuffer
137+
val colorAttachment = framebuffer.getAttachedTexture(GL_COLOR_ATTACHMENT0)
138+
val texture = if (!useCache) colorAttachment
139+
else Texture(colorAttachment.width, colorAttachment.height, GL_RGBA, GL_UNSIGNED_BYTE, true)
140+
try {
141+
if (!framebuffer.bindFramebuffer(dc)) return null // framebuffer failed to bind
142+
if (useCache) framebuffer.attachTexture(dc, texture, GL_COLOR_ATTACHMENT0)
143+
144+
// Clear the framebuffer and disable the depth test.
145+
dc.gl.viewport(0, 0, colorAttachment.width, colorAttachment.height)
146+
dc.gl.clear(GL_COLOR_BUFFER_BIT)
147+
dc.gl.disable(GL_DEPTH_TEST)
148+
149+
// Use the draw context's pick mode.
150+
program.enablePickMode(dc.isPickMode)
151+
152+
// Compute the tile common matrix that transforms geographic coordinates to texture fragments appropriate
153+
// for the terrain sector.
154+
// TODO capture this in a method on Matrix4
155+
textureMvpMatrix.setToIdentity()
156+
textureMvpMatrix.multiplyByTranslation(-1.0, -1.0, 0.0)
157+
textureMvpMatrix.multiplyByScale(
158+
2.0 / terrainSector.deltaLongitude.inDegrees,
159+
2.0 / terrainSector.deltaLatitude.inDegrees,
160+
0.0
161+
)
162+
textureMvpMatrix.multiplyByTranslation(
163+
-terrainSector.minLongitude.inDegrees,
164+
-terrainSector.minLatitude.inDegrees,
165+
0.0
166+
)
167+
for (idx in scratchList.indices) {
168+
val shape = scratchList[idx]
169+
if (shape.drawState.vertexBuffer?.bindBuffer(dc) != true) continue // vertex buffer unspecified or failed to bind
170+
if (shape.drawState.elementBuffer?.bindBuffer(dc) != true) continue // element buffer unspecified or failed to bind
171+
172+
// Transform local shape coordinates to texture fragments appropriate for the terrain sector.
173+
mvpMatrix.copy(textureMvpMatrix)
174+
mvpMatrix.multiplyByTranslation(
175+
shape.drawState.vertexOrigin.x,
176+
shape.drawState.vertexOrigin.y,
177+
shape.drawState.vertexOrigin.z
178+
)
179+
program.loadModelviewProjection(mvpMatrix)
180+
181+
// Use the shape's vertex point attribute.
182+
dc.gl.vertexAttribPointer(0 /*vertexPoint*/, 3, GL_FLOAT, false, shape.drawState.vertexStride, 0)
183+
184+
// Draw the specified primitives to the framebuffer texture.
185+
for (primIdx in 0 until shape.drawState.primCount) {
186+
val prim = shape.drawState.prims[primIdx]
187+
program.loadColor(prim.color)
188+
program.loadOpacity(prim.opacity)
189+
if (prim.texture?.bindTexture(dc) == true) {
190+
program.loadTexCoordMatrix(prim.texCoordMatrix)
191+
program.loadABCD(prim.A, prim.B, prim.C, prim.D)
192+
program.enableTexture(true)
193+
} else {
194+
program.enableTexture(false)
195+
// prevent "RENDER WARNING: there is no texture bound to unit 0"
196+
dc.defaultTexture.bindTexture(dc)
197+
}
198+
199+
dc.gl.drawElements(prim.mode, prim.count, prim.type, prim.offset)
200+
}
201+
}
202+
if (useCache) dc.texturesCache.put(hash, texture, 1)
203+
} finally {
204+
if (useCache) framebuffer.attachTexture(dc, colorAttachment, GL_COLOR_ATTACHMENT0)
205+
// Restore the default WorldWind OpenGL state.
206+
dc.bindFramebuffer(KglFramebuffer.NONE)
207+
dc.gl.viewport(dc.viewport.x, dc.viewport.y, dc.viewport.width, dc.viewport.height)
208+
dc.gl.enable(GL_DEPTH_TEST)
209+
dc.gl.lineWidth(1f)
210+
}
211+
return texture
212+
}
213+
214+
protected open fun drawTextureToTerrain(dc: DrawContext, terrain: DrawableTerrain, texture: Texture) {
215+
val program = drawState.programDrawTextureToTerrain as? TriangleShaderProgram ?: return
216+
if (!program.useProgram(dc)) return // program failed to build
217+
218+
try {
219+
if (!terrain.useVertexPointAttrib(dc, 0 /*vertexPoint*/)) return // terrain vertex attribute failed to bind
220+
if (!terrain.useVertexPointAttrib(dc, 1 /*vertexPoint*/)) return // terrain vertex attribute failed to bind
221+
if (!terrain.useVertexPointAttrib(dc, 2 /*vertexPoint*/)) return // terrain vertex attribute failed to bind
222+
if (!terrain.useVertexTexCoordAttrib(dc, 3 /*vertexTexCoord*/)) return // terrain vertex attribute failed to bind
223+
if (!texture.bindTexture(dc)) return // texture failed to bind
224+
225+
// Configure the program to draw texture fragments unmodified and aligned with the terrain.
226+
// TODO consolidate pickMode and enableTexture into a single textureMode
227+
// TODO it's confusing that pickMode must be disabled during surface shape render-to-texture
228+
program.enableOneVertexMode(true)
229+
program.enablePickMode(false)
230+
program.enableTexture(true)
231+
program.loadTexCoordMatrix(identityMatrix3)
232+
program.loadColor(color)
233+
program.loadOpacity(opacity)
234+
program.loadScreen(dc.viewport.width.toFloat(), dc.viewport.height.toFloat())
235+
236+
// Use the draw context's modelview projection matrix, transformed to terrain local coordinates.
237+
val terrainOrigin = terrain.vertexOrigin
238+
mvpMatrix.copy(dc.modelviewProjection)
239+
mvpMatrix.multiplyByTranslation(terrainOrigin.x, terrainOrigin.y, terrainOrigin.z)
240+
program.loadModelviewProjection(mvpMatrix)
241+
program.loadClipDistance(0.0f)
242+
243+
// Draw the terrain as triangles.
244+
terrain.drawTriangles(dc)
245+
} finally {
246+
// Unbind color attachment texture to avoid feedback loop
247+
dc.defaultTexture.bindTexture(dc)
248+
}
249+
}
250+
}

0 commit comments

Comments
 (0)