Skip to content

Commit

Permalink
Many changes to: LRUTileCache, assets, docs, graphics and a refactor
Browse files Browse the repository at this point in the history
- LRUTileCache now fetches tiles async
- Added graphics settings (not yet implemented)
- Updated docs, research papers and refactored class names
- Added bus model
- Added AtlasVehicle
- probably more I forgot
  • Loading branch information
mattyoung101 committed Aug 21, 2023
1 parent a09a314 commit 9adffe3
Show file tree
Hide file tree
Showing 20 changed files with 272 additions and 55 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,46 @@ Now, you can open the project in IntelliJ. You should also go to Settings (CTRL+
-> Build, Execution & Deployment -> Build Tools -> Gradle, and change "Build and run using" _from_ Gradle to
IntelliJ IDEA.

## Editing
Before writing code, please read `docs/guidelines.md` for some very loose code style guidelines. Please
remember to add `@author <Your Name>` to each class you write or append your name to the list of authors.

## Running
Run `Lwjgl3Launcher` in the "lwjgl3" subproject, or from the terminal use `./gradlew lwjgl3:run`. Log files
are available in `${HOME}/Documents/DECOSegfault/hermes.log`

You can make a release build with `./gradlew lwjgl3:jar`. This will write a runnable JAR file to lwjgl3/build/lib.
This JAR file can be run anywhere with a JRE, and it includes all the app's assets.

It should also be possible to use JPackager to generate bundled native binaries for Windows, Mac and Linux, but
I haven't got around to doing this yet. If it does become necessary ping @matt.

**Key bindings for SimulationScreen:**

- ESCAPE: Quit
- G: Show/hide debug info
- ]: Purge LRUTileCache

## Licence
Unfortunately (or fortunately, depending on who you ask), the University of Queensland owns all the IP to
this project, so you'll have to talk to them.

Hermes and Atlas use open-source data and 3D models, which are available in
[atlas_data_raw](https://github.com/DECO3801-Segfault-Coredump/atlas_data_raw).
The following copyright applies to them:

**Attribution**

- OpenStreetMap by OpenStreetMap contributors
- Open Data Commons Open Database License
- https://www.openstreetmap.org/copyright
- Bus model by Jotrain
- CC Attribution-NonCommercial-NoDerivs
- https://sketchfab.com/3d-models/brisbane-city-scania-l94ub-bus-rhd-8f41b49ba5344d3a8391a4c0d144d8e8
- TODO see if we can get a derivs licence to strip the interior
- EMU train model by Jotrain
- CC Attribution-NonCommercial
- https://sketchfab.com/3d-models/queensland-rail-emu-low-poly-47f3a898ef624ec59bc29b0a3f6c23c1
- Ferry model
- TODO
- TODO
Binary file added assets/atlas/bus_high.glb
Binary file not shown.
Binary file added assets/atlas/bus_low.glb
Binary file not shown.
1 change: 1 addition & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
api 'org.tinylog:tinylog-impl:2.6.2'
api "com.github.mgsx-dev.gdx-gltf:gltf:-SNAPSHOT"
api "com.github.ben-manes.caffeine:caffeine:3.1.8"
// api "com.esotericsoftware.yamlbeans:yamlbeans:1.15"

// this is just for the GL profiler
api "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.decosegfault.hermes;
package com.decosegfault.atlas;

import com.badlogic.gdx.Game;
import com.decosegfault.atlas.screens.LoadingScreen;

/** {@link com.badlogic.gdx.ApplicationListener} implementation shared by all platforms. */
public class HermesGame extends Game {
public class AtlasGame extends Game {
@Override
public void create() {
setScreen(new LoadingScreen(this));
Expand Down
56 changes: 46 additions & 10 deletions core/src/main/kotlin/com/decosegfault/atlas/map/LRUTileCache.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.math.Vector3
import com.badlogic.gdx.utils.Disposable
import com.github.benmanes.caffeine.cache.AsyncLoadingCache
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.LoadingCache
import ktx.assets.disposeSafely
import org.tinylog.kotlin.Logger
import java.util.concurrent.Executors
import kotlin.math.roundToInt

/**
Expand All @@ -18,7 +20,7 @@ import kotlin.math.roundToInt
*
* @author Matt Young
*/
class LRUTileCache {
class LRUTileCache : Disposable {
/**
* Mapping between (x, y, zoom) and the tile texture.
*
Expand All @@ -38,19 +40,47 @@ class LRUTileCache {
private val tileCache: Cache<Vector3?, Texture?> = Caffeine.newBuilder()
.maximumSize(MAX_TILES_RAM)
.removalListener { key: Vector3?, value: Texture?, cause ->
Logger.debug("Tile $key being removed because of $cause (evicted ${cause.wasEvicted()})")
val tileStr = "(${key?.x?.toInt()},${key?.y?.toInt()},${key?.z?.toInt()})"
Logger.debug("Tile $tileStr being removed by $cause (evicted ${cause.wasEvicted()})")
Gdx.app.postRunnable { value.disposeSafely() }
}
.recordStats()
.build()
// .buildAsync { pos: Vector3? ->
// if (pos == null) return@buildAsync null
// val pixmap = TileServerManager.fetchTileAsPixmap(pos)
// Gdx.app.postRunnable {
// val texture = Texture(pixmap)
// return@buildAsync texture
// }
// }
/** Executor used for HTTP requests */
private val executor = Executors.newCachedThreadPool()

/**
* Asynchronously retrieves a tile from the tile server. If the tile isn't in the cache, it will be
* downloaded asynchronously. [onRetrieved] is invoked after the tile is made available.
* @return tile texture if it was possible to load, otherwise null
*/
fun retrieve(pos: Vector3, onRetrieved: (Texture) -> Unit) {
val maybeTexture = tileCache.getIfPresent(pos)
if (maybeTexture != null) {
// tile was already in cache
onRetrieved(maybeTexture)
return
}
executor.submit {
// download the tile async on the executor thread
val pixmap = TileServerManager.fetchTileAsPixmap(pos) ?: return@submit
// now that we have the pixmap, we need to context switch into the render thread in order to
// upload the texture
Gdx.app.postRunnable {
val texture = Texture(pixmap)
pixmap.dispose()
tileCache.put(pos, texture)
onRetrieved(texture)
}
}
}

fun purge() {
Logger.debug("Purging LRUTileCache")
tileCache.cleanUp()
tileCache.invalidateAll()
tileCache.cleanUp()
}

/** @return cache hit rate stats for displaying */
fun getStats(): String {
Expand All @@ -64,4 +94,10 @@ class LRUTileCache {
/** Maximum number of tiles in VRAM, size will be approx 100 KiB * this */
private const val MAX_TILES_RAM = 1024L
}

override fun dispose() {
Logger.debug("Shutdown LRUTileCache")
purge()
executor.shutdownNow()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import java.io.IOException
import java.io.InputStream
import java.net.URL
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt

/**
* This file manages the Docker tile server
Expand All @@ -16,7 +15,7 @@ import kotlin.math.roundToInt
*/
object TileServerManager {
/** URL of the tile server */
const val TILESERVER_URL = "http://localhost:8080/tile/{z}/{x}/{y}.png"
private const val TILESERVER_URL = "http://localhost:8080/tile/{z}/{x}/{y}.png"

/** Command to start the OSM docker container */
private val DOCKER_START_CMD = "docker run -p 8080:80 -p 5432:5432 -e THREADS=16 -v osm-data:/data/database -v osm-tiles:/data/tiles -d overv/openstreetmap-tile-server run".split(" ")
Expand Down Expand Up @@ -68,7 +67,7 @@ object TileServerManager {
* null.
* @param pos tile pos (x, y, zoom)
*/
fun fetchTile(pos: Vector3): ByteArray? {
private fun fetchTile(pos: Vector3): ByteArray? {
val url = URL(
TILESERVER_URL
.replace("{x}", pos.x.toInt().toString())
Expand Down
46 changes: 46 additions & 0 deletions core/src/main/kotlin/com/decosegfault/atlas/render/AtlasVehicle.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.decosegfault.atlas.render

import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.math.Frustum
import com.badlogic.gdx.math.Intersector
import com.badlogic.gdx.math.Matrix4
import com.badlogic.gdx.math.Vector3
import com.badlogic.gdx.math.collision.BoundingBox
import net.mgsx.gltf.scene3d.scene.Scene

/**
* Atlas's representation of a vehicle, includes gdx-gltf high detail and low detail models and bounding bo.
*
* @author Matt Young
*
* @param sceneHigh high poly 3D model
* @param sceneLow low poly 3D model
*/
data class AtlasVehicle(val sceneHigh: Scene, val sceneLow: Scene,) {
private val transform = Matrix4()
private val bbox = BoundingBox()

/**
* @param trans new transform: x, y, theta (rad)
*/
fun updateTransform(trans: Vector3) {
// update shared transformation for the model
transform.setToTranslation(trans.x, 0.0f, trans.z) // TODO check axes
transform.setToRotation(Vector3.Z, 0.0f) // TODO check axes

// translate models
sceneHigh.modelInstance.transform.set(transform)
sceneLow.modelInstance.transform.set(transform)

// just calculate bounding box for high and assume it's the same as low for performance reasons
sceneHigh.modelInstance.calculateBoundingBox(bbox)
}

fun intersectFrustum(frustum: Frustum): Boolean {
return Intersector.intersectFrustumBounds(frustum, bbox)
}

fun distanceToCam(camera: Camera): Float {
return camera.position.dst(transform.getTranslation(Vector3()))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.decosegfault.atlas.render

/**
* Graphics preset for the render. **All distances are in metres.**
*
* @author Matt Young
*
* @param vehicleDrawDist Above this distance, vehicles are not rendered
* @param vehicleLodDist Above this distance, low poly LoDs are used; below, high poly LoDs are used
* @param tileDrawDist Above this distance, map tiles are not rendered
*/
data class GraphicsPreset(
val name: String,
val description: String,

val vehicleDrawDist: Float,
val vehicleLodDist: Float,
val tileDrawDist: Float
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.decosegfault.atlas.render

/**
* Graphics presets for the renderer
*
* @author Matt Young
*/
class GraphicsPresets {
// presets will be:
// - Genuine Potato (Worst possible settings, honestly just terrible)
// - Standard (Balanced settings for most users)
// - It Runs Crysis (Highest settings not expected to tank the FPS)
// - NASA Supercomputer (Everything maxed out)

private val genuinePotato = GraphicsPreset(
name="Genuine Potato",
description="Worst possible settings for atrocious computers",
vehicleDrawDist=10.0f,
vehicleLodDist=Float.MAX_VALUE, // always draw low LoDs
tileDrawDist=25.0f,
)

private val standard = GraphicsPreset(
name="Standard",
description="Balanced settings for good framerate on most computers",
vehicleDrawDist=100.0f,
vehicleLodDist=50.0f,
tileDrawDist=100.0f,
)

private val itRunsCrysis = GraphicsPreset(
name="It Runs Crysis",
description="High settings for powerful gaming PCs or workstations",
vehicleDrawDist=200.0f,
vehicleLodDist=100.0f,
tileDrawDist=200.0f,
)

private val nasaSupercomputer = GraphicsPreset(
name="NASA Supercomputer",
description="Max possible settings for ridiculously overpowered PCs to flex",
vehicleDrawDist=Float.MAX_VALUE, // always draw every vehicle
vehicleLodDist=Float.MIN_VALUE, // always draw high LoDs
tileDrawDist=Float.MAX_VALUE, // always draw every tile
)

private val presets = listOf(genuinePotato, standard, itRunsCrysis, nasaSupercomputer)

fun forName(name: String): GraphicsPreset {
return presets.first { it.name == name }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,21 @@ import org.tinylog.kotlin.Logger
import kotlin.concurrent.thread
import kotlin.math.roundToInt

private enum class LoadingStage(val text: String) {
STARTING_TILESERVER("Starting tile server..."),
CHECKING_CONNECTIVITY("Checking tile server connectivity..."),
LOADING_3D_ASSETS("Loading 3D assets..."),
/**
* Loading screen for the application. This class is based on my (Matt)'s previous work:
* https://github.com/UQRacing/gazilla/blob/master/core/src/main/kotlin/com/uqracing/gazilla/client/screens/LoadingScreen.kt
*
* @author Matt Young
*/
class LoadingScreen(private val game: Game) : ScreenAdapter() {
private enum class LoadingStage(val text: String) {
STARTING_TILESERVER("Starting tile server..."),
CHECKING_CONNECTIVITY("Checking tile server connectivity..."),
LOADING_3D_ASSETS("Loading 3D assets..."),

DONE("Done.")
}
DONE("Done.")
}

// This class is based on my (Matt)'s previous work:
// https://github.com/UQRacing/gazilla/blob/master/core/src/main/kotlin/com/uqracing/gazilla/client/screens/LoadingScreen.kt
class LoadingScreen(private val game: Game) : ScreenAdapter() {
private lateinit var skin: Skin
private lateinit var stage: Stage
private lateinit var label: Label
Expand Down Expand Up @@ -101,7 +105,7 @@ class LoadingScreen(private val game: Game) : ScreenAdapter() {
label.setText("Loading 3D assets... ($completion%)")
}
} else if (currentStage == LoadingStage.DONE) {
game.screen = AtlasScreen(game)
game.screen = SimulationScreen(game)
}

// if (ASSETS.update()) {
Expand All @@ -123,6 +127,10 @@ class LoadingScreen(private val game: Game) : ScreenAdapter() {
stage.viewport.update(width, height, true)
}

override fun hide() {
dispose()
}

override fun dispose() {
skin.dispose()
stage.dispose()
Expand Down
Loading

0 comments on commit 9adffe3

Please sign in to comment.