From ca8e12a7f97167389be2cd702219fce234b246ae Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Wed, 5 Mar 2025 21:15:39 +0100 Subject: [PATCH] direction vector inlined, reduces stack size in world visibility graph --- .../world/chunk/manager/ChunkManagerTest.kt | 2 +- .../java/de/bixilon/minosoft/data/Axes.kt | 11 +- .../data/direction/DirectionVector.kt | 53 +++++++++ .../minosoft/data/direction/Directions.kt | 26 +++-- .../blocks/types/pixlyzer/wall/LeverBlock.kt | 2 +- .../minosoft/data/world/chunk/ChunkSection.kt | 4 +- .../data/world/chunk/light/SectionLight.kt | 30 ++--- .../world/container/block/SectionOcclusion.kt | 16 ++- .../data/world/positions/BlockPositionUtil.kt | 4 - .../data/world/positions/InSectionPosition.kt | 12 +- .../camera/visibility/WorldVisibilityGraph.kt | 38 +++--- .../gui/rendering/chunk/mesher/ChunkMesher.kt | 2 +- .../chunk/queue/queue/ChunkQueueMaster.kt | 10 +- .../skeletal/model/elements/SkeletalFace.kt | 4 +- .../minosoft/gui/rendering/util/VecUtil.kt | 10 +- .../gui/rendering/util/vec/vec3/Vec3Util.kt | 4 + .../gui/rendering/util/vec/vec3/Vec3dUtil.kt | 4 + .../gui/rendering/util/vec/vec3/Vec3iUtil.kt | 4 + .../data/direction/DirectionVectorTest.kt | 110 ++++++++++++++++++ .../minosoft/data/direction/DirectionsTest.kt | 51 ++++++++ 20 files changed, 308 insertions(+), 89 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/data/direction/DirectionVector.kt create mode 100644 src/test/java/de/bixilon/minosoft/data/direction/DirectionVectorTest.kt create mode 100644 src/test/java/de/bixilon/minosoft/data/direction/DirectionsTest.kt diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/manager/ChunkManagerTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/manager/ChunkManagerTest.kt index cab6d62bf..cc5c76735 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/manager/ChunkManagerTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/manager/ChunkManagerTest.kt @@ -248,7 +248,7 @@ class ChunkManagerTest { val manager = create() val matrix = manager.createMatrix() matrix[1][1].getOrPut(3) - assertEquals(manager[0, 0]!![3]!!.sectionHeight, 3) + assertEquals(manager[0, 0]!![3]!!.height, 3) } fun singleBlockUpdateWorld() { diff --git a/src/main/java/de/bixilon/minosoft/data/Axes.kt b/src/main/java/de/bixilon/minosoft/data/Axes.kt index ad3a49c29..73bd015cb 100644 --- a/src/main/java/de/bixilon/minosoft/data/Axes.kt +++ b/src/main/java/de/bixilon/minosoft/data/Axes.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. * @@ -14,7 +14,6 @@ package de.bixilon.minosoft.data import de.bixilon.kutil.enums.EnumUtil import de.bixilon.kutil.enums.ValuesEnum -import de.bixilon.minosoft.data.direction.Directions enum class Axes { X { @@ -37,13 +36,5 @@ enum class Axes { companion object : ValuesEnum { override val VALUES: Array = values() override val NAME_MAP: Map = EnumUtil.getEnumValues(VALUES) - - operator fun get(direction: Directions): Axes { - return when (direction) { - Directions.EAST, Directions.WEST -> X - Directions.UP, Directions.DOWN -> Y - Directions.NORTH, Directions.SOUTH -> Z - } - } } } diff --git a/src/main/java/de/bixilon/minosoft/data/direction/DirectionVector.kt b/src/main/java/de/bixilon/minosoft/data/direction/DirectionVector.kt new file mode 100644 index 000000000..c0e3f27a7 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/direction/DirectionVector.kt @@ -0,0 +1,53 @@ +/* + * 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.direction + +import de.bixilon.minosoft.data.Axes + +@JvmInline +value class DirectionVector private constructor(val value: Int) { + constructor() : this(0) + + inline val x: Int get() = Integer.signum((value and (MASK shl SHIFT_X)) shl (Int.SIZE_BITS - SHIFT_X - BITS)) + inline val y: Int get() = Integer.signum((value and (MASK shl SHIFT_Y)) shl (Int.SIZE_BITS - SHIFT_Y - BITS)) + inline val z: Int get() = Integer.signum((value and (MASK shl SHIFT_Z)) shl (Int.SIZE_BITS - SHIFT_Z - BITS)) + + operator fun get(axis: Axes) = when (axis) { + Axes.X -> x + Axes.Y -> y + Axes.Z -> z + } + + + fun with(direction: Directions): DirectionVector { + val shift = (direction.axis.ordinal * BITS) + val mask = MASK shl shift + + val without = value and mask.inv() + + val value = if (direction.negative) 0x02 else 0x01 + + return DirectionVector(without or (value shl shift)) + } + + + companion object { + const val BITS = 2 + const val MASK = (1 shl BITS) - 1 + + const val SHIFT_X = 0 + const val SHIFT_Y = SHIFT_X + BITS + const val SHIFT_Z = SHIFT_Y + BITS + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/direction/Directions.kt b/src/main/java/de/bixilon/minosoft/data/direction/Directions.kt index 440f83f0c..2b8ea545a 100644 --- a/src/main/java/de/bixilon/minosoft/data/direction/Directions.kt +++ b/src/main/java/de/bixilon/minosoft/data/direction/Directions.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2024 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. * @@ -22,26 +22,30 @@ import de.bixilon.kutil.enums.ValuesEnum import de.bixilon.kutil.reflection.ReflectionUtil.forceSet import de.bixilon.kutil.reflection.ReflectionUtil.jvmField import de.bixilon.minosoft.data.Axes -import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.get +import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3Util.invoke +import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.invoke +import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.invoke +import kotlin.collections.set enum class Directions( - val vector: Vec3i, + val axis: Axes, val index: Vec3i, ) { - DOWN(Vec3i(0, -1, 0), Vec3i(1, -1, 1)), - UP(Vec3i(0, 1, 0), Vec3i(3, -1, 3)), - NORTH(Vec3i(0, 0, -1), Vec3i(0, 0, -1)), - SOUTH(Vec3i(0, 0, 1), Vec3i(2, 2, -1)), - WEST(Vec3i(-1, 0, 0), Vec3i(-1, 3, 2)), - EAST(Vec3i(1, 0, 0), Vec3i(-1, 1, 0)), + DOWN(Axes.Y, Vec3i(1, -1, 1)), // y- + UP(Axes.Y, Vec3i(3, -1, 3)), // y+ + NORTH(Axes.Z, Vec3i(0, 0, -1)), // z- + SOUTH(Axes.Z, Vec3i(2, 2, -1)), // z+ + WEST(Axes.X, Vec3i(-1, 3, 2)), // x- + EAST(Axes.X, Vec3i(-1, 1, 0)), // x+ ; val negative = ordinal % 2 == 0 + val vector = DirectionVector().with(this) + val vectori = Vec3i(vector) val vectorf = Vec3(vector) val vectord = Vec3d(vector) - val axis: Axes = unsafeNull() val inverted: Directions = unsafeNull() private fun invert(): Directions { @@ -100,10 +104,8 @@ enum class Directions( init { val inverted = Directions::inverted.jvmField - val axis = Directions::axis.jvmField for (direction in VALUES) { inverted.forceSet(direction, direction.invert()) - axis.forceSet(direction, Axes[direction]) } NAME_MAP.unsafeCast>()["bottom"] = DOWN } diff --git a/src/main/java/de/bixilon/minosoft/data/registries/blocks/types/pixlyzer/wall/LeverBlock.kt b/src/main/java/de/bixilon/minosoft/data/registries/blocks/types/pixlyzer/wall/LeverBlock.kt index 872011cea..a551065fe 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/blocks/types/pixlyzer/wall/LeverBlock.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/blocks/types/pixlyzer/wall/LeverBlock.kt @@ -46,7 +46,7 @@ open class LeverBlock(resourceLocation: ResourceLocation, registries: Registries val direction = blockState.getFacing().inverted val mountDirection = getRealFacing(blockState) - val position = (Vec3d(blockPosition) + 0.5).plus((direction.vector * 0.1) + (mountDirection.vector * 0.2)) + val position = (Vec3d(blockPosition) + 0.5).plus((direction.vectord * 0.1) + (mountDirection.vectord * 0.2)) particle += DustParticle(session, position, Vec3d.EMPTY, DustParticleData(Colors.TRUE_RED, scale, dustParticleType)) } 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 36235a65d..0f1f68e25 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 @@ -30,7 +30,7 @@ import java.util.* * Collection of 16x16x16 blocks */ class ChunkSection( - val sectionHeight: Int, + val height: Int, val chunk: Chunk, ) { val blocks = BlockSectionDataProvider(chunk.lock, this) @@ -43,7 +43,7 @@ class ChunkSection( fun tick(session: PlaySession, random: Random) { if (blockEntities.isEmpty) return - val offset = BlockPosition.of(chunk.position, sectionHeight) + val offset = BlockPosition.of(chunk.position, height) var position = BlockPosition() val min = blockEntities.minPosition diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt index b8b2e5622..a49ba1620 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt @@ -66,14 +66,14 @@ class SectionLight( val neighbours = section.neighbours ?: return val chunk = section.chunk if (position.y - light < 0) { - if (section.sectionHeight == chunk.minSection) { + if (section.height == chunk.minSection) { chunk.light.bottom.decreaseCheckLevel(position.x, position.z, light - position.y, reset) } else { neighbours[Directions.O_DOWN]?.light?.decreaseCheckLevel(position.x, position.z, light - position.y, reset) } } if (position.y + light > ProtocolDefinition.SECTION_MAX_Y) { - if (section.sectionHeight == chunk.maxSection) { + if (section.height == chunk.maxSection) { chunk.light.top.decreaseCheckLevel(position.x, position.z, light - (ProtocolDefinition.SECTION_MAX_Y - position.y), reset) } else { neighbours[Directions.O_UP]?.light?.decreaseCheckLevel(position.x, position.z, light - (ProtocolDefinition.SECTION_MAX_Y - position.y), reset) @@ -162,19 +162,19 @@ class SectionLight( if (target == null || (target != Directions.UP && lightProperties.propagatesLight(Directions.DOWN))) { if (position.y > 0) { traceBlockIncrease(position.minusY(), neighbourLuminance, Directions.DOWN) - } else if (section.sectionHeight == chunk.minSection) { + } else if (section.height == chunk.minSection) { chunk.light.bottom.traceBlockIncrease(position.x, position.z, neighbourLuminance) } else { - (neighbours[Directions.O_DOWN] ?: chunk.getOrPut(section.sectionHeight - 1, false))?.light?.traceBlockIncrease(position.with(y = ProtocolDefinition.SECTION_MAX_Y), neighbourLuminance, Directions.DOWN) + (neighbours[Directions.O_DOWN] ?: chunk.getOrPut(section.height - 1, false))?.light?.traceBlockIncrease(position.with(y = ProtocolDefinition.SECTION_MAX_Y), neighbourLuminance, Directions.DOWN) } } if (target == null || (target != Directions.DOWN && lightProperties.propagatesLight(Directions.UP))) { if (position.y < ProtocolDefinition.SECTION_MAX_Y) { traceBlockIncrease(position.plusY(), neighbourLuminance, Directions.UP) - } else if (section.sectionHeight == chunk.maxSection) { + } else if (section.height == chunk.maxSection) { chunk.light.top.traceBlockIncrease(position.x, position.z, neighbourLuminance) } else { - (neighbours[Directions.O_UP] ?: chunk.getOrPut(section.sectionHeight + 1, false))?.light?.traceBlockIncrease(position.with(y = 0), neighbourLuminance, Directions.UP) + (neighbours[Directions.O_UP] ?: chunk.getOrPut(section.height + 1, false))?.light?.traceBlockIncrease(position.with(y = 0), neighbourLuminance, Directions.UP) } } @@ -242,7 +242,7 @@ class SectionLight( } } section.chunk.lock.unlock() - section.chunk.light.sky.recalculate(section.sectionHeight) + section.chunk.light.sky.recalculate(section.height) } @@ -251,7 +251,7 @@ class SectionLight( // ToDo(p): this::traceIncrease checks als the block light level, not needed // ToDo: Check if current block can propagate into that direction - val baseY = section.sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y + val baseY = section.height * ProtocolDefinition.SECTION_HEIGHT_Y for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { if (neighbours[Directions.O_DOWN] != null || neighbours[Directions.O_UP] != null) { propagateY(neighbours, x, baseY) @@ -354,17 +354,17 @@ class SectionLight( if (target != Directions.UP && (target == null || lightProperties.propagatesLight(Directions.DOWN))) { if (position.y > 0) { traceSkyLightIncrease(position.minusY(), nextNeighbourLevel, Directions.DOWN, totalY - 1) - } else if (section.sectionHeight == chunk.minSection) { + } else if (section.height == chunk.minSection) { chunk.light.bottom.traceSkyIncrease(position.x, position.z, nextLevel) } else { - (neighbours[Directions.O_DOWN] ?: chunk.getOrPut(section.sectionHeight - 1, false))?.light?.traceSkyLightIncrease(position.with(y = ProtocolDefinition.SECTION_MAX_Y), nextNeighbourLevel, Directions.DOWN, totalY - 1) + (neighbours[Directions.O_DOWN] ?: chunk.getOrPut(section.height - 1, false))?.light?.traceSkyLightIncrease(position.with(y = ProtocolDefinition.SECTION_MAX_Y), nextNeighbourLevel, Directions.DOWN, totalY - 1) } } if (target != Directions.DOWN && (target != null || lightProperties.propagatesLight(Directions.UP))) { if (position.y < ProtocolDefinition.SECTION_MAX_Y) { traceSkyLightIncrease(position.plusY(), nextNeighbourLevel, Directions.UP, totalY + 1) - } else if (section.sectionHeight < chunk.maxSection) { - (neighbours[Directions.O_UP] ?: chunk.getOrPut(section.sectionHeight + 1, false))?.light?.traceSkyLightIncrease(position.with(y = 0), nextNeighbourLevel, Directions.UP, totalY + 1) + } else if (section.height < chunk.maxSection) { + (neighbours[Directions.O_UP] ?: chunk.getOrPut(section.height + 1, false))?.light?.traceSkyLightIncrease(position.with(y = 0), nextNeighbourLevel, Directions.UP, totalY + 1) } } if (target != Directions.SOUTH && (target == null || lightProperties.propagatesLight(Directions.NORTH))) { @@ -422,13 +422,13 @@ class SectionLight( if (position.y > 0) { traceSkyLightIncrease(position.minusY(), NEIGHBOUR_TRACE_LEVEL, Directions.DOWN, totalY - 1) } else { - (neighbours[Directions.O_DOWN] ?: chunk.getOrPut(section.sectionHeight - 1, false))?.light?.traceSkyLightIncrease(position.with(y = ProtocolDefinition.SECTION_MAX_Y), NEIGHBOUR_TRACE_LEVEL, Directions.DOWN, totalY - 1) + (neighbours[Directions.O_DOWN] ?: chunk.getOrPut(section.height - 1, false))?.light?.traceSkyLightIncrease(position.with(y = ProtocolDefinition.SECTION_MAX_Y), NEIGHBOUR_TRACE_LEVEL, Directions.DOWN, totalY - 1) } } } private inline operator fun Array.get(direction: Int, neighbour: Directions, neighbours: ChunkNeighbourArray): ChunkSection? { - return this[direction] ?: neighbours[neighbour]?.getOrPut(section.sectionHeight, false) + return this[direction] ?: neighbours[neighbour]?.getOrPut(section.height, false) } fun propagateFromNeighbours(position: InSectionPosition) { @@ -488,7 +488,7 @@ class SectionLight( traceBlockIncrease(position, blockLight - 1, null) - val totalY = section.sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y + position.y + val totalY = section.height * ProtocolDefinition.SECTION_HEIGHT_Y + position.y section.chunk.let { // check if neighbours are above heightmap, if so set light level to max val chunkNeighbours = it.neighbours.neighbours diff --git a/src/main/java/de/bixilon/minosoft/data/world/container/block/SectionOcclusion.kt b/src/main/java/de/bixilon/minosoft/data/world/container/block/SectionOcclusion.kt index 5433df8b5..f1127a95c 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/container/block/SectionOcclusion.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/container/block/SectionOcclusion.kt @@ -54,12 +54,20 @@ class SectionOcclusion( try { val regions = floodFill(array) update(calculateOcclusion(regions), notify) + } catch (error: StackOverflowError) { + try { + val regions = floodFill(array) + update(calculateOcclusion(regions), notify) + } catch (error: StackOverflowError) { + println("Error: ${provider.section.chunk.position}; h=${provider.section.height} (ss=${error.stackTrace.size})") + error.printStackTrace() + } } finally { ALLOCATOR.free(array) } } - private inline fun ShortArray.setIfUnset(position: InSectionPosition, region: Int): Boolean { + private inline fun ShortArray.setIfUnset(position: InSectionPosition, region: Short): Boolean { if (this[position.index] != EMPTY_REGION) { return true } @@ -68,11 +76,11 @@ class SectionOcclusion( this[position.index] = INVALID_REGION return true } - this[position.index] = region.toShort() + this[position.index] = region return false } - private fun trace(regions: ShortArray, position: InSectionPosition, region: Int) { + private fun trace(regions: ShortArray, position: InSectionPosition, region: Short) { if (regions.setIfUnset(position, region)) return if (position.x > 0) trace(regions, position.minusX(), region) @@ -88,7 +96,7 @@ class SectionOcclusion( Arrays.fill(array, EMPTY_REGION) for (index in 0 until ProtocolDefinition.BLOCKS_PER_SECTION) { - trace(array, InSectionPosition(index), index) + trace(array, InSectionPosition(index), index.toShort()) } return array diff --git a/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPositionUtil.kt b/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPositionUtil.kt index 6d07def1a..60ff01797 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPositionUtil.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPositionUtil.kt @@ -24,10 +24,6 @@ object BlockPositionUtil { return hash shr 16 } - inline fun assertPosition(condition: Boolean, message: String) { - if (!DebugOptions.VERIFY_COORDINATES) return - if (!condition) throw AssertionError("Position assert failed: $message") - } inline fun assertPosition(condition: Boolean) { if (!DebugOptions.VERIFY_COORDINATES) return if (!condition) throw AssertionError("Position assert failed!") diff --git a/src/main/java/de/bixilon/minosoft/data/world/positions/InSectionPosition.kt b/src/main/java/de/bixilon/minosoft/data/world/positions/InSectionPosition.kt index 4441e5b04..db6ce1f92 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/positions/InSectionPosition.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/positions/InSectionPosition.kt @@ -40,7 +40,7 @@ value class InSectionPosition( inline fun plusX(): InSectionPosition { - assertPosition(this.x < ProtocolDefinition.SECTION_MAX_X, "x < max") + assertPosition(this.x < ProtocolDefinition.SECTION_MAX_X) return InSectionPosition(index + X * 1) } @@ -50,12 +50,12 @@ value class InSectionPosition( } inline fun minusX(): InSectionPosition { - assertPosition(this.x > 0, "x > 0") + assertPosition(this.x > 0) return InSectionPosition(index - X * 1) } inline fun plusY(): InSectionPosition { - assertPosition(this.y < ProtocolDefinition.SECTION_MAX_Y, "y < max") + assertPosition(this.y < ProtocolDefinition.SECTION_MAX_Y) return InSectionPosition(index + Y * 1) } @@ -65,12 +65,12 @@ value class InSectionPosition( } inline fun minusY(): InSectionPosition { - assertPosition(this.y > 0, "y > 0") + assertPosition(this.y > 0) return InSectionPosition(index - Y * 1) } inline fun plusZ(): InSectionPosition { - assertPosition(this.z < ProtocolDefinition.SECTION_MAX_Z, "z < max") + assertPosition(this.z < ProtocolDefinition.SECTION_MAX_Z) return InSectionPosition(index + Z * 1) } @@ -80,7 +80,7 @@ value class InSectionPosition( } inline fun minusZ(): InSectionPosition { - assertPosition(this.z > 0, "z > 0") + assertPosition(this.z > 0) return InSectionPosition(index - Z * 1) } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/camera/visibility/WorldVisibilityGraph.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/camera/visibility/WorldVisibilityGraph.kt index 16c84ef71..0de2ceab1 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/camera/visibility/WorldVisibilityGraph.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/camera/visibility/WorldVisibilityGraph.kt @@ -18,6 +18,7 @@ import de.bixilon.kotlinglm.vec2.Vec2i import de.bixilon.kutil.array.ArrayUtil.isIndex import de.bixilon.kutil.array.BooleanArrayUtil.trySet import de.bixilon.kutil.observer.DataObserver.Companion.observe +import de.bixilon.minosoft.data.direction.DirectionVector import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.registries.shapes.aabb.AABB import de.bixilon.minosoft.data.world.chunk.chunk.Chunk @@ -227,7 +228,7 @@ class WorldVisibilityGraph( return frustum.containsChunkSection(chunkPosition, sectionHeight) } - private fun VisibilityGraph.checkSection(chunkPosition: ChunkPosition, sectionIndex: Int, chunk: Chunk, visibilities: BooleanArray, direction: Directions, directionX: Int, directionY: Int, directionZ: Int, ignoreVisibility: Boolean) { + private fun VisibilityGraph.checkSection(chunkPosition: ChunkPosition, sectionIndex: Int, chunk: Chunk, visibilities: BooleanArray, direction: Directions, vector: DirectionVector, ignoreVisibility: Boolean) { if ((direction == Directions.UP && sectionIndex >= maxIndex) || (direction == Directions.DOWN && sectionIndex < 0)) { return } @@ -246,63 +247,63 @@ class WorldVisibilityGraph( val section = chunk.sections.getOrNull(sectionIndex)?.blocks - if (directionX <= 0 && (section?.occlusion?.isOccluded(inverted, Directions.WEST) != true) && chunkPosition.x > chunkMin.x) { - val next = chunkPosition.minusX() + if (vector.x <= 0 && (section?.occlusion?.isOccluded(inverted, Directions.WEST) != true) && chunkPosition.x > chunkMin.x) { val nextChunk = chunk.neighbours[Directions.WEST] if (nextChunk != null) { + val next = chunkPosition.minusX() val nextVisibilities = getVisibility(next) ?: return if (!nextVisibilities[visibilitySectionIndex]) { nextVisibilities[visibilitySectionIndex] = true - checkSection(next, sectionIndex, nextChunk, nextVisibilities, Directions.WEST, -1, directionY, directionZ, false) + checkSection(next, sectionIndex, nextChunk, nextVisibilities, Directions.WEST, vector.with(Directions.WEST), false) } } } - if (directionX >= 0 && (section?.occlusion?.isOccluded(inverted, Directions.EAST) != true) && chunkPosition.x < chunkMax.x) { - val next = chunkPosition.plusX() + if (vector.x >= 0 && (section?.occlusion?.isOccluded(inverted, Directions.EAST) != true) && chunkPosition.x < chunkMax.x) { val nextChunk = chunk.neighbours[Directions.EAST] if (nextChunk != null) { + val next = chunkPosition.plusX() val nextVisibilities = getVisibility(next) ?: return if (!nextVisibilities[visibilitySectionIndex]) { nextVisibilities[visibilitySectionIndex] = true - checkSection(next, sectionIndex, nextChunk, nextVisibilities, Directions.EAST, 1, directionY, directionZ, false) + checkSection(next, sectionIndex, nextChunk, nextVisibilities, Directions.EAST, vector.with(Directions.EAST), false) } } } - if (sectionIndex > 0 && directionY <= 0 && (section?.occlusion?.isOccluded(inverted, Directions.DOWN) != true)) { + if (sectionIndex > 0 && vector.y <= 0 && (section?.occlusion?.isOccluded(inverted, Directions.DOWN) != true)) { if (!visibilities[visibilitySectionIndex - 1]) { visibilities[visibilitySectionIndex - 1] = true - checkSection(chunkPosition, sectionIndex - 1, chunk, visibilities, Directions.DOWN, directionX, -1, directionZ, false) + checkSection(chunkPosition, sectionIndex - 1, chunk, visibilities, Directions.DOWN, vector.with(Directions.DOWN), false) } } - if (sectionIndex < maxIndex && directionY >= 0 && (section?.occlusion?.isOccluded(inverted, Directions.UP) != true)) { + if (sectionIndex < maxIndex && vector.y >= 0 && (section?.occlusion?.isOccluded(inverted, Directions.UP) != true)) { if (!visibilities[visibilitySectionIndex + 1]) { visibilities[visibilitySectionIndex + 1] = true - checkSection(chunkPosition, sectionIndex + 1, chunk, visibilities, Directions.UP, directionX, 1, directionZ, false) + checkSection(chunkPosition, sectionIndex + 1, chunk, visibilities, Directions.UP, vector.with(Directions.UP), false) } } - if (directionZ <= 0 && (section?.occlusion?.isOccluded(inverted, Directions.NORTH) != true) && chunkPosition.z > chunkMin.z) { - val next = chunkPosition.minusZ() + if (vector.z <= 0 && (section?.occlusion?.isOccluded(inverted, Directions.NORTH) != true) && chunkPosition.z > chunkMin.z) { val nextChunk = chunk.neighbours[Directions.NORTH] if (nextChunk != null) { + val next = chunkPosition.minusZ() val nextVisibilities = getVisibility(next) ?: return if (!nextVisibilities[visibilitySectionIndex]) { nextVisibilities[visibilitySectionIndex] = true - checkSection(next, sectionIndex, nextChunk, nextVisibilities, Directions.NORTH, directionX, directionY, -1, false) + checkSection(next, sectionIndex, nextChunk, nextVisibilities, Directions.NORTH, vector.with(Directions.NORTH), false) } } } - if (directionZ >= 0 && (section?.occlusion?.isOccluded(inverted, Directions.SOUTH) != true) && chunkPosition.z < chunkMax.z) { - val next = chunkPosition.plusZ() + if (vector.z >= 0 && (section?.occlusion?.isOccluded(inverted, Directions.SOUTH) != true) && chunkPosition.z < chunkMax.z) { val nextChunk = chunk.neighbours[Directions.SOUTH] if (nextChunk != null) { + val next = chunkPosition.plusZ() val nextVisibilities = getVisibility(next) ?: return if (!nextVisibilities[visibilitySectionIndex]) { nextVisibilities[visibilitySectionIndex] = true - checkSection(next, sectionIndex, nextChunk, nextVisibilities, Directions.SOUTH, directionX, directionY, 1, false) + checkSection(next, sectionIndex, nextChunk, nextVisibilities, Directions.SOUTH, vector.with(Directions.SOUTH), false) } } } @@ -313,8 +314,7 @@ class WorldVisibilityGraph( val next = chunkPosition + direction val nextChunk = session.world.chunks[next] ?: return val nextVisibility = getVisibility(next) - val vector = direction.vector - checkSection(next, cameraSectionIndex + vector.y, nextChunk, nextVisibility ?: return, direction, vector.x, vector.y, vector.z, true) + checkSection(next, cameraSectionIndex + direction.vector.y, nextChunk, nextVisibility ?: return, direction, direction.vector, true) } @Synchronized 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 d5afcbc56..30ee1886a 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 @@ -37,7 +37,7 @@ class ChunkMesher( renderer.unload(item) return null } - val sectionNeighbours = ChunkUtil.getDirectNeighbours(neighbours.neighbours, item.chunk, item.section.sectionHeight) + val sectionNeighbours = ChunkUtil.getDirectNeighbours(neighbours.neighbours, item.chunk, item.section.height) val mesh = ChunkMeshes(renderer.context, item.chunkPosition, item.sectionHeight, item.section.smallMesh) try { solid.mesh(item.chunkPosition, item.sectionHeight, item.chunk, item.section, neighbours.neighbours, sectionNeighbours, mesh) 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 5d07b3f2b..a3303e5bd 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 @@ -33,19 +33,19 @@ class ChunkQueueMaster( private fun queue(section: ChunkSection, chunk: Chunk, force: Boolean): Boolean { if (section.blocks.isEmpty) { - renderer.unload(QueuePosition(chunk.position, section.sectionHeight)) + renderer.unload(QueuePosition(chunk.position, section.height)) return false } - val visible = force || renderer.visibilityGraph.isSectionVisible(chunk.position, section.sectionHeight, section.blocks.minPosition, section.blocks.maxPosition, true) + val visible = force || renderer.visibilityGraph.isSectionVisible(chunk.position, section.height, section.blocks.minPosition, section.blocks.maxPosition, true) if (visible) { - val center = CHUNK_CENTER + BlockPosition.of(chunk.position, section.sectionHeight) - val item = WorldQueueItem(chunk.position, section.sectionHeight, chunk, section, center) + val center = CHUNK_CENTER + BlockPosition.of(chunk.position, section.height) + val item = WorldQueueItem(chunk.position, section.height, chunk, section, center) renderer.meshingQueue.queue(item) return true } - renderer.culledQueue.queue(chunk.position, section.sectionHeight) + renderer.culledQueue.queue(chunk.position, section.height) return false } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/skeletal/model/elements/SkeletalFace.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/skeletal/model/elements/SkeletalFace.kt index 4c6a155bd..1280a1e4f 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/skeletal/model/elements/SkeletalFace.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/skeletal/model/elements/SkeletalFace.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. * @@ -45,7 +45,7 @@ data class SkeletalFace( ).toArray(direction, 0) - val normal = Vec3(direction.vector) + val normal = Vec3(direction.vectorf) if (context.rotation != null) { val origin = context.rotation.origin?.div(BLOCK_SIZE) ?: ((to + from) / 2.0f) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt index 01ea8c8b0..3fad1d9eb 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt @@ -132,11 +132,11 @@ object VecUtil { } inline infix operator fun Vec3i.plus(direction: Directions?): Vec3i { - return this + direction?.vector + return this + direction?.vectori } inline infix operator fun Vec3i.plusAssign(direction: Directions?) { - this += direction?.vector ?: return + this += direction?.vectori ?: return } inline infix operator fun Vec3i.plus(input: Vec3): Vec3 { @@ -148,7 +148,7 @@ object VecUtil { } inline infix operator fun Vec2i.plus(direction: Directions): Vec2i { - return this + direction.vector + return this + direction.vectori } fun BlockPosition.getWorldOffset(offsetType: RandomOffsetTypes): Vec3 { @@ -207,8 +207,4 @@ object VecUtil { fun Double.noised(random: Random): Double { return random.nextDouble() / this * if (random.nextBoolean()) 1.0 else -1.0 } - - operator fun Directions.plus(direction: Directions): Vec3i { - return this.vector + direction.vector - } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3Util.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3Util.kt index a1e788e21..43a67f3d1 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3Util.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3Util.kt @@ -28,6 +28,7 @@ import de.bixilon.kutil.math.interpolation.FloatInterpolation.interpolateLinear import de.bixilon.kutil.math.simple.FloatMath.floor import de.bixilon.kutil.primitive.FloatUtil.toFloat import de.bixilon.minosoft.data.Axes +import de.bixilon.minosoft.data.direction.DirectionVector import de.bixilon.minosoft.data.text.formatting.color.RGBColor import de.bixilon.minosoft.data.world.positions.BlockPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition @@ -175,6 +176,9 @@ object Vec3Util { return Vec3(this[0], this[1], this[2]) } + @JvmName("constructorDirectionVector") + operator fun Vec3.Companion.invoke(vector: DirectionVector) = Vec3(vector.x, vector.y, vector.z) + @JvmName("constructorBlockPosition") operator fun Vec3.Companion.invoke(position: BlockPosition) = Vec3(position.x, position.y, position.z) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3dUtil.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3dUtil.kt index ea40e1582..1c7d0c456 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3dUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3dUtil.kt @@ -21,6 +21,7 @@ import de.bixilon.kutil.math.interpolation.DoubleInterpolation.interpolateSine import de.bixilon.kutil.math.simple.DoubleMath.ceil import de.bixilon.kutil.math.simple.DoubleMath.floor import de.bixilon.minosoft.data.Axes +import de.bixilon.minosoft.data.direction.DirectionVector import de.bixilon.minosoft.data.world.positions.BlockPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition @@ -166,6 +167,9 @@ object Vec3dUtil { this.z = other.z } + @JvmName("constructorDirectionVector") + operator fun Vec3d.Companion.invoke(vector: DirectionVector) = Vec3d(vector.x, vector.y, vector.z) + @JvmName("constructorBlockPosition") operator fun Vec3d.Companion.invoke(position: BlockPosition) = Vec3d(position.x, position.y, position.z) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3iUtil.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3iUtil.kt index 6e39e7cd8..0a7f2f2e7 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3iUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec3/Vec3iUtil.kt @@ -18,6 +18,7 @@ import de.bixilon.kotlinglm.vec3.Vec3 import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.kutil.primitive.IntUtil.toInt import de.bixilon.minosoft.data.Axes +import de.bixilon.minosoft.data.direction.DirectionVector import de.bixilon.minosoft.data.world.positions.BlockPosition import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight @@ -95,4 +96,7 @@ object Vec3iUtil { } val Vec3i.blockPosition get() = BlockPosition(x, y, z) + + @JvmName("constructorDirectionVector") + operator fun Vec3i.Companion.invoke(vector: DirectionVector) = Vec3i(vector.x, vector.y, vector.z) } diff --git a/src/test/java/de/bixilon/minosoft/data/direction/DirectionVectorTest.kt b/src/test/java/de/bixilon/minosoft/data/direction/DirectionVectorTest.kt new file mode 100644 index 000000000..9f54f9e53 --- /dev/null +++ b/src/test/java/de/bixilon/minosoft/data/direction/DirectionVectorTest.kt @@ -0,0 +1,110 @@ +/* + * 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.direction + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class DirectionVectorTest { + + @Test + fun empty() { + val vector = DirectionVector() + assertEquals(vector.x, 0) + assertEquals(vector.y, 0) + assertEquals(vector.z, 0) + } + + @Test + fun down() { + val vector = DirectionVector().with(Directions.DOWN) + assertEquals(vector.x, 0) + assertEquals(vector.y, -1) + assertEquals(vector.z, 0) + } + + @Test + fun up() { + val vector = DirectionVector().with(Directions.UP) + assertEquals(vector.x, 0) + assertEquals(vector.y, 1) + assertEquals(vector.z, 0) + } + + + @Test + fun north() { + val vector = DirectionVector().with(Directions.NORTH) + assertEquals(vector.x, 0) + assertEquals(vector.y, 0) + assertEquals(vector.z, -1) + } + + @Test + fun south() { + val vector = DirectionVector().with(Directions.SOUTH) + assertEquals(vector.x, 0) + assertEquals(vector.y, 0) + assertEquals(vector.z, 1) + } + + @Test + fun west() { + val vector = DirectionVector().with(Directions.WEST) + assertEquals(vector.x, -1) + assertEquals(vector.y, 0) + assertEquals(vector.z, 0) + } + + @Test + fun east() { + val vector = DirectionVector().with(Directions.EAST) + assertEquals(vector.x, 1) + assertEquals(vector.y, 0) + assertEquals(vector.z, 0) + } + + + @Test + fun `north-west`() { + val vector = DirectionVector().with(Directions.NORTH).with(Directions.WEST) + assertEquals(vector.x, -1) + assertEquals(vector.y, 0) + assertEquals(vector.z, -1) + } + + @Test + fun `south-east`() { + val vector = DirectionVector().with(Directions.SOUTH).with(Directions.EAST) + assertEquals(vector.x, 1) + assertEquals(vector.y, 0) + assertEquals(vector.z, 1) + } + + @Test + fun `south-north`() { + val vector = DirectionVector().with(Directions.SOUTH).with(Directions.NORTH) + assertEquals(vector.x, 0) + assertEquals(vector.y, 0) + assertEquals(vector.z, -1) + } + + @Test + fun positive() { + val vector = DirectionVector().with(Directions.UP).with(Directions.SOUTH).with(Directions.EAST) + assertEquals(vector.x, 1) + assertEquals(vector.y, 1) + assertEquals(vector.z, 1) + } +} diff --git a/src/test/java/de/bixilon/minosoft/data/direction/DirectionsTest.kt b/src/test/java/de/bixilon/minosoft/data/direction/DirectionsTest.kt new file mode 100644 index 000000000..771fd9a62 --- /dev/null +++ b/src/test/java/de/bixilon/minosoft/data/direction/DirectionsTest.kt @@ -0,0 +1,51 @@ +/* + * 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.direction + +import de.bixilon.kotlinglm.vec3.Vec3i +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class DirectionsTest { + + @Test + fun `vector down`() { + assertEquals(Directions.DOWN.vectori, Vec3i(0, -1, 0)) + } + + @Test + fun `vector up`() { + assertEquals(Directions.UP.vectori, Vec3i(0, 1, 0)) + } + + @Test + fun `vector north`() { + assertEquals(Directions.NORTH.vectori, Vec3i(0, 0, -1)) + } + + @Test + fun `vector south`() { + assertEquals(Directions.SOUTH.vectori, Vec3i(0, 0, 1)) + } + + @Test + fun `vector west`() { + assertEquals(Directions.WEST.vectori, Vec3i(-1, 0, 0)) + } + + @Test + fun `vector east`() { + assertEquals(Directions.EAST.vectori, Vec3i(1, 0, 0)) + } +}