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 matrix = manager.createMatrix()
matrix[1][1].getOrPut(3)
assertEquals(manager[0, 0]!![3]!!.sectionHeight, 3)
assertEquals(manager[0, 0]!![3]!!.height, 3)
}
fun singleBlockUpdateWorld() {

View File

@ -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<Axes> {
override val VALUES: Array<Axes> = 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
* 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<MutableMap<String, Directions>>()["bottom"] = DOWN
}

View File

@ -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))
}

View File

@ -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

View File

@ -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<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) {
@ -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

View File

@ -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

View File

@ -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!")

View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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
}
}

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.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)

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.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)

View File

@ -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)
}

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))
}
}