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) {