• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

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.

Moderator

M

Moderator

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 15
Joined
Jan 31, 2007
Messages
502
Quite nice, like your systems before

I didnt really take a look at the code cuz i think it should be ok

only thing i just recognized was this small thing in your test triggers
JASS:
    // Still using GetSpellTargetLoc() because 1.24 isn't official yet
    local location l = GetSpellTargetLoc()
    local real lx = GetLocationX(l)
    local real ly = GetLocationY(l)

A small question(dunno, didnt read all instruction due my lazyness) but adding Locust to any dummies bugs due the interactivities between them or?


also ive recognized a strange bug
i killed that boss but afterwards all grenades or rocks i tried to trow were created at the boss´s dead body and just bounced up there

and lol btw
Climbing on rocks and grenades
lolz.jpg
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
-JonNny
Mhm... this version was released before 1.24 went public. :p
Indeed, locusted entities are not detected by the Enum I use. This is intentional.
As for the boss thing, huh?

-YourNameHere
Because if you want to do that, you could just do:
JASS:
set yourEntity.zP = height

-Sheephunter
Making this in GUI would be suicide. Also, detail about the lag? I haven't experienced any yet.

Thanks for the feedback guys!
 
Level 10
Joined
Sep 21, 2007
Messages
517
hmm... can u tell me what the exact mathematics are behind linear movement, for the height atleast )

i just started jass yesterday or so, and tried the OOP, its a pain in the ass xD how exactly did you index ur structs? where there recycling algorythms or such?

Thanks for your time. of course il credit the help on my map ) [the math, as for the indexing, a rep will do ;) ]

also, i used current height of unit - height difference (z2 - z ) to detect collision, yet there seems to be a fatal error that occurs? any info on that?


and a great job on the system !
 
Level 19
Joined
Feb 4, 2009
Messages
1,313
it seems like no1 but me knows that shift-setting the Selection Scale to 0 completely disables the green bar

go and tell other people about it!!!!:grin:

seriously it's quite logical isn't it?

:thumbs_up: great system

however I prefered the bouncing balls which collide with each other but that's up to you (or maybe it was some other similar system...long time ago)
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
Diehard@Azeroth said:
hmm... can u tell me what the exact mathematics are behind linear movement, for the height atleast )
I use a z component to position, and inside it I store the entity's fly height + the ground height at its current position. Then, each iteration, that z component changes due to collisions/friction/drag/etc and when it's time to move the entity, I do SetUnitFlyHeight with that z variable minus the entity's new height from the ground.

Diehard@Azeroth said:
how exactly did you index ur structs? where there recycling algorythms or such?
That's all included in vJass. The vJass script compiles the Jass upon saving the map, and allocation functions are added etc. Have a look at the compiled war3map.j if you really want to know.

Diehard@Azeroth said:
i used current height of unit - height difference (z2 - z ) to detect collision, yet there seems to be a fatal error that occurs? any info on that?
The fatal error is probably unrelated... are you talking about SEE or about a system you're developing yourself? Also, what collisions are you talking about? Entity-entity or entity-ground?

D4RK_G4ND4LF said:
it seems like no1 but me knows that shift-setting the Selection Scale to 0 completely disables the green bar
srsly? I'll have a look at that.

D4RK_G4ND4LF said:
however I prefered the bouncing balls which collide with each other but that's up to you
Either you're talking about an older version of SEE or grim's system. Either way, I felt like doing something a bit more concrete this time.

krisserz said:
But it seems to be great ;P
Thanks!

Dark_Dragon said:
i did not check code in detail but seems to be optimized quite a bit...
Indeed it is. If you want to know how something in particular works, I could explain it to you. It's a bit hard to understand though, since everything is ridiculously optimized and variable names are meaningless near the end of my script.
 
Level 10
Joined
Sep 21, 2007
Messages
517
oh my godness, you actually answered my questions... dude respect and + rep, btw what do you mean entitys new height from the ground? the z2? and if you use fly height + location z of current height of unit ground doesnt that set the particle's fly height above the unit if terrain is too high? you can just show me a math page if i get too annoying ;p
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
M'k, consider an entity has a position: (x1, y1, z1) at a certain time, where z1 is basically GetUnitFlyHeight(yourUnit) + GetLocationZ(Location(x1, x2)). SEE undergoes another iteration and calculates it's new position (x2, y2, z2) based on things like velocity, acceleration, friction, drag, collisions, constraints, etc. The new height from the ground would be, in Wc3 terms, GetLocationZ(Location(x2, y2)). Then, to display the correct height, you'd do: SetUnitFlyHeight(yourUnit, z2 - GetLocationZ(Location(x2, y2)), 0.0). Now of course this is not how SEE approaches this exactly due to the location leak and other efficiency issues, but that's the basic principle.
 
Level 15
Joined
Jul 19, 2007
Messages
618
HINDYhat said:
Indeed it is. If you want to know how something in particular works, I could explain it to you. It's a bit hard to understand though, since everything is ridiculously optimized and variable names are meaningless near the end of my script.

basically i could need ur help! as you know i am making as well my own entity engine but its not done jet and it wont be released to public, will be only in the mode but mode will be accessable so we can say that if someone really wants it...

anyway i was wondering when an object collides with ground or wall... how to simply get an xy angle if ground is for example under z angle...

maybe: Atan2(FloorZ(y-coll)-FloorZ(y+coll), FloorZ(x-coll)-FloorZ(x+coll))

x and y are current position of entity and z is well 0
coll is collision size of entity and FloorZ is same as LocationZ but takes x and y insted...

did not test it jet but i think its smth like that...
 
Level 15
Joined
Jul 19, 2007
Messages
618
Is there any reason as to why you're looking for this angle? Are you trying to make things bounce off the ground? If so, there's a different method that doesn't require any trigonometric functions.

yes its that i want to make objects bounce! so i wanted to know under which angle terrain is so i know in which direction objects need to bounce... z angle would be easy then if i know xy angle...

anyway if you have even better way and if you have time to tell me it will be appreciated!

ohh and gj for getting it so quickly approved ^^

Greets!
~DD
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
Your solution is overcomplicated an inefficient! :p

I actually learned about this method in Anitarf's Vector library, but I've optimized it greatly and changed a couple things based on some pointers given to me by Earth-Fury and grim001. This is how Anitarf does it in his Vector System demo:
JASS:
call helper.getTerrainNormal(helper.x, helper.y, 4.0)
if vector.dotProduct(this.velocity, helper) > 0 then
    //  ball is already going away from the surface, do not bounce
    return
endif
//  get the component of speed vector that is perpendicular to the terrain
set v=vector.projectionVector(this.velocity, helper)
//  invert the ball's speed component that's perpendicular to terrain
call v.scale(-2.0)
call this.velocity.add(v)
'helper' and 'v' are both vectors. 'this' is a bouncy ball object. If you're wondering what getTerrainNormal does, it basically calculates the vector that's perpendicularly sticking out of the ground at the point (helper.x, helper.y) using the slopes of the lines at 4 points around (helper.x, helper.y), more specifically: (helper.x - 4, helper.y) (helper.x + 4, helper.y) (helper.x, helper.y - 4) and (helper.x, helper.x + 4)

If I inline the getTerrainNormal function, I get this:
JASS:
globals
    location loc = Location(0.0, 0.0)
endglobals

local real z1
local real z2
local real z3
local real z4
call MoveLocation(loc, helper.x - 4, helper.y)
set z1=GetLocationZ(loc)
call MoveLocation(loc, helper.x + 4, helper.y)
set z2=GetLocationZ(loc)
call MoveLocation(loc, helper.x, helper.y - 4)
set z3=GetLocationZ(loc)
call MoveLocation(loc, helper.x, helper.y + 4)
set z4=GetLocationZ(loc)
set helper.x = (z1 - z2)*(2*4)
set helper.y = (z3 - z4)*(2*4)
set helper.z = (2*4)*(2*4)
if vector.dotProduct(this.velocity, helper) > 0 then
    //  ball is already going away from the surface, do not bounce
    return
endif
//  get the component of speed vector that is perpendicular to the terrain
set v=vector.projectionVector(this.velocity, helper)
//  invert the ball's speed component that's perpendicular to terrain
call v.scale(-2.0)
call this.velocity.add(v)
If you don't understand how these maths all work out, here's a suggestion. Draw it out on paper in 2D, make a line that represents the ground, and a vector that represents the ball's velocity towards the ground. Then work out the calculations on paper and you'll see how it unfolds.

SEE's optimized version ties in friction calculations as well, so it would be a lot harder for you to understand it... Also, the '4' value is a system constant and can be changed to whatever the user wants (smaller value: more precise but sometimes more hectic movement). Basically, the optimizations that I did was instead of having only one global location for GetLocationZ, I have 2:
JASS:
local real z1
local real z2
call MoveLocation(loc, helper.x - 4, helper.y)
call MoveLocation(loc2, helper.x + 4, helper.y)
set z1 = GetLocationZ(loc) - GetLocationZ(loc2)
call MoveLocation(loc, helper.x, helper.y - 4)
call MoveLocation(loc, helper.x, helper.y + 4)
set z2 = GetLocationZ(loc) - GetLocationZ(loc2)
After that, it's just a matter of inlining and simplifying the math until nothing more can be done. The final product is actually quite efficient and dare I say neat.

If you still don't understand the way getTerrainNormal works and the math behind it, just ask and I'll draw you a quick paint image explaining it.
 
Level 15
Joined
Jul 19, 2007
Messages
618
Your solution is overcomplicated an inefficient! :p

I actually learned about this method in Anitarf's Vector library, but I've optimized it greatly and changed a couple things based on some pointers given to me by Earth-Fury and grim001. This is how Anitarf does it in his Vector System demo:
JASS:
call helper.getTerrainNormal(helper.x, helper.y, 4.0)
if vector.dotProduct(this.velocity, helper) > 0 then
    //  ball is already going away from the surface, do not bounce
    return
endif
//  get the component of speed vector that is perpendicular to the terrain
set v=vector.projectionVector(this.velocity, helper)
//  invert the ball's speed component that's perpendicular to terrain
call v.scale(-2.0)
call this.velocity.add(v)
'helper' and 'v' are both vectors. 'this' is a bouncy ball object. If you're wondering what getTerrainNormal does, it basically calculates the vector that's perpendicularly sticking out of the ground at the point (helper.x, helper.y) using the slopes of the lines at 4 points around (helper.x, helper.y), more specifically: (helper.x - 4, helper.y) (helper.x + 4, helper.y) (helper.x, helper.y - 4) and (helper.x, helper.x + 4)

If I inline the getTerrainNormal function, I get this:
JASS:
globals
    location loc = Location(0.0, 0.0)
endglobals

local real z1
local real z2
local real z3
local real z4
call MoveLocation(loc, helper.x - 4, helper.y)
set z1=GetLocationZ(loc)
call MoveLocation(loc, helper.x + 4, helper.y)
set z2=GetLocationZ(loc)
call MoveLocation(loc, helper.x, helper.y - 4)
set z3=GetLocationZ(loc)
call MoveLocation(loc, helper.x, helper.y + 4)
set z4=GetLocationZ(loc)
set helper.x = (z1 - z2)*(2*4)
set helper.y = (z3 - z4)*(2*4)
set helper.z = (2*4)*(2*4)
if vector.dotProduct(this.velocity, helper) > 0 then
    //  ball is already going away from the surface, do not bounce
    return
endif
//  get the component of speed vector that is perpendicular to the terrain
set v=vector.projectionVector(this.velocity, helper)
//  invert the ball's speed component that's perpendicular to terrain
call v.scale(-2.0)
call this.velocity.add(v)
If you don't understand how these maths all work out, here's a suggestion. Draw it out on paper in 2D, make a line that represents the ground, and a vector that represents the ball's velocity towards the ground. Then work out the calculations on paper and you'll see how it unfolds.

SEE's optimized version ties in friction calculations as well, so it would be a lot harder for you to understand it... Also, the '4' value is a system constant and can be changed to whatever the user wants (smaller value: more precise but sometimes more hectic movement). Basically, the optimizations that I did was instead of having only one global location for GetLocationZ, I have 2:
JASS:
local real z1
local real z2
call MoveLocation(loc, helper.x - 4, helper.y)
call MoveLocation(loc2, helper.x + 4, helper.y)
set z1 = GetLocationZ(loc) - GetLocationZ(loc2)
call MoveLocation(loc, helper.x, helper.y - 4)
call MoveLocation(loc, helper.x, helper.y + 4)
set z2 = GetLocationZ(loc) - GetLocationZ(loc2)
After that, it's just a matter of inlining and simplifying the math until nothing more can be done. The final product is actually quite efficient and dare I say neat.

If you still don't understand the way getTerrainNormal works and the math behind it, just ask and I'll draw you a quick paint image explaining it.


thanks a lot for explaining! yes i know what you are talking about and i wanted to do it same way! i knew how to get that z1 and z2 but i am not sure how to know at wich direction should object bounce! i mean i thought of picking now angle: Atan2(z3-z4,z1-z2) now i thought that thats and xy angle at which object should go and then if i have that z angle would be Atan(z5/(2*4)) as far as i get it right... but if i dont use trigonometry i am not sure... nah i mean you have a lot of this x,y,z vectors so i am sure you made it really fast but i am just not sure how to get "x,y,z vector" on impact with wall or ground :S

EDIT: now when looking at your code i tried to understand it and well i am still not 100% sure what u did out there but basically z1 and z2 are bacically already direction vectors? coz you did xv = z1-z2 and yv = z3-z4

from that i would say i get it a little bit more now but still if you find free time to make an little picture in paint it will be great but i wont ask that... do it if you really want.

5/5 for your engine coz now when i looked a little closer i can really say its awesome!

once again thanks for your help and greets!
~DD
 
Last edited:
Level 20
Joined
Apr 22, 2007
Messages
1,960
No problem, if you need help with things like object-object collisions, I could try helping you but I haven't actually delved into such matters in such a long time, I probably forgot how it even works. :p Still, if you need any help related to understanding SEE, just drop a message and I'll do my best to help.
 
Level 15
Joined
Jul 19, 2007
Messages
618
No problem, if you need help with things like object-object collisions, I could try helping you but I haven't actually delved into such matters in such a long time, I probably forgot how it even works. :p Still, if you need any help related to understanding SEE, just drop a message and I'll do my best to help.

thanks for your offer and help ill make sure to ask if i really need help about physics/math! thats what PurplePoot said that i should ask you back then.

once agan thanks for your time and teaching ;)
Greets!
~DD
 
Level 8
Joined
Apr 5, 2008
Messages
367
Need help with importing this system.
The documentary says all I need is to copy the 4 triggers and adept the units RawCode.
Now this is what I get when I try to save (using NewGen)
set SGMNT[0].xP = e.xP - Cos(r)*100.0 - SGMNT[0] is not of a type that allows . syntax

The only way it works is when I copy all the triggers from the testmap to my own.
So where's my problem?
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
Element of Water is correct.

Anyway, updaet LULZ! The demo map is very simple. I just made it to show the new ground collisions, which are pretty neat. Also updated the implementing instructions so Na_Dann_Ma_GoGo's problem should happen less often.

Yey!
 
Level 1
Joined
Oct 18, 2009
Messages
4
Help

good job btw.

how did you do the collisions with the walls, i understand that its when the "flying height < 1" or something but how did you make it reflect off the walls.

ive been trying to make a similar thing in GUI coz no one else seems to be able to and the only thing i cant do is the angle of reflection, its all i need and i have it finished.
Cheers 5/5 btw
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
I was going to allow for toggling between UserData and grim001's AutoIndex system (I think that's what it was called, whatever) in the next version, but I just stopped modding Wc3 lol. In any case, it's very easy to do it yourself. I know at least one person who has done it without much trouble.
 
Level 2
Joined
Jan 21, 2008
Messages
16
Hi HINDYhat!
In Version 1.2 you had a neat Vehicle Plugin. To bad i cant find the map anymore. Is it possible to port it to the newest Version?
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
@Lethien, it's a physical object engine. It makes shiny physics.

@iNfamous, the entire system was rewritten several times between 1.2 and 2.4, so it won't be an easy port. If you can get your hands on the 1.2 map (which I don't have), then you should be able to port the plugin with maybe a little trouble.
 
Top