mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-14 09:56:37 -04:00
more particles: collisions, velocity, fixes, campfire particle
This commit is contained in:
parent
70875d8785
commit
e4b18cf250
3
doc/physics/Particles.md
Normal file
3
doc/physics/Particles.md
Normal file
@ -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.
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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<ExplosionEvent> {
|
||||
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<ParticleSpawnEvent> {
|
||||
@ -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)
|
||||
}
|
||||
|
@ -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<ParticleFactory<out Particle>>(
|
||||
ExplosionEmitterParticle,
|
||||
ExplosionParticle,
|
||||
ExplosionLargeParticle,
|
||||
CampfireSmokeParticle.CosySmokeParticleFactory,
|
||||
CampfireSmokeParticle.SignalSmokeParticleFactory,
|
||||
)
|
||||
|
@ -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<ParticleRenderer> {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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<ExplosionEmitterParticle> {
|
||||
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)
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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<CampfireSmokeParticle> {
|
||||
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<CampfireSmokeParticle> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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<ExplosionParticle> {
|
||||
companion object : ParticleFactory<ExplosionLargeParticle> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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()]!!
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user