diff --git a/src/main/kotlin/net/mcbrawls/railroad/CameraTrack.kt b/src/main/kotlin/net/mcbrawls/railroad/CameraTrack.kt new file mode 100644 index 0000000..a2589c4 --- /dev/null +++ b/src/main/kotlin/net/mcbrawls/railroad/CameraTrack.kt @@ -0,0 +1,46 @@ +package net.mcbrawls.railroad + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder + +/** + * A camera track which can be played to a player. + */ +data class CameraTrack( + val keyframes: List, +) { + data class TrackKeyframe( + /** + * The time at which the keyframe is played on the track. + */ + val seconds: Float, + + /** + * The keyframe to be played. + */ + val keyframe: Keyframe, + ) { + companion object { + /** + * The codec for this class. + */ + val CODEC: Codec = RecordCodecBuilder.create { instance -> + instance.group( + Codec.FLOAT.fieldOf("seconds").forGetter(TrackKeyframe::seconds), + Keyframe.CODEC.fieldOf("keyframe").forGetter(TrackKeyframe::keyframe), + ).apply(instance, ::TrackKeyframe) + } + } + } + + companion object { + /** + * The codec for this class. + */ + val CODEC: Codec = RecordCodecBuilder.create { instance -> + instance.group( + TrackKeyframe.CODEC.listOf().fieldOf("keyframes").forGetter(CameraTrack::keyframes), + ).apply(instance, ::CameraTrack) + } + } +} diff --git a/src/main/kotlin/net/mcbrawls/railroad/Interpolation.kt b/src/main/kotlin/net/mcbrawls/railroad/Interpolation.kt new file mode 100644 index 0000000..df6be83 --- /dev/null +++ b/src/main/kotlin/net/mcbrawls/railroad/Interpolation.kt @@ -0,0 +1,21 @@ +package net.mcbrawls.railroad + +import net.mcbrawls.railroad.Vectors.cubicInterpolate + +fun interface Interpolation { + fun apply(delta: Float, keyframe: Keyframe, targetKeyframe: Keyframe): Keyframe + + companion object { + val LINEAR: Interpolation = Interpolation { delta, keyframe, targetKeyframe -> + val pos = keyframe.pos.lerp(targetKeyframe.pos, delta.toDouble()) + val rotation = keyframe.rotation.lerp(targetKeyframe.rotation, delta) + Keyframe(pos, rotation) + } + + val CUBIC: Interpolation = Interpolation { delta, keyframe, targetKeyframe -> + val pos = keyframe.pos.cubicInterpolate(targetKeyframe.pos, delta) + val rotation = keyframe.rotation.cubicInterpolate(targetKeyframe.rotation, delta) + Keyframe(pos, rotation) + } + } +} diff --git a/src/main/kotlin/net/mcbrawls/railroad/Keyframe.kt b/src/main/kotlin/net/mcbrawls/railroad/Keyframe.kt new file mode 100644 index 0000000..530177b --- /dev/null +++ b/src/main/kotlin/net/mcbrawls/railroad/Keyframe.kt @@ -0,0 +1,26 @@ +package net.mcbrawls.railroad + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import org.joml.Vector2f +import org.joml.Vector3d + +/** + * A position along a camera track. + */ +data class Keyframe( + val pos: Vector3d, + val rotation: Vector2f, +) { + companion object { + /** + * The codec for this class. + */ + val CODEC: Codec = RecordCodecBuilder.create { instance -> + instance.group( + Vectors.VECTOR_3D.fieldOf("position").forGetter(Keyframe::pos), + Vectors.VECTOR_2F.fieldOf("rotation").forGetter(Keyframe::rotation), + ).apply(instance, ::Keyframe) + } + } +} diff --git a/src/main/kotlin/net/mcbrawls/railroad/Vectors.kt b/src/main/kotlin/net/mcbrawls/railroad/Vectors.kt new file mode 100644 index 0000000..69167ad --- /dev/null +++ b/src/main/kotlin/net/mcbrawls/railroad/Vectors.kt @@ -0,0 +1,56 @@ +package net.mcbrawls.railroad + +import com.mojang.serialization.Codec +import org.joml.Vector2f +import org.joml.Vector3d + +object Vectors { + val VECTOR_3D: Codec = Codec.DOUBLE.listOf().xmap( + { list -> Vector3d(list[0], list[1], list[2]) }, + { vect -> listOf(vect.x, vect.y, vect.z) } + ) + + val VECTOR_2F: Codec = Codec.FLOAT.listOf().xmap( + { list -> Vector2f(list[0], list[1]) }, + { vect -> listOf(vect.x, vect.y) } + ) + + fun Vector3d.cubicInterpolate(end: Vector3d, delta: Float): Vector3d { + val start = this + + val t2 = delta * delta + val t3 = t2 * delta + + val m0 = (end.sub(start)).mul(0.5) + + val h0 = 2 * t3 - 3 * t2 + 1 + val h1 = -2 * t3 + 3 * t2 + val h2 = t3 - 2 * t2 + delta + val h3 = t3 - t2 + + return Vector3d( + start.x * h0 + end.x * h1 + m0.x * h2 + m0.x * h3, + start.y * h0 + end.y * h1 + m0.y * h2 + m0.y * h3, + start.z * h0 + end.z * h1 + m0.z * h2 + m0.z * h3 + ) + } + + fun Vector2f.cubicInterpolate(end: Vector2f, delta: Float): Vector2f { + val start = this + + val t2 = delta * delta + val t3 = t2 * delta + + val m0 = (end.sub(start)).mul(0.5f) + + val h0 = 2 * t3 - 3 * t2 + 1 + val h1 = -2 * t3 + 3 * t2 + val h2 = t3 - 2 * t2 + delta + val h3 = t3 - t2 + + return Vector2f( + start.x * h0 + end.x * h1 + m0.x * h2 + m0.x * h3, + start.y * h0 + end.y * h1 + m0.y * h2 + m0.y * h3 + ) + } +}