Vector3 normal vector functions; locked door opening from inside mechanics; correction to item/equipment identification in WSA; quick solution to stop door from closing on player who opened it if that player is still standing within sqrt(15)m from it

This commit is contained in:
FateJH 2018-01-24 10:07:42 -05:00
parent c5246d5f10
commit f4b913a5d9
8 changed files with 414 additions and 69 deletions

View file

@ -4,28 +4,58 @@ package net.psforever.objects.serverobject.doors
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.Player
import net.psforever.packet.game.UseItemMessage
import net.psforever.types.Vector3
/**
* A structure-owned server object that is a "door" that can open and can close.
* @param ddef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class Door(private val ddef : DoorDefinition) extends PlanetSideServerObject {
private var openState : Boolean = false
private var openState : Option[Player] = None
/** a vector in the direction of the "outside" of a room;
* typically, any locking utility is on that same "outside" */
private var outwards : Vector3 = Vector3.Zero
def Open : Boolean = openState
/**
* While setting the normal rotation angle for the door (?),
* use the angular data to determine an "inside" side and an "outside" side.<br>
* <br>
* Doors are always positioned with the frame perpendicular to the ground.
* The `i` and `j` components can be excused for this reason and only the `k` component (rotation around world-up) matters.
* Due to angle-corrected North, add 90 degrees before switching to radians and negate the cosine.
* @param orient the orientation of the door
* @return the clamped orientation of the door
*/
override def Orientation_=(orient : Vector3) : Vector3 = {
val ret = super.Orientation_=(orient)
//transform angular data into unit circle components
val rang = math.toRadians(orient.z + 90)
outwards = Vector3(-math.cos(rang).toFloat, math.sin(rang).toFloat, 0)
ret
}
def Open_=(open : Boolean) : Boolean = {
def Outwards : Vector3 = outwards
def isOpen : Boolean = openState.isDefined
def Open : Option[Player] = openState
def Open_=(player : Player) : Option[Player] = {
Open_=(Some(player))
}
def Open_=(open : Option[Player]) : Option[Player] = {
openState = open
Open
}
def Use(player : Player, msg : UseItemMessage) : Door.Exchange = {
if(!openState) {
openState = true
if(openState.isEmpty) {
openState = Some(player)
Door.OpenEvent()
}
else {
openState = false
openState = None
Door.CloseEvent()
}
}
@ -79,7 +109,7 @@ object Door {
import akka.actor.ActorContext
/**
* Instantiate an configure a `Door` object
* Instantiate and configure a `Door` object.
* @param id the unique id that will be assigned to this entity
* @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `Door` object
@ -92,4 +122,25 @@ object Door {
obj.Actor = context.actorOf(Props(classOf[DoorControl], obj), s"${GlobalDefinitions.door.Name}_$id")
obj
}
import net.psforever.types.Vector3
/**
* Instantiate and configure a `Door` object that has knowledge of both its position and outwards-facing direction.
* The assumption is that this door will be paired with an IFF Lock, thus, has conditions for opening.
* @param pos the position of the door
* @param outwards_direction a vector in the direction of the door's outside
* @param id the unique id that will be assigned to this entity
* @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `Door` object
*/
def Constructor(pos : Vector3, outwards_direction : Vector3)(id : Int, context : ActorContext) : Door = {
import akka.actor.Props
import net.psforever.objects.GlobalDefinitions
val obj = Door(GlobalDefinitions.door)
obj.Position = pos
obj.Orientation = outwards_direction
obj.Actor = context.actorOf(Props(classOf[DoorControl], obj), s"${GlobalDefinitions.door.Name}_$id")
obj
}
}

View file

@ -15,7 +15,7 @@ final case class Vector3(x : Float,
* @return a new `Vector3` object with the summed values
*/
def +(vec : Vector3) : Vector3 = {
new Vector3(x + vec.x, y + vec.y, z + vec.z)
Vector3(x + vec.x, y + vec.y, z + vec.z)
}
/**
@ -25,7 +25,7 @@ final case class Vector3(x : Float,
* @return a new `Vector3` object with the difference values
*/
def -(vec : Vector3) : Vector3 = {
new Vector3(x - vec.x, y - vec.y, z - vec.z)
Vector3(x - vec.x, y - vec.y, z - vec.z)
}
/**
@ -36,11 +36,13 @@ final case class Vector3(x : Float,
* @return a new `Vector3` object
*/
def *(scalar : Float) : Vector3 = {
new Vector3(x*scalar, y*scalar, z*scalar)
Vector3(x*scalar, y*scalar, z*scalar)
}
}
object Vector3 {
final val Zero : Vector3 = Vector3(0f, 0f, 0f)
implicit val codec_pos : Codec[Vector3] = (
("x" | newcodecs.q_float(0.0, 8192.0, 20)) ::
("y" | newcodecs.q_float(0.0, 8192.0, 20)) ::
@ -102,4 +104,93 @@ object Vector3 {
val dz : Float = vec.z
(dx * dx) + (dy * dy) + (dz * dz)
}
/**
* Given a vector, find that's vector's unit vector.<br>
* <br>
* A unit vector is a vector in the direction of the original vector but with a magnitude of 1.
* @param vec the original vector
* @return the unit vector;
* if the original vector has no magnitude, a zero-vector is returned
*/
def Unit(vec : Vector3) : Vector3 = {
val mag : Float = Magnitude(vec)
if(mag == 0) {
Vector3.Zero
}
else {
Vector3(vec.x / mag, vec.y / mag, vec.z / mag)
}
}
/**
* Given two vectors, find their dot product.<br>
* <br>
* The dot product is the sum of the products of the corresponding component parts of two vectors.
* It is equal to the product of the Euclidean magnitude of the vectors and cosine of the angle between them.
* If the dot product of two vectors of non-zero magnitude is 0, then the vectors are perpendicular to each other.
* @param vec1 the first vector
* @param vec2 the second vector
* @return the dot product
*/
def DotProduct(vec1 : Vector3, vec2 : Vector3) : Float = {
vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z
}
/**
* For two vectors, find a vector that is simultaneously parallel to both vectors.<br>
* <br>
* The magnitude of the cross product is equal to
* the product of the magnitudes of both vectors
* and the sine of the angle between them.
* If the two original vectors are parallel or antiparallel, the cross product is a zero vector.
* Due to handiness rules, two non-zero cross product vectors that are antiparallel to each other can be calculated.
* @param vec1 the first vector
* @param vec2 the second vector
* @return the cross product
*/
def CrossProduct(vec1 : Vector3, vec2 : Vector3) : Vector3 = {
Vector3(
vec1.y * vec2.z - vec2.y * vec1.z,
vec2.x * vec1.z - vec1.x * vec2.z,
vec1.x * vec2.y - vec2.x * vec1.y
)
}
/**
* Given two vectors, find the scalar value of the projection of one vector on the other.<br>
* <br>
* The value of the resulting scalar is the magnitude of the vector resulting from a vector projection of `vec1` onto `vec2`.
* For perpendicular vectors, the scalar projection result will be the same as the dot product result - zero.
* A positive value indicates a projected vector in the same direction as `vec2`;
* a negative value indicates an antiparallel vector.
* @see `VectorProjection`
* @param vec1 the vector being projected
* @param vec2 the vector projected onto
* @return the magnitude of the resulting projected vector
*/
def ScalarProjection(vec1 : Vector3, vec2 : Vector3) : Float = {
val mag : Float = Magnitude(vec2)
if(mag == 0f) {
0f
}
else {
DotProduct(vec1, vec2) / mag
}
}
/**
* Given two vectors, find the projection of one vector on the other.<br>
* <br>
* The vector projection of `vec1` on `vec2` produces a vector that is
* the direction of (parallel to) `vec2`
* with a magnitude equal to the product of `vec1` and the cosine of the angle between the two vectors.
* @see `ScalarProjection`
* @param vec1 the vector being projected
* @param vec2 the vector projected onto
* @return the resulting projected vector
*/
def VectorProjection(vec1 : Vector3, vec2 : Vector3) : Vector3 = {
Unit(vec2) * ScalarProjection(vec1, vec2)
}
}

View file

@ -48,21 +48,145 @@ class Vector3Test extends Specification {
Vector3.Distance(obj1, obj2) mustEqual 0f
}
"addition" in {
"perform addition" in {
val obj1 = Vector3(3.0f, 4.0f, 5.0f)
val obj2 = Vector3(3.0f, 4.0f, 5.0f)
obj1 + obj2 mustEqual Vector3(6f, 8f, 10f)
}
"subtraction" in {
"perform subtraction" in {
val obj1 = Vector3(3.0f, 4.0f, 5.0f)
val obj2 = Vector3(3.0f, 4.0f, 5.0f)
obj1 - obj2 mustEqual Vector3(0f, 0f, 0f)
}
"scalar" in {
"multiply by a scalar" in {
vec * 3f mustEqual Vector3(3.8999999f, -7.7999997f, 11.700001f)
}
"calculate the unit vector (zero)" in {
Vector3.Unit(Vector3.Zero) mustEqual Vector3(0,0,0)
}
"calculate the unit vector (normal)" in {
import Vector3._
val one_root_two : Float = (1/math.sqrt(2)).toFloat
val one_root_three : Float = (1/math.sqrt(3)).toFloat
val ulp : Float = math.ulp(1) //measure of insignificance
Unit(Vector3(1, 0, 0)) mustEqual Vector3(1, 0, 0)
1 - Magnitude(Vector3(1, 0, 0)) < ulp mustEqual true
Unit(Vector3(1, 1, 0)) mustEqual Vector3(one_root_two, one_root_two, 0)
1 - Magnitude(Vector3(one_root_two, one_root_two, 0)) < ulp mustEqual true
Unit(Vector3(1, 1, 1)) mustEqual Vector3(one_root_three, one_root_three, one_root_three)
1 - Magnitude(Vector3(one_root_three, one_root_three, one_root_three)) < ulp mustEqual true
}
"calculate the dot product (magnitude-squared)" in {
Vector3.DotProduct(vec, vec) mustEqual Vector3.MagnitudeSquared(vec)
}
"calculate the dot product (two vectors)" in {
Vector3.DotProduct(vec, Vector3(3.4f, -5.6f, 7.8f)) mustEqual 49.4f
}
"calculate the dot product (zero vector)" in {
Vector3.DotProduct(vec, Vector3.Zero) mustEqual 0f
}
"calculate the cross product (identity)" in {
val Vx : Vector3 = Vector3(1, 0, 0)
val Vy : Vector3 = Vector3(0, 1, 0)
val Vz : Vector3 = Vector3(0, 0, 1)
Vector3.CrossProduct(Vx, Vy) mustEqual Vz
Vector3.CrossProduct(Vy, Vz) mustEqual Vx
Vector3.CrossProduct(Vz, Vx) mustEqual Vy
Vector3.CrossProduct(Vy, Vx) mustEqual Vector3(0, 0, -1)
Vector3.CrossProduct(Vz, Vy) mustEqual Vector3(-1, 0, 0)
Vector3.CrossProduct(Vx, Vz) mustEqual Vector3(0, -1, 0)
}
"calculate the cross product (full)" in {
val A : Vector3 = Vector3(2, 1, -1)
val B : Vector3 = Vector3(-3, 4, 1)
Vector3.CrossProduct(A, B) mustEqual Vector3(5, 1, 11)
Vector3.CrossProduct(B, A) mustEqual Vector3(-5, -1, -11)
}
"find a perpendicular vector with cross product" in {
val A : Vector3 = Vector3(2, 1, -1)
val B : Vector3 = Vector3(-3, 4, 1)
val C : Vector3 = Vector3.CrossProduct(A, B)
Vector3.DotProduct(A, C) mustEqual 0
Vector3.DotProduct(B, C) mustEqual 0
}
"calculate the scalar projection (perpendicular vectors)" in {
val Vx : Vector3 = Vector3(1, 0, 0)
val Vy : Vector3 = Vector3(0, 1, 0)
Vector3.ScalarProjection(Vx, Vy) mustEqual 0
}
"calculate the scalar projection (parallel vectors)" in {
val A : Vector3 = Vector3(2, 0, 0)
val B : Vector3 = Vector3(10, 0, 0)
Vector3.ScalarProjection(A, B) mustEqual 2
Vector3.ScalarProjection(B, A) mustEqual 10
}
"calculate the scalar projection (antiparallel vectors)" in {
val A : Vector3 = Vector3(2, 0, 0)
val B : Vector3 = Vector3(-10, 0, 0)
Vector3.ScalarProjection(A, B) mustEqual -2
Vector3.ScalarProjection(B, A) mustEqual -10
}
"calculate the scalar projection (normal)" in {
val A : Vector3 = Vector3(2, 1, -1)
val B : Vector3 = Vector3(3, 4, 1)
Vector3.ScalarProjection(A, B) mustEqual 1.7650452f
Vector3.ScalarProjection(B, A) mustEqual 3.6742344f
}
"calculate the vector projection (perpendicular vectors)" in {
val Vx : Vector3 = Vector3(1, 0, 0)
val Vy : Vector3 = Vector3(0, 1, 0)
Vector3.VectorProjection(Vx, Vy) mustEqual Vector3.Zero
}
"calculate the vector projection (parallel vectors)" in {
val A : Vector3 = Vector3(2, 0, 0)
val B : Vector3 = Vector3(10, 0, 0)
Vector3.VectorProjection(A, B) mustEqual A
Vector3.VectorProjection(B, A) mustEqual B
}
"calculate the vector projection (antiparallel vectors)" in {
val A : Vector3 = Vector3(2, 0, 0)
val B : Vector3 = Vector3(-10, 0, 0)
Vector3.VectorProjection(A, B) mustEqual A
Vector3.VectorProjection(B, A) mustEqual B
}
"calculate the vector projection (normal)" in {
val A : Vector3 = Vector3(2, 1, -1)
val B : Vector3 = Vector3(3, 4, 1)
Vector3.VectorProjection(A, B) mustEqual Vector3(1.0384614f, 1.3846153f, 0.34615383f)
Vector3.VectorProjection(B, A) mustEqual Vector3(2.9999998f, 1.4999999f, -1.4999999f)
}
}
}

View file

@ -19,27 +19,55 @@ class DoorTest extends Specification {
"starts as closed (false)" in {
val door = Door(GlobalDefinitions.door)
door.Open mustEqual false
door.Open mustEqual None
door.isOpen mustEqual false
}
"can be opened and closed (1; manual)" in {
"be opened and closed (1; manual)" in {
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val door = Door(GlobalDefinitions.door)
door.Open mustEqual false
door.Open = true
door.Open mustEqual true
door.Open = false
door.Open mustEqual false
door.isOpen mustEqual false
door.Open mustEqual None
door.Open = Some(player)
door.isOpen mustEqual true
door.Open mustEqual Some(player)
door.Open = None
door.isOpen mustEqual false
door.Open mustEqual None
}
"can beopened and closed (2; toggle)" in {
"be opened and closed (2; toggle)" in {
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val msg = UseItemMessage(PlanetSideGUID(6585), 0, PlanetSideGUID(372), 4294967295L, false, Vector3(5.0f,0.0f,0.0f), Vector3(0.0f,0.0f,0.0f), 11, 25, 0, 364)
val door = Door(GlobalDefinitions.door)
door.Open mustEqual false
door.Open mustEqual None
door.Use(player, msg)
door.Open mustEqual true
door.Open mustEqual Some(player)
door.Use(player, msg)
door.Open mustEqual false
door.Open mustEqual None
}
"keep track of its orientation as a North-corrected vector" in {
val ulp = math.ulp(1)
val door = Door(GlobalDefinitions.door)
door.Orientation = Vector3(0, 0, 0) //face North
door.Outwards.x < ulp mustEqual true
door.Outwards.y mustEqual 1
door.Orientation = Vector3(0, 0, 90) //face East
door.Outwards.x mustEqual 1
door.Outwards.y < ulp mustEqual true
door.Orientation = Vector3(0, 0, 180) //face South
door.Outwards.x < ulp mustEqual true
door.Outwards.y mustEqual -1
door.Orientation = Vector3(0, 0, 270) //face West
door.Outwards.x mustEqual -1
door.Outwards.y < ulp mustEqual true
}
}
}
@ -61,7 +89,7 @@ class DoorControl2Test extends ActorTest() {
door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door")
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val msg = UseItemMessage(PlanetSideGUID(1), 0, PlanetSideGUID(2), 0L, false, Vector3(0f,0f,0f),Vector3(0f,0f,0f),0,0,0,0L) //faked
assert(!door.Open)
assert(door.Open.isEmpty)
door.Actor ! Door.Use(player, msg)
val reply = receiveOne(Duration.create(500, "ms"))
@ -70,7 +98,7 @@ class DoorControl2Test extends ActorTest() {
assert(reply2.player == player)
assert(reply2.msg == msg)
assert(reply2.response == Door.OpenEvent())
assert(door.Open)
assert(door.Open.isDefined)
}
}
}
@ -80,12 +108,12 @@ class DoorControl3Test extends ActorTest() {
"do nothing if given garbage" in {
val door = Door(GlobalDefinitions.door)
door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door")
assert(!door.Open)
assert(door.Open.isEmpty)
door.Actor ! "trash"
val reply = receiveOne(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Door.NoEvent])
assert(!door.Open)
assert(door.Open.isEmpty)
}
}
}

View file

@ -9,9 +9,9 @@ import net.psforever.types.Vector3
import scala.concurrent.duration.Duration
class DoorObjectBuilderTest extends ActorTest {
class DoorObjectBuilderTest1 extends ActorTest {
import net.psforever.objects.serverobject.doors.Door
"DoorObjectBuilder" should {
"Door object" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, Door.Constructor), hub), "door")
@ -26,9 +26,29 @@ class DoorObjectBuilderTest extends ActorTest {
}
}
class DoorObjectBuilderTest2 extends ActorTest {
import net.psforever.objects.serverobject.doors.Door
"Door object" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, Door.Constructor(Vector3(1, 2, 3), Vector3(90, 180, 45))), hub), "door")
actor ! "!"
val reply = receiveOne(Duration.create(1000, "ms"))
assert(reply.isInstanceOf[Door])
assert(reply.asInstanceOf[Door].Position == Vector3(1, 2, 3))
assert(reply.asInstanceOf[Door].Orientation == Vector3(90, 180, 45))
assert(reply.asInstanceOf[Door].Outwards == Vector3(0.70710677f, 0.70710677f, 0f))
assert(reply.asInstanceOf[Door].HasGUID)
assert(reply.asInstanceOf[Door].GUID == PlanetSideGUID(1))
assert(reply == hub(1).get)
}
}
}
class IFFLockObjectBuilderTest extends ActorTest {
import net.psforever.objects.serverobject.locks.IFFLock
"IFFLockObjectBuilder" should {
"IFFLock object" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, IFFLock.Constructor), hub), "lock")
@ -45,7 +65,7 @@ class IFFLockObjectBuilderTest extends ActorTest {
class ImplantTerminalMechObjectBuilderTest extends ActorTest {
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
"IFFLockObjectBuilder" should {
"Implant terminal mech object" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, ImplantTerminalMech.Constructor), hub), "mech")
@ -63,7 +83,7 @@ class ImplantTerminalMechObjectBuilderTest extends ActorTest {
class TerminalObjectBuilderTest extends ActorTest {
import net.psforever.objects.GlobalDefinitions.order_terminal
import net.psforever.objects.serverobject.terminals.Terminal
"TerminalObjectBuilder" should {
"Terminal object" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, Terminal.Constructor(order_terminal)), hub), "term")
@ -80,7 +100,7 @@ class TerminalObjectBuilderTest extends ActorTest {
class VehicleSpawnPadObjectBuilderTest extends ActorTest {
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
"TerminalObjectBuilder" should {
"Vehicle spawn pad object" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1,
@ -131,4 +151,3 @@ object ServerObjectBuilderTest {
}
}
}