- Joined
- Dec 16, 2010
- Messages
- 31
Another Projectile library!
I was looking through the xe system/modules and really liked how xefx and xecollider worked. However I noticed that it was a bit difficult to get angled/arced motion with the xecollider. I knew there were other systems like custom missile or xemissile but I decided to try to make my own.
A lot of base code is heavily inspired/taken/modified from xecollider. This includes the indexing/timer/recycling system. However I chose to use GroupUtils for group recycling because I am used to it.
Requirements:
- Vector3 (By me, included below)
- xebasic, xefx from the xe system by Vexorian
- GroupUtils
- A dummy model that has pitch animations. I use the model and object data
From the xe system linked above.
Notes:
- Vector3 is a library/struct for 3D vectors and related functionality.
- Uses xefx for the effects aspect of the library (delegates like xecollider does)
- The test map I am including has a bunch of junk for presentation, such as hero revival, damage numbers, cast bar, effects, etc.
- The test map uses JCast for the spell system. This is again just for presentation.
- I am aware that Projectile is a generic name for a struct, but I was thinking that you wouldn't normally have more than one projectile library/struct (would you?)
- A simple example of code is shown at the bottom of this post.
Pros:
- Detailed properties and useful events for fully controllable projectiles.
- Easy to use, by just extending and implementing events (like in xecollider)
- Fire and forget! The system cleans up its own vectors, projectiles, effects, groups etc.
Cons:
- Uses multiple Vector3 structs per projectile.
- Does not consider terrain height. So a projectile flying over bumpy terrain will follow the ground and might look like it's tripping out. I may change this later.
I was hoping to get some comments/suggestions/criticism.
This is my first system I am trying to polish.
Please try out the test map to see cool spells, and thanks for reading.
Here's the Projectile library:
And the vector library:
HOW TO USE:
Extra model resources thanks to: Vexorian (Dummy model), Dan van Ohllus (Missile), s4nji (Icy Spike)
I was looking through the xe system/modules and really liked how xefx and xecollider worked. However I noticed that it was a bit difficult to get angled/arced motion with the xecollider. I knew there were other systems like custom missile or xemissile but I decided to try to make my own.
A lot of base code is heavily inspired/taken/modified from xecollider. This includes the indexing/timer/recycling system. However I chose to use GroupUtils for group recycling because I am used to it.
Requirements:
- Vector3 (By me, included below)
- xebasic, xefx from the xe system by Vexorian
- GroupUtils
- A dummy model that has pitch animations. I use the model and object data
From the xe system linked above.
Notes:
- Vector3 is a library/struct for 3D vectors and related functionality.
- Uses xefx for the effects aspect of the library (delegates like xecollider does)
- The test map I am including has a bunch of junk for presentation, such as hero revival, damage numbers, cast bar, effects, etc.
- The test map uses JCast for the spell system. This is again just for presentation.
- I am aware that Projectile is a generic name for a struct, but I was thinking that you wouldn't normally have more than one projectile library/struct (would you?)
- A simple example of code is shown at the bottom of this post.
Pros:
- Detailed properties and useful events for fully controllable projectiles.
- Easy to use, by just extending and implementing events (like in xecollider)
- Fire and forget! The system cleans up its own vectors, projectiles, effects, groups etc.
Cons:
- Uses multiple Vector3 structs per projectile.
- Does not consider terrain height. So a projectile flying over bumpy terrain will follow the ground and might look like it's tripping out. I may change this later.
I was hoping to get some comments/suggestions/criticism.
This is my first system I am trying to polish.
Please try out the test map to see cool spells, and thanks for reading.
Here's the Projectile library:
JASS:
library AdvancedProjectile uses Vector3, xefx, GroupUtils
//---------------------------------------------------------------------//
// Advanced Projectile by rogueteddybear
//---------------------------------------------------------------------//
//
// This was inspired heavily by the xecollider part of xe0.8 by Vexorian
// You will find A LOT of similarities between the two.
// What I was trying to get with this is a smooth path effect that goes
// in all directions. This takes advantage of some vector math.
//---------------------------------------------------------------------//
//
// How to use:
//
// I recommend creating your own struct, and extending Projectile.
// This gives you access to events triggered automatically by the system.
//
// Events:
// __________________________________________________________________
// |onUnitHit takes unit hitTarget returns nothing defaults nothing
// | - hitTarget: the unit that the projectile passed through.
// |
// | Implement this if you want to detect when the projectile hit a unit.
// | This is the best time to do damage.
// +------------------------------------------------------------------
// |onTargetReached takes nothing returns nothing defaults nothing
// |
// | Implement this if you want to detect when the projectile
// | reaches its target (on the ground). By default this will trigger
// | only after onUnitHit thanks to collision size/distance checks.
// | Note: projectiles will circle around targets if they can't turn
// | well enough. This is under your control.
// +------------------------------------------------------------------
// |onHitGround takes nothing returns nothing defaults nothing
// |
// | Implement this if you want to detect when the projectile
// | hits the ground. This will trigger if the projectile STARTS
// | from the ground (z = 0.0), so to avoid that simply set
// | the origins vector's z value to something greater than 0 when
// | passing it to the create() function (see below)
// +------------------------------------------------------------------
// |onTimerTick takes nothing returns nothing defaults nothing
// |
// | This is the equivelent of loopControl from xecollider.
// | This gets called every update (every DEFAULT_INTERVAL)
// +------------------------------------------------------------------
// |onExpired takes nothing returns nothing defaults nothing
// |
// | This is triggered when the lifetime of the projectile reaches 0
// +------------------------------------------------------------------
//
// Creation/Destruction:
// __________________________________________________________________
// |static create(Vector3 origin, Vector3 direction, Vector3 target)
// |
// | - origin: a vector specifying where the projectile starts
// | - direction: a vector pointing in the direction the projectile
// | will move in at the start
// | - target: a vector specifying the initial target that the projectile
// | is headed fore. NOTE: this can and probably will change if
// | a homingTarget is specified.
// |
// | This creates the projectile and registers it for updates. At this point,
// | its speed, and acceleration is 0. So you have to also set those.
// | If you extend Projectile and need to use create, you can call:
// | allocate(origin, direction, target) inside your custom create.
// +------------------------------------------------------------------
// |Projectile::terminate()
// |
// | Similarly to xecollider, this is the safest way to destroy.
// | Try not to call destroy(), ever.
// +------------------------------------------------------------------
//
// Useful public properties (case sensitive):
// - [source]: set this to a unit to store the creator of the projectile.
// - [owner]: the player owner
// - [Source]: set this to set source, and owner at the same time
// - [homingTarget]: set this to make the projectile home in on someone
// - [speed], [maxSpeed], [acceleration]: movement vars you can change.
// - [maxTurnXY], [maxTurnZ]: left/right and up/down rotation amounts per second
// - [MaxTurn]: set both Z and XY rotation per second
// - [x],[y],[z]: position vars, from xefx
// - [fxpath]: the string path to the effect of the projectile.
// - [checkCollisionHeight]: if true, considers height in hit detection
// - [lifeTime]: The amount of time in seconds that this projectile
// has left before it terminates (counts down).
// - [homingDelay]: The amount of time to delay the homing functionality.
//
//---------------------------------------------------------------------//
globals
//the same constants as found in xecollider
private constant real DEFAULT_COLLISION_SIZE = 50.0
private constant real DEFAULT_MAX_SPEED = 1500.0
private constant real DEFAULT_EXPIRATION_TIME = 5.0
private constant real DEFAULT_TARGET_DISTANCE = 25.0 //max distance at which onTargetRecahed is triggered
//Rotation modes/styles:
//NORMAL: The projectile will rotate up/down in a more casual fasion.
// This is recommended for projectiles originating for the ground.
//AGGRESSIVE: The projectile will rotate up/down more directly, as if it were a missile.
// This is recommended for projectils originiating from the air (missiles, firebombs)
constant integer ROTATE_NORMAL = 0
constant integer ROTATE_AGGRESSIVE = 1
//similar to the constant in xebasic
private constant real DEFAULT_INTERVAL = 0.025
endglobals
//---------------------------------------------------------------------//
// IProjectile Interface
//---------------------------------------------------------------------//
// Interface for events that you can use when you extend Projectile.
interface IProjectile
method onUnitHit takes unit hitTarget returns nothing defaults nothing
method onTargetReached takes nothing returns nothing defaults nothing
method onHitGround takes nothing returns nothing defaults nothing
method onTimerTick takes nothing returns nothing defaults nothing
method onExpired takes nothing returns nothing defaults nothing
endinterface
//---------------------------------------------------------------------//
// Projectile Struct - Extend this!
//---------------------------------------------------------------------//
struct Projectile extends IProjectile
//effect carrier
private delegate xefx fx
//source of any damage
unit source = null
//unit to home in on, if needed
unit homingTarget = null
//Vectors for handling correct changes in position.
//I suppose I could have just used a bunch of reals, or some arrays of reals.
Vector3 forward = 0 //<-- I don't recommend changing this vector directly, use SetProjectileFacing()
Vector3 right = 0 //<-- Same as above.
Vector3 target = 0 //<-- If the projectile has homingTarget, this vector is updated constantly.
Vector3 position = 0
integer rotationMode = ROTATE_NORMAL
//basic properties - note these should be considered "per second"
real speed = 0.0
real maxSpeed = 0.0
real acceleration = 0.0
real maxTurnZ = 0.0
real maxTurnXY = (2.0 * bj_DEGTORAD)
//life time and time before homing starts
real lifeTime = 0.0
real homingDelay = 0.0
boolean hiddenExpiration = false
boolean checkCollisionHeight = false
//Static indexing stuff similar to in xecollider
private static timer TIMER
private static integer NUM_PROJECTILES = 0
private static Projectile array PROJECTILES
private static code timerLoopFunction
//collision + data storage
static group enumGroup //i made this public so structs that extend this can use it.
private static Projectile cinstance
private static unit array picked
private static integer pickedN
private real csize = DEFAULT_COLLISION_SIZE
private group seen
private boolean dead = false
private boolean silent = false
//test
//AxisDraw ad
static method create takes Vector3 origin, Vector3 direction, Vector3 target returns thistype
local thistype e = thistype.allocate()
set e.position = origin
set e.target = target
set e.Forward = direction
set e.fx = xefx.create(e.position.x, e.position.y, e.forward.xyAngle)
set e.lifeTime = DEFAULT_EXPIRATION_TIME
set e.maxSpeed = DEFAULT_MAX_SPEED
set e.seen = NewGroup()
set e.xyangle = e.forward.xyAngle
set e.zangle = e.forward.zAngle
//test
//set e.ad = AxisDraw.create(target.x, target.y, 0)
set Projectile.PROJECTILES[Projectile.NUM_PROJECTILES] = e
set Projectile.NUM_PROJECTILES = Projectile.NUM_PROJECTILES + 1
if(Projectile.NUM_PROJECTILES==1) then
call TimerStart(Projectile.TIMER, DEFAULT_INTERVAL, true, Projectile.timerLoopFunction )
endif
return e
endmethod
static method onInit takes nothing returns nothing
set timerLoopFunction = (function thistype.Update)
set TIMER = CreateTimer()
set enumGroup = NewGroup()
endmethod
//use this to destroy
method terminate takes nothing returns nothing
set this.dead=true
set this.fxpath=""
endmethod
method hiddenTerminate takes nothing returns nothing
set silent = true
call terminate()
endmethod
method onDestroy takes nothing returns nothing
call forward.destroy()
call target.destroy()
call position.destroy()
call right.destroy()
if(silent) then
call fx.hiddenDestroy()
else
call fx.destroy()
endif
call ReleaseGroup(seen)
set source = null
endmethod
static method Update takes nothing returns nothing
local Projectile this
local integer i = 0
local integer c = 0
local integer j = 0
loop
exitwhen i >= Projectile.NUM_PROJECTILES
set this = PROJECTILES[i]
//Check liftime, simlarly to xecollider
set this.lifeTime = this.lifeTime - DEFAULT_INTERVAL
set this.homingDelay = RMaxBJ(this.homingDelay - DEFAULT_INTERVAL, 0.0)
if(this.dead or (this.lifeTime <= 0.0) ) then
if(this.onExpired.exists) then
call this.onExpired()
endif
if(hiddenExpiration) then
set silent = true
endif
call this.destroy()
else
if(this.HasHomingTarget()) then
call target.update(GetUnitX(this.homingTarget), GetUnitY(this.homingTarget), GetUnitZ(this.homingTarget)+50.0)
endif
//move the projectile forward
call this.fwd()
//if the homing delay is complete, home in on the target
if(this.homingDelay <= 0.0) then
call this.steer()
endif
//Try collision at this point
set .cinstance = this
set Projectile.pickedN = 0
call GroupEnumUnitsInRange( .enumGroup, x, y, .csize + XE_MAX_COLLISION_SIZE, function thistype.inRangeEnum)
call GroupClear(this.seen)
set j=0
loop
exitwhen (j== Projectile.pickedN)
call GroupAddUnit( this.seen, Projectile.picked[j])
set j=j+1
endloop
//copy over destroyed structs
set PROJECTILES[c]=this
set c=c+1
//loop control like in xecollider
//call BJDebugMsg("Calling on Timer tick? "+B2S(this.onTimerTick.exists)+" vs "+B2S(this.onHitGround.exists))
if( this.onTimerTick.exists and not this.dead ) then
call this.onTimerTick()
endif
//target was reached, most likely land, not a unit.
if( this.onTargetReached.exists and not this.dead ) then
if(position.distSqr(target) < DEFAULT_TARGET_DISTANCE * DEFAULT_TARGET_DISTANCE) then
call this.onTargetReached()
endif
endif
//projectile hit the ground?
if( this.onHitGround.exists and not this.dead ) then
if(position.z <= 0) then
call this.onHitGround()
endif
endif
endif
set i = i + 1
endloop
//Pause the timer if there are no more projectiles
set Projectile.NUM_PROJECTILES = c
if(c==0) then
call PauseTimer(Projectile.TIMER)
endif
endmethod
//Collision detection filter
//copied and altered from xecollider
private static method inRangeEnum takes nothing returns boolean
local Projectile this = .cinstance //adopt-a-instance
local unit u=GetFilterUnit()
if not IsUnitType(u, UNIT_TYPE_DEAD) and not(this.dead) and (GetUnitTypeId(u)!=XE_DUMMY_UNITID) and IsUnitInRangeXY(u, x, y, .csize) then
// ah, the advantages of a standardized unit id...
if(not this.checkCollisionHeight or (this.checkCollisionHeight and this.HeightCheckSqr(u) <= .csize * .csize)) then
//call BJDebugMsg("HIT UNIT: check? "+B2S(this.checkCollisionHeight)+" check: "+this.HeightCheckSqr(u)
set Projectile.picked[Projectile.pickedN] = u
set Projectile.pickedN=Projectile.pickedN + 1
if not IsUnitInGroup (u, this.seen ) then
call this.onUnitHit(u)
endif
endif
endif
set u=null
return false
endmethod
//Move the projectile forward in the direction it is facing.
//Also calc speed acceleration
private method fwd takes nothing returns nothing
local real s = speed * DEFAULT_INTERVAL
set s = RMinBJ(s + acceleration * DEFAULT_INTERVAL, maxSpeed)
call position.update(position.x + forward.x * s, position.y + forward.y * s, position.z + forward.z * s)
set x = position.x
set y = position.y
set z = position.z
endmethod
//Turn left (radians). This can be done with a global rotate because
//the frame of reference never changes for x/y rotation
private method turnLeft takes real angle returns nothing
call forward.rotate(0, 0, angle)
call right.rotate(0, 0, angle)
endmethod
//Turn right (radians). This can be done with a global rotate because
//the frame of reference never changes for x/y rotation
private method turnRight takes real angle returns nothing
call forward.rotate(0, 0, -angle)
call right.rotate(0, 0, -angle)
endmethod
//Change the direction of the projectil so that it points closer to
//the target.
private method steer takes nothing returns nothing
local real sxy = maxTurnXY * DEFAULT_INTERVAL
local real sz = maxTurnZ * DEFAULT_INTERVAL
local Vector3 seek = VectorSeek(target, position)
local Vector3 up = VectorCrossProduct(right, forward)
local Vector3 seek2
local Vector3 targetUp
local real dotSide = 0.0
local real dotForward = 0.0
local real dotUp = 0.0
if(rotationMode == ROTATE_AGGRESSIVE) then
set targetUp = seek.copy()
else
set seek2 = VectorCrossProduct(seek, WORLD_UP)
set targetUp = VectorCrossProduct(seek2, seek)
endif
call targetUp.normalize()
call right.normalize()
call up.normalize()
set seek.z = 0
call seek.normalize()
set dotSide = right.dot(seek)
set dotForward = forward.dot(seek)
set dotUp = up.dot(targetUp)
//test
//call ad.Update(forward, right, up)
//Handle left/right steering
if(dotForward < 0) then
if(dotSide < 0) then
call turnLeft(sxy)
else
call turnRight(sxy)
endif
else
call turnRight(dotSide * sxy)
endif
set dotForward = forward.dot(targetUp)
//handle up/down steering.
//This uses local rotation because up/down rotation is relative
//to the projectile, and not the world.
if(rotationMode == ROTATE_AGGRESSIVE) then
//Direct rotation - the projectile will always turn toward the target
if(dotUp < 0.01) then
call forward.rotateAxis(WORLD_ZERO, right, (1.0-RAbsBJ(dotForward)) * -sz)
elseif(dotUp > 0.01) then
call forward.rotateAxis(WORLD_ZERO, right, (1.0-RAbsBJ(dotForward)) * sz)
endif
else
//Casual rotation, the projectil will not always turn directly toward the point.
if(dotUp < 0.0) then
if(dotForward > 0) then
call forward.rotateAxis(WORLD_ZERO, right, -sz)
else
call forward.rotateAxis(WORLD_ZERO, right, sz)
endif
else
call forward.rotateAxis(WORLD_ZERO, right, dotForward * -sz)
endif
call seek2.destroy()
endif
//rotate the effect so it lines up
set xyangle = forward.xyAngle
set zangle = forward.zAngle
//cleanup
call seek.destroy()
call targetUp.destroy()
call up.destroy()
endmethod
method HasHomingTarget takes nothing returns boolean
return homingTarget != null and GetUnitState(homingTarget, UNIT_STATE_LIFE) > 0.
endmethod
method GetUnitZ takes unit u returns real
local location xy = Location(GetUnitX(u),GetUnitY(u))
local real z = GetLocationZ(xy)
call RemoveLocation(xy)
set xy = null
return z
endmethod
//basically distance squared check, but put here
//so i wouldn't have other outside library dependencies
method HeightCheckSqr takes unit u returns real
local real dx = x - GetUnitX(u)
local real dy = y - GetUnitY(u)
local real dz = z - (GetUnitZ(u)+50.)
return dx * dx + dy * dy + dz * dz
endmethod
//Use this to set the direction the projectile is headed.
//This also sets a correct "right" vector, which is essential.
method operator Forward= takes Vector3 v returns nothing
local Vector3 temp = 0
if forward != 0 then
call forward.destroy()
endif
set forward = v
call forward.normalize()
if(v.equals(WORLD_UP))then
set temp = VectorSeek(target, position)
set right = VectorCrossProduct(temp, WORLD_UP)
call right.normalize()
call temp.destroy()
else
set right = VectorCrossProduct(forward, WORLD_UP)
call right.normalize()
endif
endmethod
//Use this to make the projectile face certain angles (in radians)
//However it will still home in on the target. To rotate the target as well,
//use SetProjectileFacingEx()
method SetProjectileFacing takes real angleXY, real angleZ returns nothing
call turnLeft(angleXY - forward.xyAngle)
call forward.rotateAxis(WORLD_ZERO, right, angleZ - forward.zAngle)
set xyangle = forward.xyAngle
set zangle = forward.zAngle
endmethod
//Rotate everything about this projectile to face a certain direction.
//The target location is rotated ournd the projectiles current position
//to keep it relatively accurate.
method SetProjectileFacingEx takes real angleXY, real angleZ returns nothing
local real diffXY = angleXY - forward.xyAngle
local real diffZ = angleZ - forward.zAngle
call SetProjectileFacing(angleXY, angleZ)
call target.rotateAxis(position, WORLD_UP, diffXY)
endmethod
method operator Source= takes unit u returns nothing
set source = u
if(u != null) then
set owner = GetOwningPlayer(u)
endif
endmethod
method operator MaxTurn= takes real a returns nothing
set maxTurnZ = a
set maxTurnXY = a
endmethod
//-------------------------------------------------//
//XEFX - overwritten operators for safety
method operator z= takes real value returns nothing
set position.z = value
set fx.z = value
endmethod
method operator x= takes real value returns nothing
set position.x = value
set fx.x = value
endmethod
method operator y= takes real value returns nothing
set position.y = value
set fx.y = value
endmethod
endstruct
endlibrary
And the vector library:
JASS:
library Vector3 initializer Init_Vector3
//---------------------------------------------------------------------//
// Vector3
//---------------------------------------------------------------------//
//
// This is a struct that represents an arrow or point in 3D space.
// It is pretty similar to most other 3D vector implementations.
//
// It is easy to get carried away creating vectors so you need to be
// sure to destroy them after use. NOTE: some function return brand new
// Vector3 structs. These functions/methods are labeled.
//---------------------------------------------------------------------//
globals
Vector3 REAL_UP
Vector3 WORLD_UP
Vector3 WORLD_BACK
Vector3 WORLD_ZERO
endglobals
struct Vector3
real x = 0.0
real y = 0.0
real z = 0.0
//Constructor
//** Returns brand new Vector3!
static method create takes real x, real y, real z returns Vector3
local Vector3 v = Vector3.allocate()
set v.x = x
set v.y = y
set v.z = z
return v
endmethod
//Copy this vector
//** Returns brand new Vector3!
method copy takes nothing returns thistype
local Vector3 v = Vector3.allocate()
set v.x = x
set v.y = y
set v.z = z
return v
endmethod
//Creates a vector poiting in these angle "directions"
//Angle in paramteres are degrees for simplified use (just here though, radians everywhere else!)
//** Returns brand new Vector3!
static method fromDegreeAngles takes real xya, real za returns Vector3
local Vector3 v = Vector3.create(1,0,0)
call v.rotate(0, -za * bj_DEGTORAD, xya * bj_DEGTORAD)
return v
endmethod
//--------------------------------//
//* Basic Properties *//
//--------------------------------//
//Returns the real length of the vector
method length takes nothing returns real
return SquareRoot(x*x+y*y+z*z)
endmethod
//Get the X/Y angle this vector is poiting in
method operator xyAngle takes nothing returns real
return Atan2(y,x)
endmethod
//Get the Z (up/down) angle this vector is pointing in.
//probably not the most efficient way to do this
//but the first one that came to mind
method operator zAngle takes nothing returns real
local Vector3 c = copy()
local Vector3 c2 = copy()
local real angle = 0.0
set c2.z = 0.0
set angle = Atan2(c.z, c.dot(c2))
call c.destroy()
call c2.destroy()
return angle
endmethod
//--------------------------------//
//* Basic Manipulation Functions *//
//--------------------------------//
method update takes real nx, real ny, real nz returns nothing
set x = nx
set y = ny
set z = nz
endmethod
method add takes Vector3 v returns nothing
set x = x + v.x
set y = y + v.y
set z = z + v.z
endmethod
method subtract takes Vector3 v returns nothing
set x = x - v.x
set y = y - v.y
set z = z - v.z
endmethod
method multiply takes Vector3 v returns nothing
set x = x * v.x
set y = y * v.y
set z = z * v.z
endmethod
method scale takes real s returns nothing
set x = x * s
set y = y * s
set z = z * s
endmethod
method translate takes real tx, real ty, real tz returns nothing
set x = x + tx
set y = y + ty
set z = z + tz
endmethod
//This function forces the vector length = 1
//Useful for many things.
method normalize takes nothing returns nothing
local real l = length()
set x = x / l
set y = y / l
set z = z / l
endmethod
//Rotate around GLOBAL AXIS!, this will always rotate around
//the global x/y/z axis. Angles in radians.
//For local or arbitrary rotation see rotateAxis below.
method rotate takes real angleX, real angleY, real angleZ returns nothing
local real xp = 0.0
local real yp = 0.0
local real zp = 0.0
set yp = y * Cos(angleX) - z * Sin(angleX)
set zp = y * Sin(angleX) + z * Cos(angleX)
set xp = x
//call BJDebugMsg("Rotate X("+R2S(angleX)+") x: "+R2S(x)+" vs "+R2S(xp)+ " | y: "+R2S(y)+" vs "+R2S(yp)+" | z: "+R2S(z)+" vs "+R2S(zp))
set x = xp
set y = yp
set z = zp
set zp = z * Cos(angleY) - x * Sin(angleY)
set xp = z * Sin(angleY) + x * Cos(angleY)
set yp = y
//call BJDebugMsg("Rotate Y("+R2S(angleY)+") x: "+R2S(x)+" vs "+R2S(xp)+ " | y: "+R2S(y)+" vs "+R2S(yp)+" | z: "+R2S(z)+" vs "+R2S(zp))
set x = xp
set y = yp
set z = zp
set xp = x * Cos(angleZ) - y * Sin(angleZ)
set yp = x * Sin(angleZ) + y * Cos(angleZ)
set zp = z
//call BJDebugMsg("Rotate Z("+R2S(angleZ)+") x: "+R2S(x)+" vs "+R2S(xp)+ " | y: "+R2S(y)+" vs "+R2S(yp)+" | z: "+R2S(z)+" vs "+R2S(zp))
set x = xp
set y = yp
set z = zp
endmethod
//rotate the vector about an arbitrary point+axis that is local to this vector, angle in radians
//http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.html
//local variables are extensive in order to show off the usage of the function found in the link above
method rotateAxis takes Vector3 p, Vector3 axis, real angle returns nothing
local real cos = Cos(angle)
local real sin = Sin(angle)
//Point around which we are rotating - this can be (0,0,0) for simple local rotation at the origin of the vector.
local real a = p.x
local real b = p.y
local real c = p.z
//axis about which we are rotating, helps if normalized
local real u = axis.x
local real v = axis.y
local real w = axis.z
//other vars
local real u2 = u * u
local real v2 = v * v
local real w2 = w * w
local real d = u2 + v2 + w2 //denominator
local real dRoot = SquareRoot(d)
local real newX = 0.
local real newY = 0.
local real newZ = 0.
set newX = a*(v2+w2)+u*(-b*v-c*w+u*x+v*y+w*z)+((x-a)*(v2+w2)+u*(b*v+c*w-v*y-w*z))*cos+dRoot*(b*w-c*v-w*y+v*z)*sin
set newX = newX / d
set newY = b*(u2+w2)+v*(-a*u-c*w+u*x+v*y+w*z)+((y-b)*(u2+w2)+v*(a*u+c*w-u*x-w*z))*cos+dRoot*(-a*w+c*u+w*x-u*z)*sin
set newY = newY / d
set newZ = c*(u2+v2)+w*(-a*u-b*v+u*x+v*y+w*z)+((z-c)*(u2+v2)+w*(a*u+b*v-u*x-v*y))*cos+dRoot*(a*v-b*u-v*x+u*y)*sin
set newZ = newZ / d
set x = newX
set y = newY
set z = newZ
endmethod
//Dot product another vector onto this one
//This is a projection of vector v on this vector.
method dot takes Vector3 v returns real
return x*v.x + y*v.y + z*v.z
endmethod
method equals takes Vector3 v returns boolean
return x == v.x and y == v.y and z == v.z
endmethod
method distSqr takes Vector3 v returns real
return (v.x-x)*(v.x-x) + (v.y-y)*(v.y-y) + (v.z-z)*(v.z-z)
endmethod
method printD takes nothing returns nothing
debug call BJDebugMsg("Vector["+I2S(this)+"] x: "+R2S(x)+" y:"+R2S(y)+" z:"+R2S(z))
endmethod
endstruct
//---------------------------------------------------------------------//
// External functions
//---------------------------------------------------------------------//
//
// These functions can be used as shortcuts to get a result vector
// or value quickly. Note that most of these create brand new vector
// structs that you should destroy.
//---------------------------------------------------------------------//
//remeber to destroy vectors after usage!
//** Returns brand new Vector3!
function VectorSubtract takes Vector3 v1, Vector3 v2 returns Vector3
return Vector3.create(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z)
endfunction
//** Returns brand new Vector3!
function VectorAdd takes Vector3 v1, Vector3 v2 returns Vector3
return Vector3.create(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z)
endfunction
//** Returns brand new Vector3!
function VectorMultiply takes Vector3 v1, Vector3 v2 returns Vector3
return Vector3.create(v1.x * v2.x, v1.y * v2.y, v1.z * v2.z)
endfunction
function VectorDotProduct takes Vector3 v1, Vector3 v2 returns real
return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z
endfunction
//** Returns brand new Vector3!
function VectorCrossProduct takes Vector3 v1, Vector3 v2 returns Vector3
local Vector3 cross = Vector3.create(0,0,0)
if(not v1.equals(v2)) then
set cross.x = v1.y*v2.z - v1.z*v2.y
set cross.y = v1.z*v2.x - v1.x*v2.z
set cross.z = v1.x*v2.y - v1.y*v2.x
else
//default two parallel vectors to a global right
//for whatever reason
set cross.x = 1
endif
return cross
endfunction
//Return a normalized desired velocity, make sure to destroy after use
//** Returns brand new Vector3!
function VectorSeek takes Vector3 target, Vector3 origin returns Vector3
local Vector3 v = VectorSubtract(target, origin)
call v.normalize()
return v
endfunction
function Init_Vector3 takes nothing returns nothing
set WORLD_UP = Vector3.create(0,0,1)//z axis = up
set WORLD_BACK = Vector3.create(0,-1,0)//z axis = up
set WORLD_ZERO = Vector3.create(0,0,0)
set REAL_UP = Vector3.create(0,1,0)
endfunction
endlibrary
HOW TO USE:
JASS:
//---------------------------------------------------------------------//
// Custom Fire Projectile
//---------------------------------------------------------------------//
struct FireBoltMissile extends Projectile
real damage = 0.0
//if the firebolt hits the ground, make it explode?
method onHitGround takes nothing returns nothing
call this.terminate()
endmethod
method onUnitHit takes unit target returns nothing
if ((HasHomingTarget() and target == homingTarget) or (not HasHomingTarget() and this.source != target and not IsUnitAlly(target, owner) and IsUnitAlive(target))) then
//Damage the target unit here!
call this.terminate()
endif
endmethod
endstruct
function CastFireBolt takes nothing returns nothing
local unit caster = [Set this to the caster of the spell]
local unit target = [Set this to the target to home in on]
local FireBoltMissile p
//Where the fireball come out from
local Vector3 origin = Vector3.create(GetUnitX(caster), GetUnitY(caster), 50.0)
//Where the fireball is headed initially. The system changes this
//automatically if you set a homing target (like below)
local Vector3 target = Vector3.create(GetUnitX(target), GetUnitY(target), 0)
//What direction should the fireball be facing at the start?
//In this case, in the direction the caster is facing, and up at 45 degrees.
local Vector3 direction = Vector3.fromDegreeAngles(GetUnitFacing(caster), 45.0)
//this is important. If the length of the direction vector != 1.0 then it looks bad
call direction.normalize()
//Create fire projectile here
set p = FireBoltMissile.create(origin, direction, target)
set p.Source = caster
set p.fxpath = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
set p.speed = 700.0
set p.maxTurnXY = 400.0 * bj_DEGTORAD
set p.maxTurnZ = 250.0 * bj_DEGTORAD
set p.lifeTime = 5.0
set p.homingTarget = target
set p.homingDelay = 0.2
//firebolt specific
set p.damage = 50.0
set caster = null
set target = null
//Don't destroy the vectors. The projectile will do it when it dies.
endfunction
Extra model resources thanks to: Vexorian (Dummy model), Dan van Ohllus (Missile), s4nji (Icy Spike)