- Joined
- Mar 18, 2012
- Messages
- 1,716
Projectile Collision
Code
Library ProjectileCollision is a system that extends
library Projectile by collision detection in 2D and 3D space.
ProjectileCollision is required to configurate free flying projectiles
without predetermined target widget.
Code
Library ProjectileCollision is a system that extends
library Projectile by collision detection in 2D and 3D space.
ProjectileCollision is required to configurate free flying projectiles
without predetermined target widget.
~~~~~~~~~~~~~~~~~~~~~~~~~~

- Copy Library Projectile into your map.
- Copy Library ProjectileCollision into your map.
- Initialize your custom unit heights in function InitBodySize.
~~~~~~~~~~~~~~~~~~~~~~~~~~

JASS:
library ProjectileCollision /* Version 1.0 by BPower
*************************************************************************************
*
* Collision detection for library Projectile
*
*************************************************************************************
*
* Import instructions:
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* • Copy library ProjectileCollision into to your map.
* • Initialize custom unit heights in function InitBodySize. ( Optional )
*
*************************************************************************************
*
* */ initializer InitBodySize /* ( See function below the API documentation )
* */ requires Projectile /*
* */ optional GetUnitCollision /* github.com/nestharus/JASS/tree/master/jass/Systems/GetUnitCollision
*
*************************************************************************************
*
* API:
* ¯¯¯¯
* function SetUnitBodySize takes integer unitId, real size returns nothing
* function GetUnitBodySize takes unit whichUnit returns real
*
* struct Projectile
*
* public boolean collision3D = true
* • Set this.collision3D to false if you want 2D collision detection.
*
* method hitWidget takes widget w returns nothing
* method hasHitWidget takes widget w returns boolean
* method removeHitWidget takes widget w returns nothing
* method flushHitWidgets takes nothing returns nothing
*
* method cacheUnitPos takes nothing returns nothing
* • Must always be called before using one of the methods below.
*
* --> readonly real unitX
* --> readonly real unitY
* --> readonly real unitZ // Combined terrain z and unit fly height.
*
* method isUnitInRange takes unit whichUnit, real range returns boolean
* • Returns true for units in 3D collision range.
*
* method isItemInRange takes item whichItem, real range returns boolean
* • Returns true for items in 3D collision range.
*
* method isDestructableInRange takes destructable d, real range returns boolean
* • Returns true for destructables in 3D collision range.
*
* method isUnitInRect takes unit whichUnit, real range returns boolean
* method isItemInRect takes item whichItem, real range returns boolean
* method isDestructableInRect takes destructable d, real range returns boolean
*
*************************************************************************************/
// User settings:
// ==============
// Initialize custom body size values per unit type id.
// By default any unit uses UNIT_BODY_SIZE from the Projectile library constants.
private function InitBodySize takes nothing returns nothing
call SetUnitBodySize('hfoo', 100.00)
// ...
endfunction
//=============================================================================
// Projectile Collision Code.
//=============================================================================
// Doesn't consider unit scaling, because of the difficulty
// of properly catching Avatar, Bloodlust and SetUnitScale effects.
//! textmacro PROJECTILE_COLLISION_UTILTIY_CODE
function GetUnitBodySize takes unit whichUnit returns real
if HaveSavedReal(HASH, KEY_BODY_SIZE, GetUnitTypeId(whichUnit)) then
return LoadReal(HASH, KEY_BODY_SIZE, GetUnitTypeId(whichUnit))
endif
return UNIT_BODY_SIZE
endfunction
function SetUnitBodySize takes integer unitId, real size returns nothing
call SaveReal(HASH, KEY_BODY_SIZE, unitId, size)
endfunction
// An avarage finalizer should be able to inline this function.
private function GetUnitPathing takes unit whichUnit returns real
static if LIBRARY_GetUnitCollision then
return GetUnitCollision(whichUnit)
else
return UNIT_PATHING_RADIUS
endif
endfunction
//! endtextmacro
//! textmacro PROJECTILE_COLLISION_CODE
//===================================================================
// Collision filters
// Readonly variables for the code inside the ProjectileStruct module.
readonly static boolexpr unitFilter = null
readonly static boolexpr itemFilter = null
readonly static boolexpr destFilter = null
// Filter function array
private static boolexpr array filter
// Filter constants
private static constant integer UNIT_FILTER_2D = 0
private static constant integer ITEM_FILTER_2D = 1
private static constant integer DEST_FILTER_2D = 2
private static constant integer UNIT_FILTER_3D = 3
private static constant integer ITEM_FILTER_3D = 4
private static constant integer DEST_FILTER_3D = 5
private static constant integer UNIT_FILTER_RECT = 6
private static constant integer ITEM_FILTER_RECT = 7
private static constant integer DEST_FILTER_RECT = 8
//===================================================================
// Collision type
public boolean collision3D = true
//===================================================================
// Last cached projectile position.
readonly real unitX
readonly real unitY
readonly real unitZ // Combined terrain z and unit fly height.
method cacheUnitPos takes nothing returns nothing
set unitX = GetUnitX(dummy)
set unitY = GetUnitY(dummy)
set unitZ = GetUnitFlyHeight(dummy) + GetTerrainZ(unitX, unitY)
endmethod
method prepareCollision takes nothing returns nothing
local real r = collision + Projectile_MAX_COLLISION_SIZE
call cacheUnitPos()
if speed <= collision then
if collision3D then
set unitFilter = filter[UNIT_FILTER_3D]
set destFilter = filter[DEST_FILTER_3D]
set itemFilter = filter[ITEM_FILTER_3D]
else
set unitFilter = filter[UNIT_FILTER_2D]
set destFilter = filter[DEST_FILTER_2D]
set itemFilter = filter[ITEM_FILTER_2D]
endif
call SetRect(ENUM_RECT, unitX - r, unitY - r, unitX + r, unitY + r)
return
endif
set unitFilter = filter[UNIT_FILTER_RECT]
set destFilter = filter[DEST_FILTER_RECT]
set itemFilter = filter[ITEM_FILTER_RECT]
if prevX < x then
if prevY < y then
call SetRect(ENUM_RECT, prevX - r, prevY - r, x + r, y + r)
else
call SetRect(ENUM_RECT, prevX - r, y - r, x + r, prevY + r)
endif
elseif prevY < y then
call SetRect(ENUM_RECT, x - r, prevY - r, prevX + r, y + r)
else
call SetRect(ENUM_RECT, x - r, y - r, prevX + r, prevY + r)
endif
endmethod
//===================================================================
// Projectile collision API
// Hashtable wrappers
method hitWidget takes widget whichWidget returns nothing
call SaveWidgetHandle(HASH, this, GetHandleId(whichWidget), whichWidget)
endmethod
method hasHitWidget takes widget whichWidget returns boolean
return HaveSavedHandle(HASH, this, GetHandleId(whichWidget))
endmethod
method removeHitWidget takes widget whichWidget returns nothing
call RemoveSavedHandle(HASH, this, GetHandleId(whichWidget))
endmethod
method flushHitWidgets takes nothing returns nothing
call FlushChildHashtable(HASH, this)
call SaveWidgetHandle(HASH, this, GetHandleId(dummy), dummy)
endmethod
method isUnitInRange takes unit whichUnit, real range returns boolean
call MoveLocation(LOC, GetUnitX(whichUnit), GetUnitY(whichUnit))
set tempZ = unitZ - GetUnitFlyHeight(whichUnit) - GetLocationZ(LOC)
if GetUnitTypeId(dummy) == Projectile_DUMMY_UNIT_TYPE_ID then
return IsUnitInRange(whichUnit, dummy, range) and tempZ - range < GetUnitBodySize(whichUnit) and tempZ > -range
endif
return IsUnitInRange(whichUnit, dummy, range) and tempZ < GetUnitBodySize(whichUnit) and tempZ > -GetUnitBodySize(dummy)
endmethod
method isItemInRange takes item whichItem, real range returns boolean
set tempX = GetItemX(whichItem)
set tempY = GetItemY(whichItem)
call MoveLocation(LOC, tempX, tempY)
set tempX = unitX - tempX
set tempY = unitY - tempY
set tempZ = unitZ - GetLocationZ(LOC) - ITEM_PATHING_RADIUS
return tempX*tempX + tempY*tempY + tempZ*tempZ < range*range + ITEM_PATHING_RADIUS*ITEM_PATHING_RADIUS
endmethod
method isDestructableInRange takes destructable d, real range returns boolean
set tempX = GetDestructableX(d)
set tempY = GetDestructableY(d)
call MoveLocation(LOC, tempX, tempY)
set tempZ = unitZ - GetLocationZ(LOC)
if GetUnitTypeId(dummy) == Projectile_DUMMY_UNIT_TYPE_ID then
return IsUnitInRangeXY(dummy, tempX, tempY, range + DEST_PATHING_RADIUS)/*
*/ and tempZ - range < GetDestructableOccluderHeight(d) and tempZ > -range
endif
return IsUnitInRangeXY(dummy, tempX, tempY, range + DEST_PATHING_RADIUS)/*
*/ and tempZ < GetDestructableOccluderHeight(d) and tempZ > -GetUnitBodySize(dummy)
endmethod
// Rect collision methods for fast moving projectiles. 2D and 3D.
method isUnitInRect takes unit whichUnit, real range returns boolean
local real uX = GetUnitX(whichUnit)
local real uY = GetUnitY(whichUnit)
local real uZ = GetUnitFlyHeight(whichUnit) + GetTerrainZ(uX, uY)
local real dX = x - prevX
local real dY = y - prevY
local real dZ = z + GetTerrainZ(x, y) - prevZ
local real s = (dX*(uX - prevX) + dY*(uY - prevY) + dZ*(uZ - prevZ))/(dX*dX + dY*dY + dZ*dZ)
local real mZ
if s < 0.00 then
set s = 0.00
elseif s > 1.00 then
set s = 1.00
endif
set mZ = prevZ + s*dZ
set dZ = dZ - uZ - range
return IsUnitInRangeXY(whichUnit, prevX + s*dX, prevY + s*dY, range + GetUnitPathing(whichUnit))/*
*/ and (dZ < mZ and dZ + GetUnitBodySize(whichUnit) < mZ or not collision3D)
endmethod
method isDestructableInRect takes destructable d, real range returns boolean
local real wX = GetDestructableX(d)
local real wY = GetDestructableY(d)
local real wZ = GetTerrainZ(wX, wY)
local real dX = x - prevX
local real dY = y - prevY
local real dZ = z + GetTerrainZ(x, y) - prevZ
local real s = (dX*(wX - prevX) + dY*(wY - prevY) + dZ*(wZ - prevZ))/(dX*dX + dY*dY + dZ*dZ)
if s < 0.00 then
set s = 0.00
elseif s > 1.00 then
set s = 1.00
endif
set dX = prevX + s*dX - wX
set dY = prevY + s*dY - wY
set dZ = prevZ + s*dZ
set wZ = dZ - wZ - range
return dX*dX + dY*dY < range*range + DEST_PATHING_RADIUS*DEST_PATHING_RADIUS/*
*/ and (wZ < dZ and wZ + GetDestructableOccluderHeight(d) > dZ or not collision3D)
endmethod
method isItemInRect takes item whichItem, real range returns boolean
local real iX = GetItemX(whichItem)
local real iY = GetItemY(whichItem)
local real iZ = GetTerrainZ(iX, iY) + ITEM_PATHING_RADIUS
local real dX = x - prevX
local real dY = y - prevY
local real dZ = z + GetTerrainZ(x, y) - prevZ
local real s = (dX*(iX - prevX) + dY*(iY - prevY) + dZ*(iZ - prevZ))/(dX*dX + dY*dY + dZ*dZ)
if s < 0.00 then
set s = 0.00
elseif s > 1.00 then
set s = 1.00
endif
set dX = prevX + s*dX - iX
set dY = prevY + s*dY - iY
if collision3D then
set dZ = prevZ + s*dZ - iZ
return dX*dX +dY*dY + dZ*dZ < range*range + ITEM_PATHING_RADIUS*ITEM_PATHING_RADIUS
endif
return dX*dX + dY*dY < range*range + ITEM_PATHING_RADIUS*ITEM_PATHING_RADIUS
endmethod
//===================================================================
// Projectile projectile collision.
private static method enumProjectiles takes nothing returns nothing
local thistype this = loopIndex
local thistype node = thistype.first
local boolean fast = speed > collision
loop
exitwhen node == 0
if not HaveSavedHandle(HASH, this, GetHandleId(node.dummy)) then
if fast then
// Actually an incorrect calculation. I'll fix it somewhen.
if isUnitInRect(node.dummy, collision) then
call GroupAddUnit(FILTER_GROUP, node.dummy)
endif
elseif collision3D then
set tempX = unitX - node.x
set tempY = unitY - node.y
call MoveLocation(LOC, node.x, node.y)
set tempZ = unitZ - node.z - GetLocationZ(LOC)
if GetUnitTypeId(node.dummy) == Projectile_DUMMY_UNIT_TYPE_ID then
if tempX*tempX + tempY*tempY + tempZ*tempZ < collision*collision + node.collision*node.collision then
call GroupAddUnit(FILTER_GROUP, node.dummy)
endif
elseif isUnitInRange(node.dummy, collision) then
call GroupAddUnit(FILTER_GROUP, node.dummy)
endif
elseif IsUnitInRange(dummy, node.dummy, collision + node.collision) then
call GroupAddUnit(FILTER_GROUP, node.dummy)
endif
endif
set node = node.next
endloop
endmethod
method enumForOnProjectile takes code actionFunc returns nothing
call cacheUnitPos()
call GroupClear(FILTER_GROUP)
call ForForce(bj_FORCE_PLAYER[0], function thistype.enumProjectiles)
call ForGroup(FILTER_GROUP, actionFunc)
endmethod
//===================================================================
// Filter conditions
private static method filterUnit2D takes nothing returns boolean
return not HaveSavedHandle(HASH, loopIndex, GetHandleId(GetFilterUnit()))/*
*/ and IsUnitInRange(GetFilterUnit(), loopIndex.dummy, loopIndex.collision)
endmethod
private static method filterItem2D takes nothing returns boolean
return not HaveSavedHandle(HASH, loopIndex, GetHandleId(GetFilterItem()))/*
*/ and IsUnitInRangeXY(loopIndex.dummy, GetItemX(GetFilterItem()), GetItemY(GetFilterItem()), loopIndex.collision + ITEM_PATHING_RADIUS)
endmethod
private static method filterDest2D takes nothing returns boolean
return not HaveSavedHandle(HASH, loopIndex, GetHandleId(GetFilterDestructable()))/*
*/ and IsUnitInRangeXY(loopIndex.dummy, GetDestructableX(GetFilterDestructable()), GetDestructableY(GetFilterDestructable()), loopIndex.collision + DEST_PATHING_RADIUS)
endmethod
private static method filterUnit3D takes nothing returns boolean
return not HaveSavedHandle(HASH, loopIndex, GetHandleId(GetFilterUnit()))/*
*/ and loopIndex.isUnitInRange(GetFilterUnit(), loopIndex.collision)
endmethod
private static method filterItem3D takes nothing returns boolean
return not HaveSavedHandle(HASH, loopIndex, GetHandleId(GetFilterItem()))/*
*/ and loopIndex.isItemInRange(GetFilterItem(), loopIndex.collision)
endmethod
private static method filterDest3D takes nothing returns boolean
return not HaveSavedHandle(HASH, loopIndex, GetHandleId(GetFilterDestructable()))/*
*/ and loopIndex.isDestructableInRange(GetFilterDestructable(), loopIndex.collision)
endmethod
private static method filterUnitRect takes nothing returns boolean
return not HaveSavedHandle(HASH, loopIndex, GetHandleId(GetFilterUnit()))/*
*/ and loopIndex.isUnitInRect(GetFilterUnit(), loopIndex.collision)
endmethod
private static method filterItemRect takes nothing returns boolean
return not HaveSavedHandle(HASH, loopIndex, GetHandleId(GetFilterItem()))/*
*/ and loopIndex.isItemInRect(GetFilterItem(), loopIndex.collision)
endmethod
private static method filterDestRect takes nothing returns boolean
return not HaveSavedHandle(HASH, loopIndex, GetHandleId(GetFilterDestructable()))/*
*/ and loopIndex.isDestructableInRect(GetFilterDestructable(), loopIndex.collision)
endmethod
//! endtextmacro
//===================================================================
// Collision init
//! textmacro PROJECTILE_COLLISION_ON_INIT
set filter[UNIT_FILTER_2D] = Filter(function thistype.filterUnit2D)
set filter[ITEM_FILTER_2D] = Filter(function thistype.filterItem2D)
set filter[DEST_FILTER_2D] = Filter(function thistype.filterDest2D)
set filter[UNIT_FILTER_3D] = Filter(function thistype.filterUnit3D)
set filter[ITEM_FILTER_3D] = Filter(function thistype.filterItem3D)
set filter[DEST_FILTER_3D] = Filter(function thistype.filterDest3D)
set filter[UNIT_FILTER_RECT] = Filter(function thistype.filterUnitRect)
set filter[ITEM_FILTER_RECT] = Filter(function thistype.filterItemRect)
set filter[DEST_FILTER_RECT] = Filter(function thistype.filterDestRect)
//! endtextmacro
endlibrary
~~~~~~~~~~~~~~~~~~~~~~~~~~

- In order to compile ProjectileCollision you'll need The Jass NewGen Pack.
- Projectile can draw back on the following optional requirements:
____- Nestharus's GetUnitCollision to obtain accurate unit pathing values.
~~~~~~~~~~~~~~~~~~~~~~~~~~

- -
Last edited: