mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-20 02:24:45 +00:00
established geometry definitions for damageable object types; fixed rotations; removed unnecessary classes
This commit is contained in:
parent
fe386bd79b
commit
e41e7e7cfa
|
|
@ -7,6 +7,7 @@ import net.psforever.objects.ce._
|
|||
import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
|
||||
import net.psforever.objects.definition.converter.SmallDeployableConverter
|
||||
import net.psforever.objects.equipment.JammableUnit
|
||||
import net.psforever.objects.geometry.Geometry3D
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
|
||||
|
|
@ -118,11 +119,11 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D
|
|||
* `false`, otherwise
|
||||
*/
|
||||
def CanDetonate(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = {
|
||||
if (damage == 0 && data.cause.source.SympatheticExplosion) {
|
||||
!mine.Destroyed && (if (damage == 0 && data.cause.source.SympatheticExplosion) {
|
||||
Damageable.CanDamageOrJammer(mine, damage = 1, data)
|
||||
} else {
|
||||
Damageable.CanDamageOrJammer(mine, damage, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +170,7 @@ object ExplosiveDeployableControl {
|
|||
val zone = target.Zone
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause)
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.Detonate(target.GUID, target))
|
||||
Zone.causeExplosion(zone, target, Some(cause))
|
||||
Zone.causeExplosion(zone, target, Some(cause), ExplosiveDeployableControl.detectionForExplosiveSource(target))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -196,4 +197,48 @@ object ExplosiveDeployableControl {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Two game entities are considered "near" each other if they are within a certain distance of one another.
|
||||
* For explosives, the source of the explosion is always typically constant.
|
||||
* @see `detectsTarget`
|
||||
* @see `ObjectDefinition.Geometry`
|
||||
* @see `Vector3.relativeUp`
|
||||
* @param obj a game entity that explodes
|
||||
* @return a function that resolves a potential target as detected
|
||||
*/
|
||||
def detectionForExplosiveSource(obj: PlanetSideGameObject): (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = {
|
||||
val up = Vector3.relativeUp(obj.Orientation) //check relativeUp; rotate as little as necessary!
|
||||
val g1 = obj.Definition.Geometry(obj)
|
||||
detectTarget(g1, up)
|
||||
}
|
||||
|
||||
/**
|
||||
* Two game entities are considered "near" each other if they are within a certain distance of one another.
|
||||
* For explosives, targets in the damage radius in the direction of the blast (above the explosive) are valid targets.
|
||||
* Targets that are ~0.5916f units in the opposite direction of the blast (below the explosive) are also selected.
|
||||
* @see `ObjectDefinition.Geometry`
|
||||
* @see `PrimitiveGeometry.pointOnOutside`
|
||||
* @see `Vector3.DistanceSquared`
|
||||
* @see `Vector3.neg`
|
||||
* @see `Vector3.relativeUp`
|
||||
* @see `Vector3.ScalarProjection`
|
||||
* @see `Vector3.Unit`
|
||||
* @param g1 a cached geometric representation that should belong to `obj1`
|
||||
* @param up a cached vector in the direction of "above `obj1`'s geometric representation"
|
||||
* @param obj1 a game entity that explodes
|
||||
* @param obj2 a game entity that suffers the explosion
|
||||
* @param maxDistance the square of the maximum distance permissible between game entities
|
||||
* before they are no longer considered "near"
|
||||
* @return `true`, if the target entities are near enough to each other;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def detectTarget(g1: Geometry3D, up: Vector3)(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float) : Boolean = {
|
||||
val g2 = obj2.Definition.Geometry(obj2)
|
||||
val dir = g2.center.asVector3 - g1.center.asVector3
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import net.psforever.objects.ce.{DeployableCategory, DeployedItem}
|
|||
import net.psforever.objects.definition._
|
||||
import net.psforever.objects.definition.converter._
|
||||
import net.psforever.objects.equipment._
|
||||
import net.psforever.objects.geometry.GeometryForm
|
||||
import net.psforever.objects.inventory.InventoryTile
|
||||
import net.psforever.objects.serverobject.aura.Aura
|
||||
import net.psforever.objects.serverobject.doors.DoorDefinition
|
||||
|
|
@ -5602,6 +5603,11 @@ object GlobalDefinitions {
|
|||
* Initialize `VehicleDefinition` globals.
|
||||
*/
|
||||
private def init_vehicles(): Unit = {
|
||||
val atvForm = GeometryForm.representByCylinder(radius = 1.1797f, height = 1.1875f) _
|
||||
val delivererForm = GeometryForm.representByCylinder(radius = 2.46095f, height = 2.40626f) _ //TODO hexahedron
|
||||
val apcForm = GeometryForm.representByCylinder(radius = 4.6211f, height = 3.90626f) _ //TODO hexahedron
|
||||
val liberatorForm = GeometryForm.representByCylinder(radius = 3.74615f, height = 2.51563f) _
|
||||
|
||||
fury.Name = "fury"
|
||||
fury.MaxHealth = 650
|
||||
fury.Damageable = true
|
||||
|
|
@ -5632,6 +5638,7 @@ object GlobalDefinitions {
|
|||
fury.DrownAtMaxDepth = true
|
||||
fury.MaxDepth = 1.3f
|
||||
fury.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
fury.Geometry = atvForm
|
||||
|
||||
quadassault.Name = "quadassault" // Basilisk
|
||||
quadassault.MaxHealth = 650
|
||||
|
|
@ -5663,6 +5670,7 @@ object GlobalDefinitions {
|
|||
quadassault.DrownAtMaxDepth = true
|
||||
quadassault.MaxDepth = 1.3f
|
||||
quadassault.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
quadassault.Geometry = atvForm
|
||||
|
||||
quadstealth.Name = "quadstealth" // Wraith
|
||||
quadstealth.MaxHealth = 650
|
||||
|
|
@ -5694,6 +5702,7 @@ object GlobalDefinitions {
|
|||
quadstealth.DrownAtMaxDepth = true
|
||||
quadstealth.MaxDepth = 1.25f
|
||||
quadstealth.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
quadstealth.Geometry = atvForm
|
||||
|
||||
two_man_assault_buggy.Name = "two_man_assault_buggy" // Harasser
|
||||
two_man_assault_buggy.MaxHealth = 1250
|
||||
|
|
@ -5727,6 +5736,7 @@ object GlobalDefinitions {
|
|||
two_man_assault_buggy.DrownAtMaxDepth = true
|
||||
two_man_assault_buggy.MaxDepth = 1.5f
|
||||
two_man_assault_buggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
two_man_assault_buggy.Geometry = GeometryForm.representByCylinder(radius = 2.10545f, height = 1.59376f)
|
||||
|
||||
skyguard.Name = "skyguard"
|
||||
skyguard.MaxHealth = 1000
|
||||
|
|
@ -5749,7 +5759,6 @@ object GlobalDefinitions {
|
|||
skyguard.AutoPilotSpeeds = (22, 8)
|
||||
skyguard.DestroyedModel = Some(DestroyedVehicle.Skyguard)
|
||||
skyguard.JackingDuration = Array(0, 15, 5, 3)
|
||||
|
||||
skyguard.explodes = true
|
||||
skyguard.innateDamage = new DamageWithPosition {
|
||||
CausesDamageType = DamageType.One
|
||||
|
|
@ -5762,6 +5771,7 @@ object GlobalDefinitions {
|
|||
skyguard.DrownAtMaxDepth = true
|
||||
skyguard.MaxDepth = 1.5f
|
||||
skyguard.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
skyguard.Geometry = GeometryForm.representByCylinder(radius = 1.8867f, height = 1.4375f)
|
||||
|
||||
threemanheavybuggy.Name = "threemanheavybuggy" // Marauder
|
||||
threemanheavybuggy.MaxHealth = 1700
|
||||
|
|
@ -5801,6 +5811,7 @@ object GlobalDefinitions {
|
|||
threemanheavybuggy.DrownAtMaxDepth = true
|
||||
threemanheavybuggy.MaxDepth = 1.83f
|
||||
threemanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
threemanheavybuggy.Geometry = GeometryForm.representByCylinder(radius = 2.1953f, height = 2.03125f)
|
||||
|
||||
twomanheavybuggy.Name = "twomanheavybuggy" // Enforcer
|
||||
twomanheavybuggy.MaxHealth = 1800
|
||||
|
|
@ -5835,6 +5846,7 @@ object GlobalDefinitions {
|
|||
twomanheavybuggy.DrownAtMaxDepth = true
|
||||
twomanheavybuggy.MaxDepth = 1.95f
|
||||
twomanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
twomanheavybuggy.Geometry = GeometryForm.representByCylinder(radius = 2.60935f, height = 1.79688f)
|
||||
|
||||
twomanhoverbuggy.Name = "twomanhoverbuggy" // Thresher
|
||||
twomanhoverbuggy.MaxHealth = 1600
|
||||
|
|
@ -5868,6 +5880,7 @@ object GlobalDefinitions {
|
|||
}
|
||||
twomanhoverbuggy.DrownAtMaxDepth = true
|
||||
twomanhoverbuggy.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the thresher hovers over water, so ...?
|
||||
twomanhoverbuggy.Geometry = GeometryForm.representByCylinder(radius = 2.1875f, height = 2.01563f)
|
||||
|
||||
mediumtransport.Name = "mediumtransport" // Deliverer
|
||||
mediumtransport.MaxHealth = 2500
|
||||
|
|
@ -5909,6 +5922,7 @@ object GlobalDefinitions {
|
|||
mediumtransport.DrownAtMaxDepth = false
|
||||
mediumtransport.MaxDepth = 1.2f
|
||||
mediumtransport.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
mediumtransport.Geometry = delivererForm
|
||||
|
||||
battlewagon.Name = "battlewagon" // Raider
|
||||
battlewagon.MaxHealth = 2500
|
||||
|
|
@ -5953,6 +5967,7 @@ object GlobalDefinitions {
|
|||
battlewagon.DrownAtMaxDepth = true
|
||||
battlewagon.MaxDepth = 1.2f
|
||||
battlewagon.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
battlewagon.Geometry = delivererForm
|
||||
|
||||
thunderer.Name = "thunderer"
|
||||
thunderer.MaxHealth = 2500
|
||||
|
|
@ -5994,6 +6009,7 @@ object GlobalDefinitions {
|
|||
thunderer.DrownAtMaxDepth = true
|
||||
thunderer.MaxDepth = 1.2f
|
||||
thunderer.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
thunderer.Geometry = delivererForm
|
||||
|
||||
aurora.Name = "aurora"
|
||||
aurora.MaxHealth = 2500
|
||||
|
|
@ -6035,6 +6051,7 @@ object GlobalDefinitions {
|
|||
aurora.DrownAtMaxDepth = true
|
||||
aurora.MaxDepth = 1.2f
|
||||
aurora.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
aurora.Geometry = delivererForm
|
||||
|
||||
apc_tr.Name = "apc_tr" // Juggernaut
|
||||
apc_tr.MaxHealth = 6000
|
||||
|
|
@ -6099,6 +6116,7 @@ object GlobalDefinitions {
|
|||
apc_tr.DrownAtMaxDepth = true
|
||||
apc_tr.MaxDepth = 3
|
||||
apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
apc_tr.Geometry = apcForm
|
||||
|
||||
apc_nc.Name = "apc_nc" // Vindicator
|
||||
apc_nc.MaxHealth = 6000
|
||||
|
|
@ -6163,6 +6181,7 @@ object GlobalDefinitions {
|
|||
apc_nc.DrownAtMaxDepth = true
|
||||
apc_nc.MaxDepth = 3
|
||||
apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
apc_nc.Geometry = apcForm
|
||||
|
||||
apc_vs.Name = "apc_vs" // Leviathan
|
||||
apc_vs.MaxHealth = 6000
|
||||
|
|
@ -6227,6 +6246,7 @@ object GlobalDefinitions {
|
|||
apc_vs.DrownAtMaxDepth = true
|
||||
apc_vs.MaxDepth = 3
|
||||
apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
apc_vs.Geometry = apcForm
|
||||
|
||||
lightning.Name = "lightning"
|
||||
lightning.MaxHealth = 2000
|
||||
|
|
@ -6259,6 +6279,7 @@ object GlobalDefinitions {
|
|||
lightning.DrownAtMaxDepth = true
|
||||
lightning.MaxDepth = 1.38f
|
||||
lightning.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
lightning.Geometry = GeometryForm.representByCylinder(radius = 2.5078f, height = 1.79688f)
|
||||
|
||||
prowler.Name = "prowler"
|
||||
prowler.MaxHealth = 4800
|
||||
|
|
@ -6296,6 +6317,7 @@ object GlobalDefinitions {
|
|||
prowler.DrownAtMaxDepth = true
|
||||
prowler.MaxDepth = 3
|
||||
prowler.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
prowler.Geometry = GeometryForm.representByCylinder(radius = 3.461f, height = 3.48438f)
|
||||
|
||||
vanguard.Name = "vanguard"
|
||||
vanguard.MaxHealth = 5400
|
||||
|
|
@ -6329,6 +6351,7 @@ object GlobalDefinitions {
|
|||
vanguard.DrownAtMaxDepth = true
|
||||
vanguard.MaxDepth = 2.7f
|
||||
vanguard.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
vanguard.Geometry = GeometryForm.representByCylinder(radius = 3.8554f, height = 2.60938f)
|
||||
|
||||
magrider.Name = "magrider"
|
||||
magrider.MaxHealth = 4200
|
||||
|
|
@ -6364,6 +6387,7 @@ object GlobalDefinitions {
|
|||
magrider.DrownAtMaxDepth = true
|
||||
magrider.MaxDepth = 2
|
||||
magrider.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the magrider hovers over water, so ...?
|
||||
magrider.Geometry = GeometryForm.representByCylinder(radius = 3.3008f, height = 3.26562f)
|
||||
|
||||
val utilityConverter = new UtilityVehicleConverter
|
||||
ant.Name = "ant"
|
||||
|
|
@ -6397,6 +6421,7 @@ object GlobalDefinitions {
|
|||
ant.DrownAtMaxDepth = true
|
||||
ant.MaxDepth = 2
|
||||
ant.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
ant.Geometry = GeometryForm.representByCylinder(radius = 2.16795f, height = 2.09376f) //TODO hexahedron
|
||||
|
||||
ams.Name = "ams"
|
||||
ams.MaxHealth = 5000 // Temporary - original value is 3000
|
||||
|
|
@ -6433,6 +6458,7 @@ object GlobalDefinitions {
|
|||
ams.DrownAtMaxDepth = true
|
||||
ams.MaxDepth = 3
|
||||
ams.UnderwaterLifespan(suffocation = 5000L, recovery = 5000L)
|
||||
ams.Geometry = GeometryForm.representByCylinder(radius = 3.0117f, height = 3.39062f) //TODO hexahedron
|
||||
|
||||
val variantConverter = new VariantVehicleConverter
|
||||
router.Name = "router"
|
||||
|
|
@ -6469,6 +6495,7 @@ object GlobalDefinitions {
|
|||
router.DrownAtMaxDepth = true
|
||||
router.MaxDepth = 2
|
||||
router.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the router hovers over water, so ...?
|
||||
router.Geometry = GeometryForm.representByCylinder(radius = 3.64845f, height = 3.51563f) //TODO hexahedron
|
||||
|
||||
switchblade.Name = "switchblade"
|
||||
switchblade.MaxHealth = 1750
|
||||
|
|
@ -6505,6 +6532,7 @@ object GlobalDefinitions {
|
|||
switchblade.DrownAtMaxDepth = true
|
||||
switchblade.MaxDepth = 2
|
||||
switchblade.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the switchblade hovers over water, so ...?
|
||||
switchblade.Geometry = GeometryForm.representByCylinder(radius = 2.4335f, height = 2.73438f)
|
||||
|
||||
flail.Name = "flail"
|
||||
flail.MaxHealth = 2400
|
||||
|
|
@ -6539,6 +6567,7 @@ object GlobalDefinitions {
|
|||
flail.DrownAtMaxDepth = true
|
||||
flail.MaxDepth = 2
|
||||
flail.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the flail hovers over water, so ...?
|
||||
flail.Geometry = GeometryForm.representByCylinder(radius = 2.1875f, height = 2.21875f)
|
||||
|
||||
mosquito.Name = "mosquito"
|
||||
mosquito.MaxHealth = 665
|
||||
|
|
@ -6572,6 +6601,7 @@ object GlobalDefinitions {
|
|||
}
|
||||
mosquito.DrownAtMaxDepth = true
|
||||
mosquito.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
mosquito.Geometry = GeometryForm.representByCylinder(radius = 2.72108f, height = 2.5f)
|
||||
|
||||
lightgunship.Name = "lightgunship" // Reaver
|
||||
lightgunship.MaxHealth = 1000
|
||||
|
|
@ -6606,6 +6636,7 @@ object GlobalDefinitions {
|
|||
}
|
||||
lightgunship.DrownAtMaxDepth = true
|
||||
lightgunship.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
lightgunship.Geometry = GeometryForm.representByCylinder(radius = 2.375f, height = 1.98438f)
|
||||
|
||||
wasp.Name = "wasp"
|
||||
wasp.MaxHealth = 515
|
||||
|
|
@ -6639,6 +6670,7 @@ object GlobalDefinitions {
|
|||
}
|
||||
wasp.DrownAtMaxDepth = true
|
||||
wasp.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
wasp.Geometry = GeometryForm.representByCylinder(radius = 2.88675f, height = 2.5f)
|
||||
|
||||
liberator.Name = "liberator"
|
||||
liberator.MaxHealth = 2500
|
||||
|
|
@ -6680,6 +6712,7 @@ object GlobalDefinitions {
|
|||
}
|
||||
liberator.DrownAtMaxDepth = true
|
||||
liberator.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
liberator.Geometry = liberatorForm
|
||||
|
||||
vulture.Name = "vulture"
|
||||
vulture.MaxHealth = 2500
|
||||
|
|
@ -6722,6 +6755,7 @@ object GlobalDefinitions {
|
|||
}
|
||||
vulture.DrownAtMaxDepth = true
|
||||
vulture.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
vulture.Geometry = liberatorForm
|
||||
|
||||
dropship.Name = "dropship" // Galaxy
|
||||
dropship.MaxHealth = 5000
|
||||
|
|
@ -6796,6 +6830,7 @@ object GlobalDefinitions {
|
|||
}
|
||||
dropship.DrownAtMaxDepth = true
|
||||
dropship.MaxDepth = 2
|
||||
dropship.Geometry = GeometryForm.representByCylinder(radius = 10.52202f, height = 6.23438f)
|
||||
|
||||
galaxy_gunship.Name = "galaxy_gunship"
|
||||
galaxy_gunship.MaxHealth = 6000
|
||||
|
|
@ -6849,6 +6884,7 @@ object GlobalDefinitions {
|
|||
}
|
||||
galaxy_gunship.DrownAtMaxDepth = true
|
||||
galaxy_gunship.MaxDepth = 2
|
||||
galaxy_gunship.Geometry = GeometryForm.representByCylinder(radius = 9.2382f, height = 5.01562f)
|
||||
|
||||
lodestar.Name = "lodestar"
|
||||
lodestar.MaxHealth = 5000
|
||||
|
|
@ -6890,6 +6926,7 @@ object GlobalDefinitions {
|
|||
}
|
||||
lodestar.DrownAtMaxDepth = true
|
||||
lodestar.MaxDepth = 2
|
||||
lodestar.Geometry = GeometryForm.representByCylinder(radius = 7.8671f, height = 6.79688f) //TODO hexahedron
|
||||
|
||||
phantasm.Name = "phantasm"
|
||||
phantasm.MaxHealth = 2500
|
||||
|
|
@ -6932,6 +6969,7 @@ object GlobalDefinitions {
|
|||
}
|
||||
phantasm.DrownAtMaxDepth = true
|
||||
phantasm.MaxDepth = 2
|
||||
phantasm.Geometry = GeometryForm.representByCylinder(radius = 5.2618f, height = 3f)
|
||||
|
||||
droppod.Name = "droppod"
|
||||
droppod.MaxHealth = 20000
|
||||
|
|
@ -6945,12 +6983,18 @@ object GlobalDefinitions {
|
|||
droppod.DestroyedModel = None //the adb calls out a droppod; the cyclic nature of this confounds me
|
||||
droppod.DamageUsing = DamageCalculations.AgainstAircraft
|
||||
droppod.DrownAtMaxDepth = false
|
||||
//TODO geometry?
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize `Deployable` globals.
|
||||
*/
|
||||
private def init_deployables(): Unit = {
|
||||
val mine = GeometryForm.representByCylinder(radius = 0.1914f, height = 0.0957f) _
|
||||
val smallTurret = GeometryForm.representByCylinder(radius = 0.48435f, height = 1.23438f) _
|
||||
val sensor = GeometryForm.representByCylinder(radius = 0.1914f, height = 1.21875f) _
|
||||
val largeTurret = GeometryForm.representByCylinder(radius = 0.8437f, height = 2.29687f) _
|
||||
|
||||
boomer.Name = "boomer"
|
||||
boomer.Descriptor = "Boomers"
|
||||
boomer.MaxHealth = 100
|
||||
|
|
@ -6972,6 +7016,7 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.1f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
boomer.Geometry = mine
|
||||
|
||||
he_mine.Name = "he_mine"
|
||||
he_mine.Descriptor = "Mines"
|
||||
|
|
@ -6993,6 +7038,7 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.25f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
he_mine.Geometry = mine
|
||||
|
||||
jammer_mine.Name = "jammer_mine"
|
||||
jammer_mine.Descriptor = "JammerMines"
|
||||
|
|
@ -7002,6 +7048,7 @@ object GlobalDefinitions {
|
|||
jammer_mine.Repairable = false
|
||||
jammer_mine.DeployTime = Duration.create(1000, "ms")
|
||||
jammer_mine.DetonateOnJamming = false
|
||||
jammer_mine.Geometry = mine
|
||||
|
||||
spitfire_turret.Name = "spitfire_turret"
|
||||
spitfire_turret.Descriptor = "Spitfires"
|
||||
|
|
@ -7025,6 +7072,7 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
spitfire_turret.Geometry = smallTurret
|
||||
|
||||
spitfire_cloaked.Name = "spitfire_cloaked"
|
||||
spitfire_cloaked.Descriptor = "CloakingSpitfires"
|
||||
|
|
@ -7047,6 +7095,7 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
spitfire_cloaked.Geometry = smallTurret
|
||||
|
||||
spitfire_aa.Name = "spitfire_aa"
|
||||
spitfire_aa.Descriptor = "FlakSpitfires"
|
||||
|
|
@ -7069,6 +7118,7 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
spitfire_aa.Geometry = smallTurret
|
||||
|
||||
motionalarmsensor.Name = "motionalarmsensor"
|
||||
motionalarmsensor.Descriptor = "MotionSensors"
|
||||
|
|
@ -7077,6 +7127,7 @@ object GlobalDefinitions {
|
|||
motionalarmsensor.Repairable = true
|
||||
motionalarmsensor.RepairIfDestroyed = false
|
||||
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
|
||||
motionalarmsensor.Geometry = sensor
|
||||
|
||||
sensor_shield.Name = "sensor_shield"
|
||||
sensor_shield.Descriptor = "SensorShields"
|
||||
|
|
@ -7085,6 +7136,7 @@ object GlobalDefinitions {
|
|||
sensor_shield.Repairable = true
|
||||
sensor_shield.RepairIfDestroyed = false
|
||||
sensor_shield.DeployTime = Duration.create(5000, "ms")
|
||||
sensor_shield.Geometry = sensor
|
||||
|
||||
tank_traps.Name = "tank_traps"
|
||||
tank_traps.Descriptor = "TankTraps"
|
||||
|
|
@ -7103,6 +7155,7 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
tank_traps.Geometry = GeometryForm.representByCylinder(radius = 2.89680997f, height = 3.57812f)
|
||||
|
||||
val fieldTurretConverter = new FieldTurretConverter
|
||||
portable_manned_turret.Name = "portable_manned_turret"
|
||||
|
|
@ -7130,6 +7183,7 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.1f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret.Geometry = largeTurret
|
||||
|
||||
portable_manned_turret_nc.Name = "portable_manned_turret_nc"
|
||||
portable_manned_turret_nc.Descriptor = "FieldTurrets"
|
||||
|
|
@ -7156,6 +7210,7 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.1f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret_nc.Geometry = largeTurret
|
||||
|
||||
portable_manned_turret_tr.Name = "portable_manned_turret_tr"
|
||||
portable_manned_turret_tr.Descriptor = "FieldTurrets"
|
||||
|
|
@ -7182,6 +7237,7 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.1f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret_tr.Geometry = largeTurret
|
||||
|
||||
portable_manned_turret_vs.Name = "portable_manned_turret_vs"
|
||||
portable_manned_turret_vs.Descriptor = "FieldTurrets"
|
||||
|
|
@ -7208,6 +7264,7 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.1f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret_vs.Geometry = largeTurret
|
||||
|
||||
deployable_shield_generator.Name = "deployable_shield_generator"
|
||||
deployable_shield_generator.Descriptor = "ShieldGenerators"
|
||||
|
|
@ -7217,6 +7274,7 @@ object GlobalDefinitions {
|
|||
deployable_shield_generator.RepairIfDestroyed = false
|
||||
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
|
||||
deployable_shield_generator.Model = ComplexDeployableResolutions.calculate
|
||||
deployable_shield_generator.Geometry = GeometryForm.representByCylinder(radius = 0.6562f, height = 2.17188f)
|
||||
|
||||
router_telepad_deployable.Name = "router_telepad_deployable"
|
||||
router_telepad_deployable.MaxHealth = 100
|
||||
|
|
@ -7226,6 +7284,7 @@ object GlobalDefinitions {
|
|||
router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
|
||||
router_telepad_deployable.Packet = new TelepadDeployableConverter
|
||||
router_telepad_deployable.Model = SimpleResolutions.calculate
|
||||
router_telepad_deployable.Geometry = GeometryForm.representByRaisedSphere(radius = 1.2344f)
|
||||
|
||||
internal_router_telepad_deployable.Name = "router_telepad_deployable"
|
||||
internal_router_telepad_deployable.MaxHealth = 1
|
||||
|
|
@ -7240,6 +7299,8 @@ object GlobalDefinitions {
|
|||
* Initialize `Miscellaneous` globals.
|
||||
*/
|
||||
private def initMiscellaneous(): Unit = {
|
||||
val vterm = GeometryForm.representByCylinder(radius = 1.03515f, height = 1.09374f) _
|
||||
|
||||
ams_respawn_tube.Name = "ams_respawn_tube"
|
||||
ams_respawn_tube.Delay = 10
|
||||
ams_respawn_tube.SpecificPointFunc = SpawnPoint.AMS
|
||||
|
|
@ -7280,9 +7341,10 @@ object GlobalDefinitions {
|
|||
order_terminal.MaxHealth = 500
|
||||
order_terminal.Damageable = true
|
||||
order_terminal.Repairable = true
|
||||
order_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
order_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
order_terminal.RepairIfDestroyed = true
|
||||
order_terminal.Subtract.Damage1 = 8
|
||||
order_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.8438f, height = 1.3f)
|
||||
|
||||
order_terminala.Name = "order_terminala"
|
||||
order_terminala.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(
|
||||
|
|
@ -7344,16 +7406,18 @@ object GlobalDefinitions {
|
|||
cert_terminal.MaxHealth = 500
|
||||
cert_terminal.Damageable = true
|
||||
cert_terminal.Repairable = true
|
||||
cert_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
cert_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
cert_terminal.RepairIfDestroyed = true
|
||||
cert_terminal.Subtract.Damage1 = 8
|
||||
cert_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.66405f, height = 1.09374f)
|
||||
|
||||
implant_terminal_mech.Name = "implant_terminal_mech"
|
||||
implant_terminal_mech.MaxHealth = 1500 //TODO 1000; right now, 1000 (mech) + 500 (interface)
|
||||
implant_terminal_mech.Damageable = true
|
||||
implant_terminal_mech.Repairable = true
|
||||
implant_terminal_mech.autoRepair = AutoRepairStats(1.6f, 5000, 2400, 0.5f) //ori. 1, 5000, 2400, 0.5f
|
||||
implant_terminal_mech.autoRepair = AutoRepairStats(1.6f, 5000, 2400, 0.5f)
|
||||
implant_terminal_mech.RepairIfDestroyed = true
|
||||
implant_terminal_mech.Geometry = GeometryForm.representByCylinder(radius = 2.7813f, height = 6.4375f)
|
||||
|
||||
implant_terminal_interface.Name = "implant_terminal_interface"
|
||||
implant_terminal_interface.Tab += 0 -> OrderTerminalDefinition.ImplantPage(ImplantTerminalDefinition.implants)
|
||||
|
|
@ -7362,6 +7426,7 @@ object GlobalDefinitions {
|
|||
implant_terminal_interface.Repairable = true
|
||||
implant_terminal_interface.autoRepair = AutoRepairStats(1, 5000, 200, 1) //TODO amount and drain are default? undefined?
|
||||
implant_terminal_interface.RepairIfDestroyed = true
|
||||
//TODO will need geometry when Damageable = true
|
||||
|
||||
ground_vehicle_terminal.Name = "ground_vehicle_terminal"
|
||||
ground_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(
|
||||
|
|
@ -7372,9 +7437,10 @@ object GlobalDefinitions {
|
|||
ground_vehicle_terminal.MaxHealth = 500
|
||||
ground_vehicle_terminal.Damageable = true
|
||||
ground_vehicle_terminal.Repairable = true
|
||||
ground_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
ground_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
ground_vehicle_terminal.RepairIfDestroyed = true
|
||||
ground_vehicle_terminal.Subtract.Damage1 = 8
|
||||
ground_vehicle_terminal.Geometry = vterm
|
||||
|
||||
air_vehicle_terminal.Name = "air_vehicle_terminal"
|
||||
air_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(
|
||||
|
|
@ -7385,9 +7451,10 @@ object GlobalDefinitions {
|
|||
air_vehicle_terminal.MaxHealth = 500
|
||||
air_vehicle_terminal.Damageable = true
|
||||
air_vehicle_terminal.Repairable = true
|
||||
air_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
air_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
air_vehicle_terminal.RepairIfDestroyed = true
|
||||
air_vehicle_terminal.Subtract.Damage1 = 8
|
||||
air_vehicle_terminal.Geometry = vterm
|
||||
|
||||
dropship_vehicle_terminal.Name = "dropship_vehicle_terminal"
|
||||
dropship_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(
|
||||
|
|
@ -7398,9 +7465,10 @@ object GlobalDefinitions {
|
|||
dropship_vehicle_terminal.MaxHealth = 500
|
||||
dropship_vehicle_terminal.Damageable = true
|
||||
dropship_vehicle_terminal.Repairable = true
|
||||
dropship_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
dropship_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
dropship_vehicle_terminal.RepairIfDestroyed = true
|
||||
dropship_vehicle_terminal.Subtract.Damage1 = 8
|
||||
dropship_vehicle_terminal.Geometry = vterm
|
||||
|
||||
vehicle_terminal_combined.Name = "vehicle_terminal_combined"
|
||||
vehicle_terminal_combined.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(
|
||||
|
|
@ -7411,9 +7479,10 @@ object GlobalDefinitions {
|
|||
vehicle_terminal_combined.MaxHealth = 500
|
||||
vehicle_terminal_combined.Damageable = true
|
||||
vehicle_terminal_combined.Repairable = true
|
||||
vehicle_terminal_combined.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
vehicle_terminal_combined.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
vehicle_terminal_combined.RepairIfDestroyed = true
|
||||
vehicle_terminal_combined.Subtract.Damage1 = 8
|
||||
vehicle_terminal_combined.Geometry = vterm
|
||||
|
||||
vanu_air_vehicle_term.Name = "vanu_air_vehicle_term"
|
||||
vanu_air_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(
|
||||
|
|
@ -7424,7 +7493,7 @@ object GlobalDefinitions {
|
|||
vanu_air_vehicle_term.MaxHealth = 500
|
||||
vanu_air_vehicle_term.Damageable = true
|
||||
vanu_air_vehicle_term.Repairable = true
|
||||
vanu_air_vehicle_term.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
vanu_air_vehicle_term.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
vanu_air_vehicle_term.RepairIfDestroyed = true
|
||||
vanu_air_vehicle_term.Subtract.Damage1 = 8
|
||||
|
||||
|
|
@ -7437,7 +7506,7 @@ object GlobalDefinitions {
|
|||
vanu_vehicle_term.MaxHealth = 500
|
||||
vanu_vehicle_term.Damageable = true
|
||||
vanu_vehicle_term.Repairable = true
|
||||
vanu_vehicle_term.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
vanu_vehicle_term.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
vanu_vehicle_term.RepairIfDestroyed = true
|
||||
vanu_vehicle_term.Subtract.Damage1 = 8
|
||||
|
||||
|
|
@ -7450,9 +7519,10 @@ object GlobalDefinitions {
|
|||
bfr_terminal.MaxHealth = 500
|
||||
bfr_terminal.Damageable = true
|
||||
bfr_terminal.Repairable = true
|
||||
bfr_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
bfr_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
bfr_terminal.RepairIfDestroyed = true
|
||||
bfr_terminal.Subtract.Damage1 = 8
|
||||
bfr_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.92185f, height = 2.64693f)
|
||||
|
||||
respawn_tube.Name = "respawn_tube"
|
||||
respawn_tube.Delay = 10
|
||||
|
|
@ -7461,9 +7531,10 @@ object GlobalDefinitions {
|
|||
respawn_tube.Damageable = true
|
||||
respawn_tube.DamageableByFriendlyFire = false
|
||||
respawn_tube.Repairable = true
|
||||
respawn_tube.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1) //orig. 1, 10000, 2400, 1
|
||||
respawn_tube.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1)
|
||||
respawn_tube.RepairIfDestroyed = true
|
||||
respawn_tube.Subtract.Damage1 = 8
|
||||
respawn_tube.Geometry = GeometryForm.representByCylinder(radius = 0.9336f, height = 2.84375f)
|
||||
|
||||
respawn_tube_sanctuary.Name = "respawn_tube"
|
||||
respawn_tube_sanctuary.Delay = 10
|
||||
|
|
@ -7472,7 +7543,8 @@ object GlobalDefinitions {
|
|||
respawn_tube_sanctuary.Damageable = false //true?
|
||||
respawn_tube_sanctuary.DamageableByFriendlyFire = false
|
||||
respawn_tube_sanctuary.Repairable = true
|
||||
respawn_tube_sanctuary.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1) //orig. 1, 10000, 2400, 1
|
||||
respawn_tube_sanctuary.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1)
|
||||
//TODO will need geometry when Damageable = true
|
||||
|
||||
respawn_tube_tower.Name = "respawn_tube_tower"
|
||||
respawn_tube_tower.Delay = 10
|
||||
|
|
@ -7481,9 +7553,10 @@ object GlobalDefinitions {
|
|||
respawn_tube_tower.Damageable = true
|
||||
respawn_tube_tower.DamageableByFriendlyFire = false
|
||||
respawn_tube_tower.Repairable = true
|
||||
respawn_tube_tower.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1) //orig. 1, 10000, 2400, 1
|
||||
respawn_tube_tower.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1)
|
||||
respawn_tube_tower.RepairIfDestroyed = true
|
||||
respawn_tube_tower.Subtract.Damage1 = 8
|
||||
respawn_tube_tower.Geometry = GeometryForm.representByCylinder(radius = 0.9336f, height = 2.84375f)
|
||||
|
||||
teleportpad_terminal.Name = "teleportpad_terminal"
|
||||
teleportpad_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.routerTerminal)
|
||||
|
|
@ -7499,8 +7572,9 @@ object GlobalDefinitions {
|
|||
medical_terminal.MaxHealth = 500
|
||||
medical_terminal.Damageable = true
|
||||
medical_terminal.Repairable = true
|
||||
medical_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
medical_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
medical_terminal.RepairIfDestroyed = true
|
||||
medical_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.711f, height = 1.75f)
|
||||
|
||||
adv_med_terminal.Name = "adv_med_terminal"
|
||||
adv_med_terminal.Interval = 500
|
||||
|
|
@ -7511,8 +7585,9 @@ object GlobalDefinitions {
|
|||
adv_med_terminal.MaxHealth = 750
|
||||
adv_med_terminal.Damageable = true
|
||||
adv_med_terminal.Repairable = true
|
||||
adv_med_terminal.autoRepair = AutoRepairStats(1.57894f, 5000, 2400, 0.5f) //orig. 1, 5000, 2400, 0.5f
|
||||
adv_med_terminal.autoRepair = AutoRepairStats(1.57894f, 5000, 2400, 0.5f)
|
||||
adv_med_terminal.RepairIfDestroyed = true
|
||||
adv_med_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.8662125f, height = 3.47f)
|
||||
|
||||
crystals_health_a.Name = "crystals_health_a"
|
||||
crystals_health_a.Interval = 500
|
||||
|
|
@ -7539,7 +7614,7 @@ object GlobalDefinitions {
|
|||
portable_med_terminal.MaxHealth = 500
|
||||
portable_med_terminal.Damageable = false //TODO actually true
|
||||
portable_med_terminal.Repairable = false
|
||||
portable_med_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
portable_med_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
|
||||
pad_landing_frame.Name = "pad_landing_frame"
|
||||
pad_landing_frame.Interval = 1000
|
||||
|
|
@ -7653,7 +7728,7 @@ object GlobalDefinitions {
|
|||
manned_turret.Damageable = true
|
||||
manned_turret.DamageDisablesAt = 0
|
||||
manned_turret.Repairable = true
|
||||
manned_turret.autoRepair = AutoRepairStats(1.0909f, 10000, 1600, 0.5f) //orig. 1, 10000, 1600, 0.5f
|
||||
manned_turret.autoRepair = AutoRepairStats(1.0909f, 10000, 1600, 0.5f)
|
||||
manned_turret.RepairIfDestroyed = true
|
||||
manned_turret.Weapons += 1 -> new mutable.HashMap()
|
||||
manned_turret.Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan
|
||||
|
|
@ -7671,13 +7746,14 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.1f
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
manned_turret.Geometry = GeometryForm.representByCylinder(radius = 1.2695f, height = 2.6875f)
|
||||
|
||||
vanu_sentry_turret.Name = "vanu_sentry_turret"
|
||||
vanu_sentry_turret.MaxHealth = 1500
|
||||
vanu_sentry_turret.Damageable = true
|
||||
vanu_sentry_turret.DamageDisablesAt = 0
|
||||
vanu_sentry_turret.Repairable = true
|
||||
vanu_sentry_turret.autoRepair = AutoRepairStats(3.27272f, 10000, 1000, 0.5f) //orig. 3, 10000, 1000, 0.5f
|
||||
vanu_sentry_turret.autoRepair = AutoRepairStats(3.27272f, 10000, 1000, 0.5f)
|
||||
vanu_sentry_turret.RepairIfDestroyed = true
|
||||
vanu_sentry_turret.Weapons += 1 -> new mutable.HashMap()
|
||||
vanu_sentry_turret.Weapons(1) += TurretUpgrade.None -> vanu_sentry_turret_weapon
|
||||
|
|
@ -7685,6 +7761,7 @@ object GlobalDefinitions {
|
|||
vanu_sentry_turret.MountPoints += 2 -> 0
|
||||
vanu_sentry_turret.FactionLocked = false
|
||||
vanu_sentry_turret.ReserveAmmunition = false
|
||||
vanu_sentry_turret.Geometry = GeometryForm.representByCylinder(radius = 1.76311f, height = 3.984375f)
|
||||
|
||||
painbox.Name = "painbox"
|
||||
painbox.alwaysOn = false
|
||||
|
|
@ -7759,7 +7836,7 @@ object GlobalDefinitions {
|
|||
generator.Damageable = true
|
||||
generator.DamageableByFriendlyFire = false
|
||||
generator.Repairable = true
|
||||
generator.autoRepair = AutoRepairStats(0.77775f, 5000, 875, 1) //orig. 1, 5000, 875, 1
|
||||
generator.autoRepair = AutoRepairStats(0.77775f, 5000, 875, 1)
|
||||
generator.RepairDistance = 13.5f
|
||||
generator.RepairIfDestroyed = true
|
||||
generator.Subtract.Damage1 = 9
|
||||
|
|
@ -7774,5 +7851,6 @@ object GlobalDefinitions {
|
|||
Modifiers = ExplodingRadialDegrade
|
||||
//damage is 99999 at 14m, dropping rapidly to ~1 at 15.75m
|
||||
}
|
||||
generator.Geometry = GeometryForm.representByCylinder(radius = 1.2617f, height = 9.14063f)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,280 +0,0 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.geometry
|
||||
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
object Closest {
|
||||
object Distance {
|
||||
def apply(point : Vector3, seg : Segment2D) : Float = {
|
||||
val segdx = seg.p2.x - seg.p1.x
|
||||
val segdy = seg.p2.y - seg.p1.y
|
||||
((point.x - seg.p1.x) * segdx + (point.y - seg.p1.y) * segdy) /
|
||||
Vector3.MagnitudeSquared(Vector3(segdx, segdy, 0))
|
||||
}
|
||||
|
||||
def apply(line1 : Line2D, line2 : Line2D) : Float = {
|
||||
if (Intersection.Test(line1, line2)) { //intersecting lines
|
||||
0f
|
||||
} else {
|
||||
math.abs(
|
||||
Vector3.DotProduct(
|
||||
Vector3(line2.p.x - line1.p.x, line2.p.y - line1.p.y, 0),
|
||||
Vector3(-1/line1.d.y, 1/line1.d.x, 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def apply(seg1: Segment2D, seg2: Segment2D): Float = {
|
||||
if (Intersection.Test(seg1, seg2)) { //intersecting line segments
|
||||
0f
|
||||
} else {
|
||||
val v1a = Vector3(seg1.p1.x, seg1.p1.y, 0)
|
||||
val v2a = Vector3(seg2.p1.x, seg2.p1.y, 0)
|
||||
val v1b = Vector3(seg1.p2.x, seg1.p2.y, 0)
|
||||
val v2b = Vector3(seg2.p2.x, seg2.p2.y, 0)
|
||||
math.min(
|
||||
apply(v1a, seg2),
|
||||
math.min(
|
||||
apply(v1b, seg2),
|
||||
math.min(
|
||||
apply(v2a, seg1),
|
||||
apply(v2b, seg1)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def apply(c1: Circle, c2 : Circle): Float = {
|
||||
math.max(0, Vector3.Magnitude(Vector3(c1.p.x - c2.p.x, c1.p.y - c2.p.y, 0)) - c1.radius - c2.radius)
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param line1 na
|
||||
* @param line2 na
|
||||
* @return the shortest distance between the lines;
|
||||
* if parallel, the common perpendicular distance between the lines;
|
||||
* if coincidental, this distance will be 0
|
||||
*/
|
||||
def apply(line1: Line3D, line2: Line3D): Float = {
|
||||
val cross = Vector3.CrossProduct(line1.d, line2.d)
|
||||
if(cross != Vector3.Zero) {
|
||||
math.abs(
|
||||
Vector3.DotProduct(cross, Vector3(line1.p.x - line2.p.x, line1.p.y - line2.p.y, line1.p.z - line2.p.z))
|
||||
) / Vector3.Magnitude(cross)
|
||||
} else {
|
||||
// lines are parallel or coincidental
|
||||
// construct a right triangle with one leg on line1 and the hypotenuse between the line's known points
|
||||
val hypotenuse = Vector3(line2.p.x - line1.p.x, line2.p.y - line1.p.y, line2.p.z - line1.p.z)
|
||||
val legOnLine1 = line1.d * Vector3.DotProduct(hypotenuse, line1.d)
|
||||
Vector3.Magnitude(hypotenuse - legOnLine1)
|
||||
}
|
||||
}
|
||||
|
||||
def apply(seg1: Segment3D, seg2: Segment3D): Float = {
|
||||
//TODO make not as expensive as finding the plotted closest distance segment
|
||||
Segment(seg1, seg2) match {
|
||||
case Some(seg) => seg.length
|
||||
case None => Float.MaxValue
|
||||
}
|
||||
}
|
||||
|
||||
def apply(s1: Sphere, s2 : Sphere): Float = {
|
||||
math.max(0, Vector3.Magnitude(Vector3(s1.p.x - s2.p.x, s1.p.y - s2.p.y, s1.p.z - s2.p.z)) - s1.radius - s2.radius)
|
||||
}
|
||||
}
|
||||
|
||||
object Segment {
|
||||
/**
|
||||
* na
|
||||
* @param c1 na
|
||||
* @param c2 na
|
||||
* @return a line segment that represents the closest distance between the circle's circumferences;
|
||||
* `None`, if the circles have no distance between them (overlapping)
|
||||
*/
|
||||
def apply(c1 : Circle, c2 : Circle): Option[Segment2D] = {
|
||||
val distance = Distance(c1, c2)
|
||||
if (distance > 0) {
|
||||
val c1x = c1.p.x
|
||||
val c1y = c1.p.y
|
||||
val v = Vector3.Unit(Vector3(c2.p.x - c1x, c2.p.y - c1y, 0f))
|
||||
val c1d = v * c1.radius
|
||||
val c2d = v * c2.radius
|
||||
Some(
|
||||
Segment2D(
|
||||
c1x + c1d.x, c1y + c1d.y,
|
||||
c1x + c2d.x, c1y + c2d.y,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param line1 na
|
||||
* @param line2 na
|
||||
* @return a line segment representing the closest distance between the two not intersecting lines;
|
||||
* in the case of parallel lines, one of infinite closest distances is plotted;
|
||||
* `None`, if the lines intersect with each other
|
||||
*/
|
||||
def apply(line1 : Line3D, line2 : Line3D): Option[Segment3D] = {
|
||||
val p1 = Vector3(line1.p.x, line1.p.y, line1.p.z)
|
||||
val p3 = Vector3(line2.p.x, line2.p.y, line2.p.z)
|
||||
val p13 = p1 - p3 // vector between point on first line and point on second line
|
||||
val p43 = line2.d
|
||||
val p21 = line1.d
|
||||
if (Vector3.MagnitudeSquared(p43) < Float.MinPositiveValue ||
|
||||
Vector3.MagnitudeSquared(p21) < Float.MinPositiveValue) {
|
||||
None
|
||||
} else {
|
||||
val d2121 = Vector3.MagnitudeSquared(p21)
|
||||
val d4343 = Vector3.MagnitudeSquared(p43)
|
||||
val d4321 = Vector3.DotProduct(p43, p21)
|
||||
val denom = d2121 * d4343 - d4321 * d4321 // n where d = (m/n) and a(x,y,z) + d * V<u,v,w> = b(x,y,z) for line1
|
||||
if (math.abs(denom) < Float.MinPositiveValue) {
|
||||
// without a denominator, we have no cross product solution
|
||||
val p13u = Vector3.Unit(p13)
|
||||
if (p21 == p13u || p21 == Vector3.neg(p13u)) { //coincidental lines overlap / intersect
|
||||
None
|
||||
} else { //parallel lines
|
||||
val connecting = Vector3(line2.p.x - line1.p.x, line2.p.y - line1.p.y, line2.p.z - line1.p.z)
|
||||
val legOnLine1 = line1.d * Vector3.DotProduct(connecting, line1.d)
|
||||
val v = connecting - legOnLine1
|
||||
Some(Segment3D(
|
||||
line1.p.x, line1.p.y, line1.p.z,
|
||||
line1.p.x + v.x, line1.p.y + v.y, line1.p.z + v.z
|
||||
))
|
||||
}
|
||||
} else {
|
||||
val d1343 = Vector3.DotProduct(p13, p43)
|
||||
val numer = d1343 * d4321 -d4343 * Vector3.DotProduct(p13, p21) // m where d = (m/n) and ..., etc.
|
||||
val mua = numer / denom
|
||||
val mub = (d1343 + d4321 * mua) / d4343
|
||||
Some(Segment3D(
|
||||
p1.x + mua * p21.x,
|
||||
p1.y + mua * p21.y,
|
||||
p1.z + mua * p21.z,
|
||||
p3.x + mub * p43.x,
|
||||
p3.y + mub * p43.y,
|
||||
p3.z + mub * p43.z
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def apply(line1 : Segment3D, line2 : Segment3D): Option[Segment3D] = {
|
||||
val uline1 = Vector3.Unit(line1.d)
|
||||
val uline2 = Vector3.Unit(line2.d)
|
||||
apply(Line3D(line1.p1.x, line1.p1.y, line1.p1.z, uline1), Line3D(line2.p1.x, line2.p1.y, line2.p1.z, uline2)) match {
|
||||
case Some(seg: Segment3D) => // common skew lines and parallel lines
|
||||
val sega = Vector3(seg.p1.x, seg.p1.y, seg.p1.z)
|
||||
val p1 = Vector3(line1.p1.x, line1.p1.y, line1.p1.z)
|
||||
val d1 = sega - p1
|
||||
val out1 = if (!Geometry.equalVectors(Vector3.Unit(d1), uline1)) { //clamp seg.a(xyz) to segment line1's bounds
|
||||
p1
|
||||
} else if (Vector3.MagnitudeSquared(d1) > Vector3.MagnitudeSquared(line1.d)) {
|
||||
Vector3(line1.p2.x, line1.p2.y, line1.p2.z)
|
||||
} else {
|
||||
sega
|
||||
}
|
||||
val segb = Vector3(seg.p2.x, seg.p2.y, seg.p2.z)
|
||||
val p2 = Vector3(line2.p1.x, line2.p1.y, line2.p1.z)
|
||||
val d2 = segb - p2
|
||||
val out2 = if (!Geometry.equalVectors(Vector3.Unit(d2), uline2)) { //clamp seg.b(xyz) to segment line2's bounds
|
||||
p2
|
||||
} else if (Vector3.MagnitudeSquared(d2) > Vector3.MagnitudeSquared(line2.d)) {
|
||||
Vector3(line2.p2.x, line2.p2.y, line2.p2.z)
|
||||
} else {
|
||||
segb
|
||||
}
|
||||
Some(Segment3D(
|
||||
out1.x, out1.y, out1.z,
|
||||
out2.x, out2.y, out2.z
|
||||
))
|
||||
case None =>
|
||||
val connectingU = Vector3.Unit(Vector3(line2.p1.x - line1.p1.x, line2.p1.y - line1.p1.y, line2.p1.z - line1.p1.z))
|
||||
if (uline1 == connectingU || uline1 == Vector3.neg(connectingU)) { // coincidental line segments
|
||||
val line1a = Vector3(line1.p1.x, line1.p1.y, line1.p1.z)
|
||||
val line1b = Vector3(line1.p2.x, line1.p2.y, line1.p2.z)
|
||||
val line2a = Vector3(line2.p1.x, line2.p1.y, line2.p1.z)
|
||||
val line2b = Vector3(line2.p2.x, line2.p2.y, line2.p2.z)
|
||||
if (Vector3.Unit(line2a - line1a) != Vector3.Unit(line2b - line1a) ||
|
||||
Vector3.Unit(line2a - line1b) != Vector3.Unit(line2b - line1b) ||
|
||||
Vector3.Unit(line1a - line2a) != Vector3.Unit(line1b - line2a) ||
|
||||
Vector3.Unit(line1a - line2b) != Vector3.Unit(line1b - line2b)) {
|
||||
Some(Segment3D(
|
||||
line1.p1.x, line1.p1.y, line1a.z,
|
||||
line1.p1.x, line1.p1.y, line1a.z
|
||||
)) // overlap regions
|
||||
}
|
||||
else {
|
||||
val segs = List((line1a, line2a), (line1a, line2b), (line2a, line1b))
|
||||
val (a, b) = segs({
|
||||
//val dist = segs.map { case (_a, _b) => Vector3.DistanceSquared(_a, _b) }
|
||||
//dist.indexOf(dist.min)
|
||||
var index = 0
|
||||
var minDist = Vector3.DistanceSquared(segs.head._1, segs.head._2)
|
||||
(1 to 2).foreach { i =>
|
||||
val dist = Vector3.DistanceSquared(segs(i)._1, segs(i)._2)
|
||||
if (minDist < dist) {
|
||||
index = i
|
||||
minDist = dist
|
||||
}
|
||||
}
|
||||
index
|
||||
})
|
||||
Some(Segment3D(a.x, a.y, a.z, b.x, b.y, b.z)) // connecting across the smallest gap
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param s1 na
|
||||
* @param s2 na
|
||||
* @return a line segment that represents the closest distance between the sphere's surface areas;
|
||||
* `None`, if the spheres have no distance between them (overlapping)
|
||||
*/
|
||||
def apply(s1 : Sphere, s2 : Sphere): Option[Segment3D] = {
|
||||
val distance = Distance(s1, s2)
|
||||
if (distance > 0) {
|
||||
val s1x = s1.p.x
|
||||
val s1y = s1.p.y
|
||||
val s1z = s1.p.z
|
||||
val v = Vector3.Unit(Vector3(s2.p.x - s1x, s2.p.y - s1y, s2.p.z - s1z))
|
||||
val s1d = v * s1.radius
|
||||
val s2d = v * (s1.radius + distance)
|
||||
Some(Segment3D(s1x + s1d.x, s1y + s1d.y, s1y + s1d.y, s1x + s2d.x, s1y + s2d.y, s1y + s2d.y))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def apply(line : Line3D, sphere : Sphere): Option[Segment3D] = {
|
||||
val sphereAsPoint = Vector3(sphere.p.x, sphere.p.y, sphere.p.z)
|
||||
val lineAsPoint = Vector3(line.p.x, line.p.y, line.p.z)
|
||||
val direct = sphereAsPoint - lineAsPoint
|
||||
val projectionOfDirect = line.d * Vector3.DotProduct(direct, line.d)
|
||||
val heightFromProjection = projectionOfDirect - direct
|
||||
val heightFromProjectionDist = Vector3.Magnitude(heightFromProjection)
|
||||
if (heightFromProjectionDist <= sphere.radius) { //intersection
|
||||
None
|
||||
} else {
|
||||
val pointOnLine = lineAsPoint + projectionOfDirect
|
||||
val pointOnSphere = pointOnLine +
|
||||
Vector3.Unit(heightFromProjection) * (heightFromProjectionDist - sphere.radius)
|
||||
Some(Segment3D(
|
||||
pointOnLine.x, pointOnLine.y, pointOnLine.z,
|
||||
pointOnSphere.x, pointOnSphere.y, pointOnSphere.z
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,229 +3,6 @@ package net.psforever.objects.geometry
|
|||
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
trait PrimitiveGeometry {
|
||||
def center: Point
|
||||
|
||||
def pointOnOutside(line: Line) : Point = pointOnOutside(line.d)
|
||||
|
||||
def pointOnOutside(v: Vector3) : Point
|
||||
}
|
||||
|
||||
trait Geometry2D extends PrimitiveGeometry {
|
||||
def center: Point2D
|
||||
|
||||
def pointOnOutside(v: Vector3): Point2D = center
|
||||
}
|
||||
|
||||
trait Geometry3D extends PrimitiveGeometry {
|
||||
def center: Point3D
|
||||
|
||||
def pointOnOutside(v: Vector3): Point3D = center
|
||||
}
|
||||
|
||||
trait Point {
|
||||
def asVector3: Vector3
|
||||
}
|
||||
|
||||
trait Slope {
|
||||
def d: Vector3
|
||||
|
||||
def length: Float
|
||||
}
|
||||
|
||||
trait Line extends Slope {
|
||||
assert({
|
||||
val mag = Vector3.Magnitude(d)
|
||||
mag - 0.05f < 1f && mag + 0.05f > 1f
|
||||
}, "not a unit vector")
|
||||
|
||||
def p: Point
|
||||
|
||||
def length: Float = Float.PositiveInfinity
|
||||
}
|
||||
|
||||
trait Segment extends Slope {
|
||||
def p1: Point
|
||||
|
||||
def p2: Point
|
||||
|
||||
def length: Float = Vector3.Magnitude(d)
|
||||
|
||||
def asLine: PrimitiveGeometry
|
||||
}
|
||||
|
||||
final case class Point2D(x: Float, y: Float) extends Geometry2D with Point {
|
||||
def center: Point2D = this
|
||||
|
||||
def asVector3: Vector3 = Vector3(x, y, 0)
|
||||
}
|
||||
|
||||
object Point2D {
|
||||
def apply(): Point2D = Point2D(0, 0)
|
||||
|
||||
def apply(v: Vector3): Point2D = Point2D(v.x, v.y)
|
||||
}
|
||||
|
||||
final case class Ray2D(p: Point2D, d: Vector3) extends Geometry2D with Line {
|
||||
def center: Point2D = p
|
||||
}
|
||||
|
||||
object Ray2D {
|
||||
def apply(x: Float, y: Float, d: Vector3): Ray2D = Ray2D(Point2D(x, y), d)
|
||||
}
|
||||
|
||||
final case class Line2D(p: Point2D, d: Vector3) extends Geometry2D with Line {
|
||||
def center: Point2D = p
|
||||
}
|
||||
|
||||
object Line2D {
|
||||
def apply(ax: Float, ay: Float, d: Vector3): Line2D = {
|
||||
Line2D(Point2D(ax, ay), d)
|
||||
}
|
||||
|
||||
def apply(ax: Float, ay: Float, bx: Float, by: Float): Line2D = {
|
||||
Line2D(Point2D(ax, ay), Vector3.Unit(Vector3(bx-ax, by-ay, 0)))
|
||||
}
|
||||
|
||||
def apply(p1: Point2D, p2: Point2D): Line2D = {
|
||||
Line2D(p1, Vector3.Unit(Vector3(p2.x-p1.x, p2.y-p1.y, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
final case class Segment2D(p1: Point2D, p2: Point2D) extends Geometry2D with Segment {
|
||||
def center: Point2D = Point2D(d * 0.5f)
|
||||
|
||||
def d: Vector3 = p2.asVector3 - p1.asVector3
|
||||
|
||||
def asLine: Line2D = Line2D(p1, Vector3.Unit(d))
|
||||
}
|
||||
|
||||
object Segment2D {
|
||||
def apply(ax: Float, ay: Float, bx: Float, by: Float): Segment2D = {
|
||||
Segment2D(Point2D(ax, ay), Point2D(bx, by))
|
||||
}
|
||||
|
||||
def apply(x: Float, y: Float, d: Vector3): Segment2D = {
|
||||
Segment2D(x, y, x + d.x, y + d.y)
|
||||
}
|
||||
}
|
||||
|
||||
final case class Circle(p: Point2D, radius: Float) extends Geometry2D {
|
||||
def center : Point2D = p
|
||||
|
||||
override def pointOnOutside(v: Vector3) : Point2D = {
|
||||
val slope = Vector3.Unit(v)
|
||||
val pointOnRim = p.asVector3 + slope * radius
|
||||
Point2D(pointOnRim.x, pointOnRim.y)
|
||||
}
|
||||
}
|
||||
|
||||
object Circle {
|
||||
def apply(radius: Float): Circle = Circle(Point2D(), radius)
|
||||
|
||||
def apply(x: Float, y: Float, radius: Float): Circle = Circle(Point2D(x, y), radius)
|
||||
}
|
||||
|
||||
|
||||
final case class Point3D(x: Float, y: Float, z: Float) extends Geometry3D with Point {
|
||||
def center: Point3D = this
|
||||
|
||||
def asVector3: Vector3 = Vector3(x, y, z)
|
||||
}
|
||||
|
||||
object Point3D {
|
||||
def apply(): Point3D = Point3D(0,0,0)
|
||||
|
||||
def apply(v: Vector3): Point3D = Point3D(v.x, v.y, v.z)
|
||||
}
|
||||
|
||||
final case class Ray3D(p: Point3D, d: Vector3) extends Geometry3D with Line {
|
||||
def center: Point3D = p
|
||||
}
|
||||
|
||||
object Ray3D {
|
||||
def apply(x: Float, y: Float, z: Float, d: Vector3): Ray3D = Ray3D(Point3D(x,y,z), d)
|
||||
}
|
||||
|
||||
final case class Line3D(p: Point3D, d: Vector3) extends Geometry3D with Line {
|
||||
def center: Point3D = p
|
||||
}
|
||||
|
||||
object Line3D {
|
||||
def apply(x: Float, y: Float, z: Float, d: Vector3): Line3D = {
|
||||
Line3D(Point3D(x,y,z), d)
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
def apply(p1: Point3D, p2: Point3D): Line3D = {
|
||||
Line3D(p1, Vector3.Unit(Vector3(p2.x-p1.x, p2.y-p1.y, p2.z-p1.z)))
|
||||
}
|
||||
}
|
||||
|
||||
final case class Segment3D(p1: Point3D, p2: Point3D) extends Geometry3D with Segment {
|
||||
def center: Point3D = Point3D(d * 0.5f)
|
||||
|
||||
def d: Vector3 = p2.asVector3 - p1.asVector3
|
||||
|
||||
def asLine: Line3D = Line3D(p1, Vector3.Unit(d))
|
||||
}
|
||||
|
||||
object Segment3D {
|
||||
def apply(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float): Segment3D = {
|
||||
Segment3D(Point3D(ax, ay, az), Point3D(bx, by, bz))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
final case class Sphere(p: Point3D, radius: Float) extends Geometry3D {
|
||||
def center: Point3D = p
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
object Sphere {
|
||||
def apply(radius: Float): Sphere = Sphere(Point3D(), radius)
|
||||
|
||||
def apply(x: Float, y: Float, z: Float, radius: Float): Sphere = Sphere(Point3D(x,y,z), radius)
|
||||
|
||||
def apply(v: Vector3, radius: Float): Sphere = Sphere(Point3D(v), radius)
|
||||
}
|
||||
|
||||
final case class Cylinder(circle: Circle, z: Float, height: Float) extends Geometry3D {
|
||||
def center: Point3D = Point3D(circle.p.x, circle.p.y, z + height * 0.5f)
|
||||
|
||||
override def pointOnOutside(v: Vector3): Point3D = {
|
||||
val centerVector = center.asVector3
|
||||
val slope = Vector3.Unit(v)
|
||||
val mult = circle.radius / math.sqrt(slope.x * slope.x + slope.y * slope.y)
|
||||
val pointOnRim = centerVector + slope * mult.toFloat
|
||||
val point = if (z >= pointOnRim.z && pointOnRim.z <= height) { //side
|
||||
pointOnRim
|
||||
} else { //top or base
|
||||
val rise = height * 0.5f / slope.z
|
||||
centerVector + slope * rise
|
||||
}
|
||||
Point3D(point.x, point.y, point.z)
|
||||
}
|
||||
}
|
||||
|
||||
object Cylinder {
|
||||
def apply(x: Float, y: Float, z: Float, radius: Float, height: Float): Cylinder = {
|
||||
Cylinder(Circle(x, y, radius), z, height)
|
||||
}
|
||||
}
|
||||
|
||||
object Geometry {
|
||||
def equalFloats(value1: Float, value2: Float, off: Float = 0.001f): Boolean = {
|
||||
val diff = value1 - value2
|
||||
|
|
|
|||
|
|
@ -3,15 +3,13 @@ package net.psforever.objects.geometry
|
|||
|
||||
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player}
|
||||
import net.psforever.types.ExoSuitType
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
|
||||
object GeometryForm {
|
||||
/** this point can not be used for purposes of geometric representation */
|
||||
lazy val invalidPoint: Point3D = Point3D(Float.MinValue, Float.MinValue, Float.MinValue)
|
||||
/** this circle can not be used for purposes of geometric representation */
|
||||
lazy val invalidCircle: Circle = Circle(Point2D(invalidPoint.asVector3), 0)
|
||||
/** this cylinder can not be used for purposes of geometric representation */
|
||||
lazy val invalidCylinder: Cylinder = Cylinder(invalidCircle, Float.MinValue, 0)
|
||||
lazy val invalidCylinder: Cylinder = Cylinder(invalidPoint.asVector3, Vector3.Zero, Float.MinValue, 0)
|
||||
|
||||
/**
|
||||
* The geometric representation is the entity's centroid.
|
||||
|
|
@ -27,21 +25,43 @@ object GeometryForm {
|
|||
}
|
||||
|
||||
/**
|
||||
* The geometric representation is the a sphere around the entity's centroid.
|
||||
* The geometric representation is a sphere around the entity's centroid
|
||||
* positioned following the axis of rotation (the entity's base).
|
||||
* @param radius how wide a hemisphere is
|
||||
* @param o the entity
|
||||
* @return the representation
|
||||
*/
|
||||
def representBySphere(radius: Float)(o: Any): Geometry3D = {
|
||||
o match {
|
||||
case p: PlanetSideGameObject => Sphere(p.Position, radius)
|
||||
case s: SourceEntry => Sphere(s.Position, radius)
|
||||
case _ => Sphere(invalidPoint, radius)
|
||||
case p: PlanetSideGameObject =>
|
||||
Sphere(p.Position, radius)
|
||||
case s: SourceEntry =>
|
||||
Sphere(s.Position, radius)
|
||||
case _ =>
|
||||
Sphere(invalidPoint, radius)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The geometric representation is the a cylinder around the entity's base.
|
||||
* The geometric representation is a sphere around the entity's centroid
|
||||
* positioned following the axis of rotation (the entity's base).
|
||||
* @param radius how wide a hemisphere is
|
||||
* @param o the entity
|
||||
* @return the representation
|
||||
*/
|
||||
def representByRaisedSphere(radius: Float)(o: Any): Geometry3D = {
|
||||
o match {
|
||||
case p: PlanetSideGameObject =>
|
||||
Sphere(p.Position + Vector3.relativeUp(p.Orientation) * radius, radius)
|
||||
case s: SourceEntry =>
|
||||
Sphere(s.Position + Vector3.relativeUp(s.Orientation) * radius, radius)
|
||||
case _ =>
|
||||
Sphere(invalidPoint, radius)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The geometric representation is a cylinder around the entity's base.
|
||||
* @param radius half the distance across
|
||||
* @param height how tall the cylinder is (the distance of the top to the base)
|
||||
* @param o the entity
|
||||
|
|
@ -49,14 +69,14 @@ object GeometryForm {
|
|||
*/
|
||||
def representByCylinder(radius: Float, height: Float)(o: Any): Geometry3D = {
|
||||
o match {
|
||||
case p: PlanetSideGameObject => Cylinder(Circle(p.Position.x, p.Position.y, radius), p.Position.z, height)
|
||||
case s: SourceEntry => Cylinder(Circle(s.Position.x, s.Position.y, radius), s.Position.z, height)
|
||||
case p: PlanetSideGameObject => Cylinder(p.Position, Vector3.relativeUp(p.Orientation), radius, height)
|
||||
case s: SourceEntry => Cylinder(s.Position, Vector3.relativeUp(s.Orientation), radius, height)
|
||||
case _ => invalidCylinder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The geometric representation is the a cylinder around the entity's base
|
||||
* The geometric representation is a cylinder around the entity's base
|
||||
* if the target represents a player entity.
|
||||
* @param radius a measure of the player's bulk
|
||||
* @param o the entity
|
||||
|
|
@ -67,16 +87,17 @@ object GeometryForm {
|
|||
case p: Player =>
|
||||
val radialOffset = if(p.ExoSuit == ExoSuitType.MAX) 0.25f else 0f
|
||||
Cylinder(
|
||||
Circle(p.Position.x, p.Position.y, radius + radialOffset),
|
||||
p.Position.z,
|
||||
p.Position,
|
||||
radius + radialOffset,
|
||||
GlobalDefinitions.MaxDepth(p)
|
||||
)
|
||||
case p: PlayerSource =>
|
||||
val radialOffset = if(p.ExoSuit == ExoSuitType.MAX) 0.25f else 0f
|
||||
val radialOffset = if(p.ExoSuit == ExoSuitType.MAX) 0.125f else 0f
|
||||
val heightOffset = if(p.crouching) 1.093750f else GlobalDefinitions.avatar.MaxDepth
|
||||
Cylinder(
|
||||
Circle(p.Position.x, p.Position.y, radius + radialOffset),
|
||||
p.Position.z,
|
||||
GlobalDefinitions.avatar.MaxDepth
|
||||
p.Position,
|
||||
radius + radialOffset,
|
||||
heightOffset
|
||||
)
|
||||
case _ =>
|
||||
invalidCylinder
|
||||
|
|
@ -84,7 +105,7 @@ object GeometryForm {
|
|||
}
|
||||
|
||||
/**
|
||||
* The geometric representation is the a cylinder around the entity's base
|
||||
* The geometric representation is a cylinder around the entity's base
|
||||
* as if the target is displaced from the ground at an expected (fixed?) distance.
|
||||
* @param radius half the distance across
|
||||
* @param height how tall the cylinder is (the distance of the top to the base)
|
||||
|
|
@ -95,17 +116,9 @@ object GeometryForm {
|
|||
def representHoveringEntityByCylinder(radius: Float, height: Float, hoversAt: Float)(o: Any): Geometry3D = {
|
||||
o match {
|
||||
case p: PlanetSideGameObject =>
|
||||
Cylinder(
|
||||
Circle(p.Position.x, p.Position.y, radius),
|
||||
p.Position.z + hoversAt,
|
||||
height
|
||||
)
|
||||
Cylinder(p.Position, Vector3.relativeUp(p.Orientation), radius, height)
|
||||
case s: SourceEntry =>
|
||||
Cylinder(
|
||||
Circle(s.Position.x, s.Position.y, radius),
|
||||
s.Position.z + hoversAt,
|
||||
height
|
||||
)
|
||||
Cylinder(s.Position, Vector3.relativeUp(s.Orientation), radius, height)
|
||||
case _ =>
|
||||
invalidCylinder
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,158 +0,0 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.geometry
|
||||
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
object Intersection {
|
||||
object Test {
|
||||
/**
|
||||
* Do these two lines intersect?
|
||||
* Lines in 2D space will always intersect unless they are parallel or antiparallel.
|
||||
* In that case, however, they can still "intersect" if provided that the lines are coincidental.
|
||||
*/
|
||||
def apply(line1: Line2D, line2: Line2D): Boolean = {
|
||||
line1.d != line2.d || {
|
||||
//parallel or antiparallel?
|
||||
val u = Vector3.Unit(Vector3(line2.p.x - line1.p.x, line2.p.y - line1.p.y, 0))
|
||||
u == Vector3.Zero || line1.d == u || line1.d == Vector3.neg(u)
|
||||
}
|
||||
}
|
||||
|
||||
private def pointOnSegment(ax: Float, ay: Float, px: Float, py: Float, bx: Float, by: Float): Boolean = {
|
||||
px <= math.max(ax, bx) && px >= math.min(ax, bx) && py <= math.max(ay, by) && py >= math.min(ay, by)
|
||||
}
|
||||
|
||||
object PointTripleOrientation extends Enumeration {
|
||||
val Colinear, Clockwise, Counterclockwise = Value
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the orientation of the given three two-dimensional points.
|
||||
* Any triple has one of three orientations:
|
||||
* clockwise - the third point is to the right side of a line plotted by the first two points;
|
||||
* counterclockwise - the third point is to the left side of a line plotted by the first two points;
|
||||
* and, colinear - the third point is reachable along the line plotted by the first two points.
|
||||
* @param ax x-coordinate of the first point
|
||||
* @param ay y-coordinate of the first point
|
||||
* @param px x-coordinate of the second point
|
||||
* @param py y-coordinate of the second point
|
||||
* @param bx x-coordinate of the third point
|
||||
* @param by y-coordinate of the third point
|
||||
* @return the orientation value
|
||||
*/
|
||||
private def orientationOfPoints(
|
||||
ax: Float, ay: Float,
|
||||
px: Float, py: Float,
|
||||
bx: Float, by: Float
|
||||
): PointTripleOrientation.Value = {
|
||||
val out = (py - ay) * (bx - px) - (px - ax) * (by - py)
|
||||
if (out == 0) PointTripleOrientation.Colinear
|
||||
else if (out > 0) PointTripleOrientation.Clockwise
|
||||
else PointTripleOrientation.Counterclockwise
|
||||
}
|
||||
|
||||
/**
|
||||
* Do these two line segments intersect?
|
||||
* Intersection of two two-dimensional line segments can be determined by the orientation of their endpoints.
|
||||
* If a test of multiple ordered triple points reveals that certain triples have different orientations,
|
||||
* then we can safely assume the intersection state of the segments.
|
||||
*/
|
||||
def apply(line1: Segment2D, line2: Segment2D): Boolean = {
|
||||
//setup
|
||||
val ln1ax = line1.p1.x
|
||||
val ln1ay = line1.p1.y
|
||||
val ln1bx = line1.p2.x
|
||||
val ln1by = line1.p2.y
|
||||
val ln2ax = line2.p1.x
|
||||
val ln2ay = line2.p1.y
|
||||
val ln2bx = line2.p2.x
|
||||
val ln2by = line2.p2.y
|
||||
val ln1_ln2a = orientationOfPoints(ln1ax, ln1ay, ln1bx, ln1by, ln2ax, ln2ay)
|
||||
val ln1_ln2b = orientationOfPoints(ln1ax, ln1ay, ln1bx, ln1by, ln2bx, ln2by)
|
||||
val ln2_ln1a = orientationOfPoints(ln2ax, ln2ay, ln2bx, ln2by, ln1ax, ln1ay)
|
||||
val ln2_ln1b = orientationOfPoints(ln2ax, ln2ay, ln2bx, ln2by, ln1bx, ln1by)
|
||||
//results
|
||||
import PointTripleOrientation._
|
||||
(ln1_ln2a != ln1_ln2b && ln2_ln1a != ln2_ln1b) ||
|
||||
(ln1_ln2a == Colinear && pointOnSegment(ln1ax, ln1ay, ln2ax, ln2ay, ln1bx, ln1by)) || // line2 A is on line1
|
||||
(ln1_ln2b == Colinear && pointOnSegment(ln1ax, ln1ay, ln2bx, ln2by, ln1bx, ln1by)) || // line2 B is on line1
|
||||
(ln2_ln1a == Colinear && pointOnSegment(ln2ax, ln2ay, ln1ax, ln1ay, ln2bx, ln2by)) || // line1 A is on line2
|
||||
(ln2_ln1b == Colinear && pointOnSegment(ln2ax, ln2ay, ln1bx, ln1by, ln2bx, ln2by)) // line1 B is on line2
|
||||
}
|
||||
|
||||
/**
|
||||
* Do these two lines intersect?
|
||||
* Actual mathematically-sound intersection between lines and line segments in 3D-space is terribly uncommon.
|
||||
* Instead, check that the closest distance between two line segments is below a threshold value.
|
||||
*/
|
||||
def apply(line1: Line3D, line2: Line3D): Boolean = {
|
||||
apply(line1, line2, 0.15f)
|
||||
}
|
||||
def apply(line1: Line3D, line2: Line3D, threshold: Float): Boolean = {
|
||||
Closest.Distance(line1, line2) < threshold
|
||||
}
|
||||
|
||||
def apply(c1: Circle, c2 : Circle): Boolean = {
|
||||
Vector3.Magnitude(Vector3(c1.p.x - c2.p.x, c1.p.y - c2.p.y, 0)) <= c1.radius + c2.radius
|
||||
}
|
||||
|
||||
/**
|
||||
* Do these two line segments intersect?
|
||||
* Actual mathematically-sound intersection between lines and line segments in 3D-space is terribly uncommon.
|
||||
* Instead, check that the closest distance between two line segments is below a threshold value.
|
||||
*/
|
||||
def apply(seg1: Segment3D, seg2: Segment3D): Boolean = {
|
||||
apply(seg1, seg2, 0.15f)
|
||||
}
|
||||
def apply(seg1: Segment3D, seg2: Segment3D, threshold: Float): Boolean = {
|
||||
Closest.Distance(seg1, seg2) < threshold
|
||||
}
|
||||
|
||||
def apply(s1: Sphere, s2 : Sphere): Boolean = {
|
||||
Vector3.Magnitude(
|
||||
Vector3(
|
||||
s1.p.x - s2.p.x,
|
||||
s1.p.y - s2.p.y,
|
||||
s1.p.z - s2.p.z
|
||||
)
|
||||
) <= s1.radius + s2.radius
|
||||
}
|
||||
|
||||
def apply(c1: Cylinder, c2: Cylinder): Boolean = {
|
||||
apply(c1.circle, c2.circle) &&
|
||||
((c1.height >= c2.z && c1.z <= c2.height) || (c2.height >= c1.z && c2.z <= c1.height))
|
||||
}
|
||||
|
||||
def apply(cylinder: Cylinder, sphere: Sphere): Boolean = {
|
||||
val cylinderCircle = cylinder.circle
|
||||
val cylinderCircleRadius = cylinderCircle.radius
|
||||
val cylinderTop = cylinder.z + cylinder.height
|
||||
val sphereRadius = sphere.radius
|
||||
val sphereBase = sphere.p.z - sphereRadius
|
||||
val sphereTop = sphere.p.z + sphereRadius
|
||||
if (apply(cylinderCircle, Circle(sphere.p.x, sphere.p.y, sphereRadius)) &&
|
||||
((sphereTop >= cylinder.z && sphereBase <= cylinderTop) ||
|
||||
(cylinderTop >= sphereBase && cylinder.z <= sphereTop))) {
|
||||
// potential intersection ...
|
||||
val sphereAsPoint = Vector3(sphere.p.x, sphere.p.y, sphere.p.z)
|
||||
val cylinderAsPoint = Vector3(cylinderCircle.p.x, cylinderCircle.p.y, cylinder.z)
|
||||
val segmentFromCylinderToSphere = sphereAsPoint - cylinderAsPoint
|
||||
val segmentFromCylinderToSphereXY = segmentFromCylinderToSphere.xy
|
||||
if ((cylinder.z <= sphere.p.z && sphere.p.z <= cylinderTop) ||
|
||||
Vector3.MagnitudeSquared(segmentFromCylinderToSphereXY) <= cylinderCircleRadius * cylinderCircleRadius) {
|
||||
true // top or bottom of sphere, or widest part of the sphere, must interact with the cylinder
|
||||
} else {
|
||||
// only option left is the curves of the sphere interacting with the cylinder's rim, top or base
|
||||
val directionFromCylinderToSphere = Vector3.Unit(segmentFromCylinderToSphereXY)
|
||||
val pointOnCylinderRimBase = cylinderAsPoint + directionFromCylinderToSphere * cylinderCircleRadius
|
||||
val pointOnCylinderRimTop = pointOnCylinderRimBase + Vector3.z(cylinder.height)
|
||||
val sqSphereRadius = sphereRadius * sphereRadius
|
||||
Vector3.DistanceSquared(sphereAsPoint, pointOnCylinderRimTop) <= sqSphereRadius ||
|
||||
Vector3.DistanceSquared(sphereAsPoint, pointOnCylinderRimBase) <= sqSphereRadius
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.geometry
|
||||
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
trait PrimitiveGeometry {
|
||||
def center: Point
|
||||
|
||||
def pointOnOutside(line: Line) : Point = pointOnOutside(line.d)
|
||||
|
||||
def pointOnOutside(v: Vector3) : Point
|
||||
}
|
||||
|
||||
//trait Geometry2D extends PrimitiveGeometry {
|
||||
// def center: Point2D
|
||||
//
|
||||
// def pointOnOutside(v: Vector3): Point2D = center
|
||||
//}
|
||||
|
||||
trait Geometry3D extends PrimitiveGeometry {
|
||||
def center: Point3D
|
||||
|
||||
def pointOnOutside(v: Vector3): Point3D = center
|
||||
}
|
||||
|
||||
trait Point {
|
||||
def asVector3: Vector3
|
||||
}
|
||||
|
||||
trait Slope {
|
||||
def d: Vector3
|
||||
|
||||
def length: Float
|
||||
}
|
||||
|
||||
trait Line extends Slope {
|
||||
assert({
|
||||
val mag = Vector3.Magnitude(d)
|
||||
mag - 0.05f < 1f && mag + 0.05f > 1f
|
||||
}, "not a unit vector")
|
||||
|
||||
def p: Point
|
||||
|
||||
def length: Float = Float.PositiveInfinity
|
||||
}
|
||||
|
||||
trait Segment extends Slope {
|
||||
def p1: Point
|
||||
|
||||
def p2: Point
|
||||
|
||||
def length: Float = Vector3.Magnitude(d)
|
||||
|
||||
def asLine: PrimitiveGeometry
|
||||
}
|
||||
|
||||
final case class Point3D(x: Float, y: Float, z: Float) extends Geometry3D with Point {
|
||||
def center: Point3D = this
|
||||
|
||||
def asVector3: Vector3 = Vector3(x, y, z)
|
||||
}
|
||||
|
||||
object Point3D {
|
||||
def apply(): Point3D = Point3D(0,0,0)
|
||||
|
||||
def apply(v: Vector3): Point3D = Point3D(v.x, v.y, v.z)
|
||||
}
|
||||
|
||||
final case class Ray3D(p: Point3D, d: Vector3) extends Geometry3D with Line {
|
||||
def center: Point3D = p
|
||||
}
|
||||
|
||||
object Ray3D {
|
||||
def apply(x: Float, y: Float, z: Float, d: Vector3): Ray3D = Ray3D(Point3D(x,y,z), d)
|
||||
}
|
||||
|
||||
final case class Line3D(p: Point3D, d: Vector3) extends Geometry3D with Line {
|
||||
def center: Point3D = p
|
||||
}
|
||||
|
||||
object Line3D {
|
||||
def apply(x: Float, y: Float, z: Float, d: Vector3): Line3D = {
|
||||
Line3D(Point3D(x,y,z), d)
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
def apply(p1: Point3D, p2: Point3D): Line3D = {
|
||||
Line3D(p1, Vector3.Unit(Vector3(p2.x-p1.x, p2.y-p1.y, p2.z-p1.z)))
|
||||
}
|
||||
}
|
||||
|
||||
final case class Segment3D(p1: Point3D, p2: Point3D) extends Geometry3D with Segment {
|
||||
def center: Point3D = Point3D(d * 0.5f)
|
||||
|
||||
def d: Vector3 = p2.asVector3 - p1.asVector3
|
||||
|
||||
def asLine: Line3D = Line3D(p1, Vector3.Unit(d))
|
||||
}
|
||||
|
||||
object Segment3D {
|
||||
def apply(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float): Segment3D = {
|
||||
Segment3D(Point3D(ax, ay, az), Point3D(bx, by, bz))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
final case class Sphere(p: Point3D, radius: Float) extends Geometry3D {
|
||||
def center: Point3D = p
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
object Sphere {
|
||||
def apply(radius: Float): Sphere = Sphere(Point3D(), radius)
|
||||
|
||||
def apply(x: Float, y: Float, z: Float, radius: Float): Sphere = Sphere(Point3D(x,y,z), radius)
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
||||
object Cylinder {
|
||||
def apply(v: Vector3, radius: Float, height: Float): Cylinder = Cylinder(v, Vector3(0,0,1), radius, height)
|
||||
|
||||
def apply(p: Point3D, radius: Float, height: Float): Cylinder = Cylinder(p.asVector3, Vector3(0,0,1), radius, height)
|
||||
|
||||
def apply(p: Point3D, v: Vector3, radius: Float, height: Float): Cylinder = Cylinder(p.asVector3, v, radius, height)
|
||||
}
|
||||
|
|
@ -1135,7 +1135,7 @@ object Zone {
|
|||
.flatMap { _.Amenities }
|
||||
.filter { _.Definition.Damageable }
|
||||
}
|
||||
//restrict to targets in the damage radius
|
||||
//restrict to targets according to the detection plan
|
||||
val allAffectedTargets = (playerTargets ++ vehicleTargets ++ complexDeployableTargets ++ soiTargets)
|
||||
.filter { target =>
|
||||
(target ne obj) && detectionTest(obj, target, radius)
|
||||
|
|
@ -1177,11 +1177,11 @@ object Zone {
|
|||
* A default function literal mainly used for `causesExplosion`.
|
||||
* @see `causeExplosion`
|
||||
* @see `ObjectDefinition.Geometry`
|
||||
* @param obj1 a game entity
|
||||
* @param obj2 a game entity
|
||||
* @param obj1 a game entity, should be the source of the explosion
|
||||
* @param obj2 a game entity, should be the target of the explosion
|
||||
* @param maxDistance the square of the maximum distance permissible between game entities
|
||||
* before they are no longer considered "near"
|
||||
* @return `true`, if the target entities are near to each other;
|
||||
* @return `true`, if the target entities are near enough to each other;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def distanceCheck(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float): Boolean = {
|
||||
|
|
@ -1193,7 +1193,7 @@ object Zone {
|
|||
* @param g2 the geometric representation of a game entity
|
||||
* @param maxDistance the square of the maximum distance permissible between game entities
|
||||
* before they are no longer considered "near"
|
||||
* @return `true`, if the target entities are near to each other;
|
||||
* @return `true`, if the target entities are near enough to each other;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def distanceCheck(g1: Geometry3D, g2: Geometry3D, maxDistance: Float): Boolean = {
|
||||
|
|
|
|||
|
|
@ -387,8 +387,8 @@ object Vector3 {
|
|||
|
||||
/**
|
||||
* Given a `Vector3` element composed of Euler angles
|
||||
* and a `Vector3` element in the direction of "up",
|
||||
* find a standard unit vector that points in the direction of "up" after rotating by the Euler angles.
|
||||
* and a `Vector3` element in the vector direction of "up",
|
||||
* find a standard unit vector that points in the direction of the entity's "up" after rotating by the Euler angles.
|
||||
* Compass direction rules apply (North is 0 degrees, East is 90 degrees, etc.).
|
||||
* @see `Vector3.Rx(Float)`
|
||||
* @see `Vector3.Ry(Float)`
|
||||
|
|
@ -398,7 +398,12 @@ object Vector3 {
|
|||
* @return a mathematical vector representing a relative "up" direction
|
||||
*/
|
||||
def relativeUp(orient: Vector3, up: Vector3): Vector3 = {
|
||||
//TODO is the missing calculation before Rz(Rx(Ry(v, x), y), z) or after Rz(Ry(Rx(v, y), x), z)?
|
||||
Rz(Rx(up, orient.y), (orient.z + 180) % 360f)
|
||||
/*
|
||||
rotate in Ry using the x-component and rotate in Rx using the y-component
|
||||
only Rz is rotated using its corresponding component, and you add 180 clamping to 0-360 degrees
|
||||
I'm sure mathematicians know what's going on here, but I don't
|
||||
the purpose of this comment is to make certain that the future me knows that all this is not a mistake
|
||||
*/
|
||||
Rz(Rx(Ry(Unit(up), orient.x), orient.y), (orient.z + 180) % 360f)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,18 +257,62 @@ class Vector3Test extends Specification {
|
|||
|
||||
"find a relative up (y-rot)" in {
|
||||
Vector3.relativeUp(Vector3(0, 0, 0)) mustEqual Vector3(0,0,1) //up
|
||||
Vector3.relativeUp(Vector3(0, 90, 0)) mustEqual Vector3(0,-1,0) //north
|
||||
Vector3.relativeUp(Vector3(0, 90, 0)) mustEqual Vector3(0,-1,0) //south
|
||||
Vector3.relativeUp(Vector3(0, 180, 0)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(0, 270, 0)) mustEqual Vector3(0,1,0) //south
|
||||
Vector3.relativeUp(Vector3(0, 270, 0)) mustEqual Vector3(0,1,0) //north
|
||||
Vector3.relativeUp(Vector3(0, 360, 0)) mustEqual Vector3(0,0,1) //up
|
||||
}
|
||||
|
||||
"find a relative up (x-rot)" in {
|
||||
Vector3.relativeUp(Vector3(0, 0, 0)) mustEqual Vector3(0,0,1) //up
|
||||
Vector3.relativeUp(Vector3(90, 0, 0)) mustEqual Vector3(-1,0,0) //west
|
||||
Vector3.relativeUp(Vector3(180, 0, 0)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(270, 0, 0)) mustEqual Vector3(1,0,0) //east
|
||||
Vector3.relativeUp(Vector3(360, 0, 0)) mustEqual Vector3(0,0,1) //up
|
||||
}
|
||||
|
||||
"find a relative up (combined y,z)" in {
|
||||
Vector3.relativeUp(Vector3(0, 0, 90)) mustEqual Vector3(0,0,1) //up
|
||||
Vector3.relativeUp(Vector3(0, 90, 90)) mustEqual Vector3(-1,0,0) //west
|
||||
Vector3.relativeUp(Vector3(0, 180, 90)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(0, 270, 90)) mustEqual Vector3(1,0,0) //east
|
||||
Vector3.relativeUp(Vector3(0, 360, 90)) mustEqual Vector3(0,0,1) //up
|
||||
|
||||
Vector3.relativeUp(Vector3(0, 90, 180)) mustEqual Vector3(0,1,0) //north
|
||||
Vector3.relativeUp(Vector3(0, 180, 180)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(0, 270, 180)) mustEqual Vector3(0,-1,0) //south
|
||||
Vector3.relativeUp(Vector3(0, 360, 180)) mustEqual Vector3(0,0,1) //up
|
||||
|
||||
Vector3.relativeUp(Vector3(0, 90, 270)) mustEqual Vector3(1,0,0) //east
|
||||
Vector3.relativeUp(Vector3(0, 180, 270)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(0, 270, 270)) mustEqual Vector3(-1,0,0) //west
|
||||
Vector3.relativeUp(Vector3(0, 360, 270)) mustEqual Vector3(0,0,1) //up
|
||||
}
|
||||
|
||||
"find a relative up (combined x,z)" in {
|
||||
Vector3.relativeUp(Vector3(0, 0, 90)) mustEqual Vector3(0,0,1) //up
|
||||
Vector3.relativeUp(Vector3(90, 0, 90)) mustEqual Vector3(0,-1,0) //south
|
||||
Vector3.relativeUp(Vector3(180, 0, 90)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(270, 0, 90)) mustEqual Vector3(0,1,0) //north
|
||||
Vector3.relativeUp(Vector3(360, 0, 90)) mustEqual Vector3(0,0,1) //up
|
||||
|
||||
Vector3.relativeUp(Vector3(90, 0, 180)) mustEqual Vector3(1,0,0) //east
|
||||
Vector3.relativeUp(Vector3(180, 0, 180)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(270, 0, 180)) mustEqual Vector3(-1,0,0) //west
|
||||
Vector3.relativeUp(Vector3(360, 0, 180)) mustEqual Vector3(0,0,1) //up
|
||||
|
||||
Vector3.relativeUp(Vector3(90, 0, 270)) mustEqual Vector3(0,1,0) //north
|
||||
Vector3.relativeUp(Vector3(180, 0, 270)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(270, 0, 270)) mustEqual Vector3(0,-1,0) //south
|
||||
Vector3.relativeUp(Vector3(360, 0, 270)) mustEqual Vector3(0,0,1) //up
|
||||
}
|
||||
|
||||
"find a relative up (combined x,y)" in {
|
||||
val south = Vector3(0,-1,0)
|
||||
Vector3.relativeUp(Vector3(0, 90, 0)) mustEqual Vector3(0,-1,0) //south
|
||||
Vector3.relativeUp(Vector3(90, 90, 0)) mustEqual Vector3(-1,0,0) //west
|
||||
Vector3.relativeUp(Vector3(180, 90, 0)) mustEqual Vector3(0,1,0) //north
|
||||
Vector3.relativeUp(Vector3(270, 90, 0)) mustEqual Vector3(1,0,0) //east
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,533 +0,0 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package objects
|
||||
|
||||
import net.psforever.objects.geometry._
|
||||
import net.psforever.types.Vector3
|
||||
import org.specs2.mutable.Specification
|
||||
|
||||
class IntersectionTest extends Specification {
|
||||
"Line2D" should {
|
||||
"detect intersection on target points(s)" in {
|
||||
//these lines intersect at (0, 0)
|
||||
val result = Intersection.Test(
|
||||
Line2D(0,0, 1,0),
|
||||
Line2D(0,0, 0,1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection on a target point" in {
|
||||
//these lines intersect at (0, 0); start of segment 1, middle of segment 2
|
||||
val result = Intersection.Test(
|
||||
Line2D( 0,0, 0,1),
|
||||
Line2D(-1,0, 1,0)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection anywhere else" in {
|
||||
//these lines intersect at (0.5f, 0.5f)
|
||||
val result = Intersection.Test(
|
||||
Line2D(0,0, 1,1),
|
||||
Line2D(1,0, 0,1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection anywhere else (2)" in {
|
||||
//these lines intersect at (0, 0.5)
|
||||
val result = Intersection.Test(
|
||||
Line2D(0, 0, 1, 0),
|
||||
Line2D(0.5f,1, 0.5f,-1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"not detect intersection if the lines are parallel" in {
|
||||
val result = Intersection.Test(
|
||||
Line2D(0,0, 1,1),
|
||||
Line2D(1,0, 2,1)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
|
||||
"detect intersection if the lines overlap" in {
|
||||
//the lines are coincidental
|
||||
val result = Intersection.Test(
|
||||
Line2D(0,0, 1,1),
|
||||
Line2D(1,1, 2,2)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
}
|
||||
|
||||
"Segment2D" should {
|
||||
"detect intersection on target points(s)" in {
|
||||
//these line segments intersect at (0, 0)
|
||||
val result = Intersection.Test(
|
||||
Segment2D(0,0, 1,0),
|
||||
Segment2D(0,0, 0,1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection on a target point" in {
|
||||
//these line segments intersect at (0, 0); start of segment 1, middle of segment 2
|
||||
val result = Intersection.Test(
|
||||
Segment2D( 0,0, 0,1),
|
||||
Segment2D(-1,0, 1,0)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection anywhere else" in {
|
||||
//these line segments intersect at (0.5f, 0.5f)
|
||||
val result = Intersection.Test(
|
||||
Segment2D(0,0, 1,1),
|
||||
Segment2D(1,0, 0,1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection anywhere else (2)" in {
|
||||
//these line segments intersect at (0, 0.5)
|
||||
val result = Intersection.Test(
|
||||
Segment2D(0, 0, 1, 0),
|
||||
Segment2D(0.5f,1, 0.5f,-1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"not detect intersection if the lines are parallel" in {
|
||||
val result = Intersection.Test(
|
||||
Segment2D(0,0, 1,1),
|
||||
Segment2D(1,0, 2,1)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
|
||||
"detect intersection if the lines overlap" in {
|
||||
//the lines are coincidental
|
||||
val result = Intersection.Test(
|
||||
Line2D(0,0, 1,1),
|
||||
Line2D(1,1, 2,2)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
}
|
||||
|
||||
"Circle" should {
|
||||
"intersect when overlapping (coincidental)" in {
|
||||
val result = Intersection.Test(
|
||||
Circle(0,0, 1),
|
||||
Circle(0,0, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"intersect when overlapping (engulfed)" in {
|
||||
val result = Intersection.Test(
|
||||
Circle(0,0, 2),
|
||||
Circle(1,0, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"intersect when overlapping (partial 1)" in {
|
||||
val result = Intersection.Test(
|
||||
Circle(0,0, 2),
|
||||
Circle(2,0, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"intersect when overlapping (partial 2)" in {
|
||||
val result = Intersection.Test(
|
||||
Circle(0, 0, 2),
|
||||
Circle(2.5f,0, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"intersect when the circumferences are touching" in {
|
||||
val result = Intersection.Test(
|
||||
Circle(0,0, 2),
|
||||
Circle(3,0, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"not intersect when not touching" in {
|
||||
val result = Intersection.Test(
|
||||
Circle(0,0, 2),
|
||||
Circle(4,0, 1)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
}
|
||||
|
||||
"Line3D" should {
|
||||
"detect intersection on target point(s)" in {
|
||||
//these lines intersect at (0, 0, 0)
|
||||
val result = Intersection.Test(
|
||||
Line3D(0,0,0, Vector3(1,0,0)),
|
||||
Line3D(0,0,0, Vector3(0,1,0))
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection on a target point" in {
|
||||
//these lines intersect at (0, 0, 0); start of segment 1, middle of segment 2
|
||||
val result = Intersection.Test(
|
||||
Line3D(0,0,0, Vector3(0,1,0)),
|
||||
Line3D(-1,0,0, Vector3(1,0,0))
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection anywhere else" in {
|
||||
//these lines intersect at (0.5f, 0.5f, 0)
|
||||
val result = Intersection.Test(
|
||||
Line3D(0,0,0, Vector3.Unit(Vector3(1, 1, 0))),
|
||||
Line3D(1,0,0, Vector3(0,1,0))
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection anywhere else (2)" in {
|
||||
//these lines intersect at (0, 0.5, 0)
|
||||
val result = Intersection.Test(
|
||||
Line3D(0,0,0, Vector3(1,0,0)),
|
||||
Line3D(0.5f,1,0, Vector3.Unit(Vector3(0.5f,-1,0)))
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"not detect intersection if the lines are parallel" in {
|
||||
val result = Intersection.Test(
|
||||
Line3D(0,0,0, Vector3.Unit(Vector3(1,1,1))),
|
||||
Line3D(1,1,2, Vector3.Unit(Vector3(1,1,1)))
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
|
||||
"detect intersection if the lines overlap" in {
|
||||
//the sub-segment (1,0,0) to (2,0,0) is an overlap region shared between the two segments
|
||||
val result = Intersection.Test(
|
||||
Line3D(0,0,0, Vector3.Unit(Vector3(2,0,0))),
|
||||
Line3D(1,0,0, Vector3.Unit(Vector3(3,0,0)))
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"not detect intersection (generic skew)" in {
|
||||
//these segments will not intersect
|
||||
val result = Intersection.Test(
|
||||
Segment3D(-3,-8,7, Vector3.Unit(Vector3(-3,-9,8))),
|
||||
Segment3D(6,3,0, Vector3.Unit(Vector3(2,0,0)))
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
}
|
||||
|
||||
"Segment3D" should {
|
||||
"detect intersection of the first point(s)" in {
|
||||
//these segments intersect at (0, 0, 0)
|
||||
val result = Intersection.Test(
|
||||
Segment3D(0,0,0, 1,0,0),
|
||||
Segment3D(0,0,0, 0,1,0)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection of the first point" in {
|
||||
//these segments intersect at (0, 0, 0); start of segment 1, middle of segment 2
|
||||
val result = Intersection.Test(
|
||||
Segment3D(0,0,0, 0,2,0),
|
||||
Segment3D(-1,0,0, 1,0,0)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection on the farther point(s)" in {
|
||||
//these segments intersect at (0, 1, 0)
|
||||
val result = Intersection.Test(
|
||||
Segment3D(0,0,1, 0,1,0),
|
||||
Segment3D(1,0,0, 0,1,0)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection on the farther point" in {
|
||||
//these segments intersect at (1, 1, 0); end of segment 1, middle of segment 2
|
||||
val result = Intersection.Test(
|
||||
Segment3D(1,0,0, 1,1,0),
|
||||
Segment3D(2,0,0, 0,2,0)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection in the middle(s)" in {
|
||||
//these segments intersect at (0.5f, 0.5f, 0)
|
||||
val result = Intersection.Test(
|
||||
Segment3D(0,0,0, 1,1,0),
|
||||
Segment3D(1,0,0, 0,1,0)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection in the middle " in {
|
||||
//these segments intersect at (0, 0.5, 0)
|
||||
val result = Intersection.Test(
|
||||
Segment3D(0,0,0, 1,0,0),
|
||||
Segment3D(0.5f,1,0, 0.5f,-1,0)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"not detect intersection if the point of intersection would be before the start of the segments" in {
|
||||
//these segments will not intersect as segments; but, as lines, they would intersect at (0, 0, 0)
|
||||
val result = Intersection.Test(
|
||||
Segment3D(1,1,0, 2,2,0),
|
||||
Segment3D(1,0,0, 2,0,0)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
|
||||
"not detect intersection if the point of intersection would be after the end of the segments" in {
|
||||
//these segments will not intersect as segments; but, as lines, they would intersect at (2, 2, 0)
|
||||
val result = Intersection.Test(
|
||||
Segment3D(0,0,0, 1,1,0),
|
||||
Segment3D(2,0,0, 2,1,0)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
|
||||
"not detect intersection if the line segments are parallel" in {
|
||||
val result = Intersection.Test(
|
||||
Segment3D(0,0,0, 1,1,1),
|
||||
Segment3D(1,1,2, 2,2,3)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
|
||||
"detect intersection with overlapping" in {
|
||||
//the sub-segment (1,0,0) to (2,0,0) is an overlap region shared between the two segments
|
||||
val result = Intersection.Test(
|
||||
Segment3D(0,0,0, 2,0,0),
|
||||
Segment3D(1,0,0, 3,0,0)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"not detect intersection with coincidental, non-overlapping" in {
|
||||
//the sub-segment (1,0,0) to (2,0,0) is an overlap region shared between the two segments
|
||||
val result = Intersection.Test(
|
||||
Segment3D(0,0,0, 1,0,0),
|
||||
Segment3D(2,0,0, 3,0,0)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
|
||||
"not detect intersection (generic skew)" in {
|
||||
//these segments will not intersect
|
||||
val result = Intersection.Test(
|
||||
Segment3D(-3,-8,7, -3,-9,8),
|
||||
Segment3D(6,3,0, 2,0,0)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
}
|
||||
|
||||
"Sphere" should {
|
||||
"intersect when overlapping (coincidental)" in {
|
||||
val result = Intersection.Test(
|
||||
Sphere(Vector3.Zero, 1),
|
||||
Sphere(Vector3.Zero, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"intersect when overlapping (engulfed)" in {
|
||||
val result = Intersection.Test(
|
||||
Sphere(Vector3.Zero, 5),
|
||||
Sphere(Vector3(1,0,0), 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"intersect when overlapping (partial 1)" in {
|
||||
val result = Intersection.Test(
|
||||
Sphere(Vector3.Zero, 2),
|
||||
Sphere(Vector3(2,0,0), 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"intersect when overlapping (partial 2)" in {
|
||||
val result = Intersection.Test(
|
||||
Sphere(Vector3.Zero, 2),
|
||||
Sphere(Vector3(2.5f,0,0), 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"intersect when the circumferences are touching" in {
|
||||
val result = Intersection.Test(
|
||||
Sphere(Vector3.Zero, 2),
|
||||
Sphere(Vector3(3,0,0), 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"not intersect when not touching" in {
|
||||
val result = Intersection.Test(
|
||||
Sphere(Vector3.Zero, 2),
|
||||
Sphere(Vector3(4,0,0), 1)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
}
|
||||
|
||||
"Cylinder" should {
|
||||
"detect intersection if overlapping" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 2),
|
||||
Cylinder(0, 0, 0, 1, 2)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection if sides clip" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 2),
|
||||
Cylinder(0.5f, 0.5f, 0, 1, 2)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection if touching" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 2),
|
||||
Cylinder(1, 0, 0, 1, 2)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection if stacked" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(1, 0, 0, 1, 2),
|
||||
Cylinder(1, 0, 2, 1, 2)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection if one is sunken into the other" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(1, 0, 0, 1, 2),
|
||||
Cylinder(1, 0, 1, 1, 2)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"not detect intersection if not near each other" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 2),
|
||||
Cylinder(2, 2, 0, 1, 2)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
|
||||
"not detect intersection if one is too high / low" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(1, 0, 0, 1, 2),
|
||||
Cylinder(1, 0, 5, 1, 2)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
}
|
||||
|
||||
"Cylinder and Sphere" should {
|
||||
"detect intersection if overlapping" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(1, 0, 0, 1, 1),
|
||||
Sphere(1, 0, 2, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection if cylinder top touches sphere base" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 1),
|
||||
Sphere(1, 0, 2, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection if cylinder base touches sphere top" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 1),
|
||||
Sphere(-1, 0, -1, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection if cylinder edge touches sphere edge" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 1),
|
||||
Sphere(2, 0, 0.5f, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection if on cylinder top rim" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 1),
|
||||
Sphere(1.75f, 0, 1.25f, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"detect intersection if on cylinder base rim" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 1),
|
||||
Sphere(1.75f, 0, -0.5f, 1)
|
||||
)
|
||||
result mustEqual true
|
||||
}
|
||||
|
||||
"not detect intersection if too far above" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 1),
|
||||
Sphere(0, 0, 3, 1)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
|
||||
"not detect intersection if too far below" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 1),
|
||||
Sphere(0, 0, -3, 1)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
|
||||
"not detect intersection if too far out (sideways)" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 1),
|
||||
Sphere(2, 2, 0, 1)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
|
||||
"not detect intersection if too far out (skew)" in {
|
||||
val result = Intersection.Test(
|
||||
Cylinder(0, 0, 0, 1, 1),
|
||||
Sphere(1.5f, 1.5f, 1.5f, 1)
|
||||
)
|
||||
result mustEqual false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object GeometryTest { }
|
||||
Loading…
Reference in a new issue