Actual Light test
This commit is contained in:
parent
0f4b7ace07
commit
6397637538
@ -4,6 +4,8 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
|||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.longs.LongArraySet
|
import it.unimi.dsi.fastutil.longs.LongArraySet
|
||||||
import it.unimi.dsi.fastutil.objects.ReferenceArraySet
|
import it.unimi.dsi.fastutil.objects.ReferenceArraySet
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadVertexTransformer
|
||||||
import ru.dbotthepony.kstarbound.client.render.ConfiguredMesh
|
import ru.dbotthepony.kstarbound.client.render.ConfiguredMesh
|
||||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||||
import ru.dbotthepony.kstarbound.client.render.Mesh
|
import ru.dbotthepony.kstarbound.client.render.Mesh
|
||||||
@ -24,6 +26,7 @@ import ru.dbotthepony.kvector.util2d.AABB
|
|||||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
import ru.dbotthepony.kvector.vector.Vector2f
|
import ru.dbotthepony.kvector.vector.Vector2f
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ClientWorld(
|
class ClientWorld(
|
||||||
val client: StarboundClient,
|
val client: StarboundClient,
|
||||||
@ -261,20 +264,28 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val pos = client.screenToWorld(client.mouseCoordinatesF).toDoubleVector()
|
/*val pos = client.screenToWorld(client.mouseCoordinatesF).toDoubleVector()
|
||||||
|
|
||||||
/*layers.add(-999999) {
|
layers.add(-999999) {
|
||||||
val lightsize = 16
|
val lightsize = 16
|
||||||
|
|
||||||
val lightmap = floodLight(
|
val lightmap = floodLight(
|
||||||
Vector2i(pos.x.roundToInt(), pos.y.roundToInt()), lightsize
|
Vector2i(pos.x.roundToInt(), pos.y.roundToInt()), lightsize
|
||||||
)
|
)
|
||||||
|
|
||||||
client.gl.quadWireframe {
|
client.gl.quadColor {
|
||||||
for (column in 0 until lightmap.columns) {
|
for (column in 0 until lightmap.columns) {
|
||||||
for (row in 0 until lightmap.rows) {
|
for (row in 0 until lightmap.rows) {
|
||||||
if (lightmap[column, row] > 0) {
|
if (lightmap[column, row] > 0) {
|
||||||
it.quad(pos.x.roundToInt() + column.toFloat() - lightsize, pos.y.roundToInt() + row.toFloat() - lightsize, pos.x.roundToInt() + column + 1f - lightsize, pos.y.roundToInt() + row + 1f - lightsize)
|
val color = lightmap[column, row] / 16f
|
||||||
|
|
||||||
|
it.quad(
|
||||||
|
pos.x.roundToInt() + column.toFloat() - lightsize,
|
||||||
|
pos.y.roundToInt() + row.toFloat() - lightsize,
|
||||||
|
pos.x.roundToInt() + column + 1f - lightsize,
|
||||||
|
pos.y.roundToInt() + row + 1f - lightsize,
|
||||||
|
QuadTransformers.vec4(color, color, color, color),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,20 @@ import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
|||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.client.gl.BlendFunc
|
import ru.dbotthepony.kstarbound.client.gl.BlendFunc
|
||||||
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
|
||||||
import ru.dbotthepony.kstarbound.client.input.UserInput
|
import ru.dbotthepony.kstarbound.client.input.UserInput
|
||||||
import ru.dbotthepony.kstarbound.client.render.Camera
|
import ru.dbotthepony.kstarbound.client.render.Camera
|
||||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||||
import ru.dbotthepony.kstarbound.client.render.TextAlignY
|
import ru.dbotthepony.kstarbound.client.render.TextAlignY
|
||||||
import ru.dbotthepony.kstarbound.client.render.TileRenderers
|
import ru.dbotthepony.kstarbound.client.render.TileRenderers
|
||||||
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||||
|
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||||
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
||||||
import ru.dbotthepony.kstarbound.util.PausableTimeSource
|
import ru.dbotthepony.kstarbound.util.PausableTimeSource
|
||||||
import ru.dbotthepony.kstarbound.util.formatBytesShort
|
import ru.dbotthepony.kstarbound.util.formatBytesShort
|
||||||
|
import ru.dbotthepony.kstarbound.world.LightCalculator
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||||
import ru.dbotthepony.kvector.arrays.Matrix4f
|
import ru.dbotthepony.kvector.arrays.Matrix4f
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
@ -30,9 +36,9 @@ import ru.dbotthepony.kvector.vector.Vector3f
|
|||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.locks.LockSupport
|
import java.util.concurrent.locks.LockSupport
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class StarboundClient(val starbound: Starbound) : Closeable {
|
class StarboundClient(val starbound: Starbound) : Closeable {
|
||||||
val time = PausableTimeSource(JVMTimeSource.INSTANCE)
|
val time = PausableTimeSource(JVMTimeSource.INSTANCE)
|
||||||
@ -260,6 +266,46 @@ class StarboundClient(val starbound: Starbound) : Closeable {
|
|||||||
|
|
||||||
val settings = ClientSettings()
|
val settings = ClientSettings()
|
||||||
|
|
||||||
|
var viewportCellX = 0
|
||||||
|
private set
|
||||||
|
var viewportCellY = 0
|
||||||
|
private set
|
||||||
|
var viewportCellWidth = 0
|
||||||
|
private set
|
||||||
|
var viewportCellHeight = 0
|
||||||
|
private set
|
||||||
|
var viewportRectangle = AABB.rectangle(Vector2d.ZERO, 0.0, 0.0)
|
||||||
|
private set
|
||||||
|
|
||||||
|
val viewportCells: ICellAccess = object : ICellAccess {
|
||||||
|
override fun getCell(x: Int, y: Int): IChunkCell? {
|
||||||
|
return world?.getCell(x + viewportCellX, y + viewportCellY)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCellDirect(x: Int, y: Int): IChunkCell? {
|
||||||
|
return world?.getCellDirect(x + viewportCellX, y + viewportCellY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun updateViewportParams() {
|
||||||
|
viewportRectangle = AABB.rectangle(
|
||||||
|
camera.pos.toDoubleVector(),
|
||||||
|
viewportWidth / settings.zoom / PIXELS_IN_STARBOUND_UNIT,
|
||||||
|
viewportHeight / settings.zoom / PIXELS_IN_STARBOUND_UNIT)
|
||||||
|
|
||||||
|
viewportCellX = roundTowardsNegativeInfinity(viewportRectangle.mins.x) - 4
|
||||||
|
viewportCellY = roundTowardsNegativeInfinity(viewportRectangle.mins.y) - 4
|
||||||
|
viewportCellWidth = roundTowardsPositiveInfinity(viewportRectangle.width) + 8
|
||||||
|
viewportCellHeight = roundTowardsPositiveInfinity(viewportRectangle.height) + 8
|
||||||
|
|
||||||
|
if (viewportLighting.width != viewportCellWidth || viewportLighting.height != viewportCellHeight) {
|
||||||
|
viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val onDrawGUI = ArrayList<() -> Unit>()
|
private val onDrawGUI = ArrayList<() -> Unit>()
|
||||||
private val onPreDrawWorld = ArrayList<(LayeredRenderer) -> Unit>()
|
private val onPreDrawWorld = ArrayList<(LayeredRenderer) -> Unit>()
|
||||||
private val onPostDrawWorld = ArrayList<() -> Unit>()
|
private val onPostDrawWorld = ArrayList<() -> Unit>()
|
||||||
@ -305,6 +351,7 @@ class StarboundClient(val starbound: Starbound) : Closeable {
|
|||||||
val world = world
|
val world = world
|
||||||
|
|
||||||
if (world != null) {
|
if (world != null) {
|
||||||
|
updateViewportParams()
|
||||||
val layers = LayeredRenderer()
|
val layers = LayeredRenderer()
|
||||||
|
|
||||||
if (frameRenderTime != 0.0 && starbound.initialized)
|
if (frameRenderTime != 0.0 && starbound.initialized)
|
||||||
@ -330,13 +377,50 @@ class StarboundClient(val starbound: Starbound) : Closeable {
|
|||||||
|
|
||||||
world.addLayers(
|
world.addLayers(
|
||||||
layers = layers,
|
layers = layers,
|
||||||
size = AABB.rectangle(
|
size = viewportRectangle)
|
||||||
camera.pos.toDoubleVector(),
|
|
||||||
viewportWidth / settings.zoom / PIXELS_IN_STARBOUND_UNIT,
|
|
||||||
viewportHeight / settings.zoom / PIXELS_IN_STARBOUND_UNIT))
|
|
||||||
|
|
||||||
layers.render(gl.matrixStack)
|
layers.render(gl.matrixStack)
|
||||||
|
|
||||||
|
/*viewportLighting.clear()
|
||||||
|
|
||||||
|
val (x, y) = screenToWorld(mouseCoordinates)
|
||||||
|
//viewportLighting.addPointLight(x.roundToInt() - viewportCellX, y.roundToInt() - viewportCellY, 179f / 255f, 149f / 255f, 107f / 255f)
|
||||||
|
val ix = x.roundToInt()
|
||||||
|
val iy = y.roundToInt()
|
||||||
|
|
||||||
|
viewportLighting.addPointLight(ix - viewportCellX, iy - viewportCellY, 1f, 1f, 1f)
|
||||||
|
viewportLighting.addPointLight(ix - viewportCellX - 16, iy - viewportCellY - 4, 1f, 1f, 1f)
|
||||||
|
viewportLighting.addPointLight(ix - viewportCellX + 17, iy - viewportCellY - 20, 1f, 1f, 1f)
|
||||||
|
|
||||||
|
viewportLighting.addPointLight(ix - viewportCellX + 19, iy - viewportCellY - 35, 1f, 1f, 1f)
|
||||||
|
viewportLighting.addPointLight(ix - viewportCellX + 21, iy - viewportCellY - 39, 1f, 1f, 1f)
|
||||||
|
viewportLighting.addPointLight(ix - viewportCellX + 24, iy - viewportCellY - 41, 1f, 1f, 1f)
|
||||||
|
|
||||||
|
viewportLighting.addPointLight(ix - viewportCellX - 39, iy - viewportCellY - 35, 1f, 1f, 1f)
|
||||||
|
viewportLighting.addPointLight(ix - viewportCellX - 41, iy - viewportCellY - 39, 1f, 1f, 1f)
|
||||||
|
viewportLighting.addPointLight(ix - viewportCellX - 44, iy - viewportCellY - 41, 1f, 1f, 1f)
|
||||||
|
|
||||||
|
viewportLighting.multithreaded = true
|
||||||
|
viewportLighting.calculate()
|
||||||
|
|
||||||
|
gl.quadColor {
|
||||||
|
for (x in 0 until viewportLighting.width) {
|
||||||
|
for (y in 0 until viewportLighting.height) {
|
||||||
|
val cell = viewportLighting[x, y]
|
||||||
|
|
||||||
|
if (cell.alpha > 0f) {
|
||||||
|
it.quad(
|
||||||
|
(viewportCellX + x).toFloat(),
|
||||||
|
(viewportCellY + y).toFloat(),
|
||||||
|
(viewportCellX + x + 1.0).toFloat(),
|
||||||
|
(viewportCellY + y + 1.0).toFloat(),
|
||||||
|
QuadTransformers.vec4(cell.red, cell.green, cell.blue, cell.alpha),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
world.physics.debugDraw()
|
world.physics.debugDraw()
|
||||||
|
|
||||||
for (lambda in onPostDrawWorld) {
|
for (lambda in onPostDrawWorld) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.gl.vertex
|
package ru.dbotthepony.kstarbound.client.gl.vertex
|
||||||
|
|
||||||
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
|
|
||||||
typealias QuadVertexTransformer = (VertexBuilder, Int) -> VertexBuilder
|
typealias QuadVertexTransformer = (VertexBuilder, Int) -> VertexBuilder
|
||||||
|
|
||||||
val EMPTY_VERTEX_TRANSFORM: QuadVertexTransformer = { it, _ -> it }
|
val EMPTY_VERTEX_TRANSFORM: QuadVertexTransformer = { it, _ -> it }
|
||||||
@ -50,16 +52,10 @@ object QuadTransformers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uv(lambda: QuadVertexTransformer): QuadVertexTransformer {
|
fun vec4(x: Float, y: Float, z: Float, w: Float, after: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): QuadVertexTransformer {
|
||||||
return transformer@{ it, index ->
|
return transformer@{ it, index ->
|
||||||
when (index) {
|
it.pushVec4f(x, y, z, w)
|
||||||
0 -> it.pushVec2f(0f, 0f)
|
return@transformer after(it, index)
|
||||||
1 -> it.pushVec2f(1f, 0f)
|
|
||||||
2 -> it.pushVec2f(0f, 1f)
|
|
||||||
3 -> it.pushVec2f(1f, 1f)
|
|
||||||
}
|
|
||||||
|
|
||||||
return@transformer lambda(it, index)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,11 +37,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
var changeset = 0
|
var changeset = 0
|
||||||
private set(value) {
|
private set(value) {
|
||||||
field = value
|
field = value
|
||||||
|
promote()
|
||||||
if (isEmpty) {
|
|
||||||
isEmpty = false
|
|
||||||
world.chunkMap.promote(this as This)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var tileChangeset = 0
|
var tileChangeset = 0
|
||||||
@ -60,21 +56,32 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
|
|
||||||
private var isEmpty = true
|
private var isEmpty = true
|
||||||
|
|
||||||
protected val cells by lazy {
|
fun promote() {
|
||||||
|
if (isEmpty) {
|
||||||
|
isEmpty = false
|
||||||
|
world.chunkMap.promote(this as This)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val cells = lazy {
|
||||||
Object2DArray.nulls<Cell>(CHUNK_SIZE, CHUNK_SIZE)
|
Object2DArray.nulls<Cell>(CHUNK_SIZE, CHUNK_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
override fun getCell(x: Int, y: Int): IChunkCell {
|
||||||
var get = cells[x, y]
|
var get = cells.value[x, y]
|
||||||
|
|
||||||
if (get == null) {
|
if (get == null) {
|
||||||
get = Cell(x, y)
|
get = Cell(x, y)
|
||||||
cells[x, y] = get
|
cells.value[x, y] = get
|
||||||
}
|
}
|
||||||
|
|
||||||
return get
|
return get
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCellDirect(x: Int, y: Int): IChunkCell {
|
||||||
|
return getCell(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
// local cells' tile access
|
// local cells' tile access
|
||||||
val localBackgroundView = TileView.Background(this)
|
val localBackgroundView = TileView.Background(this)
|
||||||
val localForegroundView = TileView.Foreground(this)
|
val localForegroundView = TileView.Foreground(this)
|
||||||
|
@ -0,0 +1,602 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntArraySet
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||||
|
import ru.dbotthepony.kvector.api.IStruct4f
|
||||||
|
import ru.dbotthepony.kvector.arrays.Object2DArray
|
||||||
|
import ru.dbotthepony.kvector.util.linearInterpolation
|
||||||
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
import java.util.concurrent.locks.LockSupport
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
// this implementation quite closely resembles original code, mostly
|
||||||
|
// because i found no other solution for light spreading
|
||||||
|
// however, code flow is HEAVILY altered, with many additions and changes
|
||||||
|
class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) {
|
||||||
|
enum class Quality(val secondDiagonal: Boolean, val extraCell: Boolean) {
|
||||||
|
HIGH(true, false),
|
||||||
|
MEDIUM(false, true),
|
||||||
|
LOW(false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICell : IStruct4f {
|
||||||
|
val red: Float
|
||||||
|
val green: Float
|
||||||
|
val blue: Float
|
||||||
|
|
||||||
|
val alpha: Float get() {
|
||||||
|
return (red * red + green * green + blue * blue).coerceIn(0f, 1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component1(): Float {
|
||||||
|
return red
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component2(): Float {
|
||||||
|
return green
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component3(): Float {
|
||||||
|
return blue
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component4(): Float {
|
||||||
|
return alpha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun interface Getter {
|
||||||
|
operator fun get(x: Int, y: Int): Grid.LightCell
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class Grid {
|
||||||
|
inner class LightCell(val x: Int, val y: Int) : ICell {
|
||||||
|
val actualDropoff by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
val parent = this@LightCalculator.parent.getCell(x, y)
|
||||||
|
val lightBlockStrength: Float
|
||||||
|
|
||||||
|
if (parent?.foreground?.material != null) {
|
||||||
|
lightBlockStrength = 1f
|
||||||
|
} else {
|
||||||
|
lightBlockStrength = 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
linearInterpolation(lightBlockStrength, invMaxAirSpread, invMaxObstacleSpread)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var empty = true
|
||||||
|
override var red: Float = 0f
|
||||||
|
override var green: Float = 0f
|
||||||
|
override var blue: Float = 0f
|
||||||
|
|
||||||
|
fun spreadInto(target: LightCell, drop: Float) {
|
||||||
|
val max = red.coerceAtLeast(green).coerceAtLeast(blue)
|
||||||
|
if (max <= 0f) return
|
||||||
|
val newDrop = 1f - actualDropoff / max * drop
|
||||||
|
|
||||||
|
target.red = target.red.coerceAtLeast(red * newDrop)
|
||||||
|
target.green = target.green.coerceAtLeast(green * newDrop)
|
||||||
|
target.blue = target.blue.coerceAtLeast(blue * newDrop)
|
||||||
|
|
||||||
|
if (target.empty && (target.red >= epsilon || target.blue >= epsilon || target.green >= epsilon)) {
|
||||||
|
minX = minX.coerceAtMost(target.x - 1)
|
||||||
|
minY = minY.coerceAtMost(target.y - 1)
|
||||||
|
maxX = maxX.coerceAtLeast(target.x + 1)
|
||||||
|
maxY = maxY.coerceAtLeast(target.y + 1)
|
||||||
|
target.empty = false
|
||||||
|
clampRect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var minX = width
|
||||||
|
var maxX = 0
|
||||||
|
var minY = height
|
||||||
|
var maxY = 0
|
||||||
|
|
||||||
|
fun clampRect() {
|
||||||
|
minX = minX.coerceIn(1, width - 2)
|
||||||
|
maxX = maxX.coerceIn(1, width - 2)
|
||||||
|
minY = minY.coerceIn(1, height - 2)
|
||||||
|
maxY = maxY.coerceIn(1, height - 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
clampRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mem by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
Object2DArray.nulls<LightCell>(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(x: Int, y: Int): LightCell {
|
||||||
|
var get = mem[x, y]
|
||||||
|
|
||||||
|
if (get == null) {
|
||||||
|
get = LightCell(x, y)
|
||||||
|
mem[x, y] = get
|
||||||
|
}
|
||||||
|
|
||||||
|
return get
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get0(x: Int, y: Int) = mem[x, y]
|
||||||
|
|
||||||
|
fun safeGet(x: Int, y: Int): LightCell? {
|
||||||
|
if (x in 0 until width && y in 0 until height) {
|
||||||
|
return this[x, y]
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
private val mem = Int2ObjectOpenHashMap<LightCell>(2048)
|
||||||
|
|
||||||
|
operator fun get(x: Int, y: Int): LightCell {
|
||||||
|
var get = mem[x shl 16 or y]
|
||||||
|
|
||||||
|
if (get == null) {
|
||||||
|
get = LightCell(x, y)
|
||||||
|
mem[x shl 16 or y] = get
|
||||||
|
}
|
||||||
|
|
||||||
|
return get
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get0(x: Int, y: Int) = mem[x shl 16 or y]
|
||||||
|
|
||||||
|
fun safeGet(x: Int, y: Int): LightCell? {
|
||||||
|
if (x in 0 until width && y in 0 until height) {
|
||||||
|
return this[x, y]
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
private inner class Copy : Getter {
|
||||||
|
private val minX = this@Grid.minX
|
||||||
|
private val maxX = this@Grid.maxX
|
||||||
|
private val minY = this@Grid.minY
|
||||||
|
private val maxY = this@Grid.maxY
|
||||||
|
|
||||||
|
private val mem = Object2DArray(maxX - minX + 3, maxY - minY + 3) { a, b -> this@Grid[a + minX - 1, b + minY - 1] }
|
||||||
|
|
||||||
|
override fun get(x: Int, y: Int): LightCell {
|
||||||
|
return mem[x - minX + 1, y - minY + 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val passthrough = Getter { x, y -> this@Grid[x, y] }
|
||||||
|
|
||||||
|
fun calculateSpread() {
|
||||||
|
if (minX > maxX || minY > maxY) return
|
||||||
|
|
||||||
|
// spread light in several passes
|
||||||
|
var repeats = passes
|
||||||
|
|
||||||
|
var copy: Getter? = null
|
||||||
|
|
||||||
|
while (repeats-- >= 0) {
|
||||||
|
val minX = minX
|
||||||
|
val maxX = maxX
|
||||||
|
val minY = minY
|
||||||
|
val maxY = maxY
|
||||||
|
|
||||||
|
if (copy == null) {
|
||||||
|
copy = if (maxX - minX >= width / 2 || maxY - minY >= height / 2) {
|
||||||
|
passthrough
|
||||||
|
} else {
|
||||||
|
Copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bottom to top
|
||||||
|
for (y in minY .. maxY) {
|
||||||
|
// left to right
|
||||||
|
for (x in minX .. maxX) {
|
||||||
|
val current = copy[x, y]
|
||||||
|
|
||||||
|
current.spreadInto(copy[x, y + 1], 1f)
|
||||||
|
current.spreadInto(copy[x + 1, y], 1f)
|
||||||
|
current.spreadInto(copy[x + 1, y + 1], 1.4142135f)
|
||||||
|
|
||||||
|
// original code performs this spread to camouflage prism shape of light spreading
|
||||||
|
// we instead gonna do light pass on different diagonal
|
||||||
|
if (quality.extraCell)
|
||||||
|
current.spreadInto(copy[x + 1, y - 1], 1.4142135f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// right to left
|
||||||
|
if (quality.secondDiagonal) {
|
||||||
|
for (x in maxX downTo minX) {
|
||||||
|
val current = copy[x, y]
|
||||||
|
|
||||||
|
current.spreadInto(copy[x, y + 1], 1f)
|
||||||
|
current.spreadInto(copy[x - 1, y], 1f)
|
||||||
|
current.spreadInto(copy[x - 1, y + 1], 1.4142135f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// top to bottom
|
||||||
|
for (y in maxY downTo minY) {
|
||||||
|
// right to left
|
||||||
|
for (x in maxX downTo minX) {
|
||||||
|
val current = this[x, y]
|
||||||
|
|
||||||
|
current.spreadInto(this[x, y - 1], 1f)
|
||||||
|
current.spreadInto(this[x - 1, y], 1f)
|
||||||
|
current.spreadInto(this[x - 1, y - 1], 1.4142135f)
|
||||||
|
|
||||||
|
// original code performs this spread to camouflage prism shape of light spreading
|
||||||
|
// we instead gonna do light pass on different diagonal
|
||||||
|
if (quality.extraCell)
|
||||||
|
current.spreadInto(this[x - 1, y + 1], 1.4142135f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// left to right
|
||||||
|
if (quality.secondDiagonal) {
|
||||||
|
for (x in minX .. maxX) {
|
||||||
|
val current = this[x, y]
|
||||||
|
|
||||||
|
current.spreadInto(this[x, y - 1], 1f)
|
||||||
|
current.spreadInto(this[x + 1, y], 1f)
|
||||||
|
current.spreadInto(this[x + 1, y - 1], 1.4142135f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if our boundaries have updated, re-spread light
|
||||||
|
if (
|
||||||
|
minX != this.minX ||
|
||||||
|
maxX != this.maxX ||
|
||||||
|
minY != this.minY ||
|
||||||
|
maxY != this.maxY
|
||||||
|
) {
|
||||||
|
repeats++
|
||||||
|
|
||||||
|
copy = if (this.maxX - this.minX >= width / 2 || this.maxY - this.minY >= height / 2) {
|
||||||
|
passthrough
|
||||||
|
} else {
|
||||||
|
Copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var quality = Quality.HIGH
|
||||||
|
|
||||||
|
// light values below this are considered too small to bother with
|
||||||
|
var epsilon = 0.01f
|
||||||
|
|
||||||
|
// values below are specified by lighting.config
|
||||||
|
|
||||||
|
// Number of ambient spread passes. Needs to be at least spreadMaxAir /
|
||||||
|
// spreadMaxObstacle big, but sometimes it can stand to be a bit less and
|
||||||
|
// you won't notice.
|
||||||
|
var passes = 3
|
||||||
|
|
||||||
|
// Maximum distance through empty space that 100% ambient light can pass through
|
||||||
|
var maxAirSpread = 32f
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
invMaxAirSpread = 1f / field
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum distance through rock that 100% ambient light can pass through
|
||||||
|
var maxObstacleSpread = 8f
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
invMaxObstacleSpread = 1f / field
|
||||||
|
}
|
||||||
|
|
||||||
|
private var invMaxAirSpread = 1f / maxAirSpread
|
||||||
|
private var invMaxObstacleSpread = 1f / maxObstacleSpread
|
||||||
|
|
||||||
|
// Maximum distance through emtpy space that 100% point light can pass through
|
||||||
|
var pointMaxAir = 48f
|
||||||
|
// Maximum distance through rock that 100% point light can pass through
|
||||||
|
var pointMaxObstacle = 9f
|
||||||
|
|
||||||
|
private data class Cell(override var red: Float = 0f, override var green: Float = 0f, override var blue: Float = 0f) : ICell
|
||||||
|
|
||||||
|
private var mainGrid = lazy {
|
||||||
|
Object2DArray.nulls<Cell>(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
private object Empty : ICell {
|
||||||
|
override val red: Float = 0f
|
||||||
|
override val green: Float = 0f
|
||||||
|
override val blue: Float = 0f
|
||||||
|
override val alpha: Float = 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PointLight(val x: Int, val y: Int, var red: Float, var green: Float, var blue: Float) {
|
||||||
|
var assignedTo: TaskCluster? = null
|
||||||
|
|
||||||
|
fun error(to: TaskCluster): Double {
|
||||||
|
val dx = x - to.x
|
||||||
|
val dy = y - to.y
|
||||||
|
return dx * dx + dy * dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val pointLights = ArrayList<PointLight>()
|
||||||
|
|
||||||
|
fun addPointLight(x: Int, y: Int, red: Float, green: Float, blue: Float) {
|
||||||
|
if (x !in 0 until width || y !in 0 until height) return
|
||||||
|
pointLights.add(PointLight(x, y, red, green, blue))
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(x: Int, y: Int): ICell {
|
||||||
|
if (!mainGrid.isInitialized()) return Empty
|
||||||
|
return mainGrid.value[x, y] ?: Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
var multithreaded = false
|
||||||
|
|
||||||
|
fun calculate() {
|
||||||
|
if (pointLights.isEmpty()) return
|
||||||
|
|
||||||
|
// perform multithreaded calculation only when it makes sense
|
||||||
|
if (multithreaded && pointLights.size > 1) {
|
||||||
|
val mainGrid = mainGrid.value
|
||||||
|
val thread = Thread.currentThread()
|
||||||
|
|
||||||
|
// calculate k-means clusters of point lights
|
||||||
|
// to effectively utilize CPU cores
|
||||||
|
val clusterCount = threads.size.coerceAtMost(pointLights.size)
|
||||||
|
val clusters = ArrayList<TaskCluster>(clusterCount)
|
||||||
|
val startingPoints = IntArraySet()
|
||||||
|
val rand = Random(System.nanoTime())
|
||||||
|
|
||||||
|
while (startingPoints.size < clusterCount) {
|
||||||
|
//startingPoints.add(rand.nextInt(0, pointLights.size))
|
||||||
|
startingPoints.add(startingPoints.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index in startingPoints.intIterator()) {
|
||||||
|
clusters.add(TaskCluster(pointLights[index].x.toDouble(), pointLights[index].y.toDouble(), this, thread))
|
||||||
|
}
|
||||||
|
|
||||||
|
var converged = false
|
||||||
|
|
||||||
|
while (!converged) {
|
||||||
|
converged = true
|
||||||
|
|
||||||
|
// assign
|
||||||
|
for (light in pointLights) {
|
||||||
|
val oldCluster = light.assignedTo
|
||||||
|
|
||||||
|
// do selection sort since it is faster here
|
||||||
|
for (cluster in clusters) {
|
||||||
|
if (cluster === light.assignedTo) continue
|
||||||
|
val old = light.assignedTo?.let { light.error(it) } ?: Double.MAX_VALUE
|
||||||
|
val new = light.error(cluster)
|
||||||
|
|
||||||
|
if (new < old) {
|
||||||
|
light.assignedTo = cluster
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldCluster != light.assignedTo) {
|
||||||
|
oldCluster?.lights?.remove(light)
|
||||||
|
light.assignedTo!!.lights.add(light)
|
||||||
|
converged = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update center of mass
|
||||||
|
for (cluster in clusters) {
|
||||||
|
cluster.updateCenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
// we settled on clusters
|
||||||
|
// check their centres of mass, and probably
|
||||||
|
// merge clusters which are too close to each other,
|
||||||
|
// to avoid excess work
|
||||||
|
// if we merge something, re-run k-clusters algorithm
|
||||||
|
if (converged) {
|
||||||
|
for (cluster1 in clusters) {
|
||||||
|
for (cluster2 in clusters) {
|
||||||
|
if (cluster1 === cluster2) continue
|
||||||
|
// don't create big clusters
|
||||||
|
if (cluster1.lights.size >= 4 && cluster2.lights.size >= 4) continue
|
||||||
|
|
||||||
|
val dx = cluster1.x - cluster2.x
|
||||||
|
val dy = cluster1.y - cluster2.y
|
||||||
|
|
||||||
|
val distance = dx * dx + dy * dy
|
||||||
|
|
||||||
|
if (distance <= 64.0) {
|
||||||
|
cluster1.lights.addAll(cluster2.lights)
|
||||||
|
clusters.remove(cluster2)
|
||||||
|
converged = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!converged) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((i, cluster) in clusters.withIndex()) {
|
||||||
|
val (r, g, b) = clusterColors[i]
|
||||||
|
|
||||||
|
for (light in cluster.lights) {
|
||||||
|
light.red = r
|
||||||
|
light.green = g
|
||||||
|
light.blue = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.addAll(clusters)
|
||||||
|
wakeup()
|
||||||
|
|
||||||
|
while (clusters.isNotEmpty()) {
|
||||||
|
clusters.removeIf {
|
||||||
|
val grid = it.grid
|
||||||
|
|
||||||
|
if (grid != null) {
|
||||||
|
for (x in grid.minX - 1 .. grid.maxX) {
|
||||||
|
for (y in grid.minY - 1 .. grid.maxY) {
|
||||||
|
val a = grid.get0(x, y) ?: continue
|
||||||
|
var b = mainGrid[x, y]
|
||||||
|
|
||||||
|
if (b == null) {
|
||||||
|
b = Cell()
|
||||||
|
mainGrid[x, y] = b
|
||||||
|
}
|
||||||
|
|
||||||
|
b.red = a.red.coerceAtLeast(b.red)
|
||||||
|
b.green = a.green.coerceAtLeast(b.green)
|
||||||
|
b.blue = a.blue.coerceAtLeast(b.blue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grid != null
|
||||||
|
}
|
||||||
|
|
||||||
|
LockSupport.parkNanos(500_000)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val grid = Grid()
|
||||||
|
val mainGrid = mainGrid.value
|
||||||
|
|
||||||
|
for (light in pointLights) {
|
||||||
|
val cell = grid.safeGet(light.x, light.y) ?: continue
|
||||||
|
val speculatedSpread = (maxAirSpread * light.red.coerceAtLeast(light.green).coerceAtLeast(light.blue)).roundToInt()
|
||||||
|
|
||||||
|
cell.red = light.red
|
||||||
|
cell.green = light.green
|
||||||
|
cell.blue = light.blue
|
||||||
|
|
||||||
|
grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread)
|
||||||
|
grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread)
|
||||||
|
|
||||||
|
grid.maxX = grid.maxX.coerceAtLeast(light.x + speculatedSpread)
|
||||||
|
grid.maxY = grid.maxY.coerceAtLeast(light.y + speculatedSpread)
|
||||||
|
|
||||||
|
grid.clampRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.calculateSpread()
|
||||||
|
|
||||||
|
for (x in grid.minX - 1 .. grid.maxX) {
|
||||||
|
for (y in grid.minY - 1 .. grid.maxY) {
|
||||||
|
val a = grid.get0(x, y) ?: continue
|
||||||
|
|
||||||
|
mainGrid[x, y] = Cell(
|
||||||
|
a.red,
|
||||||
|
a.green,
|
||||||
|
a.blue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
pointLights.clear()
|
||||||
|
mainGrid = lazy { Object2DArray.nulls(width, height) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TaskCluster(
|
||||||
|
var x: Double,
|
||||||
|
var y: Double,
|
||||||
|
val parent: LightCalculator,
|
||||||
|
val parentThread: Thread
|
||||||
|
) {
|
||||||
|
val lights = ArrayList<PointLight>()
|
||||||
|
|
||||||
|
fun updateCenter() {
|
||||||
|
if (lights.isEmpty()) return
|
||||||
|
|
||||||
|
x = lights.first().x.toDouble()
|
||||||
|
y = lights.first().y.toDouble()
|
||||||
|
|
||||||
|
for (i in 1 until lights.size) {
|
||||||
|
x += lights[i].x.toDouble()
|
||||||
|
y += lights[i].y.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
x /= lights.size
|
||||||
|
y /= lights.size
|
||||||
|
}
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
var grid: Grid? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun execute() {
|
||||||
|
if (grid != null) return
|
||||||
|
|
||||||
|
val grid = parent.Grid()
|
||||||
|
|
||||||
|
for (light in lights) {
|
||||||
|
val speculatedSpread = (parent.maxAirSpread * light.red.coerceAtLeast(light.green).coerceAtLeast(light.blue)).roundToInt()
|
||||||
|
val cell = grid.safeGet(light.x, light.y) ?: continue
|
||||||
|
|
||||||
|
cell.red = light.red
|
||||||
|
cell.green = light.green
|
||||||
|
cell.blue = light.blue
|
||||||
|
|
||||||
|
grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread)
|
||||||
|
grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread)
|
||||||
|
|
||||||
|
grid.maxX = grid.maxX.coerceAtLeast(light.x + speculatedSpread)
|
||||||
|
grid.maxY = grid.maxY.coerceAtLeast(light.y + speculatedSpread)
|
||||||
|
|
||||||
|
grid.clampRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.calculateSpread()
|
||||||
|
this.grid = grid
|
||||||
|
LockSupport.unpark(parentThread)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val tasks = ConcurrentLinkedQueue<TaskCluster>()
|
||||||
|
private val threads = ArrayList<Thread>()
|
||||||
|
|
||||||
|
private val clusterColors = ArrayList<RGBAColor>()
|
||||||
|
|
||||||
|
private fun wakeup() {
|
||||||
|
for (thread in threads) {
|
||||||
|
LockSupport.unpark(thread)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun thread() {
|
||||||
|
while (true) {
|
||||||
|
val next = tasks.poll()
|
||||||
|
|
||||||
|
if (next == null) {
|
||||||
|
LockSupport.park()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
next.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
for (i in 0 until Runtime.getRuntime().availableProcessors()) {
|
||||||
|
Thread(::thread, "Starbound Lighting Thread $i").also { threads.add(it); it.isDaemon = true }.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
val rand = Random(System.nanoTime())
|
||||||
|
|
||||||
|
for (i in threads.indices) {
|
||||||
|
clusterColors.add(RGBAColor(rand.nextFloat() * 0.5f + 0.5f, rand.nextFloat() * 0.5f + 0.5f, rand.nextFloat() * 0.5f + 0.5f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -418,107 +418,4 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Свет
|
|
||||||
|
|
||||||
private fun floodLightInto(
|
|
||||||
lightmap: Int2DArray,
|
|
||||||
thisIntensity: Int,
|
|
||||||
lightBlockerStrength: Int,
|
|
||||||
posX: Int,
|
|
||||||
worldPosX: Int,
|
|
||||||
posY: Int,
|
|
||||||
worldPosY: Int,
|
|
||||||
): Int {
|
|
||||||
if (lightmap[posX, posY] >= thisIntensity) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
val tile = getCell(worldPosX, worldPosY)
|
|
||||||
|
|
||||||
var newIntensity: Int
|
|
||||||
|
|
||||||
if (tile?.foreground?.material?.renderParameters?.lightTransparent == false) {
|
|
||||||
newIntensity = thisIntensity - lightBlockerStrength
|
|
||||||
} else {
|
|
||||||
newIntensity = thisIntensity - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tile?.foreground?.material != null)
|
|
||||||
newIntensity = 0
|
|
||||||
|
|
||||||
lightmap[posX, posY] = newIntensity.coerceAtLeast(0)
|
|
||||||
|
|
||||||
if (newIntensity > 1) {
|
|
||||||
var c = 1
|
|
||||||
|
|
||||||
c += floodLightInto(
|
|
||||||
lightmap, newIntensity, lightBlockerStrength,
|
|
||||||
posX + 1,
|
|
||||||
worldPosX + 1,
|
|
||||||
posY,
|
|
||||||
worldPosY,
|
|
||||||
)
|
|
||||||
|
|
||||||
c += floodLightInto(
|
|
||||||
lightmap, newIntensity, lightBlockerStrength,
|
|
||||||
posX - 1,
|
|
||||||
worldPosX - 1,
|
|
||||||
posY,
|
|
||||||
worldPosY,
|
|
||||||
)
|
|
||||||
|
|
||||||
c += floodLightInto(
|
|
||||||
lightmap, newIntensity, lightBlockerStrength,
|
|
||||||
posX,
|
|
||||||
worldPosX,
|
|
||||||
posY + 1,
|
|
||||||
worldPosY + 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
c += floodLightInto(
|
|
||||||
lightmap, newIntensity, lightBlockerStrength,
|
|
||||||
posX,
|
|
||||||
worldPosX,
|
|
||||||
posY - 1,
|
|
||||||
worldPosY - 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Просчитывает распространение света во все стороны на указанной позиции (в тайлах)
|
|
||||||
*
|
|
||||||
* [lightIntensity] - максимальное расстояние, которое может пройти свет из точки своего появления.
|
|
||||||
* Имеет жёсткое ограничение в [CHUNK_SIZE].
|
|
||||||
*
|
|
||||||
* [lightBlockerStrength] - какова стоимость "пробития" тайла насквозь, который не пропускает свет
|
|
||||||
*/
|
|
||||||
fun floodLight(
|
|
||||||
lightPosition: Vector2i,
|
|
||||||
lightIntensity: Int,
|
|
||||||
lightBlockerStrength: Int = 4,
|
|
||||||
): Int2DArray {
|
|
||||||
require(lightIntensity >= 1) { "Invalid light intensity $lightIntensity" }
|
|
||||||
require(lightBlockerStrength >= 1) { "Invalid light blocker strength $lightBlockerStrength" }
|
|
||||||
require(lightIntensity <= CHUNK_SIZE) { "Too intensive light! $lightIntensity" }
|
|
||||||
|
|
||||||
val lightmap = Int2DArray.allocate(lightIntensity * 2 + 1, lightIntensity * 2 + 1)
|
|
||||||
|
|
||||||
floodLightInto(
|
|
||||||
lightmap,
|
|
||||||
lightIntensity,
|
|
||||||
lightBlockerStrength,
|
|
||||||
lightIntensity,
|
|
||||||
lightPosition.x,
|
|
||||||
lightIntensity,
|
|
||||||
lightPosition.y,
|
|
||||||
)
|
|
||||||
|
|
||||||
return lightmap
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,7 @@ interface ICellAccess {
|
|||||||
* non-null - valid cell and not wrapped around
|
* non-null - valid cell and not wrapped around
|
||||||
* null - invalid cell (outside world bounds)
|
* null - invalid cell (outside world bounds)
|
||||||
*/
|
*/
|
||||||
fun getCellDirect(x: Int, y: Int): IChunkCell? {
|
fun getCellDirect(x: Int, y: Int): IChunkCell?
|
||||||
val cell = getCell(x, y)
|
|
||||||
|
|
||||||
if (cell == null || cell.x != x || cell.y != y)
|
|
||||||
return null
|
|
||||||
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCellDirect(pos: IStruct2i) = getCellDirect(pos.component1(), pos.component2())
|
fun getCellDirect(pos: IStruct2i) = getCellDirect(pos.component1(), pos.component2())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,13 +45,17 @@ interface ICellAccess {
|
|||||||
fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y)
|
fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
class OffsetCellAccess(private val parent: ICellAccess, private val x: Int, private val y: Int) : ICellAccess {
|
class OffsetCellAccess(private val parent: ICellAccess, var x: Int, var y: Int) : ICellAccess {
|
||||||
constructor(parent: ICellAccess, offset: IStruct2i) : this(parent, offset.component1(), offset.component2())
|
constructor(parent: ICellAccess, offset: IStruct2i) : this(parent, offset.component1(), offset.component2())
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): IChunkCell? {
|
override fun getCell(x: Int, y: Int): IChunkCell? {
|
||||||
return parent.getCell(x + this.x, y + this.y)
|
return parent.getCell(x + this.x, y + this.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCellDirect(x: Int, y: Int): IChunkCell? {
|
||||||
|
return parent.getCellDirect(x + this.x, y + this.y)
|
||||||
|
}
|
||||||
|
|
||||||
override fun randomLongFor(x: Int, y: Int) = parent.randomLongFor(x + this.x, y + this.y)
|
override fun randomLongFor(x: Int, y: Int) = parent.randomLongFor(x + this.x, y + this.y)
|
||||||
override fun randomDoubleFor(x: Int, y: Int) = parent.randomDoubleFor(x + this.x, y + this.y)
|
override fun randomDoubleFor(x: Int, y: Int) = parent.randomDoubleFor(x + this.x, y + this.y)
|
||||||
override fun randomLongFor(pos: Vector2i) = parent.randomLongFor(pos.x + this.x, pos.y + this.y)
|
override fun randomLongFor(pos: Vector2i) = parent.randomLongFor(pos.x + this.x, pos.y + this.y)
|
||||||
|
Loading…
Reference in New Issue
Block a user