mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
mechanism for server-driven emp caused by projectiles with emp properties; finalization of geometry elements and tests for geometric tests
This commit is contained in:
parent
e41e7e7cfa
commit
6e81ee7e95
|
|
@ -5252,8 +5252,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
|
||||
CheckForHitPositionDiscrepancy(projectile_guid, target.Position, target)
|
||||
ResolveProjectileInteraction(projectile, resolution1, target, target.Position) match {
|
||||
case Some(projectile) =>
|
||||
HandleDealingDamage(target, projectile)
|
||||
case Some(_projectile) =>
|
||||
HandleDealingDamage(target, _projectile)
|
||||
case None => ;
|
||||
}
|
||||
case _ => ;
|
||||
|
|
@ -5264,13 +5264,25 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
|
||||
CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
|
||||
ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos) match {
|
||||
case Some(projectile) =>
|
||||
HandleDealingDamage(target, projectile)
|
||||
case Some(_projectile) =>
|
||||
HandleDealingDamage(target, _projectile)
|
||||
case None => ;
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
})
|
||||
if (
|
||||
projectile.profile.HasJammedEffectDuration ||
|
||||
projectile.profile.JammerProjectile ||
|
||||
projectile.profile.SympatheticExplosion
|
||||
) {
|
||||
Zone.causeSpecialEmp(
|
||||
continent,
|
||||
player,
|
||||
explosion_pos,
|
||||
GlobalDefinitions.special_emp.innateDamage.get
|
||||
)
|
||||
}
|
||||
if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
|
||||
//cleanup
|
||||
val localIndex = projectile_guid.guid - Projectile.baseUID
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D
|
|||
mine,
|
||||
DamageInteraction(
|
||||
SourceEntry(mine),
|
||||
TriggerUsedReason(PlayerSource(player), trigger),
|
||||
TriggerUsedReason(PlayerSource(player), trigger.GUID),
|
||||
mine.Position
|
||||
).calculate()(mine),
|
||||
damage = 0
|
||||
|
|
@ -239,6 +239,10 @@ object ExplosiveDeployableControl {
|
|||
val scalar = Vector3.ScalarProjection(dir, up)
|
||||
val point1 = g1.pointOnOutside(dir).asVector3
|
||||
val point2 = g2.pointOnOutside(Vector3.neg(dir)).asVector3
|
||||
(scalar >= 0 || Vector3.MagnitudeSquared(up * scalar) < 0.35f) && Vector3.DistanceSquared(point1, point2) <= maxDistance
|
||||
(scalar >= 0 || Vector3.MagnitudeSquared(up * scalar) < 0.35f) &&
|
||||
math.min(
|
||||
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3),
|
||||
Vector3.DistanceSquared(point1, point2)
|
||||
) <= maxDistance
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -972,6 +972,8 @@ object GlobalDefinitions {
|
|||
|
||||
val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
|
||||
|
||||
val special_emp = ExplosiveDeployableDefinition(DeployedItem.jammer_mine)
|
||||
|
||||
//this is only treated like a deployable
|
||||
val internal_router_telepad_deployable = InternalTelepadDefinition() //objectId: 744
|
||||
init_deployables()
|
||||
|
|
@ -7293,6 +7295,22 @@ object GlobalDefinitions {
|
|||
internal_router_telepad_deployable.DeployTime = Duration.create(1, "ms")
|
||||
internal_router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
|
||||
internal_router_telepad_deployable.Packet = new InternalTelepadDeployableConverter
|
||||
|
||||
special_emp.Name = "emp"
|
||||
special_emp.MaxHealth = 1
|
||||
special_emp.Damageable = false
|
||||
special_emp.Repairable = false
|
||||
special_emp.DeployCategory = DeployableCategory.Mines
|
||||
special_emp.explodes = true
|
||||
special_emp.innateDamage = new DamageWithPosition {
|
||||
CausesDamageType = DamageType.Splash
|
||||
SympatheticExplosion = true
|
||||
Damage0 = 0
|
||||
DamageAtEdge = 1.0f
|
||||
DamageRadius = 5f
|
||||
AdditionalEffect = true
|
||||
Modifiers = MaxDistanceCutoff
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -7844,12 +7862,11 @@ object GlobalDefinitions {
|
|||
generator.innateDamage = new DamageWithPosition {
|
||||
CausesDamageType = DamageType.One
|
||||
Damage0 = 99999
|
||||
//DamageRadius should be 14, but 14 is insufficient for hitting the whole chamber; hence, ...
|
||||
DamageRadius = 15.75f
|
||||
DamageRadiusMin = 14
|
||||
DamageRadius = 14.5f
|
||||
DamageAtEdge = 0.00002f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
//damage is 99999 at 14m, dropping rapidly to ~1 at 15.75m
|
||||
//damage is 99999 at 14m, dropping rapidly to ~1 at 14.5m
|
||||
}
|
||||
generator.Geometry = GeometryForm.representByCylinder(radius = 1.2617f, height = 9.14063f)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import net.psforever.types.Vector3
|
|||
object Geometry {
|
||||
def equalFloats(value1: Float, value2: Float, off: Float = 0.001f): Boolean = {
|
||||
val diff = value1 - value2
|
||||
(diff >= 0 && diff <= off) || diff > -off
|
||||
if (diff >= 0) diff <= off else diff > -off
|
||||
}
|
||||
|
||||
def equalVectors(value1: Vector3, value2: Vector3, off: Float = 0.001f): Boolean = {
|
||||
|
|
|
|||
|
|
@ -3,11 +3,22 @@ package net.psforever.objects.geometry
|
|||
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
/**
|
||||
* Basic interface for all geometry.
|
||||
*/
|
||||
trait PrimitiveGeometry {
|
||||
/**
|
||||
* The centroid of the geometry.
|
||||
* @return a point
|
||||
*/
|
||||
def center: Point
|
||||
|
||||
def pointOnOutside(line: Line) : Point = pointOnOutside(line.d)
|
||||
|
||||
/**
|
||||
* Find a point on the exterior of the geometry if a line was drawn outwards from the centroid.
|
||||
* What counts as "the exterior" is limited to the complexity of the geometry.
|
||||
* @param v the vector in the direction of the point on the exterior
|
||||
* @return a point
|
||||
*/
|
||||
def pointOnOutside(v: Vector3) : Point
|
||||
}
|
||||
|
||||
|
|
@ -17,43 +28,103 @@ trait PrimitiveGeometry {
|
|||
// def pointOnOutside(v: Vector3): Point2D = center
|
||||
//}
|
||||
|
||||
/**
|
||||
* Basic interface of all three-dimensional geometry.
|
||||
* For the only real requirement for a hree-dimensional geometric figure is that it has three components of position
|
||||
* and an equal number of components demonstrating equal that said dimensionality.
|
||||
*/
|
||||
trait Geometry3D extends PrimitiveGeometry {
|
||||
def center: Point3D
|
||||
|
||||
def pointOnOutside(v: Vector3): Point3D = center
|
||||
}
|
||||
|
||||
/**
|
||||
* Characteristics of a geometric figure with only three coordinates to define a position.
|
||||
*/
|
||||
trait Point {
|
||||
/**
|
||||
* Transform the point into the common interchangeable format for coordinates.
|
||||
* They're very similar, anyway.
|
||||
* @return a `Vector3` entity of the same denomination
|
||||
*/
|
||||
def asVector3: Vector3
|
||||
}
|
||||
|
||||
/**
|
||||
* Characteristics of a geometric figure defining a direction or a progressive change in coordinates.
|
||||
*/
|
||||
trait Slope {
|
||||
/**
|
||||
* The slope itself.
|
||||
* @return a `Vector3` entity
|
||||
*/
|
||||
def d: Vector3
|
||||
|
||||
/**
|
||||
* How long the slope goes on for.
|
||||
* @return The length of the slope
|
||||
*/
|
||||
def length: Float
|
||||
}
|
||||
|
||||
object Slope {
|
||||
/**
|
||||
* On occasions, the defined slope should have a length of one unit.
|
||||
* It is a unit vector.
|
||||
* @param v the input slope as a `Vector3` entity
|
||||
* @throws `AssertionError` if the length is more or less than 1.
|
||||
*/
|
||||
def assertUnitVector(v: Vector3): Unit = {
|
||||
assert({
|
||||
val mag = Vector3.Magnitude(v)
|
||||
mag - 0.05f < 1f && mag + 0.05f > 1f
|
||||
}, "not a unit vector")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Characteristics of a geometric figure indicating an infinite slope - a mathematical line.
|
||||
* The slope is always a unit vector.
|
||||
* The point that assists to define the line is a constraint that the line must pass through.
|
||||
*/
|
||||
trait Line extends Slope {
|
||||
assert({
|
||||
val mag = Vector3.Magnitude(d)
|
||||
mag - 0.05f < 1f && mag + 0.05f > 1f
|
||||
}, "not a unit vector")
|
||||
Slope.assertUnitVector(d)
|
||||
|
||||
def p: Point
|
||||
|
||||
/**
|
||||
* The length of a mathematical line is infinite.
|
||||
* @return The length of the slope
|
||||
*/
|
||||
def length: Float = Float.PositiveInfinity
|
||||
}
|
||||
|
||||
/**
|
||||
* Characteristics of a geometric figure that have two endpoints, defining a fixed-length slope.
|
||||
*/
|
||||
trait Segment extends Slope {
|
||||
/** The first point, considered the "start". */
|
||||
def p1: Point
|
||||
|
||||
/** The second point, considered the "end". */
|
||||
def p2: Point
|
||||
|
||||
def length: Float = Vector3.Magnitude(d)
|
||||
|
||||
/**
|
||||
* Transform the segment into a matheatical line of the same slope.
|
||||
* @return
|
||||
*/
|
||||
def asLine: PrimitiveGeometry
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of a geometric coordinate position.
|
||||
* @see `Vector3`
|
||||
* @param x the 'x' coordinate of the position
|
||||
* @param y the 'y' coordinate of the position
|
||||
* @param z the 'z' coordinate of the position
|
||||
*/
|
||||
final case class Point3D(x: Float, y: Float, z: Float) extends Geometry3D with Point {
|
||||
def center: Point3D = this
|
||||
|
||||
|
|
@ -61,39 +132,119 @@ final case class Point3D(x: Float, y: Float, z: Float) extends Geometry3D with P
|
|||
}
|
||||
|
||||
object Point3D {
|
||||
/**
|
||||
* An overloaded constructor that assigns world origin coordinates.
|
||||
* @return a `Point3D` entity
|
||||
*/
|
||||
def apply(): Point3D = Point3D(0,0,0)
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses the same coordinates from a `Vector3` entity.
|
||||
* @param v the entity with the corresponding points
|
||||
* @return a `Point3D` entity
|
||||
*/
|
||||
def apply(v: Vector3): Point3D = Point3D(v.x, v.y, v.z)
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of a geometric coordinate position and a specific direction from that position.
|
||||
* Rays are like mathematical lines in that they have infinite length;
|
||||
* but, that infinite length is only expressed in a single direction,
|
||||
* rather than proceeding in both a direction and its opposite direction from a target point.
|
||||
* Infinity just be like that.
|
||||
* Additionally, the point is not merely any point on the ray used to assist defining it
|
||||
* and is instead considered the clearly-defined origin of the ray.
|
||||
* @param p the point of origin
|
||||
* @param d the direction
|
||||
*/
|
||||
final case class Ray3D(p: Point3D, d: Vector3) extends Geometry3D with Line {
|
||||
def center: Point3D = p
|
||||
}
|
||||
|
||||
object Ray3D {
|
||||
/**
|
||||
* An overloaded constructor that uses individual coordinates.
|
||||
* @param x the 'x' coordinate of the position
|
||||
* @param y the 'y' coordinate of the position
|
||||
* @param z the 'z' coordinate of the position
|
||||
* @param d the direction
|
||||
* @return a `Ray3D` entity
|
||||
*/
|
||||
def apply(x: Float, y: Float, z: Float, d: Vector3): Ray3D = Ray3D(Point3D(x,y,z), d)
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses a `Vector3` entity to express coordinates.
|
||||
* @param v the coordinates of the position
|
||||
* @param d the direction
|
||||
* @return a `Ray3D` entity
|
||||
*/
|
||||
def apply(v: Vector3, d: Vector3): Ray3D = Ray3D(Point3D(v.x, v.y, v.z), d)
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of a geometric coordinate position and a specific direction from that position.
|
||||
* Mathematical lines have infinite length and their slope is represented as a unit vector.
|
||||
* The point is merely a point used to assist in defining the line.
|
||||
* @param p the point of origin
|
||||
* @param d the direction
|
||||
*/
|
||||
final case class Line3D(p: Point3D, d: Vector3) extends Geometry3D with Line {
|
||||
def center: Point3D = p
|
||||
}
|
||||
|
||||
object Line3D {
|
||||
/**
|
||||
* An overloaded constructor that uses individual coordinates.
|
||||
* @param x the 'x' coordinate of the position
|
||||
* @param y the 'y' coordinate of the position
|
||||
* @param z the 'z' coordinate of the position
|
||||
* @param d the direction
|
||||
* @return a `Line3D` entity
|
||||
*/
|
||||
def apply(x: Float, y: Float, z: Float, d: Vector3): Line3D = {
|
||||
Line3D(Point3D(x,y,z), d)
|
||||
}
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses a pair of individual coordinates
|
||||
* and uses their difference to produce a unit vector to define a direction.
|
||||
* @param ax the 'x' coordinate of the position
|
||||
* @param ay the 'y' coordinate of the position
|
||||
* @param az the 'z' coordinate of the position
|
||||
* @param bx the 'x' coordinate of a destination position
|
||||
* @param by the 'y' coordinate of a destination position
|
||||
* @param bz the 'z' coordinate of a destination position
|
||||
* @return a `Line3D` entity
|
||||
*/
|
||||
def apply(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float): Line3D = {
|
||||
Line3D(Point3D(ax, ay, az), Vector3.Unit(Vector3(bx-ax, by-ay, bz-az)))
|
||||
}
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses a pair of points
|
||||
* and uses their difference to produce a unit vector to define a direction.
|
||||
* @param p1 the coordinates of the position
|
||||
* @param p2 the coordinates of a destination position
|
||||
* @return a `Line3D` entity
|
||||
*/
|
||||
def apply(p1: Point3D, p2: Point3D): Line3D = {
|
||||
Line3D(p1, Vector3.Unit(Vector3(p2.x-p1.x, p2.y-p1.y, p2.z-p1.z)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of a limited span between two geometric coordinate positions, called "endpoints".
|
||||
* Unlike mathematical lines, slope is treated the same as the vector leading from one point to the other
|
||||
* and is the length of the segment.
|
||||
* @param p1 a point
|
||||
* @param p2 another point
|
||||
*/
|
||||
final case class Segment3D(p1: Point3D, p2: Point3D) extends Geometry3D with Segment {
|
||||
def center: Point3D = Point3D(d * 0.5f)
|
||||
/**
|
||||
* The center point of a segment is a position that is equally in between both endpoints.
|
||||
* @return a point
|
||||
*/
|
||||
def center: Point3D = Point3D((p2.asVector3 + p1.asVector3) * 0.5f)
|
||||
|
||||
def d: Vector3 = p2.asVector3 - p1.asVector3
|
||||
|
||||
|
|
@ -101,65 +252,182 @@ final case class Segment3D(p1: Point3D, p2: Point3D) extends Geometry3D with Seg
|
|||
}
|
||||
|
||||
object Segment3D {
|
||||
/**
|
||||
* An overloaded constructor that uses a pair of individual coordinates
|
||||
* and uses their difference to define a direction.
|
||||
* @param ax the 'x' coordinate of the position
|
||||
* @param ay the 'y' coordinate of the position
|
||||
* @param az the 'z' coordinate of the position
|
||||
* @param bx the 'x' coordinate of a destination position
|
||||
* @param by the 'y' coordinate of a destination position
|
||||
* @param bz the 'z' coordinate of a destination position
|
||||
* @return a `Segment3D` entity
|
||||
*/
|
||||
def apply(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float): Segment3D = {
|
||||
Segment3D(Point3D(ax, ay, az), Point3D(bx, by, bz))
|
||||
}
|
||||
|
||||
/**
|
||||
* An overloaded constructor.
|
||||
* @param p the point of origin
|
||||
* @param d the direction and distance (of the second point)
|
||||
*/
|
||||
def apply(p: Point3D, d: Vector3): Segment3D = {
|
||||
Segment3D(p, Point3D(p.x + d.x, p.y + d.y, p.z + d.z))
|
||||
}
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses individual coordinates.
|
||||
* @param x the 'x' coordinate of the position
|
||||
* @param y the 'y' coordinate of the position
|
||||
* @param z the 'z' coordinate of the position
|
||||
* @param d the direction
|
||||
* @return a `Segment3D` entity
|
||||
*/
|
||||
def apply(x: Float, y: Float, z: Float, d: Vector3): Segment3D = {
|
||||
Segment3D(Point3D(x, y, z), Point3D(x + d.x, y + d.y, z + d.z))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of a volumetric region that encapsulates all points within a certain distance of a central point.
|
||||
* (That's what a sphere is.)
|
||||
* A sphere has no real "top", "base", or "side" as all directions are described the same.
|
||||
* @param p the point
|
||||
* @param radius a distance that spans all points in any direction from the central point
|
||||
*/
|
||||
final case class Sphere(p: Point3D, radius: Float) extends Geometry3D {
|
||||
def center: Point3D = p
|
||||
|
||||
/**
|
||||
* Find a point on the exterior of the geometry if a line was drawn outwards from the centroid.
|
||||
* All points that exist on the exterior of a sphere are on the surface of that sphere
|
||||
* and are equally distant from the central point.
|
||||
* @param v the vector in the direction of the point on the exterior
|
||||
* @return a point
|
||||
*/
|
||||
override def pointOnOutside(v: Vector3): Point3D = {
|
||||
val slope = Vector3.Unit(v)
|
||||
val mult = radius / math.sqrt(slope.x * slope.x + slope.y * slope.y + slope.z * slope.z)
|
||||
val pointOnSurface = center.asVector3 + slope * mult.toFloat
|
||||
Point3D(pointOnSurface.x, pointOnSurface.y, pointOnSurface.z)
|
||||
val mult = radius / Vector3.Magnitude(slope)
|
||||
Point3D(center.asVector3 + slope * mult)
|
||||
}
|
||||
}
|
||||
|
||||
object Sphere {
|
||||
/**
|
||||
* An overloaded constructor that only defines the radius of the sphere
|
||||
* and places it at the world origin.
|
||||
* @param radius a distance around the world origin coordinates
|
||||
* @return a `Sphere` entity
|
||||
*/
|
||||
def apply(radius: Float): Sphere = Sphere(Point3D(), radius)
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses individual coordinates to define the central point.
|
||||
* * @param x the 'x' coordinate of the position
|
||||
* * @param y the 'y' coordinate of the position
|
||||
* * @param z the 'z' coordinate of the position
|
||||
* @param radius a distance around the world origin coordinates
|
||||
* @return a `Sphere` entity
|
||||
*/
|
||||
def apply(x: Float, y: Float, z: Float, radius: Float): Sphere = Sphere(Point3D(x,y,z), radius)
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses vector coordinates to define the central point.
|
||||
* @param v the coordinates of the position
|
||||
* @param radius a distance around the world origin coordinates
|
||||
* @return a `Sphere` entity
|
||||
*/
|
||||
def apply(v: Vector3, radius: Float): Sphere = Sphere(Point3D(v), radius)
|
||||
}
|
||||
|
||||
final case class Cylinder(position: Vector3, relativeUp: Vector3, radius: Float, height: Float) extends Geometry3D {
|
||||
def center: Point3D = Point3D(position + relativeUp * height * 0.5f)
|
||||
/**
|
||||
* The instance of a volumetric region that encapsulates all points within a certain distance of a central point.
|
||||
* The region is characterized by a regular circular cross-section when observed from above or below
|
||||
* and a flat top and a flat base when viewed from the side.
|
||||
* The "base" is where the origin point is defined (at the center of a circular cross-section)
|
||||
* and the "top" is discovered a `height` from the base along what the cylinder considers its `relativeUp` direction.
|
||||
* @param p the point
|
||||
* @param relativeUp what the cylinder considers its "up" direction
|
||||
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
|
||||
* @param height the distance between the "base" and the "top"
|
||||
*/
|
||||
final case class Cylinder(p: Point3D, relativeUp: Vector3, radius: Float, height: Float) extends Geometry3D {
|
||||
Slope.assertUnitVector(relativeUp)
|
||||
|
||||
/**
|
||||
* The center point of a cylinder is halfway between the "top" and the "base" along the direction of `relativeUp`.
|
||||
* @return a point
|
||||
*/
|
||||
def center: Point3D = Point3D(p.asVector3 + relativeUp * height * 0.5f)
|
||||
|
||||
/**
|
||||
* Find a point on the exterior of the geometry if a line was drawn outwards from the centroid.
|
||||
* A cylinder is composed of three clearly-defined regions on its exterior -
|
||||
* two flat but circular surfaces that are the "top" and the "base"
|
||||
* and a wrapped "sides" surface that defines all points connecting the "base" to the "top"
|
||||
* along the `relativeUp` direction.
|
||||
* The requested point may exist on any of these surfaces.
|
||||
* @param v the vector in the direction of the point on the exterior
|
||||
* @return a point
|
||||
*/
|
||||
override def pointOnOutside(v: Vector3): Point3D = {
|
||||
val centerVector = center.asVector3
|
||||
val slope = Vector3.Unit(v)
|
||||
val acrossTopAndBase = slope - relativeUp
|
||||
val pointOnSide = centerVector + slope * (radius / Vector3.Magnitude(acrossTopAndBase))
|
||||
val pointOnBase = position + acrossTopAndBase * radius
|
||||
val pointOnTop = pointOnBase + relativeUp * height
|
||||
val fromPointOnTopToSide = Vector3.Unit(pointOnTop - pointOnSide)
|
||||
val fromPointOnSideToBase = Vector3.Unit(pointOnSide - pointOnBase)
|
||||
val target = if(fromPointOnTopToSide == Vector3.Zero ||
|
||||
fromPointOnSideToBase == Vector3.Zero ||
|
||||
Geometry.equalVectors(fromPointOnTopToSide, fromPointOnSideToBase)) {
|
||||
//on side, including top rim or base rim
|
||||
pointOnSide
|
||||
val dotProdOfSlopeAndUp = Vector3.DotProduct(slope, relativeUp)
|
||||
if (Geometry.equalFloats(dotProdOfSlopeAndUp, value2 = 1) || Geometry.equalFloats(dotProdOfSlopeAndUp, value2 = -1)) {
|
||||
// very rare condition: 'slope' and 'relativeUp' are parallel or antiparallel
|
||||
Point3D(centerVector + slope * height * 0.5f)
|
||||
} else {
|
||||
//on top or base
|
||||
// the full equation would be 'centerVector + slope * (height * 0.5f / Vector3.Magnitude(relativeUp))'
|
||||
// 'relativeUp` is already a unit vector (magnitude of 1)
|
||||
centerVector + slope * height * 0.5f
|
||||
val acrossTopAndBase = slope - relativeUp * dotProdOfSlopeAndUp
|
||||
val pointOnSide = centerVector + slope * (radius / Vector3.Magnitude(acrossTopAndBase))
|
||||
val pointOnBase = p.asVector3 + acrossTopAndBase * radius
|
||||
val pointOnTop = pointOnBase + relativeUp * height
|
||||
val fromPointOnTopToSide = Vector3.Unit(pointOnTop - pointOnSide)
|
||||
val fromPointOnSideToBase = Vector3.Unit(pointOnSide - pointOnBase)
|
||||
val target = if(Geometry.equalVectors(fromPointOnTopToSide, Vector3.Zero) ||
|
||||
Geometry.equalVectors(fromPointOnSideToBase, Vector3.Zero) ||
|
||||
Geometry.equalVectors(fromPointOnTopToSide, fromPointOnSideToBase)) {
|
||||
//on side, including top rim or base rim
|
||||
pointOnSide
|
||||
} else {
|
||||
//on top or base
|
||||
// the full equation would be 'centerVector + slope * (height * 0.5f / Vector3.Magnitude(relativeUp))'
|
||||
// 'relativeUp` is already a unit vector (magnitude of 1)
|
||||
centerVector + slope * height * 0.5f
|
||||
}
|
||||
Point3D(target)
|
||||
}
|
||||
Point3D(target)
|
||||
}
|
||||
}
|
||||
|
||||
object Cylinder {
|
||||
def apply(v: Vector3, radius: Float, height: Float): Cylinder = Cylinder(v, Vector3(0,0,1), radius, height)
|
||||
/**
|
||||
* An overloaded constructor where the 'relativeUp' of the cylinder is perpendicular to the xy-plane.
|
||||
* @param p the point
|
||||
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
|
||||
* @param height the distance between the "base" and the "top"
|
||||
* @return
|
||||
*/
|
||||
def apply(p: Point3D, radius: Float, height: Float): Cylinder = Cylinder(p, Vector3(0,0,1), radius, height)
|
||||
|
||||
def apply(p: Point3D, radius: Float, height: Float): Cylinder = Cylinder(p.asVector3, Vector3(0,0,1), radius, height)
|
||||
/**
|
||||
* An overloaded constructor where the origin point is expressed as a vector
|
||||
* and the 'relativeUp' of the cylinder is perpendicular to the xy-plane.
|
||||
* @param p the point
|
||||
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
|
||||
* @param height the distance between the "base" and the "top"
|
||||
* @return
|
||||
*/
|
||||
def apply(p: Vector3, radius: Float, height: Float): Cylinder = Cylinder(Point3D(p), Vector3(0,0,1), radius, height)
|
||||
|
||||
def apply(p: Point3D, v: Vector3, radius: Float, height: Float): Cylinder = Cylinder(p.asVector3, v, radius, height)
|
||||
/**
|
||||
* An overloaded constructor the origin point is expressed as a vector.
|
||||
* @param p the point
|
||||
* @param v what the cylinder considers its "up" direction
|
||||
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
|
||||
* @param height the distance between the "base" and the "top"
|
||||
* @return
|
||||
*/
|
||||
def apply(p: Vector3, v: Vector3, radius: Float, height: Float): Cylinder = Cylinder(Point3D(p), v, radius, height)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.vital.etc
|
||||
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||
import net.psforever.objects.vital.resolution.DamageAndResistance
|
||||
|
||||
/**
|
||||
* A wrapper for a "damage source" in damage calculations
|
||||
* that parameterizes information necessary to explain a server-driven electromagnetic pulse occurring.
|
||||
* @see `VitalityDefinition.explodes`
|
||||
* @see `VitalityDefinition.innateDamage`
|
||||
* @see `Zone.causesSpecialEmp`
|
||||
* @param entity the source of the explosive yield
|
||||
* @param damageModel the model to be utilized in these calculations;
|
||||
* typically, but not always, defined by the target
|
||||
*/
|
||||
final case class EmpReason(
|
||||
entity: SourceEntry,
|
||||
source: DamageWithPosition,
|
||||
damageModel: DamageAndResistance,
|
||||
override val attribution: Int
|
||||
) extends DamageReason {
|
||||
def resolution: DamageResolution.Value = DamageResolution.Splash
|
||||
|
||||
def same(test: DamageReason): Boolean = test match {
|
||||
case eer: ExplodingEntityReason => eer.entity eq entity
|
||||
case _ => false
|
||||
}
|
||||
|
||||
/** lay the blame on that which caused this emp to occur */
|
||||
def adversary: Option[SourceEntry] = Some(entity)
|
||||
}
|
||||
|
||||
object EmpReason {
|
||||
def apply(
|
||||
owner: PlanetSideGameObject with FactionAffinity,
|
||||
source: DamageWithPosition,
|
||||
target: PlanetSideServerObject with Vitality
|
||||
): EmpReason = {
|
||||
EmpReason(SourceEntry(owner), source, target.DamageModel, owner.Definition.ObjectId)
|
||||
}
|
||||
}
|
||||
|
|
@ -73,12 +73,12 @@ case object ExplodingRadialDegrade extends ExplodingDamageModifiers.Mod {
|
|||
def calculate(damage: Int, data: DamageInteraction, cause: ExplodingEntityReason): Int = {
|
||||
cause.source match {
|
||||
case withPosition: DamageWithPosition =>
|
||||
val distance = math.sqrt(Zone.distanceCheck(
|
||||
val radius = withPosition.DamageRadius
|
||||
val radiusMin = withPosition.DamageRadiusMin
|
||||
val distance = math.sqrt(Zone.distanceCheck(
|
||||
cause.entity.Definition.asInstanceOf[ObjectDefinition].Geometry(cause.entity),
|
||||
data.target.Definition.Geometry(data.target)
|
||||
))
|
||||
val radius = withPosition.DamageRadius
|
||||
val radiusMin = withPosition.DamageRadiusMin
|
||||
if (distance <= radiusMin) {
|
||||
damage
|
||||
} else if (distance <= radius) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital.etc
|
||||
|
||||
import net.psforever.objects.BoomerTrigger
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
|
||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.damage.DamageCalculations.AgainstExoSuit
|
||||
import net.psforever.objects.vital.prop.DamageProperties
|
||||
import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
|
||||
/**
|
||||
* A wrapper for a "damage source" in damage calculations
|
||||
|
|
@ -23,16 +24,16 @@ import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResist
|
|||
* @see `DamageCalculations`
|
||||
* @see `VitalityDefinition.DamageableByFriendlyFire`
|
||||
* @param user the player who is holding the trigger
|
||||
* @param item the trigger
|
||||
* @param item_guid the trigger
|
||||
*/
|
||||
final case class TriggerUsedReason(user: PlayerSource, item: BoomerTrigger)
|
||||
final case class TriggerUsedReason(user: PlayerSource, item_guid: PlanetSideGUID)
|
||||
extends DamageReason {
|
||||
def source: DamageProperties = TriggerUsedReason.triggered
|
||||
|
||||
def resolution: DamageResolution.Value = DamageResolution.Resolved
|
||||
|
||||
def same(test: DamageReason): Boolean = test match {
|
||||
case tur: TriggerUsedReason => tur.item eq item
|
||||
case tur: TriggerUsedReason => tur.item_guid == item_guid && tur.user.Name.equals(user.Name)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ final case class TriggerUsedReason(user: PlayerSource, item: BoomerTrigger)
|
|||
|
||||
/** while weird, the trigger was accredited as the method of death on Gemini Live;
|
||||
* even though its icon looks like an misshapen AMS */
|
||||
override def attribution: Int = item.Definition.ObjectId
|
||||
override def attribution: Int = GlobalDefinitions.boomer_trigger.ObjectId
|
||||
}
|
||||
|
||||
object TriggerUsedReason {
|
||||
|
|
|
|||
|
|
@ -42,9 +42,10 @@ import net.psforever.objects.geometry.Geometry3D
|
|||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.vehicles.UtilityType
|
||||
import net.psforever.objects.vital.etc.ExplodingEntityReason
|
||||
import net.psforever.objects.vital.etc.{EmpReason, ExplodingEntityReason}
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||
|
||||
/**
|
||||
* A server object representing the one-landmass planets as well as the individual subterranean caverns.<br>
|
||||
|
|
@ -1172,6 +1173,64 @@ object Zone {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates `Damageable` targets within the radius of a server-prepared electromagnetic pulse
|
||||
* and informs those entities that they have affected by the aforementioned pulse.
|
||||
* Targets within the effect radius within other rooms are affected, unlike with normal damage.
|
||||
* The only affected target is Boomer deployables.
|
||||
* @see `Amenity.Owner`
|
||||
* @see `BoomerDeployable`
|
||||
* @see `DamageInteraction`
|
||||
* @see `DamageResult`
|
||||
* @see `DamageWithPosition`
|
||||
* @see `EmpReason`
|
||||
* @see `Zone.DeployableList`
|
||||
* @param zone the zone in which the emp should occur
|
||||
* @param obj the entity that triggered the emp (information)
|
||||
* @param sourcePosition where the emp physically originates
|
||||
* @param effect characteristics of the emp produced
|
||||
* @param detectionTest a custom test to determine if any given target is affected;
|
||||
* defaults to an internal test for simple radial proximity
|
||||
* @return a list of affected entities
|
||||
*/
|
||||
def causeSpecialEmp(
|
||||
zone: Zone,
|
||||
obj: PlanetSideServerObject with Vitality,
|
||||
sourcePosition: Vector3,
|
||||
effect: DamageWithPosition,
|
||||
detectionTest: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = distanceCheck
|
||||
): List[PlanetSideServerObject] = {
|
||||
val proxy: ExplosiveDeployable = {
|
||||
//construct a proxy unit to represent the pulse
|
||||
val o = new ExplosiveDeployable(GlobalDefinitions.special_emp)
|
||||
o.Owner = Some(obj.GUID)
|
||||
o.OwnerName = obj match {
|
||||
case p: Player => p.Name
|
||||
case o: OwnableByPlayer => o.OwnerName.getOrElse("")
|
||||
case _ => ""
|
||||
}
|
||||
o.Position = sourcePosition
|
||||
o.Faction = obj.Faction
|
||||
o
|
||||
}
|
||||
val radius = effect.DamageRadius * effect.DamageRadius
|
||||
//only boomers can be affected (that's why it's special)
|
||||
val allAffectedTargets = zone.DeployableList
|
||||
.collect { case o: BoomerDeployable if !o.Destroyed && (o ne obj) && detectionTest(proxy, o, radius) => o }
|
||||
//inform targets that they have suffered the effects of the emp
|
||||
allAffectedTargets
|
||||
.foreach { target =>
|
||||
target.Actor ! Vitality.Damage(
|
||||
DamageInteraction(
|
||||
SourceEntry(target),
|
||||
EmpReason(obj, effect, target),
|
||||
sourcePosition
|
||||
).calculate()
|
||||
)
|
||||
}
|
||||
allAffectedTargets
|
||||
}
|
||||
|
||||
/**
|
||||
* Two game entities are considered "near" each other if they are within a certain distance of one another.
|
||||
* A default function literal mainly used for `causesExplosion`.
|
||||
|
|
@ -1197,6 +1256,7 @@ object Zone {
|
|||
* `false`, otherwise
|
||||
*/
|
||||
def distanceCheck(g1: Geometry3D, g2: Geometry3D, maxDistance: Float): Boolean = {
|
||||
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3) <= maxDistance ||
|
||||
distanceCheck(g1, g2) <= maxDistance
|
||||
}
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -364,7 +364,7 @@ object PointOfInterest {
|
|||
"anguta" -> Vector3(3999, 4170, 266),
|
||||
"igaluk" -> Vector3(3241, 5658, 235),
|
||||
"keelut" -> Vector3(3630, 1904, 265),
|
||||
"nerrivik" -> Vector3(3522, 3703, 322),
|
||||
"nerrivik" -> Vector3(3522, 3703, 222),
|
||||
"pinga" -> Vector3(5938, 3545, 96),
|
||||
"sedna" -> Vector3(3932, 5160, 232),
|
||||
"tarqaq" -> Vector3(2980, 2155, 237),
|
||||
|
|
|
|||
244
src/test/scala/objects/GeometryTest.scala
Normal file
244
src/test/scala/objects/GeometryTest.scala
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package objects
|
||||
|
||||
import net.psforever.objects.geometry._
|
||||
import net.psforever.types.Vector3
|
||||
import org.specs2.mutable.Specification
|
||||
|
||||
class GeometryTest extends Specification {
|
||||
"Point3D" should {
|
||||
"construct (1)" in {
|
||||
Point3D(1,2,3.5f)
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Point3D() mustEqual Point3D(0,0,0)
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Point3D(Vector3(1,2,3)) mustEqual Point3D(1,2,3)
|
||||
}
|
||||
|
||||
"be its own center point" in {
|
||||
val obj = Point3D(1,2,3.5f)
|
||||
obj.center mustEqual obj
|
||||
}
|
||||
|
||||
"define its own exterior" in {
|
||||
val obj = Point3D(1,2,3.5f)
|
||||
obj.pointOnOutside(Vector3(1,0,0)) mustEqual obj
|
||||
obj.pointOnOutside(Vector3(0,1,0)) mustEqual obj
|
||||
obj.pointOnOutside(Vector3(0,0,1)) mustEqual obj
|
||||
}
|
||||
|
||||
"convert to Vector3" in {
|
||||
val obj = Point3D(1,2,3.5f)
|
||||
obj.asVector3 mustEqual Vector3(1,2,3.5f)
|
||||
}
|
||||
}
|
||||
|
||||
"Ray3D" should {
|
||||
"construct (1)" in {
|
||||
Ray3D(Point3D(1,2,3.5f), Vector3(1,0,0))
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Ray3D(1,2,3.5f, Vector3(1,0,0)) mustEqual Ray3D(Point3D(1,2,3.5f), Vector3(1,0,0))
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Ray3D(Vector3(1,2,3.5f), Vector3(1,0,0)) mustEqual Ray3D(Point3D(1,2,3.5f), Vector3(1,0,0))
|
||||
}
|
||||
|
||||
"have a unit vector as its direction vector" in {
|
||||
Ray3D(1,2,3.5f, Vector3(1,1,1)) must throwA[AssertionError]
|
||||
}
|
||||
|
||||
"have its target point as the center point" in {
|
||||
val obj = Ray3D(1,2,3.5f, Vector3(1,0,0))
|
||||
obj.center mustEqual Point3D(1,2,3.5f)
|
||||
}
|
||||
|
||||
"define its own exterior" in {
|
||||
val obj1 = Ray3D(1,2,3.5f, Vector3(1,0,0))
|
||||
val obj2 = Point3D(1,2,3.5f)
|
||||
obj1.pointOnOutside(Vector3(1,0,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,1,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,0,1)) mustEqual obj2
|
||||
}
|
||||
}
|
||||
|
||||
"Line3D" should {
|
||||
"construct (1)" in {
|
||||
Line3D(Point3D(1,2,3.5f), Vector3(1,0,0))
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Line3D(1,2,3.5f, Vector3(1,0,0))
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Line3D(1,2,3.5f, 2,2,3.5f) mustEqual Line3D(1,2,3.5f, Vector3(1,0,0))
|
||||
}
|
||||
|
||||
"have a unit vector as its direction vector" in {
|
||||
Line3D(1,2,3.5f, Vector3(1,1,1)) must throwA[AssertionError]
|
||||
}
|
||||
|
||||
"have its target point as the center point" in {
|
||||
val obj = Line3D(1,2,3.5f, Vector3(1,0,0))
|
||||
obj.center mustEqual Point3D(1,2,3.5f)
|
||||
}
|
||||
|
||||
"define its own exterior" in {
|
||||
val obj1 = Line3D(1,2,3.5f, Vector3(1,0,0))
|
||||
val obj2 = Point3D(1,2,3.5f)
|
||||
obj1.pointOnOutside(Vector3(1,0,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,1,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,0,1)) mustEqual obj2
|
||||
}
|
||||
}
|
||||
|
||||
"Segment3D" should {
|
||||
"construct (1)" in {
|
||||
Segment3D(Point3D(1,2,3), Point3D(3,2,3))
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Segment3D(1,2,3, 3,2,3) mustEqual Segment3D(Point3D(1,2,3), Point3D(3,2,3))
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Segment3D(Point3D(1,2,3), Vector3(1,0,0)) mustEqual Segment3D(Point3D(1,2,3), Point3D(2,2,3))
|
||||
}
|
||||
|
||||
"construct (4)" in {
|
||||
Segment3D(1,2,3, Vector3(1,0,0)) mustEqual Segment3D(Point3D(1,2,3), Point3D(2,2,3))
|
||||
}
|
||||
|
||||
"does not need to have unit vector as its direction vector" in {
|
||||
val obj1 = Segment3D(1,2,3, Vector3(5,1,1))
|
||||
val obj2 = Segment3D(Point3D(1,2,3), Point3D(6,3,4))
|
||||
obj1 mustEqual obj2
|
||||
obj1.d mustEqual obj2.d
|
||||
}
|
||||
|
||||
"have a midway point between its two endpoints" in {
|
||||
Segment3D(Point3D(1,2,3), Point3D(3,4,5)).center mustEqual Point3D(2,3,4)
|
||||
}
|
||||
|
||||
"report the point on the outside as its center point" in {
|
||||
val obj1 = Segment3D(Point3D(1,2,3), Point3D(3,4,5))
|
||||
val obj2 = obj1.center
|
||||
obj1.pointOnOutside(Vector3(1,0,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,1,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,0,1)) mustEqual obj2
|
||||
}
|
||||
}
|
||||
|
||||
"Sphere3D" should {
|
||||
"construct (1)" in {
|
||||
Sphere(Point3D(1,2,3), 3)
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Sphere(3) mustEqual Sphere(Point3D(0,0,0), 3)
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Sphere(1,2,3, 3) mustEqual Sphere(Point3D(1,2,3), 3)
|
||||
}
|
||||
|
||||
"construct (4)" in {
|
||||
Sphere(Vector3(1,2,3), 3) mustEqual Sphere(Point3D(1,2,3), 3)
|
||||
}
|
||||
|
||||
"the center point is self-evident" in {
|
||||
Sphere(Point3D(1,2,3), 3).center mustEqual Point3D(1,2,3)
|
||||
}
|
||||
|
||||
"report the point on the outside depending on the requested direction" in {
|
||||
val obj1 = Sphere(1,2,3, 3)
|
||||
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustEqual Point3D( 4, 2,3) //east
|
||||
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustEqual Point3D( 1, 5,3) //north
|
||||
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustEqual Point3D( 1, 2,6) //up
|
||||
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustEqual Point3D(-2, 2,3) //west
|
||||
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustEqual Point3D( 1,-1,3) //south
|
||||
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustEqual Point3D( 1, 2,0) //down
|
||||
}
|
||||
}
|
||||
|
||||
"Cylinder (normal)" should {
|
||||
"construct (1)" in {
|
||||
Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Cylinder(Point3D(1,2,3), 2, 3) mustEqual Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Cylinder(Vector3(1,2,3), 2, 3) mustEqual Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
|
||||
}
|
||||
|
||||
"construct (4)" in {
|
||||
Cylinder(Vector3(1,2,3), Vector3(0,0,1), 2, 3) mustEqual Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
|
||||
}
|
||||
|
||||
"report the center point as the center of the cylinder" in {
|
||||
Cylinder(Point3D(1,2,3), 2, 3).center mustEqual Point3D(1,2,4.5f)
|
||||
}
|
||||
|
||||
"the point on the outside is different depending on the requested direction" in {
|
||||
val obj1 = Cylinder(Point3D(1,2,3), 2, 3)
|
||||
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustEqual Point3D( 3, 2, 4.5f) //east
|
||||
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustEqual Point3D( 1, 4, 4.5f) //north
|
||||
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustEqual Point3D( 1, 2, 6f) //up
|
||||
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustEqual Point3D(-1, 2, 4.5f) //west
|
||||
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustEqual Point3D( 1, 0, 4.5f) //south
|
||||
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustEqual Point3D( 1, 2, 3f) //down
|
||||
}
|
||||
}
|
||||
|
||||
"Cylinder (side tilt)" should {
|
||||
"not require a specific direction to be relative up" in {
|
||||
Cylinder(Point3D(1,2,3), Vector3(1,0,0), 2, 3)
|
||||
ok
|
||||
}
|
||||
|
||||
"require its specific relative up direction to be expressed as a unit vector" in {
|
||||
Cylinder(Point3D(1,2,3), Vector3(4,0,0), 2, 3) must throwA[AssertionError]
|
||||
}
|
||||
|
||||
"report the center point as the center of the cylinder, as if rotated about its base" in {
|
||||
Cylinder(Point3D(1,2,3), Vector3(1,0,0), 2, 3).center mustEqual Point3D(2.5f, 2, 3)
|
||||
}
|
||||
|
||||
"report the point on the outside as different depending on the requested direction and the relative up direction" in {
|
||||
val obj1 = Cylinder(Point3D(1,2,3), Vector3(1,0,0), 2, 3)
|
||||
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustEqual Point3D(4, 2, 3) //east
|
||||
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustEqual Point3D(2.5f, 4, 3) //north
|
||||
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustEqual Point3D(2.5f, 2, 5) //up
|
||||
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustEqual Point3D(1, 2, 3) //west
|
||||
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustEqual Point3D(2.5f, 0, 3) //south
|
||||
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustEqual Point3D(2.5f, 2, 1) //down
|
||||
|
||||
val obj2 = Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
|
||||
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustNotEqual obj2.pointOnOutside(Vector3( 1, 0, 0))
|
||||
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustNotEqual obj2.pointOnOutside(Vector3( 1, 1, 0))
|
||||
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustNotEqual obj2.pointOnOutside(Vector3( 1, 0, 1))
|
||||
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustNotEqual obj2.pointOnOutside(Vector3(-1, 0, 0))
|
||||
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustNotEqual obj2.pointOnOutside(Vector3( 0,-1, 0))
|
||||
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustNotEqual obj2.pointOnOutside(Vector3( 0, 0,-1))
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue