multithreaded chunk geometry batching
This commit is contained in:
parent
71a6f388fc
commit
6ae8066ebc
@ -82,7 +82,7 @@ dependencies {
|
||||
implementation("net.java.dev.jna:jna:5.13.0")
|
||||
implementation("com.github.jnr:jnr-ffi:2.2.13")
|
||||
|
||||
implementation("ru.dbotthepony:kbox2d:2.4.1.7")
|
||||
implementation("ru.dbotthepony:kbox2d:2.4.1-1.0.2")
|
||||
implementation("ru.dbotthepony:kvector:2.10.2")
|
||||
|
||||
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
|
||||
|
@ -7,6 +7,7 @@ import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||
import ru.dbotthepony.kstarbound.io.BTreeDB
|
||||
import ru.dbotthepony.kstarbound.io.json.BinaryJsonReader
|
||||
import ru.dbotthepony.kstarbound.player.Avatar
|
||||
import ru.dbotthepony.kstarbound.player.QuestDescriptor
|
||||
import ru.dbotthepony.kstarbound.player.QuestInstance
|
||||
@ -17,6 +18,7 @@ import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
||||
import ru.dbotthepony.kstarbound.io.json.VersionedJson
|
||||
import ru.dbotthepony.kstarbound.io.readVarInt
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
import ru.dbotthepony.kstarbound.world.entities.Move
|
||||
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import java.io.BufferedInputStream
|
||||
@ -24,8 +26,10 @@ import java.io.ByteArrayInputStream
|
||||
import java.io.DataInputStream
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
import java.util.zip.Inflater
|
||||
import java.util.zip.InflaterInputStream
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
@ -37,6 +41,11 @@ fun main() {
|
||||
val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world"))
|
||||
//val db = BTreeDB(File("world.world"))
|
||||
|
||||
//val meta = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(db.read(byteArrayOf(0, 0, 0, 0, 0))), Inflater())))
|
||||
|
||||
//println(meta.readInt())
|
||||
//println(meta.readInt())
|
||||
|
||||
val client = StarboundClient()
|
||||
|
||||
//Starbound.addFilePath(File("./unpacked_assets/"))
|
||||
@ -64,6 +73,7 @@ fun main() {
|
||||
//for (chunkX in 17 .. 18) {
|
||||
//for (chunkX in 14 .. 24) {
|
||||
for (chunkX in 0 .. 100) {
|
||||
//for (chunkX in 0 .. 17) {
|
||||
// for (chunkY in 21 .. 21) {
|
||||
for (chunkY in 18 .. 24) {
|
||||
val data = db.read(byteArrayOf(1, 0, chunkX.toByte(), 0, chunkY.toByte()))
|
||||
@ -114,8 +124,6 @@ fun main() {
|
||||
val item = Registries.items.values.random()
|
||||
val rand = Random()
|
||||
|
||||
client.world!!.physics.gravity = Vector2d.ZERO
|
||||
|
||||
for (i in 0 .. 0) {
|
||||
val item = ItemEntity(client.world!!, item.value)
|
||||
|
||||
@ -149,7 +157,7 @@ fun main() {
|
||||
}
|
||||
|
||||
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
||||
ent.position = Vector2d(600.0 + 16.0, 721.0 + 48.0)
|
||||
ent.position = Vector2d(238.0, 685.0)
|
||||
client.camera.pos = Vector2d(238.0, 685.0)
|
||||
//client.camera.pos = Vector2f(0f, 0f)
|
||||
|
||||
@ -166,57 +174,6 @@ fun main() {
|
||||
//client.camera.pos.y = ent.pos.y.toFloat()
|
||||
}
|
||||
|
||||
/*val lightRenderer = LightRenderer(client.gl)
|
||||
|
||||
lightRenderer.resizeFramebuffer(client.viewportWidth, client.viewportHeight)
|
||||
client.onViewportChanged(lightRenderer::resizeFramebuffer)
|
||||
|
||||
lightRenderer.addShadowGeometry(object : LightRenderer.ShadowGeometryRenderer {
|
||||
override fun renderHardGeometry(lightPosition: Vector2f, stack: Matrix4fStack, program: GLHardLightGeometryProgram) {
|
||||
val builder = lightRenderer.builder
|
||||
|
||||
builder.begin()
|
||||
builder.quad(-6f, 0f, -2f, 2f)
|
||||
builder.quad(0f, 0f, 2f, 2f)
|
||||
|
||||
builder.upload()
|
||||
builder.draw(GL_LINES)
|
||||
}
|
||||
|
||||
override fun renderSoftGeometry(lightPosition: Vector2f, stack: Matrix4fStack, program: GLSoftLightGeometryProgram) {
|
||||
val builder = lightRenderer.builderSoft
|
||||
|
||||
builder.begin()
|
||||
builder.shadowQuad(-6f, 0f, -2f, 2f)
|
||||
builder.shadowQuad(0f, 0f, 2f, 2f)
|
||||
|
||||
builder.upload()
|
||||
builder.draw(GL_TRIANGLES)
|
||||
}
|
||||
})
|
||||
|
||||
client.onPostDrawWorld {
|
||||
lightRenderer.begin()
|
||||
|
||||
for ((lightPosition, color) in listOf(
|
||||
(client.screenToWorld(client.mouseCoordinatesF)) to Color.RED,
|
||||
(client.screenToWorld(client.mouseCoordinatesF) + Vector2f(0.1f)) to Color.GREEN,
|
||||
(client.screenToWorld(client.mouseCoordinatesF) + Vector2f(-0.1f)) to Color.BLUE,
|
||||
)) {
|
||||
lightRenderer.renderSoftLight(lightPosition, color, radius = 40f)
|
||||
}
|
||||
|
||||
for ((lightPosition, color) in listOf(
|
||||
(client.screenToWorld(client.mouseCoordinatesF) + Vector2f(10.1f)) to Color.RED,
|
||||
(client.screenToWorld(client.mouseCoordinatesF) + Vector2f(10.1f)) to Color.GREEN,
|
||||
(client.screenToWorld(client.mouseCoordinatesF) + Vector2f(9.9f)) to Color.BLUE,
|
||||
)) {
|
||||
//lightRenderer.renderSoftLight(lightPosition, color, radius = 10f)
|
||||
}
|
||||
|
||||
lightRenderer.renderOutputAdditive()
|
||||
}*/
|
||||
|
||||
client.box2dRenderer.drawShapes = false
|
||||
client.box2dRenderer.drawPairs = false
|
||||
client.box2dRenderer.drawAABB = false
|
||||
@ -240,8 +197,8 @@ fun main() {
|
||||
//client.camera.pos.y = ent.position.y.toFloat()
|
||||
|
||||
client.camera.pos += Vector2d(
|
||||
(if (client.input.KEY_LEFT_DOWN || client.input.KEY_A_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) + (if (client.input.KEY_RIGHT_DOWN || client.input.KEY_D_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0),
|
||||
(if (client.input.KEY_UP_DOWN || client.input.KEY_W_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) + (if (client.input.KEY_DOWN_DOWN || client.input.KEY_S_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0)
|
||||
(if (client.input.KEY_A_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) + (if (client.input.KEY_D_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0),
|
||||
(if (client.input.KEY_W_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) + (if (client.input.KEY_S_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0)
|
||||
)
|
||||
|
||||
//println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1)
|
||||
@ -249,7 +206,7 @@ fun main() {
|
||||
//if (ent.onGround)
|
||||
//ent.velocity += client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1
|
||||
|
||||
/*if (client.input.KEY_LEFT_DOWN) {
|
||||
if (client.input.KEY_LEFT_DOWN) {
|
||||
ent.movement.moveDirection = Move.MOVE_LEFT
|
||||
} else if (client.input.KEY_RIGHT_DOWN) {
|
||||
ent.movement.moveDirection = Move.MOVE_RIGHT
|
||||
@ -261,7 +218,7 @@ fun main() {
|
||||
ent.movement.requestJump()
|
||||
} else if (client.input.KEY_SPACE_RELEASED) {
|
||||
ent.movement.recallJump()
|
||||
}*/
|
||||
}
|
||||
|
||||
if (client.input.KEY_ESCAPE_PRESSED) {
|
||||
glfwSetWindowShouldClose(client.window, true)
|
||||
|
@ -49,6 +49,7 @@ import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
||||
import ru.dbotthepony.kstarbound.defs.image.Image
|
||||
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||
import ru.dbotthepony.kstarbound.util.Either
|
||||
import ru.dbotthepony.kstarbound.util.forEachValid
|
||||
import ru.dbotthepony.kstarbound.util.formatBytesShort
|
||||
import ru.dbotthepony.kstarbound.world.LightCalculator
|
||||
@ -65,20 +66,32 @@ import ru.dbotthepony.kvector.vector.Vector2i
|
||||
import ru.dbotthepony.kvector.vector.Vector4f
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.lang.ref.Cleaner
|
||||
import java.lang.ref.PhantomReference
|
||||
import java.lang.ref.ReferenceQueue
|
||||
import java.lang.ref.WeakReference
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.FutureTask
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
import java.util.concurrent.locks.LockSupport
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.stream.StreamSupport
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import java.util.function.IntConsumer
|
||||
import kotlin.NoSuchElementException
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class StarboundClient : Closeable {
|
||||
class StarboundClient : Closeable, ExecutorService {
|
||||
val window: Long
|
||||
val camera = Camera(this)
|
||||
val input = UserInput()
|
||||
@ -111,7 +124,7 @@ class StarboundClient : Closeable {
|
||||
var viewportTopRight = Vector2d()
|
||||
private set
|
||||
|
||||
var fullbright = false
|
||||
var fullbright = true
|
||||
|
||||
var clientTerminated = false
|
||||
private set
|
||||
@ -128,25 +141,141 @@ class StarboundClient : Closeable {
|
||||
private set
|
||||
|
||||
private val scissorStack = LinkedList<ScissorRect>()
|
||||
private val cleanerBacklog = ArrayList<() -> Unit>()
|
||||
private val onDrawGUI = ArrayList<() -> Unit>()
|
||||
private val onPreDrawWorld = ArrayList<(LayeredRenderer) -> Unit>()
|
||||
private val onPostDrawWorld = ArrayList<() -> Unit>()
|
||||
private val onPostDrawWorldOnce = ArrayList<(LayeredRenderer) -> Unit>()
|
||||
private val onViewportChanged = ArrayList<(width: Int, height: Int) -> Unit>()
|
||||
private val terminateCallbacks = ArrayList<() -> Unit>()
|
||||
|
||||
val loadingLog = LoadingLog()
|
||||
|
||||
private val cleaner = Cleaner.create { r ->
|
||||
val thread = Thread(r, "OpenGL Cleaner for '${thread.name}'")
|
||||
thread.priority = 2
|
||||
thread
|
||||
private val executeQueue = ConcurrentLinkedQueue<Runnable>()
|
||||
private val futureQueue = ConcurrentLinkedQueue<FutureTask<*>>()
|
||||
private val openglCleanQueue = ReferenceQueue<Any>()
|
||||
private var openglCleanQueueHead: CleanRef? = null
|
||||
|
||||
private class CleanRef(ref: Any, queue: ReferenceQueue<Any>, val fn: IntConsumer, val value: Int) : PhantomReference<Any>(ref, queue) {
|
||||
var next: CleanRef? = null
|
||||
var prev: CleanRef? = null
|
||||
}
|
||||
|
||||
var objectsCreated = 0L
|
||||
private data class InPlaceFuture<V>(private val value: V) : Future<V> {
|
||||
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isCancelled(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isDone(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun get(): V {
|
||||
return value
|
||||
}
|
||||
|
||||
override fun get(timeout: Long, unit: TimeUnit): V {
|
||||
return value
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = InPlaceFuture(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
override fun execute(command: Runnable) {
|
||||
if (isSameThread()) {
|
||||
command.run()
|
||||
} else {
|
||||
executeQueue.add(command)
|
||||
LockSupport.unpark(thread)
|
||||
}
|
||||
}
|
||||
|
||||
override fun shutdown() {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun shutdownNow(): MutableList<Runnable> {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun isShutdown(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isTerminated(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun <T : Any?> submit(task: Callable<T>): Future<T> {
|
||||
if (isSameThread()) return InPlaceFuture(task.call())
|
||||
return FutureTask(task).also { futureQueue.add(it); LockSupport.unpark(thread) }
|
||||
}
|
||||
|
||||
override fun <T : Any?> submit(task: Runnable, result: T): Future<T> {
|
||||
if (isSameThread()) { task.run(); return InPlaceFuture(result) }
|
||||
return FutureTask { task.run(); result }.also { futureQueue.add(it); LockSupport.unpark(thread) }
|
||||
}
|
||||
|
||||
override fun submit(task: Runnable): Future<*> {
|
||||
if (isSameThread()) { task.run(); return InPlaceFuture.EMPTY }
|
||||
return FutureTask { task.run() }.also { futureQueue.add(it); LockSupport.unpark(thread) }
|
||||
}
|
||||
|
||||
override fun <T : Any?> invokeAll(tasks: Collection<Callable<T>>): List<Future<T>> {
|
||||
if (isSameThread()) {
|
||||
return tasks.map { InPlaceFuture(it.call()) }
|
||||
} else {
|
||||
return tasks.map { submit(it) }.onEach { it.get() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T : Any?> invokeAll(
|
||||
tasks: Collection<Callable<T>>,
|
||||
timeout: Long,
|
||||
unit: TimeUnit
|
||||
): List<Future<T>> {
|
||||
if (isSameThread()) {
|
||||
return tasks.map { InPlaceFuture(it.call()) }
|
||||
} else {
|
||||
return tasks.map { submit(it) }.onEach { it.get(timeout, unit) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T : Any?> invokeAny(tasks: Collection<Callable<T>>): T {
|
||||
if (tasks.isEmpty())
|
||||
throw NoSuchElementException("Provided task list is empty")
|
||||
|
||||
if (isSameThread()) {
|
||||
return tasks.first().call()
|
||||
} else {
|
||||
return submit(tasks.first()).get()
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T : Any?> invokeAny(tasks: Collection<Callable<T>>, timeout: Long, unit: TimeUnit): T {
|
||||
if (tasks.isEmpty())
|
||||
throw NoSuchElementException("Provided task list is empty")
|
||||
|
||||
if (isSameThread()) {
|
||||
return tasks.first().call()
|
||||
} else {
|
||||
return submit(tasks.first()).get(timeout, unit)
|
||||
}
|
||||
}
|
||||
|
||||
var openglObjectsCreated = 0L
|
||||
private set
|
||||
|
||||
var objectsCleaned = 0L
|
||||
var openglObjectsCleaned = 0L
|
||||
private set
|
||||
|
||||
init {
|
||||
@ -278,35 +407,52 @@ class StarboundClient : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable {
|
||||
objectsCreated++
|
||||
fun registerCleanable(ref: Any, fn: IntConsumer, nativeRef: Int) {
|
||||
openglObjectsCreated++
|
||||
val ref0 = CleanRef(ref, openglCleanQueue, fn, nativeRef)
|
||||
|
||||
val cleanable = cleaner.register(ref) {
|
||||
if (isSameThread()) {
|
||||
objectsCleaned++
|
||||
fn(nativeRef)
|
||||
checkForGLError()
|
||||
} else {
|
||||
synchronized(cleanerBacklog) {
|
||||
cleanerBacklog.add {
|
||||
objectsCleaned++
|
||||
fn(nativeRef)
|
||||
checkForGLError()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (openglCleanQueueHead == null) {
|
||||
openglCleanQueueHead = ref0
|
||||
} else {
|
||||
ref0.next = openglCleanQueueHead
|
||||
openglCleanQueueHead!!.prev = ref0
|
||||
openglCleanQueueHead = ref0
|
||||
}
|
||||
|
||||
return cleanable
|
||||
}
|
||||
|
||||
fun cleanup() {
|
||||
synchronized(cleanerBacklog) {
|
||||
for (lambda in cleanerBacklog) {
|
||||
lambda.invoke()
|
||||
private fun executeQueuedTasks() {
|
||||
var next = executeQueue.poll()
|
||||
|
||||
while (next != null) {
|
||||
next.run()
|
||||
next = executeQueue.poll()
|
||||
}
|
||||
|
||||
var next2 = futureQueue.poll()
|
||||
|
||||
while (next2 != null) {
|
||||
next2.run()
|
||||
Thread.interrupted()
|
||||
next2 = futureQueue.poll()
|
||||
}
|
||||
|
||||
var next3 = openglCleanQueue.poll() as CleanRef?
|
||||
|
||||
while (next3 != null) {
|
||||
openglObjectsCleaned++
|
||||
next3.fn.accept(next3.value)
|
||||
checkForGLError("Removing unreachable OpenGL object")
|
||||
|
||||
val head = openglCleanQueueHead
|
||||
|
||||
if (next3 === head) {
|
||||
openglCleanQueueHead = head.next
|
||||
} else {
|
||||
next3.prev?.next = next3.next
|
||||
next3.next?.prev = next3.prev
|
||||
}
|
||||
|
||||
cleanerBacklog.clear()
|
||||
next3 = openglCleanQueue.poll() as CleanRef?
|
||||
}
|
||||
}
|
||||
|
||||
@ -731,7 +877,7 @@ class StarboundClient : Closeable {
|
||||
onPostDrawWorldOnce.add(lambda)
|
||||
}
|
||||
|
||||
private val layers = LayeredRenderer()
|
||||
private val layers = LayeredRenderer(this)
|
||||
|
||||
fun renderFrame(): Boolean {
|
||||
ensureSameThread()
|
||||
@ -740,6 +886,8 @@ class StarboundClient : Closeable {
|
||||
|
||||
// try to sleep until next frame as precise as possible
|
||||
while (diff > 0L) {
|
||||
executeQueuedTasks()
|
||||
|
||||
if (diff >= 1_500_000L) {
|
||||
LockSupport.parkNanos(1_000_000L)
|
||||
} else {
|
||||
@ -760,7 +908,7 @@ class StarboundClient : Closeable {
|
||||
val world = world
|
||||
|
||||
if (!isRenderingGame) {
|
||||
cleanup()
|
||||
executeQueuedTasks()
|
||||
GLFW.glfwPollEvents()
|
||||
|
||||
if (world != null && Starbound.initialized)
|
||||
@ -852,7 +1000,9 @@ class StarboundClient : Closeable {
|
||||
|
||||
layers.render()
|
||||
|
||||
box2dRenderer.begin()
|
||||
world.physics.debugDraw()
|
||||
box2dRenderer.render()
|
||||
|
||||
for (lambda in onPostDrawWorld) {
|
||||
lambda.invoke()
|
||||
@ -906,7 +1056,7 @@ class StarboundClient : Closeable {
|
||||
font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f)
|
||||
font.render("Frame: ${(averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f)
|
||||
font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 1.2f, scale = 0.4f)
|
||||
font.render("OGL C: $objectsCreated D: $objectsCleaned A: ${objectsCreated - objectsCleaned}", y = font.lineHeight * 1.8f, scale = 0.4f)
|
||||
font.render("OGL C: $openglObjectsCreated D: $openglObjectsCleaned A: ${openglObjectsCreated - openglObjectsCleaned}", y = font.lineHeight * 1.8f, scale = 0.4f)
|
||||
|
||||
GLFW.glfwSwapBuffers(window)
|
||||
GLFW.glfwPollEvents()
|
||||
@ -914,7 +1064,7 @@ class StarboundClient : Closeable {
|
||||
|
||||
camera.think(Starbound.TICK_TIME_ADVANCE)
|
||||
|
||||
cleanup()
|
||||
executeQueuedTasks()
|
||||
|
||||
return true
|
||||
} finally {
|
||||
|
@ -5,6 +5,7 @@ import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.gl.VertexArrayObject
|
||||
import ru.dbotthepony.kstarbound.client.gl.BufferObject
|
||||
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
/**
|
||||
* Быстрое наполнение буфера вершинами, загрузка в память видеокарты, и отрисовка
|
||||
@ -13,29 +14,38 @@ class StreamVertexBuilder(
|
||||
attributes: VertexAttributes,
|
||||
type: GeometryType? = null,
|
||||
initialCapacity: Int = 32,
|
||||
val client: StarboundClient = StarboundClient.current()
|
||||
) {
|
||||
val state = StarboundClient.current()
|
||||
val builder = VertexBuilder(attributes, type, initialCapacity)
|
||||
private val vao = VertexArrayObject()
|
||||
private val vbo = BufferObject.VBO()
|
||||
private val ebo = BufferObject.EBO()
|
||||
|
||||
init {
|
||||
vao.elementBuffer = ebo
|
||||
vao.bindAttributes(vbo, attributes)
|
||||
}
|
||||
private val vao = client.submit(Callable { VertexArrayObject() })
|
||||
private val vbo = client.submit(Callable { BufferObject.VBO() })
|
||||
private val ebo = client.submit(Callable { BufferObject.EBO() })
|
||||
|
||||
private var initialized = false
|
||||
|
||||
fun upload(drawType: Int = GL45.GL_DYNAMIC_DRAW) {
|
||||
builder.upload(vbo, ebo, drawType)
|
||||
if (vbo.isDone && ebo.isDone)
|
||||
builder.upload(vbo.get(), ebo.get(), drawType)
|
||||
}
|
||||
|
||||
fun bind() = vao.bind()
|
||||
fun unbind() = vao.unbind()
|
||||
fun bind() = vao.get().bind()
|
||||
fun unbind() = vao.get().unbind()
|
||||
|
||||
fun draw(primitives: Int = GL45.GL_TRIANGLES) {
|
||||
if (!initialized) {
|
||||
vao.get().elementBuffer = ebo.get()
|
||||
vao.get().bindAttributes(vbo.get(), builder.attributes)
|
||||
initialized = true
|
||||
}
|
||||
|
||||
bind()
|
||||
GL45.glDrawElements(primitives, builder.indexCount, builder.indexType, 0L)
|
||||
checkForGLError()
|
||||
unbind()
|
||||
|
||||
try {
|
||||
GL45.glDrawElements(primitives, builder.indexCount, builder.indexType, 0L)
|
||||
checkForGLError()
|
||||
} finally {
|
||||
unbind()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import kotlin.math.sin
|
||||
|
||||
class Box2DRenderer : IDebugDraw {
|
||||
val state = StarboundClient.current()
|
||||
private val identity = Matrix3f.identity()
|
||||
|
||||
override var drawShapes: Boolean = false
|
||||
override var drawJoints: Boolean = false
|
||||
@ -25,51 +24,57 @@ class Box2DRenderer : IDebugDraw {
|
||||
override var drawPairs: Boolean = false
|
||||
override var drawCenterOfMess: Boolean = false
|
||||
|
||||
private val builder = StreamVertexBuilder(VertexAttributes.POSITION)
|
||||
private val identity = Matrix3f.identity()
|
||||
|
||||
private val lines = StreamVertexBuilder(VertexAttributes.POSITION_COLOR, GeometryType.LINES)
|
||||
private val triangles = StreamVertexBuilder(VertexAttributes.POSITION_COLOR, GeometryType.TRIANGLES)
|
||||
|
||||
fun begin() {
|
||||
lines.builder.begin()
|
||||
triangles.builder.begin()
|
||||
}
|
||||
|
||||
fun render() {
|
||||
if (lines.builder.isEmpty() && triangles.builder.isEmpty()) return
|
||||
|
||||
lines.upload(GL_STREAM_DRAW)
|
||||
triangles.upload(GL_STREAM_DRAW)
|
||||
|
||||
state.programs.positionColor.use()
|
||||
state.programs.positionColor.colorMultiplier = RGBAColor.WHITE
|
||||
state.programs.positionColor.modelMatrix = identity
|
||||
|
||||
lines.draw(GL_LINES)
|
||||
triangles.draw(GL_TRIANGLES)
|
||||
}
|
||||
|
||||
override fun drawPolygon(vertices: List<Vector2d>, color: RGBAColor) {
|
||||
require(vertices.size > 1) { "Vertex list had only ${vertices.size} namings in it" }
|
||||
|
||||
builder.builder.begin()
|
||||
if (!vertices.any { state.viewportRectangle.isInside(it) }) return
|
||||
|
||||
for (i in vertices.indices) {
|
||||
val current = vertices[i]
|
||||
val next = vertices[(i + 1) % vertices.size]
|
||||
builder.builder.vertex(current.x.toFloat(), current.y.toFloat())
|
||||
builder.builder.vertex(next.x.toFloat(), next.y.toFloat())
|
||||
lines.builder.vertex(state.stack.last(), current.x.toFloat(), current.y.toFloat()).color(color)
|
||||
lines.builder.vertex(state.stack.last(), next.x.toFloat(), next.y.toFloat()).color(color)
|
||||
}
|
||||
|
||||
builder.upload()
|
||||
|
||||
state.programs.position.use()
|
||||
state.programs.position.colorMultiplier = color
|
||||
state.programs.position.modelMatrix = state.stack.last()
|
||||
|
||||
builder.draw(GL_LINES)
|
||||
}
|
||||
|
||||
private fun drawSolid(vertices: List<Vector2d>, color: RGBAColor) {
|
||||
require(vertices.size >= 3) { "Vertex list had only ${vertices.size} namings in it" }
|
||||
|
||||
builder.builder.begin(GeometryType.TRIANGLES)
|
||||
if (!vertices.any { state.viewportRectangle.isInside(it) }) return
|
||||
|
||||
val zero = vertices[0]
|
||||
|
||||
for (i in 1 until vertices.size) {
|
||||
val current = vertices[i]
|
||||
val next = vertices[(i + 1) % vertices.size]
|
||||
builder.builder.vertex(zero.x.toFloat(), zero.y.toFloat())
|
||||
builder.builder.vertex(current.x.toFloat(), current.y.toFloat())
|
||||
builder.builder.vertex(next.x.toFloat(), next.y.toFloat())
|
||||
triangles.builder.vertex(state.stack.last(), zero.x.toFloat(), zero.y.toFloat()).color(color)
|
||||
triangles.builder.vertex(state.stack.last(), current.x.toFloat(), current.y.toFloat()).color(color)
|
||||
triangles.builder.vertex(state.stack.last(), next.x.toFloat(), next.y.toFloat()).color(color)
|
||||
}
|
||||
|
||||
builder.upload()
|
||||
|
||||
state.programs.position.use()
|
||||
state.programs.position.colorMultiplier = color
|
||||
state.programs.position.modelMatrix = state.stack.last()
|
||||
|
||||
builder.draw(GL_TRIANGLES)
|
||||
}
|
||||
|
||||
override fun drawSolidPolygon(vertices: List<Vector2d>, color: RGBAColor) {
|
||||
|
@ -3,8 +3,10 @@ package ru.dbotthepony.kstarbound.client.render
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap
|
||||
import org.lwjgl.opengl.GL15.GL_STREAM_DRAW
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.function.Function
|
||||
import java.util.stream.Stream
|
||||
|
||||
@ -38,9 +40,10 @@ object OneShotGeometryLayer : IGeometryLayer {
|
||||
}
|
||||
}
|
||||
|
||||
class LayeredRenderer {
|
||||
class Layer(val layer: RenderLayer.Point) : IGeometryLayer {
|
||||
private data class Tracker(val builder: StreamVertexBuilder, var emptyFrames: Int = 0)
|
||||
class LayeredRenderer(val client: StarboundClient) {
|
||||
private data class Tracker(val builder: StreamVertexBuilder, var emptyFrames: Int = 0)
|
||||
|
||||
inner class Layer(val layer: RenderLayer.Point) : IGeometryLayer {
|
||||
private val meshes = ArrayList<ConfiguredMesh<*>>()
|
||||
private val callbacks = ArrayList<() -> Unit>()
|
||||
private val builders = Reference2ObjectLinkedOpenHashMap<RenderConfig<*>, Tracker>()
|
||||
@ -51,7 +54,7 @@ class LayeredRenderer {
|
||||
|
||||
override fun getBuilder(config: RenderConfig<*>): VertexBuilder {
|
||||
return builders.computeIfAbsent(config, Function {
|
||||
Tracker(StreamVertexBuilder(config.program.attributes, initialCapacity = config.initialBuilderCapacity))
|
||||
Tracker(StreamVertexBuilder(config.program.attributes, initialCapacity = config.initialBuilderCapacity, client = client))
|
||||
}).builder.builder
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import ru.dbotthepony.kvector.arrays.Matrix3f
|
||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.Callable
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
@ -51,14 +52,14 @@ class TileRenderers(val client: StarboundClient) {
|
||||
fun getMaterialRenderer(defName: String): TileRenderer {
|
||||
return matCache.get(defName) {
|
||||
val def = Registries.tiles[defName] // TODO: Пустой рендерер
|
||||
TileRenderer(this, def!!.value)
|
||||
client.submit(Callable { TileRenderer(this, def!!.value) }).get()
|
||||
}
|
||||
}
|
||||
|
||||
fun getModifierRenderer(defName: String): TileRenderer {
|
||||
return modCache.get(defName) {
|
||||
val def = Registries.tileModifiers[defName] // TODO: Пустой рендерер
|
||||
TileRenderer(this, def!!.value)
|
||||
client.submit(Callable { TileRenderer(this, def!!.value) }).get()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,12 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.longs.LongArraySet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceArraySet
|
||||
import ru.dbotthepony.kbox2d.api.BodyDef
|
||||
import ru.dbotthepony.kbox2d.api.BodyType
|
||||
import ru.dbotthepony.kbox2d.api.FixtureDef
|
||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.render.ConfiguredMesh
|
||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||
@ -31,6 +36,9 @@ import ru.dbotthepony.kvector.vector.RGBAColor
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import ru.dbotthepony.kvector.vector.Vector2f
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
import java.util.concurrent.Future
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
@ -80,42 +88,56 @@ class ClientWorld(
|
||||
inner class RenderRegion(val x: Int, val y: Int) {
|
||||
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
|
||||
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, RenderLayer.Point>>()
|
||||
private var currentBakeTask: Future<LayeredRenderer>? = null
|
||||
var isDirty = true
|
||||
|
||||
fun bake() {
|
||||
if (!isDirty) return
|
||||
if (!isDirty) {
|
||||
val currentBakeTask = currentBakeTask ?: return
|
||||
|
||||
if (currentBakeTask.isDone) {
|
||||
bakedMeshes.clear()
|
||||
|
||||
for ((baked, zLevel) in currentBakeTask.get().bakeIntoMeshes()) {
|
||||
bakedMeshes.add(baked to zLevel)
|
||||
}
|
||||
|
||||
this.currentBakeTask = null
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
isDirty = false
|
||||
|
||||
bakedMeshes.clear()
|
||||
currentBakeTask = ForkJoinPool.commonPool().submit(Callable {
|
||||
val meshes = LayeredRenderer(client)
|
||||
|
||||
val meshes = LayeredRenderer()
|
||||
for (x in 0 until renderRegionWidth) {
|
||||
for (y in 0 until renderRegionHeight) {
|
||||
if (!inBounds(x, y)) continue
|
||||
|
||||
for (x in 0 until renderRegionWidth) {
|
||||
for (y in 0 until renderRegionHeight) {
|
||||
if (!inBounds(x, y)) continue
|
||||
val tile = view.getTile(x, y) ?: continue
|
||||
val material = tile.material
|
||||
|
||||
val tile = view.getTile(x, y) ?: continue
|
||||
val material = tile.material
|
||||
if (!material.isMeta) {
|
||||
client.tileRenderers
|
||||
.getMaterialRenderer(material.materialName)
|
||||
.tesselate(tile, view, meshes, Vector2i(x, y), isBackground = isBackground)
|
||||
}
|
||||
|
||||
if (!material.isMeta) {
|
||||
client.tileRenderers
|
||||
.getMaterialRenderer(material.materialName)
|
||||
.tesselate(tile, view, meshes, Vector2i(x, y), isBackground = isBackground)
|
||||
}
|
||||
val modifier = tile.modifier
|
||||
|
||||
val modifier = tile.modifier
|
||||
|
||||
if (modifier != null) {
|
||||
client.tileRenderers
|
||||
.getModifierRenderer(modifier.modName)
|
||||
.tesselate(tile, view, meshes, Vector2i(x, y), isBackground = isBackground, isModifier = true)
|
||||
if (modifier != null) {
|
||||
client.tileRenderers
|
||||
.getModifierRenderer(modifier.modName)
|
||||
.tesselate(tile, view, meshes, Vector2i(x, y), isBackground = isBackground, isModifier = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((baked, zLevel) in meshes.bakeIntoMeshes()) {
|
||||
bakedMeshes.add(baked to zLevel)
|
||||
}
|
||||
meshes
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,8 @@ package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
||||
import ru.dbotthepony.kbox2d.api.BodyDef
|
||||
import ru.dbotthepony.kbox2d.api.FixtureDef
|
||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||
import ru.dbotthepony.kbox2d.dynamics.B2Fixture
|
||||
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
@ -290,38 +292,19 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
|
||||
collisionChains.clear()
|
||||
|
||||
val xyAdd = Vector2d(pos.x * CHUNK_SIZEd, pos.y * CHUNK_SIZEd)
|
||||
val seen = BooleanArray(CHUNK_SIZE * CHUNK_SIZE)
|
||||
|
||||
/*for (y in 0 .. CHUNK_SIZE_FF) {
|
||||
for (y in 0 .. CHUNK_SIZE_FF) {
|
||||
for (x in 0..CHUNK_SIZE_FF) {
|
||||
if (!seen[x or (y shl CHUNK_SIZE_BITS)] && cells[x, y].foreground.material != null) {
|
||||
val depthFirst = RectTileFlooderDepthFirst(this, seen, x, y)
|
||||
val sizeFirst = RectTileFlooderSizeFirst(this, seen, x, y)
|
||||
val xSpanDepth = depthFirst.maxs.x - depthFirst.mins.x
|
||||
val ySpanDepth = depthFirst.maxs.y - depthFirst.mins.y
|
||||
val xSpanSize = sizeFirst.maxs.x - sizeFirst.mins.x
|
||||
val ySpanSize = sizeFirst.maxs.y - sizeFirst.mins.y
|
||||
|
||||
val aabb: AABB
|
||||
|
||||
if (xSpanDepth * ySpanDepth > xSpanSize * ySpanSize) {
|
||||
depthFirst.markSeen()
|
||||
aabb = AABB(depthFirst.mins.toDoubleVector(), depthFirst.maxs.toDoubleVector() + Vector2d.POSITIVE_XY)
|
||||
} else {
|
||||
sizeFirst.markSeen()
|
||||
aabb = AABB(sizeFirst.mins.toDoubleVector(), sizeFirst.maxs.toDoubleVector() + Vector2d.POSITIVE_XY)
|
||||
}
|
||||
val cell = getCell(x, y)
|
||||
|
||||
/*if (!cell.foreground.material.collisionKind.isEmpty) {
|
||||
collisionChains.add(body.createFixture(FixtureDef(
|
||||
shape = PolygonShape().also { it.setAsBox(aabb.width / 2.0, aabb.height / 2.0, aabb.centre, 0.0) },
|
||||
shape = PolygonShape().also { it.setAsBox(0.5, 0.5, Vector2d(x + 0.5, y + 0.5), 0.0) },
|
||||
friction = 0.4,
|
||||
userData = cell,
|
||||
)))
|
||||
|
||||
collisionCache.add(aabb + xyAdd)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,177 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.world.phys
|
||||
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_BITS
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||
import ru.dbotthepony.kvector.arrays.Object2DArray
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
|
||||
class RectTileFlooderDepthFirst(
|
||||
private val tiles: ICellAccess,
|
||||
private val seen: BooleanArray,
|
||||
rootx: Int,
|
||||
rooty: Int
|
||||
) {
|
||||
val mins: Vector2i
|
||||
val maxs: Vector2i
|
||||
|
||||
private fun filled(x: Int, y: Int): Boolean {
|
||||
if (x < 0 || x > CHUNK_SIZE_FF) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (y < 0 || y > CHUNK_SIZE_FF) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !seen[x or (y shl CHUNK_SIZE_BITS)] && tiles.getCell(x, y)?.foreground?.material != null
|
||||
}
|
||||
|
||||
init {
|
||||
// expand wide
|
||||
var widthPositive = 1
|
||||
|
||||
while (true) {
|
||||
if (!filled(rootx + widthPositive, rooty)) {
|
||||
break
|
||||
}
|
||||
|
||||
widthPositive++
|
||||
}
|
||||
|
||||
var widthNegative = 1
|
||||
|
||||
while (true) {
|
||||
if (!filled(rootx - widthNegative, rooty)) {
|
||||
break
|
||||
}
|
||||
|
||||
widthNegative++
|
||||
}
|
||||
|
||||
// expand tall
|
||||
var heightPositive = 1
|
||||
|
||||
while (true) {
|
||||
if (!filled(rootx, rooty + heightPositive)) {
|
||||
break
|
||||
}
|
||||
|
||||
heightPositive++
|
||||
}
|
||||
|
||||
var heightNegative = 1
|
||||
|
||||
while (true) {
|
||||
if (!filled(rootx, rooty - heightNegative)) {
|
||||
break
|
||||
}
|
||||
|
||||
heightNegative++
|
||||
}
|
||||
|
||||
widthPositive -= 1
|
||||
widthNegative -= 1
|
||||
|
||||
heightNegative -= 1
|
||||
heightPositive -= 1
|
||||
|
||||
if (heightPositive + heightNegative > widthPositive + widthNegative) {
|
||||
// height is bigger
|
||||
// try to expand wide
|
||||
widthPositive = 0
|
||||
widthNegative = 0
|
||||
|
||||
while (true) {
|
||||
var escape = false
|
||||
|
||||
for (i in -heightNegative .. heightPositive) {
|
||||
if (!filled(rootx + widthPositive, rooty + i)) {
|
||||
escape = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (escape) {
|
||||
break
|
||||
}
|
||||
|
||||
widthPositive++
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var escape = false
|
||||
|
||||
for (i in -heightNegative .. heightPositive) {
|
||||
if (!filled(rootx - widthNegative, rooty + i)) {
|
||||
escape = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (escape) {
|
||||
break
|
||||
}
|
||||
|
||||
widthNegative++
|
||||
}
|
||||
|
||||
widthNegative -= 1
|
||||
widthPositive -= 1
|
||||
} else {
|
||||
// height is equal or lesser than width
|
||||
// expand high
|
||||
heightNegative = 0
|
||||
heightPositive = 0
|
||||
|
||||
while (true) {
|
||||
var escape = false
|
||||
|
||||
for (i in -widthNegative .. widthPositive) {
|
||||
if (!filled(rootx + i, rooty + heightPositive)) {
|
||||
escape = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (escape) {
|
||||
break
|
||||
}
|
||||
|
||||
heightPositive++
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var escape = false
|
||||
|
||||
for (i in -widthNegative .. widthPositive) {
|
||||
if (!filled(rootx + i, rooty - heightNegative)) {
|
||||
escape = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (escape) {
|
||||
break
|
||||
}
|
||||
|
||||
heightNegative++
|
||||
}
|
||||
|
||||
heightNegative -= 1
|
||||
heightPositive -= 1
|
||||
}
|
||||
|
||||
mins = Vector2i(rootx - widthNegative, rooty - heightNegative)
|
||||
maxs = Vector2i(rootx + widthPositive, rooty + heightPositive)
|
||||
}
|
||||
|
||||
fun markSeen() {
|
||||
for (x in mins.x .. maxs.x) {
|
||||
for (y in mins.y .. maxs.y) {
|
||||
seen[x or (y shl CHUNK_SIZE_BITS)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.world.phys
|
||||
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_BITS
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||
import ru.dbotthepony.kvector.arrays.Object2DArray
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
|
||||
class RectTileFlooderSizeFirst(
|
||||
private val tiles: ICellAccess,
|
||||
private val seen: BooleanArray,
|
||||
private val rootx: Int,
|
||||
private val rooty: Int
|
||||
) {
|
||||
val mins: Vector2i
|
||||
val maxs: Vector2i
|
||||
|
||||
private fun filled(x: Int, y: Int): Boolean {
|
||||
if (x < 0 || x > CHUNK_SIZE_FF) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (y < 0 || y > CHUNK_SIZE_FF) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !seen[x or (y shl CHUNK_SIZE_BITS)] && tiles.getCell(x, y)?.foreground?.material != null
|
||||
}
|
||||
|
||||
private var widthPositive = 0
|
||||
private var widthNegative = 0
|
||||
private var heightPositive = 0
|
||||
private var heightNegative = 0
|
||||
|
||||
private fun checkLeft(): Boolean {
|
||||
for (i in -heightNegative .. heightPositive) {
|
||||
if (!filled(rootx - widthNegative, rooty + i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun checkRight(): Boolean {
|
||||
for (i in -heightNegative .. heightPositive) {
|
||||
if (!filled(rootx + widthPositive, rooty + i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun checkUp(): Boolean {
|
||||
for (i in -widthNegative .. widthPositive) {
|
||||
if (!filled(rootx + i, rooty + heightPositive)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun checkDown(): Boolean {
|
||||
for (i in -widthNegative .. widthPositive) {
|
||||
if (!filled(rootx + i, rooty - heightNegative)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
init {
|
||||
var expanded = true
|
||||
var hitLeft = false
|
||||
var hitRight = false
|
||||
var hitUp = false
|
||||
var hitDown = false
|
||||
|
||||
while (expanded) {
|
||||
expanded = false
|
||||
|
||||
// expand left
|
||||
if (!hitLeft) {
|
||||
widthNegative++
|
||||
|
||||
if (!checkLeft()) {
|
||||
widthNegative--
|
||||
hitLeft = true
|
||||
} else {
|
||||
expanded = true
|
||||
}
|
||||
}
|
||||
|
||||
// expand up
|
||||
if (!hitUp) {
|
||||
heightPositive++
|
||||
|
||||
if (!checkUp()) {
|
||||
heightPositive--
|
||||
hitUp = true
|
||||
} else {
|
||||
expanded = true
|
||||
}
|
||||
}
|
||||
|
||||
// expand right
|
||||
if (!hitRight) {
|
||||
widthPositive++
|
||||
|
||||
if (!checkRight()) {
|
||||
widthPositive--
|
||||
hitRight = true
|
||||
} else {
|
||||
expanded = true
|
||||
}
|
||||
}
|
||||
|
||||
// expand down
|
||||
if (!hitDown) {
|
||||
heightNegative++
|
||||
|
||||
if (!checkDown()) {
|
||||
heightNegative--
|
||||
hitDown = true
|
||||
} else {
|
||||
expanded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mins = Vector2i(rootx - widthNegative, rooty - heightNegative)
|
||||
maxs = Vector2i(rootx + widthPositive, rooty + heightPositive)
|
||||
}
|
||||
|
||||
fun markSeen() {
|
||||
for (x in mins.x .. maxs.x) {
|
||||
for (y in mins.y .. maxs.y) {
|
||||
seen[x or (y shl CHUNK_SIZE_BITS)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user