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