diff --git a/src/main/scala/li/cil/oc/common/block/Item.scala b/src/main/scala/li/cil/oc/common/block/Item.scala index 08acf44317..985113b211 100644 --- a/src/main/scala/li/cil/oc/common/block/Item.scala +++ b/src/main/scala/li/cil/oc/common/block/Item.scala @@ -58,13 +58,12 @@ class Item(value: Block, props: Properties) extends BlockItem(value, props) { ctx.getLevel.getBlockEntity(ctxToUse.getClickedPos) match { case keyboard: blockentity.Keyboard => // Ignore. case rotatable: blockentity.traits.Rotatable => - val physicalYaw = RotationHelper.fromYaw(ctxToUse.getPlayer.getYRot) - val localYaw = SableCompat.localFacing(ctxToUse.getLevel, - Vec3.atCenterOf(ctxToUse.getClickedPos), physicalYaw) - rotatable.setFromEntityPitchAndYaw(ctxToUse.getPlayer, localYaw) - if (!rotatable.validFacings.contains(rotatable.pitch)) { - rotatable.pitch = rotatable.validFacings.headOption.getOrElse(Direction.NORTH) - } + val localClickPos = Vec3.atCenterOf(ctxToUse.getClickedPos) + val forward = Vec3.directionFromRotation(ctxToUse.getPlayer.getXRot, ctxToUse.getPlayer.getYRot).reverse() + val side = Vec3.directionFromRotation(0, ctxToUse.getPlayer.getYRot + 90).reverse() + val localYaw = SableCompat.localHeading(ctxToUse.getLevel, localClickPos, forward, side) + val localPitch = SableCompat.localPitch(ctxToUse.getLevel, localClickPos, forward) + rotatable.setFromPitchAndYaw(localPitch.toFloat, localYaw.toFloat) if (!rotatable.isInstanceOf[blockentity.RobotProxy]) { rotatable.invertRotation() } diff --git a/src/main/scala/li/cil/oc/common/blockentity/traits/Rotatable.scala b/src/main/scala/li/cil/oc/common/blockentity/traits/Rotatable.scala index 22c2ff003b..371b3eb565 100644 --- a/src/main/scala/li/cil/oc/common/blockentity/traits/Rotatable.scala +++ b/src/main/scala/li/cil/oc/common/blockentity/traits/Rotatable.scala @@ -59,6 +59,11 @@ trait Rotatable extends RotationAware with internal.Rotatable { pitch2Direction((entity.getXRot / 90).round + 1), localYaw) + def setFromPitchAndYaw(localPitch: Float, localYaw: Float) = + trySetPitchYaw( + pitch2Direction((localPitch / 90).round + 1), + yaw2Direction((localYaw / 360 * 4).round & 3)) + def setFromFacing(value: Direction) = value match { case Direction.DOWN | Direction.UP => @@ -135,6 +140,12 @@ trait Rotatable extends RotationAware with internal.Rotatable { /** Validates new values against the allowed rotations as set in our block. */ protected def trySetPitchYaw(pitch: Direction, yaw: Direction) = { + var safePitch = pitch + var safeYaw = yaw + if (!pitch2Direction.contains(pitch)) + safePitch = Direction.NORTH + if (!validFacings.contains(yaw)) + safeYaw = validFacings.headOption.getOrElse(Direction.NORTH) val oldState = getLevel.getBlockState(getBlockPos) def setState(newState: BlockState): Boolean = { if (oldState.hashCode() != newState.hashCode()) { @@ -146,9 +157,9 @@ trait Rotatable extends RotationAware with internal.Rotatable { } getBlockState.getBlock match { case rotatable if oldState.hasProperty(PropertyRotatable.Pitch) && oldState.hasProperty(PropertyRotatable.Yaw) => - setState(oldState.setValue(PropertyRotatable.Pitch, pitch).setValue(PropertyRotatable.Yaw, yaw)) + setState(oldState.setValue(PropertyRotatable.Pitch, safePitch).setValue(PropertyRotatable.Yaw, safeYaw)) case rotatable if oldState.hasProperty(PropertyRotatable.Facing) => - setState(oldState.setValue(PropertyRotatable.Facing, yaw)) + setState(oldState.setValue(PropertyRotatable.Facing, safeYaw)) case _ => false } } diff --git a/src/main/scala/li/cil/oc/util/SableCompat.scala b/src/main/scala/li/cil/oc/util/SableCompat.scala index 77e655ec8a..326edfc98c 100644 --- a/src/main/scala/li/cil/oc/util/SableCompat.scala +++ b/src/main/scala/li/cil/oc/util/SableCompat.scala @@ -44,6 +44,20 @@ object SableCompat { } } + /** Transform a world-space direction vector into a local-space vector. + * + * 'position' should be a location on the target sublevel + */ + def localDirection(level: Level, position: Vec3, facing: Vec3): Vec3 = { + if (facing == null) Vec3.ZERO + else if (level == null || position == null) facing + else { + val sublevel = SableCompanion.INSTANCE.getContaining(level, position) + if (sublevel == null) facing + else sublevel.logicalPose().transformNormalInverse(facing) + } + } + def distanceSquared(level: Level, a: Vec3, b: Vec3): Double = { if (level == null) a.distanceToSqr(b) else SableCompanion.INSTANCE.distanceSquaredWithSubLevels(level, a, b) @@ -60,7 +74,7 @@ object SableCompat { } } - /** Get the transformed direction's horizontal heading in degrees. + /** Get the transformed direction's horizontal heading in degrees, converted from local-space to world-space. * * Zero points north (-Z), and values increase clockwise when viewed from * above: east is 90, south is 180, and west is 270. @@ -75,12 +89,41 @@ object SableCompat { } } - /** Get the transformed direction's elevation above the horizontal plane. */ + /** Get the transformed direction's horizontal heading in degrees, converted from world-space to local-space. + * + * Zero points north (-Z), and values increase clockwise when viewed from + * above: east is 90, south is 180, and west is 270. + */ + def localHeading(level: Level, position: Vec3, facing: Vec3, side: Vec3): Double = { + var direction = localDirection(level, position, facing) + var horizontalLength = math.sqrt(direction.x * direction.x + direction.z * direction.z) + if (horizontalLength < 1.0e-3) { // Try to use the side/right vector to deduce yaw + direction = localDirection(level, position, side) + horizontalLength = math.sqrt(direction.x * direction.x + direction.z * direction.z) + if (horizontalLength < 1.0e-3) 0.0 + else { + val heading = math.toDegrees(math.atan2(direction.z, direction.x)) % 360.0 + if (heading < 0.0) heading + 360.0 else heading + } + } + else { + val heading = math.toDegrees(math.atan2(direction.x, -direction.z)) % 360.0 + if (heading < 0.0) heading + 360.0 else heading + } + } + + /** Get the transformed direction's elevation above the horizontal plane, converted from local-space to world-space. */ def physicalPitch(level: Level, position: Vec3, facing: Direction): Double = { val direction = physicalDirection(level, position, facing) math.toDegrees(math.atan2(direction.y, math.sqrt(direction.x * direction.x + direction.z * direction.z))) } + /** Get the transformed direction's elevation above the horizontal plane, converted from world-space to local-space. */ + def localPitch(level: Level, position: Vec3, facing: Vec3): Double = { + val direction = localDirection(level, position, facing) + math.toDegrees(math.atan2(direction.y, math.sqrt(direction.x * direction.x + direction.z * direction.z))) + } + /** Convert a physical cardinal direction into the local direction at a block. */ def localFacing(level: Level, position: Vec3, facing: Direction): Direction = { if (level == null || position == null || facing == null) facing