Liquid rendering in regions
This commit is contained in:
parent
36d83b6a8e
commit
f71b561ad7
@ -26,75 +26,9 @@ import java.util.LinkedList
|
|||||||
const val Z_LEVEL_BACKGROUND = 60000
|
const val Z_LEVEL_BACKGROUND = 60000
|
||||||
const val Z_LEVEL_LIQUID = 10000
|
const val Z_LEVEL_LIQUID = 10000
|
||||||
|
|
||||||
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos), Closeable {
|
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){
|
||||||
val state: GLStateTracker get() = world.client.gl
|
val state: GLStateTracker get() = world.client.gl
|
||||||
|
|
||||||
private inner class TileLayerRenderer(private val view: ITileAccess, private val isBackground: Boolean) : AutoCloseable {
|
|
||||||
private val layers = TileLayerList()
|
|
||||||
val bakedMeshes = LinkedList<Pair<ConfiguredStaticMesh, Int>>()
|
|
||||||
var isDirty = false
|
|
||||||
|
|
||||||
fun bake() {
|
|
||||||
if (!isDirty) return
|
|
||||||
isDirty = false
|
|
||||||
|
|
||||||
if (state.isSameThread()) {
|
|
||||||
for (mesh in bakedMeshes) {
|
|
||||||
mesh.first.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bakedMeshes.clear()
|
|
||||||
|
|
||||||
layers.clear()
|
|
||||||
|
|
||||||
for (x in 0 until CHUNK_SIZE) {
|
|
||||||
for (y in 0 until CHUNK_SIZE) {
|
|
||||||
if (!world.inBounds(x, y)) continue
|
|
||||||
val tile = view.getTile(x, y) ?: continue
|
|
||||||
val material = tile.material
|
|
||||||
|
|
||||||
if (material != null) {
|
|
||||||
world.client.tileRenderers.getTileRenderer(material.materialName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground)
|
|
||||||
}
|
|
||||||
|
|
||||||
val modifier = tile.modifier
|
|
||||||
|
|
||||||
if (modifier != null) {
|
|
||||||
world.client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground, isModifier = true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun upload() {
|
|
||||||
if (layers.isNotEmpty) {
|
|
||||||
for (mesh in bakedMeshes) {
|
|
||||||
mesh.first.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
bakedMeshes.clear()
|
|
||||||
|
|
||||||
for ((baked, builder, zLevel) in layers.buildSortedLayerList()) {
|
|
||||||
bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
layers.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
for (mesh in bakedMeshes) {
|
|
||||||
mesh.first.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val posVector2d = Vector2d(x = pos.x * CHUNK_SIZEd, y = pos.y * CHUNK_SIZEd)
|
|
||||||
|
|
||||||
private val foregroundRenderer = TileLayerRenderer(worldForegroundView, isBackground = false)
|
|
||||||
private val backgroundRenderer = TileLayerRenderer(worldBackgroundView, isBackground = true)
|
|
||||||
|
|
||||||
override fun foregroundChanges(cell: Cell) {
|
override fun foregroundChanges(cell: Cell) {
|
||||||
super.foregroundChanges(cell)
|
super.foregroundChanges(cell)
|
||||||
|
|
||||||
@ -111,99 +45,11 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bake() {
|
override fun liquidChanges(cell: Cell) {
|
||||||
backgroundRenderer.bake()
|
super.liquidChanges(cell)
|
||||||
foregroundRenderer.bake()
|
|
||||||
|
|
||||||
if (state.isSameThread())
|
world.forEachRenderRegion(cell) {
|
||||||
upload()
|
it.liquidIsDirty = true
|
||||||
}
|
|
||||||
|
|
||||||
fun upload() {
|
|
||||||
backgroundRenderer.upload()
|
|
||||||
foregroundRenderer.upload()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val liquidTypes = ReferenceArraySet<LiquidDefinition>()
|
|
||||||
private var liquidTypesVer = 0
|
|
||||||
|
|
||||||
private fun getLiquidTypes(): Collection<LiquidDefinition> {
|
|
||||||
if (liquidTypesVer != liquidChangeset) {
|
|
||||||
liquidTypes.clear()
|
|
||||||
liquidTypesVer = liquidChangeset
|
|
||||||
|
|
||||||
for (x in 0 until CHUNK_SIZE) {
|
|
||||||
for (y in 0 until CHUNK_SIZE) {
|
|
||||||
getCell(x, y).liquid.def?.let { liquidTypes.add(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return liquidTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class Renderer(val renderOrigin: Vector2f) {
|
|
||||||
fun addLayers(layers: LayeredRenderer) {
|
|
||||||
for ((baked, zLevel) in backgroundRenderer.bakedMeshes) {
|
|
||||||
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
|
|
||||||
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
|
||||||
baked.renderStacked(it)
|
|
||||||
it.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((baked, zLevel) in foregroundRenderer.bakedMeshes) {
|
|
||||||
layers.add(zLevel) {
|
|
||||||
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
|
||||||
baked.renderStacked(it)
|
|
||||||
it.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (renderer in entityRenderers.values) {
|
|
||||||
layers.add(renderer.layer) {
|
|
||||||
val relative = renderer.renderPos - posVector2d
|
|
||||||
it.push().last().translateWithMultiplication(renderOrigin.x + relative.x.toFloat(), renderOrigin.y + relative.y.toFloat())
|
|
||||||
renderer.render(it)
|
|
||||||
it.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val types = getLiquidTypes()
|
|
||||||
|
|
||||||
if (types.isNotEmpty()) {
|
|
||||||
layers.add(Z_LEVEL_LIQUID) {
|
|
||||||
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
|
||||||
|
|
||||||
val program = state.programs.liquid
|
|
||||||
|
|
||||||
program.use()
|
|
||||||
program.transform = it.last()
|
|
||||||
|
|
||||||
val builder = program.builder
|
|
||||||
|
|
||||||
for (type in types) {
|
|
||||||
builder.builder.begin()
|
|
||||||
|
|
||||||
for (x in 0 until CHUNK_SIZE) {
|
|
||||||
for (y in 0 until CHUNK_SIZE) {
|
|
||||||
val state = getCell(x, y)
|
|
||||||
|
|
||||||
if (state.liquid.def === type) {
|
|
||||||
builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.liquid.level)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
program.baselineColor = type.color
|
|
||||||
|
|
||||||
builder.upload()
|
|
||||||
builder.draw()
|
|
||||||
}
|
|
||||||
|
|
||||||
it.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,13 +72,4 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
|||||||
override fun onEntityRemoved(entity: Entity) {
|
override fun onEntityRemoved(entity: Entity) {
|
||||||
entityRenderers.remove(entity)!!.close()
|
entityRenderers.remove(entity)!!.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
backgroundRenderer.close()
|
|
||||||
foregroundRenderer.close()
|
|
||||||
|
|
||||||
for (renderer in entityRenderers.values) {
|
|
||||||
renderer.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,12 @@ package ru.dbotthepony.kstarbound.client
|
|||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
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 ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh
|
import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh
|
||||||
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.TileLayerList
|
import ru.dbotthepony.kstarbound.client.render.TileLayerList
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||||
import ru.dbotthepony.kstarbound.world.*
|
import ru.dbotthepony.kstarbound.world.*
|
||||||
@ -15,6 +18,7 @@ import ru.dbotthepony.kstarbound.world.api.TileView
|
|||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
import ru.dbotthepony.kvector.api.IStruct2i
|
import ru.dbotthepony.kvector.api.IStruct2i
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
import ru.dbotthepony.kvector.vector.Vector2f
|
import ru.dbotthepony.kvector.vector.Vector2f
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
@ -95,7 +99,7 @@ class ClientWorld(
|
|||||||
|
|
||||||
bakedMeshes.clear()
|
bakedMeshes.clear()
|
||||||
|
|
||||||
for ((baked, builder, zLevel) in layers.buildSortedLayerList()) {
|
for ((baked, builder, zLevel) in layers.layers()) {
|
||||||
bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel)
|
bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +108,9 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val liquidMesh = ArrayList<Pair<Mesh, RGBAColor>>()
|
||||||
|
var liquidIsDirty = true
|
||||||
|
|
||||||
val view = OffsetCellAccess(this@ClientWorld, x * renderRegionWidth, y * renderRegionHeight)
|
val view = OffsetCellAccess(this@ClientWorld, x * renderRegionWidth, y * renderRegionHeight)
|
||||||
|
|
||||||
val background = Layer(TileView.Background(view), true)
|
val background = Layer(TileView.Background(view), true)
|
||||||
@ -113,6 +120,37 @@ class ClientWorld(
|
|||||||
background.bake()
|
background.bake()
|
||||||
foreground.bake()
|
foreground.bake()
|
||||||
|
|
||||||
|
if (liquidIsDirty) {
|
||||||
|
liquidIsDirty = false
|
||||||
|
liquidMesh.clear()
|
||||||
|
|
||||||
|
val liquidTypes = ReferenceArraySet<LiquidDefinition>()
|
||||||
|
|
||||||
|
for (x in 0 until renderRegionWidth) {
|
||||||
|
for (y in 0 until renderRegionHeight) {
|
||||||
|
view.getCell(x, y)?.liquid?.def?.let { liquidTypes.add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (type in liquidTypes) {
|
||||||
|
val builder = client.gl.programs.liquid.builder.builder
|
||||||
|
|
||||||
|
builder.begin()
|
||||||
|
|
||||||
|
for (x in 0 until renderRegionWidth) {
|
||||||
|
for (y in 0 until renderRegionHeight) {
|
||||||
|
val state = view.getCell(x, y)
|
||||||
|
|
||||||
|
if (state?.liquid?.def === type) {
|
||||||
|
builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state!!.liquid.level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
liquidMesh.add(Mesh(client.gl, builder) to type.color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for ((baked, zLevel) in background.bakedMeshes) {
|
for ((baked, zLevel) in background.bakedMeshes) {
|
||||||
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
|
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
|
||||||
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
||||||
@ -129,50 +167,23 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*for (renderer in entityRenderers.values) {
|
if (liquidMesh.isNotEmpty()) {
|
||||||
layers.add(renderer.layer) {
|
|
||||||
val relative = renderer.renderPos - posVector2d
|
|
||||||
it.push().last().translateWithMultiplication(renderOrigin.x + relative.x.toFloat(), renderOrigin.y + relative.y.toFloat())
|
|
||||||
renderer.render(it)
|
|
||||||
it.pop()
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*val types = getLiquidTypes()
|
|
||||||
|
|
||||||
if (types.isNotEmpty()) {
|
|
||||||
layers.add(Z_LEVEL_LIQUID) {
|
layers.add(Z_LEVEL_LIQUID) {
|
||||||
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
||||||
|
|
||||||
val program = state.programs.liquid
|
val program = client.gl.programs.liquid
|
||||||
|
|
||||||
program.use()
|
program.use()
|
||||||
program.transform = it.last()
|
program.transform = it.last()
|
||||||
|
|
||||||
val builder = program.builder
|
for ((mesh, color) in liquidMesh) {
|
||||||
|
program.baselineColor = color
|
||||||
for (type in types) {
|
mesh.render()
|
||||||
builder.builder.begin()
|
|
||||||
|
|
||||||
for (x in 0 until CHUNK_SIZE) {
|
|
||||||
for (y in 0 until CHUNK_SIZE) {
|
|
||||||
val state = getCell(x, y)
|
|
||||||
|
|
||||||
if (state.liquid.def === type) {
|
|
||||||
builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.liquid.level)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
program.baselineColor = type.color
|
|
||||||
|
|
||||||
builder.upload()
|
|
||||||
builder.draw()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it.pop()
|
it.pop()
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
|
import org.lwjgl.opengl.GL46
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
||||||
|
|
||||||
|
class Mesh(state: GLStateTracker) {
|
||||||
|
constructor(state: GLStateTracker, builder: VertexBuilder) : this(state) {
|
||||||
|
load(builder, GL46.GL_STATIC_DRAW)
|
||||||
|
}
|
||||||
|
|
||||||
|
val vao = state.newVAO()
|
||||||
|
val vbo = state.newVBO()
|
||||||
|
val ebo = state.newEBO()
|
||||||
|
|
||||||
|
var indexCount = 0
|
||||||
|
private set
|
||||||
|
var indexType = 0
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun load(builder: VertexBuilder, mode: Int = GL46.GL_DYNAMIC_DRAW) {
|
||||||
|
vao.bind()
|
||||||
|
vbo.bind()
|
||||||
|
ebo.bind()
|
||||||
|
|
||||||
|
builder.upload(vbo, ebo, mode)
|
||||||
|
builder.attributes.apply(vao, true)
|
||||||
|
|
||||||
|
indexCount = builder.indexCount
|
||||||
|
indexType = builder.indexType
|
||||||
|
|
||||||
|
vao.unbind()
|
||||||
|
vbo.unbind()
|
||||||
|
ebo.unbind()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render() {
|
||||||
|
vao.bind()
|
||||||
|
GL46.glDrawElements(GL46.GL_TRIANGLES, indexCount, indexType, 0L)
|
||||||
|
checkForGLError()
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
|
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.lwjgl.opengl.GL46.*
|
import org.lwjgl.opengl.GL46.*
|
||||||
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||||
@ -16,6 +16,7 @@ import ru.dbotthepony.kstarbound.world.api.ITileState
|
|||||||
import ru.dbotthepony.kstarbound.world.api.TileColor
|
import ru.dbotthepony.kstarbound.world.api.TileColor
|
||||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
|
import java.util.stream.Stream
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
data class TileLayer(
|
data class TileLayer(
|
||||||
@ -25,7 +26,7 @@ data class TileLayer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
class TileLayerList {
|
class TileLayerList {
|
||||||
private val layers = HashMap<ConfiguredShaderProgram<GLTileProgram>, Int2ObjectAVLTreeMap<TileLayer>>()
|
private val layers = HashMap<ConfiguredShaderProgram<GLTileProgram>, Int2ObjectOpenHashMap<TileLayer>>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получает геометрию слоя ([DynamicVertexBuilder]), который имеет программу для отрисовки [program] и располагается на [zLevel].
|
* Получает геометрию слоя ([DynamicVertexBuilder]), который имеет программу для отрисовки [program] и располагается на [zLevel].
|
||||||
@ -33,24 +34,13 @@ class TileLayerList {
|
|||||||
* Если такого слоя нет, вызывается [compute] и создаётся новый [TileLayer], затем возвращается результат [compute].
|
* Если такого слоя нет, вызывается [compute] и создаётся новый [TileLayer], затем возвращается результат [compute].
|
||||||
*/
|
*/
|
||||||
fun computeIfAbsent(program: ConfiguredShaderProgram<GLTileProgram>, zLevel: Int, compute: () -> VertexBuilder): VertexBuilder {
|
fun computeIfAbsent(program: ConfiguredShaderProgram<GLTileProgram>, zLevel: Int, compute: () -> VertexBuilder): VertexBuilder {
|
||||||
return layers.computeIfAbsent(program) { Int2ObjectAVLTreeMap() }.computeIfAbsent(zLevel, Int2ObjectFunction {
|
return layers.computeIfAbsent(program) { Int2ObjectOpenHashMap() }.computeIfAbsent(zLevel, Int2ObjectFunction {
|
||||||
return@Int2ObjectFunction TileLayer(program, compute.invoke(), zLevel)
|
return@Int2ObjectFunction TileLayer(program, compute.invoke(), zLevel)
|
||||||
}).vertices
|
}).vertices
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildSortedLayerList(): List<TileLayer> {
|
fun layers(): Stream<TileLayer> {
|
||||||
val list = ArrayList<TileLayer>()
|
return layers.values.stream().flatMap { it.values.stream() }
|
||||||
|
|
||||||
for (getList in layers.values) {
|
|
||||||
list.addAll(getList.values)
|
|
||||||
}
|
|
||||||
|
|
||||||
list.sortBy {
|
|
||||||
// унарный минус для инвентирования порядка (сначала большие, потом маленькие)
|
|
||||||
return@sortBy -it.zPos
|
|
||||||
}
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() = layers.clear()
|
fun clear() = layers.clear()
|
||||||
|
Loading…
Reference in New Issue
Block a user