diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesherTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesherTest.kt index f42af94bf..7e6abdeb6 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesherTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesherTest.kt @@ -27,6 +27,7 @@ import de.bixilon.minosoft.data.registries.dimension.DimensionProperties import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft import de.bixilon.minosoft.data.world.positions.BlockPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.RenderContext import de.bixilon.minosoft.gui.rendering.camera.Camera import de.bixilon.minosoft.gui.rendering.chunk.entities.BlockEntityRenderer @@ -80,9 +81,9 @@ class SolidSectionMesherTest { val mesher = SolidSectionMesher(context) val chunk = world.chunks[0, 0]!! - val meshes = ChunkMeshes(context, chunk.position, 0, true) + val meshes = ChunkMeshes(context, SectionPosition.of(chunk.position, 0), true) - mesher.mesh(chunk.position, 0, chunk, chunk.sections[0]!!, chunk.neighbours.neighbours, chunk.sections[0]!!.neighbours!!, meshes) + mesher.mesh(SectionPosition.of(chunk.position, 0), chunk, chunk.sections[0]!!, chunk.neighbours.neighbours, chunk.sections[0]!!.neighbours!!, meshes) return meshes } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt index 0f1f68e25..97686e82c 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt @@ -23,6 +23,7 @@ import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider import de.bixilon.minosoft.data.world.positions.BlockPosition import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition +import de.bixilon.minosoft.data.world.positions.SectionHeight import de.bixilon.minosoft.protocol.network.session.play.PlaySession import java.util.* @@ -30,7 +31,7 @@ import java.util.* * Collection of 16x16x16 blocks */ class ChunkSection( - val height: Int, + val height: SectionHeight, val chunk: Chunk, ) { val blocks = BlockSectionDataProvider(chunk.lock, this) diff --git a/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPosition.kt b/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPosition.kt index c92910f1d..f9e244497 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPosition.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPosition.kt @@ -111,6 +111,7 @@ value class BlockPosition( inline val hash get() = generatePositionHash(x, y, z) inline val sectionHeight get() = y.sectionHeight inline val chunkPosition get() = ChunkPosition(x shr 4, z shr 4) + inline val sectionPosition get() = SectionPosition(x shr 4, y shr 4, z shr 4) inline val inChunkPosition get() = InChunkPosition(x and 0x0F, y, this.z and 0x0F) inline val inSectionPosition get() = InSectionPosition(x and 0x0F, y.inSectionHeight, z and 0x0F) @@ -169,5 +170,13 @@ value class BlockPosition( chunk.z * ProtocolDefinition.SECTION_WIDTH_Z + inSection.z ) // ToDo: Confirm } + + fun of(section: SectionPosition, inSection: InSectionPosition): BlockPosition { + return BlockPosition( + section.x * ProtocolDefinition.SECTION_WIDTH_X + inSection.x, + section.y * ProtocolDefinition.SECTION_HEIGHT_Y + inSection.y, + section.z * ProtocolDefinition.SECTION_WIDTH_Z + inSection.z + ) // ToDo: Confirm + } } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/positions/ChunkPosition.kt b/src/main/java/de/bixilon/minosoft/data/world/positions/ChunkPosition.kt index 5a558d441..97c4c47a5 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/positions/ChunkPosition.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/positions/ChunkPosition.kt @@ -88,6 +88,8 @@ value class ChunkPosition( inline operator fun component1() = x inline operator fun component2() = z + fun sectionPosition(y: SectionHeight) = SectionPosition(x, y, z) + override fun toText() = "(${this.x.format()} ${this.z.format()})" override fun toString() = "c($x $z)" @@ -105,8 +107,8 @@ value class ChunkPosition( const val Z = 1L shl SHIFT_Z - const val MAX_X = (BlockPosition.MAX_X shr 4) + 1 - const val MAX_Z = (BlockPosition.MAX_Z shr 4) + 1 + const val MAX_X = BlockPosition.MAX_X shr 4 + const val MAX_Z = BlockPosition.MAX_Z shr 4 val EMPTY = ChunkPosition(0, 0) diff --git a/src/main/java/de/bixilon/minosoft/data/world/positions/SectionPosition.kt b/src/main/java/de/bixilon/minosoft/data/world/positions/SectionPosition.kt new file mode 100644 index 000000000..952e37c35 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/world/positions/SectionPosition.kt @@ -0,0 +1,146 @@ +/* + * Minosoft + * Copyright (C) 2020-2025 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.world.positions + +import de.bixilon.minosoft.data.direction.Directions +import de.bixilon.minosoft.data.text.formatting.TextFormattable +import de.bixilon.minosoft.data.world.positions.BlockPositionUtil.assertPosition +import de.bixilon.minosoft.util.KUtil.format + +@JvmInline +value class SectionPosition( + inline val raw: Long, +) : TextFormattable { + + constructor() : this(0, 0, 0) + + constructor(x: Int, y: SectionHeight, z: Int) : this(((y and MASK_Y).toLong() shl SHIFT_Y) or ((z and MASK_Z).toLong() shl SHIFT_Z) or ((x and MASK_X).toLong() shl SHIFT_X)) { + assertPosition(x, -MAX_X, MAX_X) + assertPosition(y, MIN_Y, MAX_Y) + assertPosition(z, -MAX_Z, MAX_Z) + } + + inline val x: Int get() = (((raw ushr SHIFT_X).toInt() and MASK_X) shl (Int.SIZE_BITS - BITS_X)) shr (Int.SIZE_BITS - BITS_X) + inline val y: SectionHeight get() = (((raw ushr SHIFT_Y).toInt() and MASK_Y) shl (Int.SIZE_BITS - BITS_Y)) shr (Int.SIZE_BITS - BITS_Y) + inline val z: Int get() = (((raw ushr SHIFT_Z).toInt() and MASK_Z) shl (Int.SIZE_BITS - BITS_Z)) shr (Int.SIZE_BITS - BITS_Z) + + inline fun plusX(): SectionPosition { + assertPosition(this.x < MAX_X) + return SectionPosition(raw + X * 1) + } + + inline fun plusX(x: Int): SectionPosition { + assertPosition(this.x + x, -MAX_X, MAX_X) + return SectionPosition(raw + X * x) + } + + inline fun minusX(): SectionPosition { + assert(this.x > -MAX_X) + return SectionPosition(raw - X * 1) + } + + inline fun plusY(): SectionPosition { + assertPosition(this.y < MAX_Y) + return SectionPosition(raw + Y * 1) + } + + inline fun plusY(y: Int): SectionPosition { + assertPosition(this.y + y, MIN_Y, MAX_Y) + return SectionPosition(raw + Y * y) + } + + inline fun minusY(): SectionPosition { + assert(this.y > MIN_Y) + return SectionPosition(raw - Y * 1) + } + + inline fun plusZ(): SectionPosition { + assert(this.z < MAX_Z) + return SectionPosition(raw + Z * 1) + } + + inline fun plusZ(z: Int): SectionPosition { + assertPosition(this.z + z, -MAX_Z, MAX_Z) + return SectionPosition(raw + Z * z) + } + + inline fun minusZ(): SectionPosition { + assert(this.z > -MAX_Z) + return SectionPosition(raw - Z * 1) + } + + inline fun with(x: Int = this.x, y: Int = this.y, z: Int = this.z) = SectionPosition(x, y, z) + + inline operator fun plus(value: Int) = SectionPosition(this.x + value, this.y + value, this.z + value) + inline operator fun minus(value: Int) = SectionPosition(this.x - value, this.y - value, this.z - value) + inline operator fun times(value: Int) = SectionPosition(this.x * value, this.y * value, this.z * value) + inline operator fun div(value: Int) = SectionPosition(this.x / value, this.y * value, this.z / value) + + inline operator fun plus(position: SectionPosition) = SectionPosition(this.x + position.x, this.y + position.y, this.z + position.z) + inline operator fun minus(position: SectionPosition) = SectionPosition(this.x - position.x, this.y - position.y, this.z - position.z) + + inline operator fun plus(position: ChunkPosition) = SectionPosition(this.x + position.x, this.y, this.z + position.z) + inline operator fun minus(position: ChunkPosition) = SectionPosition(this.x - position.x, this.y, this.z - position.z) + + inline operator fun plus(direction: Directions) = SectionPosition(this.x + direction.vector.x, this.y + direction.vector.y, this.z + direction.vector.z) + inline operator fun minus(direction: Directions) = SectionPosition(this.x - direction.vector.x, this.y - direction.vector.y, this.z - direction.vector.z) + + inline operator fun unaryMinus() = SectionPosition(-this.x, -this.y, -this.z) + inline operator fun unaryPlus() = this + + + inline operator fun component1() = x + inline operator fun component2() = y + inline operator fun component3() = z + + inline fun length2() = (x * x + y * y + z * z) + + inline fun sectionIndex(minSection: SectionHeight): SectionIndex = this.y - minSection + inline val chunkPosition get() = ChunkPosition(x, z) + + override fun toText() = "(${this.x.format()} ${this.y.format()} ${this.z.format()})" + override fun toString() = "c($x $y $z)" + + + companion object { + const val BITS_X = 22 + const val MASK_X = (1 shl BITS_X) - 1 + const val SHIFT_X = 0 + + const val BITS_Z = 22 + const val MASK_Z = (1 shl BITS_Z) - 1 + const val SHIFT_Z = BITS_X + + const val BITS_Y = 8 + const val MASK_Y = (1 shl BITS_Y) - 1 + const val SHIFT_Y = BITS_X + BITS_Z + + const val X = 1L shl SHIFT_X + const val Z = 1L shl SHIFT_Z + const val Y = 1L shl SHIFT_Y + + + const val MAX_X = BlockPosition.MAX_X shr 4 + const val MIN_Y = BlockPosition.MIN_Y shr 4 + const val MAX_Y = BlockPosition.MAX_Y shr 4 + const val MAX_Z = BlockPosition.MAX_Z shr 4 + + + fun of(chunkPosition: ChunkPosition, sectionHeight: Int) = SectionPosition(chunkPosition.x, sectionHeight, chunkPosition.z) + fun of(chunkPosition: ChunkPosition, sectionIndex: SectionIndex, minSection: SectionHeight) = SectionPosition(chunkPosition.x, sectionIndex + minSection, chunkPosition.z) + + + val EMPTY = SectionPosition(0, 0, 0) + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/camera/MatrixHandler.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/camera/MatrixHandler.kt index e259b08af..29b0bef0b 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/camera/MatrixHandler.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/camera/MatrixHandler.kt @@ -138,7 +138,7 @@ class MatrixHandler( if (view.updateFrustum) { frustum.recalculate() - camera.visibilityGraph.updateCamera(cameraBlockPosition.chunkPosition, cameraBlockPosition.sectionHeight) + camera.visibilityGraph.updateCamera(cameraBlockPosition.sectionPosition) } session.events.fire(CameraPositionChangeEvent(context, useEyePosition)) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt index ba5562d9b..11f9a4f95 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt @@ -23,6 +23,7 @@ import de.bixilon.minosoft.config.key.KeyCodes import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft import de.bixilon.minosoft.data.world.World import de.bixilon.minosoft.data.world.positions.ChunkPosition +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.RenderContext import de.bixilon.minosoft.gui.rendering.RenderingStates import de.bixilon.minosoft.gui.rendering.chunk.mesh.VisibleMeshes @@ -86,8 +87,7 @@ class ChunkRenderer( private var previousViewDistance = session.world.view.viewDistance private var cameraPosition = Vec3.EMPTY - var cameraChunkPosition = ChunkPosition.EMPTY - var cameraSectionHeight = 0 + var cameraSectionPosition = SectionPosition.EMPTY var limitChunkTransferTime = true @@ -198,15 +198,15 @@ class ChunkRenderer( } - fun unload(item: WorldQueueItem) = unload(QueuePosition(item.chunkPosition, item.sectionHeight)) + fun unload(item: WorldQueueItem) = unload(QueuePosition(item.position)) fun unload(position: QueuePosition) { lock.lock() - loaded.unload(position.position, position.sectionHeight, false) - culledQueue.remove(position.position, position.sectionHeight, false) + loaded.unload(position.position, false) + culledQueue.remove(position.position.chunkPosition, false) meshingQueue.remove(position, false) loadingQueue.abort(position, false) - meshingQueue.tasks.interrupt(position.position, position.sectionHeight) + meshingQueue.tasks.interrupt(position.position.chunkPosition) lock.unlock() } @@ -252,15 +252,10 @@ class ChunkRenderer( private fun onFrustumChange() { var sortQueue = false val cameraPosition = Vec3(session.player.renderInfo.eyePosition - context.camera.offset.offset) - val cameraChunkPosition = cameraPosition.blockPosition.chunkPosition - val cameraSectionHeight = this.cameraSectionHeight + val sectionPosition = cameraPosition.blockPosition.sectionPosition if (this.cameraPosition != cameraPosition) { - if (this.cameraChunkPosition != cameraChunkPosition) { - this.cameraChunkPosition = cameraChunkPosition - sortQueue = true - } - if (this.cameraSectionHeight != cameraSectionHeight) { - this.cameraSectionHeight = cameraSectionHeight + if (this.cameraSectionPosition != sectionPosition) { + this.cameraSectionPosition = sectionPosition sortQueue = true } this.cameraPosition = cameraPosition diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/LoadedMeshes.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/LoadedMeshes.kt index 88c2d996b..eef4622a4 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/LoadedMeshes.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/LoadedMeshes.kt @@ -15,6 +15,7 @@ package de.bixilon.minosoft.gui.rendering.chunk import de.bixilon.kutil.concurrent.lock.RWLock import de.bixilon.minosoft.data.world.positions.ChunkPosition +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.chunk.mesh.ChunkMeshes import de.bixilon.minosoft.gui.rendering.chunk.mesh.VisibleMeshes import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap @@ -67,17 +68,17 @@ class LoadedMeshes( if (lock) unlock() } - fun unload(position: ChunkPosition, sectionHeight: Int, lock: Boolean) { + fun unload(position: SectionPosition, lock: Boolean) { if (lock) lock() - val meshes = this.meshes[position] + val meshes = this.meshes[position.chunkPosition] if (meshes != null) { - meshes.remove(sectionHeight)?.let { + meshes.remove(position.y)?.let { renderer.unloadingQueue.forceQueue(it, lock) if (meshes.isEmpty()) { - this.meshes.remove(position) + this.meshes.remove(position.chunkPosition) } } } @@ -106,7 +107,7 @@ class LoadedMeshes( for (entry in meshes.int2ObjectEntrySet()) { val mesh = entry.value - if (!renderer.visibilityGraph.isSectionVisible(chunkPosition, entry.intKey, mesh.minPosition, mesh.maxPosition, false)) { + if (!renderer.visibilityGraph.isSectionVisible(SectionPosition.of(chunkPosition, entry.intKey), mesh.minPosition, mesh.maxPosition, false)) { continue } visible.addMesh(mesh) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldQueueItem.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldQueueItem.kt index fa59cb89b..82352dbc8 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldQueueItem.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldQueueItem.kt @@ -14,22 +14,18 @@ package de.bixilon.minosoft.gui.rendering.chunk import de.bixilon.kotlinglm.vec3.Vec3 -import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.data.world.chunk.chunk.Chunk -import de.bixilon.minosoft.data.world.positions.ChunkPosition +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.chunk.mesh.ChunkMeshes import de.bixilon.minosoft.gui.rendering.chunk.queue.QueuePosition -import java.util.* class WorldQueueItem( - val chunkPosition: ChunkPosition, - val sectionHeight: Int, + val position: SectionPosition, val chunk: Chunk, val section: ChunkSection, val center: Vec3, ) { - val sectionPosition = Vec3i(chunkPosition.x, sectionHeight, chunkPosition.z) var mesh: ChunkMeshes? = null var distance = 0 @@ -37,15 +33,15 @@ class WorldQueueItem( override fun equals(other: Any?): Boolean { if (other is WorldQueueItem) { - return chunkPosition == other.chunkPosition && sectionHeight == other.sectionHeight + return position == other.position } if (other is QueuePosition) { - return chunkPosition == other.position && sectionHeight == other.sectionHeight + return position == other.position } return false } override fun hashCode(): Int { - return Objects.hash(chunkPosition, sectionHeight) + return position.hashCode() } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesh/ChunkMeshes.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesh/ChunkMeshes.kt index e9e8ce79c..3002750ac 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesh/ChunkMeshes.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesh/ChunkMeshes.kt @@ -17,8 +17,8 @@ import de.bixilon.kotlinglm.vec2.Vec2 import de.bixilon.kotlinglm.vec3.Vec3 import de.bixilon.kutil.exception.Broken import de.bixilon.minosoft.data.world.positions.BlockPosition -import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.RenderContext import de.bixilon.minosoft.gui.rendering.chunk.entities.BlockEntityRenderer import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureTransparencies @@ -29,11 +29,10 @@ import de.bixilon.minosoft.util.collections.floats.DirectArrayFloatList class ChunkMeshes( context: RenderContext, - val chunkPosition: ChunkPosition, - val sectionHeight: Int, + val position: SectionPosition, smallMesh: Boolean = false, ) : BlockVertexConsumer { - val center: Vec3 = Vec3(BlockPosition.of(chunkPosition, sectionHeight, InSectionPosition(8, 8, 8))) + val center: Vec3 = Vec3(BlockPosition.of(position, InSectionPosition(8, 8, 8))) var opaqueMesh: ChunkMesh? = ChunkMesh(context, if (smallMesh) 8192 else 65536) var translucentMesh: ChunkMesh? = ChunkMesh(context, if (smallMesh) 4096 else 16384) var textMesh: ChunkMesh? = ChunkMesh(context, if (smallMesh) 1024 else 4096) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/ChunkMesher.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/ChunkMesher.kt index 30ee1886a..4c50d2c39 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/ChunkMesher.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/ChunkMesher.kt @@ -38,12 +38,12 @@ class ChunkMesher( return null } val sectionNeighbours = ChunkUtil.getDirectNeighbours(neighbours.neighbours, item.chunk, item.section.height) - val mesh = ChunkMeshes(renderer.context, item.chunkPosition, item.sectionHeight, item.section.smallMesh) + val mesh = ChunkMeshes(renderer.context, item.position, item.section.smallMesh) try { - solid.mesh(item.chunkPosition, item.sectionHeight, item.chunk, item.section, neighbours.neighbours, sectionNeighbours, mesh) + solid.mesh(item.position, item.chunk, item.section, neighbours.neighbours, sectionNeighbours, mesh) if (item.section.blocks.hasFluid) { - fluid.mesh(item.chunkPosition, item.sectionHeight, item.chunk, item.section, mesh) + fluid.mesh(item.position, item.chunk, item.section, mesh) } } catch (exception: Exception) { mesh.unload() diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/FluidSectionMesher.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/FluidSectionMesher.kt index cfb66729e..7bc8bde54 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/FluidSectionMesher.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/FluidSectionMesher.kt @@ -30,9 +30,9 @@ import de.bixilon.minosoft.data.text.formatting.color.Colors import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.positions.BlockPosition -import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.RenderContext import de.bixilon.minosoft.gui.rendering.chunk.mesh.ChunkMesh import de.bixilon.minosoft.gui.rendering.chunk.mesh.ChunkMeshes @@ -62,7 +62,7 @@ class FluidSectionMesher( // ToDo: Should this be combined with the solid renderer (but we'd need to render faces twice, because of cullface) - fun mesh(chunkPosition: ChunkPosition, sectionHeight: Int, chunk: Chunk, section: ChunkSection, mesh: ChunkMeshes) { + fun mesh(sectionPosition: SectionPosition, chunk: Chunk, section: ChunkSection, mesh: ChunkMeshes) { val blocks = section.blocks var position = BlockPosition() @@ -70,9 +70,9 @@ class FluidSectionMesher( val cameraOffset = context.camera.offset.offset - val offsetX = chunkPosition.x * ProtocolDefinition.SECTION_WIDTH_X - val offsetY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y - val offsetZ = chunkPosition.z * ProtocolDefinition.SECTION_WIDTH_Z + val offsetX = sectionPosition.x * ProtocolDefinition.SECTION_WIDTH_X + val offsetY = sectionPosition.y * ProtocolDefinition.SECTION_HEIGHT_Y + val offsetZ = sectionPosition.z * ProtocolDefinition.SECTION_WIDTH_Z for (y in blocks.minPosition.y..blocks.maxPosition.y) { for (z in blocks.minPosition.z..blocks.maxPosition.z) { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt index 7cf00306f..a434d5623 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt @@ -32,9 +32,9 @@ import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.chunk.light.SectionLight import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbourArray import de.bixilon.minosoft.data.world.positions.BlockPosition -import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.RenderContext import de.bixilon.minosoft.gui.rendering.chunk.entities.BlockEntityRenderer import de.bixilon.minosoft.gui.rendering.chunk.entities.renderer.RenderedBlockEntity @@ -59,12 +59,12 @@ class SolidSectionMesher( profile.light::ambientOcclusion.observe(this, true) { this.ambientOcclusion = it } } - fun mesh(chunkPosition: ChunkPosition, sectionHeight: Int, chunk: Chunk, section: ChunkSection, neighbourChunks: ChunkNeighbourArray, neighbours: Array, mesh: ChunkMeshes) { + fun mesh(sectionPosition: SectionPosition, chunk: Chunk, section: ChunkSection, neighbourChunks: ChunkNeighbourArray, neighbours: Array, mesh: ChunkMeshes) { val random = if (profile.antiMoirePattern) Random(0L) else null - val isLowestSection = sectionHeight == chunk.minSection - val isHighestSection = sectionHeight == chunk.maxSection + val isLowestSection = sectionPosition.y == chunk.minSection + val isHighestSection = sectionPosition.y == chunk.maxSection val blocks = section.blocks val entities: ArrayList> = ArrayList(section.blockEntities.count) @@ -76,9 +76,9 @@ class SolidSectionMesher( val cameraOffset = context.camera.offset.offset - val offsetX = chunkPosition.x * ProtocolDefinition.SECTION_WIDTH_X - val offsetY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y - val offsetZ = chunkPosition.z * ProtocolDefinition.SECTION_WIDTH_Z + val offsetX = sectionPosition.x * ProtocolDefinition.SECTION_WIDTH_X + val offsetY = sectionPosition.y * ProtocolDefinition.SECTION_HEIGHT_Y + val offsetZ = sectionPosition.z * ProtocolDefinition.SECTION_WIDTH_Z val floatOffset = FloatArray(3) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/CulledQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/CulledQueue.kt index ddbb342c8..819b17a6a 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/CulledQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/CulledQueue.kt @@ -17,6 +17,7 @@ import de.bixilon.kutil.concurrent.lock.RWLock import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.data.world.positions.SectionHeight +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer import it.unimi.dsi.fastutil.ints.IntOpenHashSet @@ -99,7 +100,7 @@ class CulledQueue( val heightIterator = sectionHeights.intIterator() for (sectionHeight in heightIterator) { val section = chunk[sectionHeight] ?: continue - if (!renderer.visibilityGraph.isSectionVisible(chunkPosition, sectionHeight, section.blocks.minPosition, section.blocks.maxPosition, false)) { + if (!renderer.visibilityGraph.isSectionVisible(SectionPosition.Companion.of(chunkPosition, sectionHeight), section.blocks.minPosition, section.blocks.maxPosition, false)) { continue } list += Pair(chunk, sectionHeight) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/QueuePosition.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/QueuePosition.kt index 72009c201..8bc366cb9 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/QueuePosition.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/QueuePosition.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2023 Moritz Zwerger + * Copyright (C) 2020-2025 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. * @@ -13,30 +13,28 @@ package de.bixilon.minosoft.gui.rendering.chunk.queue -import de.bixilon.minosoft.data.world.positions.ChunkPosition +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.chunk.WorldQueueItem import de.bixilon.minosoft.gui.rendering.chunk.mesh.ChunkMeshes -import java.util.* class QueuePosition( - val position: ChunkPosition, - val sectionHeight: Int, + val position: SectionPosition, ) { - constructor(mesh: ChunkMeshes) : this(mesh.chunkPosition, mesh.sectionHeight) + constructor(mesh: ChunkMeshes) : this(mesh.position) override fun equals(other: Any?): Boolean { if (other is WorldQueueItem) { - return position == other.chunkPosition && sectionHeight == other.sectionHeight + return position == other.position } if (other is QueuePosition) { - return position == other.position && sectionHeight == other.sectionHeight + return position == other.position } return false } override fun hashCode(): Int { - return Objects.hash(position, sectionHeight) + return position.hashCode() } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshLoadingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshLoadingQueue.kt index b3b29023a..502c5536a 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshLoadingQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshLoadingQueue.kt @@ -58,18 +58,18 @@ class MeshLoadingQueue( mesh.load() - if (position != mesh.chunkPosition) { - meshes = renderer.loaded.meshes.getOrPut(mesh.chunkPosition) { Int2ObjectOpenHashMap() } - position = mesh.chunkPosition + if (position != mesh.position.chunkPosition) { + meshes = renderer.loaded.meshes.getOrPut(mesh.position.chunkPosition) { Int2ObjectOpenHashMap() } + position = mesh.position.chunkPosition } - meshes.put(mesh.sectionHeight, mesh)?.let { + meshes.put(mesh.position.y, mesh)?.let { renderer.visible.removeMesh(it) it.unload() } - val visible = renderer.visibilityGraph.isSectionVisible(mesh.chunkPosition, mesh.sectionHeight, mesh.minPosition, mesh.maxPosition, true) + val visible = renderer.visibilityGraph.isSectionVisible(mesh.position, mesh.minPosition, mesh.maxPosition, true) if (visible) { count++ renderer.visible.addMesh(mesh) @@ -91,7 +91,7 @@ class MeshLoadingQueue( // already inside, remove meshes.remove(mesh) } - if (mesh.chunkPosition == renderer.cameraChunkPosition) { + if (mesh.position.chunkPosition == renderer.cameraSectionPosition.chunkPosition) { // still higher priority meshes.add(0, mesh) } else { @@ -104,20 +104,20 @@ class MeshLoadingQueue( if (lock) lock() val positions: MutableSet = mutableSetOf() this.positions.removeAll { - if (it.position != position) { + if (it.position.chunkPosition != position) { return@removeAll false } positions += it return@removeAll true } - this.meshes.removeAll { QueuePosition(it.chunkPosition, it.sectionHeight) in positions } + this.meshes.removeAll { QueuePosition(it.position) in positions } if (lock) unlock() } fun abort(position: QueuePosition, lock: Boolean = true) { if (lock) lock() if (this.positions.remove(position)) { - this.meshes.removeAll { it.chunkPosition == position.position && it.sectionHeight == position.sectionHeight } + this.meshes.removeAll { it.position == position.position } } if (lock) unlock() } @@ -128,7 +128,7 @@ class MeshLoadingQueue( if (lock) lock() this.positions.removeAll { - if (renderer.visibilityGraph.isChunkVisible(it.position)) { + if (renderer.visibilityGraph.isChunkVisible(it.position.chunkPosition)) { return@removeAll false } remove += it diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshUnloadingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshUnloadingQueue.kt index ef3ef4383..ef66fafa2 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshUnloadingQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshUnloadingQueue.kt @@ -60,7 +60,7 @@ class MeshUnloadingQueue( fun forceQueue(mesh: ChunkMeshes, lock: Boolean = true) { if (lock) lock() - if (mesh.chunkPosition == renderer.session.camera.entity.physics.positionInfo.chunkPosition) { + if (mesh.position.chunkPosition == renderer.session.camera.entity.physics.positionInfo.chunkPosition) { this.meshes.add(0, mesh) } else { this.meshes += mesh diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/ChunkMeshingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/ChunkMeshingQueue.kt index ef046ba9c..bc83905ba 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/ChunkMeshingQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/ChunkMeshingQueue.kt @@ -72,11 +72,11 @@ class ChunkMeshingQueue( items += item } unlock() - val camera = renderer.cameraChunkPosition + val camera = renderer.cameraSectionPosition for (item in items) { - val distance = abs(item.chunkPosition.x - camera.x) + abs(item.chunkPosition.z - camera.z) + val distance = abs(item.position.x - camera.x) + abs(item.position.z - camera.z) // TODO: Check y? val runnable = HeavyPoolRunnable(if (distance < 1) ThreadPool.HIGH else ThreadPool.LOW, interruptable = true) - val task = MeshPrepareTask(item.chunkPosition, item.sectionHeight, runnable) + val task = MeshPrepareTask(item.position, runnable) task.runnable.runnable = Runnable { renderer.mesher.tryMesh(item, task, task.runnable) } tasks += task } @@ -87,7 +87,7 @@ class ChunkMeshingQueue( fun remove(chunkPosition: ChunkPosition) { val remove: MutableSet = mutableSetOf() queue.removeAll { - if (it.chunkPosition != chunkPosition) { + if (it.position.chunkPosition != chunkPosition) { return@removeAll false } remove += it @@ -100,7 +100,7 @@ class ChunkMeshingQueue( if (lock) lock() val remove: MutableSet = mutableSetOf() queue.removeAll { - if (renderer.visibilityGraph.isChunkVisible(it.chunkPosition)) { + if (renderer.visibilityGraph.isChunkVisible(it.position.chunkPosition)) { return@removeAll false } remove += it @@ -153,7 +153,7 @@ class ChunkMeshingQueue( if (set.remove(item)) { queue -= item } - if (item.chunkPosition == renderer.cameraChunkPosition) { + if (item.position.chunkPosition == renderer.cameraSectionPosition.chunkPosition) { queue.add(0, item) } else { queue += item diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/ChunkQueueComparator.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/ChunkQueueComparator.kt index e20bc7a1f..e90ad8970 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/ChunkQueueComparator.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/ChunkQueueComparator.kt @@ -13,31 +13,26 @@ package de.bixilon.minosoft.gui.rendering.chunk.queue.meshing -import de.bixilon.minosoft.data.world.positions.ChunkPosition +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer import de.bixilon.minosoft.gui.rendering.chunk.WorldQueueItem class ChunkQueueComparator : Comparator { private var sort = 1 - private var position: ChunkPosition = ChunkPosition.EMPTY - private var height = 0 + private var position = SectionPosition() fun update(renderer: ChunkRenderer) { - if (this.position == renderer.cameraChunkPosition && this.height == renderer.cameraSectionHeight) return - this.position = renderer.cameraChunkPosition - this.height = renderer.cameraSectionHeight + if (this.position == renderer.cameraSectionPosition) return + this.position = renderer.cameraSectionPosition sort++ } private fun getDistance(item: WorldQueueItem): Int { if (item.sort == this.sort) return item.distance - val array = item.sectionPosition.array - val x = array[0] - position.x - val y = array[1] - height - val z = array[2] - position.z - val distance = (x * x + y * y + z * z) + val position = item.position + val distance = (position - this.position).length2() item.distance = distance item.sort = sort diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/tasks/MeshPrepareTask.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/tasks/MeshPrepareTask.kt index 85df1b22c..4ea66b634 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/tasks/MeshPrepareTask.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/tasks/MeshPrepareTask.kt @@ -14,10 +14,9 @@ package de.bixilon.minosoft.gui.rendering.chunk.queue.meshing.tasks import de.bixilon.kutil.concurrent.pool.runnable.HeavyPoolRunnable -import de.bixilon.minosoft.data.world.positions.ChunkPosition +import de.bixilon.minosoft.data.world.positions.SectionPosition class MeshPrepareTask( - val chunkPosition: ChunkPosition, - val sectionHeight: Int, + val position: SectionPosition, val runnable: HeavyPoolRunnable, ) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/tasks/MeshPrepareTaskManager.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/tasks/MeshPrepareTaskManager.kt index 5e50a9bd2..1ab340f1f 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/tasks/MeshPrepareTaskManager.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/meshing/tasks/MeshPrepareTaskManager.kt @@ -16,7 +16,7 @@ package de.bixilon.minosoft.gui.rendering.chunk.queue.meshing.tasks import de.bixilon.kutil.concurrent.lock.RWLock import de.bixilon.kutil.concurrent.pool.DefaultThreadPool import de.bixilon.minosoft.data.world.positions.ChunkPosition -import de.bixilon.minosoft.data.world.positions.SectionHeight +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer class MeshPrepareTaskManager( @@ -58,17 +58,17 @@ class MeshPrepareTaskManager( fun interrupt(position: ChunkPosition) { lock.acquire() for (task in tasks) { - if (task.chunkPosition == position) { + if (task.position.chunkPosition == position) { task.runnable.interrupt() } } lock.release() } - fun interrupt(position: ChunkPosition, height: SectionHeight) { + fun interrupt(position: SectionPosition) { lock.acquire() for (task in tasks) { - if (task.chunkPosition == position && task.sectionHeight == height) { + if (task.position == position) { task.runnable.interrupt() } } @@ -79,7 +79,7 @@ class MeshPrepareTaskManager( fun cleanup() { lock.acquire() for (task in tasks) { - if (!renderer.visibilityGraph.isChunkVisible(task.chunkPosition)) { + if (!renderer.visibilityGraph.isChunkVisible(task.position.chunkPosition)) { task.runnable.interrupt() } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/queue/ChunkQueueMaster.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/queue/ChunkQueueMaster.kt index a3303e5bd..31f7ca231 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/queue/ChunkQueueMaster.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/queue/ChunkQueueMaster.kt @@ -20,6 +20,7 @@ import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.positions.BlockPosition import de.bixilon.minosoft.data.world.positions.SectionHeight +import de.bixilon.minosoft.data.world.positions.SectionPosition import de.bixilon.minosoft.gui.rendering.RenderingStates import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer import de.bixilon.minosoft.gui.rendering.chunk.WorldQueueItem @@ -32,15 +33,16 @@ class ChunkQueueMaster( ) { private fun queue(section: ChunkSection, chunk: Chunk, force: Boolean): Boolean { + val position = SectionPosition.of(chunk.position, section.height) if (section.blocks.isEmpty) { - renderer.unload(QueuePosition(chunk.position, section.height)) + renderer.unload(QueuePosition(position)) return false } - val visible = force || renderer.visibilityGraph.isSectionVisible(chunk.position, section.height, section.blocks.minPosition, section.blocks.maxPosition, true) + val visible = force || renderer.visibilityGraph.isSectionVisible(position, section.blocks.minPosition, section.blocks.maxPosition, true) if (visible) { val center = CHUNK_CENTER + BlockPosition.of(chunk.position, section.height) - val item = WorldQueueItem(chunk.position, section.height, chunk, section, center) + val item = WorldQueueItem(position, chunk, section, center) renderer.meshingQueue.queue(item) return true } diff --git a/src/test/java/de/bixilon/minosoft/data/world/positions/BlockPositionTest.kt b/src/test/java/de/bixilon/minosoft/data/world/positions/BlockPositionTest.kt index e65040c0a..cff3bdd9f 100644 --- a/src/test/java/de/bixilon/minosoft/data/world/positions/BlockPositionTest.kt +++ b/src/test/java/de/bixilon/minosoft/data/world/positions/BlockPositionTest.kt @@ -84,8 +84,8 @@ class BlockPositionTest { @Test fun `correct negative y large`() { - val position = BlockPosition(-2048, 0xF, 0xF) - assertEquals(position.x, -2048) + val position = BlockPosition(1234, -2048, 0xF) + assertEquals(position.y, -2048) } @Test diff --git a/src/test/java/de/bixilon/minosoft/data/world/positions/SectionPositionTest.kt b/src/test/java/de/bixilon/minosoft/data/world/positions/SectionPositionTest.kt new file mode 100644 index 000000000..d896ececc --- /dev/null +++ b/src/test/java/de/bixilon/minosoft/data/world/positions/SectionPositionTest.kt @@ -0,0 +1,179 @@ +/* + * Minosoft + * Copyright (C) 2020-2025 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.world.positions + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals + +class SectionPositionTest { + + @Test + fun `init correct min`() { + val position = SectionPosition(-1875000, -128, -1875000) + } + + @Test + fun `init correct max`() { + val position = SectionPosition(1875000, 127, 1875000) + } + + @Test + fun `init badly`() { + assertThrows { SectionPosition(-1875001, 128, -1875001) } + } + + @Test + fun `correct positive x`() { + val position = SectionPosition(2, 0xF, 0xF) + assertEquals(position.x, 2) + } + + @Test + fun `correct positive x large`() { + val position = SectionPosition(1875000, 0xF, 0xF) + assertEquals(position.x, 1875000) + } + + @Test + fun `correct negative x`() { + val position = SectionPosition(-2, 0xF, 0xF) + assertEquals(position.x, -2) + } + + @Test + fun `correct negative x large`() { + val position = SectionPosition(-1875000, 0xF, 0xF) + assertEquals(position.x, -1875000) + } + + @Test + fun `correct plus x`() { + val position = SectionPosition(2, 0xF, 0xF) + assertEquals(position.plusX().x, 3) + } + + @Test + fun `correct plus 2 x`() { + val position = SectionPosition(2, 0xF, 0xF) + assertEquals(position.plusX(2).x, 4) + } + + @Test + fun `correct minus x`() { + val position = SectionPosition(2, 0xF, 0xF) + assertEquals(position.minusX().x, 1) + } + + @Test + fun `correct negative y`() { + val position = SectionPosition(0xF, -4, 0xF) + assertEquals(position.y, -4) + } + + @Test + fun `correct negative y large`() { + val position = SectionPosition(123, -128, 0xF) + assertEquals(position.y, -128) + } + + @Test + fun `correct positive y`() { + val position = SectionPosition(0xF, 100, 0xF) + assertEquals(position.y, 100) + } + + @Test + fun `correct positive y large`() { + val position = SectionPosition(0xF, 127, 0xF) + assertEquals(position.y, 127) + } + + @Test + fun `correct plus y`() { + val position = SectionPosition(0xF, 2, 0xF) + assertEquals(position.plusY().y, 3) + } + + @Test + fun `correct plus 2 y`() { + val position = SectionPosition(0xF, 2, 0xF) + assertEquals(position.plusY(2).y, 4) + } + + @Test + fun `correct minus y`() { + val position = SectionPosition(0xF, 2, 0xF) + assertEquals(position.minusY().y, 1) + } + + @Test + fun `correct positive z`() { + val position = SectionPosition(0xF, 0xF, 4) + assertEquals(position.z, 4) + } + + @Test + fun `correct positive z large`() { + val position = SectionPosition(0, 0, 1875000) + assertEquals(position.z, 1875000) + } + + @Test + fun `correct negative z`() { + val position = SectionPosition(0xF, 0xF, -4) + assertEquals(position.z, -4) + } + + @Test + fun `correct negative z large`() { + val position = SectionPosition(0, 0, -1875000) + assertEquals(position.z, -1875000) + } + + + @Test + fun `correct plus z`() { + val position = SectionPosition(0xF, 0xF, 2) + assertEquals(position.plusZ().z, 3) + } + + @Test + fun `correct plus 2 z`() { + val position = SectionPosition(0xF, 0xF, 2) + assertEquals(position.plusZ(2).z, 4) + } + + @Test + fun `correct minus z`() { + val position = SectionPosition(0xF, 0xF, 2) + assertEquals(position.minusZ().z, 1) + } + + @Test + fun `unary minus`() { + val position = -SectionPosition(2, 2, 2) + assertEquals(position.x, -2) + assertEquals(position.y, -2) + assertEquals(position.z, -2) + } + + @Test + fun `unary plus`() { + val position = +SectionPosition(2, 2, 2) + assertEquals(position.x, 2) + assertEquals(position.y, 2) + assertEquals(position.z, 2) + } +}