diff --git a/doc/physics/Particles.md b/doc/physics/Particles.md new file mode 100644 index 000000000..94558092e --- /dev/null +++ b/doc/physics/Particles.md @@ -0,0 +1,3 @@ +# Particle physics + +The velocity is in blocks/tick. The velocity gets recalculated every real tick (50ms). They still move if (`movement` is `true`) every tick call. But only the right velocity (according to the delta time) gets applied. diff --git a/src/main/java/de/bixilon/minosoft/data/entities/entities/Entity.kt b/src/main/java/de/bixilon/minosoft/data/entities/entities/Entity.kt index 2d771a66e..2fc26d05f 100644 --- a/src/main/java/de/bixilon/minosoft/data/entities/entities/Entity.kt +++ b/src/main/java/de/bixilon/minosoft/data/entities/entities/Entity.kt @@ -12,8 +12,6 @@ */ package de.bixilon.minosoft.data.entities.entities -import de.bixilon.minosoft.data.Axes -import de.bixilon.minosoft.data.Directions import de.bixilon.minosoft.data.entities.EntityMetaDataFields import de.bixilon.minosoft.data.entities.EntityRotation import de.bixilon.minosoft.data.entities.Poses @@ -24,11 +22,9 @@ import de.bixilon.minosoft.data.inventory.ItemStack import de.bixilon.minosoft.data.mappings.effects.StatusEffect import de.bixilon.minosoft.data.mappings.entities.EntityType import de.bixilon.minosoft.data.text.ChatComponent -import de.bixilon.minosoft.gui.rendering.chunk.VoxelShape import de.bixilon.minosoft.gui.rendering.chunk.models.AABB import de.bixilon.minosoft.gui.rendering.util.VecUtil.blockPosition import de.bixilon.minosoft.gui.rendering.util.VecUtil.chunkPosition -import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkPosition import de.bixilon.minosoft.protocol.network.connection.PlayConnection import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.KUtil.synchronizedMapOf @@ -202,82 +198,11 @@ abstract class Entity( forceMove(deltaPosition) return } - val currentAABB = aabb - val collisionsToCheck = getCollisionsToCheck(deltaPosition, currentAABB, ignoreUnloadedChunks) - val realMovement = collide(deltaPosition, collisionsToCheck, currentAABB) + val collisionsToCheck = connection.collisionDetector.getCollisionsToCheck(deltaPosition, aabb, ignoreUnloadedChunks) + val realMovement = connection.collisionDetector.collide(this, deltaPosition, collisionsToCheck, aabb) forceMove(realMovement) } - private fun getCollisionsToCheck(deltaPosition: Vec3, originalAABB: AABB, ignoreUnloadedChunks: Boolean = true): VoxelShape { - // also look at blocks further down to also cover blocks with a higher than normal hitbox (for example fences) - val blockPositions = (originalAABB extend deltaPosition extend Directions.DOWN.directionVector).getBlockPositions() - val result = VoxelShape() - for (blockPosition in blockPositions) { - val chunk = connection.world[blockPosition.chunkPosition] - if ((chunk == null || !chunk.isFullyLoaded) && !ignoreUnloadedChunks) { - // chunk is not loaded - result.add(VoxelShape.FULL + blockPosition) - continue - } - val blockState = chunk?.get(blockPosition.inChunkPosition) ?: continue - result.add(blockState.collisionShape + blockPosition) - } - return result - } - - private fun collide(deltaPosition: Vec3, collisionsToCheck: VoxelShape, aabb: AABB): Vec3 { - val delta = Vec3(deltaPosition) - if (delta.y != 0.0f) { - delta.y = collisionsToCheck.computeOffset(aabb, deltaPosition.y, Axes.Y) - if (delta.y != deltaPosition.y) { - onGround = false - velocity.y = 0.0f - if (deltaPosition.y < 0) { - onGround = true - } - aabb += Vec3(0f, delta.y, 0f) - } else if (delta.y < 0) { - onGround = false - } - } - if ((deltaPosition.x != 0f || deltaPosition.z != 0f)) { - val testDelta = Vec3(delta) - testDelta.y = STEP_HEIGHT - val stepMovementY = collisionsToCheck.computeOffset(aabb + testDelta, -STEP_HEIGHT, Axes.Y) - if (stepMovementY < 0 && stepMovementY >= -STEP_HEIGHT) { - testDelta.y = STEP_HEIGHT + stepMovementY - aabb += Vec3(0f, testDelta.y, 0f) - delta.y += testDelta.y - } - } - val xPriority = delta.x > delta.z - if (delta.x != 0.0f && xPriority) { - delta.x = collisionsToCheck.computeOffset(aabb, deltaPosition.x, Axes.X) - aabb += Vec3(delta.x, 0f, 0f) - if (delta.x != deltaPosition.x) { - velocity.x = 0.0f - } - } - if (delta.z != 0.0f) { - delta.z = collisionsToCheck.computeOffset(aabb, deltaPosition.z, Axes.Z) - aabb += Vec3(0f, 0f, delta.z) - if (delta.z != deltaPosition.z) { - velocity.z = 0.0f - } - } - if (delta.x != 0.0f && !xPriority) { - delta.x = collisionsToCheck.computeOffset(aabb, deltaPosition.x, Axes.X) - // no need to offset the aabb any more, as it won't be used any more - if (delta.x != deltaPosition.x) { - velocity.x = 0.0f - } - } - if (delta.length() > deltaPosition.length() + STEP_HEIGHT) { - return Vec3() // abort all movement if the collision system would move the entity further than wanted - } - return delta - } - private fun tickMovement(deltaMillis: Long) { if (connection.world[position.blockPosition.chunkPosition]?.isFullyLoaded != true) { return // ignore update if chunk is not loaded yet @@ -312,6 +237,6 @@ abstract class Entity( companion object { private const val HITBOX_MARGIN = 1e-5f - private const val STEP_HEIGHT = 0.6f + const val STEP_HEIGHT = 0.6f } } diff --git a/src/main/java/de/bixilon/minosoft/data/physics/CollisionDetector.kt b/src/main/java/de/bixilon/minosoft/data/physics/CollisionDetector.kt new file mode 100644 index 000000000..99c4f0dec --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/physics/CollisionDetector.kt @@ -0,0 +1,99 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.physics + +import de.bixilon.minosoft.data.Axes +import de.bixilon.minosoft.data.Directions +import de.bixilon.minosoft.data.entities.entities.Entity +import de.bixilon.minosoft.gui.rendering.chunk.VoxelShape +import de.bixilon.minosoft.gui.rendering.chunk.models.AABB +import de.bixilon.minosoft.gui.rendering.util.VecUtil.chunkPosition +import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkPosition +import de.bixilon.minosoft.protocol.network.connection.PlayConnection +import glm_.vec3.Vec3 + +class CollisionDetector(val connection: PlayConnection) { + + fun getCollisionsToCheck(deltaPosition: Vec3, aabb: AABB, ignoreUnloadedChunks: Boolean = true): VoxelShape { + // also look at blocks further down to also cover blocks with a higher than normal hitbox (for example fences) + val blockPositions = (aabb extend deltaPosition extend Directions.DOWN.directionVector).getBlockPositions() + val result = VoxelShape() + for (blockPosition in blockPositions) { + val chunk = connection.world[blockPosition.chunkPosition] + if ((chunk == null || !chunk.isFullyLoaded) && !ignoreUnloadedChunks) { + // chunk is not loaded + result.add(VoxelShape.FULL + blockPosition) + continue + } + val blockState = chunk?.get(blockPosition.inChunkPosition) ?: continue + result.add(blockState.collisionShape + blockPosition) + } + return result + } + + fun collide(entity: Entity?, deltaPosition: Vec3, collisionsToCheck: VoxelShape, aabb: AABB): Vec3 { + val delta = Vec3(deltaPosition) + if (delta.y != 0.0f) { + delta.y = collisionsToCheck.computeOffset(aabb, deltaPosition.y, Axes.Y) + if (delta.y != deltaPosition.y) { + entity?.let { + it.onGround = false + it.velocity.y = 0.0f + if (deltaPosition.y < 0) { + it.onGround = true + } + } + aabb += Vec3(0f, delta.y, 0f) + } else if (delta.y < 0) { + entity?.let { it.onGround = false } + } + } + if ((deltaPosition.x != 0f || deltaPosition.z != 0f)) { + val testDelta = Vec3(delta) + testDelta.y = Entity.STEP_HEIGHT + val stepMovementY = collisionsToCheck.computeOffset(aabb + testDelta, -Entity.STEP_HEIGHT, Axes.Y) + if (stepMovementY < 0 && stepMovementY >= -Entity.STEP_HEIGHT) { + testDelta.y = Entity.STEP_HEIGHT + stepMovementY + aabb += Vec3(0f, testDelta.y, 0f) + delta.y += testDelta.y + } + } + val xPriority = delta.x > delta.z + if (delta.x != 0.0f && xPriority) { + delta.x = collisionsToCheck.computeOffset(aabb, deltaPosition.x, Axes.X) + aabb += Vec3(delta.x, 0f, 0f) + if (delta.x != deltaPosition.x) { + entity?.let { it.velocity.x = 0.0f } + } + } + if (delta.z != 0.0f) { + delta.z = collisionsToCheck.computeOffset(aabb, deltaPosition.z, Axes.Z) + aabb += Vec3(0f, 0f, delta.z) + if (delta.z != deltaPosition.z) { + entity?.let { it.velocity.z = 0.0f } + } + } + if (delta.x != 0.0f && !xPriority) { + delta.x = collisionsToCheck.computeOffset(aabb, deltaPosition.x, Axes.X) + // no need to offset the aabb any more, as it won't be used any more + if (delta.x != deltaPosition.x) { + entity?.let { it.velocity.x = 0.0f } + } + } + if (delta.length() > deltaPosition.length() + Entity.STEP_HEIGHT) { + return Vec3() // abort all movement if the collision system would move the entity further than wanted + } + return delta + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/text/RGBColor.kt b/src/main/java/de/bixilon/minosoft/data/text/RGBColor.kt index dc48f9841..efea7b520 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/RGBColor.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/RGBColor.kt @@ -12,6 +12,7 @@ */ package de.bixilon.minosoft.data.text +import de.bixilon.minosoft.util.MMath import org.checkerframework.common.value.qual.IntRange class RGBColor(val rgba: Int) : ChatCode { @@ -21,7 +22,7 @@ class RGBColor(val rgba: Int) : ChatCode { constructor(red: Byte, green: Byte, blue: Byte, alpha: Byte = 0xFF.toByte()) : this(red.toInt() and 0xFF, green.toInt() and 0xFF, blue.toInt() and 0xFF, alpha.toInt() and 0xFF) - constructor(red: Float, green: Float, blue: Float, alpha: Float = 1.0f) : this((red * 255.0f).toInt(), (green * 255.0f).toInt(), (blue * 255.0f).toInt(), (alpha * 255.0f).toInt()) + constructor(red: Float, green: Float, blue: Float, alpha: Float = 1.0f) : this((red * 255.0f).toInt(), (green * COLOR_FLOAT_DIVIDER).toInt(), (blue * COLOR_FLOAT_DIVIDER).toInt(), (alpha * COLOR_FLOAT_DIVIDER).toInt()) val alpha: @IntRange(from = 0L, to = 255L) Int get() = rgba and 0xFF @@ -44,6 +45,9 @@ class RGBColor(val rgba: Int) : ChatCode { val floatBlue: @IntRange(from = 0L, to = 1L) Float get() = blue / COLOR_FLOAT_DIVIDER + val floatAlpha: @IntRange(from = 0L, to = 1L) Float + get() = alpha / COLOR_FLOAT_DIVIDER + val rgb: Int get() = rgba ushr 8 @@ -67,6 +71,14 @@ class RGBColor(val rgba: Int) : ChatCode { } } + fun with(red: Int = this.red, green: Int = this.green, blue: Int = this.blue, alpha: Int = this.alpha): RGBColor { + return RGBColor(red, green, blue, alpha) + } + + fun with(red: Float = this.floatRed, green: Float = this.floatGreen, blue: Float = this.floatBlue, alpha: Float = this.floatAlpha): RGBColor { + return RGBColor(MMath.clamp(red, 0.0f, 1.0f), MMath.clamp(green, 0.0f, 1.0f), MMath.clamp(blue, 0.0f, 1.0f), MMath.clamp(alpha, 0.0f, 1.0f)) + } + companion object { private const val COLOR_FLOAT_DIVIDER = 255.0f diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/AABB.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/AABB.kt index 59a251eeb..29435664a 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/AABB.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/AABB.kt @@ -166,7 +166,13 @@ class AABB { return (position.x in min.x..max.x && position.y in min.y..max.y && position.z in min.z..max.z) } + val center: Vec3 + get() = Vec3((min.x + max.x) / 2.0f, (min.y + max.y) / 2.0f, (min.z + max.z) / 2.0f) + companion object { + val EMPTY: AABB + get() = AABB(Vec3.EMPTY, Vec3.EMPTY) + private fun getRange(min: Float, max: Float): IntRange { return IntRange(glm.floor(min).toInt(), glm.ceil(max).toInt()) } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/DefaultParticleBehavior.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/DefaultParticleBehavior.kt index 41e03008b..7f5e80f41 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/DefaultParticleBehavior.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/DefaultParticleBehavior.kt @@ -14,7 +14,7 @@ package de.bixilon.minosoft.gui.rendering.particle import de.bixilon.minosoft.gui.rendering.particle.types.norender.ExplosionEmitterParticle -import de.bixilon.minosoft.gui.rendering.particle.types.render.texture.simple.ExplosionParticle +import de.bixilon.minosoft.gui.rendering.particle.types.render.texture.simple.ExplosionLargeParticle import de.bixilon.minosoft.gui.rendering.util.VecUtil.times import de.bixilon.minosoft.modding.event.CallbackEventInvoker import de.bixilon.minosoft.modding.event.events.ExplosionEvent @@ -29,14 +29,14 @@ object DefaultParticleBehavior { fun register(connection: PlayConnection, particleRenderer: ParticleRenderer) { val random = java.util.Random() - val explosionParticleType = connection.registries.particleTypeRegistry[ExplosionParticle]!! + val explosionParticleType = connection.registries.particleTypeRegistry[ExplosionLargeParticle]!! val explosionEmitterParticleType = connection.registries.particleTypeRegistry[ExplosionEmitterParticle]!! val invokers = listOf( CallbackEventInvoker.of { if (it.power >= 2.0f) { particleRenderer.add(ExplosionEmitterParticle(connection, particleRenderer, it.position, explosionEmitterParticleType.simple())) } else { - particleRenderer.add(ExplosionParticle(connection, particleRenderer, it.position, explosionParticleType.simple())) + particleRenderer.add(ExplosionLargeParticle(connection, particleRenderer, it.position, explosionParticleType.simple())) } }, CallbackEventInvoker.of { @@ -54,8 +54,8 @@ object DefaultParticleBehavior { spawn(it.position, velocity) } else { for (i in 0 until it.count) { - val offset = it.offset * { random.nextGaussian() } - val velocity = Vec3(it.speed) * { random.nextGaussian() } + val offset = it.offset * { random.nextGaussian().toFloat() } + val velocity = Vec3(it.speed) * { random.nextGaussian().toFloat() } spawn(it.position + offset, velocity) } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/DefaultParticleFactory.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/DefaultParticleFactory.kt index 2e5ced862..05a0a4b9d 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/DefaultParticleFactory.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/DefaultParticleFactory.kt @@ -16,9 +16,12 @@ package de.bixilon.minosoft.gui.rendering.particle import de.bixilon.minosoft.data.mappings.DefaultFactory import de.bixilon.minosoft.gui.rendering.particle.types.Particle import de.bixilon.minosoft.gui.rendering.particle.types.norender.ExplosionEmitterParticle -import de.bixilon.minosoft.gui.rendering.particle.types.render.texture.simple.ExplosionParticle +import de.bixilon.minosoft.gui.rendering.particle.types.render.texture.simple.CampfireSmokeParticle +import de.bixilon.minosoft.gui.rendering.particle.types.render.texture.simple.ExplosionLargeParticle object DefaultParticleFactory : DefaultFactory>( ExplosionEmitterParticle, - ExplosionParticle, + ExplosionLargeParticle, + CampfireSmokeParticle.CosySmokeParticleFactory, + CampfireSmokeParticle.SignalSmokeParticleFactory, ) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/ParticleRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/ParticleRenderer.kt index 18eaa4301..088e3e227 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/ParticleRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/ParticleRenderer.kt @@ -20,6 +20,7 @@ import de.bixilon.minosoft.gui.rendering.Renderer import de.bixilon.minosoft.gui.rendering.RendererBuilder import de.bixilon.minosoft.gui.rendering.modding.events.CameraMatrixChangeEvent import de.bixilon.minosoft.gui.rendering.particle.types.Particle +import de.bixilon.minosoft.gui.rendering.particle.types.render.texture.simple.CampfireSmokeParticle import de.bixilon.minosoft.gui.rendering.shader.Shader import de.bixilon.minosoft.gui.rendering.textures.Texture import de.bixilon.minosoft.gui.rendering.textures.TextureArray @@ -30,6 +31,8 @@ import de.bixilon.minosoft.util.KUtil.synchronizedSetOf import de.bixilon.minosoft.util.KUtil.toSynchronizedSet import de.bixilon.minosoft.util.MMath import glm_.vec3.Vec3 +import org.lwjgl.opengl.GL11.glDepthMask +import kotlin.random.Random class ParticleRenderer( @@ -75,6 +78,7 @@ class ParticleRenderer( particles += particle } + var lastTickTime = System.currentTimeMillis() override fun draw() { particleShader.use() @@ -82,6 +86,26 @@ class ParticleRenderer( particleMesh.unload() particleMesh = ParticleMesh() + // ToDo: Remove, this ist just for testing purposes + if (System.currentTimeMillis() - lastTickTime > ProtocolDefinition.TICK_TIME) { + val blockPosition = Vec3(0, 5, 0) + if (Random.nextFloat() < 0.11f) { + for (i in 0 until Random.nextInt(2) + 2) { + val horizontal = { 0.5f + Random.nextFloat() / 3.0f * if (Random.nextBoolean()) 1.0f else -1.0f } + val position = Vec3( + blockPosition.x + horizontal(), + blockPosition.y + Random.nextFloat() + Random.nextFloat(), + blockPosition.z + horizontal() + ) + + val data = connection.registries.particleTypeRegistry[CampfireSmokeParticle.CosySmokeParticleFactory]!! + val particle = CampfireSmokeParticle(connection, this, position, Vec3(0.0f, 0.07f, 0.0f), data.simple(), true) + add(particle) + } + } + lastTickTime = System.currentTimeMillis() + } + for (particle in particles.toSynchronizedSet()) { particle.tick() if (particle.dead) { @@ -93,7 +117,9 @@ class ParticleRenderer( particleMesh.load() + glDepthMask(false) particleMesh.draw() + glDepthMask(true) } companion object : RendererBuilder { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/Particle.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/Particle.kt index 1b36ba35e..5b8091aad 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/Particle.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/Particle.kt @@ -14,26 +14,30 @@ package de.bixilon.minosoft.gui.rendering.particle.types import de.bixilon.minosoft.data.mappings.particle.data.ParticleData +import de.bixilon.minosoft.gui.rendering.chunk.models.AABB import de.bixilon.minosoft.gui.rendering.particle.ParticleMesh import de.bixilon.minosoft.gui.rendering.particle.ParticleRenderer import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY -import de.bixilon.minosoft.gui.rendering.util.VecUtil.ONE -import de.bixilon.minosoft.gui.rendering.util.VecUtil.clear +import de.bixilon.minosoft.gui.rendering.util.VecUtil.assign +import de.bixilon.minosoft.gui.rendering.util.VecUtil.millis +import de.bixilon.minosoft.gui.rendering.util.VecUtil.plusAssign +import de.bixilon.minosoft.gui.rendering.util.VecUtil.sqr import de.bixilon.minosoft.protocol.network.connection.PlayConnection import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.KUtil.millis import glm_.vec3.Vec3 +import kotlin.math.sqrt import kotlin.random.Random abstract class Particle( protected val connection: PlayConnection, protected val particleRenderer: ParticleRenderer, protected val position: Vec3, + protected val velocity: Vec3 = Vec3.EMPTY, protected val data: ParticleData, ) { protected val random = Random - private var lastTickTime = -1L - + var lastTickTime = -1L // ageing var dead = false @@ -42,24 +46,77 @@ abstract class Particle( var maxAge: Int = (4.0f / (random.nextFloat() * 0.9f + 0.1f)).millis // moving - val friction = Vec3.EMPTY - val velocity = Vec3.EMPTY + var previousPosition = position + var movement: Boolean = true + var physics: Boolean = true + var friction = 0.98f + var gravityStrength = 0.0f + + // collisions + var onGround: Boolean = true + var accelerateIfYBlocked = false + var aabb: AABB = AABB.EMPTY + var spacing: Vec3 = Vec3.EMPTY + set(value) { + if (field == value) { + return + } + field = value - protected var lastRealTickTime = 0L + val x = ((aabb.min.x + aabb.max.x) - spacing.x) / 2.0f + val z = ((aabb.min.z + aabb.max.z) - spacing.z) / 2.0f + + aabb = AABB(Vec3(x, aabb.min.y, z), Vec3(x + spacing.x, aabb.min.y + spacing.y, z + spacing.z)) + } - private fun move(deltaTime: Int) { - val perSecond = deltaTime / 1000.0f - position += velocity * perSecond - velocity *= Vec3.ONE - friction * perSecond + private var lastRealTickTime = -1L - if (velocity.length() < MINIMUM_VELOCITY) { - velocity.clear() + + init { + velocity += { (random.nextFloat() * 2.0f - 1.0f) * MAGIC_VELOCITY_CONSTANTf } + val modifier = (random.nextFloat() + random.nextFloat() + 1.0f) * 0.15000000596046448f + val divider = sqrt((velocity.x.sqr + velocity.y.sqr + velocity.z.sqr).toDouble()).toFloat() + + velocity assign velocity / divider * modifier * MAGIC_VELOCITY_CONSTANTf + velocity.y += 0.10000000149011612f + + spacing = Vec3(0.2f) + } + + fun move(velocity: Vec3) { + var newVelocity = Vec3(velocity) + if (this.physics && newVelocity != Vec3.EMPTY) { + val aabb = aabb + position + val collisions = connection.collisionDetector.getCollisionsToCheck(newVelocity, aabb) + newVelocity = connection.collisionDetector.collide(null, newVelocity, collisions, aabb) + } + + if (newVelocity != Vec3.EMPTY) { + position += newVelocity + } + + onGround = (newVelocity.y != velocity.y) && velocity.y < 0.0f + + if (newVelocity.x != velocity.x) { + this.velocity.x = 0.0f + } + if (newVelocity.z != velocity.z) { + this.velocity.z = 0.0f } } + private fun move(deltaTime: Int) { + if (!movement) { + return + } + previousPosition = Vec3(position) + velocity.y -= (0.04f * gravityStrength).millis + + move(velocity.millis * (deltaTime / 1000.0f)) + } fun tick() { val currentTime = System.currentTimeMillis() @@ -72,6 +129,10 @@ abstract class Particle( tick(deltaTime) + if (dead) { + return + } + if (lastRealTickTime == -1L) { lastRealTickTime = System.currentTimeMillis() } else if (currentTime - lastRealTickTime >= ProtocolDefinition.TICK_TIME) { @@ -79,27 +140,55 @@ abstract class Particle( lastRealTickTime = currentTime } + postTick(deltaTime) lastTickTime = currentTime } + protected fun age(deltaTime: Int) { + age += deltaTime + + if (age >= maxAge) { + dead = true + } + } + open fun tick(deltaTime: Int) { check(!dead) { "Cannot tick dead particle!" } check(deltaTime >= 0) - age += deltaTime - if (age >= maxAge) { - dead = true + age(deltaTime) + if (dead) { return } + } + open fun postTick(deltaTime: Int) { move(deltaTime) } - open fun realTick() {} + open fun realTick() { + if (!movement) { + return + } + + if (accelerateIfYBlocked && position.y == previousPosition.y) { + velocity.x *= 1.1f + velocity.z *= 1.1f + } + + velocity *= friction + + if (onGround) { + velocity.x *= 0.699999988079071f + velocity.y *= 0.699999988079071f + } + } abstract fun addVertex(particleMesh: ParticleMesh) companion object { - const val MINIMUM_VELOCITY = 0.01f + private const val MINIMUM_VELOCITY = 0.01f + private const val MAGIC_VELOCITY_CONSTANT = 0.4000000059604645 + private const val MAGIC_VELOCITY_CONSTANTf = MAGIC_VELOCITY_CONSTANT.toFloat() } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/norender/ExplosionEmitterParticle.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/norender/ExplosionEmitterParticle.kt index 9e0f4e4ca..98fe31532 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/norender/ExplosionEmitterParticle.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/norender/ExplosionEmitterParticle.kt @@ -17,33 +17,38 @@ import de.bixilon.minosoft.data.mappings.ResourceLocation import de.bixilon.minosoft.data.mappings.particle.data.ParticleData import de.bixilon.minosoft.gui.rendering.particle.ParticleFactory import de.bixilon.minosoft.gui.rendering.particle.ParticleRenderer -import de.bixilon.minosoft.gui.rendering.particle.types.render.texture.simple.ExplosionParticle +import de.bixilon.minosoft.gui.rendering.particle.types.render.texture.simple.ExplosionLargeParticle +import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY import de.bixilon.minosoft.gui.rendering.util.VecUtil.plus import de.bixilon.minosoft.protocol.network.connection.PlayConnection import de.bixilon.minosoft.util.KUtil.asResourceLocation -import de.bixilon.minosoft.util.KUtil.millis -import de.bixilon.minosoft.util.KUtil.ticks import glm_.vec3.Vec3 -class ExplosionEmitterParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, data: ParticleData) : NoRenderParticle(connection, particleRenderer, position, data) { - private val explosionParticleType = connection.registries.particleTypeRegistry[ExplosionParticle]!! +class ExplosionEmitterParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, data: ParticleData) : NoRenderParticle(connection, particleRenderer, position, Vec3.EMPTY, data) { + private val explosionParticleType = connection.registries.particleTypeRegistry[ExplosionLargeParticle]!! init { - maxAge = 8.millis + maxAge = Int.MAX_VALUE + movement = false } - + private var ticks = 0 override fun realTick() { super.realTick() for (i in 0 until 6) { val position = position + { (random.nextFloat() - random.nextFloat()) * 4.0f } - particleRenderer.add(ExplosionParticle(connection, particleRenderer, position, explosionParticleType.simple(), (age.ticks.toFloat() / maxAge.ticks))) + particleRenderer.add(ExplosionLargeParticle(connection, particleRenderer, position, explosionParticleType.simple(), (ticks.toFloat() / MAX_AGE))) + } + + if (ticks == MAX_AGE) { + dead = true } } companion object : ParticleFactory { override val RESOURCE_LOCATION: ResourceLocation = "minecraft:explosion_emitter".asResourceLocation() + private const val MAX_AGE = 8 override fun build(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, velocity: Vec3, data: ParticleData): ExplosionEmitterParticle { return ExplosionEmitterParticle(connection, particleRenderer, position, data) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/norender/NoRenderParticle.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/norender/NoRenderParticle.kt index 9c9d02c79..a32715528 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/norender/NoRenderParticle.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/norender/NoRenderParticle.kt @@ -20,7 +20,7 @@ import de.bixilon.minosoft.gui.rendering.particle.types.Particle import de.bixilon.minosoft.protocol.network.connection.PlayConnection import glm_.vec3.Vec3 -abstract class NoRenderParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, data: ParticleData) : Particle(connection, particleRenderer, position, data) { +abstract class NoRenderParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, velocity: Vec3, data: ParticleData) : Particle(connection, particleRenderer, position, velocity, data) { override fun addVertex(particleMesh: ParticleMesh) {} } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/RenderParticle.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/RenderParticle.kt index b14ec2b64..28516dda8 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/RenderParticle.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/RenderParticle.kt @@ -22,7 +22,7 @@ import de.bixilon.minosoft.protocol.network.connection.PlayConnection import glm_.vec3.Vec3 import kotlin.math.abs -abstract class RenderParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, data: ParticleData) : Particle(connection, particleRenderer, position, data) { +abstract class RenderParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, velocity: Vec3, data: ParticleData) : Particle(connection, particleRenderer, position, velocity, data) { protected var scale: Float = 0.1f * (random.nextFloat() * 0.5f + 0.5f) * 2.0f protected var color: RGBColor = ChatColors.WHITE diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/TextureParticle.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/TextureParticle.kt index 5b4b49f19..efdf0460c 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/TextureParticle.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/TextureParticle.kt @@ -21,7 +21,7 @@ import de.bixilon.minosoft.gui.rendering.textures.Texture import de.bixilon.minosoft.protocol.network.connection.PlayConnection import glm_.vec3.Vec3 -abstract class TextureParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, data: ParticleData) : RenderParticle(connection, particleRenderer, position, data) { +abstract class TextureParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, velocity: Vec3, position: Vec3, data: ParticleData) : RenderParticle(connection, particleRenderer, velocity, position, data) { abstract val texture: Texture diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/CampfireSmokeParticle.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/CampfireSmokeParticle.kt new file mode 100644 index 000000000..f7930f57b --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/CampfireSmokeParticle.kt @@ -0,0 +1,93 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.particle.types.render.texture.simple + +import de.bixilon.minosoft.data.mappings.ResourceLocation +import de.bixilon.minosoft.data.mappings.particle.data.ParticleData +import de.bixilon.minosoft.gui.rendering.particle.ParticleFactory +import de.bixilon.minosoft.gui.rendering.particle.ParticleRenderer +import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY +import de.bixilon.minosoft.gui.rendering.util.VecUtil.assign +import de.bixilon.minosoft.gui.rendering.util.VecUtil.millis +import de.bixilon.minosoft.protocol.network.connection.PlayConnection +import de.bixilon.minosoft.util.KUtil.asResourceLocation +import de.bixilon.minosoft.util.KUtil.millis +import glm_.vec3.Vec3 + +class CampfireSmokeParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, velocity: Vec3, data: ParticleData, signal: Boolean) : SimpleTextureParticle(connection, particleRenderer, position, Vec3.EMPTY, data) { + + init { + scale *= 3.0f + spacing = Vec3(0.25f) + maxAge = random.nextInt(50).millis + if (signal) { + maxAge += 280.millis + color = color.with(alpha = 0.95f) + } else { + maxAge += 80.millis + color = color.with(alpha = 0.90f) + } + + gravityStrength = 3.0E-6f + + this.velocity assign Vec3(velocity.x, velocity.y + (random.nextFloat() / 500.0f), velocity.z) + movement = false + } + + override fun tick(deltaTime: Int) { + super.tick(deltaTime) + if (dead) { + return + } + } + + + override fun realTick() { + super.realTick() + val horizontal = { (random.nextFloat() / 5000.0f * (if (random.nextBoolean()) 1.0f else -1.0f)) } + velocity.x += horizontal() + velocity.y -= gravityStrength + velocity.z += horizontal() + + if (age >= maxAge - 60.millis) { + color = color.with(alpha = color.floatAlpha - 0.015f) + } + if (color.alpha == 0) { + dead = true + } + } + + override fun postTick(deltaTime: Int) { + super.postTick(deltaTime) + move(velocity.millis * (deltaTime / 1000.0f)) + } + + + object CosySmokeParticleFactory : ParticleFactory { + override val RESOURCE_LOCATION: ResourceLocation = "minecraft:campfire_cosy_smoke".asResourceLocation() + + override fun build(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, velocity: Vec3, data: ParticleData): CampfireSmokeParticle { + return CampfireSmokeParticle(connection, particleRenderer, position, velocity, data, false) + } + } + + + object SignalSmokeParticleFactory : ParticleFactory { + override val RESOURCE_LOCATION: ResourceLocation = "minecraft:campfire_signal_smoke".asResourceLocation() + + override fun build(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, velocity: Vec3, data: ParticleData): CampfireSmokeParticle { + return CampfireSmokeParticle(connection, particleRenderer, position, velocity, data, true) + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/ExplosionParticle.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/ExplosionLargeParticle.kt similarity index 73% rename from src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/ExplosionParticle.kt rename to src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/ExplosionLargeParticle.kt index b0bae4cc5..8276c394c 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/ExplosionParticle.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/ExplosionLargeParticle.kt @@ -18,25 +18,27 @@ import de.bixilon.minosoft.data.mappings.particle.data.ParticleData import de.bixilon.minosoft.data.text.RGBColor.Companion.asGray import de.bixilon.minosoft.gui.rendering.particle.ParticleFactory import de.bixilon.minosoft.gui.rendering.particle.ParticleRenderer +import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY import de.bixilon.minosoft.protocol.network.connection.PlayConnection import de.bixilon.minosoft.util.KUtil.asResourceLocation -import de.bixilon.minosoft.util.KUtil.ticks +import de.bixilon.minosoft.util.KUtil.millis import glm_.vec3.Vec3 -class ExplosionParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, data: ParticleData, val power: Float = 1.0f) : SimpleTextureParticle(connection, particleRenderer, position, data) { +class ExplosionLargeParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, data: ParticleData, power: Float = 1.0f) : SimpleTextureParticle(connection, particleRenderer, position, Vec3.EMPTY, data) { init { - maxAge = (6 + random.nextInt(4)).ticks + movement = false + maxAge = (6 + random.nextInt(4)).millis val gray = random.nextFloat() * 0.6f + 0.4f color = gray.asGray() scale = 2.0f * (power - gray * 0.5f) } - companion object : ParticleFactory { + companion object : ParticleFactory { override val RESOURCE_LOCATION: ResourceLocation = "minecraft:explosion".asResourceLocation() - override fun build(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, velocity: Vec3, data: ParticleData): ExplosionParticle { - return ExplosionParticle(connection, particleRenderer, position, data, velocity.x) + override fun build(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, velocity: Vec3, data: ParticleData): ExplosionLargeParticle { + return ExplosionLargeParticle(connection, particleRenderer, position, data, velocity.x) } } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/SimpleTextureParticle.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/SimpleTextureParticle.kt index 7689d1609..1761131b0 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/SimpleTextureParticle.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/texture/simple/SimpleTextureParticle.kt @@ -19,7 +19,7 @@ import de.bixilon.minosoft.gui.rendering.particle.types.render.texture.TexturePa import de.bixilon.minosoft.protocol.network.connection.PlayConnection import glm_.vec3.Vec3 -abstract class SimpleTextureParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, data: ParticleData) : TextureParticle(connection, particleRenderer, position, data) { +abstract class SimpleTextureParticle(connection: PlayConnection, particleRenderer: ParticleRenderer, position: Vec3, velocity: Vec3, data: ParticleData) : TextureParticle(connection, particleRenderer, position, velocity, data) { override var texture = particleRenderer.renderWindow.textures.allTextures[data.type.textures.first()]!! diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt index 697a68363..c6f066be0 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt @@ -70,14 +70,14 @@ object VecUtil { z = 0.0f } - fun Vec3.assign(vec3: Vec3) { + infix fun Vec3.assign(vec3: Vec3) { x = vec3.x y = vec3.y z = vec3.z } @JvmName(name = "times2") - infix operator fun Vec3.times(lambda: () -> Double): Vec3 { + infix operator fun Vec3.times(lambda: () -> Float): Vec3 { return Vec3( x = x * lambda(), y = y * lambda(), @@ -93,6 +93,23 @@ object VecUtil { ) } + infix operator fun Vec3.plusAssign(lambda: () -> Float) { + this assign this + lambda + } + + infix operator fun Vec3.timesAssign(lambda: () -> Float) { + this assign this * lambda + } + + val Float.sqr: Float + get() = this * this + + val Vec3.ticks: Vec3 + get() = this / ProtocolDefinition.TICKS_PER_SECOND + + val Vec3.millis: Vec3 + get() = this * ProtocolDefinition.TICKS_PER_SECOND + fun getRotatedValues(x: Float, y: Float, sin: Float, cos: Float, rescale: Boolean): Vec2 { val result = Vec2(x * cos - y * sin, x * sin + y * cos) if (rescale) { @@ -290,14 +307,10 @@ object VecUtil { get() = glm.max(this.x, this.y, this.z) val Vec3.signs: Vec3 - get() { - return Vec3(glm.sign(this.x), glm.sign(this.y), glm.sign(this.z)) - } + get() = Vec3(glm.sign(this.x), glm.sign(this.y), glm.sign(this.z)) val Vec3.floor: Vec3i - get() { - return Vec3i(this.x.floor, this.y.floor, this.z.floor) - } + get() = Vec3i(this.x.floor, this.y.floor, this.z.floor) fun Vec3.getMinDistanceDirection(aabb: AABB): Directions { var minDistance = Float.MAX_VALUE diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/connection/PlayConnection.kt b/src/main/java/de/bixilon/minosoft/protocol/network/connection/PlayConnection.kt index 644d0ebae..9cec15be2 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/network/connection/PlayConnection.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/network/connection/PlayConnection.kt @@ -24,6 +24,7 @@ import de.bixilon.minosoft.data.mappings.ResourceLocation import de.bixilon.minosoft.data.mappings.recipes.Recipes import de.bixilon.minosoft.data.mappings.versions.Registries import de.bixilon.minosoft.data.mappings.versions.Version +import de.bixilon.minosoft.data.physics.CollisionDetector import de.bixilon.minosoft.data.player.Player import de.bixilon.minosoft.data.player.tab.TabList import de.bixilon.minosoft.data.scoreboard.ScoreboardManager @@ -79,6 +80,7 @@ class PlayConnection( lateinit var velocityHandlerTask: TimeWorkerTask private var velocityHandlerLastExecutionTime: Long = 0L + val collisionDetector = CollisionDetector(this) override var connectionState: ConnectionStates = ConnectionStates.DISCONNECTED set(value) {