Skip to content

Commit

Permalink
Initial code and documentation for building generation
Browse files Browse the repository at this point in the history
  • Loading branch information
mattyoung101 committed Sep 12, 2023
1 parent 5a9d025 commit e9e5b7e
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.badlogic.gdx.math.collision.Ray;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.decosegfault.atlas.map.BuildingManager;
import net.mgsx.gltf.scene3d.attributes.PBRMatrixAttribute;
import net.mgsx.gltf.scene3d.lights.DirectionalShadowLight;
import net.mgsx.gltf.scene3d.lights.PointLightEx;
Expand Down Expand Up @@ -88,6 +89,9 @@ public class AtlasSceneManager implements Disposable {
/** Controller to render ground plane tiles */
private AtlasTileManager atlasTileManager;

/** Controller used to render buildings */
private BuildingManager buildingManager;

public AtlasSceneManager(GraphicsPreset graphics) {
this(24);
this.graphics = graphics;
Expand Down Expand Up @@ -238,6 +242,12 @@ public void update(float delta, List<AtlasVehicle> vehicles) {
if (decal != null) tileDecals.add(decal);
}

// Submit building chunks for rendering
var buildingChunks = buildingManager.getBuildingChunksCulled(camera, graphics);
for (ModelCache chunk : buildingChunks) {
renderableProviders.add(chunk);
}

if (camera != null) {
updateEnvironment();
if (skyBox != null) skyBox.update(camera, delta);
Expand Down Expand Up @@ -496,6 +506,14 @@ public void setAtlasTileManager(AtlasTileManager atlasTileManager) {
this.atlasTileManager = atlasTileManager;
}

/**
* Sets the building manager
* @param buildingManager New building manager instance
*/
public void setBuildingManager(BuildingManager buildingManager) {
this.buildingManager = buildingManager;
}

public void setAmbientLight(float lum) {
environment.get(ColorAttribute.class, ColorAttribute.AmbientLight).color.set(lum, lum, lum, 1);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,43 @@
package com.decosegfault.atlas.map

import com.badlogic.gdx.graphics.g3d.ModelCache
import com.badlogic.gdx.math.Vector3
import com.google.common.util.concurrent.ThreadFactoryBuilder
import org.tinylog.kotlin.Logger
import java.sql.Connection
import java.sql.DriverManager
import java.util.Properties
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit

/**
* Class that handles the asynchronous generation and texturing of building meshes
* Class that handles the asynchronous generation of building models from OpenStreetMap PostGIS data.
* For more info see docs/atlas_buildings_design.md.
*
* @author Matt Young
*/
object BuildingGenerator {
/** Limit thread pool size to 2x the number of processors to prevent memory issues */
private val threadPoolSize = Runtime.getRuntime().availableProcessors() / 2

/** Queue used to manage tasks the executor should run when it's full (overflow) */
private val executorQueue = LinkedBlockingQueue<Runnable>()

/**
* Executor used for generating buildings.
* This is the exact same as `Executors.newFixedThreadPool`, but we control the queue.
*/
private val executor = ThreadPoolExecutor(
threadPoolSize, threadPoolSize,
0L, TimeUnit.MILLISECONDS,
executorQueue,
ThreadFactoryBuilder().setNameFormat("BuildingGen-%d").build()
)
private lateinit var conn: Connection

/** Connects to the PostGIS database */
fun connect() {
Logger.info("Connecting to PostGIS database")
val props = Properties()
props["user"] = "renderer"
props["password"] = "renderer"

conn = DriverManager.getConnection("jdbc:postgresql://localhost:5432/gis", props)
Logger.info("Connected to PostGIS successfully! ${conn.metaData.databaseProductName} ${conn.metaData.databaseProductVersion}")
}

/**
* Generates a building chunk. Buildings are packaged together into a ModelCache.
*/
fun generateBuildingChunk(chunk: Vector3): ModelCache {
val cache = ModelCache()
cache.begin()
cache.end()
return cache
}
}
17 changes: 17 additions & 0 deletions core/src/main/kotlin/com/decosegfault/atlas/map/BuildingManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.decosegfault.atlas.map

import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.g3d.ModelCache
import com.decosegfault.atlas.render.GraphicsPreset

/**
* This class manages fetching and rendering buildings. Basically the building equivalent of
* AtlasTileManager. For more info see docs/atlas_buildings_design.md
*
* @author Matt Young
*/
class BuildingManager {
fun getBuildingChunksCulled(cam: Camera, graphics: GraphicsPreset): List<ModelCache> {
return listOf()
}
}
31 changes: 31 additions & 0 deletions core/src/main/kotlin/com/decosegfault/atlas/map/GCBuildingCache.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.decosegfault.atlas.map

import com.badlogic.gdx.graphics.g3d.ModelCache
import com.badlogic.gdx.math.Vector3
import com.decosegfault.atlas.util.AbstractGarbageCollectedCache

/** **Soft** limit of buildings */
private const val MAX_BUILDINGS_RAM = 2048.0

/** If the cache is this% full, start trying to evict items */
private const val START_GC_THRESHOLD = 0.90

/** If the cache is below this% full, stop evicting */
private const val END_GC_THRESHOLD = 0.50

/**
* Implementation of [AbstractGarbageCollectedCache] used to store buildings
*
* @author Matt Young
*/
object GCBuildingCache : AbstractGarbageCollectedCache<Vector3, ModelCache>(
"GCBuildingCache",
MAX_BUILDINGS_RAM,
START_GC_THRESHOLD,
END_GC_THRESHOLD,
Runtime.getRuntime().availableProcessors()
) {
override fun newItem(key: Vector3): ModelCache {
return BuildingGenerator.generateBuildingChunk(key)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.Skin
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.viewport.ExtendViewport
import com.badlogic.gdx.utils.viewport.FitViewport
import com.decosegfault.atlas.map.BuildingManager
import com.decosegfault.atlas.map.GCBuildingCache
import com.decosegfault.atlas.map.GCTileCache
import com.decosegfault.atlas.render.*
import com.decosegfault.atlas.util.Assets
Expand Down Expand Up @@ -106,6 +108,8 @@ class SimulationScreen(private val game: Game) : ScreenAdapter() {

private val atlasTileManager = AtlasTileManager()

private val buildingManager = BuildingManager()

private fun createTextUI() {
val skin = ASSETS["ui/uiskin.json", Skin::class.java]

Expand Down Expand Up @@ -185,6 +189,7 @@ class SimulationScreen(private val game: Game) : ScreenAdapter() {

// setup ground plane tile manager
sceneManager.setAtlasTileManager(atlasTileManager)
sceneManager.setBuildingManager(buildingManager)

// setup decal batch for rendering
sceneManager.decalBatch = DecalBatch(CameraGroupStrategy(cam))
Expand Down Expand Up @@ -318,6 +323,7 @@ class SimulationScreen(private val game: Game) : ScreenAdapter() {
sceneManager.update(delta, vehicles)
sceneManager.render()
GCTileCache.nextFrame()
GCBuildingCache.nextFrame()

// render debug UI
if (isDebugDraw) {
Expand All @@ -326,6 +332,7 @@ class SimulationScreen(private val game: Game) : ScreenAdapter() {
debugLabel.setText(
"""FPS: ${Gdx.graphics.framesPerSecond} (${deltaMs} ms) Draw calls: ${profiler.drawCalls} Memory: $mem MB
|${GCTileCache.getStats()}
|${GCBuildingCache.getStats()}
|Vehicles culled: ${sceneManager.cullRate}% low LoD: ${sceneManager.lowLodRate}% full: ${sceneManager.fullRenderRate}% total: ${sceneManager.totalVehicles}
|Tiles on screen: ${atlasTileManager.numRetrievedTiles}
|Texture work queue done: $workIdx left: ${TEX_WORK_QUEUE.size}
Expand Down
48 changes: 48 additions & 0 deletions docs/atlas_buildings_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,51 @@
## Introduction
This document describes the pipeline that turns OpenStreetMap data into 3D buildings for Atlas.

## Implementation
### Overview
Like the Atlas tile renderer, the building generator needs to be highly asynchronous. Because buildings
don't currently have textures (or if they do, share basically the same textures), we should be able to get
away with _never_ blocking the render thread.

The implementation faces two significant obstacles:
1. We would like to generate model caches for entire chunks, not just each building
2. If a building overlaps multiple chunks, it's ambiguous which chunk gets to process it. We would get duplicate buildings.

To fix issue 2, I plan to introduce a "first in, best dressed" scheme for building to chunk allocation. As
explained later, each chunk gets its own thread, and whichever thread "bags" a building first (using a
concurrent data structure to prevent race conditions), will get to process that building in that chunk for
as long as the chunk is kept in the chunk cache.

First, we will divide the map into fixed-size chunks using something like Henry's code. For example, we'll
divide the map into chunks at something like zoom level 16. So they'll be medium-sized.

In `AtlasSceneManager`, we call `BuildingManager#getBuildingsCulled` which will determine what chunks are
visible. We then call `GCBuildingChunkCache#retrieve`. If the building chunk is in the cache, we get a
`ModelCache` back which we can draw.

If the building chunk is not in the cache, the `GCBuildingChunkCache` will call `BuildingGenerator#generateBuildingChunk`.
Like `GCTileCache`, `GCBuildingChunkCache` is multi-threaded and each building chunk will be submitted to a
new thread on the executor. There will be `$nproc` threads.

### Constructing building geometry
The BuildingGenerator's main job is to query the PostGIS database, extrude the building geometry and assign
textures if possible. Let's break it down.

1. `generateBuildingChunk` calls `getBuildingsIn(rect: Rectangle)`
2. We convert Atlas coords to lat/long
3. We submit a query to the PostGIS database and ask for all buildings in this rectangle
- At this point, we are in an executor belonging to `GCBuildingChunkCache`, so we are allowed to block
4. For each building:
- We convert the building geometry back to Atlas coords
- We compute the Delaunay triangulation of the building polygon
- We extrude the vertices of each triangle based on the building height if present; otherwise a default height
- We convert vertices into a model
- (Optional) We assign textures to the model
5. We return the building models individually back to `generateBuildingChunk`
6. Building models are packaged back into a `ModelCache`
7. The `ModelCache` is sent back to the `GCBuildingChunkCache`, which is in turn sent to `BuildingManager`,
which is in turn drawn to the screen

## Notes
We will use the same Docker container for rendering, by connecting to the PostgresSQL database.

Expand All @@ -14,3 +59,6 @@ Then we can use this Java library: https://github.com/sebasbaumh/postgis-java-ng
We may also be able to use this: https://osm2pgsql.org/examples/3dbuildings/

Otherwise, we should port OSMBuilding: https://github.com/Beakerboy/OSMBuilding/

### Extrusion links
TODO
4 changes: 4 additions & 0 deletions docs/atlas_tile_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ will attempt to automatically start the tile server. It will then verify the til
If the tile server crashes, Atlas should throw an error dialogue and probably quit the game.

## Constructing the grid
**OUTDATED**

- Imagine the ground is an infinite plane
- We can determine the area of this plane that is visible on screen
- Using the camera frustum, we can cast rays through the four corners and see where they intersect
Expand All @@ -21,6 +23,8 @@ but we can fix this by still doing distance thresholding
- Either truncate the rectangle or cap ray length

## Fetching and caching tiles
**PARTIALLY OUTDATED**

Once the grid is constructed, Atlas will fetch tiles from the tile server using HTTP. To avoid blocking the
main thread, the HTTP fetch is done async using `Gdx.net`.

Expand Down

0 comments on commit e9e5b7e

Please sign in to comment.