@@ -5,6 +5,8 @@ buildscript { | |||
ext.coroutines_version = '1.4.2' | |||
ext.serialization_version = '0.20.0' | |||
ext.kds_version = '2.0.6' | |||
repositories { | |||
google() | |||
jcenter() | |||
@@ -28,6 +30,7 @@ allprojects { | |||
google() | |||
jcenter() | |||
maven { url 'https://jitpack.io' } | |||
maven { url 'https://dl.bintray.com/korlibs/korlibs' } | |||
} | |||
} | |||
@@ -19,7 +19,7 @@ kotlin { | |||
dependencies { | |||
implementation kotlin('stdlib-common') | |||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" | |||
implementation 'com.github.romainguy:kotlin-math:02ca2b7f0c' | |||
implementation "com.soywiz.korlibs.kds:kds:$kds_version" | |||
} | |||
} | |||
@@ -33,7 +33,7 @@ kotlin { | |||
jvmMain { | |||
dependencies { | |||
implementation kotlin('stdlib-jdk8') | |||
implementation 'org.jogamp.jogl:jogl-all:2.3.2' | |||
implementation "com.soywiz.korlibs.kds:kds-jvm:$kds_version" | |||
} | |||
} | |||
@@ -19,7 +19,7 @@ class Config { | |||
var ambientLight: Int = -0x1000000 | |||
var shaders: List<Shader> = ArrayList() | |||
var fullscreen: Boolean = true | |||
var fps: Int = 15 | |||
var fps: Int = 30 | |||
var currentWidth: Int = 0 | |||
var currentHeight: Int = 0 | |||
@@ -6,7 +6,7 @@ import me.jfenn.dungeon.api.timing.currentTimeMillis | |||
import me.jfenn.dungeon.core.extensions.multiply | |||
import me.jfenn.dungeon.core.graphics.Luminance | |||
import me.jfenn.dungeon.core.graphics.pixel.Pixel | |||
import me.jfenn.dungeon.core.spatial.SpatialMap | |||
import me.jfenn.dungeon.core.spatial.* | |||
class GraphicsManager( | |||
val env: Config | |||
@@ -14,7 +14,7 @@ class GraphicsManager( | |||
val sprites = ArrayList<Sprite>() | |||
val assembledPixels = SpatialMap<Pixel>() | |||
val assembledPixels = SpatialMapChunked<Pixel>() | |||
val assembledLights = SpatialMap<Luminance>() | |||
var renderedPixels = SpatialMap<Int>() | |||
@@ -29,7 +29,7 @@ class GraphicsManager( | |||
sprites.remove(sprite) | |||
} | |||
suspend fun render() : SpatialMap<Int> { | |||
suspend fun render() : SpatialCollection<Int> { | |||
assembledPixels.clear() | |||
assembledLights.clear() | |||
@@ -5,29 +5,29 @@ import me.jfenn.dungeon.core.extensions.alpha | |||
import me.jfenn.dungeon.core.extensions.set | |||
import me.jfenn.dungeon.core.graphics.Luminance | |||
import me.jfenn.dungeon.core.graphics.pixel.Pixel | |||
import me.jfenn.dungeon.core.spatial.SpatialMap | |||
import me.jfenn.dungeon.core.spatial.SpatialCollection | |||
import me.jfenn.dungeon.core.spatial.SpatialMapFast | |||
import me.jfenn.dungeon.core.vector.IntVector | |||
import me.jfenn.dungeon.core.vector.area.forLine | |||
import me.jfenn.dungeon.core.vector.area.LineIterator | |||
class ExhaustiveShader(env: Config) : Shader(env) { | |||
override suspend fun drawLightMap(position: IntVector, light: Luminance, pixels: SpatialMap<Pixel>) { | |||
val visited = SpatialMap<Boolean>() | |||
override suspend fun drawLightMap(position: IntVector, light: Luminance, pixels: SpatialCollection<Pixel>) { | |||
val visited = SpatialMapFast<Boolean>() | |||
for (pixelPos in pixels.keys()) { | |||
if (pixelPos.z != position.z || position.distanceTo(pixelPos) > light.getAffectedRadius() || visited[pixelPos] == true) | |||
continue | |||
pixels.forEachRadius(position, light.getAffectedRadius()) { pixelPos, _ -> | |||
if (visited[pixelPos] == true) | |||
return@forEachRadius | |||
var intensity = 1f // start at light source center | |||
// trace line from light source to pixel | |||
forLine(position, pixelPos) { vector -> | |||
val pixel = pixels[vector] ?: return@forLine | |||
//if (intensity < 0.05f) break | |||
for (vector in LineIterator(position, pixelPos)) { | |||
val pixel = pixels[vector] ?: continue | |||
if (intensity < 0.1f) break | |||
// calculate pixel color & insert into list | |||
if (intensity > 0.01f && visited[vector] != true) { | |||
if (visited[vector] != true) { | |||
val color = light.getColor(position.distanceTo(vector).toFloat()) | |||
addLight(vector, color.set(alpha = color.alpha() * intensity)) | |||
visited[vector] = true | |||
@@ -4,18 +4,18 @@ import kotlinx.coroutines.* | |||
import me.jfenn.dungeon.api.env.Config | |||
import me.jfenn.dungeon.core.graphics.Luminance | |||
import me.jfenn.dungeon.core.graphics.pixel.Pixel | |||
import me.jfenn.dungeon.core.spatial.SpatialMap | |||
import me.jfenn.dungeon.core.spatial.SpatialCollection | |||
import me.jfenn.dungeon.core.spatial.SpatialMapFast | |||
import me.jfenn.dungeon.core.vector.IntVector | |||
import kotlin.coroutines.CoroutineContext | |||
import kotlin.jvm.Synchronized | |||
abstract class Shader( | |||
val env: Config | |||
) { | |||
val lightMap = SpatialMap<ArrayList<Int>>() | |||
val lightMap = SpatialMapFast<ArrayList<Int>>() | |||
abstract suspend fun drawLightMap(position: IntVector, light: Luminance, pixels: SpatialMap<Pixel>) | |||
abstract suspend fun drawLightMap(position: IntVector, light: Luminance, pixels: SpatialCollection<Pixel>) | |||
@Synchronized | |||
fun addLight(position: IntVector, light: Int) { | |||
@@ -27,7 +27,7 @@ abstract class Shader( | |||
private val deferred = ArrayList<Deferred<Unit>>() | |||
suspend fun draw(lights: SpatialMap<Luminance>, pixels: SpatialMap<Pixel>) { | |||
suspend fun draw(lights: SpatialCollection<Luminance>, pixels: SpatialCollection<Pixel>) { | |||
lightMap.clear() | |||
lights.forEach { position, light -> | |||
@@ -12,4 +12,6 @@ interface ViewEventListener { | |||
fun onKeyUp(keyCode: Int) {} | |||
fun onWindowResized(width: Int, height: Int) {} | |||
} |
@@ -16,8 +16,8 @@ data class Luminance( | |||
return color.set(alpha = color.alpha() * (1 - ratio).coerceIn(0f, 1f)) | |||
} | |||
fun getAffectedRadius(): Int { | |||
return ceil(1.0 / degredation).toInt() | |||
fun getAffectedRadius(): Float { | |||
return ceil(1.0 / degredation).toFloat() | |||
} | |||
} |
@@ -19,10 +19,10 @@ interface SpatialCollection<T>: Collection<T>, SpatialObject { | |||
fun clone(): SpatialCollection<T> | |||
fun area(area: Area<IntVector>): SpatialCollection<T> | |||
fun forEach(block: (IntVector, T) -> Unit) | |||
fun forEachRadius(center: IntVector, radius: Float, block: (IntVector, T) -> Unit) | |||
fun keys(): List<IntVector> | |||
} | |||
@@ -1,8 +1,8 @@ | |||
package me.jfenn.dungeon.core.spatial | |||
import me.jfenn.dungeon.core.extensions.vectorOf | |||
import me.jfenn.dungeon.core.vector.IntVector | |||
import me.jfenn.dungeon.core.vector.MutableIntVector | |||
import me.jfenn.dungeon.core.vector.area.Area | |||
import kotlin.collections.HashMap | |||
class SpatialMap<T>(): SpatialCollection<T> { | |||
@@ -23,21 +23,6 @@ class SpatialMap<T>(): SpatialCollection<T> { | |||
} | |||
} | |||
/** | |||
* Get a subset of the current map that falls inside | |||
* of a given area. | |||
* | |||
* @param area The area that objects must be inside. | |||
* @return A new SpatialMap containing the objects. | |||
*/ | |||
override fun area(area: Area<IntVector>): SpatialMap<T> { | |||
val subset = SpatialMap<T>() | |||
for (vector in area) | |||
map[vector.x]?.get(vector.y)?.get(vector.z)?.let { subset[vector] = it } | |||
return subset | |||
} | |||
/** | |||
* Returns the size of the collection. | |||
*/ | |||
@@ -63,18 +48,18 @@ class SpatialMap<T>(): SpatialCollection<T> { | |||
size-- | |||
if (value != null) { | |||
((map[position.x] ?: run { | |||
HashMap<Int, MutableMap<Int, T>>().also { map[position.x] = it } | |||
((map[position.z] ?: run { | |||
HashMap<Int, MutableMap<Int, T>>().also { map[position.z] = it } | |||
})[position.y] ?: run { | |||
HashMap<Int, T>().also { map[position.x]?.set(position.y, it) } | |||
})[position.z] = value | |||
HashMap<Int, T>().also { map[position.z]?.set(position.y, it) } | |||
})[position.x] = value | |||
} else { | |||
map[position.x]?.get(position.y)?.remove(position.z) | |||
map[position.z]?.get(position.y)?.remove(position.x) | |||
} | |||
} | |||
override operator fun get(position: IntVector): T? { | |||
return map[position.x]?.get(position.y)?.get(position.z) | |||
return map[position.z]?.get(position.y)?.get(position.x) | |||
} | |||
override fun clear() { | |||
@@ -122,15 +107,26 @@ class SpatialMap<T>(): SpatialCollection<T> { | |||
} | |||
override fun forEach(block: (IntVector, T) -> Unit) { | |||
map.forEach { (x, yMap) -> | |||
yMap.forEach { (y, zMap) -> | |||
zMap.forEach { (z, value) -> | |||
map.forEach { (z, yMap) -> | |||
yMap.forEach { (y, xMap) -> | |||
xMap.forEach { (x, value) -> | |||
block(MutableIntVector(x, y, z), value) | |||
} | |||
} | |||
} | |||
} | |||
override fun forEachRadius(center: IntVector, radius: Float, block: (IntVector, T) -> Unit) { | |||
map.forEach { (z, yMap) -> | |||
yMap.forEach { (y, xMap) -> | |||
xMap.forEach { (x, value) -> | |||
if (vectorOf(x, y, z).distanceTo(center) < radius) | |||
block(MutableIntVector(x, y, z), value) | |||
} | |||
} | |||
} | |||
} | |||
override fun keys(): List<IntVector> { | |||
val list = ArrayList<IntVector>(this.size) | |||
this.forEach { i, _ -> | |||
@@ -145,8 +141,8 @@ class SpatialMap<T>(): SpatialCollection<T> { | |||
val iterator = ArrayList<T>().apply { | |||
for (yMap in map.values) { | |||
for (zMap in yMap.values) { | |||
for (value in zMap.values) { | |||
for (xMap in yMap.values) { | |||
for (value in xMap.values) { | |||
add(value) | |||
} | |||
} | |||
@@ -0,0 +1,141 @@ | |||
package me.jfenn.dungeon.core.spatial | |||
import me.jfenn.dungeon.core.extensions.vectorOf | |||
import me.jfenn.dungeon.core.vector.IntVector | |||
import me.jfenn.dungeon.core.vector.MutableIntVector | |||
import me.jfenn.dungeon.core.vector.area.Area | |||
import kotlin.collections.HashMap | |||
class SpatialMapChunked<T>( | |||
private val chunkSize: Int = 8 | |||
): SpatialCollection<T> { | |||
private val map = SpatialMapFast<SpatialMapFast<T>>() | |||
override val position: MutableIntVector | |||
get() = map.position | |||
override val area: MutableIntVector | |||
get() = map.area * chunkSize | |||
constructor(map: Map<IntVector, T>): this() { | |||
for ((k, v) in map) | |||
set(k, v) | |||
} | |||
constructor(collection: SpatialCollection<T>): this() { | |||
collection.forEach { position, item -> | |||
this[position] = item | |||
} | |||
} | |||
/** | |||
* Returns the size of the collection. | |||
*/ | |||
override var size: Int = 0 | |||
override operator fun set(position: IntVector, value: T?) { | |||
val chunkPosition: IntVector = position / chunkSize | |||
(map[chunkPosition] ?: run { | |||
SpatialMapFast<T>().also { map[chunkPosition] = it } | |||
})[position] = value | |||
} | |||
override operator fun get(position: IntVector): T? { | |||
return map[position / chunkSize]?.get(position) | |||
} | |||
override fun clear() { | |||
size = 0 | |||
map.clear() | |||
} | |||
override fun clone(): SpatialCollection<T> { | |||
return SpatialMapChunked(this) | |||
} | |||
/** | |||
* Checks if the specified element is contained in this collection. | |||
*/ | |||
override operator fun contains(element: T): Boolean { | |||
for (x in this) { | |||
if (x == element) | |||
return true | |||
} | |||
return false | |||
} | |||
/** | |||
* Checks if all elements in the specified collection are contained in this collection. | |||
*/ | |||
override fun containsAll(elements: Collection<T>): Boolean { | |||
for (spatial in elements) { | |||
if (!contains(spatial)) | |||
return false | |||
} | |||
return true | |||
} | |||
/** | |||
* Returns `true` if the collection is empty (contains no elements), `false` otherwise. | |||
*/ | |||
override fun isEmpty(): Boolean { | |||
return size == 0 | |||
} | |||
override fun iterator(): Iterator<T> { | |||
return ChunkIterator(map) | |||
} | |||
override fun forEach(block: (IntVector, T) -> Unit) { | |||
map.forEach { _, chunk -> | |||
chunk.forEach(block) | |||
} | |||
} | |||
override fun forEachRadius(center: IntVector, radius: Float, block: (IntVector, T) -> Unit) { | |||
map.forEach { position, chunk -> | |||
val chunkPosition: IntVector = position * chunkSize | |||
if (center.distanceTo(chunkPosition + (chunkSize / 2)) < radius + (chunkSize * .75)) | |||
chunk.forEachRadius(center, radius, block) | |||
} | |||
} | |||
override fun keys(): List<IntVector> { | |||
val list = ArrayList<IntVector>(this.size) | |||
this.forEach { i, _ -> | |||
list.add(i) | |||
} | |||
return list | |||
} | |||
class ChunkIterator<T>( | |||
map: SpatialMapFast<SpatialMapFast<T>> | |||
): Iterator<T> { | |||
val iterator = ArrayList<T>().apply { | |||
for (chunk in map) { | |||
for (item in chunk) { | |||
add(item) | |||
} | |||
} | |||
}.iterator() | |||
/** | |||
* Returns `true` if the iteration has more elements. | |||
*/ | |||
override fun hasNext(): Boolean { | |||
return iterator.hasNext() | |||
} | |||
/** | |||
* Returns the next element in the iteration. | |||
*/ | |||
override fun next(): T { | |||
return iterator.next() | |||
} | |||
} | |||
} |
@@ -0,0 +1,153 @@ | |||
package me.jfenn.dungeon.core.spatial | |||
import com.soywiz.kds.* | |||
import me.jfenn.dungeon.core.extensions.vectorOf | |||
import me.jfenn.dungeon.core.vector.IntVector | |||
import me.jfenn.dungeon.core.vector.MutableIntVector | |||
import kotlin.collections.HashMap | |||
class SpatialMapFast<T>(): SpatialCollection<T> { | |||
override val position: MutableIntVector = MutableIntVector(0, 0, 0) | |||
override val area: MutableIntVector = MutableIntVector(0, 0, 0) | |||
private val map: FastIntMap<Node<T>> = FastIntMap() | |||
constructor(map: Map<IntVector, T>): this() { | |||
for ((k, v) in map) | |||
set(k, v) | |||
} | |||
constructor(collection: SpatialCollection<T>): this() { | |||
collection.forEach { position, item -> | |||
this[position] = item | |||
} | |||
} | |||
/** | |||
* Returns the size of the collection. | |||
*/ | |||
override val size: Int | |||
get() = map.size | |||
override operator fun set(position: IntVector, value: T?) { | |||
if (position.x < this.position.x) | |||
this.position.x = position.x | |||
if (position.y < this.position.y) | |||
this.position.y = position.y | |||
if (position.z < this.position.z) | |||
this.position.z = position.z | |||
if (this.area.x < (position.x - this.position.x)) | |||
this.area.x = (position.x - this.position.x) | |||
if (this.area.y < (position.y - this.position.y)) | |||
this.area.y = (position.y - this.position.y) | |||
if (this.area.z < (position.z - this.position.z)) | |||
this.area.z = (position.z - this.position.z) | |||
if (value != null) { | |||
map[position.hashCode()] = Node(position, value) | |||
} else { | |||
map.remove(position.hashCode()) | |||
} | |||
} | |||
override operator fun get(position: IntVector): T? { | |||
return map[position.hashCode()]?.value | |||
} | |||
override fun clear() { | |||
map.clear() | |||
} | |||
override fun clone(): SpatialCollection<T> { | |||
return SpatialMapFast(this) | |||
} | |||
/** | |||
* Checks if the specified element is contained in this collection. | |||
*/ | |||
override operator fun contains(element: T): Boolean { | |||
for (x in this) { | |||
if (x == element) | |||
return true | |||
} | |||
return false | |||
} | |||
/** | |||
* Checks if all elements in the specified collection are contained in this collection. | |||
*/ | |||
override fun containsAll(elements: Collection<T>): Boolean { | |||
for (spatial in elements) { | |||
if (!contains(spatial)) | |||
return false | |||
} | |||
return true | |||
} | |||
/** | |||
* Returns `true` if the collection is empty (contains no elements), `false` otherwise. | |||
*/ | |||
override fun isEmpty(): Boolean { | |||
return size == 0 | |||
} | |||
override fun iterator(): Iterator<T> { | |||
return SpatialIterator(map) | |||
} | |||
override fun forEach(block: (IntVector, T) -> Unit) { | |||
map.fastForEach { _, entry -> | |||
block(entry.position, entry.value) | |||
} | |||
} | |||
override fun forEachRadius(center: IntVector, radius: Float, block: (IntVector, T) -> Unit) { | |||
map.fastForEach { _, entry -> | |||
if (entry.position.distanceTo(center) < radius) | |||
block(entry.position, entry.value) | |||
} | |||
} | |||
override fun keys(): List<IntVector> { | |||
val list = ArrayList<IntVector>(this.size) | |||
this.forEach { i, _ -> | |||
list.add(i) | |||
} | |||
return list | |||
} | |||
inner class Node<T>( | |||
val position: IntVector, | |||
val value: T | |||
) | |||
inner class SpatialIterator<T>( | |||
map: FastIntMap<Node<T>> | |||
): Iterator<T> { | |||
val iterator = ArrayList<T>().apply { | |||
map.fastValueForEachNullable { | |||
it?.let { add(it.value) } | |||
} | |||
}.iterator() | |||
/** | |||
* Returns `true` if the iteration has more elements. | |||
*/ | |||
override fun hasNext(): Boolean { | |||
return iterator.hasNext() | |||
} | |||
/** | |||
* Returns the next element in the iteration. | |||
*/ | |||
override fun next(): T { | |||
return iterator.next() | |||
} | |||
} | |||
} |
@@ -18,6 +18,14 @@ abstract class Vector<T: Number>: Comparable<Vector<T>>, VectorFactory { | |||
) | |||
} | |||
operator fun <V: Vector<T>> plus(other: T): V { | |||
return create( | |||
x + other, | |||
y + other, | |||
z + other | |||
) | |||
} | |||
operator fun <V: Vector<T>> minus(other: V): V { | |||
return create( | |||
x - other.x, | |||
@@ -26,6 +34,14 @@ abstract class Vector<T: Number>: Comparable<Vector<T>>, VectorFactory { | |||
) | |||
} | |||
operator fun <V: Vector<T>> minus(other: T): V { | |||
return create( | |||
x - other, | |||
y - other, | |||
z - other | |||
) | |||
} | |||
operator fun <V: Vector<T>> times(other: V): V { | |||
return create( | |||
x * other.x, | |||
@@ -34,6 +50,14 @@ abstract class Vector<T: Number>: Comparable<Vector<T>>, VectorFactory { | |||
) | |||
} | |||
operator fun <V: Vector<T>> times(other: T): V { | |||
return create( | |||
x * other, | |||
y * other, | |||
z * other | |||
) | |||
} | |||
operator fun <V: Vector<T>> div(other: V): V { | |||
return create( | |||
x / other.x, | |||
@@ -42,6 +66,14 @@ abstract class Vector<T: Number>: Comparable<Vector<T>>, VectorFactory { | |||
) | |||
} | |||
operator fun <V: Vector<T>> div(other: T): V { | |||
return create( | |||
x / other, | |||
y / other, | |||
z / other | |||
) | |||
} | |||
fun <V: Vector<T>> flatten(): V { | |||
return create( | |||
x, y, typeNum(0, x) | |||
@@ -4,6 +4,7 @@ import me.jfenn.dungeon.core.extensions.vectorOf | |||
import me.jfenn.dungeon.core.vector.* | |||
import me.jfenn.dungeon.core.vector.iterator.AreaIterator | |||
import kotlin.Exception | |||
import kotlin.math.abs | |||
import kotlin.math.roundToInt | |||
inline fun forLine(start: IntVector, end: IntVector, crossinline block: (IntVector) -> Unit) { | |||
@@ -23,6 +24,49 @@ inline fun forLine(start: IntVector, end: IntVector, crossinline block: (IntVect | |||
} | |||
} | |||
class LineIterator( | |||
private val start: IntVector, | |||
private val end: IntVector | |||
) : Iterator<IntVector> { | |||
private val diff = vectorOf( | |||
abs(start.x - end.x), | |||
abs(start.y - end.y), | |||
abs(start.z - end.z) | |||
) | |||
private val distance = when { | |||
diff.x > diff.y -> diff.x.toFloat() | |||
diff.y > diff.z -> diff.y.toFloat() | |||
diff.z != 0 -> diff.z.toFloat() | |||
else -> start.distanceTo(end).toFloat() * LineArea.SQUARE_MULTIPLIER | |||
} | |||
private val delta = MutableFloatVector( | |||
(end.x - start.x) / distance, | |||
(end.y - start.y) / distance, | |||
(end.z - start.z) / distance | |||
) | |||
private var current = 0 | |||
override fun hasNext(): Boolean { | |||
return current < distance | |||
} | |||
override fun next(): IntVector { | |||
val position = vectorOf( | |||
start.x + (delta.x * current).toInt(), | |||
start.y + (delta.y * current).toInt(), | |||
start.z + (delta.z * current).toInt() | |||
) | |||
current++ | |||
return position | |||
} | |||
} | |||
class LineArea( | |||
val start: IntVector, | |||
val end: IntVector | |||
@@ -58,6 +58,10 @@ open class JDungeonCanvas( | |||
env.currentWidth = size.x / pixelSize | |||
env.currentHeight = size.y / pixelSize | |||
env.viewEventListeners.forEach { | |||
it.onWindowResized(env.currentWidth, env.currentHeight) | |||
} | |||
revalidate() | |||
repaint() | |||
} | |||
@@ -23,4 +23,8 @@ class Background : Sprite() { | |||
} | |||
} | |||
override fun onWindowResized(width: Int, height: Int) { | |||
invalidate() | |||
} | |||
} |