HTTPS
https://git.nageyqma.icu/nageyqma/odin_next.git
SSH
git@git.nageyqma.icu:nageyqma/odin_next.git
HTTPS 链接可直接 git clone;推送代码请使用 SSH。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
package com.odtheking.odin.utils.render
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.odtheking.mixin.accessors.BeaconBeamAccessor
import com.odtheking.odin.OdinMod.mc
import com.odtheking.odin.events.RenderExtractEvent
import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoints
import com.odtheking.odin.utils.Color
import com.odtheking.odin.utils.Color.Companion.multiplyAlpha
import com.odtheking.odin.utils.addVec
import com.odtheking.odin.utils.center
import net.fabricmc.fabric.api.client.rendering.v1.SubmitRenderPhases
import net.minecraft.client.gui.Font
import net.minecraft.client.renderer.feature.TextFeatureRenderer
import net.minecraft.client.renderer.rendertype.RenderTypes
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.core.BlockPos
import net.minecraft.locale.Language
import net.minecraft.network.chat.FormattedText
import net.minecraft.resources.Identifier
import net.minecraft.util.LightCoordsUtil
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import org.joml.Matrix4f
import org.joml.Vector3f
import org.joml.unaryMinus
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.sin
import kotlin.math.sqrt
private val BEAM_TEXTURE = Identifier.withDefaultNamespace("textures/entity/beacon/beacon_beam.png")
private fun Int.isFullyOpaque(): Boolean = ((this ushr 24) and 0xFF) == 0xFF
private fun resolveLineRenderType(depth: Boolean, fullyOpaque: Boolean) = when {
depth && fullyOpaque -> RenderTypes.LINES
depth -> RenderTypes.LINES_TRANSLUCENT
fullyOpaque -> CustomRenderType.LINES_ESP
else -> CustomRenderType.LINES_TRANSLUCENT_ESP
}
private fun resolveQuadRenderType(depth: Boolean, fullyOpaque: Boolean) = when {
depth && fullyOpaque -> CustomRenderType.QUADS_OPAQUE
depth -> CustomRenderType.QUADS_TRANSLUCENT
fullyOpaque -> CustomRenderType.QUADS_ESP
else -> CustomRenderType.QUADS_TRANSLUCENT_ESP
}
private fun RenderExtractEvent.cameraRelativePose(offset: Vec3 = Vec3.ZERO): PoseStack {
val camera = mc.gameRenderer.mainCamera().position()
val poseStack = context.poseStack()
poseStack.pushPose()
poseStack.translate(offset.x - camera.x, offset.y - camera.y, offset.z - camera.z)
return poseStack
}
fun RenderExtractEvent.drawTexturedQuad(
texture: Identifier,
pos: Vec3,
width: Float,
height: Float,
yaw: Float = 0f,
color: Color = Color(255, 255, 255)
) {
val yawRad = Math.toRadians(yaw.toDouble())
val rx = cos(yawRad).toFloat()
val rz = sin(yawRad).toFloat()
val hw = width * 0.5
val hh = height * 0.5
val poseStack = cameraRelativePose()
context.submitNodeCollector().submitCustomGeometry(poseStack, RenderTypes.entityCutout(texture)) { pose, buffer ->
fun vertex(p: Vec3, u: Float, v: Float) {
buffer.addVertex(pose, p.x.toFloat(), p.y.toFloat(), p.z.toFloat())
.setColor(color.rgba)
.setUv(u, v)
.setOverlay(OverlayTexture.NO_OVERLAY)
.setUv2(LightCoordsUtil.FULL_BRIGHT, LightCoordsUtil.FULL_BRIGHT)
.setNormal(pose, -rz, 0f, rx)
}
vertex(Vec3(pos.x - rx * hw, pos.y - hh, pos.z - rz * hw), 0f, 1f)
vertex(Vec3(pos.x - rx * hw, pos.y + hh, pos.z - rz * hw), 0f, 0f)
vertex(Vec3(pos.x + rx * hw, pos.y + hh, pos.z + rz * hw), 1f, 0f)
vertex(Vec3(pos.x + rx * hw, pos.y - hh, pos.z + rz * hw), 1f, 1f)
}
poseStack.popPose()
}
fun RenderExtractEvent.drawTracer(to: Vec3, color: Color, depth: Boolean, thickness: Float = 3f) {
val cam = mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState
drawLine(listOf(cam.pos.add(Vec3.directionFromRotation(cam.xRot, cam.yRot)), to), color, depth, thickness)
}
fun RenderExtractEvent.drawLine(points: Collection<Vec3>, color: Color, depth: Boolean, thickness: Float = 3f) {
drawLine(points, color, color, depth, thickness)
}
fun RenderExtractEvent.drawLine(points: Collection<Vec3>, color1: Color, color2: Color, depth: Boolean, thickness: Float = 3f) {
if (points.size < 2) return
val rgba1 = color1.rgba
val rgba2 = color2.rgba
val renderType = resolveLineRenderType(depth, rgba1.isFullyOpaque() && rgba2.isFullyOpaque())
val segments = points.zipWithNext()
val poseStack = cameraRelativePose()
context.submitNodeCollector().submitCustomGeometry(poseStack, renderType) { pose, buffer ->
for ((from, to) in segments) {
PrimitiveRenderer.renderVector(
pose, buffer,
Vector3f(from.x.toFloat(), from.y.toFloat(), from.z.toFloat()),
to.subtract(from), rgba1, rgba2, thickness
)
}
}
poseStack.popPose()
}
fun RenderExtractEvent.drawWireFrameBox(aabb: AABB, color: Color, thickness: Float = 3f, depth: Boolean = false) {
val renderType = resolveLineRenderType(depth, color.alphaFloat >= 0.999f)
val poseStack = cameraRelativePose()
context.submitNodeCollector().submitCustomGeometry(poseStack, renderType) { pose, buffer ->
PrimitiveRenderer.renderLineBox(pose, buffer, aabb, color.redFloat, color.greenFloat, color.blueFloat, color.alphaFloat, thickness)
}
poseStack.popPose()
}
fun RenderExtractEvent.drawFilledBox(aabb: AABB, color: Color, depth: Boolean = false) {
val renderType = resolveQuadRenderType(depth, color.alphaFloat >= 0.999f)
val poseStack = cameraRelativePose()
context.submitNodeCollector().submitCustomGeometry(poseStack, renderType) { pose, buffer ->
PrimitiveRenderer.addChainedFilledBoxVertices(
pose, buffer,
aabb.minX.toFloat(), aabb.minY.toFloat(), aabb.minZ.toFloat(),
aabb.maxX.toFloat(), aabb.maxY.toFloat(), aabb.maxZ.toFloat(),
color.redFloat, color.greenFloat, color.blueFloat, color.alphaFloat
)
}
poseStack.popPose()
}
enum class BoxStyle { FILLED, OUTLINE, FILLED_OUTLINE }
fun RenderExtractEvent.drawStyledBox(
aabb: AABB,
color: Color,
style: BoxStyle = BoxStyle.FILLED,
depth: Boolean = true
) {
when (style) {
BoxStyle.FILLED -> drawFilledBox(aabb, color, depth = depth)
BoxStyle.OUTLINE -> drawWireFrameBox(aabb, color, depth = depth)
BoxStyle.FILLED_OUTLINE -> {
drawFilledBox(aabb.inflate(0.00005), color.multiplyAlpha(0.5f), depth = depth)
drawWireFrameBox(aabb, color, depth = depth)
}
}
}
fun RenderExtractEvent.drawBeaconBeam(position: BlockPos, color: Color) {
val isScoping = mc.player?.isScoping == true
val gameTime = mc.level?.gameTime ?: 0L
val camera = mc.gameRenderer.mainCamera().position()
val centerX = position.x + 0.5
val centerZ = position.z + 0.5
val dx = camera.x - centerX
val dz = camera.z - centerZ
val length = sqrt(dx * dx + dz * dz).toFloat()
val scale = if (isScoping) 1.0f else maxOf(1.0f, length * 0.010416667f)
val poseStack = cameraRelativePose(Vec3(position.x.toDouble(), position.y.toDouble(), position.z.toDouble()))
BeaconBeamAccessor.invokeRenderBeam(
poseStack, context.submitNodeCollector(), BEAM_TEXTURE, 1f,
gameTime.toFloat(), 0, 319, color.rgba, 0.2f * scale, 0.25f * scale
)
poseStack.popPose()
}
fun RenderExtractEvent.drawText(text: String, pos: Vec3, scale: Float, depth: Boolean) {
val scaleFactor = scale * 0.025f
val displayMode = if (depth) Font.DisplayMode.POLYGON_OFFSET else Font.DisplayMode.SEE_THROUGH
val string = Language.getInstance().getVisualOrder(FormattedText.of(text))
val x = -mc.font.width(text).toFloat() / 2f
val poseStack = context.poseStack()
poseStack.pushPose()
poseStack.last().pose()
.translate(pos.toVector3f())
.translate(-mc.gameRenderer.mainCamera().position().toVector3f())
.rotate(mc.gameRenderer.mainCamera().rotation())
.scale(scaleFactor, -scaleFactor, scaleFactor)
if (displayMode == Font.DisplayMode.SEE_THROUGH) {
context.submitNodeCollector().submitCustom(SubmitRenderPhases.AFTER_TERRAIN,
TextFeatureRenderer.Submit(
Matrix4f(poseStack.last().pose()), displayMode, LightCoordsUtil.FULL_BRIGHT,
TextFeatureRenderer.Content.Text(x, 0f, string, true, -1, 0, 0)
)
)
} else context.submitNodeCollector().submitText(poseStack, x, 0f, string, true, displayMode, LightCoordsUtil.FULL_BRIGHT, -1, 0, 0)
poseStack.popPose()
}
fun RenderExtractEvent.drawCustomBeacon(title: String, position: BlockPos, color: Color, increase: Boolean = true, distance: Boolean = true) {
val dist = mc.player?.blockPosition()?.distManhattan(position) ?: return
drawWireFrameBox(AABB(position), color, depth = false)
drawBeaconBeam(position, color)
drawText(
(if (distance) ("$title §f(§3${dist}m§f)") else title),
position.center.addVec(y = 1.7),
if (increase) max(1f, dist * 0.05f) else 2f,
false
)
}
fun RenderExtractEvent.drawCylinder(
center: Vec3,
radius: Float,
height: Float,
color: Color,
segments: Int = 32,
thickness: Float = 5f,
depth: Boolean = false
) {
val rgba = color.rgba
val renderType = resolveLineRenderType(depth, rgba.isFullyOpaque())
val angleStep = 2.0 * Math.PI / segments
val poseStack = cameraRelativePose()
context.submitNodeCollector().submitCustomGeometry(poseStack, renderType) { pose, buffer ->
fun segment(from: Vec3, to: Vec3) {
PrimitiveRenderer.renderVector(
pose, buffer,
Vector3f(from.x.toFloat(), from.y.toFloat(), from.z.toFloat()),
to.subtract(from), rgba, rgba, thickness
)
}
for (i in 0 until segments) {
val angle1 = i * angleStep
val angle2 = (i + 1) * angleStep
val x1 = (radius * cos(angle1)).toFloat()
val z1 = (radius * sin(angle1)).toFloat()
val x2 = (radius * cos(angle2)).toFloat()
val z2 = (radius * sin(angle2)).toFloat()
val p1Top = center.add(x1.toDouble(), height.toDouble(), z1.toDouble())
val p2Top = center.add(x2.toDouble(), height.toDouble(), z2.toDouble())
val p1Bottom = center.add(x1.toDouble(), 0.0, z1.toDouble())
val p2Bottom = center.add(x2.toDouble(), 0.0, z2.toDouble())
segment(p1Top, p2Top)
segment(p1Bottom, p2Bottom)
segment(p1Bottom, p1Top)
}
}
poseStack.popPose()
}
fun RenderExtractEvent.drawBoxes(waypoints: Collection<DungeonWaypoints.DungeonWaypoint>, disableDepth: Boolean) {
if (waypoints.isEmpty()) return
for ((blockPos, color, filled, depth1, aabb1, _, _, isClicked) in waypoints) {
if (isClicked || color.isTransparent) continue
val aabb = aabb1.move(blockPos)
val depth = depth1 && !disableDepth
if (filled) drawFilledBox(aabb, color, depth = depth)
else drawWireFrameBox(aabb, color, depth = depth)
}
}
object PrimitiveRenderer {
private val edges = intArrayOf(
0, 1, 1, 5, 5, 4, 4, 0,
3, 2, 2, 6, 6, 7, 7, 3,
0, 3, 1, 2, 5, 6, 4, 7
)
fun renderLineBox(
pose: PoseStack.Pose,
buffer: VertexConsumer,
aabb: AABB,
r: Float, g: Float, b: Float, a: Float,
thickness: Float
) {
val x0 = aabb.minX.toFloat()
val y0 = aabb.minY.toFloat()
val z0 = aabb.minZ.toFloat()
val x1 = aabb.maxX.toFloat()
val y1 = aabb.maxY.toFloat()
val z1 = aabb.maxZ.toFloat()
val corners = floatArrayOf(
x0, y0, z0,
x1, y0, z0,
x1, y1, z0,
x0, y1, z0,
x0, y0, z1,
x1, y0, z1,
x1, y1, z1,
x0, y1, z1
)
for (i in edges.indices step 2) {
val i0 = edges[i] * 3
val i1 = edges[i + 1] * 3
val x0 = corners[i0]
val y0 = corners[i0 + 1]
val z0 = corners[i0 + 2]
val x1 = corners[i1]
val y1 = corners[i1 + 1]
val z1 = corners[i1 + 2]
val dx = x1 - x0
val dy = y1 - y0
val dz = z1 - z0
buffer.addVertex(pose, x0, y0, z0).setColor(r, g, b, a).setNormal(pose, dx, dy, dz).setLineWidth(thickness)
buffer.addVertex(pose, x1, y1, z1).setColor(r, g, b, a).setNormal(pose, dx, dy, dz).setLineWidth(thickness)
}
}
fun addChainedFilledBoxVertices(
pose: PoseStack.Pose,
buffer: VertexConsumer,
minX: Float, minY: Float, minZ: Float,
maxX: Float, maxY: Float, maxZ: Float,
r: Float, g: Float, b: Float, a: Float
) {
val matrix = pose.pose()
fun vertex(x: Float, y: Float, z: Float) {
buffer.addVertex(matrix, x, y, z).setColor(r, g, b, a)
}
vertex(minX, minY, minZ)
vertex(minX, minY, maxZ)
vertex(minX, maxY, maxZ)
vertex(minX, maxY, minZ)
vertex(maxX, minY, maxZ)
vertex(maxX, minY, minZ)
vertex(maxX, maxY, minZ)
vertex(maxX, maxY, maxZ)
vertex(minX, minY, minZ)
vertex(minX, maxY, minZ)
vertex(maxX, maxY, minZ)
vertex(maxX, minY, minZ)
vertex(maxX, minY, maxZ)
vertex(maxX, maxY, maxZ)
vertex(minX, maxY, maxZ)
vertex(minX, minY, maxZ)
vertex(minX, minY, minZ)
vertex(maxX, minY, minZ)
vertex(maxX, minY, maxZ)
vertex(minX, minY, maxZ)
vertex(minX, maxY, maxZ)
vertex(maxX, maxY, maxZ)
vertex(maxX, maxY, minZ)
vertex(minX, maxY, minZ)
}
fun renderVector(
pose: PoseStack.Pose,
buffer: VertexConsumer,
start: Vector3f,
direction: Vec3,
startColor: Int,
endColor: Int,
thickness: Float
) {
val endX = start.x() + direction.x.toFloat()
val endY = start.y() + direction.y.toFloat()
val endZ = start.z() + direction.z.toFloat()
val nx = direction.x.toFloat()
val ny = direction.y.toFloat()
val nz = direction.z.toFloat()
buffer.addVertex(pose, start.x(), start.y(), start.z())
.setColor(startColor)
.setNormal(pose, nx, ny, nz)
.setLineWidth(thickness)
buffer.addVertex(pose, endX, endY, endZ)
.setColor(endColor)
.setNormal(pose, nx, ny, nz)
.setLineWidth(thickness)
}
}