Archived
0

Work on RegionTraverser (wasted a lotta time but we'll get there)

This commit is contained in:
Dico
2018-09-25 08:40:02 +01:00
parent 98395542a5
commit dcd90c09ad
10 changed files with 407 additions and 116 deletions

View File

@@ -17,8 +17,11 @@ plugins {
id("com.github.johnrengelman.plugin-shadow") version "2.0.3"
}
allprojects {
apply<JavaPlugin>()
apply(plugin = "idea")
repositories {
mavenCentral()
@@ -68,7 +71,7 @@ dependencies {
// not on sk89q maven repo yet
compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar"))
compileClasspath(files("$rootDir/debug/lib/spigot-1.13.1.jar"))
//compileClasspath(files("$rootDir/debug/lib/spigot-1.13.1.jar"))
compile("org.jetbrains.exposed:exposed:0.10.5") { isTransitive = false }
compile("joda-time:joda-time:2.10")
@@ -91,7 +94,7 @@ tasks {
javaParameters = true
suppressWarnings = true
jvmTarget = "1.8"
//freeCompilerArgs = listOf("-XXLanguage:+InlineClasses", "-Xuse-experimental=kotlin.Experimental")
freeCompilerArgs = listOf("-XXLanguage:+InlineClasses", "-Xuse-experimental=kotlin.Experimental")
}
}

View File

@@ -1,67 +1,61 @@
package io.dico.parcels2.blockvisitor
import io.dico.parcels2.util.Vec3i
import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix
import io.dico.parcels2.util.ext.getMaterialsWithWoolColorPrefix
import org.bukkit.Material
import org.bukkit.Material.*
import org.bukkit.block.BlockFace
import org.bukkit.block.data.BlockData
import org.bukkit.block.data.Directional
import java.util.EnumSet
val attachables: Set<Material> = EnumSet.of(
ACACIA_DOOR,
ACTIVATOR_RAIL,
BIRCH_DOOR,
BROWN_MUSHROOM,
CACTUS,
CAKE,
WHITE_CARPET, ORANGE_CARPET, MAGENTA_CARPET, LIGHT_BLUE_CARPET, YELLOW_CARPET, LIME_CARPET, PINK_CARPET, GRAY_CARPET, LIGHT_GRAY_CARPET, CYAN_CARPET, PURPLE_CARPET, BLUE_CARPET, BROWN_CARPET, GREEN_CARPET, RED_CARPET, BLACK_CARPET,
CARROT,
COCOA,
WHEAT,
DARK_OAK_DOOR,
DEAD_BUSH,
DETECTOR_RAIL,
REPEATER,
TALL_GRASS, TALL_SEAGRASS,
DRAGON_EGG,
FIRE,
FLOWER_POT,
OAK_PRESSURE_PLATE, BIRCH_PRESSURE_PLATE, SPRUCE_PRESSURE_PLATE, JUNGLE_PRESSURE_PLATE, ACACIA_PRESSURE_PLATE, DARK_OAK_PRESSURE_PLATE,
private val attachables = EnumSet.of(
REPEATER, COMPARATOR,
*getMaterialsWithWoodTypePrefix("PRESSURE_PLATE"),
STONE_PRESSURE_PLATE, LIGHT_WEIGHTED_PRESSURE_PLATE, HEAVY_WEIGHTED_PRESSURE_PLATE,
IRON_DOOR,
OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR,
OAK_BUTTON, BIRCH_BUTTON, SPRUCE_BUTTON, JUNGLE_BUTTON, ACACIA_BUTTON, DARK_OAK_BUTTON,
STONE_BUTTON,
OAK_TRAPDOOR, BIRCH_TRAPDOOR, SPRUCE_TRAPDOOR, JUNGLE_TRAPDOOR, ACACIA_TRAPDOOR, DARK_OAK_TRAPDOOR,
IRON_TRAPDOOR,
LADDER,
LEVER,
MELON_STEM,
NETHER_WART,
PISTON,
STICKY_PISTON,
NETHER_PORTAL,
POTATO,
POWERED_RAIL,
PUMPKIN_STEM,
RAIL,
COMPARATOR,
REDSTONE_TORCH,
REDSTONE_WIRE,
RED_MUSHROOM,
SUNFLOWER,
*getMaterialsWithWoodTypePrefix("BUTTON"),
STONE_BUTTON, LEVER,
*getMaterialsWithWoodTypePrefix("DOOR"), IRON_DOOR,
ACTIVATOR_RAIL, POWERED_RAIL, DETECTOR_RAIL, RAIL,
PISTON, STICKY_PISTON,
REDSTONE_TORCH, REDSTONE_WALL_TORCH, REDSTONE_WIRE,
TRIPWIRE, TRIPWIRE_HOOK,
BROWN_MUSHROOM, RED_MUSHROOM, CACTUS, CARROT, COCOA,
WHEAT, DEAD_BUSH, CHORUS_FLOWER, DANDELION, SUGAR_CANE,
TALL_GRASS, TALL_SEAGRASS, NETHER_WART, MELON_STEM,
PUMPKIN_STEM, SUNFLOWER, POTATO, LILY_PAD, VINE,
*getMaterialsWithWoodTypePrefix("SAPLING"),
SAND, RED_SAND, DRAGON_EGG, ANVIL,
*getMaterialsWithWoolColorPrefix("CONCRETE_POWDER"),
*getMaterialsWithWoolColorPrefix("CARPET"),
CAKE, FIRE,
FLOWER_POT,
LADDER,
// NETHER_PORTAL, fuck nether portals
FLOWER_POT,
CHORUS_FLOWER,
OAK_SAPLING, BIRCH_SAPLING, SPRUCE_SAPLING, JUNGLE_SAPLING, ACACIA_SAPLING, DARK_OAK_SAPLING,
SIGN,
SNOW,
SPRUCE_DOOR,
STONE_BUTTON,
SUGAR_CANE,
TORCH,
TRIPWIRE,
TRIPWIRE_HOOK,
VINE,
WHITE_BANNER, ORANGE_BANNER, MAGENTA_BANNER, LIGHT_BLUE_BANNER, YELLOW_BANNER, LIME_BANNER, PINK_BANNER, GRAY_BANNER, LIGHT_GRAY_BANNER, CYAN_BANNER, PURPLE_BANNER, BLUE_BANNER, BROWN_BANNER, GREEN_BANNER, RED_BANNER, BLACK_BANNER,
WHITE_WALL_BANNER, ORANGE_WALL_BANNER, MAGENTA_WALL_BANNER, LIGHT_BLUE_WALL_BANNER, YELLOW_WALL_BANNER, LIME_WALL_BANNER, PINK_WALL_BANNER, GRAY_WALL_BANNER, LIGHT_GRAY_WALL_BANNER, CYAN_WALL_BANNER, PURPLE_WALL_BANNER, BLUE_WALL_BANNER, BROWN_WALL_BANNER, GREEN_WALL_BANNER, RED_WALL_BANNER, BLACK_WALL_BANNER,
WALL_SIGN,
LILY_PAD,
DANDELION
)
TORCH, WALL_TORCH,
*getMaterialsWithWoolColorPrefix("BANNER"),
*getMaterialsWithWoolColorPrefix("WALL_BANNER"),
SIGN, WALL_SIGN
)
fun isAttachable(type: Material) = attachables.contains(type)
fun supportingBlock(data: BlockData): Vec3i = when (data) {
//is MultipleFacing -> // fuck it xD this is good enough
is Directional -> Vec3i.convert(when (data.material) {
// exceptions
COCOA -> data.facing
OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR, IRON_DOOR -> BlockFace.DOWN
else -> data.facing.oppositeFace
})
else -> Vec3i.down
}

View File

@@ -2,64 +2,291 @@ package io.dico.parcels2.blockvisitor
import io.dico.parcels2.util.Region
import io.dico.parcels2.util.Vec3i
import io.dico.parcels2.util.ext.clampMax
abstract class RegionTraverser {
fun traverseRegion(region: Region): Iterable<Vec3i> = Iterable { iterator<Vec3i> { build(region) } }
private typealias Scope = SequenceScope<Vec3i>
protected abstract suspend fun SequenceScope<Vec3i>.build(region: Region)
sealed class RegionTraverser {
fun traverseRegion(region: Region, worldHeight: Int = 256): Iterable<Vec3i> =
Iterable { iterator<Vec3i> { build(validify(region, worldHeight)) } }
private fun validify(region: Region, worldHeight: Int): Region {
if (region.origin.y < 0) {
val origin = region.origin withY 0
val size = region.size.withY((region.size.y + region.origin.y).clampMax(worldHeight))
return Region(origin, size)
}
if (region.origin.y + region.size.y > worldHeight) {
val size = region.size.withY(worldHeight - region.origin.y)
return Region(region.origin, size)
}
return region
}
protected abstract suspend fun Scope.build(region: Region)
companion object {
val upward = create { traverseUpward(it) }
val downward = create { traverseDownward(it) }
val forClearing get() = downward
val forFilling get() = upward
val upward = Directional(TraverseDirection(1, 1, 1), TraverseOrder(Dimension.Y, Dimension.X))
val downward = Directional(TraverseDirection(1, -1, 1), TraverseOrder(Dimension.Y, Dimension.X))
val toClear get() = downward
val toFill get() = upward
inline fun create(crossinline builder: suspend SequenceScope<Vec3i>.(Region) -> Unit) = object : RegionTraverser() {
override suspend fun SequenceScope<Vec3i>.build(region: Region) {
builder(region)
}
fun convergingTo(y: Int) = Slicing(y, upward, downward, true)
fun separatingFrom(y: Int) = Slicing(y, downward, upward, false)
}
class Directional(
val direction: TraverseDirection,
val order: TraverseOrder
) : RegionTraverser() {
override suspend fun Scope.build(region: Region) {
//traverserLogic(region, order, direction)
}
private suspend fun SequenceScope<Vec3i>.traverseDownward(region: Region) {
val origin = region.origin
val size = region.size
repeat(size.y) { y ->
repeat(size.z) { z ->
repeat(size.x) { x ->
yield(origin.add(x, size.y - y - 1, z))
}
}
}
}
}
private suspend fun SequenceScope<Vec3i>.traverseUpward(region: Region) {
val origin = region.origin
val size = region.size
repeat(size.y) { y ->
repeat(size.z) { z ->
repeat(size.x) { x ->
yield(origin.add(x, size.y - y - 1, z))
}
}
}
}
class Slicing(
val bottomSectionMaxY: Int,
val bottomTraverser: RegionTraverser,
val topTraverser: RegionTraverser,
val bottomFirst: Boolean = true
) : RegionTraverser() {
private fun slice(region: Region, atY: Int): Pair<Region, Region?> {
if (atY < region.size.y + 1) {
val first = Region(region.origin, region.size.withY(atY + 1))
val second = Region(region.origin.withY(atY), region.size.addY(-atY-1))
val second = Region(region.origin.withY(atY), region.size.addY(-atY - 1))
return first to second
}
return region to null
}
fun upToAndDownUntil(y: Int) = create { region ->
val (bottom, top) = slice(region, y)
traverseUpward(bottom)
top?.let { traverseDownward(it) }
override suspend fun Scope.build(region: Region) {
val (bottom, top) = slice(region, bottomSectionMaxY)
if (bottomFirst) {
with(bottomTraverser) { build(bottom) }
top?.let { with(topTraverser) { build(it) } }
} else {
top?.let { with(topTraverser) { build(it) } }
with(bottomTraverser) { build(bottom) }
}
}
}
}
enum class Dimension {
X,
Y,
Z;
fun extract(block: Vec3i) =
when (this) {
X -> block.x
Y -> block.y
Z -> block.z
}
companion object {
private val values = values()
operator fun get(ordinal: Int) = values[ordinal]
}
}
inline class TraverseOrder(val orderNum: Int) {
private constructor(first: Dimension, swap: Boolean)
: this(if (swap) first.ordinal + 3 else first.ordinal)
@Suppress("NOTHING_TO_INLINE")
private inline fun element(index: Int) = Dimension[(orderNum + index) % 3]
private val swap inline get() = orderNum >= 3
/**
* The slowest changing dimension
*/
val primary: Dimension get() = element(0)
/**
* Second slowest changing dimension
*/
val secondary: Dimension get() = element(if (swap) 2 else 1)
/**
* Dimension that changes every block
*/
val tertiary: Dimension get() = element(if (swap) 1 else 2)
/**
* All 3 dimensions in this order
*/
fun toArray() = arrayOf(primary, secondary, tertiary)
companion object {
private fun isSwap(primary: Dimension, secondary: Dimension) = secondary.ordinal != (primary.ordinal + 1) % 3
operator fun invoke(primary: Dimension, secondary: Dimension): TraverseOrder {
// tertiary is implicit
if (primary == secondary) throw IllegalArgumentException()
return TraverseOrder(primary, isSwap(primary, secondary))
}
}
fun add(vec: Vec3i, dp: Int, ds: Int, dt: Int): Vec3i =
// optimize this, will be called lots
when (orderNum) {
0 -> vec.add(dp, ds, dt) // xyz
1 -> vec.add(dt, dp, ds) // yzx
2 -> vec.add(ds, dt, dp) // zxy
3 -> vec.add(dp, dt, ds) // xzy
4 -> vec.add(ds, dp, dt) // yxz
5 -> vec.add(dt, ds, dp) // zyx
else -> error("Invalid orderNum $orderNum")
}
}
class AltTraverser(val size: Vec3i,
val order: TraverseOrder,
val direction: TraverseDirection) {
suspend fun Scope.build() {
doPrimary()
}
private suspend fun Scope.doPrimary() {
val dimension = order.primary
direction.directionOf(dimension).traverse(dimension.extract(size)) { value ->
}
}
private fun Dimension.setValue(value: Int) {
}
}
enum class Increment(val offset: Int) {
UP(1),
DOWN(-1);
companion object {
fun convert(bool: Boolean) = if (bool) UP else DOWN
}
inline fun traverse(size: Int, op: (Int) -> Unit) {
when (this) {
UP -> repeat(size, op)
DOWN -> repeat(size) { op(size - it - 1) }
}
}
}
inline class TraverseDirection(val bits: Int) {
fun directionOf(dimension: Dimension) = Increment.convert((1 shl dimension.ordinal) and bits != 0)
companion object {
operator fun invoke(x: Int, y: Int, z: Int) = invoke(Vec3i(x, y, z))
operator fun invoke(block: Vec3i): TraverseDirection {
if (block.x == 0 || block.y == 0 || block.z == 0) throw IllegalArgumentException()
var bits = 0
if (block.x > 0) bits = bits or 1
if (block.y > 0) bits = bits or 2
if (block.z > 0) bits = bits or 3
return TraverseDirection(bits)
}
}
}
/*
private typealias Scope = SequenceScope<Vec3i>
private typealias ScopeAction = suspend Scope.(Int, Int, Int) -> Unit
@Suppress("NON_EXHAUSTIVE_WHEN")
suspend fun Scope.traverserLogic(
region: Region,
order: TraverseOrder,
direction: TraverseDirection
) = with(direction) {
val (primary, secondary, tertiary) = order.toArray()
val (origin, size) = region
when (order.primary) {
Dimension.X ->
when (order.secondary) {
Dimension.Y -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(p, s, t))
}
}
}
}
Dimension.Z -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(p, t, s))
}
}
}
}
}
Dimension.Y ->
when (order.secondary) {
Dimension.X -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(s, p, t))
}
}
}
}
Dimension.Z -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(t, p, s))
}
}
}
}
}
Dimension.Z ->
when (order.secondary) {
Dimension.X -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(s, t, p))
}
}
}
}
Dimension.Y -> {
directionOf(primary).traverse(primary.extract(size)) { p ->
directionOf(secondary).traverse(secondary.extract(size)) { s ->
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
yield(origin.add(t, s, p))
}
}
}
}
}
}
}
*/

View File

@@ -3,9 +3,13 @@ package io.dico.parcels2.blockvisitor
import io.dico.parcels2.util.Region
import io.dico.parcels2.util.Vec3i
import io.dico.parcels2.util.get
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.World
import org.bukkit.block.data.BlockData
private val air = Bukkit.createBlockData(Material.AIR)
// TODO order paste such that attachables are placed after the block they depend on
class Schematic {
val size: Vec3i get() = _size!!
@@ -15,15 +19,17 @@ class Schematic {
field = value
}
private var _data: Array<BlockData?>? = null
private var blockDatas: Array<BlockData?>? = null
//private var extra: Map<Vec3i, (Block) -> Unit>? = null
private var isLoaded = false; private set
private val traverser: RegionTraverser = RegionTraverser.upward
fun getLoadTask(world: World, region: Region): TimeLimitedTask = {
val size = region.size.also { _size = it }
val data = arrayOfNulls<BlockData>(region.blockCount).also { _data = it }
_size = region.size
val data = arrayOfNulls<BlockData>(region.blockCount).also { blockDatas = it }
//val extra = mutableMapOf<Vec3i, (Block) -> Unit>().also { extra = it }
val blocks = RegionTraverser.downward.traverseRegion(region)
val blocks = traverser.traverseRegion(region)
for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint()
@@ -39,14 +45,26 @@ class Schematic {
fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = {
if (!isLoaded) throw IllegalStateException()
val region = Region(position, _size!!)
val blocks = RegionTraverser.downward.traverseRegion(region)
val data = _data!!
val blocks = traverser.traverseRegion(region, worldHeight = world.maxHeight)
val blockDatas = blockDatas!!
val postponed = mutableListOf<Pair<Vec3i, BlockData>>()
for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint()
val block = world[vec]
if (block.y > 255) continue
data[index]?.let { block.blockData = it }
val type = blockDatas[index] ?: air
if (type !== air && isAttachable(type.material)) {
postponed += vec to type
} else {
block.blockData = type
}
}
for ((vec, data) in postponed) {
}
}

View File

@@ -10,6 +10,8 @@ import io.dico.parcels2.blockvisitor.RegionTraverser
import io.dico.parcels2.doBlockOperation
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.block.BlockFace
import org.bukkit.block.data.Directional
import org.bukkit.entity.Player
import java.util.Random
@@ -55,4 +57,19 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
}
}
@Cmd("directionality", aliases = ["dir"])
fun cmdDirectionality(sender: Player, context: ExecutionContext, material: Material): Any? {
val senderLoc = sender.location
val block = senderLoc.add(senderLoc.direction.setY(0).normalize().multiply(2).toLocation(sender.world)).block
val blockData = Bukkit.createBlockData(material)
if (blockData is Directional) {
blockData.facing = BlockFace.SOUTH
}
block.blockData = blockData
return if (blockData is Directional) "The block is facing south" else "The block is not directional, however it implements " +
blockData.javaClass.interfaces!!.contentToString()
}
}

View File

@@ -162,7 +162,7 @@ class DefaultParcelGenerator(
override val worktimeLimiter: WorktimeLimiter
) : ParcelBlockManagerBase(), CoroutineScope by coroutineScope {
override val world: World = this@DefaultParcelGenerator.world
override val parcelTraverser: RegionTraverser = RegionTraverser.upToAndDownUntil(o.floorHeight)
override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight)
/*override*/ fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i(
sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,

View File

@@ -33,7 +33,7 @@ class ParcelEntityTracker(val parcelProvider: ParcelProvider) {
remove()
val newParcel = parcelProvider.getParcelAt(entity.location)
if (newParcel !== parcel && !(newParcel != null && newParcel.hasBlockVisitors)) {
if (newParcel !== parcel && (newParcel == null || !newParcel.hasBlockVisitors)) {
entity.remove()
}
@@ -41,14 +41,13 @@ class ParcelEntityTracker(val parcelProvider: ParcelProvider) {
}
val newParcel = parcelProvider.getParcelAt(entity.location)
if (newParcel !== parcel && !(newParcel != null && newParcel.hasBlockVisitors)) {
if (newParcel !== parcel && (newParcel == null || !newParcel.hasBlockVisitors)) {
remove()
entity.remove()
}
}
}
@Suppress("RedundantLambdaArrow")
fun swapParcels(parcel1: Parcel, parcel2: Parcel) {
map.editLoop { ->
if (value === parcel1) {

View File

@@ -196,11 +196,10 @@ class ParcelListeners(
when (event.action) {
Action.RIGHT_CLICK_BLOCK -> run {
val type = clickedBlock.type
val interactable = Interactables.listedMaterials.containsKey(type)
&& (parcel.effectiveInteractableConfig.isInteractable(type) || (parcel != null && parcel.canBuild(user)))
if (!interactable) {
val interactableClassName = Interactables[type]!!.name
user.sendParcelMessage(nopermit = true, message = "You cannot interact with $interactableClassName in this parcel")
val interactableClass = Interactables[type]
if (interactableClass != null && (parcel.effectiveInteractableConfig.isInteractable(type) || (parcel != null && parcel.canBuild(user)))) {
user.sendParcelMessage(nopermit = true, message = "You cannot interact with ${interactableClass.name} in this parcel")
event.isCancelled = true
return@l
}

View File

@@ -1,11 +1,14 @@
package io.dico.parcels2.util
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Delay
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.timeunit.TimeUnit
import org.bukkit.plugin.Plugin
import kotlin.coroutines.CoroutineContext
abstract class MainThreadDispatcher : CoroutineDispatcher() {
abstract class MainThreadDispatcher : CoroutineDispatcher(), Delay {
abstract val mainThread: Thread
abstract fun runOnMainThread(task: Runnable)
}
@@ -27,5 +30,14 @@ fun MainThreadDispatcher(plugin: Plugin): MainThreadDispatcher {
if (Thread.currentThread() === mainThread) task.run()
else plugin.server.scheduler.runTaskLater(plugin, task, 0)
}
override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
val task = Runnable {
with (continuation) { resumeUndispatched(Unit) }
}
val millis = TimeUnit.MILLISECONDS.convert(time, unit)
plugin.server.scheduler.runTaskLater(plugin, task, (millis + 25) / 50 - 1)
}
}
}

View File

@@ -2,6 +2,7 @@ package io.dico.parcels2.util
import org.bukkit.World
import org.bukkit.block.Block
import org.bukkit.block.BlockFace
data class Vec3d(
val x: Double,
@@ -32,6 +33,27 @@ data class Vec3i(
infix fun withY(o: Int) = Vec3i(x, o, z)
infix fun withZ(o: Int) = Vec3i(x, y, o)
fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz)
fun neg() = Vec3i(-x, -y, -z)
companion object {
private operator fun invoke(face: BlockFace) = Vec3i(face.modX, face.modY, face.modZ)
val down = Vec3i(BlockFace.DOWN)
val up = Vec3i(BlockFace.UP)
val north = Vec3i(BlockFace.NORTH)
val east = Vec3i(BlockFace.EAST)
val south = Vec3i(BlockFace.SOUTH)
val west = Vec3i(BlockFace.WEST)
fun convert(face: BlockFace) = when (face) {
BlockFace.DOWN -> down
BlockFace.UP -> up
BlockFace.NORTH -> north
BlockFace.EAST -> east
BlockFace.SOUTH -> south
BlockFace.WEST -> west
else -> Vec3i(face)
}
}
}
@Suppress("NOTHING_TO_INLINE")