diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/BlackHoleRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/BlackHoleRenderer.kt index 6257de0ac..ffbd23cbe 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/BlackHoleRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/BlackHoleRenderer.kt @@ -9,13 +9,15 @@ import net.minecraft.client.renderer.MultiBufferSource import net.minecraft.client.renderer.blockentity.BeaconRenderer import net.minecraft.client.renderer.blockentity.BlockEntityRenderer import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider -import net.minecraft.resources.ResourceLocation +import net.minecraft.network.chat.TranslatableComponent import net.minecraft.world.phys.Vec3 import org.lwjgl.opengl.GL30 import ru.dbotthepony.mc.otm.Registry import ru.dbotthepony.mc.otm.block.entity.BlockEntityGravitationStabilizer import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntityBlackHole +import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.core.* +import kotlin.math.PI private const val BEAM_WIDTH = 0.2 @@ -82,7 +84,7 @@ private fun quadZ(y: Double): Array { class BlackHoleRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { override fun render( - blockEntityBlackHole: BlockEntityBlackHole, + tile: BlockEntityBlackHole, v: Float, poseStack: PoseStack, multiBufferSource: MultiBufferSource, @@ -98,8 +100,8 @@ class BlackHoleRenderer(private val context: BlockEntityRendererProvider.Context RenderSystem.disableCull() poseStack.pushPose() - poseStack.translate(0.5, -blockEntityBlackHole.gravitationStrength / 2 + 0.5, 0.5) - RenderHelper.colorSphere(poseStack, blockEntityBlackHole.gravitationStrength.toFloat()) + poseStack.translate(0.5, -tile.gravitationStrength / 2 + 0.5, 0.5) + RenderHelper.colorSphere(poseStack, tile.gravitationStrength.toFloat()) RenderSystem.enableCull() RenderSystem.enableTexture() @@ -137,6 +139,40 @@ class BlackHoleRenderer(private val context: BlockEntityRendererProvider.Context BufferUploader.end(builder) RenderSystem.enableCull() } + + if ( + ply != null && + ply.getCapability(MatteryCapability.ANDROID).isPresent && + ply.getCapability(MatteryCapability.ANDROID).resolve().get().isAndroid() && + poseStack.translation().length() < tile.gravitationStrength * 16.0 + ) { + val facing = tile.blockPos.asVector() - Minecraft.getInstance().gameRenderer.mainCamera.position + val normal = facing.normalize() + + val ang = normal.asMutableAngle() + ang.roll = ang.pitch - PI + ang.yaw = ang.yaw - PI * 1.1 + ang.pitch = 0.0 + + poseStack.pushPose() + poseStack.translate(0.5, 0.75, 0.5) + poseStack.translate(ang.forward() * tile.gravitationStrength * 1.1) + + poseStack.rotateAroundPoint(poseStack.translation(), ang) + poseStack.scale(0.05f, 0.05f, 0.05f) + + val font = Minecraft.getInstance().font + val text1 = TranslatableComponent("otm.3d2d.gravitation_stabilizer.mass", tile.mass.decimalString(2)) + val text2 = TranslatableComponent("otm.3d2d.gravitation_stabilizer.strength", "%.2f".format(tile.gravitationStrength)) + + font.drawAligned(poseStack, text1, TextAlign.TOP_LEFT, 0.8f, 0.8f - font.lineHeight.toFloat() / 2f, 0x0) + font.drawAligned(poseStack, text2, TextAlign.TOP_LEFT, 0.8f, 0.8f + font.lineHeight.toFloat() / 2f, 0x0) + poseStack.translate(0.0, 0.0, -1.0) + font.drawAligned(poseStack, text1, TextAlign.TOP_LEFT, 0f, -font.lineHeight.toFloat() / 2f, 0xFFFFFF) + font.drawAligned(poseStack, text2, TextAlign.TOP_LEFT, 0f, font.lineHeight.toFloat() / 2f, 0xFFFFFF) + + poseStack.popPose() + } } override fun shouldRenderOffScreen(p_112306_: BlockEntityBlackHole): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Ext.kt index 59b7fe849..977b3c5f9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Ext.kt @@ -1,9 +1,119 @@ package ru.dbotthepony.mc.otm.client.render +import com.mojang.blaze3d.vertex.PoseStack import com.mojang.blaze3d.vertex.VertexConsumer import com.mojang.math.Matrix4f -import ru.dbotthepony.mc.otm.core.Vector +import com.mojang.math.Vector3f +import net.minecraft.client.gui.Font +import net.minecraft.network.chat.Component +import net.minecraft.util.FormattedCharSequence +import ru.dbotthepony.mc.otm.core.* fun VertexConsumer.normal(vector: Vector) = normal(vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat()) fun VertexConsumer.vertex(matrix4f: Matrix4f, vector: Vector) = vertex(matrix4f, vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat()) fun VertexConsumer.color(color: RGBAColor) = color(color.r, color.g, color.b, color.a) + +fun PoseStack.translate(vector: Vector) = translate(vector.x, vector.y, vector.z) +fun PoseStack.translate(vector: Vector3f) = last().pose().multiplyWithTranslation(vector.x(), vector.y(), vector.z()) + +fun PoseStack.rotateAroundPoint(point: Vector, axis: Vector, rotation: Float, isDegrees: Boolean = false) { + val last = last() + last.pose().rotateAroundPoint(point, axis, rotation, isDegrees) + last.normal().mul(axis.rotateAroundThis(rotation, isDegrees)) +} + +fun PoseStack.rotateAroundPoint(point: Vector, rotation: IAngle) { + val last = last() + last.pose().rotateAroundPoint(point, rotation) + // last.normal().mul(rotation.forward().rotateAroundThis(rotation)) +} + +fun PoseStack.rotateAroundPoint(point: Vector3f, axis: Vector, rotation: Float, isDegrees: Boolean = false) { + val last = last() + last.pose().rotateAroundPoint(point, axis, rotation, isDegrees) + last.normal().mul(axis.rotateAroundThis(rotation, isDegrees)) +} + +fun PoseStack.rotateAroundPoint(point: Vector3f, rotation: IAngle) { + val last = last() + last.pose().rotateAroundPoint(point, rotation) + // last.normal().mul(rotation.forward().rotateAroundThis(rotation)) +} + +fun PoseStack.translation(): Vector3f { + return last().pose().getTranslation() +} + +enum class TextAlign { + TOP_LEFT, + TOP_CENTER, + TOP_RIGHT, + + CENTER_LEFT, + CENTER_CENTER, + CENTER_RIGHT, + + BOTTOM_LEFT, + BOTTOM_CENTER, + BOTTOM_RIGHT, +} + +fun Font.drawAligned(poseStack: PoseStack, text: String, align: TextAlign, x: Float, y: Float, color: Int): Int { + return when (align) { + TextAlign.TOP_LEFT -> draw(poseStack, text, x, y, color) + TextAlign.TOP_CENTER -> draw(poseStack, text, x - width(text) / 2f, y, color) + TextAlign.TOP_RIGHT -> draw(poseStack, text, x - width(text), y, color) + + TextAlign.CENTER_LEFT -> draw(poseStack, text, x, y - lineHeight / 2f, color) + TextAlign.CENTER_CENTER -> draw(poseStack, text, x - width(text) / 2f, y - lineHeight / 2f, color) + TextAlign.CENTER_RIGHT -> draw(poseStack, text, x - width(text), y - lineHeight / 2f, color) + + TextAlign.BOTTOM_LEFT -> draw(poseStack, text, x, y - lineHeight, color) + TextAlign.BOTTOM_CENTER -> draw(poseStack, text, x - width(text) / 2f, y - lineHeight, color) + TextAlign.BOTTOM_RIGHT -> draw(poseStack, text, x - width(text), y - lineHeight, color) + } +} + +fun Font.drawAligned(poseStack: PoseStack, text: Component, align: TextAlign, x: Float, y: Float, color: Int): Int { + return when (align) { + TextAlign.TOP_LEFT -> draw(poseStack, text, x, y, color) + TextAlign.TOP_CENTER -> draw(poseStack, text, x - width(text) / 2f, y, color) + TextAlign.TOP_RIGHT -> draw(poseStack, text, x - width(text), y, color) + + TextAlign.CENTER_LEFT -> draw(poseStack, text, x, y - lineHeight / 2f, color) + TextAlign.CENTER_CENTER -> draw(poseStack, text, x - width(text) / 2f, y - lineHeight / 2f, color) + TextAlign.CENTER_RIGHT -> draw(poseStack, text, x - width(text), y - lineHeight / 2f, color) + + TextAlign.BOTTOM_LEFT -> draw(poseStack, text, x, y - lineHeight, color) + TextAlign.BOTTOM_CENTER -> draw(poseStack, text, x - width(text) / 2f, y - lineHeight, color) + TextAlign.BOTTOM_RIGHT -> draw(poseStack, text, x - width(text), y - lineHeight, color) + } +} + +fun Font.drawAligned(poseStack: PoseStack, text: FormattedCharSequence, align: TextAlign, x: Float, y: Float, color: Int): Int { + return when (align) { + TextAlign.TOP_LEFT -> draw(poseStack, text, x, y, color) + TextAlign.TOP_CENTER -> draw(poseStack, text, x - width(text) / 2f, y, color) + TextAlign.TOP_RIGHT -> draw(poseStack, text, x - width(text), y, color) + + TextAlign.CENTER_LEFT -> draw(poseStack, text, x, y - lineHeight / 2f, color) + TextAlign.CENTER_CENTER -> draw(poseStack, text, x - width(text) / 2f, y - lineHeight / 2f, color) + TextAlign.CENTER_RIGHT -> draw(poseStack, text, x - width(text), y - lineHeight / 2f, color) + + TextAlign.BOTTOM_LEFT -> draw(poseStack, text, x, y - lineHeight, color) + TextAlign.BOTTOM_CENTER -> draw(poseStack, text, x - width(text) / 2f, y - lineHeight, color) + TextAlign.BOTTOM_RIGHT -> draw(poseStack, text, x - width(text), y - lineHeight, color) + } +} + +fun Font.drawAligned(poseStack: PoseStack, text: String, align: TextAlign, x: Float, y: Float, color: RGBAColor): Int { + return drawAligned(poseStack, text, align, x, y, color.toInt()) +} + +fun Font.drawAligned(poseStack: PoseStack, text: Component, align: TextAlign, x: Float, y: Float, color: RGBAColor): Int { + return drawAligned(poseStack, text, align, x, y, color.toInt()) +} + +fun Font.drawAligned(poseStack: PoseStack, text: FormattedCharSequence, align: TextAlign, x: Float, y: Float, color: RGBAColor): Int { + return drawAligned(poseStack, text, align, x, y, color.toInt()) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GravitationStabilizerRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GravitationStabilizerRenderer.kt index 847240c06..408c4adbf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GravitationStabilizerRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GravitationStabilizerRenderer.kt @@ -2,6 +2,9 @@ package ru.dbotthepony.mc.otm.client.render import com.mojang.blaze3d.vertex.* import com.mojang.math.Matrix4f +import com.mojang.math.Vector3f +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.Font import net.minecraft.client.renderer.MultiBufferSource import net.minecraft.client.renderer.RenderType import net.minecraft.client.renderer.blockentity.BeaconRenderer @@ -9,6 +12,8 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderer import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider import net.minecraft.client.renderer.texture.OverlayTexture import net.minecraft.core.Direction +import net.minecraft.network.chat.TextComponent +import net.minecraft.network.chat.TranslatableComponent import ru.dbotthepony.mc.otm.block.BlockBlackHole import ru.dbotthepony.mc.otm.block.BlockMatteryRotatable import ru.dbotthepony.mc.otm.block.entity.BlockEntityGravitationStabilizer @@ -35,13 +40,16 @@ class GravitationStabilizerRenderer(private val context: BlockEntityRendererProv var len = 64.0 val normal = tile.blockState.getValue(BlockMatteryRotatable.FACING_FULL).normal val level = tile.level + var bhTile: BlockEntityBlackHole? = null if (level != null) { for (i in 1 .. BlockEntityGravitationStabilizer.RANGE) { val pos = tile.blockPos + normal * i + val getTile = level.getBlockEntity(pos) as? BlockEntityBlackHole - if (level.getBlockEntity(tile.blockPos + pos) is BlockEntityBlackHole || level.getBlockState(pos).block is BlockBlackHole) { + if (getTile != null || level.getBlockState(pos).block is BlockBlackHole) { len = i.toDouble() + bhTile = getTile break } } @@ -51,12 +59,13 @@ class GravitationStabilizerRenderer(private val context: BlockEntityRendererProv poseStack.translate(0.5, 0.5, 0.5) val matrix = poseStack.last().pose() + val facing: Direction = tile.blockState.getValue(BlockMatteryRotatable.FACING_FULL) run { val rotation = (System.currentTimeMillis().toDouble() / 1000.0) % (PI * 2) val consumer = p_112310_.getBuffer(BEAM_RENDER_TYPE_INNER) - when (tile.blockState.getValue(BlockMatteryRotatable.FACING_FULL)) { + when (facing) { Direction.DOWN -> ray(matrix, consumer, len, rotation, -PI / 2, 0.0, RAY_RADIUS_INNER) Direction.UP -> ray(matrix, consumer, len, rotation, PI / 2, 0.0, RAY_RADIUS_INNER) Direction.NORTH -> ray(matrix, consumer, len, rotation, 0.0, PI / 2, RAY_RADIUS_INNER) @@ -70,7 +79,7 @@ class GravitationStabilizerRenderer(private val context: BlockEntityRendererProv val consumer = p_112310_.getBuffer(BEAM_RENDER_TYPE_OUTER) val rotation = (-System.currentTimeMillis().toDouble() / 700.0) % (PI * 2) - when (tile.blockState.getValue(BlockMatteryRotatable.FACING_FULL)) { + when (facing) { Direction.DOWN -> ray(matrix, consumer, len, rotation, -PI / 2, 0.0, RAY_RADIUS_OUTER, OUTER_COLOR) Direction.UP -> ray(matrix, consumer, len, rotation, PI / 2, 0.0, RAY_RADIUS_OUTER, OUTER_COLOR) Direction.NORTH -> ray(matrix, consumer, len, rotation, 0.0, PI / 2, RAY_RADIUS_OUTER, OUTER_COLOR) @@ -81,6 +90,24 @@ class GravitationStabilizerRenderer(private val context: BlockEntityRendererProv } poseStack.popPose() + + if (bhTile != null) { + poseStack.pushPose() + poseStack.translate(0.5, 0.5, 0.5) + + val translation = poseStack.translation() + + poseStack.translate(facing.opposite.normal * 0.6) + poseStack.rotateAroundPoint(translation, facing.opposite.asAngle().copy(pitch = PI)) + + poseStack.scale(0.01f, 0.01f, 0.01f) + + val font = Minecraft.getInstance().font + font.drawAligned(poseStack, TranslatableComponent("otm.3d2d.gravitation_stabilizer.mass", bhTile.mass.decimalString(2)), TextAlign.TOP_CENTER, 0f, -font.lineHeight.toFloat() / 2f, 0xFFFFFF) + font.drawAligned(poseStack, TranslatableComponent("otm.3d2d.gravitation_stabilizer.strength", "%.2f".format(bhTile.gravitationStrength)), TextAlign.TOP_CENTER, 0f, font.lineHeight.toFloat() / 2f, 0xFFFFFF) + + poseStack.popPose() + } } override fun shouldRenderOffScreen(p_112306_: BlockEntityGravitationStabilizer) = true diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/EuclidMath.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/EuclidMath.kt index b00ec28dd..506473401 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/EuclidMath.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/EuclidMath.kt @@ -2,30 +2,68 @@ package ru.dbotthepony.mc.otm.core import com.mojang.math.Quaternion import com.mojang.math.Vector3f +import net.minecraft.core.Direction import net.minecraft.world.phys.Vec3 -import kotlin.math.acos -import kotlin.math.cos -import kotlin.math.sin +import kotlin.math.* typealias Vector = Vec3 -val VECTOR_UP = Vector(0.0, 1.0, 0.0) -val VECTOR_FORWARD = Vector(1.0, 0.0, 0.0) -val VECTOR_BACKWARD = Vector(1.0, 0.0, 0.0) -val VECTOR_DOWN = Vector(0.0, -1.0, 0.0) -val VECTOR_RIGHT = Vector(0.0, 0.0, 1.0) -val VECTOR_LEFT = Vector(0.0, 0.0, -1.0) +@JvmField val VECTOR_UP = Vector(0.0, 1.0, 0.0) +@JvmField val VECTOR_FORWARD = Vector(1.0, 0.0, 0.0) +@JvmField val VECTOR_BACKWARD = Vector(-1.0, 0.0, 0.0) +@JvmField val VECTOR_DOWN = Vector(0.0, -1.0, 0.0) +@JvmField val VECTOR_RIGHT = Vector(0.0, 0.0, 1.0) +@JvmField val VECTOR_LEFT = Vector(0.0, 0.0, -1.0) fun Vector.asAngle(): Angle { val norm = normalize() - - if (norm.x < 0) { - return Angle(acos(norm.dot(VECTOR_UP)) - Angle.PI_HALF, -acos(Vector(norm.x, 0.0, norm.z).normalize().dot(VECTOR_RIGHT))) - } - - return Angle(acos(norm.dot(VECTOR_UP)) - Angle.PI_HALF, acos(Vector(norm.x, 0.0, norm.z).normalize().dot(VECTOR_RIGHT))) + return Angle(asin(norm.y), atan2(norm.x, norm.z)) } +fun Vector.asMutableAngle(): MutableAngle { + val norm = normalize() + return MutableAngle(asin(norm.y), atan2(norm.x, norm.z)) +} + +fun Vector3f.asAngle(): Angle { + val len = length() + val norm = Vector(x() / len, y() / len, z() / len) + return Angle(asin(norm.y), atan2(norm.x, norm.z)) +} + +fun Vector3f.asMutableAngle(): MutableAngle { + val len = length() + val norm = Vector(x() / len, y() / len, z() / len) + return MutableAngle(asin(norm.y), atan2(norm.x, norm.z)) +} + +operator fun Vector3f.unaryMinus(): Vector3f { + setX(-x()) + setY(-y()) + setZ(-z()) + return this +} + +fun Vector3f.length() = sqrt(x().toDouble() * x() + y().toDouble() * y() + z().toDouble() * z()) + +operator fun Vector3f.times(v: Float): Vector3f { + setX(x() * v) + setY(y() * v) + setZ(z() * v) + return this +} + +operator fun Vector3f.div(v: Float): Vector3f { + setX(x() / v) + setY(y() / v) + setZ(z() / v) + return this +} + +operator fun Vector3f.component1() = x() +operator fun Vector3f.component2() = y() +operator fun Vector3f.component3() = z() + operator fun Vector.plus(direction: Vector): Vector = this.add(direction) operator fun Vector.minus(direction: Vector): Vector = this.subtract(direction) operator fun Vector.unaryMinus(): Vector = Vector(-x, -y, -z) @@ -44,19 +82,30 @@ fun Vector.up() = asAngle().up() * this.length() fun Vector.down() = asAngle().down() * this.length() fun Vector.rotateAroundAxis(axis: Vector, rotation: Double): Vector { - return this * cos(rotation) + axis.cross(this) * sin(rotation) + axis * axis.dot(this) * (1 - cos(rotation)) + val cross = axis.cross(this) + val dot = axis.dot(this) + val cos = cos(rotation) + val sin = sin(rotation) + //return this * cos(rotation) + axis.cross(this) * sin(rotation) + axis * axis.dot(this) * (1 - cos(rotation)) + + return Vector( + this.x * cos + cross.x * sin + axis.x * dot * (1.0 - cos), + this.y * cos + cross.y * sin + axis.y * dot * (1.0 - cos), + this.z * cos + cross.z * sin + axis.z * dot * (1.0 - cos), + ) } -fun Vector.rotate(angle: Angle): Vector { - val rotatedYaw = this.rotateAroundAxis(VECTOR_UP, angle.yaw) - return rotatedYaw.rotateAroundAxis(rotatedYaw.asAngle().left(), angle.pitch) +fun Vector.rotate(angle: IAngle): Vector { + val asMatrix = asMatrix() + angle.rotationXYZ().multiply(asMatrix, inverse = true) + return Vector(asMatrix[0, 0], asMatrix[1, 0], asMatrix[2, 0]) } fun Vector.asVector3f(): Vector3f { return Vector3f(x.toFloat(), y.toFloat(), z.toFloat()) } -fun Quaternion(vec: Vector, rotation: Float, isDegrees: Boolean): Quaternion { +fun Quaternion(vec: Vector, rotation: Float, isDegrees: Boolean = false): Quaternion { return Quaternion(vec.asVector3f(), rotation, isDegrees) } @@ -77,55 +126,138 @@ fun Vector.rotate(q: Quaternion): Vector { return Vector(quaternion.i().toDouble(), quaternion.j().toDouble(), quaternion.k().toDouble()) } -data class Angle(val pitch: Double = 0.0, val yaw: Double = 0.0, val roll: Double = 0.0) { - fun forward(): Vector { - val yaw = yaw - val x = cos(yaw) * cos(pitch) - val y = -sin(pitch) - val z = sin(yaw) * cos(pitch) +fun Vector.rotateAroundThis(rotation: Float, isDegrees: Boolean = false): Quaternion { + return Quaternion(this, rotation, isDegrees) +} - return Vector(x, y, z) +fun Vector.asMatrix(): MutableMatrix { + return MutableMatrix(3, 1).also { + it[0, 0] = x + it[1, 0] = y + it[2, 0] = z + } +} + +fun Vector.asMatrix4(): MutableMatrix { + return MutableMatrix(4, 1).also { + it[0, 0] = x + it[1, 0] = y + it[2, 0] = z + } +} + +fun Direction.asAngle(): Angle { + return when (this) { + Direction.DOWN -> Angle.DOWN + Direction.UP -> Angle.UP + Direction.NORTH -> Angle.NORTH + Direction.SOUTH -> Angle.SOUTH + Direction.WEST -> Angle.WEST + Direction.EAST -> Angle.EAST + } +} + +interface IAngle { + val pitch: Double + val yaw: Double + val roll: Double + + fun forward() = VECTOR_FORWARD.rotate(this) + fun left() = VECTOR_LEFT.rotate(this) + fun right() = VECTOR_RIGHT.rotate(this) + fun up() = VECTOR_UP.rotate(this) + fun down() = VECTOR_DOWN.rotate(this) + + fun rotationX(): MutableMatrix { + if (roll == 0.0) { + return MutableMatrix(3, 3).also { it.identityFast() } + } + + return MutableMatrix(3, 3).also { + val s = sin(roll) + val c = cos(roll) + + it[0, 0] = 1.0 + it[1, 1] = c + it[1, 2] = -s + it[2, 1] = s + it[2, 2] = c + } } - fun left(): Vector { - return -right() + fun rotationZ(): MutableMatrix { + if (pitch == 0.0) { + return MutableMatrix(3, 3).also { it.identityFast() } + } + + return MutableMatrix(3, 3).also { + val s = sin(pitch) + val c = cos(pitch) + + it[0, 0] = c + it[0, 1] = -s + it[1, 0] = s + it[1, 1] = c + it[2, 2] = 1.0 + } } - fun right(): Vector { - val yaw = yaw + PI_HALF - val pitch = 0.0 - val x = cos(yaw) * cos(pitch) - val y = -sin(pitch) - val z = sin(yaw) * cos(pitch) + fun rotationY(): MutableMatrix { + if (yaw == 0.0) { + return MutableMatrix(3, 3).also { it.identityFast() } + } - return Vector(x, y, z) + return MutableMatrix(3, 3).also { + val s = sin(yaw) + val c = cos(yaw) + + it[0, 0] = c + it[0, 2] = s + + it[2, 0] = -s + it[2, 2] = c + + it[1, 1] = 1.0 + } } - fun up(): Vector { - val yaw = yaw - PI_HALF - val pitch = pitch - PI_HALF - val x = cos(yaw) * cos(pitch) - val y = -sin(pitch) - val z = sin(yaw) * cos(pitch) + fun rotationXYZ(): MutableMatrix { + if (pitch == 0.0 && yaw == 0.0 && roll == 0.0) + return MutableMatrix(3, 3).also { it.identity() } - return Vector(x, y, z) + return rotationY().multiply(rotationZ()).multiply(rotationX()) } - fun down(): Vector { - val yaw = yaw - PI_HALF - val pitch = pitch + PI_HALF - val x = cos(yaw) * cos(pitch) - val y = -sin(pitch) - val z = sin(yaw) * cos(pitch) + fun rotationXYZW(): MutableMatrix { + if (pitch == 0.0 && yaw == 0.0 && roll == 0.0) + return MutableMatrix(4, 4).also { it.identity() } - return Vector(x, y, z) + return rotationXYZ().resize(4, 4).also { it[3, 3] = 1.0 } } +} +data class Angle(override val pitch: Double = 0.0, override val yaw: Double = 0.0, override val roll: Double = 0.0) : IAngle { companion object { const val PI_HALF = Math.PI / 2.0 - fun degrees(pitch: Double, yaw: Double, roll: Double): Angle { + fun deg(pitch: Double, yaw: Double, roll: Double): Angle { return Angle(Math.toDegrees(pitch), Math.toDegrees(yaw), Math.toDegrees(roll)) } + + @JvmField val ZERO = Angle() + @JvmField val UP = Angle(pitch = PI / 2) + @JvmField val DOWN = Angle(pitch = -PI / 2) + @JvmField val NORTH = Angle() + @JvmField val SOUTH = Angle(yaw = PI) + @JvmField val WEST = Angle(yaw = PI / 2) + @JvmField val EAST = Angle(yaw = -PI / 2) + } +} + +data class MutableAngle(override var pitch: Double = 0.0, override var yaw: Double = 0.0, override var roll: Double = 0.0) : IAngle { + companion object { + fun deg(pitch: Double, yaw: Double, roll: Double): MutableAngle { + return MutableAngle(Math.toDegrees(pitch), Math.toDegrees(yaw), Math.toDegrees(roll)) + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt index 946e10c5b..38c08014d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -15,6 +15,7 @@ operator fun Vec3i.minus(direction: Vec3i): Vec3i = this.subtract(direction) operator fun Vec3i.minus(direction: Direction): Vec3i = this.subtract(direction.normal) operator fun Vec3i.times(int: Int): Vec3i = this.multiply(int) +operator fun Vec3i.times(double: Double): Vector = Vector(x * double, y * double, z * double) fun BlockPos.asVector(): Vector { return Vector(x + 0.5, y + 0.5, z + 0.5) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/MutableMatrix.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/MutableMatrix.kt new file mode 100644 index 000000000..ce5e5cfa6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/MutableMatrix.kt @@ -0,0 +1,276 @@ +package ru.dbotthepony.mc.otm.core + +import com.mojang.math.Matrix3f +import com.mojang.math.Matrix4f +import com.mojang.math.Vector3f +import com.mojang.math.Vector4f +import org.jetbrains.annotations.Contract +import java.nio.FloatBuffer + +fun Matrix3f.asMutableMatrix(): MutableMatrix = MutableMatrix(this) +fun Matrix4f.asMutableMatrix(): MutableMatrix = MutableMatrix(this) + +fun Matrix4f.rotate(angle: IAngle): Matrix4f { + multiply(angle.rotationXYZW().toMatrix4f()) + return this +} + +fun Matrix4f.translate(vector: Vector) { + translate(-vector.asVector3f()) +} + +fun Matrix4f.rotateAroundPoint(point: Vector, axis: Vector, rotation: Float, isDegrees: Boolean = false) { + val p = point.asVector3f() + translate(-p) + multiply(axis.rotateAroundThis(rotation, isDegrees)) + translate(-p) +} + +fun Matrix4f.rotateAroundPoint(point: Vector, axis: Vector3f, rotation: Float, isDegrees: Boolean = false) { + val p = point.asVector3f() + translate(-p) + multiply(if (isDegrees) axis.rotationDegrees(rotation) else axis.rotation(rotation)) + translate(-p) +} + +fun Matrix4f.rotateAroundPoint(point: Vector, rotation: IAngle) { + val p = point.asVector3f() + translate(-p) + multiply(rotation.rotationXYZW().toMatrix4f()) + translate(-p) +} + +fun Matrix4f.rotateAroundPoint(point: Vector3f, axis: Vector, rotation: Float, isDegrees: Boolean = false) { + translate(-point) + multiply(axis.rotateAroundThis(rotation, isDegrees)) + translate(-point) +} + +fun Matrix4f.rotateAroundPoint(point: Vector3f, rotation: IAngle) { + translate(-point) + multiply(rotation.rotationXYZW().toMatrix4f()) + translate(-point) +} + +fun Matrix4f.rotateAroundPoint(point: Vector3f, axis: Vector3f, rotation: Float, isDegrees: Boolean = false) { + translate(-point) + multiply(if (isDegrees) axis.rotationDegrees(rotation) else axis.rotation(rotation)) + translate(-point) +} + +private val floatBuff3 = FloatBuffer.allocate(3 * 3) +private val floatBuff4 = FloatBuffer.allocate(4 * 4) + +fun Matrix4f.getTranslation(): Vector3f { + store(floatBuff4) + + return Vector3f(floatBuff4[0 + 3 * 4], floatBuff4[1 + 3 * 4], floatBuff4[2 + 3 * 4]) +} + +fun Matrix4f.getTranslation4(): Vector4f { + store(floatBuff4) + + return Vector4f(floatBuff4[0 + 3 * 4], floatBuff4[1 + 3 * 4], floatBuff4[2 + 3 * 4], floatBuff4[3 + 3 * 4]) +} + +class MutableMatrix(val rows: Int, val columns: Int = 1) { + private val buffer = Array(columns * rows) {0.0} + + constructor(matrix3f: Matrix3f) : this(3, 3) { + matrix3f.store(floatBuff3) + load(floatBuff3) + } + + constructor(matrix4f: Matrix4f) : this(4, 4) { + matrix4f.store(floatBuff4) + load(floatBuff4) + } + + constructor(other: MutableMatrix) : this(other.rows, other.columns) { + for (i in 0 .. buffer.size) { + buffer[i] = other.buffer[i] + } + } + + fun identity(): MutableMatrix { + check(columns == rows) { "Matrix is not cubic ($rows by $columns)" } + + for (column in 0 until columns) { + for (row in 0 until rows) { + buffer[row + column * rows] = if (column == row) 1.0 else 0.0 + } + } + + return this + } + + fun identityFast(): MutableMatrix { + check(columns == rows) { "Matrix is not cubic ($rows by $columns)" } + + for (i in 0 until rows) { + buffer[i + i * columns] = 1.0 + } + + return this + } + + fun toMatrix3f(): Matrix3f { + check(rows == columns && rows == 3) { "This matrix has wrong dimensions: $rows by $columns" } + store(floatBuff3) + return Matrix3f().also { it.load(floatBuff3) } + } + + fun toMatrix4f(): Matrix4f { + check(rows == columns && rows == 4) { "This matrix has wrong dimensions: $rows by $columns" } + store(floatBuff4) + return Matrix4f().also { it.load(floatBuff4) } + } + + fun store(buff: FloatBuffer) { + for (row in 0 until rows) { + for (column in 0 until columns) { + buff.put(row + column * columns, buffer[row + column * columns].toFloat()) + } + } + } + + fun store(matrix: Matrix4f) { + check(rows == columns && rows == 4) { "This matrix has wrong dimensions: $rows by $columns" } + store(floatBuff4) + matrix.load(floatBuff4) + } + + fun store(matrix: Matrix3f) { + check(rows == columns && rows == 3) { "This matrix has wrong dimensions: $rows by $columns" } + store(floatBuff3) + matrix.load(floatBuff3) + } + + fun load(buff: FloatBuffer) { + for (row in 0 until rows) { + for (column in 0 until columns) { + buffer[row + column * columns] = buff[row + column * columns].toDouble() + } + } + } + + fun load(matrix: Matrix4f) { + check(rows == columns && rows == 4) { "This matrix has wrong dimensions: $rows by $columns" } + matrix.store(floatBuff4) + load(floatBuff4) + } + + fun load(matrix: Matrix3f) { + check(rows == columns && rows == 3) { "This matrix has wrong dimensions: $rows by $columns" } + matrix.store(floatBuff3) + load(floatBuff3) + } + + fun resize(rows: Int, columns: Int): MutableMatrix { + return MutableMatrix(rows, columns).also { + for (row in 0 until this.rows.coerceAtMost(rows)) { + for (column in 0 until this.columns.coerceAtMost(columns)) { + it.buffer[row + column * columns] = buffer[row + column * this.columns] + } + } + } + } + + @Contract(pure = true) + operator fun get(row: Int, column: Int): Double { + require(row >= 0) { "Row out of bounds: $row" } + require(row < rows) { "Row out of bounds: $row" } + require(column >= 0) { "Column out of bounds: $row" } + require(column < columns) { "Column out of bounds: $row" } + return buffer[row + column * rows] + } + + operator fun set(row: Int, column: Int, value: Double) { + require(row >= 0) { "Row out of bounds: $row" } + require(row < rows) { "Row out of bounds: $row" } + require(column >= 0) { "Column out of bounds: $row" } + require(column < columns) { "Column out of bounds: $row" } + buffer[row + column * rows] = value + } + + /** + * this = this * other + * при inverse = true + * other = this * other + * + * без создания новой матрицы + */ + fun multiply(other: MutableMatrix, inverse: Boolean = false): MutableMatrix { + check(columns == other.rows) { "Matrixes are not compatible for multiplication (this: $rows by $columns, other: ${other.rows} by ${other.columns})" } + val resultSize = rows * other.columns + check(!inverse && buffer.size == resultSize || inverse && other.buffer.size == resultSize) { "Matrixes are not compatible for ${if (inverse) "direct" else "inverse"} multiplication in place (this: $rows by $columns, other: ${other.rows} by ${other.columns})" } + + val buff = Array(resultSize) { 0.0 } + + for (row in 0 until rows) { + for (column in 0 until other.columns) { + var sum = 0.0 + + for (elem in 0 until columns) { + sum += this[row, elem] * other[elem, column] + } + + buff[row + column * columns] = sum + } + } + + if (inverse) { + for (i in buff.indices) { + other.buffer[i] = buff[i] + } + } else { + for (i in buff.indices) { + buffer[i] = buff[i] + } + } + + return if (inverse) other else this + } + + /** + * new = this * other + * + * с созданием новой матрицы + */ + operator fun times(other: MutableMatrix): MutableMatrix { + check(columns == other.rows) { "Matrixes are not compatible for multiplication (this: $rows by $columns, other: ${other.rows} by ${other.columns})" } + val new = MutableMatrix(rows, other.columns) + + for (row in 0 until rows) { + for (column in 0 until other.columns) { + var sum = 0.0 + + for (elem in 0 until columns) { + sum += this[row, elem] * other[elem, column] + } + + new[row, column] = sum + } + } + + return new + } + + var translation: Vector + get() { + check(columns == rows && rows == 4) { "Matrix is expected to be 4x4 (this one is $rows by $columns)" } + return Vector(this[0, 3], this[1, 3], this[2, 3]) + } + + set(value) { + check(columns == rows && rows == 4) { "Matrix is expected to be 4x4 (this one is $rows by $columns)" } + this[0, 0] = 1.0 + this[1, 1] = 1.0 + this[2, 2] = 1.0 + this[3, 3] = 1.0 + + this[0, 3] = value.x + this[1, 3] = value.y + this[2, 3] = value.z + } +} diff --git a/src/main/resources/assets/overdrive_that_matters/lang/en_us.json b/src/main/resources/assets/overdrive_that_matters/lang/en_us.json index 64a748e60..36651e748 100644 --- a/src/main/resources/assets/overdrive_that_matters/lang/en_us.json +++ b/src/main/resources/assets/overdrive_that_matters/lang/en_us.json @@ -41,6 +41,9 @@ "otm.gui.redstone.low.description": "Machine work if no redstone signal is present", "otm.gui.redstone.high.description": "Machine work only if any redstone signal is present", + "otm.3d2d.gravitation_stabilizer.mass": "Singularity mass: %s", + "otm.3d2d.gravitation_stabilizer.strength": "Gravitation strength: %s", + "otm.death_reason": "Decommissioned!", "otm.item.power.infinite.storage": "Stored energy: Infinity / Infinity",