Skip to content

Commit

Permalink
Region types and serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
andantet committed Nov 13, 2023
1 parent 572c1af commit 0c2b2f8
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 1 deletion.
11 changes: 10 additions & 1 deletion src/main/kotlin/net/mcbrawls/blueprint/Blueprint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package net.mcbrawls.blueprint

import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.mcbrawls.blueprint.region.serialization.SerializableRegion
import net.minecraft.block.BlockState
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos
Expand All @@ -22,6 +23,11 @@ data class Blueprint(
* The block states stored as their palette ids.
*/
val palettedBlockStates: List<PalettedState>,

/**
* The regions stored within this blueprint.
*/
val regions: List<SerializableRegion>,
) {
/**
* A list of the positions to their actual block states.
Expand Down Expand Up @@ -51,6 +57,9 @@ data class Blueprint(
PalettedState.CODEC.listOf()
.fieldOf("block_states")
.forGetter(Blueprint::palettedBlockStates),
SerializableRegion.CODEC.listOf()
.fieldOf("regions")
.forGetter(Blueprint::regions),
).apply(instance, ::Blueprint)
}

Expand Down Expand Up @@ -80,7 +89,7 @@ data class Blueprint(
palettedBlockStates.add(PalettedState(relativePos, paletteId))
}

return Blueprint(palette, palettedBlockStates)
return Blueprint(palette, palettedBlockStates, listOf())
}

private fun BlockPos.compared(other: BlockPos): Pair<BlockPos, BlockPos> {
Expand Down
39 changes: 39 additions & 0 deletions src/main/kotlin/net/mcbrawls/blueprint/region/CompoundRegion.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package net.mcbrawls.blueprint.region

import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.mcbrawls.blueprint.region.serialization.SerializableRegion
import net.minecraft.entity.Entity
import net.minecraft.util.math.BlockPos

/**
* A region defined of multiple other regions.
* Useful for regions which cannot be defined through a single format.
*/
data class CompoundRegion(
/**
* The regions which compose this compound region.
*/
val regions: List<SerializableRegion>
) : Region {
constructor(vararg regions: SerializableRegion) : this(regions.toList())

override val positions: Set<BlockPos> = regions.flatMap(SerializableRegion::positions).toSet()

override fun contains(entity: Entity): Boolean {
return regions.any { region -> region.contains(entity) }
}

companion object {
/**
* The codec of a compound region.
*/
val CODEC: Codec<CompoundRegion> = RecordCodecBuilder.create { instance ->
instance.group(
SerializableRegion.CODEC.listOf()
.fieldOf("regions")
.forGetter(CompoundRegion::regions)
).apply(instance, ::CompoundRegion)
}
}
}
71 changes: 71 additions & 0 deletions src/main/kotlin/net/mcbrawls/blueprint/region/CuboidRegion.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package net.mcbrawls.blueprint.region

import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.mcbrawls.blueprint.region.Region.Companion.iterateBoxBlockPositions
import net.mcbrawls.blueprint.region.serialization.SerializableRegion
import net.mcbrawls.blueprint.region.serialization.SerializableRegionTypes
import net.minecraft.entity.Entity
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Box
import net.minecraft.util.math.Vec3d

/**
* A region defined by a cuboid.
*/
data class CuboidRegion(
/**
* The root position of the region.
*/
val rootPosition: Vec3d,

/**
* The size of the cuboid, expanding from the root position.
*/
val size: Vec3d
) : SerializableRegion(SerializableRegionTypes.CUBOID) {
/**
* The cached box of this cuboid region.
*/
private val box: Box = createBox()

override val positions: Set<BlockPos> = computePositions()

/**
* A predicate to check if an entity is within the cuboid bounding box.
*/
override fun contains(entity: Entity): Boolean {
return entity.boundingBox.intersects(box)
}

/**
* Creates a box of this cuboid region.
*/
private fun createBox(): Box {
val rootBox = Box.from(rootPosition)

val endPosition = rootPosition.add(size)
val endBox = Box.from(endPosition)

return rootBox.intersection(endBox)
}

/**
* Calculates all positions for this cuboid region.
*/
private fun computePositions(): Set<BlockPos> {
return iterateBoxBlockPositions(box).toSet()
}

companion object {
/**
* The codec of a cuboid region.
*/
val CODEC: Codec<CuboidRegion> = RecordCodecBuilder.create { instance ->
instance.group(
Vec3d.CODEC.fieldOf("root_position").forGetter(CuboidRegion::rootPosition),
Vec3d.CODEC.fieldOf("size").forGetter(CuboidRegion::size)
).apply(instance, ::CuboidRegion)
}
}
}
39 changes: 39 additions & 0 deletions src/main/kotlin/net/mcbrawls/blueprint/region/PointRegion.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package net.mcbrawls.blueprint.region

import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.mcbrawls.blueprint.region.serialization.SerializableRegion
import net.mcbrawls.blueprint.region.serialization.SerializableRegionTypes
import net.minecraft.entity.Entity
import net.minecraft.util.math.BlockPos

/**
* A region defined by a single point in the world.
* Useful for cases such as respawn or chest positions.
*/
data class PointRegion(
/**
* The position of the point.
*/
val position: BlockPos
) : SerializableRegion(SerializableRegionTypes.POINT) {
override val positions: Set<BlockPos> = setOf(position)

/**
* A predicate to check if an entity is at the point position.
*/
override fun contains(entity: Entity): Boolean {
return entity.blockPos.equals(position)
}

companion object {
/**
* The codec of a cuboid region.
*/
val CODEC: Codec<PointRegion> = RecordCodecBuilder.create { instance ->
instance.group(
BlockPos.CODEC.fieldOf("position").forGetter(PointRegion::position),
).apply(instance, ::PointRegion)
}
}
}
43 changes: 43 additions & 0 deletions src/main/kotlin/net/mcbrawls/blueprint/region/Region.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package net.mcbrawls.blueprint.region

import net.minecraft.entity.Entity
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Box

/**
* A region is a defined volume of space within a blueprint.
*/
interface Region {
/**
* All positions stored within this region.
*/
val positions: Set<BlockPos>

/**
* A predicate to check if an entity is within the region.
*/
fun contains(entity: Entity): Boolean

/**
* Loops through each position in the region and performs the given action.
*/
fun forEachPosition(action: (BlockPos) -> Unit) {
positions.forEach(action)
}

companion object {
/**
* Creates an iterable from a box.
* @return a block position iterator
*/
fun iterateBoxBlockPositions(box: Box): Iterable<BlockPos> {
val minX = box.minX.toInt()
val minY = box.minY.toInt()
val minZ = box.minZ.toInt()
val maxX = box.maxX.toInt()
val maxY = box.maxY.toInt()
val maxZ = box.maxZ.toInt()
return BlockPos.iterate(minX, minY, minZ, maxX, maxY, maxZ)
}
}
}
63 changes: 63 additions & 0 deletions src/main/kotlin/net/mcbrawls/blueprint/region/SphericalRegion.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package net.mcbrawls.blueprint.region

import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.mcbrawls.blueprint.region.Region.Companion.iterateBoxBlockPositions
import net.mcbrawls.blueprint.region.serialization.SerializableRegion
import net.mcbrawls.blueprint.region.serialization.SerializableRegionTypes
import net.minecraft.entity.Entity
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Box
import net.minecraft.util.math.Vec3d

// TODO: test: see if radii need squaring

/**
* A region defined by a sphere.
*/
data class SphericalRegion(
/**
* The root position of the region.
*/
val rootPosition: Vec3d,

/**
* The radius of the sphere, expanding from the root position.
*/
val radius: Double
) : SerializableRegion(SerializableRegionTypes.SPHERE) {
override val positions: Set<BlockPos> = computePositions()

override fun contains(entity: Entity): Boolean {
return entity.squaredDistanceTo(rootPosition) <= radius
}

/**
* Calculates all positions for this spherical region.
*/
private fun computePositions(): Set<BlockPos> {
// create box
val diameter = radius * 2
val box = Box.of(rootPosition, diameter, diameter, diameter)

// iterate through all positions and find all that are within range for a sphere
val boxPositions = iterateBoxBlockPositions(box)
return boxPositions.filter(::isPositionWithinRadius).toSet()
}

private fun isPositionWithinRadius(position: BlockPos): Boolean {
return position.getSquaredDistance(rootPosition) <= radius
}

companion object {
/**
* The codec of a spherical region.
*/
val CODEC: Codec<SphericalRegion> = RecordCodecBuilder.create { instance ->
instance.group(
Vec3d.CODEC.fieldOf("root_position").forGetter(SphericalRegion::rootPosition),
Codec.DOUBLE.fieldOf("radius").forGetter(SphericalRegion::radius)
).apply(instance, ::SphericalRegion)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package net.mcbrawls.blueprint.region.serialization

import com.mojang.serialization.Codec
import net.mcbrawls.blueprint.region.Region

/**
* A region which can be serialized or deserialized by codecs.
*/
abstract class SerializableRegion(
/**
* The serialized type of this region.
*/
val type: SerializableRegionType
) : Region {
companion object {
/**
* The codec for a serializable region, defined by its type.
*/
val CODEC: Codec<SerializableRegion> = SerializableRegionTypes.REGISTRY.getCodec()
.dispatch("type", SerializableRegion::type, SerializableRegionType::codec)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.mcbrawls.blueprint.region.serialization

import com.mojang.serialization.Codec

/**
* A type of serializable region.
*/
data class SerializableRegionType(
/**
* The codec of this serializable region type.
*/
val codec: Codec<out SerializableRegion>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package net.mcbrawls.blueprint.region.serialization

import com.mojang.serialization.Lifecycle
import net.mcbrawls.blueprint.BlueprintMod
import net.mcbrawls.blueprint.region.CuboidRegion
import net.mcbrawls.blueprint.region.PointRegion
import net.mcbrawls.blueprint.region.SphericalRegion
import net.minecraft.registry.Registry
import net.minecraft.registry.RegistryKey
import net.minecraft.registry.SimpleRegistry
import net.minecraft.util.Identifier

object SerializableRegionTypes {
/**
* The registry key for serializable region types.
*/
val REGISTRY_KEY: RegistryKey<Registry<SerializableRegionType>> =
RegistryKey.ofRegistry(Identifier(BlueprintMod.MOD_ID, "serializable_region_types"))

/**
* The registry of serializable region types.
*/
val REGISTRY: Registry<SerializableRegionType> = SimpleRegistry(REGISTRY_KEY, Lifecycle.stable())

val POINT = register("point", SerializableRegionType(PointRegion.CODEC))
val CUBOID = register("cuboid", SerializableRegionType(CuboidRegion.CODEC))
val SPHERE = register("sphere", SerializableRegionType(SphericalRegion.CODEC))

private fun register(id: String, type: SerializableRegionType): SerializableRegionType {
return Registry.register(REGISTRY, Identifier(BlueprintMod.MOD_ID, id), type)
}
}

0 comments on commit 0c2b2f8

Please sign in to comment.