• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

SEE : Simple Entity Engine 2.4

Version 2.4

The third system of its kind created by me, this one will hopefully be more succesful and will live up to my expectations. The two others were Simple Particle System, and then Simple Particle System Mark II. The first of the three was buggy, very slow, and extremely inaccurate. The second used procedural-style coding, which people didn't seem to appreciate. I finally created this: SEE - Simple Entity Engine. It uses object-oriented style scripting, which makes things alot easier to read and understand. I've worked a lot on getting this system to where I want it to be.

Please only report bugs or give criticism RELATED to this system!
This system requires JassNewGenPack for the use of vJass syntax!
This system uses UnitUserData!


Features:
  • Easy to use, easy to read layout.
  • Efficient and fast system.
  • 'Realistic' bouncing off other entities and the ground.
  • Spherical collisions between entities.
  • Easy to create custom entity types.
  • Mass and volume play important roles in entity behaviors.
  • Documented just for you! :D

Credits go to HINDYhat for the creation of this system.

Thanks to Earth-Fury for helping me a lot and teaching me how to use object-oriented programming. Also thanks to him for coming up with this system's neat name, SEE: Simple Entity Engine.
------------------------
Thanks to PurplePoot for teaching me some things in Jass.
------------------------
Thanks to moyack for helping me on a nagging collision problem, and for being nice and helpful. :D
------------------------
Many thanks to grim001 for helping with a couple of bugs in collision detection.
------------------------
Thanks to anyone and everyone who might use this system in a map of theirs.


And now the code:

JASS:
library SEEcore requires SEEentityData

globals

    private constant integer DUMMY_ID           = 'e000'
    
    public constant real FPS                    = 40.0

    private constant real GRAVITY_ACCELERATION  = -3000.0
    
    private constant real AIR_DENSITY           = 0.001
        
    private constant integer REST_TALLY         = 30
    
    private constant real REST_STATE_SPD        = 60.0
    
    private constant real COLL_ALGO_SPD         = 300.0
    
    private constant real THRESHOLD_COLL_SPD    = 70.0
    
    private constant real THRESHOLD_SLIDE_SPD   = 100.0
    
    private constant real SAMPLE_RADIUS         = 32.0
    
    private constant real MAX_TERRAIN_HEIGHT    = 1230.500
    
endglobals

globals
    public real minX
    public real maxX
    public real minY
    public real maxY
    private entity enumEntity
    private boolexpr filter
    private real RADIUS = 0.0
    private real array tempReal
    private group G = CreateGroup()
    private integer I
endglobals

private function enum takes nothing returns boolean
    local entity e = entity(GetUnitUserData(GetFilterUnit()))
    return e.data.collideable and not IsUnitInGroup(e.dummy, enumEntity.collGroup) and ( (enumEntity.zP + enumEntity.data.radius + enumEntity.zV >= e.zP - e.data.radius + e.zV) or (enumEntity.zP - enumEntity.data.radius + enumEntity.zV >= e.zP + e.data.radius + e.zV) )
endfunction

private function main takes nothing returns nothing
    local integer i = 0 // Iterator
    local entity e      // Dummy entity used in the main loop.
    local entity c      // Dummy entity used in collisions.
    
    local real r
    local real r2
    local real s = 0.0
    local real px 
    local real py 
    local real pz = 0.0
    local real vx = 0.0
    local real vy = 0.0
    local real vz = 0.0
            
    loop
        exitwhen i >= entity.COUNT
        set e = entity.ENTITY[i]
        
        // --
        // The following only exectutes if timed life is activated
        if e.timedLife > -1.0 then
            set e.timedLife = e.timedLife - 1.0/FPS
            if e.timedLife <= 0.0 then
                call e.data.onDeath(e)
                call e.remove(e.wantRemoveUnit)
            endif
        endif
        // --
        
        call e.data.onLoop(e)
        if not e.data.affected then
            set px = GetUnitX(e.dummy)
            set py = GetUnitY(e.dummy)
            call MoveLocation(entity.tloc, px, py)
            set pz = GetUnitFlyHeight(e.dummy) + GetLocationZ(entity.tloc) - e.data.oZ
            if px != e.xP or py != e.yP or pz != e.zP then
                set e.restCount = 0
                set e.xV = px - e.xP
                set e.yV = py - e.yP
                set e.zV = pz - e.zP
                set e.xP = px
                set e.yP = py
                set e.zP = pz
            endif
        endif
        
        if e.restCount < REST_TALLY then
            set s = SquareRoot(e.xV*e.xV + e.yV*e.yV + e.zV*e.zV)
            if s <= REST_STATE_SPD/FPS then
                set e.restCount = e.restCount + 1
                if e.restCount >= REST_TALLY then
                    set e.xV = 0.0
                    set e.yV = 0.0
                    set e.zV = 0.0
                    set s = 0.0
                    call e.data.onRest(e)
                endif
            else
                set e.restCount = 0
            endif
            
            if e.data.affected then
                
                // --
                // The following involves calculations for spring constraints
                if e.constrained != 0 then 
                    set vx = e.xP - e.constrained.xP
                    set vy = e.yP - e.constrained.yP
                    set vz = e.zP - e.constrained.zP
                    set r = SquareRoot(vx*vx + vy*vy + vz*vz) + 0.01
                    set r = -e.constraintConstant*(1 - e.constraintLength/r)
                    set vx = (vx*r - (e.xV - e.constrained.xV)*e.constraintFriction)/(e.data.mass*FPS*FPS)
                    set vy = (vy*r - (e.yV - e.constrained.yV)*e.constraintFriction)/(e.data.mass*FPS*FPS)
                    set vz = (vz*r - (e.zV - e.constrained.zV)*e.constraintFriction)/(e.data.mass*FPS*FPS)
                    set e.restCount = 0
                    set e.xV = e.xV + 1000.0*vx
                    set e.yV = e.yV + 1000.0*vy
                    set e.zV = e.zV + 1000.0*vz
                    if e.constrained.data.affected then
                        set e.constrained.restCount = 0
                        set e.constrained.xV = e.constrained.xV - 1000.0*vx
                        set e.constrained.yV = e.constrained.yV - 1000.0*vy
                        set e.constrained.zV = e.constrained.zV - 1000.0*vz
                    endif
                endif
                // --
                        
                // --
                // The following involves air drag calculations and position/velocity updating
                set px = 1.0 - AIR_DENSITY*s*e.data.radius*e.data.radius*e.data.drag/(e.data.mass + 0.001)
                set e.xV = e.xV*px + e.xA
                set e.yV = e.yV*px + e.yA
                set e.zV = e.zV*px + e.zA
                set e.xP = e.xP + e.xV
                set e.yP = e.yP + e.yV
                set e.zP = e.zP + e.zV
                // --
                
                // --
                // The following is a check for if the entity is within map bounds
                if e.xP < minX or e.yP < minY or e.xP > maxX or e.yP > maxY then
                    call e.data.onExit(e)
                endif
                // --
              
                if e.zP <= MAX_TERRAIN_HEIGHT then
                
                    // --
                    // The following gets the terrain normal <vx, vy, SAMPLE_RADIUS>
                    // and checks to see if the concerned entity sphere is colliding
                    // with the terrain plane.
                    call MoveLocation(entity.tloc, e.xP - SAMPLE_RADIUS, e.yP)
                    call MoveLocation(entity.tloc2, e.xP + SAMPLE_RADIUS, e.yP)
                    set vx = GetLocationZ(entity.tloc) - GetLocationZ(entity.tloc2)
                    call MoveLocation(entity.tloc, e.xP, e.yP - SAMPLE_RADIUS)
                    call MoveLocation(entity.tloc2, e.xP, e.yP + SAMPLE_RADIUS)
                    set vy = GetLocationZ(entity.tloc) - GetLocationZ(entity.tloc2)
                    set vz = vx*vx + vy*vy + SAMPLE_RADIUS*SAMPLE_RADIUS
                    call MoveLocation(entity.tloc, e.xP, e.yP)
                    set r = e.data.radius/SquareRoot(vz) - (e.zP - GetLocationZ(entity.tloc))*SAMPLE_RADIUS/vz
                    // --
                    
                    if r >= 0.0 then
                    
                        // --
                        // Moves the entity to the touching point between the
                        // entity and the terrain plane.
                        set e.xP = e.xP + r*vx
                        set e.yP = e.yP + r*vy
                        set e.zP = e.zP + r*SAMPLE_RADIUS
                        // --
                        
                        // --
                        // Gets the projection of the entity's velocity onto the
                        // terrain normal: <px, py, pz>
                        set vz = (e.xV*vx + e.yV*vy + e.zV*SAMPLE_RADIUS)/vz
                        set px = vx*vz
                        set py = vy*vz
                        set pz = SAMPLE_RADIUS*vz
                        // --
                        
                        if px*px + py*py + pz*pz <= THRESHOLD_SLIDE_SPD*THRESHOLD_SLIDE_SPD/(FPS*FPS) or not(e.data.bounceable) then
                            
                            // --
                            // Calculates the entity's new velocity after impact. This script
                            // scales the velocity component parallel to the terrain plane by
                            // taking in account friction, and completely removes the
                            // perpendicular component. This gives a sliding effect.
                            set e.xV = (e.xV - px)*(1.0 - e.data.friction)
                            set e.yV = (e.yV - py)*(1.0 - e.data.friction)
                            set e.zV = (e.zV - pz)*(1.0 - e.data.friction)
                            // --
                            
                        else
                        
                            // --
                            // The parallel component functions as described above. However,
                            // the perpendicular component is added here, and scaled taking
                            // in account restitution. Bouncing is determined by resitution.
                            set e.xV = (e.xV - px)*(1.0 - e.data.friction) - px*e.data.restitution
                            set e.yV = (e.yV - py)*(1.0 - e.data.friction) - py*e.data.restitution
                            set e.zV = (e.zV - pz)*(1.0 - e.data.friction) - pz*e.data.restitution
                            // --
                            
                        endif
                        set s = SquareRoot(e.xV*e.xV + e.yV*e.yV + e.zV*e.zV)
                        call e.data.onGround(e)
                    endif
                endif
                
                // --
                // Rendering the entity based on position and offset
                call MoveLocation(entity.tloc, e.xP, e.yP)
                set r = GetLocationZ(entity.tloc)
                call SetUnitX(e.dummy, e.xP + e.data.oX)
                call SetUnitY(e.dummy, e.yP + e.data.oY)
                call SetUnitFlyHeight(e.dummy, e.zP - r + e.data.oZ, 0.0)
                // --
                
            endif
            
            // --
            // Below is the collision detection and resolution
            if e.data.collideable then
            
                set enumEntity = e
                
                // --
                // Enum'ing through the entities in a cylindrical range first;
                // speed*2.0 is an added term due to a logical constraint (thanks to grim001);
                // RADIUS is the largest current radius of all entities;
                // e.collGroup contains entities with which collisions have already been
                // dealt. Thus, calculating a second collision would be redundant.
                // Entities are properly sifted considering this.
                call GroupEnumUnitsInRange(entity.COLL, e.xP, e.yP, e.data.radius + RADIUS + s*2.0, filter)
                call GroupRemoveUnit(entity.COLL, e.dummy)
                call GroupClear(e.collGroup)
                // --
                
                loop
                    set c = entity(GetUnitUserData(FirstOfGroup(entity.COLL)))
                    exitwhen c.dummy == null
                    
                    // --
                    // Storing of various relative components and dot products
                    set vx = e.xV - c.xV
                    set vy = e.yV - c.yV
                    set vz = e.zV - c.zV
                    set r2 = vx*vx + vy*vy + vz*vz
                    // --
                    
                    // The following condition evaluates the relative velocity with respect to the collision separator
                    if r2 >= COLL_ALGO_SPD*COLL_ALGO_SPD/(FPS*FPS) then
                        // If the relative velocity is larger than the collision separator, advanced collision detection is executed
                        // The following collision detection was established using the equation: |deltaP + t*deltaV| <= r1 + r2
                        // Note that at this point, variable names most likely have nothing to do with what they hold
                        set px = e.xP - c.xP
                        set py = e.yP - c.yP
                        set pz = e.zP - c.zP
                        set s  = px*vx + py*vy + pz*vz
                        set r  = px*px + py*py + pz*pz
                        set vx = e.data.radius + c.data.radius
                        set vy = s*s - r2*(r - vx*vx)
                        if vy >= 0.0 then
                            set vy = (-s - SquareRoot(vy))/r2
                            if vy > 0.0 and vy <= 1.0 then
                            
                                // --
                                // The following prepares collision resolution while moving entities to their touching position
                                set c.xP = c.xP + c.xV*vy
                                set c.yP = c.yP + c.yV*vy
                                set c.zP = c.zP + c.zV*vy
                                set e.xP = e.xP + e.xV*vy
                                set e.yP = e.yP + e.yV*vy
                                set e.zP = e.zP + e.zV*vy
                                // --
                                
                                // --
                                // The following is code for collision resolution
                                set s = s*(1.0 + c.data.restitution*e.data.restitution)/(r*(c.data.mass + e.data.mass) + 0.001)
                                if c.data.bounceable then
                                    set vx = s*e.data.mass
                                    set c.xV = c.xV + px*vx
                                    set c.yV = c.yV + py*vx
                                    set c.zV = c.zV + pz*vx
                                    if c.xV*c.xV + c.yV*c.yV + c.zV*c.zV <= THRESHOLD_COLL_SPD*THRESHOLD_COLL_SPD/(FPS*FPS) then
                                        // If the entity moves slower than the threshold velocity, set the velocity to 0
                                        set c.xV = 0.0
                                        set c.yV = 0.0
                                        set c.zV = 0.0
                                    else
                                        // If the entity begins moving again, its rest has been disturbed
                                        set c.restCount = 0
                                    endif
                                endif
                                if e.data.bounceable then
                                    set vx = s*c.data.mass
                                    set e.xV = e.xV - px*vx
                                    set e.yV = e.yV - py*vx
                                    set e.zV = e.zV - pz*vx
                                    if e.xV*e.xV + e.yV*e.yV + e.zV*e.zV <= THRESHOLD_COLL_SPD*THRESHOLD_COLL_SPD/(FPS*FPS) then
                                        set e.xV = 0.0
                                        set e.yV = 0.0
                                        set e.zV = 0.0
                                    else
                                        set e.restCount = 0
                                    endif
                                endif
                                // --
                                
                                call e.data.onCollision(e, c)
                                call c.data.onCollision(c, e)
                            elseif vy < 0.0 and r < vx*vx then
                                set vz = (vx/(SquareRoot(r) + 0.001) - 1.0)/(c.data.mass + e.data.mass + 0.001)
                                set vy = e.data.mass*vz
                                if r*vy*vy > 0.01/(FPS*FPS) then
                                    set c.xP = c.xP - vy*px
                                    set c.yP = c.yP - vy*py
                                    set c.zP = c.zP - vy*pz
                                    set c.restCount = 0
                                endif
                                set vy = c.data.mass*vz
                                if r*vy*vy > 0.01/(FPS*FPS) then
                                    set e.xP = e.xP + vy*px
                                    set e.yP = e.yP + vy*py
                                    set e.zP = e.zP + vy*pz
                                    set e.restCount = 0
                                endif
                            endif
                        endif
                    else
                        set px = e.xP - c.xP
                        set py = e.yP - c.yP
                        set pz = e.zP - c.zP
                        set r  = px*px + py*py + pz*pz
                        set r2 = e.data.radius + c.data.radius
                        if r < r2*r2 then
                            // If the relative velocity is smaller than the collision separator;
                            // and if two entities are currently colliding, simple collision script is executed
                            
                            // --
                            // The following code moves entities apart from eachother based on their mass and the distance between them
                            set s = (px*vx + py*vy + pz*vz)*(1.0 + c.data.restitution*e.data.restitution)/(r*(c.data.mass + e.data.mass) + 0.001)
                            set vz = (r2/(SquareRoot(r) + 0.001) - 1.0)/(c.data.mass + e.data.mass + 0.001)
                            set vy = c.data.mass*vz
                            if r*vy*vy > 0.01/(FPS*FPS) then
                                set e.xP = e.xP + vy*px
                                set e.yP = e.yP + vy*py
                                set e.zP = e.zP + vy*pz
                                set e.restCount = 0
                            endif
                            set vy = e.data.mass*vz
                            if r*vy*vy > 0.01/(FPS*FPS) then
                                set c.xP = c.xP - vy*px
                                set c.yP = c.yP - vy*py
                                set c.zP = c.zP - vy*pz
                                set c.restCount = 0
                            endif
                            // --
                            
                            if c.data.bounceable then
                                set vx = s*e.data.mass
                                set c.xV = c.xV + px*vx
                                set c.yV = c.yV + py*vx
                                set c.zV = c.zV + pz*vx
                                if c.xV*c.xV + c.yV*c.yV + c.zV*c.zV <= THRESHOLD_COLL_SPD*THRESHOLD_COLL_SPD/(FPS*FPS) then
                                    set c.xV = 0.0
                                    set c.yV = 0.0
                                    set c.zV = 0.0
                                else
                                    set c.restCount = 0
                                endif
                            endif
                            if e.data.bounceable then
                                set vx = s*c.data.mass
                                set e.xV = e.xV - px*vx
                                set e.yV = e.yV - py*vx
                                set e.zV = e.zV - pz*vx
                                if e.xV*e.xV + e.yV*e.yV + e.zV*e.zV <= THRESHOLD_COLL_SPD*THRESHOLD_COLL_SPD/(FPS*FPS) then
                                    set e.xV = 0.0
                                    set e.yV = 0.0
                                    set e.zV = 0.0
                                else
                                    set e.restCount = 0
                                endif
                            endif
                            
                            call e.data.onCollision(e, c)
                            call c.data.onCollision(c, e)
                            
                        endif
                        // --
                        
                    endif
                    
                    call GroupRemoveUnit(entity.COLL, c.dummy)
                    call GroupAddUnit(c.collGroup, e.dummy)
                endloop
                            
            endif
            // --
        
        endif
        
        set i = i + 1
    endloop
    
    // --
    // The following destroys all entities added to the destroy stack
    set i = entity.COUNT_DES
    loop
        set i = i - 1
        exitwhen i < 0
        call entity.ENTITY_DES[i].destroy()
    endloop
    set entity.COUNT_DES = 0
    // --
        
endfunction

struct entity
    entityData data
    
    unit dummy
    effect eff
    
    real xP
    real yP
    real zP
    
    real xV = 0.0
    real yV = 0.0
    real zV = 0.0
    
    real xA = 0.0
    real yA = 0.0
    real zA = 0.0
    
    trigger onEvent = CreateTrigger()
    
    real timedLife = -1.0
    
    integer restCount = 0
    
    integer ID
    
    boolean wantRemoveUnit
    
    entity constrained = 0
    real constraintConstant
    real constraintLength
    real constraintFriction
    
    group collGroup = CreateGroup()
    
    static timer T = CreateTimer()
    
    static group COLL = CreateGroup()
    
    static integer COUNT = 0
    static entity array ENTITY
    
    static integer COUNT_DES = 0
    static entity array ENTITY_DES
    
    static location tloc = Location(0.0, 0.0)
    static location tloc2 = Location(0.0, 0.0)
    
////////////////////////////////////////////////////////////
///////////////// __FUNCTIONALITY METHODS__ ////////////////

    method remove takes boolean wantRemoveUnit returns nothing
        if this.timedLife != -2.0 then
            set this.constrained = 0
            set this.data.collideable = false
            set this.wantRemoveUnit = wantRemoveUnit
            set this.timedLife = -2.0
            set entity.ENTITY_DES[entity.COUNT_DES] = this
            set entity.COUNT_DES = entity.COUNT_DES + 1
        endif
    endmethod
    
    method applyTimedLife takes real time, boolean removeUnit returns nothing
        set this.timedLife = time
        set this.wantRemoveUnit = removeUnit
    endmethod

    method addGravity takes real factor returns nothing
        set this.zA = GRAVITY_ACCELERATION*factor/(FPS*FPS)
    endmethod
    
    method setVelocity takes real xVel, real yVel, real zVel returns nothing
        set this.xV = xVel/FPS
        set this.yV = yVel/FPS
        set this.zV = zVel/FPS
    endmethod
    
    method addVelocity takes real x, real y, real z returns nothing
        set this.xV = this.xV + x/FPS
        set this.yV = this.yV + y/FPS
        set this.zV = this.zV + z/FPS
    endmethod
    
    method setAcceleration takes real xAcc, real yAcc, real zAcc returns nothing
        set this.xA = xAcc/(FPS*FPS)
        set this.yA = yAcc/(FPS*FPS)
        set this.zA = zAcc/(FPS*FPS)
    endmethod
    
    method addAcceleration takes real x, real y, real z returns nothing
        set this.xA = this.xA + x/(FPS*FPS)
        set this.yA = this.yA + y/(FPS*FPS)
        set this.zA = this.zA + z/(FPS*FPS)
    endmethod
    
    method setEntityFlyHeight takes real z returns nothing
        call MoveLocation(entity.tloc, this.xP, this.yP)
        set this.zP = GetLocationZ(entity.tloc) + z
    endmethod
    
    method removeHealthBar takes nothing returns nothing
        call UnitAddAbility(this.dummy, 'Aloc')
        call ShowUnit(this.dummy, false)
        call UnitRemoveAbility(this.dummy, 'Aloc')
        call ShowUnit(this.dummy, true)
    endmethod
    
////////////// __END OF FUNCTIONALITY METHODS__ ////////////
////////////////////////////////////////////////////////////  


////////////////////////////////////////////////////////////
/////////////////// __CONSTRAINT METHODS__ /////////////////

    method addSpringConstraint takes entity attached, real springConstant, real springLength, real friction returns nothing
        set this.constrained = attached
        set this.constraintConstant = springConstant
        set this.constraintLength = springLength
        set this.constraintFriction = friction
    endmethod
    
    method removeSpringConstraint takes nothing returns nothing
        set this.constrained = 0
    endmethod

//////////////// __END OF CONSTRAINT METHODS__ /////////////
////////////////////////////////////////////////////////////  


////////////////////////////////////////////////////////////
/////////////////// __MOVEMENT METHODS__ ///////////////////

     method projectWithAngle takes real xyAngle, real zAngle, real speed returns nothing
        local real cos = Cos(zAngle)*speed/FPS
        set this.restCount = 0
        set this.xV = Cos(xyAngle)*cos
        set this.yV = Sin(xyAngle)*cos
        set this.zV = Sin(zAngle)*speed/FPS
    endmethod
        
    method projectToPointTimed takes real xPos, real yPos, real zPos, real time returns nothing
        set this.restCount = 0
        set this.xV = this.xV + (xPos - this.xP)/(time*FPS)
        set this.yV = this.yV + (yPos - this.yP)/(time*FPS)
        call MoveLocation(entity.tloc, xPos, yPos)
        set this.zV = this.zV + (zPos + GetLocationZ(entity.tloc) - this.zP)/(time*FPS) - 0.5*this.zA*FPS*time
    endmethod
    
    method projectToPointTimedEx takes real xPos, real yPos, real zPos, real time, real maxDist returns nothing
        local real d
        call MoveLocation(entity.tloc, xPos, yPos)
        set xPos = xPos - this.xP
        set yPos = yPos - this.yP
        set zPos = zPos + GetLocationZ(entity.tloc) - this.zP
        set d = xPos*xPos + yPos*yPos + zPos*zPos
        if d > maxDist*maxDist then
            set d = maxDist/(SquareRoot(d) + 0.01)
            set xPos = xPos*d
            set yPos = yPos*d
            set zPos = zPos*d
        endif
        call MoveLocation(entity.tloc, this.xP, this.yP)
        call this.projectToPointTimed(this.xP + xPos, this.yP + yPos, this.zP + zPos - GetLocationZ(entity.tloc), time)
    endmethod
    
    method projectTowardsPointSpeed takes real xPos, real yPos, real zPos, real speed returns nothing
        local real dx = xPos - this.xP
        local real dy = yPos - this.yP
        local real dz
        local real d
        call MoveLocation(entity.tloc, xPos, yPos)
        set dz = zPos + GetLocationZ(entity.tloc) - this.zP
        set this.restCount = 0
        set d = speed/(FPS*SquareRoot(dx*dx + dy*dy + dz*dz) + 0.001)
        set this.xV = dx*d
        set this.yV = dy*d
        set this.zV = dz*d
    endmethod
    
    method projectToPointSpeed takes real xPos, real yPos, real zPos, real speed, boolean lob returns boolean
        local real dx = xPos - this.xP
        local real dy = yPos - this.yP
        local real d
        local real des
        call MoveLocation(entity.tloc, xPos, yPos)
        set speed = speed/FPS
        if this.zA != 0.0 then
            set d = SquareRoot(dx*dx + dy*dy) + 0.001
            set des = speed*speed*speed*speed - this.zA*this.zA*d*d + 2*this.zA*speed*speed*(GetLocationZ(entity.tloc) + zPos - this.zP)
            if des >= 0.0 then
                set this.restCount = 0
                if lob then
                    set des = Atan((-speed*speed-SquareRoot(des))/(this.zA*d))
                else
                    set des = Atan((-speed*speed+SquareRoot(des))/(this.zA*d))
                endif
                set this.zV = speed*Sin(des)
                set des = speed*Cos(des)/d
                set this.xV = des*dx
                set this.yV = des*dy
                return true
            endif
            return false
        else
            set this.restCount = 0
            set d = GetLocationZ(entity.tloc) + zPos - this.zP
            set des = speed/(SquareRoot(dx*dx + dy*dy + d*d) + 0.001)
            set this.xV = dx*des
            set this.yV = dy*des
            set this.zV = d*des
            return true
        endif
    endmethod
    
    method projectToPointSpeedEx takes real xPos, real yPos, real zPos, real speed, boolean lob, real maxDist returns boolean
        local real d
        call MoveLocation(entity.tloc, xPos, yPos)
        set xPos = xPos - this.xP
        set yPos = yPos - this.yP
        set zPos = zPos + GetLocationZ(entity.tloc) - this.zP
        set d = xPos*xPos + yPos*yPos + zPos*zPos
        if d > maxDist*maxDist then
            set d = maxDist/(SquareRoot(d) + 0.01)
            set xPos = xPos*d
            set yPos = yPos*d
            set zPos = zPos*d
        endif
        call MoveLocation(entity.tloc, this.xP, this.yP)
        return this.projectToPointSpeed(this.xP + xPos, this.yP + yPos, this.zP + zPos - GetLocationZ(entity.tloc), speed, lob)
    endmethod
    
    method projectToEntityTimed takes entity e, real time returns nothing
        local real xPos = e.xP + e.xV*time*FPS
        local real yPos = e.yP + e.yV*time*FPS
        local real zPos = e.zP + e.zV*time*FPS
        set this.restCount = 0
        set this.xV = (xPos - this.xP)/(time*FPS)
        set this.yV = (yPos - this.yP)/(time*FPS)
        set this.zV = (zPos - this.zP)/(time*FPS) - 0.5*this.zA*FPS*time
    endmethod

//////////////// __END OF MOVEMENT METHODS__ ///////////////
////////////////////////////////////////////////////////////  


////////////////////////////////////////////////////////////
//////////////////// __GROUP METHODS__ /////////////////////
    
    static method isEntityInRangeFilter takes nothing returns boolean
        local entity e = entity(GetUnitUserData(GetFilterUnit()))
        local real dx
        local real dy
        local real dz
        if integer(e) != 0 then
            set dx = e.xP - tempReal[0]
            set dy = e.yP - tempReal[1]
            set dz = e.zP - tempReal[2]
            return dx*dx + dy*dy + dz*dz <= tempReal[3]*tempReal[3]
        endif
        return false
    endmethod
    
    static method enumEntitiesInRange takes group g, real x, real y, real z, real range, boolexpr filter returns nothing
        local boolexpr b = And(filter, Filter(function entity.isEntityInRangeFilter))
        debug if filter == null then
        debug call BJDebugMsg(SCOPE_PREFIX + ": null filter in enumEntitiesInRange")
        debug endif
        set tempReal[0] = x
        set tempReal[1] = y
        set tempReal[2] = z
        set tempReal[3] = range
        call GroupEnumUnitsInRange(g, x, y, range, b)
        call DestroyBoolExpr(b)
        set b = null
    endmethod
    
    static method isEntityFilter takes nothing returns boolean
        return GetUnitUserData(GetFilterUnit()) != 0
    endmethod
    
    static method enumEntitiesInRect takes group g, rect whichRect, boolexpr filter returns nothing
        local boolexpr b = And(filter, Filter(function entity.isEntityFilter))
        debug if filter == null then
        debug call BJDebugMsg(SCOPE_PREFIX + ": null filter in enumEntitiesInRect")
        debug endif
        call GroupEnumUnitsInRect(g, whichRect, b)
        call DestroyBoolExpr(b)
        set b = null
    endmethod
    
    static method isEntityOfTypeFilter takes nothing returns boolean
        local entity e = GetUnitUserData(GetFilterUnit())
        return integer(e) != 0 and e.data.getType() == I
    endmethod
    
    static method enumEntitiesOfType takes group g, integer typeID, boolexpr filter returns nothing
        local boolexpr b = And(filter, Filter(function entity.isEntityOfTypeFilter))
        debug if filter == null then
        debug call BJDebugMsg(SCOPE_PREFIX + ": null filter in enumEntitiesOfType")
        debug endif
        set I = typeID
        call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, b)
        call DestroyBoolExpr(b)
        set b = null
    endmethod
    
    static method enumEntitiesOfPlayer takes group g, player whichPlayer, boolexpr filter returns nothing
        local boolexpr b = And(filter, Filter(function entity.isEntityFilter))
        debug if filter == null then
        debug call BJDebugMsg(SCOPE_PREFIX + ": null filter in enumEntitiesOfPlayer")
        debug endif
        call GroupEnumUnitsOfPlayer(g, whichPlayer, b)
        call DestroyBoolExpr(b)
        set b = null
    endmethod

////////////////// __END OF GROUP METHODS__ /////////////////
/////////////////////////////////////////////////////////////  
    
    
////////////////////////////////////////////////////////////
//////////////////// __CORE METHODS__ //////////////////////

    method onDestroy takes nothing returns nothing
        local integer i = 0
        
        // If the unit is labeled to be removed, the kill it, hide it, and destroy the attached effect.
        call SetUnitUserData(this.dummy, 0)
        call DestroyTrigger(this.onEvent)
        set this.onEvent = null
        call DestroyGroup(this.collGroup)
        set this.collGroup = null
        if this.wantRemoveUnit then
            call RemoveUnit(this.dummy)
            call DestroyEffect(this.eff)
        endif
        set this.dummy = null
        set this.eff = null
        
        if this.data.radius == RADIUS then
            loop
                exitwhen i >= entity.COUNT
                if entity.ENTITY[i].data.radius > RADIUS then
                    set RADIUS = entity.ENTITY[i].data.radius
                endif
                set i = i + 1
            endloop
        endif
        
        // Updating the entity stack.
        set entity.COUNT = entity.COUNT - 1
        set entity.ENTITY[this.ID] = entity.ENTITY[entity.COUNT]
        set entity.ENTITY[this.ID].ID = this.ID
        
        if entity.COUNT == 0 then
            call PauseTimer(entity.T)
        endif
        
    endmethod
    
    static method onEvent_Action takes nothing returns boolean
        local eventid id = GetTriggerEventId()
        local entity e = GetUnitUserData(GetTriggerUnit())
        
        // Checking all possible eventids to call the correct method.
        if id == EVENT_UNIT_DEATH then
            call e.data.onDeath(e)
        elseif id == EVENT_UNIT_SELECTED then
            call e.data.onSelect(e)
        elseif id == EVENT_UNIT_DESELECTED then
            call e.data.onDeselect(e)
        elseif id == EVENT_UNIT_ISSUED_ORDER then
            call e.data.onOrder(e)
        elseif id == EVENT_UNIT_ISSUED_POINT_ORDER then
            call e.data.onPointOrder(e)
        elseif id == EVENT_UNIT_ISSUED_TARGET_ORDER then
            call e.data.onTargetOrder(e)
        endif
        
        return false
    endmethod
    
////////////////// __END OF CORE METHODS__ /////////////////
////////////////////////////////////////////////////////////  

    
////////////////////////////////////////////////////////////
/////////////////// __CREATION METHODS__ ///////////////////

    static method create takes entityData data, player owner, real xPos, real yPos, real height, real facing returns entity
        local entity e = entity.allocate()
        
        set e.data = data
        
        // Creating dummy unit.
        set e.dummy = CreateUnit(owner, DUMMY_ID, xPos, yPos, facing)
        call UnitAddAbility(e.dummy, 'Aloc')
        call ShowUnit(e.dummy, false)
        call UnitRemoveAbility(e.dummy, 'Aloc')
        call ShowUnit(e.dummy, true)
        if data.scale == -1.0 then
            set data.scale = 1.0
        endif
        call SetUnitScale(e.dummy, data.scale, data.scale, data.scale)
        call UnitAddAbility(e.dummy, 'Amrf')
        call UnitRemoveAbility(e.dummy, 'Amrf')
        set e.eff = AddSpecialEffectTarget(data.modelPath, e.dummy, "origin")
        call SetUnitAnimationByIndex(e.dummy, 90)
        
        // Initializing position.
        set e.xP = xPos
        set e.yP = yPos
        call MoveLocation(entity.tloc, xPos, yPos)
        set e.zP = height + GetLocationZ(entity.tloc)
        call SetUnitFlyHeight(e.dummy, height, data.oZ)
        
        // Initializing the onEvent trigger.
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DEATH)
        if data.collideable then
            call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_ORDER)
            call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_POINT_ORDER)
            call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_TARGET_ORDER)
            call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_SELECTED)
            call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DESELECTED)
        else
            call UnitAddAbility(e.dummy, 'Aloc')
        endif
        call TriggerAddCondition(e.onEvent, Filter(function entity.onEvent_Action))
        
        // Updating the entity stack.
        set e.ID = entity.COUNT
        set entity.ENTITY[entity.COUNT] = e
        set entity.COUNT = entity.COUNT + 1
        
        call SetUnitUserData(e.dummy, integer(e))
        
        if entity.COUNT == 1 then
            call TimerStart(entity.T, 1.0/FPS, true, function main)
        endif
        
        if data.radius > RADIUS then
            set RADIUS = data.radius
        endif
        
        call data.onCreate(e)
        
        return e
    endmethod
    
    static method createFromUnit takes entityData data, unit someUnit returns entity
        local entity e = entity.allocate()
        
        if GetUnitUserData(someUnit) != 0 then
            debug call BJDebugMsg(SCOPE_PREFIX + ": createFromUnit argument is already an entity/UnitUserData was used")
            call e.destroy()
            return 0
        endif
        
        if data.scale != -1.0 then
            call SetUnitScale(someUnit, data.scale, data.scale, data.scale)
        endif
            
        set e.data = data
        
        // Initializing the entity's unit.
        set e.dummy = someUnit
        call UnitAddAbility(someUnit, 'Amrf')
        call UnitRemoveAbility(someUnit, 'Amrf')
        
        // Initializing the entity's position.
        set e.xP = GetUnitX(someUnit)
        set e.yP = GetUnitY(someUnit)
        call MoveLocation(entity.tloc, e.xP, e.yP)
        set e.zP = GetUnitFlyHeight(someUnit) + GetLocationZ(entity.tloc) + data.radius
        
        // Initializing the onEvent trigger.
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DEATH)
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_ORDER)
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_POINT_ORDER)
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_TARGET_ORDER)
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_SELECTED)
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DESELECTED)
        call TriggerAddCondition(e.onEvent, Filter(function entity.onEvent_Action))
        
        // Updating the entity stack.
        set e.ID = entity.COUNT
        set entity.ENTITY[entity.COUNT] = e
        set entity.COUNT = entity.COUNT + 1
        
        
        call SetUnitUserData(someUnit, integer(e))
        
        if entity.COUNT == 1 then
            call TimerStart(entity.T, 1.0/FPS, true, function main)
        endif
        
        if data.radius > RADIUS then
            set RADIUS = data.radius
        endif
        
        call data.onCreate(e)
        
        return e
    endmethod
    
    static method createAsUnit takes entityData data, player whichPlayer, integer unitID, real x, real y, real height, real facing returns entity
        local entity e = entity.allocate()
        
        set e.dummy = CreateUnit(whichPlayer, unitID, x, y, facing)
        
        if data.scale != -1.0 then
            call SetUnitScale(e.dummy, data.scale, data.scale, data.scale)
        endif
            
        set e.data = data
        
        // Initializing the entity's unit.
        call UnitAddAbility(e.dummy, 'Amrf')
        call UnitRemoveAbility(e.dummy, 'Amrf')
        call SetUnitFlyHeight(e.dummy, height, 0.0)
        
        // Initializing the entity's position.
        set e.xP = x
        set e.yP = y
        call MoveLocation(entity.tloc, e.xP, e.yP)
        set e.zP = height + GetLocationZ(entity.tloc) + data.radius
        
        // Initializing the onEvent trigger.
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DEATH)
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_ORDER)
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_POINT_ORDER)
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_TARGET_ORDER)
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_SELECTED)
        call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DESELECTED)
        call TriggerAddCondition(e.onEvent, Filter(function entity.onEvent_Action))
        
        // Updating the entity stack.
        set e.ID = entity.COUNT
        set entity.ENTITY[entity.COUNT] = e
        set entity.COUNT = entity.COUNT + 1
        
        call SetUnitUserData(e.dummy, integer(e))
        
        if entity.COUNT == 1 then
            call TimerStart(entity.T, 1.0/FPS, true, function main)
        endif
        
        if data.radius > RADIUS then
            set RADIUS = data.radius
        endif
        
        call data.onCreate(e)
        
        return e
    endmethod
    
    static method createFromGroupChild takes nothing returns nothing
        call entity.createFromUnit(entityData.create(I), GetEnumUnit())
    endmethod
    
    static method createFromGroup takes integer entityType, group whichGroup returns nothing
        set I = entityType
        call ForGroup(whichGroup, function entity.createFromGroupChild)
    endmethod
    
    static method createFromUnitType takes integer entityType, integer unitID, boolexpr filter returns nothing
        local boolexpr b = And(filterGetUnitsOfTypeIdAll, filter)
        set bj_groupEnumTypeId = unitID
        call GroupEnumUnitsInRect(G, bj_mapInitialPlayableArea, b)
        set I = entityType
        call ForGroup(G, function entity.createFromGroupChild)
        call DestroyBoolExpr(b)
        set b = null
    endmethod
        
//////////////// __END OF CREATION METHODS__ ///////////////
////////////////////////////////////////////////////////////

    private static method onInit takes nothing returns nothing
        set minX = GetRectMinX(bj_mapInitialPlayableArea)
        set minY = GetRectMinY(bj_mapInitialPlayableArea)
        set maxX = GetRectMaxX(bj_mapInitialPlayableArea)
        set maxY = GetRectMaxY(bj_mapInitialPlayableArea)
        set filter = Filter(function enum)
    endmethod
    
    //! runtextmacro SEEplugin()
    
endstruct
//call proj.projectWithAngle(a*bj_DEGTORAD, Atan(-1500.0*1500.0/(SquareRoot(x*x + y*y)*SEEcore_FPS*SEEcore_FPS*proj.zA)), 1500.0)

endlibrary


JASS:
library SEEentityData

interface entityData

        real radius
        real mass = 0.0
        real drag = 0.0
        real restitution = 1.0
        real friction = 0.0
        
        real scale = -1.0
        
        boolean collideable = false
        boolean bounceable = false
        
        string modelPath = ""

        real oX = 0.0
        real oY = 0.0
        real oZ = 0.0
        
        boolean affected = true
        
        method onCreate takes entity e returns nothing defaults nothing
        method onGround takes entity e returns nothing defaults nothing
        method onCollision takes entity e, entity e2 returns nothing defaults nothing
        method onRest takes entity e returns nothing defaults nothing
        method onSelect takes entity e returns nothing defaults nothing
        method onDeselect takes entity e returns nothing defaults nothing
        method onDeath takes entity e returns nothing defaults nothing
        method onExit takes entity e returns nothing defaults nothing
        method onPointOrder takes entity e returns nothing defaults nothing
        method onTargetOrder takes entity e returns nothing defaults nothing
        method onOrder takes entity e returns nothing defaults nothing
        method onLoop takes entity e returns nothing defaults nothing

endinterface

endlibrary


Additional triggers, such as the plugin triggers, can be found inside the map.

********************************

And now, documentation:
*******************************************
****** ~ Implementing instructions ~ ******
*******************************************

Follow these steps to succesfully implement this
system into your map.

1. Import the "dummy.mdx" model from this map to yours,
and be sure to keep the same model path for it.

2. Copy the dummy unit 'e000' from this map to yours.
You can change the rawcode if you want, you'll just
have to change another value later in the code.

3. Copy these triggers into your map:
"SEEentityData"
"SEEcore"
"SEEdatalib"
"SEEplugin"
"SEEheight"

4. Go to the top of the "SEEcore" trigger, and toggle the
constants of the trigger to fit your needs (if you
changed the rawcode of the dummy unit before,
you have to set it to the changed rawcode now. The constant
you have to change is called "DUMMY_ID".

5. Delete everything in between the library delimiters
in the "SEEdatalib" trigger.

6. Run your map once with SEEheight enabled. Follow the
on-screen instructions.

7. Have fun making your maps with this system! :D

8. And for GOD's sake, read the damned documentation!!!


*******************************************
****** ~ Change Log ~ ******
*******************************************

Simple Entity Engine. Version 2.4

--- 2.4 ---
Rewrote ground collision detection script. It's now feckin great.
Replaced the debug messages to use SCOPE_PREFIX.
Added the SEEheight library. (neat idea, shamelessly taken from grim001)
Made SEEcore_FPS a constant again.
Fixed some stuff in the implementing instructions.
Removed the selection circle from the dummy unit.

--- 2.3 ----
Changed friction calculations once again. Now seems much better.
Now keeps default scale on entity.createFromUnit if no scale is specified.
Removed health bars on non-locusted entities from entity.create.
projectTowardsPointSpeed now works as desired.
Entity collisions are now prevented from being calculated twice.
Fixed some bugs in the demo.
onCollision is now executed after collision resolution.
Fixed a wrong entity collision calculation.
Added an extra term in terrain collision detection for better precision.
Added the SAMPLE_RADIUS constant for terrain collisions.
Entities can now slide on the ground. Added a THRESHOLD_VEL constant for this.
Added the setEntityFlyHeight method.
Added the entity.createAsUnit method.
Fixed an issue with resting non-affected entities.
Removed a useless parameter for the removeSpringConstraint method.
Added the removeHealthBar method.
Added a "Constant List" documentation page.
Moved the onLoop call in SEEcore's main function (fixed a bug in resting non-affected entities).
Added a new demo.

--- 2.2 ---
Added projectToPointSpeedEx and projectToPointTimedEx.
Added projectTowardsPointSpeed.
Added enumEntitiesOfPlayer/InRect/OfType.
Added createFromGroup and createFromUnitType.
Rewrote the applyTimedLife method into something much simpler.
Fixed an issue with rest on non-affected entities.
Improved collision detection (now there are two integrated methods)
Removed some useless operations.
Added COLLISION_VEL and THRESHOLD_VEL constants.
Removed some lame parts about the entity struct.
Added the appropriate scale for createFromUnit.
Added a new demo.
Fixed some parts about the documentation.
Most stuff that I've forgotten.

--- 2.1 ---
Fixed friction calculations. (1.0 friction is now no motion, 0.0 friction is no friction at all)
Added an enum method.
Added a new projectToPointSpeed which takes in account gravity.
Made a new demo. (Credits to WILLTHEALMIGHTY for the nuke effect)
Probably added more stuff but I don't remember.

--- 2.0 ---
Remade the whole thing from scratch.
Collision is now time-velocity based.
New collision resolution technique.
Added air drag.
Added spring constraints.
TONS of optimization.
Added a few onX methods to entityData.
COMPLETE makeover on the syntax.
Made a new demo.
Probably many more changes that I forgot about.
--- 2.0.1 ---
Fixed a bug in the demo.

--- 1.2 ---
Added a Bounceable property.
Inlined the GroundBounce function.
Inlined (and optimized) the CollisionBounce function.
Replaced Static entities with an Affected state.
Added a Rest tally for more precise resting states.
Collision optimization.
Added Offsetx/y/z coordinates.
Replaced the crappy Projectile plugin with a cool Vehicle plugin.
Added documentation.
Smaller fixes...

--- 1.1 ---
Patched up collision detection and prevention.
Optimized the GroundBounce function (just a little bit).
Fixed an Entity removal problem (double free).
Removed the use of TriggerAddAction. The system now uses TriggerAddCondition.
Fixed an onEvent method problem with the CreateEntity method.
Added a little debug function.
A couple of smaller bug fixes...

--- 1.0 ---
Public release of the system.

*******************************************
****** ~ Previous Systems ~ ******
*******************************************
- Simple Particle System (slow, buggy and bad)
- Simple Particle System Mark II (used procedural sloppy coding)


JASS:
     *******************************************
     *****      ~ SEE Constant List ~      *****
     *******************************************

The default values listed here are not static, and should be changed if
a user deems it necessary. They are not perfectly calibrated by any means.
     
    private constant integer DUMMY_ID = 'e000'
-- This represents the unit rawcode of the dummy unit. You should transfer the dummy unit
included in this map to yours, or be sure that your dummy works in the same way as the one
in this map (z-axis orientation by animation index).

    public real FPS = 40.0
-- The FPS constant represents the frequency of the SEEcore main function. A higher FPS
means smoother motion, but more processing stress on the system.

    private real GRAVITY_ACCELERATION = -3000.0
-- GRAVITY_ACCELERATION is like the gravitational constant g in real life physics. It
represents the downwards acceleration of an object. This means -3000.0 Wc3 units per second
per second.

    private constant real AIR_DENSITY = 0.001
-- AIR_DENSITY is the global air density constant used in drag calculations. It's abstract
in this case, as it includes many factors such as pi and 0.5 and others. Consider it an
arbitrary constant.

    private constant integer REST_TALLY = 30
-- When an entity is reaching its resting state, this is the amount of iterations of it being
at a resting speed for it to be considered at rest. Higher REST_TALLY indicates more processing
stress on the system, but possibly more precision on resting states.

    private constant real REST_STATE_SPD = 60.0
-- This constant represents the maximum speed that an entity can have to still be considered
at rest. If an entity's speed goes above REST_STATE_SPD, then an entity will stop being
considered for a resting state.

    private constant real COLL_ALGO_SPD = 300.0
-- COLL_ALGO_SPD represents the collision detection algorithm threshold speed. There are two
different collision algorithms included in SEE. One of them is used when entities move at slow
speeds, and the other when entities move at high speeds. When an entity's speed is below
COLL_ALGO_SPD on collision, then the simple algorithm will run. 300.0 is a good value for this.

    private constant real THRESHOLD_COLL_SPD    = 70.0
-- THRESHOLD_COLL_SPEED is used in collision resoltion. If an entity bounces off another entity
and it's resulting speed is lower than THRESHOLD_COLL_SPD, then its speed will be set to 0.0.
    
    private constant real THRESHOLD_SLIDE_SPD   = 100.0
-- THRESHOLD_SLIDE_SPEED is used in collision resolution with the ground. When a bounceable
entity collides with the ground and its perpendicular component of velocity is smaller than
THRESHOLD_SLIDE_SPD, then its perpendicular component is set to 0.0. Therefore, the only
velocity remaining is parallel to the ground, i.e. the entity is sliding.
    
    private constant real SAMPLE_RADIUS         = 32.0
-- SAMPLE_RADIUS is used in terrain collision resolution. SAMPLE_RADIUS is the radius from which
a terrain normal is established. 32.0 is a good value. Try  messing around with it if you don't
like the way entities bounce currently, but don't expect much of a difference.

    private constant real MAX_TERRAIN_HEIGHT    = 1230.500
-- MAX_TERRAIN_HEIGHT is the highest terrain point in your map. The SEEheight library automatically
calculates this value if its trigger is enabled. If you are using very tall walkable destrucables,
then you might have to enter the value manually. Usually not, though.



JASS:
     *******************************************
     ******      ~ SEE Member List ~      ******
     *******************************************

-----------------------------------------------------
*** 1. 'entityData' struct members:
-----------------------------------------------------

    real radius
-- The radius is used in collision detection, both entity-entity and ground-entity.
Radius does NOT automatically detect the appropriate scale of a model.
    
    real mass
-- Mass is used in elastic collisions. There is no unit for mass: all entered
mass properties are relative to eachother.
    
    real drag
-- The drag coefficient is related solely to the decelleration caused by air
resistance. The higher the coefficient, the more air resistance.
    
    real restitution
-- The coefficient of restitution is a coefficient of velocities after entity-entity
impact. It's an abstract value. A coeffiecient of 1.0 is perfectly elastic collision.
    
    real friction
-- This is a coefficient to velocity on ground-entity collisions. If friction = 1.0,
then there is no motion. If friction = 0.0, then there is no friction.
    
    real scale
-- This is a coefficient to the model's scale that will be applied upon entity creation.
    
    boolean collideable
-- This boolean determines whether an entity is collideable or not. Useful if you just want particles.
    
    boolean bounceable
-- This boolean determines if an entity should bounce using elastic collisions.
        
    string modelPath
-- modelPath points to a string, which refers to the entity's model, in the case of
the entity.create method. If you are using entity.createFromUnit, this parameter is useless.

    real oX
    real oY
    real oZ
-- The 'o' vector is an offset parameter, meaning that the model's position will be offset
by (oX, oY, oZ) during entity rendering. Useful for things like inanimate objects. This has
been created because entities must not sink into the ground, thus their height can be, at the
least, their radius. In certain cases, this results in a floating model. A negative oZ can fix
this issue.
        
    boolean affected
-- This boolean determines if an entity is affected by gravity or terrain etc...
Basically means that if affected is true, then the entity will behave like an entity.
If affected is false, the entity will behave like a regular unit in Warcraft III: you can
order it and it will move like a regular unit. One important thing to remember is that
velocity is conserved, even if affected is false.

-----------------------------------------------------
*** 2. 'entity' struct members:
-----------------------------------------------------

    entityData data
-- 'data' points to the assigned entityData of an entity.
    
    unit dummy
-- This unit is the concerned entity unit. For entity.create method, this points
to the dummy unit, on which is attached an effect.
    
    effect eff
-- This variable points to the attached effect for the entity.create method.
    
    real xP
    real yP
    real zP
-- The 'P' vector is the current position vector. zP is the sum of the unit's fly
height and the terrain height at point (xP, yP).
    
    real xV
    real yV
    real zV
-- The 'V' vector is the current velocity vector. Each parameter is divided by FPS,
the amount of frames per second, which is defined as a constant near the top of SEEcore.
'V' is the rate of change of 'P'.
    
    real xA
    real yA
    real zA
-- The 'A' vector is the current acceleration vector. Usually, only zA is used, for
gravitational acceleration, but xA and yA can also be used for wind and such.
'A' is the rate of change of 'V'.
    
    trigger onEvent
-- This trigger points to most of the methods defined in entityData.
    
    integer restCount
-- 'restCount' is the current amount of tallied rests.
    
    integer ID
-- 'ID' is the entity's position in the entity.ENTITY array. Useful in entity destruction.
    
    boolean wantRemoveUnit
-- If wantRemoveUnit is true, then the entity will be removed after the end of the current timeframe.

    real timedLife
-- The time remaining for timedLife. -1.0 if no timedLife is set. -2.0 if an entity has been removed.

    entity constrained
-- This is the pointer to the constrained entity, if any. If an entity A is constrained by another entity B,
then B is not constrained by A.

    real constraintConstant
-- The spring constant "k" in Hooke's law: F = -k*d. A high constant is like a very strong rope. A small
constant is like a rubber band.

    real constraintLength
-- The distance between two constrained entities.

    real constraintFriction
-- The friction opposing the force in Hooke's law, of two constrained entities.
    
    static timer T
-- The main timer controlling the function main.

    static group COLL
-- The group inside which collision detection is ran.
    
    static integer COUNT
-- The current amount of entities, active or not.

    static entity array ENTITY
-- An array containing all entities in a stack.
    
    static integer COUNT_DES
-- The current amount of 'to-be-destroyed' entities.

    static entity array ENTITY_DES
-- An array containing the entities to be destroyed in a stack.
    
    static location tloc = Location(0.0, 0.0)
-- A temporary location used for terrain collision and height.

    static location tloc2 = Location(0.0, 0.0)
-- A temporary location used for terrain collision and height.



JASS:
     *******************************************
     ******      ~ SEE Method List ~      ******
     *******************************************

-----------------------------------------------------
*** 1. 'entityData' struct methods:
-----------------------------------------------------
All of the methods below will work with natives corresponding
to the concerned event. For example, GetTriggerPlayer() will
return the selecting player, for the onSelect method.
 
    method onGround takes entity e returns nothing defaults nothing
-- The method that will be run when an entity collides with the ground.
    
    method onCollision takes entity e, entity e2 returns nothing defaults nothing
-- The method that will be run when two entities collide.
    
    method onRest takes entity e returns nothing defaults nothing
-- This method will be run when an entity reaches a resting state.
    
    method onSelect takes entity e returns nothing defaults nothing
-- The onSelect method will run when a player selects an entity.
NOTE : onSelect and onDeselect are delayed, due to Warcraft III's engine.
    
    method onDeselect takes entity e returns nothing defaults nothing
-- The onDeselect method will run when a player deselects an entity.
NOTE : onSelect and onDeselect are delayed, due to Warcraft III's engine.
    
    method onDeath takes entity e returns nothing defaults nothing
-- The onDeath method will run when an entity dies of any cause including
timed life by <entity>.applyTimedLife.
    
    method onExit takes entity e returns nothing defaults nothing
-- onExit will run once an entity exits map bounds.
    
    method onPointOrder takes entity e returns nothing defaults nothing
-- onPointOrder will run when an entity is issued an order to a point.
NOTE : A Warcraft III bug/stupidity makes it such that PauseUnit registers as a point order.
    
    method onTargetOrder takes entity e returns nothing defaults nothing
-- onTargetOrder will be run when an entity is issued an order to a target.
    
    method onOrder takes entity e returns nothing defaults nothing
-- onOrder will run when an entity is issued an immediate order.

    method onCreate takes entity e returns nothing defaults nothing
-- onCreate will run when an entity is created.

    method onLoop takes entity e returns nothing defaults nothing
-- onLoop will run every iteration of the main function.

-----------------------------------------------------
*** 2. 'entity' struct methods:
-----------------------------------------------------

    static method create takes entityData data, player owner, real xPos, real yPos, real height, real facing returns entity
-- Creates an entity with all of the chosen parameters as data.
    
    static method createFromUnit takes entityData data, unit someUnit returns entity
-- Turns someUnit into an entity.

    static method createAsUnit takes entityData data, player owner, integer unitID, real xPos, real yPos, real height, real facing returns entity
-- Creates a unit of type unitID at the specified flying height and turns it into an entity.

    static method createFromGroup takes integer entityType, group whichGroup returns nothing
-- Takes all units from the group whichGroup and turns them into entities of type entityType. entityType
is a struct's typeid. For example, if a certain entity type is called "Boulder", then this function could
be called in the following manner:
i.e. call entity.createFromGroup(Boulder.typeid, yourGroup)

    static method createFromUnitType takes integer entityType, integer unitID, boolexpr filter returns nothing
-- Takes all units on the map of type unitID and obeying to filter and turns them into entities of type entityType.
entityType is a struct's typeid. For example, if a certain entity type is called "Boulder", and you would want to
make a Boulder entity for every human peasant on the map, then the following should be called:
i.e. call entity.createFromUnitType(Boulder.typeid, 'hpea', Filter(function True))
Where Filter(function True) is a filter that always returns true.

    method remove takes boolean wantRemoveUnit returns nothing
-- Removes the concerned entity. If wantRemoveUnit is true, then the entity
dummy and effect will be removed too.

    method applyTimedLife takes real time, boolean removeUnit returns nothing
-- Applies a timed life to the concerned entity. Is precise up to 1.0/FPS seconds.
    
    method addGravity takes real factor returns nothing
-- Adds gravity's acceleration to the 'A' vector in the entity struct. 'factor'
is a factor to the gravitational acceleration. If it is 1.0, then nothing is changed.
If it is 2.0, then gravity's acceleration is doubled.
    
    method setVelocity takes real xVel, real yVel, real zVel returns nothing
-- Sets the current velocity vector to the vector parameter.
    
    method addVelocity takes real x, real y, real z returns nothing
-- Adds the vector parameter to the current velocity vector.
    
    method setAcceleration takes real xAcc, real yAcc, real zAcc returns nothing
-- Sets the current acceleration vector to the vector parameter.
    
    method addAcceleration takes real x, real y, real z returns nothing
-- Adds the vector parameter to the current acceleration vector.

    method setEntityFlyHeight takes real height returns nothing
-- Sets the concerned entity's fly height to the given value. This acts in the same manner as SetUnitFlyHeight.

    method removeHealthBar takes nothing returns nothing
-- This method removes the floating health bar above an entity. Useful sometimes for collideable entities created
with createFromUnit or createAsUnit.
    
    method projectWithAngle takes real xyAngle, real zAngle, real speed returns nothing
-- Shoots the concerned entity towards xyAngle and zAngle, with the chosen speed.
    
    method projectToPointTimed takes real xPos, real yPos, real zPos, real time returns nothing
-- Shoots the concerned entity to the target point in a parabolic trajectory,
taking in account the time until ground contact.
NOTE : This DOES take in account gravity's acceleration.

    method projectToPointTimedEx takes real xPos, real yPos, real zPos, real time, real maxDist returns nothing
-- This method behaves in the same manner as projectToPointTimed. The difference is that
projectToPointTimedEx has a maxDist parameter which limits the distance to the target point.
If the target point is located further from the entity's current position than maxDist, then
the target point will be scaled to fit maxDist.
NOTE : This DOES take in account gravity's acceleration.
    
    method projectToPointSpeed takes real xPos, real yPos, real zPos, real speed, boolean lob returns nothing
-- Shoots the concerned entity to the position parameter with the chosen speed.
If lob is true, then the entity will be projected to the target point with optimal angle of elevation
to make a lob. If it is false, then it will be projected to the point with optimal angle of elevation
to make a direct hit. If the entity has no gravity, then lob is a useless parameter and the entity will
be projected in a straight line (in such a case, it's possible to use projectTowardsPointSpeed instead)
NOTE : This DOES take in account gravity's acceleration.

    method projectToPointSpeedEx takes real xPos, real yPos, real zPos, real speed, boolean lob, real maxDist returns nothing
-- This method behaves in the same manner as projectToPointSpeed. The difference is that
projectToPointSpeedEx has a maxDist parameter which limits the distance to the target point.
If the target point is located further from the entity's current position than maxDist, then
the target point will be scaled to fit maxDist.
NOTE : This DOES take in account gravity's acceleration.

    method projectTowardsPointSpeed takes real xPos, real yPos, real zPos, real speed returns nothing
-- This method projects an entity towards the target point. If the entity has acceleration,
then it cannot his the target point, as its velocity will stoop while it is on trajectory. If the
entity does not have acceleration, then it will hit the target point successfully.
NOTE : This does NOT take in account gravity's acceleration.

    method addSpringConstraint takes entity attached, real springConstant, real springLength, real friction returns nothing
-- Constrains two entities as a spring constraint, with Hooke's law : F = -k*d. springConstant is the
spring constant in Hooke's law. springLength is the maximum distance between the two entities. friction
is the force opposing the resulting force of Hooke's law.

    method removeSpringConstraint takes nothing returns nothing
-- Removes the currently attached entity from the constraint.

    method enumEntitiesInRange takes group g, real x, real y, real z, real range, boolexpr filter returns nothing
-- Adds all entities within a given (spherical) range from the chosen point to the group g.

    method enumEntitiesInRect takes group g, rect whichRect, boolexpr filter returns nothing
-- Adds all entities within a given rect to the group g.

    method enumEntitiesOfType takes group g, integer entityType, boolexpr filter returns nothing
-- Adds all entities of type entityType to the group g. entityType is found using the 'typeid' member.
For example, for a certain entityData struct called "A", the typeid of "A" would be "A.typeid".
Calling the function to get all entities of type "A" would look like the following:
i.e. call entity.enumEntitiesOfType(yourGroup, A.typeid, yourFilter)

    method enumEntitiesOfPlayer takes group g, player whichPlayer, boolexpr filter returns nothing
-- Adds all entities owned by a certain player to the group g.


Keywords:
Physics, System, Engine, Entity, Bounce, Grenade, Naruto, TITTIES
Contents

Simple Entity Engine 2.4 (Map)

Reviews
19 Oct 2011 Bribe: While obviously still as great, this doesn't meet updated standards of coding practices to have Director's Cut. PM me if you have any complaints.
Level 20
Joined
Apr 22, 2007
Messages
1,960
Mostly depends on your computer and on what kind of entities. There can be a lot in certain situations. For example if you put 200 tiny balls in a bowl, it will lag like crazy. If you make 200 bullets fly around the map, it pretty much won't lag at all.
 
Level 8
Joined
Jun 28, 2008
Messages
356
Mostly depends on your computer and on what kind of entities. There can be a lot in certain situations. For example if you put 200 tiny balls in a bowl, it will lag like crazy. If you make 200 bullets fly around the map, it pretty much won't lag at all.

You just told me "you suck" in face. :O

EDIT: I see you are Looping through an array instead of using the ForGroup method, does that make it faster?
 
Last edited:
Level 8
Joined
Jun 28, 2008
Messages
356
Um, in the map I am currently developing I'm using a a much simpler physics engine to simulate bullets, knockbacks, explosions and so on, and it supports only up to 50 at once laglessly, despite the fact that your code is much more complex and probably two times longer :goblin_cry: And you said 200 :eekani:
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
The most lag comes from collision detection and resolution. Because of how bullets have small radii and are often fired in wide spaces, there isn't really much collision detection other than the native Enum call, and there is basically no collision resolution at all. But the collision detection/resolution was probably the most difficult part to write for this system, so I can understand your issues :p
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
This code is very old. When it was written, I was pretty terrible. If I ever feel really nostalgic and stupid, I might come back some day and rewrite it entirely, and add stuff like angular velocity, and maybe properly implement friction.

That said, if you need any help understanding part of the code, let me know. I'll try my best to explain.

What do you mean about movement oriented spells though?
 
Level 12
Joined
Nov 20, 2007
Messages
660
This code is very old. When it was written, I was pretty terrible. If I ever feel really nostalgic and stupid, I might come back some day and rewrite it entirely, and add stuff like angular velocity, and maybe properly implement friction.

That said, if you need any help understanding part of the code, let me know. I'll try my best to explain.

What do you mean about movement oriented spells though?

He was talking about dinamic spells i think (like a shooting spell --> bullet moving ...)
 
This needs a huge update:

- Use Table instead of UnitUserData (Using UnitUserData would make ANYONE using AIDS, UnitIndexer or AutoIndex trash this D:) We can't let an amazing system like this get thrown away because of some lame Blizzard-thing D:

- GroupEnumUnitsInRange with a null boolexpr and a FoG loop would be way faster than your current setup.

- Don't use onDestroy D:
 
Level 4
Joined
Jan 20, 2011
Messages
65
hi

is the geniouse OP still watching?
i am intrested in the collision resolution, i would like to know how it works
i made something like this myself, i just split the game area allways into 4 smaller quads and sorting all the units insite into seperate groups
on internet its called Quad Tree
but it is not very efficient

are you useing mb a better method? or shall i try optimizing my functions?

help much appriciated :(((
 
Last edited:
Level 4
Joined
Jan 20, 2011
Messages
65
ok i didnt try to completly understand ur code HINDY
but i think i copied the method u used now
before i was testing for each unit if the range between them is below a limit
really like everything mathematically, zooming in with a quadtree

but i saw in ur code that u used the GroupEnumUnitsInRange() function, so now i make this enum in range thing on every moveing unit, and if any other units are in that group i know its collision, its really nice, it is much more efficient then this quadtree stuff, though i dont understand how wc3 can know that a unit is in range without going through the same steps i did^^, maybe the jass is just not as efficient?

well i have 500 static objects and 120 moveing ones scatterd around the map, for my purpose the quadtree system was not as efficient

but im still intrested what the purpose of your "resolution system" is
i was thinking maybe to make a system which prevents the collision checks if the unit has been detected to not collide with any other object soon, like makeing a bigger collision first or something^^
is ur resolution system something like this?
 
Last edited:
Level 20
Joined
Apr 22, 2007
Messages
1,960
ok i didnt try to completly understand ur code HINDY
but i think i copied the method u used now
before i was testing for each unit if the range between them is below a limit
really like everything mathematically, zooming in with a quadtree

but i saw in ur code that u used the GroupEnumUnitsInRange() function, so now i make this enum in range thing on every moveing unit, and if any other units are in that group i know its collision, its really nice, it is much more efficient then this quadtree stuff, though i dont understand how wc3 can know that a unit is in range without going through the same steps i did^^, maybe the jass is just not as efficient?

well i have 500 static objects and 120 moveing ones scatterd around the map, for my purpose the quadtree system was not as efficient

but im still intrested what the purpose of your "resolution system" is
i was thinking maybe to make a system which prevents the collision checks if the unit has been detected to not collide with any other object soon, like makeing a bigger collision first or something^^
is ur resolution system something like this?

Hey, thanks for trying to understand the code! It's nice to see people who actually want to learn stuff.

I'm not sure how the GroupEnum functions are implemented, but yeah Jass is just pretty slow, so that's likely mostly why your code wasn't as fast.

There are a few things I do to speed up collision detection and resolution. As for detection:
  • The GroupEnum radius is this.radius + MAXRADIUS + this.speed*2, where MAXRADIUS is the biggest radius of all entities currently active. The sum of the radii should be pretty clear. The this.speed*2 part is a bit trickier: imagine your entity is a bullet with a small radius moving very fast, and another entity is moving towards the bullet. Normally, a collision should happen. If you don't have the speed*2 term, the other entity will never be considered and the bullet will just fly through. Again, think about it, and if you don't get it at first, try drawing a diagram (it's hard to explain in words why this works).
  • For each pair of colliding entities, I only compute a collision once per iteration, by keeping track of collided entities in collGroup.
  • If the entity's speed is greater than some threshold, then I try and find the precise location where two entities will collide in the future, and fast forward to that position. If not, then I simply check for intersecting entities.

And for collision resolution, really I just try to minimize the calls to math functions (SquareRoot, Sin, Cos, etc.). Though they aren't very expensive, it's nice to know how few you actually need.
 
Level 4
Joined
Jan 20, 2011
Messages
65
ah ty for the inspiration, my system does not care about very fast moveing objects, nice idea putting the this.speed*2 thingy there to fix it, i will update my system to do the same

to do less cos and stuff i do when checking if a distance is below a limit like this:

instead of
Sqrt( xvector*xvector + yvector*yvector ) < 100

u do
xvector*xvector + yvector*yvector < 10000
or
xvector*xvector + yvector*yvector < 100 * 100

saw it in some math tutorial
 
Level 12
Joined
Mar 28, 2005
Messages
160
why is it that "remove health bars" doesn't seem to quite cut it?

hmm also - i am trying to make some sense of your math

JASS:
// --
                    // The following gets the terrain normal <vx, vy, SAMPLE_RADIUS>
                    // and checks to see if the concerned entity sphere is colliding
                    // with the terrain plane.
                    call MoveLocation(entity.tloc, e.xP - SAMPLE_RADIUS, e.yP)
                    call MoveLocation(entity.tloc2, e.xP + SAMPLE_RADIUS, e.yP)
                    set vx = GetLocationZ(entity.tloc) - GetLocationZ(entity.tloc2)
                    call MoveLocation(entity.tloc, e.xP, e.yP - SAMPLE_RADIUS)
                    call MoveLocation(entity.tloc2, e.xP, e.yP + SAMPLE_RADIUS)
                    set vy = GetLocationZ(entity.tloc) - GetLocationZ(entity.tloc2)
                    set vz = vx*vx + vy*vy + SAMPLE_RADIUS*SAMPLE_RADIUS
                    call MoveLocation(entity.tloc, e.xP, e.yP)
                    set r = e.data.radius/SquareRoot(vz) - (e.zP - GetLocationZ(entity.tloc))*SAMPLE_RADIUS/vz
so this determines whether the entity has struck ground or not

if r>0 than it has

what I am interested in is if the entity has struck the side of a cliff - which I imagine would produce a radically different r value

thoughts?

**through some experimientation - it looks as though r is very close to 0 when striking a cliff - the largest value I have encountered is <.03 - when striking the ground the values are much larger, the smallest I have encountered being >.2

seems like a decent enough approach, and has worked full proof so far through my testing ;)
 
Last edited:
It's kind of sad this never sees any usage due to its complex difficulty, making something like this in gui would be pointless though being able to access it by custom script would have been nice. I am aware there are again multiple resources that offer what this system does while also providing custom script access, however anyone have an idea of which is superior in terms of speed? Only Mados and Quilnez's I know of being the alternatives with mados no longer being easily obtainable due to wc3c being down still.

I suppose I probably answered my own question since the more updated one would have better tested code with faster checks, only problem is missing the specific bounce as well linking units together. Kind of ironic with it being so difficult to decide which one to use when likely all current ones will be useless for my later plans of trying to set new floors for multiple levels in the same area since I don't see any kind of ground height fields to disable downwards movement.

Thinking about it, if there's a knockup system and a knockback system with friction that I like then I could probably imitate what this does with my desired features without all the extra cloggage. Edit: I really have to stop answering myself.
 
Last edited:
Top