Skip to content

Commit

Permalink
fixed rotateToFaceVector; minor changes to planet display entities
Browse files Browse the repository at this point in the history
  • Loading branch information
kwazedilla authored and Gutin1 committed Apr 11, 2024
1 parent 0e91979 commit 7366ac7
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.joml.AxisAngle4f
import org.joml.Quaternionf
import org.joml.Vector3f
import java.util.UUID
import kotlin.math.atan2

/**
* Functions for creating client-side display entities.
Expand Down Expand Up @@ -215,10 +216,11 @@ object ClientDisplayEntities : IonServerComponent() {
* @param direction the direction that an object should face
*/
fun rotateToFaceVector(direction: Vector3f): Quaternionf {
// Two rotations are necessary:
// - the first rotation rolls the object around the z-axis so that the object's local x-axis is
// aligned with the eventual axis of rotation
// - the second rotation rotates the object around the axis of rotation and results in the finished rotation
// Attempt with axis-angle representation
// Two rotations are necessary (global transforms assumed):
// - the first rotation rotates the object around the axis of rotation and results in the finished rotation
// - the second rotation rolls the object around the direction vector so that the object's local y-axis is
// aligned as close as possible to the global y-axis

// Find the axis of rotation and final rotation angle

Expand All @@ -229,21 +231,36 @@ object ClientDisplayEntities : IonServerComponent() {
// angle between the initial vector and final vector to determine the rotation needed (in radians)
val angle = globalZAxis.angle(direction)
// get the quaternion to rotate the object to face the initial vector
val secondRotation = Quaternionf(AxisAngle4f(angle, cross))
val firstRotation = Quaternionf(AxisAngle4f(angle, cross))

// Find the roll amount

// If initial rotation vector is always SOUTH, the axis of rotation (cross) will always be orthogonal to UP
// (+y direction)
// Method derived from:
// https://math.stackexchange.com/questions/2548811/find-an-angle-to-rotate-a-vector-around-a-ray-so-that-the-vector-gets-as-close-a

// In the answer above, this is vector B (the vector to get close to)
val globalYAxis = Vector3f(0f, 1f, 0f)
// angle between the y-axis and the axis of rotation; used to remove the roll from the object (so only
// yaw and pitch remain) (in radians)
val rollAngle = globalYAxis.angle(cross)
// get the quaternion to roll the object
val firstRotation = Quaternionf(AxisAngle4f(rollAngle, globalZAxis))
// Vector U is the direction vector given
// Vector A (the vector to rotate) == Vector E due to A and U already being perpendicular
// Rotating the global Y axis using the firstRotation transform gets the local Y axis
val localYAxis = (globalYAxis.clone() as Vector3f).rotate(firstRotation)
// Vector F (U cross E), equivalent to the local X axis here
val localXAxis = (direction.clone() as Vector3f).cross((localYAxis.clone() as Vector3f).normalize()).normalize()
// Theta = arc tangent 2(B dot F, B dot E)
val rollAngle = atan2(globalYAxis.dot(localXAxis), globalYAxis.dot(localYAxis))

// get the quaternion to roll the object
val secondRotation = Quaternionf(AxisAngle4f(rollAngle, (direction.clone() as Vector3f).normalize()))
// Combine the two rotations
return firstRotation.mul(secondRotation)

// When multiplying quaternions together, going from right to left applies the quaternions in that order,
// assuming rotation from the global PoV (the rotation axes do not need to factor in previous rotations).
// Going from left to right is equivalent if rotations are applied in that order from a local PoV (the
// rotation axes must follow previous rotations).
//
// This implementation assumes rotation on the global axes, which is why secondRotation appears first and
// secondRotation uses the direction vector as its axis of rotation (instead of the local Z axis).
return secondRotation.mul(firstRotation)
}

/**
Expand All @@ -252,14 +269,14 @@ object ClientDisplayEntities : IonServerComponent() {
* @return the axis-angle representation to get the desired rotation
* @param direction the direction that an object should face
*/
fun rotateToFaceVector2d(direction: Vector3f): AxisAngle4f {
fun rotateToFaceVector2d(direction: Vector3f): Quaternionf {
// Assuming initial rotation vector is facing SOUTH (+z direction)
val globalZAxis = Vector3f(0f, 0f, 1f)
// create a new vector with no y-component
val flattenedDirection = (direction.clone() as Vector3f).apply { this.y = 0f }
// angle between vectors to determine the rotation needed (in radians)
val angle = if (flattenedDirection.x > 0) globalZAxis.angle(flattenedDirection) else globalZAxis.angle(flattenedDirection) * -1
// return the axis-angle representation, with the axis of rotation around the y-axis
return AxisAngle4f(angle, Vector3f(0f, 1f, 0f))
return Quaternionf(AxisAngle4f(angle, Vector3f(0f, 1f, 0f)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
import org.bukkit.util.Transformation
import org.bukkit.util.Vector
import org.joml.AxisAngle4f
import org.joml.Quaternionf
import org.joml.Vector3f
import java.util.UUID
Expand Down Expand Up @@ -65,7 +64,7 @@ object PlanetSpaceRendering : IonServerComponent() {
val entity = ClientDisplayEntityFactory.createItemDisplay(player)
val entityRenderDistance = getViewDistanceEdge(player)
// do not render if the planet is closer than the entity render distance
if (distance < entityRenderDistance) return null
if (distance < entityRenderDistance * 2) return null

entity.itemStack = getPlanetItemStack(identifier)
entity.billboard = Display.Billboard.FIXED
Expand All @@ -83,7 +82,7 @@ object PlanetSpaceRendering : IonServerComponent() {
offset.toVector3f(),
ClientDisplayEntities.rotateToFaceVector2d(offset.toVector3f()),
Vector3f(scale(distance) * viewDistanceFactor(entityRenderDistance)),
AxisAngle4f()
Quaternionf()
)

// position needs to be assigned immediately or else the entity gets culled as it's not in a loaded chunk
Expand Down Expand Up @@ -113,7 +112,7 @@ object PlanetSpaceRendering : IonServerComponent() {
// also do not render if the planet is closer than the entity render distance
if (!nmsEntity.isChunkLoaded ||
nmsEntity.level().world.name != player.world.name ||
distance < (entityRenderDistance * 2)
distance < entityRenderDistance * 2
) {
ClientDisplayEntities.deleteDisplayEntityPacket(player, nmsEntity)
ClientDisplayEntities[player.uniqueId]?.remove(identifier)
Expand All @@ -128,7 +127,7 @@ object PlanetSpaceRendering : IonServerComponent() {
// apply transformation
val transformation = com.mojang.math.Transformation(
offset.toVector3f(),
Quaternionf(ClientDisplayEntities.rotateToFaceVector2d(offset.toVector3f())),
ClientDisplayEntities.rotateToFaceVector2d(offset.toVector3f()),
Vector3f(scale * viewDistanceFactor(entityRenderDistance)),
Quaternionf()
)
Expand Down Expand Up @@ -174,7 +173,7 @@ object PlanetSpaceRendering : IonServerComponent() {
offset.toVector3f(),
ClientDisplayEntities.rotateToFaceVector2d(offset.toVector3f()),
Vector3f(data.scale * viewDistanceFactor(data.distance)),
AxisAngle4f()
Quaternionf()
)

// position needs to be assigned immediately or else the entity gets culled as it's not in a loaded chunk
Expand Down Expand Up @@ -214,7 +213,7 @@ object PlanetSpaceRendering : IonServerComponent() {
// apply transformation
val transformation = com.mojang.math.Transformation(
offset.toVector3f(),
Quaternionf(ClientDisplayEntities.rotateToFaceVector2d(offset.toVector3f())),
ClientDisplayEntities.rotateToFaceVector2d(offset.toVector3f()),
Vector3f(data.scale * viewDistanceFactor(data.distance)),
Quaternionf()
)
Expand Down Expand Up @@ -263,9 +262,9 @@ object PlanetSpaceRendering : IonServerComponent() {
// apply transformation
entity.transformation = Transformation(
offset.toVector3f(),
ClientDisplayEntities.rotateToFaceVector2d(offset.toVector3f()).apply { this.angle += PI.toFloat() },
ClientDisplayEntities.rotateToFaceVector2d(offset.toVector3f().mul(-1f)),
Vector3f(data.scale * viewDistanceFactor(data.distance)),
AxisAngle4f()
Quaternionf()
)

// position needs to be assigned immediately or else the entity gets culled as it's not in a loaded chunk
Expand Down Expand Up @@ -305,7 +304,7 @@ object PlanetSpaceRendering : IonServerComponent() {
// apply transformation
val transformation = com.mojang.math.Transformation(
offset.toVector3f(),
Quaternionf(ClientDisplayEntities.rotateToFaceVector2d(offset.toVector3f()).apply { this.angle += PI.toFloat() }),
ClientDisplayEntities.rotateToFaceVector2d(offset.toVector3f().mul(-1f)),
Vector3f(data.scale * viewDistanceFactor(data.distance)),
Quaternionf()
)
Expand Down Expand Up @@ -411,6 +410,7 @@ object PlanetSpaceRendering : IonServerComponent() {

// Rendering planets
for (planet in planetList) {
println("rendering ${planet.name}")
val distance = player.location.toVector().distance(planet.location.toVector())
val direction = planet.location.toVector().subtract(player.location.toVector()).normalize()

Expand Down

0 comments on commit 7366ac7

Please sign in to comment.