parent
86a44581a5
commit
7fcad1352e
@ -119,7 +119,7 @@ class StarboundClient : Closeable {
|
|||||||
var viewportTopRight = Vector2d()
|
var viewportTopRight = Vector2d()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var fullbright = false
|
var fullbright = true
|
||||||
|
|
||||||
var clientTerminated = false
|
var clientTerminated = false
|
||||||
private set
|
private set
|
||||||
|
@ -8,6 +8,7 @@ import ru.dbotthepony.kvector.arrays.Object2DArray
|
|||||||
import ru.dbotthepony.kvector.util.linearInterpolation
|
import ru.dbotthepony.kvector.util.linearInterpolation
|
||||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.concurrent.Callable
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
import java.util.concurrent.ForkJoinPool
|
import java.util.concurrent.ForkJoinPool
|
||||||
@ -15,6 +16,7 @@ import java.util.concurrent.Future
|
|||||||
import java.util.concurrent.locks.LockSupport
|
import java.util.concurrent.locks.LockSupport
|
||||||
import java.util.function.Supplier
|
import java.util.function.Supplier
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
// this implementation quite closely resembles original code, mostly
|
// this implementation quite closely resembles original code, mostly
|
||||||
// because i found no other solution for light spreading
|
// because i found no other solution for light spreading
|
||||||
@ -52,113 +54,58 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
private fun interface Getter {
|
||||||
private const val STRIDE = 4 * 3 /* red + green + blue */ +
|
operator fun get(x: Int, y: Int): Grid.LightCell
|
||||||
1 /* isEmpty */ + 4 /* actualDropoff */
|
|
||||||
}
|
|
||||||
|
|
||||||
private val memoryPool = ConcurrentLinkedQueue<ByteBuffer>()
|
|
||||||
|
|
||||||
private fun acquire(): ByteBuffer {
|
|
||||||
// using direct buffer because memset is very likely to be more efficient than Arrays.fill
|
|
||||||
val first = memoryPool.poll() ?: return ByteBuffer.allocateDirect(STRIDE * width * height)
|
|
||||||
first.position(0)
|
|
||||||
BufferUtils.zeroBuffer(first)
|
|
||||||
return first
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun release(mem: ByteBuffer) {
|
|
||||||
memoryPool.add(mem)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class Grid {
|
private inner class Grid {
|
||||||
inner class LightCell(var x: Int, var y: Int) : ICell {
|
inner class LightCell(val x: Int, val y: Int) : ICell {
|
||||||
private var actualDropoff = 0f
|
val actualDropoff by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
val parent = this@LightCalculator.parent.getCell(x, y) ?: return@lazy 0f
|
||||||
|
val lightBlockStrength: Float
|
||||||
|
|
||||||
private var index = 0
|
if (parent.foreground.material.renderParameters.lightTransparent) {
|
||||||
private var isNotEmpty = true
|
lightBlockStrength = 0f
|
||||||
|
} else {
|
||||||
override var red: Float = 0f
|
lightBlockStrength = 1f
|
||||||
private set
|
|
||||||
override var green: Float = 0f
|
|
||||||
private set
|
|
||||||
override var blue: Float = 0f
|
|
||||||
private set
|
|
||||||
|
|
||||||
fun set(red: Float, green: Float, blue: Float) {
|
|
||||||
mem.position(index)
|
|
||||||
mem.putFloat(red)
|
|
||||||
mem.putFloat(green)
|
|
||||||
mem.putFloat(blue)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
get(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun get(x: Int, y: Int): LightCell {
|
|
||||||
index = (x + y * width) * STRIDE
|
|
||||||
|
|
||||||
this.x = x
|
|
||||||
this.y = y
|
|
||||||
|
|
||||||
mem.position(index)
|
|
||||||
|
|
||||||
red = mem.getFloat()
|
|
||||||
green = mem.getFloat()
|
|
||||||
blue = mem.getFloat()
|
|
||||||
isNotEmpty = mem.get() > 0
|
|
||||||
|
|
||||||
actualDropoff = mem.getFloat()
|
|
||||||
|
|
||||||
if (actualDropoff == 0f) {
|
|
||||||
actualDropoff = run {
|
|
||||||
val parent = this@LightCalculator.parent.getCell(x, y) ?: return@run 1f
|
|
||||||
val lightBlockStrength: Float
|
|
||||||
|
|
||||||
if (parent.foreground.material.renderParameters.lightTransparent) {
|
|
||||||
lightBlockStrength = 0f
|
|
||||||
} else {
|
|
||||||
lightBlockStrength = 1f
|
|
||||||
}
|
|
||||||
|
|
||||||
1f + linearInterpolation(lightBlockStrength, invMaxAirSpread, invMaxObstacleSpread)
|
|
||||||
}
|
|
||||||
|
|
||||||
mem.position(mem.position() - 4)
|
|
||||||
mem.putFloat(actualDropoff)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this
|
linearInterpolation(lightBlockStrength, invMaxAirSpread, invMaxObstacleSpread)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun spreadInto(target: LightCell, drop: Float, changes: Boolean): Boolean {
|
private var empty = true
|
||||||
val max = red.coerceAtLeast(green).coerceAtLeast(blue)
|
override var red: Float = 0f
|
||||||
if (max <= 0f) return changes
|
override var green: Float = 0f
|
||||||
val newDrop = 1f - (actualDropoff - 1f) / max * drop
|
override var blue: Float = 0f
|
||||||
|
|
||||||
|
fun spreadInto(target: LightCell, drop: Float, alreadyHadChanges: Boolean): Boolean {
|
||||||
|
val max = red.coerceAtLeast(green).coerceAtLeast(blue)
|
||||||
|
if (max <= 0f) return alreadyHadChanges
|
||||||
|
val newDrop = 1f - actualDropoff / max * drop
|
||||||
|
|
||||||
|
@Suppress("name_shadowing")
|
||||||
|
var alreadyHadChanges = alreadyHadChanges
|
||||||
val nred = target.red.coerceAtLeast(red * newDrop)
|
val nred = target.red.coerceAtLeast(red * newDrop)
|
||||||
val ngreen = target.green.coerceAtLeast(green * newDrop)
|
val ngreen = target.green.coerceAtLeast(green * newDrop)
|
||||||
val nblue = target.blue.coerceAtLeast(blue * newDrop)
|
val nblue = target.blue.coerceAtLeast(blue * newDrop)
|
||||||
|
|
||||||
mem.position(target.index)
|
if (!alreadyHadChanges)
|
||||||
|
alreadyHadChanges = nred != target.red || ngreen != target.green || nblue != target.blue
|
||||||
|
|
||||||
mem.putFloat(nred)
|
target.red = nred
|
||||||
mem.putFloat(ngreen)
|
target.green = ngreen
|
||||||
mem.putFloat(nblue)
|
target.blue = nblue
|
||||||
|
|
||||||
if (!target.isNotEmpty && (nred >= epsilon || ngreen >= epsilon || nblue >= epsilon)) {
|
if (target.empty && (target.red >= epsilon || target.blue >= epsilon || target.green >= epsilon)) {
|
||||||
minX = minX.coerceAtMost(target.x - 1)
|
minX = minX.coerceAtMost(target.x - 1)
|
||||||
minY = minY.coerceAtMost(target.y - 1)
|
minY = minY.coerceAtMost(target.y - 1)
|
||||||
maxX = maxX.coerceAtLeast(target.x + 1)
|
maxX = maxX.coerceAtLeast(target.x + 1)
|
||||||
maxY = maxY.coerceAtLeast(target.y + 1)
|
maxY = maxY.coerceAtLeast(target.y + 1)
|
||||||
mem.put(1)
|
target.empty = false
|
||||||
clampRect()
|
clampRect()
|
||||||
}
|
}
|
||||||
|
|
||||||
return changes || nred != target.red ||
|
return alreadyHadChanges
|
||||||
ngreen != target.green ||
|
|
||||||
nblue != target.blue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,16 +125,23 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
clampRect()
|
clampRect()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mem = acquire()
|
private val mem by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
Object2DArray.nulls<LightCell>(width, height)
|
||||||
fun release() {
|
|
||||||
release(mem)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun get(x: Int, y: Int): LightCell {
|
operator fun get(x: Int, y: Int): LightCell {
|
||||||
return LightCell(x, y)
|
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? {
|
fun safeGet(x: Int, y: Int): LightCell? {
|
||||||
if (x in 0 until width && y in 0 until height) {
|
if (x in 0 until width && y in 0 until height) {
|
||||||
return this[x, y]
|
return this[x, y]
|
||||||
@ -196,45 +150,92 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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() {
|
fun calculateSpread() {
|
||||||
if (minX > maxX || minY > maxY) return
|
if (minX > maxX || minY > maxY) return
|
||||||
|
|
||||||
val current = LightCell(0, 0)
|
// spread light in several passes
|
||||||
val target = LightCell(0, 0)
|
var repeats = passes
|
||||||
|
|
||||||
var passes = passes
|
var copy: Getter? = null
|
||||||
|
|
||||||
while (passes-- >= 0) {
|
while (repeats-- >= 0) {
|
||||||
val minX = minX
|
val minX = minX
|
||||||
val maxX = maxX
|
val maxX = maxX
|
||||||
val minY = minY
|
val minY = minY
|
||||||
val maxY = maxY
|
val maxY = maxY
|
||||||
var changes = false
|
var changes = false
|
||||||
|
|
||||||
|
if (copy == null) {
|
||||||
|
copy = if (maxX - minX >= width / 2 || maxY - minY >= height / 2) {
|
||||||
|
passthrough
|
||||||
|
} else {
|
||||||
|
Copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// bottom to top
|
// bottom to top
|
||||||
for (y in minY .. maxY) {
|
for (y in minY .. maxY) {
|
||||||
// left to right
|
// left to right
|
||||||
for (x in minX .. maxX) {
|
for (x in minX .. maxX) {
|
||||||
current[x, y]
|
val current = copy[x, y]
|
||||||
|
|
||||||
changes = current.spreadInto(target[x, y + 1], 1f, changes)
|
changes = current.spreadInto(copy[x, y + 1], 1f, changes)
|
||||||
changes = current.spreadInto(target[x + 1, y], 1f, changes)
|
changes = current.spreadInto(copy[x + 1, y], 1f, changes)
|
||||||
changes = current.spreadInto(target[x + 1, y + 1], 1.4142135f, changes)
|
changes = current.spreadInto(copy[x + 1, y + 1], 1.4142135f, changes)
|
||||||
|
|
||||||
// original code performs this spread to camouflage prism shape of light spreading
|
// original code performs this spread to camouflage prism shape of light spreading
|
||||||
// we instead gonna do light pass on different diagonal
|
// we instead gonna do light pass on different diagonal
|
||||||
if (quality.extraCell)
|
if (quality.extraCell)
|
||||||
changes = current.spreadInto(target[x + 1, y - 1], 1.4142135f, changes)
|
changes = current.spreadInto(copy[x + 1, y - 1], 1.4142135f, changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// right to left
|
// right to left
|
||||||
if (quality.secondDiagonal) {
|
if (quality.secondDiagonal) {
|
||||||
for (x in maxX downTo minX) {
|
for (x in maxX downTo minX) {
|
||||||
current[x, y]
|
val current = copy[x, y]
|
||||||
|
|
||||||
changes = current.spreadInto(target[x, y + 1], 1f, changes)
|
changes = current.spreadInto(copy[x, y + 1], 1f, changes)
|
||||||
changes = current.spreadInto(target[x - 1, y], 1f, changes)
|
changes = current.spreadInto(copy[x - 1, y], 1f, changes)
|
||||||
changes = current.spreadInto(target[x - 1, y + 1], 1.4142135f, changes)
|
changes = current.spreadInto(copy[x - 1, y + 1], 1.4142135f, changes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,36 +244,46 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
for (y in maxY downTo minY) {
|
for (y in maxY downTo minY) {
|
||||||
// right to left
|
// right to left
|
||||||
for (x in maxX downTo minX) {
|
for (x in maxX downTo minX) {
|
||||||
current[x, y]
|
val current = copy[x, y]
|
||||||
|
|
||||||
changes = current.spreadInto(target[x, y - 1], 1f, changes)
|
changes = current.spreadInto(copy[x, y - 1], 1f, changes)
|
||||||
changes = current.spreadInto(target[x - 1, y], 1f, changes)
|
changes = current.spreadInto(copy[x - 1, y], 1f, changes)
|
||||||
changes = current.spreadInto(target[x - 1, y - 1], 1.4142135f, changes)
|
changes = current.spreadInto(copy[x - 1, y - 1], 1.4142135f, changes)
|
||||||
|
|
||||||
// original code performs this spread to camouflage prism shape of light spreading
|
// original code performs this spread to camouflage prism shape of light spreading
|
||||||
// we instead gonna do light pass on different diagonal
|
// we instead gonna do light pass on different diagonal
|
||||||
if (quality.extraCell)
|
if (quality.extraCell)
|
||||||
changes = current.spreadInto(target[x - 1, y + 1], 1.4142135f, changes)
|
current.spreadInto(copy[x - 1, y + 1], 1.4142135f, changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// left to right
|
// left to right
|
||||||
if (quality.secondDiagonal) {
|
if (quality.secondDiagonal) {
|
||||||
for (x in minX .. maxX) {
|
for (x in minX .. maxX) {
|
||||||
current[x, y]
|
val current = this[x, y]
|
||||||
|
|
||||||
changes = current.spreadInto(target[x, y - 1], 1f, changes)
|
changes = current.spreadInto(copy[x, y - 1], 1f, changes)
|
||||||
changes = current.spreadInto(target[x + 1, y], 1f, changes)
|
changes = current.spreadInto(copy[x + 1, y], 1f, changes)
|
||||||
changes = current.spreadInto(target[x + 1, y - 1], 1.4142135f, changes)
|
changes = current.spreadInto(copy[x + 1, y - 1], 1.4142135f, changes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if our boundaries have updated, re-spread light, otherwise stop
|
// if our boundaries have updated, re-spread light
|
||||||
if (minX != this.minX || maxX != this.maxX || minY != this.minY || maxY != this.maxY) {
|
if (
|
||||||
passes++
|
minX != this.minX ||
|
||||||
} else if (!changes) {
|
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()
|
||||||
|
}
|
||||||
|
} else if (!changes)
|
||||||
break
|
break
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -464,7 +475,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
for (x in grid.minX - 1 .. grid.maxX) {
|
for (x in grid.minX - 1 .. grid.maxX) {
|
||||||
for (y in grid.minY - 1 .. grid.maxY) {
|
for (y in grid.minY - 1 .. grid.maxY) {
|
||||||
if (x !in 0 until targetWidth || y !in 0 until targetHeight) continue
|
if (x !in 0 until targetWidth || y !in 0 until targetHeight) continue
|
||||||
val a = grid[x, y]
|
val a = grid.get0(x, y) ?: continue
|
||||||
val pos = initialPos + (targetWidth * y + x) * 3
|
val pos = initialPos + (targetWidth * y + x) * 3
|
||||||
targetMem.position(pos)
|
targetMem.position(pos)
|
||||||
|
|
||||||
@ -488,7 +499,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
|
|
||||||
for (x in grid.minX - 1 .. grid.maxX) {
|
for (x in grid.minX - 1 .. grid.maxX) {
|
||||||
for (y in grid.minY - 1 .. grid.maxY) {
|
for (y in grid.minY - 1 .. grid.maxY) {
|
||||||
val a = grid[x, y]
|
val a = grid.get0(x, y) ?: continue
|
||||||
var b = mainGrid[x, y]
|
var b = mainGrid[x, y]
|
||||||
|
|
||||||
if (b == null) {
|
if (b == null) {
|
||||||
@ -502,8 +513,6 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
grid.release()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it.isDone
|
it.isDone
|
||||||
@ -518,7 +527,9 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
val cell = grid.safeGet(light.x, light.y) ?: continue
|
val cell = grid.safeGet(light.x, light.y) ?: continue
|
||||||
val speculatedSpread = (maxAirSpread * light.red.coerceAtLeast(light.green).coerceAtLeast(light.blue)).roundToInt()
|
val speculatedSpread = (maxAirSpread * light.red.coerceAtLeast(light.green).coerceAtLeast(light.blue)).roundToInt()
|
||||||
|
|
||||||
cell.set(light.red, light.green, light.blue)
|
cell.red = light.red
|
||||||
|
cell.green = light.green
|
||||||
|
cell.blue = light.blue
|
||||||
|
|
||||||
grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread)
|
grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread)
|
||||||
grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread)
|
grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread)
|
||||||
@ -535,7 +546,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
for (x in grid.minX - 1 .. grid.maxX) {
|
for (x in grid.minX - 1 .. grid.maxX) {
|
||||||
for (y in grid.minY - 1 .. grid.maxY) {
|
for (y in grid.minY - 1 .. grid.maxY) {
|
||||||
if (x !in 0 until targetWidth || y !in 0 until targetHeight) continue
|
if (x !in 0 until targetWidth || y !in 0 until targetHeight) continue
|
||||||
val a = grid[x, y]
|
val a = grid.get0(x, y) ?: continue
|
||||||
val (bRed, bGreen, bBlue) = a
|
val (bRed, bGreen, bBlue) = a
|
||||||
|
|
||||||
targetMem.position(initialPos + (targetWidth * y + x) * 3)
|
targetMem.position(initialPos + (targetWidth * y + x) * 3)
|
||||||
@ -549,7 +560,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
|
|
||||||
for (x in grid.minX - 1 .. grid.maxX) {
|
for (x in grid.minX - 1 .. grid.maxX) {
|
||||||
for (y in grid.minY - 1 .. grid.maxY) {
|
for (y in grid.minY - 1 .. grid.maxY) {
|
||||||
val a = grid[x, y]
|
val a = grid.get0(x, y) ?: continue
|
||||||
|
|
||||||
mainGrid[x, y] = Cell(
|
mainGrid[x, y] = Cell(
|
||||||
a.red,
|
a.red,
|
||||||
@ -596,7 +607,9 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
val speculatedSpread = (parent.maxAirSpread * light.red.coerceAtLeast(light.green).coerceAtLeast(light.blue)).roundToInt()
|
val speculatedSpread = (parent.maxAirSpread * light.red.coerceAtLeast(light.green).coerceAtLeast(light.blue)).roundToInt()
|
||||||
val cell = grid.safeGet(light.x, light.y) ?: continue
|
val cell = grid.safeGet(light.x, light.y) ?: continue
|
||||||
|
|
||||||
cell.set(light.red, light.green, light.blue)
|
cell.red = light.red
|
||||||
|
cell.green = light.green
|
||||||
|
cell.blue = light.blue
|
||||||
|
|
||||||
grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread)
|
grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread)
|
||||||
grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread)
|
grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread)
|
||||||
|
Loading…
Reference in New Issue
Block a user