occlusion: handle section updates, clamp biome y position at dimension height

In GH-4 I realized that the biome of the chunk (or extreme position) should be used as default biome and not null. This has nothing todo with the issue itself.
This commit is contained in:
Bixilon 2022-05-07 21:50:04 +02:00
parent d6cd333ed4
commit cbb2dfe241
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
15 changed files with 93 additions and 49 deletions

View File

@ -32,29 +32,29 @@ data class DimensionProperties(
val bedWorks: Boolean = true,
val skyProperties: ResourceLocation = ResourceLocation("overworld"),
val hasRaids: Boolean = true,
val logicalHeight: Int = 256,
val logicalHeight: Int = DEFAULT_MAX_Y,
val coordinateScale: Double = 0.0,
val minY: Int = 0,
val hasCeiling: Boolean = false,
val ultraWarm: Boolean = false,
@Deprecated("Height does not differ from logical height in 1.18")
val dataHeight: Int = 256,
val dataHeight: Int = DEFAULT_MAX_Y,
val supports3DBiomes: Boolean = true,
) {
val height = logicalHeight + minY
val lowestSection = if (minY < 0) {
val maxY = logicalHeight + minY
val minSection = if (minY < 0) {
(minY + 1) / ProtocolDefinition.SECTION_HEIGHT_Y - 1
} else {
minY / ProtocolDefinition.SECTION_HEIGHT_Y
}
val highestSection = if (height < 0) {
(height + 1) / ProtocolDefinition.SECTION_HEIGHT_Y - 1
val maxSection = if (maxY < 0) {
(maxY + 1) / ProtocolDefinition.SECTION_HEIGHT_Y - 1
} else {
height / ProtocolDefinition.SECTION_HEIGHT_Y
maxY / ProtocolDefinition.SECTION_HEIGHT_Y
}
val lightLevels = FloatArray(16)
val sections = highestSection - lowestSection
val sections = maxSection - minSection
init {
val ambientLight = 0.0f // ToDo: 0.1 in nether
@ -68,6 +68,8 @@ data class DimensionProperties(
companion object {
const val DEFAULT_MAX_Y = 256
fun deserialize(data: Map<String, Any>): DimensionProperties {
return DimensionProperties(
piglinSafe = data["piglin_safe"]?.toBoolean() ?: false,

View File

@ -22,6 +22,7 @@ import de.bixilon.minosoft.data.registries.blocks.types.entity.BlockWithEntity
import de.bixilon.minosoft.data.world.ChunkSection.Companion.index
import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor
import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.container.BlockSectionDataProvider
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkSectionPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inSectionHeight
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
@ -41,8 +42,8 @@ class Chunk(
private val world = connection.world
var bottomLight: ByteArray? = null
var topLight: ByteArray? = null
val lowestSection = world.dimension!!.lowestSection
val highestSection = world.dimension!!.highestSection
val lowestSection = world.dimension!!.minSection
val highestSection = world.dimension!!.maxSection
val cacheBiomes = world.cacheBiomeAccessor != null
var blocksInitialized = false // All block data was received
@ -179,7 +180,7 @@ class Chunk(
var section = sections[sectionIndex]
if (section == null) {
section = ChunkSection()
section = ChunkSection(BlockSectionDataProvider(occlusionUpdateCallback = world.occlusionUpdateCallback))
val neighbours: Array<Chunk> = world.getChunkNeighbours(chunkPosition).unsafeCast()
val cacheBiomeAccessor = world.cacheBiomeAccessor
if (cacheBiomeAccessor != null && biomesInitialized && neighboursLoaded) {

View File

@ -28,7 +28,7 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
* Collection of 16x16x16 blocks
*/
class ChunkSection(
var blocks: BlockSectionDataProvider = BlockSectionDataProvider(),
var blocks: BlockSectionDataProvider,
var biomes: SectionDataProvider<Biome> = SectionDataProvider(checkSize = false),
var blockEntities: SectionDataProvider<BlockEntity?> = SectionDataProvider(checkSize = false),
var light: ByteArray = ByteArray(ProtocolDefinition.BLOCKS_PER_SECTION), // packed (skyLight: 0xF0, blockLight: 0x0F)

View File

@ -0,0 +1,19 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world
interface OcclusionUpdateCallback {
fun onOcclusionChange()
}

View File

@ -76,6 +76,7 @@ class World(
override var audioPlayer: AbstractAudioPlayer? = null
override var particleRenderer: AbstractParticleRenderer? = null
var occlusionUpdateCallback: OcclusionUpdateCallback? = null
operator fun get(blockPosition: Vec3i): BlockState? {
return chunks[blockPosition.chunkPosition]?.get(blockPosition.inChunkPosition)
@ -125,7 +126,7 @@ class World(
return false
}
val dimension = connection.world.dimension!!
return (blockPosition.y >= dimension.minY || blockPosition.y < dimension.height)
return (blockPosition.y >= dimension.minY || blockPosition.y < dimension.maxY)
}
fun forceSetBlockState(blockPosition: Vec3i, blockState: BlockState?, check: Boolean = false) {
@ -167,7 +168,16 @@ class World(
}
override fun getBiome(blockPosition: Vec3i): Biome? {
return this[blockPosition.chunkPosition]?.getBiome(blockPosition.inChunkPosition)
val inChunkPosition = blockPosition.inChunkPosition
val minY = dimension?.minY ?: 0
if (inChunkPosition.y < minY) {
inChunkPosition.y = minY
}
val maxY = dimension?.maxY ?: DimensionProperties.DEFAULT_MAX_Y
if (inChunkPosition.y > maxY) {
inChunkPosition.y = maxY
}
return this[blockPosition.chunkPosition]?.getBiome(inChunkPosition)
}
override fun getBiome(x: Int, y: Int, z: Int): Biome? {

View File

@ -19,11 +19,13 @@ import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.data.registries.blocks.properties.BlockProperties
import de.bixilon.minosoft.data.registries.blocks.types.FluidBlock
import de.bixilon.minosoft.data.registries.blocks.types.FluidFillable
import de.bixilon.minosoft.data.world.OcclusionUpdateCallback
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import it.unimi.dsi.fastutil.ints.IntOpenHashSet
class BlockSectionDataProvider(
data: Array<BlockState?>? = null,
val occlusionUpdateCallback: OcclusionUpdateCallback?,
) : SectionDataProvider<BlockState?>(data, true, false) {
var fluidCount = 0
private set
@ -264,7 +266,7 @@ class BlockSectionDataProvider(
if (!this.occlusion.contentEquals(occlusion)) {
this.occlusion = occlusion
// ToDo: Recalculate visibility graph
occlusionUpdateCallback?.onOcclusionChange()
}
}

View File

@ -34,6 +34,7 @@ class Camera(
fun draw() {
matrixHandler.entity.tick()
matrixHandler.draw()
visibilityGraph.draw()
targetHandler.raycast()
fogManager.draw()
}

View File

@ -24,8 +24,8 @@ import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.camera.frustum.Frustum
import de.bixilon.minosoft.gui.rendering.modding.events.CameraMatrixChangeEvent
import de.bixilon.minosoft.gui.rendering.modding.events.CameraPositionChangeEvent
import de.bixilon.minosoft.gui.rendering.modding.events.FrustumChangeEvent
import de.bixilon.minosoft.gui.rendering.modding.events.ResizeWindowEvent
import de.bixilon.minosoft.gui.rendering.modding.events.VisibilityGraphChangeEvent
import de.bixilon.minosoft.gui.rendering.util.VecUtil.blockPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.chunkPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
@ -151,7 +151,7 @@ class MatrixHandler(
private fun updateFrustum() {
frustum.recalculate()
connection.fireEvent(FrustumChangeEvent(renderWindow, frustum))
connection.fireEvent(VisibilityGraphChangeEvent(renderWindow))
}
private fun updateRotation(rotation: EntityRotation = entity.rotation) {

View File

@ -26,7 +26,7 @@ import de.bixilon.minosoft.data.entities.entities.Entity
import de.bixilon.minosoft.data.player.LocalPlayerEntity
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.modding.events.FrustumChangeEvent
import de.bixilon.minosoft.gui.rendering.modding.events.VisibilityGraphChangeEvent
import de.bixilon.minosoft.gui.rendering.renderer.Renderer
import de.bixilon.minosoft.gui.rendering.renderer.RendererBuilder
import de.bixilon.minosoft.gui.rendering.system.base.DepthFunctions
@ -71,7 +71,7 @@ class EntityHitboxRenderer(
}
toUnload += meshes.remove(it.entity) ?: return@of
})
connection.registerEvent(CallbackEventInvoker.of<FrustumChangeEvent> {
connection.registerEvent(CallbackEventInvoker.of<VisibilityGraphChangeEvent> {
if (!enabled) {
return@of
}

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
@ -15,9 +15,7 @@ package de.bixilon.minosoft.gui.rendering.modding.events
import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.Rendering
import de.bixilon.minosoft.gui.rendering.camera.frustum.Frustum
class FrustumChangeEvent(
class VisibilityGraphChangeEvent(
renderWindow: RenderWindow = Rendering.currentContext!!,
val frustum: Frustum,
) : RenderEvent(renderWindow)

View File

@ -40,8 +40,8 @@ import de.bixilon.minosoft.data.world.World
import de.bixilon.minosoft.data.world.view.ViewDistanceChangeEvent
import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.RenderingStates
import de.bixilon.minosoft.gui.rendering.modding.events.FrustumChangeEvent
import de.bixilon.minosoft.gui.rendering.modding.events.RenderingStateChangeEvent
import de.bixilon.minosoft.gui.rendering.modding.events.VisibilityGraphChangeEvent
import de.bixilon.minosoft.gui.rendering.renderer.Renderer
import de.bixilon.minosoft.gui.rendering.renderer.RendererBuilder
import de.bixilon.minosoft.gui.rendering.system.base.DepthFunctions
@ -160,7 +160,7 @@ class WorldRenderer(
loadWorldShader(this.textShader, false)
connection.registerEvent(CallbackEventInvoker.of<FrustumChangeEvent> { onFrustumChange() })
connection.registerEvent(CallbackEventInvoker.of<VisibilityGraphChangeEvent> { onFrustumChange() })
connection.registerEvent(CallbackEventInvoker.of<RespawnEvent> { unloadWorld() })
connection.registerEvent(CallbackEventInvoker.of<ChunkDataChangeEvent> { queueChunk(it.chunkPosition, it.chunk) })
@ -715,7 +715,7 @@ class WorldRenderer(
val cameraSectionHeight = cameraPosition.blockPosition.sectionHeight
val minSectionHeight = connection.world.dimension?.minY?.sectionHeight ?: 0
val maxSectionHeight = connection.world.dimension?.height?.sectionHeight ?: 16
val maxSectionHeight = connection.world.dimension?.maxY?.sectionHeight ?: 16
val visible = VisibleMeshes(cameraPosition)

View File

@ -83,8 +83,8 @@ class ChunkBorderRenderer(
else -> ChatColors.YELLOW
}
mesh.drawLine(Vec3(basePosition.x + x, dimension.minY, basePosition.y), Vec3(basePosition.x + x, dimension.height, basePosition.y), RenderConstants.DEFAULT_LINE_WIDTH * 5, color)
mesh.drawLine(Vec3(basePosition.x + x, dimension.minY, basePosition.y + ProtocolDefinition.SECTION_WIDTH_Z), Vec3(basePosition.x + x, dimension.height, basePosition.y + ProtocolDefinition.SECTION_WIDTH_Z), RenderConstants.DEFAULT_LINE_WIDTH * 5, color)
mesh.drawLine(Vec3(basePosition.x + x, dimension.minY, basePosition.y), Vec3(basePosition.x + x, dimension.maxY, basePosition.y), RenderConstants.DEFAULT_LINE_WIDTH * 5, color)
mesh.drawLine(Vec3(basePosition.x + x, dimension.minY, basePosition.y + ProtocolDefinition.SECTION_WIDTH_Z), Vec3(basePosition.x + x, dimension.maxY, basePosition.y + ProtocolDefinition.SECTION_WIDTH_Z), RenderConstants.DEFAULT_LINE_WIDTH * 5, color)
}
for (z in 0..ProtocolDefinition.SECTION_WIDTH_Z) {
@ -94,12 +94,12 @@ class ChunkBorderRenderer(
else -> ChatColors.YELLOW
}
mesh.drawLine(Vec3(basePosition.x, dimension.minY, basePosition.y + z), Vec3(basePosition.x, dimension.height, basePosition.y + z), RenderConstants.DEFAULT_LINE_WIDTH * 5, color)
mesh.drawLine(Vec3(basePosition.x + ProtocolDefinition.SECTION_WIDTH_X, dimension.minY, basePosition.y + z), Vec3(basePosition.x + ProtocolDefinition.SECTION_WIDTH_X, dimension.height, basePosition.y + z), RenderConstants.DEFAULT_LINE_WIDTH * 5, color)
mesh.drawLine(Vec3(basePosition.x, dimension.minY, basePosition.y + z), Vec3(basePosition.x, dimension.maxY, basePosition.y + z), RenderConstants.DEFAULT_LINE_WIDTH * 5, color)
mesh.drawLine(Vec3(basePosition.x + ProtocolDefinition.SECTION_WIDTH_X, dimension.minY, basePosition.y + z), Vec3(basePosition.x + ProtocolDefinition.SECTION_WIDTH_X, dimension.maxY, basePosition.y + z), RenderConstants.DEFAULT_LINE_WIDTH * 5, color)
}
// horizontal lines
for (y in dimension.minY..dimension.height) {
for (y in dimension.minY..dimension.maxY) {
val borderColor = when {
y % ProtocolDefinition.SECTION_HEIGHT_Y == 0 -> ChatColors.BLUE
y % 2 == 0 -> ChatColors.GREEN

View File

@ -21,9 +21,11 @@ import de.bixilon.kutil.time.TimeUtil
import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.data.registries.AABB
import de.bixilon.minosoft.data.world.Chunk
import de.bixilon.minosoft.data.world.OcclusionUpdateCallback
import de.bixilon.minosoft.data.world.container.BlockSectionDataProvider
import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.camera.Camera
import de.bixilon.minosoft.gui.rendering.modding.events.VisibilityGraphChangeEvent
import de.bixilon.minosoft.gui.rendering.util.VecUtil.chunkPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.plus
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
@ -38,8 +40,8 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet
class WorldVisibilityGraph(
private val renderWindow: RenderWindow,
private val camera: Camera,
) {
camera: Camera,
) : OcclusionUpdateCallback {
private val connection = renderWindow.connection
private val frustum = camera.matrixHandler.frustum
private var cameraChunkPosition = Vec2i.EMPTY
@ -47,6 +49,8 @@ class WorldVisibilityGraph(
private var viewDistance = connection.world.view.viewDistance
private val chunks = connection.world.chunks.original
private var recalculateNextFrame = false
var minSection = 0
var maxSection = 16
var maxIndex = 15
@ -67,6 +71,7 @@ class WorldVisibilityGraph(
init {
calculateGraph()
connection.world.occlusionUpdateCallback = this
}
fun isInViewDistance(chunkPosition: Vec2i): Boolean {
@ -140,8 +145,8 @@ class WorldVisibilityGraph(
}
this.cameraChunkPosition = chunkPosition
this.cameraSectionHeight = sectionHeight
this.minSection = connection.world.dimension?.lowestSection ?: 0
this.maxSection = connection.world.dimension?.highestSection ?: 16
this.minSection = connection.world.dimension?.minSection ?: 0
this.maxSection = connection.world.dimension?.maxSection ?: 16
this.sections = maxSection - minSection
this.maxIndex = sections - 1
calculateGraph()
@ -250,6 +255,7 @@ class WorldVisibilityGraph(
private fun calculateGraph() {
connection.world.chunks.lock.acquire()
recalculateNextFrame = false
val start = TimeUtil.nanos
println("Calculating graph...")
@ -276,10 +282,11 @@ class WorldVisibilityGraph(
updateVisibilityGraph(graph)
println("Done in ${(TimeUtil.nanos - start) / 1000}")
connection.world.chunks.lock.release()
connection.fireEvent(VisibilityGraphChangeEvent(renderWindow))
}
private fun createVisibilityArray(): Array<BooleanArray> {
@ -314,10 +321,14 @@ class WorldVisibilityGraph(
visibilityLock.unlock()
}
private fun createVisibilityStatus(sectionIndex: Int, `in`: Directions, out: Directions): Int {
val preferIn = `in`.ordinal < out.ordinal
override fun onOcclusionChange() {
recalculateNextFrame = true
}
return (sectionIndex and 0xFFFF shl 6) or ((if (preferIn) `in` else `out`).ordinal shl 3) or (if (preferIn) out else `in`).ordinal
fun draw() {
if (recalculateNextFrame) {
calculateGraph()
}
}
companion object {

View File

@ -107,7 +107,7 @@ class ChunkS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
}
buffer.versionId < V_21W37A -> {
val blockEntities: MutableMap<Vec3i, BlockEntity> = mutableMapOf()
val positionOffset = Vec3i.of(chunkPosition, dimension.lowestSection, Vec3i.EMPTY)
val positionOffset = Vec3i.of(chunkPosition, dimension.minSection, Vec3i.EMPTY)
for (i in 0 until buffer.readVarInt()) {
val nbt = buffer.readNBT().asJsonObject()
val position = Vec3i(nbt["x"]?.toInt() ?: continue, nbt["y"]?.toInt() ?: continue, nbt["z"]?.toInt() ?: continue) - positionOffset

View File

@ -72,7 +72,7 @@ object ChunkUtil {
// parse data
var arrayPosition = 0
val sectionBlocks: Array<BlockSectionDataProvider?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.lowestSection until dimension.highestSection).withIndex()) {
for ((sectionIndex, sectionHeight) in (dimension.minSection until dimension.maxSection).withIndex()) {
if (!sectionBitMask[sectionIndex]) {
continue
}
@ -103,7 +103,7 @@ object ChunkUtil {
blocks[blockNumber] = buffer.connection.registries.blockStateRegistry[blockId] ?: continue
}
sectionBlocks[sectionHeight] = BlockSectionDataProvider(blocks)
sectionBlocks[sectionHeight] = BlockSectionDataProvider(blocks, buffer.connection.world.occlusionUpdateCallback)
}
chunkData.blocks = sectionBlocks
return chunkData
@ -138,7 +138,7 @@ object ChunkUtil {
var arrayPos = 0
val sectionBlocks: Array<BlockSectionDataProvider?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.lowestSection until dimension.highestSection).withIndex()) { // max sections per chunks in chunk column
for ((sectionIndex, sectionHeight) in (dimension.minSection until dimension.maxSection).withIndex()) { // max sections per chunks in chunk column
if (!sectionBitMask[sectionIndex]) {
continue
}
@ -148,7 +148,7 @@ object ChunkUtil {
val block = buffer.connection.registries.blockStateRegistry[blockId] ?: continue
blocks[blockNumber] = block
}
sectionBlocks[sectionHeight] = BlockSectionDataProvider(blocks)
sectionBlocks[sectionHeight] = BlockSectionDataProvider(blocks, buffer.connection.world.occlusionUpdateCallback)
}
chunkData.blocks = sectionBlocks
return chunkData
@ -161,7 +161,7 @@ object ChunkUtil {
var lightReceived = 0
val biomes: Array<Array<Biome>?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.lowestSection until (sectionBitMask?.length() ?: dimension.highestSection)).withIndex()) { // max sections per chunks in chunk column
for ((sectionIndex, sectionHeight) in (dimension.minSection until (sectionBitMask?.length() ?: dimension.maxSection)).withIndex()) { // max sections per chunks in chunk column
if (sectionBitMask?.get(sectionIndex) == false) {
continue
}
@ -173,11 +173,11 @@ object ChunkUtil {
val blockContainer: PalettedContainer<BlockState?> = PalettedContainerReader.read(buffer, buffer.connection.registries.blockStateRegistry, paletteFactory = BlockStatePaletteFactory)
if (blockContainer.palette !is SingularPalette<*> || blockContainer.palette.item != null) {
sectionBlocks[sectionHeight - dimension.lowestSection] = BlockSectionDataProvider(blockContainer.unpack())
sectionBlocks[sectionHeight - dimension.minSection] = BlockSectionDataProvider(blockContainer.unpack(), buffer.connection.world.occlusionUpdateCallback)
}
if (buffer.versionId >= V_21W37A) {
val biomeContainer: PalettedContainer<Biome> = PalettedContainerReader.read(buffer, buffer.connection.registries.biomeRegistry, paletteFactory = BiomePaletteFactory)
biomes[sectionHeight - dimension.lowestSection] = biomeContainer.unpack()
biomes[sectionHeight - dimension.minSection] = biomeContainer.unpack()
}
@ -187,7 +187,7 @@ object ChunkUtil {
if (containsSkyLight) {
skyLight = buffer.readByteArray(ProtocolDefinition.BLOCKS_PER_SECTION / 2)
}
light[sectionHeight - dimension.lowestSection] = LightUtil.mergeLight(blockLight, skyLight ?: LightUtil.EMPTY_LIGHT_ARRAY)
light[sectionHeight - dimension.minSection] = LightUtil.mergeLight(blockLight, skyLight ?: LightUtil.EMPTY_LIGHT_ARRAY)
lightReceived++
}
}
@ -197,7 +197,7 @@ object ChunkUtil {
chunkData.light = light
}
if (buffer.versionId >= V_21W37A) {
chunkData.biomeSource = PalettedBiomeArray(biomes, dimension.lowestSection, BiomePaletteFactory.edgeBits)
chunkData.biomeSource = PalettedBiomeArray(biomes, dimension.minSection, BiomePaletteFactory.edgeBits)
} else if (buffer.versionId < V_19W36A && isFullChunk) {
chunkData.biomeSource = readLegacyBiomeArray(buffer)
}