rendering: improve 3d line rendering, render entity hit boxes

This commit is contained in:
Bixilon 2021-06-09 22:37:55 +02:00
parent da739cad79
commit 5b680ae066
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
10 changed files with 265 additions and 57 deletions

View File

@ -15,6 +15,7 @@ package de.bixilon.minosoft.config.config.game
import de.bixilon.minosoft.config.config.game.controls.ControlsGameConfig
import de.bixilon.minosoft.config.config.game.elements.ElementsGameConfig
import de.bixilon.minosoft.config.config.game.entities.EntitiesConfig
import de.bixilon.minosoft.config.config.game.graphics.GraphicsGameConfig
import de.bixilon.minosoft.config.config.game.sound.SoundConfig
@ -26,4 +27,5 @@ data class GameConfig(
var elements: ElementsGameConfig = ElementsGameConfig(),
var camera: CameraGameConfig = CameraGameConfig(),
var sound: SoundConfig = SoundConfig(),
var entities: EntitiesConfig = EntitiesConfig(),
)

View File

@ -0,0 +1,20 @@
/*
* Minosoft
* Copyright (C) 2021 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.config.config.game.entities
import com.squareup.moshi.Json
data class EntitiesConfig(
@Json(name = "hit_box") val hitBox: EntityHitBoxConfig = EntityHitBoxConfig(),
)

View File

@ -0,0 +1,27 @@
/*
* Minosoft
* Copyright (C) 2021 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.config.config.game.entities
import com.squareup.moshi.Json
import de.bixilon.minosoft.data.text.ChatColors
import de.bixilon.minosoft.data.text.RGBColor
data class EntityHitBoxConfig(
@Json(name = "enabled") val enabled: Boolean = true,
@Json(name = "disable_z_buffer") val disableZBuffer: Boolean = false,
@Json(name = "hit_box_color") val hitBoxColor: RGBColor = ChatColors.WHITE,
@Json(name = "eye_height_color") val eyeHeightColor: RGBColor = ChatColors.RED,
@Json(name = "render_invisible_entities") val renderInvisibleEntities: Boolean = false,
@Json(name = "invisible_entities_color") val invisibleEntitiesColor: RGBColor = ChatColors.GREEN,
)

View File

@ -19,10 +19,14 @@ import de.bixilon.minosoft.data.mappings.entities.EntityFactory
import de.bixilon.minosoft.data.mappings.entities.EntityType
import de.bixilon.minosoft.data.mappings.particle.data.ParticleData
import de.bixilon.minosoft.protocol.network.connection.PlayConnection
import glm_.vec2.Vec2
import glm_.vec3.Vec3d
class AreaEffectCloud(connection: PlayConnection, entityType: EntityType, position: Vec3d, rotation: EntityRotation) : Entity(connection, entityType, position, rotation) {
override val dimensions: Vec2
get() = Vec2(radius * 2, super.dimensions.y)
@get:EntityMetaDataFunction(name = "Ignore radius")
val ignoreRadius: Boolean
get() = entityMetaData.sets.getBoolean(EntityMetaDataFields.AREA_EFFECT_CLOUD_IGNORE_RADIUS)

View File

@ -19,6 +19,7 @@ import de.bixilon.minosoft.config.config.game.controls.KeyBindingsNames
import de.bixilon.minosoft.data.mappings.ResourceLocation
import de.bixilon.minosoft.gui.rendering.chunk.WorldRenderer
import de.bixilon.minosoft.gui.rendering.chunk.block.outline.BlockOutlineRenderer
import de.bixilon.minosoft.gui.rendering.entities.EntityHitBoxRenderer
import de.bixilon.minosoft.gui.rendering.font.Font
import de.bixilon.minosoft.gui.rendering.hud.HUDRenderer
import de.bixilon.minosoft.gui.rendering.hud.atlas.TextureLike
@ -115,6 +116,9 @@ class RenderWindow(
if (Minosoft.config.config.game.graphics.particles.enabled) {
registerRenderer(ParticleRenderer)
}
if (Minosoft.config.config.game.entities.hitBox.enabled) {
registerRenderer(EntityHitBoxRenderer)
}
registerRenderer(HUDRenderer)
}

View File

@ -35,17 +35,16 @@ class BlockOutlineRenderer(
private var currentOutlinePosition: Vec3i? = null
private var currentOutlineBlockState: BlockState? = null
private var outlineMesh: LineMesh? = null
private var collisionMesh: LineMesh? = null
private var currentMesh: LineMesh? = null
private fun draw(outlineMesh: LineMesh, collisionMesh: LineMesh?) {
private fun drawMesh() {
val currentMesh = currentMesh ?: return
glDisable(GL_CULL_FACE)
if (Minosoft.config.config.game.other.blockOutline.disableZBuffer) {
glDepthFunc(GL_ALWAYS)
}
renderWindow.shaderManager.genericColorShader.use()
outlineMesh.draw()
collisionMesh?.draw()
currentMesh.draw()
glEnable(GL_CULL_FACE)
if (Minosoft.config.config.game.other.blockOutline.disableZBuffer) {
glDepthFunc(GL_LESS)
@ -53,11 +52,9 @@ class BlockOutlineRenderer(
}
private fun unload() {
outlineMesh ?: return
outlineMesh?.unload()
collisionMesh?.unload()
this.outlineMesh = null
this.collisionMesh = null
currentMesh ?: return
currentMesh?.unload()
this.currentMesh = null
this.currentOutlinePosition = null
this.currentOutlineBlockState = null
}
@ -65,8 +62,7 @@ class BlockOutlineRenderer(
override fun draw() {
val raycastHit = renderWindow.inputHandler.camera.getTargetBlock()
var outlineMesh = outlineMesh
var collisionMesh = collisionMesh
var currentMesh = currentMesh
if (raycastHit == null) {
unload()
@ -86,33 +82,29 @@ class BlockOutlineRenderer(
}
if (raycastHit.blockPosition == currentOutlinePosition && raycastHit.blockState == currentOutlineBlockState) {
draw(outlineMesh!!, collisionMesh)
drawMesh()
return
}
outlineMesh?.unload()
collisionMesh?.unload()
outlineMesh = LineMesh(Minosoft.config.config.game.other.blockOutline.outlineColor, LINE_WIDTH)
currentMesh?.unload()
currentMesh = LineMesh()
val blockOffset = raycastHit.blockPosition.toVec3d + raycastHit.blockPosition.getWorldOffset(raycastHit.blockState.block)
outlineMesh.drawVoxelShape(raycastHit.blockState.outlineShape, blockOffset, outlineMesh)
outlineMesh.load()
currentMesh.drawVoxelShape(raycastHit.blockState.outlineShape, blockOffset, LINE_WIDTH, Minosoft.config.config.game.other.blockOutline.outlineColor)
if (Minosoft.config.config.game.other.blockOutline.collisionBoxes) {
collisionMesh = LineMesh(Minosoft.config.config.game.other.blockOutline.collisionColor, LINE_WIDTH)
collisionMesh.drawVoxelShape(raycastHit.blockState.collisionShape, blockOffset, collisionMesh, 0.005f)
collisionMesh.load()
this.collisionMesh = collisionMesh
currentMesh.drawVoxelShape(raycastHit.blockState.collisionShape, blockOffset, LINE_WIDTH, Minosoft.config.config.game.other.blockOutline.collisionColor, 0.005f)
}
currentMesh.load()
this.currentOutlinePosition = raycastHit.blockPosition
this.currentOutlineBlockState = raycastHit.blockState
this.outlineMesh = outlineMesh
draw(outlineMesh, collisionMesh)
this.currentMesh = currentMesh
drawMesh()
}

View File

@ -0,0 +1,45 @@
/*
* Minosoft
* Copyright (C) 2021 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.gui.rendering.entities
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.data.entities.entities.Entity
import de.bixilon.minosoft.gui.rendering.chunk.models.AABB
import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY
import de.bixilon.minosoft.gui.rendering.util.mesh.LineMesh
import glm_.vec3.Vec3
import glm_.vec3.Vec3d
class EntityHitBoxMesh(
val entity: Entity,
) : LineMesh() {
val aabb = entity.aabb
init {
val hitboxColor = when {
entity.isInvisible -> Minosoft.config.config.game.entities.hitBox.invisibleEntitiesColor
else -> Minosoft.config.config.game.entities.hitBox.hitBoxColor
}
drawAABB(entity.aabb, Vec3d.EMPTY, LINE_WIDTH, hitboxColor)
val halfWidth = entity.dimensions.x / 2
val eyeAABB = AABB(Vec3(-halfWidth, entity.eyeHeight - LINE_WIDTH, -halfWidth), Vec3(halfWidth, entity.eyeHeight - LINE_WIDTH, halfWidth))
drawAABB(eyeAABB, entity.position, LINE_WIDTH, Minosoft.config.config.game.entities.hitBox.eyeHeightColor)
}
companion object {
private const val LINE_WIDTH = 1.0f / 128.0f
}
}

View File

@ -0,0 +1,105 @@
/*
* Minosoft
* Copyright (C) 2021 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.gui.rendering.entities
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.data.entities.entities.Entity
import de.bixilon.minosoft.data.mappings.ResourceLocation
import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.Renderer
import de.bixilon.minosoft.gui.rendering.RendererBuilder
import de.bixilon.minosoft.modding.event.CallbackEventInvoker
import de.bixilon.minosoft.modding.event.events.EntityDestroyEvent
import de.bixilon.minosoft.modding.event.events.EntitySpawnEvent
import de.bixilon.minosoft.protocol.network.connection.PlayConnection
import de.bixilon.minosoft.util.KUtil.synchronizedMapOf
import de.bixilon.minosoft.util.KUtil.toSynchronizedMap
import de.bixilon.minosoft.util.collections.SynchronizedMap
import org.lwjgl.opengl.GL11.*
class EntityHitBoxRenderer(
val connection: PlayConnection,
val renderWindow: RenderWindow,
) : Renderer {
private val meshes: SynchronizedMap<Entity, EntityHitBoxMesh> = synchronizedMapOf()
private fun createMesh(entity: Entity): EntityHitBoxMesh? {
if (entity.isInvisible && !Minosoft.config.config.game.entities.hitBox.renderInvisibleEntities) {
return null
}
val mesh = EntityHitBoxMesh(entity)
mesh.load()
this.meshes[entity] = mesh
return mesh
}
override fun init() {
connection.registerEvent(CallbackEventInvoker.of<EntitySpawnEvent> {
renderWindow.queue += {
createMesh(it.entity)
}
})
connection.registerEvent(CallbackEventInvoker.of<EntityDestroyEvent> {
val meshes: MutableSet<EntityHitBoxMesh> = mutableSetOf()
for (entity in it.entities) {
val mesh = this.meshes.getAndRemove(entity) ?: continue
meshes += mesh
}
renderWindow.queue += {
for (mesh in meshes) {
mesh.unload(false)
}
}
})
}
override fun draw() {
glDisable(GL_CULL_FACE)
if (Minosoft.config.config.game.entities.hitBox.disableZBuffer) {
glDepthFunc(GL_ALWAYS)
}
renderWindow.shaderManager.genericColorShader.use()
for ((entity, mesh) in meshes.toSynchronizedMap()) {
val aabb = entity.aabb
if (aabb != mesh.aabb) {
this.meshes.remove(entity)
mesh.unload()
createMesh(entity)?.draw()
continue
}
mesh.draw()
}
glEnable(GL_CULL_FACE)
if (Minosoft.config.config.game.entities.hitBox.disableZBuffer) {
glDepthFunc(GL_LESS)
}
}
companion object : RendererBuilder<EntityHitBoxRenderer> {
override val RESOURCE_LOCATION = ResourceLocation("minosoft:entity_hitbox")
override fun build(connection: PlayConnection, renderWindow: RenderWindow): EntityHitBoxRenderer {
return EntityHitBoxRenderer(connection, renderWindow)
}
}
}

View File

@ -15,6 +15,7 @@ package de.bixilon.minosoft.gui.rendering.util.mesh
import de.bixilon.minosoft.data.text.RGBColor
import de.bixilon.minosoft.gui.rendering.chunk.VoxelShape
import de.bixilon.minosoft.gui.rendering.chunk.models.AABB
import de.bixilon.minosoft.gui.rendering.chunk.models.renderable.ElementRenderer
import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY
import de.bixilon.minosoft.util.BitByte.isBit
@ -22,14 +23,9 @@ import de.bixilon.minosoft.util.MMath.positiveNegative
import glm_.vec3.Vec3
import glm_.vec3.Vec3d
class LineMesh(
var color: RGBColor,
lineWidth: Float = 0.1f,
) : GenericColorMesh() {
private var halfLineWidth = lineWidth / 2.0f
open class LineMesh : GenericColorMesh() {
fun drawLine(start: Vec3, end: Vec3, mesh: GenericColorMesh) {
fun drawLine(start: Vec3, end: Vec3, lineWidth: Float, color: RGBColor) {
val direction = (end - start).normalize()
val normal1 = Vec3(direction.z, direction.z, direction.x - direction.y)
if (normal1 == Vec3.EMPTY) {
@ -39,11 +35,12 @@ class LineMesh(
normal1.normalizeAssign()
val normal2 = (direction cross normal1).normalize()
for (i in 0..4) {
drawLineQuad(mesh, start, end, direction, normal1, normal2, i.isBit(0), i.isBit(1))
drawLineQuad(start, end, direction, normal1, normal2, i.isBit(0), i.isBit(1), lineWidth, color)
}
}
fun drawLineQuad(mesh: GenericColorMesh, start: Vec3, end: Vec3, direction: Vec3, normal1: Vec3, normal2: Vec3, invertNormal1: Boolean, invertNormal2: Boolean) {
private fun drawLineQuad(start: Vec3, end: Vec3, direction: Vec3, normal1: Vec3, normal2: Vec3, invertNormal1: Boolean, invertNormal2: Boolean, lineWidth: Float, color: RGBColor) {
val halfLineWidth = lineWidth / 2
val normal1Multiplier = invertNormal1.positiveNegative
val normal2Multiplier = invertNormal2.positiveNegative
val positions = listOf(
@ -53,33 +50,37 @@ class LineMesh(
end + normal2 * normal2Multiplier * halfLineWidth + direction * halfLineWidth,
)
for ((_, positionIndex) in ElementRenderer.DRAW_ODER) {
mesh.addVertex(positions[positionIndex], color)
addVertex(positions[positionIndex], color)
}
}
fun drawVoxelShape(shape: VoxelShape, blockPosition: Vec3d, mesh: GenericColorMesh, margin: Float = 0.0f) {
fun drawAABB(aabb: AABB, position: Vec3d, lineWidth: Float, color: RGBColor, margin: Float = 0.0f) {
val min = position + aabb.min - margin
val max = position + aabb.max + margin
fun drawSideQuad(x: Double) {
drawLine(Vec3(x, min.y, min.z), Vec3(x, max.y, min.z), lineWidth, color)
drawLine(Vec3(x, min.y, min.z), Vec3(x, min.y, max.z), lineWidth, color)
drawLine(Vec3(x, max.y, min.z), Vec3(x, max.y, max.z), lineWidth, color)
drawLine(Vec3(x, min.y, max.z), Vec3(x, max.y, max.z), lineWidth, color)
}
// left quad
drawSideQuad(min.x)
// right quad
drawSideQuad(max.x)
// connections between 2 quads
drawLine(Vec3(min.x, min.y, min.z), Vec3(max.x, min.y, min.z), lineWidth, color)
drawLine(Vec3(min.x, max.y, min.z), Vec3(max.x, max.y, min.z), lineWidth, color)
drawLine(Vec3(min.x, max.y, max.z), Vec3(max.x, max.y, max.z), lineWidth, color)
drawLine(Vec3(min.x, min.y, max.z), Vec3(max.x, min.y, max.z), lineWidth, color)
}
fun drawVoxelShape(shape: VoxelShape, position: Vec3d, lineWidth: Float, color: RGBColor, margin: Float = 0.0f) {
for (aabb in shape) {
val min = blockPosition + aabb.min - margin
val max = blockPosition + aabb.max + margin
fun drawSideQuad(x: Double) {
drawLine(Vec3(x, min.y, min.z), Vec3(x, max.y, min.z), mesh)
drawLine(Vec3(x, min.y, min.z), Vec3(x, min.y, max.z), mesh)
drawLine(Vec3(x, max.y, min.z), Vec3(x, max.y, max.z), mesh)
drawLine(Vec3(x, min.y, max.z), Vec3(x, max.y, max.z), mesh)
}
// left quad
drawSideQuad(min.x)
// right quad
drawSideQuad(max.x)
// connections between 2 quads
drawLine(Vec3(min.x, min.y, min.z), Vec3(max.x, min.y, min.z), mesh)
drawLine(Vec3(min.x, max.y, min.z), Vec3(max.x, max.y, min.z), mesh)
drawLine(Vec3(min.x, max.y, max.z), Vec3(max.x, max.y, max.z), mesh)
drawLine(Vec3(min.x, min.y, max.z), Vec3(max.x, min.y, max.z), mesh)
drawAABB(aabb, position, lineWidth, color, margin)
}
}
}

View File

@ -189,4 +189,12 @@ class SynchronizedMap<K, V>(
return original.replace(key, oldValue, newValue)
}
}
fun getAndRemove(key: K): V? {
synchronized(lock) {
val value = this[key] ?: return null
this.remove(key)
return value
}
}
}