direction vector

inlined, reduces stack size in world visibility graph
This commit is contained in:
Moritz Zwerger 2025-03-05 21:15:39 +01:00
parent 39d97b5a40
commit ca8e12a7f9
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
20 changed files with 308 additions and 89 deletions

View File

@ -248,7 +248,7 @@ class ChunkManagerTest {
val manager = create() val manager = create()
val matrix = manager.createMatrix() val matrix = manager.createMatrix()
matrix[1][1].getOrPut(3) matrix[1][1].getOrPut(3)
assertEquals(manager[0, 0]!![3]!!.sectionHeight, 3) assertEquals(manager[0, 0]!![3]!!.height, 3)
} }
fun singleBlockUpdateWorld() { fun singleBlockUpdateWorld() {

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * 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. * 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.EnumUtil
import de.bixilon.kutil.enums.ValuesEnum import de.bixilon.kutil.enums.ValuesEnum
import de.bixilon.minosoft.data.direction.Directions
enum class Axes { enum class Axes {
X { X {
@ -37,13 +36,5 @@ enum class Axes {
companion object : ValuesEnum<Axes> { companion object : ValuesEnum<Axes> {
override val VALUES: Array<Axes> = values() override val VALUES: Array<Axes> = values()
override val NAME_MAP: Map<String, Axes> = EnumUtil.getEnumValues(VALUES) override val NAME_MAP: Map<String, Axes> = 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
}
}
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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
}
}

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * 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. * 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.forceSet
import de.bixilon.kutil.reflection.ReflectionUtil.jvmField import de.bixilon.kutil.reflection.ReflectionUtil.jvmField
import de.bixilon.minosoft.data.Axes 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( enum class Directions(
val vector: Vec3i, val axis: Axes,
val index: Vec3i, val index: Vec3i,
) { ) {
DOWN(Vec3i(0, -1, 0), Vec3i(1, -1, 1)), DOWN(Axes.Y, Vec3i(1, -1, 1)), // y-
UP(Vec3i(0, 1, 0), Vec3i(3, -1, 3)), UP(Axes.Y, Vec3i(3, -1, 3)), // y+
NORTH(Vec3i(0, 0, -1), Vec3i(0, 0, -1)), NORTH(Axes.Z, Vec3i(0, 0, -1)), // z-
SOUTH(Vec3i(0, 0, 1), Vec3i(2, 2, -1)), SOUTH(Axes.Z, Vec3i(2, 2, -1)), // z+
WEST(Vec3i(-1, 0, 0), Vec3i(-1, 3, 2)), WEST(Axes.X, Vec3i(-1, 3, 2)), // x-
EAST(Vec3i(1, 0, 0), Vec3i(-1, 1, 0)), EAST(Axes.X, Vec3i(-1, 1, 0)), // x+
; ;
val negative = ordinal % 2 == 0 val negative = ordinal % 2 == 0
val vector = DirectionVector().with(this)
val vectori = Vec3i(vector)
val vectorf = Vec3(vector) val vectorf = Vec3(vector)
val vectord = Vec3d(vector) val vectord = Vec3d(vector)
val axis: Axes = unsafeNull()
val inverted: Directions = unsafeNull() val inverted: Directions = unsafeNull()
private fun invert(): Directions { private fun invert(): Directions {
@ -100,10 +104,8 @@ enum class Directions(
init { init {
val inverted = Directions::inverted.jvmField val inverted = Directions::inverted.jvmField
val axis = Directions::axis.jvmField
for (direction in VALUES) { for (direction in VALUES) {
inverted.forceSet(direction, direction.invert()) inverted.forceSet(direction, direction.invert())
axis.forceSet(direction, Axes[direction])
} }
NAME_MAP.unsafeCast<MutableMap<String, Directions>>()["bottom"] = DOWN NAME_MAP.unsafeCast<MutableMap<String, Directions>>()["bottom"] = DOWN
} }

View File

@ -46,7 +46,7 @@ open class LeverBlock(resourceLocation: ResourceLocation, registries: Registries
val direction = blockState.getFacing().inverted val direction = blockState.getFacing().inverted
val mountDirection = getRealFacing(blockState) 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)) particle += DustParticle(session, position, Vec3d.EMPTY, DustParticleData(Colors.TRUE_RED, scale, dustParticleType))
} }

View File

@ -30,7 +30,7 @@ import java.util.*
* Collection of 16x16x16 blocks * Collection of 16x16x16 blocks
*/ */
class ChunkSection( class ChunkSection(
val sectionHeight: Int, val height: Int,
val chunk: Chunk, val chunk: Chunk,
) { ) {
val blocks = BlockSectionDataProvider(chunk.lock, this) val blocks = BlockSectionDataProvider(chunk.lock, this)
@ -43,7 +43,7 @@ class ChunkSection(
fun tick(session: PlaySession, random: Random) { fun tick(session: PlaySession, random: Random) {
if (blockEntities.isEmpty) return if (blockEntities.isEmpty) return
val offset = BlockPosition.of(chunk.position, sectionHeight) val offset = BlockPosition.of(chunk.position, height)
var position = BlockPosition() var position = BlockPosition()
val min = blockEntities.minPosition val min = blockEntities.minPosition

View File

@ -66,14 +66,14 @@ class SectionLight(
val neighbours = section.neighbours ?: return val neighbours = section.neighbours ?: return
val chunk = section.chunk val chunk = section.chunk
if (position.y - light < 0) { 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) chunk.light.bottom.decreaseCheckLevel(position.x, position.z, light - position.y, reset)
} else { } else {
neighbours[Directions.O_DOWN]?.light?.decreaseCheckLevel(position.x, position.z, light - position.y, reset) neighbours[Directions.O_DOWN]?.light?.decreaseCheckLevel(position.x, position.z, light - position.y, reset)
} }
} }
if (position.y + light > ProtocolDefinition.SECTION_MAX_Y) { 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) chunk.light.top.decreaseCheckLevel(position.x, position.z, light - (ProtocolDefinition.SECTION_MAX_Y - position.y), reset)
} else { } else {
neighbours[Directions.O_UP]?.light?.decreaseCheckLevel(position.x, position.z, light - (ProtocolDefinition.SECTION_MAX_Y - position.y), reset) 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 (target == null || (target != Directions.UP && lightProperties.propagatesLight(Directions.DOWN))) {
if (position.y > 0) { if (position.y > 0) {
traceBlockIncrease(position.minusY(), neighbourLuminance, Directions.DOWN) 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) chunk.light.bottom.traceBlockIncrease(position.x, position.z, neighbourLuminance)
} else { } 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 (target == null || (target != Directions.DOWN && lightProperties.propagatesLight(Directions.UP))) {
if (position.y < ProtocolDefinition.SECTION_MAX_Y) { if (position.y < ProtocolDefinition.SECTION_MAX_Y) {
traceBlockIncrease(position.plusY(), neighbourLuminance, Directions.UP) 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) chunk.light.top.traceBlockIncrease(position.x, position.z, neighbourLuminance)
} else { } 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.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(p): this::traceIncrease checks als the block light level, not needed
// ToDo: Check if current block can propagate into that direction // 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) { for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) {
if (neighbours[Directions.O_DOWN] != null || neighbours[Directions.O_UP] != null) { if (neighbours[Directions.O_DOWN] != null || neighbours[Directions.O_UP] != null) {
propagateY(neighbours, x, baseY) propagateY(neighbours, x, baseY)
@ -354,17 +354,17 @@ class SectionLight(
if (target != Directions.UP && (target == null || lightProperties.propagatesLight(Directions.DOWN))) { if (target != Directions.UP && (target == null || lightProperties.propagatesLight(Directions.DOWN))) {
if (position.y > 0) { if (position.y > 0) {
traceSkyLightIncrease(position.minusY(), nextNeighbourLevel, Directions.DOWN, totalY - 1) 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) chunk.light.bottom.traceSkyIncrease(position.x, position.z, nextLevel)
} else { } 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 (target != Directions.DOWN && (target != null || lightProperties.propagatesLight(Directions.UP))) {
if (position.y < ProtocolDefinition.SECTION_MAX_Y) { if (position.y < ProtocolDefinition.SECTION_MAX_Y) {
traceSkyLightIncrease(position.plusY(), nextNeighbourLevel, Directions.UP, totalY + 1) traceSkyLightIncrease(position.plusY(), nextNeighbourLevel, Directions.UP, totalY + 1)
} else if (section.sectionHeight < chunk.maxSection) { } else if (section.height < chunk.maxSection) {
(neighbours[Directions.O_UP] ?: chunk.getOrPut(section.sectionHeight + 1, false))?.light?.traceSkyLightIncrease(position.with(y = 0), nextNeighbourLevel, Directions.UP, totalY + 1) (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))) { if (target != Directions.SOUTH && (target == null || lightProperties.propagatesLight(Directions.NORTH))) {
@ -422,13 +422,13 @@ class SectionLight(
if (position.y > 0) { if (position.y > 0) {
traceSkyLightIncrease(position.minusY(), NEIGHBOUR_TRACE_LEVEL, Directions.DOWN, totalY - 1) traceSkyLightIncrease(position.minusY(), NEIGHBOUR_TRACE_LEVEL, Directions.DOWN, totalY - 1)
} else { } 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<ChunkSection?>.get(direction: Int, neighbour: Directions, neighbours: ChunkNeighbourArray): ChunkSection? { private inline operator fun Array<ChunkSection?>.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) { fun propagateFromNeighbours(position: InSectionPosition) {
@ -488,7 +488,7 @@ class SectionLight(
traceBlockIncrease(position, blockLight - 1, null) 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 { section.chunk.let {
// check if neighbours are above heightmap, if so set light level to max // check if neighbours are above heightmap, if so set light level to max
val chunkNeighbours = it.neighbours.neighbours val chunkNeighbours = it.neighbours.neighbours

View File

@ -54,12 +54,20 @@ class SectionOcclusion(
try { try {
val regions = floodFill(array) val regions = floodFill(array)
update(calculateOcclusion(regions), notify) 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 { } finally {
ALLOCATOR.free(array) 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) { if (this[position.index] != EMPTY_REGION) {
return true return true
} }
@ -68,11 +76,11 @@ class SectionOcclusion(
this[position.index] = INVALID_REGION this[position.index] = INVALID_REGION
return true return true
} }
this[position.index] = region.toShort() this[position.index] = region
return false 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 (regions.setIfUnset(position, region)) return
if (position.x > 0) trace(regions, position.minusX(), region) if (position.x > 0) trace(regions, position.minusX(), region)
@ -88,7 +96,7 @@ class SectionOcclusion(
Arrays.fill(array, EMPTY_REGION) Arrays.fill(array, EMPTY_REGION)
for (index in 0 until ProtocolDefinition.BLOCKS_PER_SECTION) { for (index in 0 until ProtocolDefinition.BLOCKS_PER_SECTION) {
trace(array, InSectionPosition(index), index) trace(array, InSectionPosition(index), index.toShort())
} }
return array return array

View File

@ -24,10 +24,6 @@ object BlockPositionUtil {
return hash shr 16 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) { inline fun assertPosition(condition: Boolean) {
if (!DebugOptions.VERIFY_COORDINATES) return if (!DebugOptions.VERIFY_COORDINATES) return
if (!condition) throw AssertionError("Position assert failed!") if (!condition) throw AssertionError("Position assert failed!")

View File

@ -40,7 +40,7 @@ value class InSectionPosition(
inline fun plusX(): 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) return InSectionPosition(index + X * 1)
} }
@ -50,12 +50,12 @@ value class InSectionPosition(
} }
inline fun minusX(): InSectionPosition { inline fun minusX(): InSectionPosition {
assertPosition(this.x > 0, "x > 0") assertPosition(this.x > 0)
return InSectionPosition(index - X * 1) return InSectionPosition(index - X * 1)
} }
inline fun plusY(): InSectionPosition { 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) return InSectionPosition(index + Y * 1)
} }
@ -65,12 +65,12 @@ value class InSectionPosition(
} }
inline fun minusY(): InSectionPosition { inline fun minusY(): InSectionPosition {
assertPosition(this.y > 0, "y > 0") assertPosition(this.y > 0)
return InSectionPosition(index - Y * 1) return InSectionPosition(index - Y * 1)
} }
inline fun plusZ(): InSectionPosition { 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) return InSectionPosition(index + Z * 1)
} }
@ -80,7 +80,7 @@ value class InSectionPosition(
} }
inline fun minusZ(): InSectionPosition { inline fun minusZ(): InSectionPosition {
assertPosition(this.z > 0, "z > 0") assertPosition(this.z > 0)
return InSectionPosition(index - Z * 1) return InSectionPosition(index - Z * 1)
} }

View File

@ -18,6 +18,7 @@ import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.kutil.array.ArrayUtil.isIndex import de.bixilon.kutil.array.ArrayUtil.isIndex
import de.bixilon.kutil.array.BooleanArrayUtil.trySet import de.bixilon.kutil.array.BooleanArrayUtil.trySet
import de.bixilon.kutil.observer.DataObserver.Companion.observe 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.direction.Directions
import de.bixilon.minosoft.data.registries.shapes.aabb.AABB import de.bixilon.minosoft.data.registries.shapes.aabb.AABB
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
@ -227,7 +228,7 @@ class WorldVisibilityGraph(
return frustum.containsChunkSection(chunkPosition, sectionHeight) 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)) { if ((direction == Directions.UP && sectionIndex >= maxIndex) || (direction == Directions.DOWN && sectionIndex < 0)) {
return return
} }
@ -246,63 +247,63 @@ class WorldVisibilityGraph(
val section = chunk.sections.getOrNull(sectionIndex)?.blocks val section = chunk.sections.getOrNull(sectionIndex)?.blocks
if (directionX <= 0 && (section?.occlusion?.isOccluded(inverted, Directions.WEST) != true) && chunkPosition.x > chunkMin.x) { if (vector.x <= 0 && (section?.occlusion?.isOccluded(inverted, Directions.WEST) != true) && chunkPosition.x > chunkMin.x) {
val next = chunkPosition.minusX()
val nextChunk = chunk.neighbours[Directions.WEST] val nextChunk = chunk.neighbours[Directions.WEST]
if (nextChunk != null) { if (nextChunk != null) {
val next = chunkPosition.minusX()
val nextVisibilities = getVisibility(next) ?: return val nextVisibilities = getVisibility(next) ?: return
if (!nextVisibilities[visibilitySectionIndex]) { if (!nextVisibilities[visibilitySectionIndex]) {
nextVisibilities[visibilitySectionIndex] = true 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) { if (vector.x >= 0 && (section?.occlusion?.isOccluded(inverted, Directions.EAST) != true) && chunkPosition.x < chunkMax.x) {
val next = chunkPosition.plusX()
val nextChunk = chunk.neighbours[Directions.EAST] val nextChunk = chunk.neighbours[Directions.EAST]
if (nextChunk != null) { if (nextChunk != null) {
val next = chunkPosition.plusX()
val nextVisibilities = getVisibility(next) ?: return val nextVisibilities = getVisibility(next) ?: return
if (!nextVisibilities[visibilitySectionIndex]) { if (!nextVisibilities[visibilitySectionIndex]) {
nextVisibilities[visibilitySectionIndex] = true 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]) { if (!visibilities[visibilitySectionIndex - 1]) {
visibilities[visibilitySectionIndex - 1] = true 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]) { if (!visibilities[visibilitySectionIndex + 1]) {
visibilities[visibilitySectionIndex + 1] = true 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) { if (vector.z <= 0 && (section?.occlusion?.isOccluded(inverted, Directions.NORTH) != true) && chunkPosition.z > chunkMin.z) {
val next = chunkPosition.minusZ()
val nextChunk = chunk.neighbours[Directions.NORTH] val nextChunk = chunk.neighbours[Directions.NORTH]
if (nextChunk != null) { if (nextChunk != null) {
val next = chunkPosition.minusZ()
val nextVisibilities = getVisibility(next) ?: return val nextVisibilities = getVisibility(next) ?: return
if (!nextVisibilities[visibilitySectionIndex]) { if (!nextVisibilities[visibilitySectionIndex]) {
nextVisibilities[visibilitySectionIndex] = true 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) { if (vector.z >= 0 && (section?.occlusion?.isOccluded(inverted, Directions.SOUTH) != true) && chunkPosition.z < chunkMax.z) {
val next = chunkPosition.plusZ()
val nextChunk = chunk.neighbours[Directions.SOUTH] val nextChunk = chunk.neighbours[Directions.SOUTH]
if (nextChunk != null) { if (nextChunk != null) {
val next = chunkPosition.plusZ()
val nextVisibilities = getVisibility(next) ?: return val nextVisibilities = getVisibility(next) ?: return
if (!nextVisibilities[visibilitySectionIndex]) { if (!nextVisibilities[visibilitySectionIndex]) {
nextVisibilities[visibilitySectionIndex] = true 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 next = chunkPosition + direction
val nextChunk = session.world.chunks[next] ?: return val nextChunk = session.world.chunks[next] ?: return
val nextVisibility = getVisibility(next) val nextVisibility = getVisibility(next)
val vector = direction.vector checkSection(next, cameraSectionIndex + direction.vector.y, nextChunk, nextVisibility ?: return, direction, direction.vector, true)
checkSection(next, cameraSectionIndex + vector.y, nextChunk, nextVisibility ?: return, direction, vector.x, vector.y, vector.z, true)
} }
@Synchronized @Synchronized

View File

@ -37,7 +37,7 @@ class ChunkMesher(
renderer.unload(item) renderer.unload(item)
return null 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) val mesh = ChunkMeshes(renderer.context, item.chunkPosition, item.sectionHeight, item.section.smallMesh)
try { try {
solid.mesh(item.chunkPosition, item.sectionHeight, item.chunk, item.section, neighbours.neighbours, sectionNeighbours, mesh) solid.mesh(item.chunkPosition, item.sectionHeight, item.chunk, item.section, neighbours.neighbours, sectionNeighbours, mesh)

View File

@ -33,19 +33,19 @@ class ChunkQueueMaster(
private fun queue(section: ChunkSection, chunk: Chunk, force: Boolean): Boolean { private fun queue(section: ChunkSection, chunk: Chunk, force: Boolean): Boolean {
if (section.blocks.isEmpty) { if (section.blocks.isEmpty) {
renderer.unload(QueuePosition(chunk.position, section.sectionHeight)) renderer.unload(QueuePosition(chunk.position, section.height))
return false 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) { if (visible) {
val center = CHUNK_CENTER + BlockPosition.of(chunk.position, section.sectionHeight) val center = CHUNK_CENTER + BlockPosition.of(chunk.position, section.height)
val item = WorldQueueItem(chunk.position, section.sectionHeight, chunk, section, center) val item = WorldQueueItem(chunk.position, section.height, chunk, section, center)
renderer.meshingQueue.queue(item) renderer.meshingQueue.queue(item)
return true return true
} }
renderer.culledQueue.queue(chunk.position, section.sectionHeight) renderer.culledQueue.queue(chunk.position, section.height)
return false return false
} }

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * 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. * 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) ).toArray(direction, 0)
val normal = Vec3(direction.vector) val normal = Vec3(direction.vectorf)
if (context.rotation != null) { if (context.rotation != null) {
val origin = context.rotation.origin?.div(BLOCK_SIZE) ?: ((to + from) / 2.0f) val origin = context.rotation.origin?.div(BLOCK_SIZE) ?: ((to + from) / 2.0f)

View File

@ -132,11 +132,11 @@ object VecUtil {
} }
inline infix operator fun Vec3i.plus(direction: Directions?): Vec3i { 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?) { 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 { inline infix operator fun Vec3i.plus(input: Vec3): Vec3 {
@ -148,7 +148,7 @@ object VecUtil {
} }
inline infix operator fun Vec2i.plus(direction: Directions): Vec2i { inline infix operator fun Vec2i.plus(direction: Directions): Vec2i {
return this + direction.vector return this + direction.vectori
} }
fun BlockPosition.getWorldOffset(offsetType: RandomOffsetTypes): Vec3 { fun BlockPosition.getWorldOffset(offsetType: RandomOffsetTypes): Vec3 {
@ -207,8 +207,4 @@ object VecUtil {
fun Double.noised(random: Random): Double { fun Double.noised(random: Random): Double {
return random.nextDouble() / this * if (random.nextBoolean()) 1.0 else -1.0 return random.nextDouble() / this * if (random.nextBoolean()) 1.0 else -1.0
} }
operator fun Directions.plus(direction: Directions): Vec3i {
return this.vector + direction.vector
}
} }

View File

@ -28,6 +28,7 @@ import de.bixilon.kutil.math.interpolation.FloatInterpolation.interpolateLinear
import de.bixilon.kutil.math.simple.FloatMath.floor import de.bixilon.kutil.math.simple.FloatMath.floor
import de.bixilon.kutil.primitive.FloatUtil.toFloat import de.bixilon.kutil.primitive.FloatUtil.toFloat
import de.bixilon.minosoft.data.Axes 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.text.formatting.color.RGBColor
import de.bixilon.minosoft.data.world.positions.BlockPosition import de.bixilon.minosoft.data.world.positions.BlockPosition
import de.bixilon.minosoft.data.world.positions.InChunkPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition
@ -175,6 +176,9 @@ object Vec3Util {
return Vec3(this[0], this[1], this[2]) 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") @JvmName("constructorBlockPosition")
operator fun Vec3.Companion.invoke(position: BlockPosition) = Vec3(position.x, position.y, position.z) operator fun Vec3.Companion.invoke(position: BlockPosition) = Vec3(position.x, position.y, position.z)

View File

@ -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.ceil
import de.bixilon.kutil.math.simple.DoubleMath.floor import de.bixilon.kutil.math.simple.DoubleMath.floor
import de.bixilon.minosoft.data.Axes 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.BlockPosition
import de.bixilon.minosoft.data.world.positions.InChunkPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition
import de.bixilon.minosoft.data.world.positions.InSectionPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition
@ -166,6 +167,9 @@ object Vec3dUtil {
this.z = other.z this.z = other.z
} }
@JvmName("constructorDirectionVector")
operator fun Vec3d.Companion.invoke(vector: DirectionVector) = Vec3d(vector.x, vector.y, vector.z)
@JvmName("constructorBlockPosition") @JvmName("constructorBlockPosition")
operator fun Vec3d.Companion.invoke(position: BlockPosition) = Vec3d(position.x, position.y, position.z) operator fun Vec3d.Companion.invoke(position: BlockPosition) = Vec3d(position.x, position.y, position.z)

View File

@ -18,6 +18,7 @@ import de.bixilon.kotlinglm.vec3.Vec3
import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.kutil.primitive.IntUtil.toInt import de.bixilon.kutil.primitive.IntUtil.toInt
import de.bixilon.minosoft.data.Axes 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.BlockPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
@ -95,4 +96,7 @@ object Vec3iUtil {
} }
val Vec3i.blockPosition get() = BlockPosition(x, y, z) val Vec3i.blockPosition get() = BlockPosition(x, y, z)
@JvmName("constructorDirectionVector")
operator fun Vec3i.Companion.invoke(vector: DirectionVector) = Vec3i(vector.x, vector.y, vector.z)
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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)
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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))
}
}