BMax-like wrapper to GLB commands

Previous topic - Next topic

AndyH

Hi all - bit of a delay in posting this (away at family party) and I haven't finished the bits I wanted to yet (collisions and text print still to do) but here's the BMax-like wrapper I was talking about.

It contains a combination of code I've written, with code found on the forums integrated or modified to fit in.

You have to call oInitialiseSprites() at the start of your program to set up some values.  In BMax I would just write code 'outside' of a function in the sprites include file and it would be run but I don't think GLB supports that (include files must only contain functions, types and global declarations from what I can tell).

Once you've called the initialise function, you are free to start using the functions within.  Note that you must use the functions to load sprites or animations, otherwise you'll get conflicts.


Here's a brief summary of what the functions allow you to do:

= oLoadSprite( "filename" )  // load a single sprite, returns an ovine sprite Id (not a GLB sprite id) for you to reference your sprite

= oReserveSprite()  // just returns and ovine sprite Id - useful if you want to manipulate sprites yourself such as with GRABSPRITE but don't want to load a sprite in - just creates the place holder for it

= oLoadTiles( "filename", width, height ) // load a tilestrip in, returns the ovine sprite Id - uses LOADANIM and keeps the tiles/frames in one sprite.  Tilestrips can not (currently) be rotated, scaled or have colour applied to it.  It is intended for drawing game levels from tilemaps or simple animation.   Gernot has added a function to allow ANIM's to be rotated and scaled so I'll update this to allow that.  If Gernot can also give us the colour parameter for ROTOZOOMANIM (as I believe it uses POLYVECTOR) then this could support the colour modifier too.

= oLoadAnim( "filename", width, height ) // load an animation strip in, returns the ovine sprite Id.  Internally, each animation frame is GRABSPRITE'd into it's own sprite.  This allows the use of rotate, zoom and colour modification commands.  There appears to be a bug with GRABSPRITE where coloured pixels bordering with transparent pixels are altered in the GRABSPRITE version of the sprite which only appears with ZOOM or ROTATION is used.  I'm hoping Gernot will fix this.

oAutoMidHandle( TRUE or FALSE )  // set whether auto mid-handle (the pivot point) is on or off -  automatically sets the handle to the middle of the sprite if TRUE when oLoad.... functions are used.

oSetHandle( ovineSpriteId, hx, hy )  // allow you to set the handle (pivot point) to a specific X and Y position of a sprite / tilemap already loaded in with one of the oLoad... functions

oMidHandle( ovineSpriteId ) // set the handle to the mid-point of the supplied ovine sprite Id

oSetScale( scale ) // set the scale for all sprite drawing from this point on (excludes tilemaps currently).  1=normal, 0.5 = half size, 2.0 = double size

oSetRotation( angle ) // set the rotation for all sprite drawing from this point on

oSetColor( rgbValue ) // sets the colour modifier for all sprite drawing from this point on ( RGB(255,255,255) will draw the sprite normally, RGB(255,0,0) will make the sprite Red etc)

oSetPrecision( TRUE or FALSE ) // if TRUE - your game will use the real SIN / COS functions supplied by GLB.  If FALSE, will use the QSIN and QCOS variants which may be faster on some platforms (eg: GP2X) but with less accuracy.  I've not noticed a problem with these.

oDrawSprite( ovineSpriteId, xPos, yPos, Frame ) // draws the sprite at the X and Y position, with the handle, colour, rotation and scale as set previously.  Note that on platforms that have hardware accelerated support (Windows, Mac, Linux) the rotation, scale and colour modifiers will be pretty quick.  On GP2X, PocketPC and Smart Phones you are advised to use these sparingly as it will all be done in software.



Example

If you have used BMax then the above may not need much explanation.  Here's a brief example of the commands:

Code (glbasic) Select
oInitialiseSprites()

oAutoMidHandle(TRUE)  // from this point on, all oLoad... commands will set the handle to the centre of the sprite

spr1 = oLoadSprite("sprite1.png") // load a single frame sprite in, variable spr1 contains the Id of that ovineSpriteId
spr2 = oLoadSprite("sprite2.png") // load a second single frame sprite in

oSetHandle( spr2, 16,0 ) // change the handle (pivot point) of sprite 2 to be 16 across, 0 down

oAutoMidHandle(FALSE)  // from this point on, all oLoad/// commands will leave the handle at top/left (0,0)

spr3 = oLoadAnim("player.png", 32,32, 10) // load an animation for the player in this example, where each frame is 32x32 in size and there are 10 frames to load in.

oSetHandle (spr3, 16,32 ) // set the handle for all frames in this animation to the bottom centre (where his feet are)

oSetPrecision( FALSE ) // use the quick QCOS/QSIN functions for rotations and scale

oDrawSprite( spr1, 320, 240, 1 ) // draw the sprite at the centre of the screen (assuming 640x480 game window) but it's handle will mean it will be centred too.  It only has one frame cause we used oLoadSprite

oSetScale( 2 ) // set scale to 2
oSetRotation( 45 ) // set angle
oSetColor( RGB(0,255,128) ) // lets tint the colour value
oDrawSprite( spr2, 320, 240, 1 ) // draws this sprite at a scale of 2, 45 degree angle with a greeny-blue tint
// more sprite drawing from this point on would use same scale, rotation and color values until you set them to a different value

// now a for loop
oSetColor( RGB(255,255,255) )
FOR t = 1 TO 20
  oSetScale (t / 10) // set the scale
  oSetRotation( t * 18 ) // set the scale
  oDrawSprite( spr3, t*40, 100, MOD(t, 10) ) // draw frames from the animation going across the screen using current scale+rotation
NEXT t
// put everything back
oSetScale(1)
oSetRotation(0)

SHOWSCREEN
MOUSEWAIT
Note that I have used variables spr1, spr2 and spr3 in the example above although these variables could be called anything, or in most cases you'd store them in your own types for access later.  These just store the special ovine sprite Id's returned by function and refers to an array index in oSprites[].  You can write more functions to allow access to this, or access the type'd properties directly if you wish.  Confession: I've not actually tried to run the code above as I've just typed in directly into the forum, but should all work, if not probably just a silly typo on my part  

The code:

Well here it is, I've got this in an include file in my project.  Will add more to this, fix bugs, make changes etc as my game develops.  Feel free to use this in your own games if you want, but would appreciate if you'd share your ideas or improvements with the whole GLB community as the idea of all this is to help make things easier to do :D  As per the usual disclaimers - it's work in progress, will probably get optimised as time goes on, and use at your own risk.  

Note: new version(s) can be found in later posts in this thread.  Scroll down to see them.

Code (glbasic) Select
// --------------------------------- //
// Project: A wrapper around SPRITES
// so never need to manage Id's directly
// --------------------------------- //
//   Version:  1.000
// --------------------------------- //
// --------------------------------- //
// Please retain comments-free to use
// these functions in any freeware or
// commercial programs.  Given to the
// community without any warranties
// of any kind.  Please send updates
// or improvements to andyh@ovine.net
// --------------------------------- //

// TODO:
// - PRINT TEXT command (with color and rotation/scaling options)
// - Collision functions - decide to use anim or sprite collide, box collision, point collision with box, point collision with sprite or anim
// (note collisions only work with unscales/rotated sprites/anims)
// note also collisions must take into account the handle!

TYPE OSprType
    id // Sprite Id
    w;h // width and height
    hx;hy // handle X and Y

    maxframes // total num of frames,
    // 0 = Tilemap (use DRAWANIM) - no scale, rotate or color support - but can be used to draw tilemaps quickly
    // 1 = Single Sprite
    // >1 = the count of sprite Id's used, ie:  id+(maxFrames-1) == last sprite Id FOR this animation
ENDTYPE

GLOBAL oSprites[] AS OSprType


// this global type is used to store settings that modify the way sprites are drawn
TYPE OSpriteSettings
scale // scale for sprite
rotation // current rotation settings for sprite
color // RGB colour to apply (POLYVECTOR)
midHandle // auto-mid handle mode (T/F)
mathPre // maths precision (TRUE = use real COS/SIN, FALSE = use false SIN/COS)
ENDTYPE

GLOBAL OSprSettings AS OSpriteSettings



FUNCTION oInitialiseSprites:
OSprSettings.color = RGB(255,255,255)
OSprSettings.scale = 1.0
OSprSettings.rotation = 0
OSprSettings.midHandle = TRUE
OSprSettings.mathPre = TRUE

ENDFUNCTION



// starts at SPRITE ID = 1, Sprite Id = 0 is reserved for loading
@FUNCTION _GetNextSpriteID:
STATIC _oSprNextId
INC _oSprNextId, 1
    RETURN _oSprNextId
ENDFUNCTION

// Get next oSprites, starts at 0
@FUNCTION _GetNextOSprID:
STATIC  _oSprNextArrayId
INC _oSprNextArrayId, 1
    RETURN (_oSprNextArrayId - 1)
ENDFUNCTION



// --------------------------------- //
// Project: Loading routines - only load
// sprites at start or when not in middle
// of a gameloop where you are updating the
// screen - do not load sprites directly
// with the GLB LOADSPRITE command
// --------------------------------- //
FUNCTION oLoadSprite: _file$
LOCAL t AS OSprType
LOCAL num
    t.id = _GetNextSpriteID()
    LOADSPRITE _file$, t.id
    t.maxframes = 1  // defines a single sprite
    GETSPRITESIZE t.id,t.w,t.h
    IF OSprSettings.midHandle
    t.hx = t.w/2
    t.hy = t.h/2
    ELSE
    t.hx = 0
    t.hy = 0
    ENDIF
    DIMPUSH oSprites[], t
    RETURN _GetNextOSprID()
ENDFUNCTION

// used to reserve a sprite Id and slot for own use (eg: if you want to GRABSPRITE)
FUNCTION oReserveSprite:
LOCAL t AS OSprType
LOCAL num
    t.id = _GetNextSpriteID()
t.maxframes = 1  // defines a single sprite
    t.w = 0
    t.h = 0
  t.hx = 0
  t.hy = 0
    DIMPUSH oSprites[], t
    RETURN _GetNextOSprID()
ENDFUNCTION

FUNCTION oLoadTiles: _file$, _w, _h
LOCAL t AS OSprType
LOCAL num
    t.id = _GetNextSpriteID()
    LOADANIM _file$, t.id, _w, _h
    t.maxframes = 0 // defines a tilemap of frames in a sprite
    GETSPRITESIZE t.id,t.w,t.h
    IF OSprSettings.midHandle
    t.hx = t.w/2
    t.hy = t.h/2
    ELSE
    t.hx = 0
    t.hy = 0
    ENDIF
    DIMPUSH oSprites[], t
    RETURN _GetNextOSprID()
ENDFUNCTION

FUNCTION oLoadAnim: _file$, _w, _h, _maxframes
LOCAL t AS OSprType
LOCAL num
    LOADANIM _file$, 0, _w, _h

LOCAL i
SETTRANSPARENCY RGB(0,0,0)
FOR i=0 TO _maxframes-1
DRAWRECT 0,0, _w,_h, RGB(0,0,0) // transparent colour
DRAWANIM 0, i, 0,0
LOCAL nextSprite
nextSprite = _GetNextSpriteID()
IF t.id = 0 THEN t.id = nextSprite
GRABSPRITE nextSprite, 0,0, _w,_h
NEXT
    t.maxframes = _maxframes // defines a sequence of grabbed sprites
    t.w = _w
    t.h = _h
    IF OSprSettings.midHandle
    t.hx = t.w/2
    t.hy = t.h/2
    ELSE
    t.hx = 0
    t.hy = 0
    ENDIF
    DIMPUSH oSprites[], t
    LOADSPRITE "", 0 // clear out temporary sprite from memory
    BLACKSCREEN
    SETTRANSPARENCY RGB(255,0,128)
    RETURN _GetNextOSprID()
ENDFUNCTION




// - Modifiers
FUNCTION oAutoMidHandle: x
OSprSettings.midHandle = x
ENDFUNCTION

FUNCTION oSetHandle: _sprId, _hx, _hy
oSprites[_sprId].hx = _hx
oSprites[_sprId].hy = _hy
ENDFUNCTION

FUNCTION oMidHandle: _sprId
oSprites[_sprId].hx = oSprites[_sprId].w/2
oSprites[_sprId].hy = oSprites[_sprId].h/2
ENDFUNCTION

FUNCTION oSetScale: _scale
OSprSettings.scale = _scale
ENDFUNCTION

FUNCTION oSetRotation: _rotation
OSprSettings.rotation = _rotation
ENDFUNCTION

FUNCTION oSetColor: _color
OSprSettings.color = _color
ENDFUNCTION

FUNCTION oSetPrecision: x
OSprSettings.mathPre = x
ENDFUNCTION



FUNCTION oDrawSprite: _spr, _x, _y, _frame
SELECT oSprites[_spr].maxframes
CASE 0 // -------- A tilemap --------
DRAWANIM oSprites[_spr].id, _frame, _x - oSprites[_spr].hx, _y - oSprites[_spr].hy

DEFAULT // -------- A Sprite --------

LOCAL sprId
LOCAL dframe
IF _frame > oSprites[_spr].maxframes - 1
dframe = oSprites[_spr].maxframes - 1
ELSE
dframe = _frame
ENDIF
sprId = oSprites[_spr].id + dframe

IF OSprSettings.color <> RGB(255,255,255)
// must use polyvector to draw the sprite
_oRotoZoomPoly( _spr, sprId, _x, _y, OSprSettings.rotation, OSprSettings.scale, OSprSettings.color)

ELSEIF OSprSettings.scale = 1 AND OSprSettings.rotation <> 0
// must use ROTOSPRITE
_oRotoPivot( _spr, sprId, _x, _y, OSprSettings.rotation)

ELSEIF OSprSettings.scale <> 1 AND OSprSettings.rotation = 0
// must use ZOOMSPRITE
_oZoomSprite( _spr, sprId, _x, _y, OSprSettings.scale)

ELSEIF OSprSettings.scale <> 1 AND OSprSettings.rotation <> 0
// must use ROTOZOOMSPRITE
_oRotoZoomPivot( _spr, sprId, _x, _y, OSprSettings.rotation, OSprSettings.scale)

ELSE
// use DRAWSPRITE
DRAWSPRITE sprId, _x - oSprites[_spr].hx, _y - oSprites[_spr].hy

ENDIF
ENDSELECT
ENDFUNCTION




// TODO : Optimise this
@FUNCTION _oZoomSprite: _spr, _id, _x, _y, _scale
LOCAL sx, sy,  hx,hy

    sx = oSprites[_spr].w / 2
    sy = oSprites[_spr].h / 2

    hx = oSprites[_spr].hx
    hy = oSprites[_spr].hy

    DEC hx, sx
    DEC hy, sy

    hx = hx * _scale
    hy = hy * _scale

ZOOMSPRITE _id,  _x-hx-sx, _y-hy-sx, _scale, _scale

ENDFUNCTION


@FUNCTION _oRotoZoomPivot: _spr, _id, _x,_y, _angle, _scale
LOCAL sx, sy, rx,ry, px,py
LOCAL cosp, sinp

    cosp=QCOS(_angle)
    sinp=QSIN(_angle)

    sx = oSprites[_spr].w / 2
    sy = oSprites[_spr].h / 2

    px = oSprites[_spr].hx
    py = oSprites[_spr].hy

    // reverse rotate pivot point arount center
    // of sprite
    DEC px, sx
    DEC py, sy
    rx = (px*cosp + py*sinp) * _scale
    ry = (py*cosp - px*sinp) * _scale

    // adjust center
    INC rx,sx
    INC ry,sy

    // perform drawing
    ROTOZOOMSPRITE _id, _x-rx, _y-ry, _angle, _scale

ENDFUNCTION


FUNCTION _oRotoZoomPoly: _spr, _id, _x,_y, _angle, _scale, _col
LOCAL sinp, cosp, spw, sph, dx, dy
LOCAL hx, hy, dxp, dxm, dyp, dym

    sinp = QSIN(_angle)
    cosp = QCOS(_angle)

    spw = oSprites[_spr].w
    sph = oSprites[_spr].h

    dx=spw/2
    dy=sph/2

    hx = (dx - oSprites[_spr].hx)
    hy = (dy - oSprites[_spr].hy)

    dxp = (dx+hx) * _scale
    dxm = (dx-hx) * _scale
    dyp = (dy+hy) * _scale
    dym = (dy-hy) * _scale

    STARTPOLY _id
    POLYVECTOR _x - cosp * dxm - sinp * dym,   _y - cosp * dym + sinp * dxm,   0,   0,    _col
    POLYVECTOR _x - cosp * dxm + sinp * dyp,   _y + cosp * dyp + sinp * dxm,   0,   sph,  _col
    POLYVECTOR _x + cosp * dxp + sinp * dyp,   _y + cosp * dyp - sinp * dxp,   spw, sph,  _col
    POLYVECTOR _x + cosp * dxp - sinp * dym,   _y - cosp * dym - sinp * dxp,   spw, 0,    _col
    ENDPOLY

ENDFUNCTION

FUNCTION _oRotoZoomPolyBAK: _spr, _id, _x,_y, _angle, _scale, _col
LOCAL sphi, cphi, spx, spy, dx, dy

    sphi=QSIN(_angle)
    cphi=QCOS(_angle)
    spx = oSprites[_spr].w
    spy = oSprites[_spr].h
    dx=spx/2*_scale
    dy=spy/2*_scale

    LOCAL hx, hy
    hx = (spx/2-oSprites[_spr].hx)*_scale
    hy= (spy/2-oSprites[_spr].hy)*_scale
//    hx = ((spx/2)*_scale)-(oSprites[_spr].hx*_scale)
//    hy= ((spy/2)*_scale)-(oSprites[_spr].hy*_scale)

    STARTPOLY _id
    POLYVECTOR _x - cphi* (dx-hx) - sphi* (dy-hy),  _y - cphi*(dy-hy) + sphi*(dx-hx),   0,   0,   _col
    POLYVECTOR _x - cphi* (dx-hx) + sphi* (dy+hy),  _y + cphi*(dy+hy) + sphi*(dx-hx),   0,   spy, _col
    POLYVECTOR _x + cphi* (dx+hx) + sphi*(dy+hy),   _y + cphi*(dy+hy) - sphi*(dx+hx),   spx, spy, _col
    POLYVECTOR _x + cphi* (dx+hx) - sphi*(dy-hy),   _y - cphi*(dy-hy) - sphi*(dx+hx),   spx, 0,   _col
    ENDPOLY

ENDFUNCTION


@FUNCTION _oRotoPivot: _spr, _id, _x, _y, _angle
LOCAL sx, sy, rx,ry, px,py
LOCAL cosp, sinp

    cosp=QCOS(_angle)
    sinp=QSIN(_angle)

    sx = oSprites[_spr].w / 2
    sy = oSprites[_spr].h / 2

    px = oSprites[_spr].hx
    py = oSprites[_spr].hy

    DEC px, sx
    DEC py, sy

    rx = px*cosp + py*sinp
    ry = py*cosp - px*sinp

    INC rx,sx
    INC ry,sy

    ROTOSPRITE _id, _x-rx, _y-ry, _angle

ENDFUNCTION



FUNCTION QSIN: x
IF OSprSettings.mathPre THEN RETURN SIN(x)

    WHILE x>360.0;  DEC x, 360.0; WEND
    WHILE x<0;      INC x, 360.0; WEND
    IF x>180.0 THEN x = 180.0-x

    x = x/57.296
    x = 1.2732 * x -0.4053 * x * ABS(x)

x = 0.225*(x*ABS(x)-x)+x
RETURN x
ENDFUNCTION


FUNCTION QCOS: x
IF OSprSettings.mathPre THEN RETURN COS(x)
RETURN QSIN(x+90)
ENDFUNCTION


INLINE
float qInvSqrt(float x){
   float xhalf = 0.5f * x;
   int i = *(int*)&x; // store floating-point bits in integer
   i = 0x5f3759d5 - (i >> 1); // initial guess for Newton's method
   x = *(float*)&i; // convert new bits into float
   x = x*(1.5f - xhalf*x*x); // One round of Newton's method
   return x;
}
ENDINLINE


FUNCTION QSQR: y
INLINE
return 1.0f / qInvSqrt(y);
ENDINLINE
ENDFUNCTION

bigsofty

Very nice work indeed Andy! :)
Cheers,

Ian.

"It is practically impossible to teach good programming style to students that have had prior exposure to BASIC.  As potential programmers, they are mentally mutilated beyond hope of regeneration."
(E. W. Dijkstra)

AndyH

New version, some improvements and bug fixes.  

New oSprColl and oAnimColl commands which take into account the handles of a sprite during the collision but currently not the scale and rotation (will see about a solution to that at a later date) and not for Tilemaps yet.  They take the ovine sprite Id as the parameters.  

Tilemaps (anim frames) now can rotate and scale.  If GLBasic supports the colour parameter for the ROTOZOOMANIM command (or similar) then will be able to add colour support too)

Code (glbasic) Select
// --------------------------------- //
// Project: A wrapper around SPRITES
// so never need to manage Id's directly
// --------------------------------- //
//   Version:  1.001
// --------------------------------- //
// --------------------------------- //
// Please retain comments-free to use
// these functions in any freeware or
// commercial programs.  Given to the
// community without any warranties
// of any kind.  Please send updates
// or improvements to andyh@ovine.net
// --------------------------------- //

// TODO:
// - PRINT TEXT command (with color and rotation/scaling options)
// - Collision functions - decide to use anim or sprite collide, box collision, point collision with box, point collision with sprite or anim
// (note collisions only work with unscales/rotated sprites/anims)
// note also collisions must take into account the handle!

TYPE OSprType
    id // Sprite Id
    w;h // width and height
    hx;hy // handle X and Y

    maxframes // total num of frames,
    // 0 = Tilemap (use DRAWANIM) - no scale, rotate or color support - but can be used to draw tilemaps quickly
    // 1 = Single Sprite
    // >1 = the count of sprite Id's used, ie:  id+(maxFrames-1) == last sprite Id FOR this animation
ENDTYPE

GLOBAL oSprites[] AS OSprType


// this global type is used to store settings that modify the way sprites are drawn
TYPE OSpriteSettings
scale // scale for sprite
rotation // current rotation settings for sprite
color // RGB colour to apply (POLYVECTOR)
midHandle // auto-mid handle mode (T/F)
mathPre // maths precision (TRUE = use real COS/SIN, FALSE = use false SIN/COS)
ENDTYPE

GLOBAL OSprSettings AS OSpriteSettings



FUNCTION oInitialiseSprites:
OSprSettings.color = RGB(255,255,255)
OSprSettings.scale = 1.0
OSprSettings.rotation = 0
OSprSettings.midHandle = TRUE
OSprSettings.mathPre = TRUE

// TODO: to support point collision with Sprite/anim must create a sprite (id = 1) that is just 1 pixel
ENDFUNCTION


// starts at SPRITE ID = 1, Sprite Id = 0 is reserved for loading
@FUNCTION _GetNextSpriteID:
STATIC _oSprNextId
INC _oSprNextId, 1
    RETURN _oSprNextId
ENDFUNCTION

// Get next oSprites, starts at 0
@FUNCTION _GetNextOSprID:
STATIC  _oSprNextArrayId
INC _oSprNextArrayId, 1
    RETURN (_oSprNextArrayId - 1)
ENDFUNCTION



// --------------------------------- //
// Project: Loading routines - only load
// sprites at start or when not in middle
// of a gameloop where you are updating the
// screen - do not load sprites directly
// with the GLB LOADSPRITE command
// --------------------------------- //
FUNCTION oLoadSprite: _file$
LOCAL t AS OSprType
LOCAL num
    t.id = _GetNextSpriteID()
    LOADSPRITE _file$, t.id
    t.maxframes = 1  // defines a single sprite
    GETSPRITESIZE t.id,t.w,t.h
    IF OSprSettings.midHandle
    t.hx = t.w/2
    t.hy = t.h/2
    ELSE
    t.hx = 0
    t.hy = 0
    ENDIF
    DIMPUSH oSprites[], t
    RETURN _GetNextOSprID()
ENDFUNCTION

// used to reserve a sprite Id and slot for own use (eg: if you want to GRABSPRITE)
FUNCTION oReserveSprite:
LOCAL t AS OSprType
LOCAL num
    t.id = _GetNextSpriteID()
t.maxframes = 1  // defines a single sprite
    t.w = 0
    t.h = 0
  t.hx = 0
  t.hy = 0
    DIMPUSH oSprites[], t
    RETURN _GetNextOSprID()
ENDFUNCTION

FUNCTION oLoadTiles: _file$, _w, _h
LOCAL t AS OSprType
LOCAL num
    t.id = _GetNextSpriteID()
    LOADANIM _file$, t.id, _w, _h
    t.maxframes = 0 // defines a tilemap of frames in a sprite
    t.w = _w
    t.h = _h
    //GETSPRITESIZE t.id,t.w,t.h
    IF OSprSettings.midHandle
    t.hx = t.w/2
    t.hy = t.h/2
    ELSE
    t.hx = 0
    t.hy = 0
    ENDIF
    DIMPUSH oSprites[], t
    RETURN _GetNextOSprID()
ENDFUNCTION

FUNCTION oLoadAnim: _file$, _w, _h, _maxframes
LOCAL t AS OSprType
LOCAL num
    LOADANIM _file$, 0, _w, _h

LOCAL i
SETTRANSPARENCY RGB(255,0,128)
BLACKSCREEN
FOR i=0 TO _maxframes-1
DRAWRECT 0,0, _w+6,_h+6, RGB(255,0,128) // transparent colour
DRAWANIM 0, i, 3,3
LOCAL nextSprite
nextSprite = _GetNextSpriteID()
IF t.id = 0 THEN t.id = nextSprite
GRABSPRITE nextSprite, 3,3, _w,_h
NEXT
    t.maxframes = _maxframes // defines a sequence of grabbed sprites
    t.w = _w
    t.h = _h
    IF OSprSettings.midHandle
    t.hx = t.w/2
    t.hy = t.h/2
    ELSE
    t.hx = 0
    t.hy = 0
    ENDIF
    DIMPUSH oSprites[], t
    LOADSPRITE "", 0 // clear out temporary sprite from memory
    BLACKSCREEN
    //SETTRANSPARENCY RGB(255,0,128)
    RETURN _GetNextOSprID()
ENDFUNCTION




// - Modifiers
FUNCTION oAutoMidHandle: x
OSprSettings.midHandle = x
ENDFUNCTION

FUNCTION oSetHandle: _sprId, _hx, _hy
oSprites[_sprId].hx = _hx
oSprites[_sprId].hy = _hy
ENDFUNCTION

FUNCTION oMidHandle: _sprId
oSprites[_sprId].hx = oSprites[_sprId].w/2
oSprites[_sprId].hy = oSprites[_sprId].h/2
ENDFUNCTION

FUNCTION oSetScale: _scale
OSprSettings.scale = _scale
ENDFUNCTION

FUNCTION oSetRotation: _rotation
OSprSettings.rotation = _rotation
ENDFUNCTION

FUNCTION oSetColor: _color
OSprSettings.color = _color
ENDFUNCTION

FUNCTION oSetPrecision: x
OSprSettings.mathPre = x
ENDFUNCTION



// do not use with TILEMAPS yet!
FUNCTION oAnimColl: _spr1, _f1, _x1, _y1, _spr2, _f2, _x2, _y2
LOCAL sprId1, sprId2
LOCAL dframe

IF _f1 > oSprites[_spr1].maxframes - 1
dframe = oSprites[_spr1].maxframes - 1
ELSE
dframe = _f1
ENDIF
sprId1 = oSprites[_spr1].id + dframe

IF _f2 > oSprites[_spr2].maxframes - 1
dframe = oSprites[_spr2].maxframes - 1
ELSE
dframe = _f2
ENDIF
sprId2 = oSprites[_spr2].id + dframe
LOCAL tmp

RETURN ANIMCOLL( sprId1, 0, _x1 - oSprites[_spr1].hx, _y1 - oSprites[_spr1].hy,    sprId2, 0, _x2 - oSprites[_spr2].hx, _y2 - oSprites[_spr2].hy)
ENDFUNCTION

FUNCTION oSprColl: _spr1, _x1, _y1, _spr2, _x2, _y2
RETURN SPRCOLL( oSprites[_spr1].id, _x1 - oSprites[_spr1].hx, _y1 - oSprites[_spr1].hy,   oSprites[_spr2].id, _x2 - oSprites[_spr1].hx, _y2 - oSprites[_spr1].hy)
ENDFUNCTION



FUNCTION oDrawSprite: _spr, _x, _y, _frame
SMOOTHSHADING FALSE
SELECT oSprites[_spr].maxframes
CASE 0 // -------- A tilemap --------
IF OSprSettings.scale = 1 AND OSprSettings.rotation = 0
DRAWANIM oSprites[_spr].id, _frame, _x - oSprites[_spr].hx, _y - oSprites[_spr].hy
ELSE
_oRotoZoomAnimPivot( _spr, _frame, _x, _y, OSprSettings.rotation, OSprSettings.scale)
ENDIF

DEFAULT // -------- A Sprite --------

LOCAL sprId
LOCAL dframe
IF _frame > oSprites[_spr].maxframes - 1
dframe = oSprites[_spr].maxframes - 1
ELSE
dframe = _frame
ENDIF
sprId = oSprites[_spr].id + dframe

IF OSprSettings.color <> RGB(255,255,255)
// must use polyvector to draw the sprite
_oRotoZoomPoly( _spr, sprId, _x, _y, OSprSettings.rotation, OSprSettings.scale, OSprSettings.color)

ELSEIF OSprSettings.scale = 1 AND OSprSettings.rotation <> 0
// must use ROTOSPRITE
_oRotoPivot( _spr, sprId, _x, _y, OSprSettings.rotation)

ELSEIF OSprSettings.scale <> 1 AND OSprSettings.rotation = 0
// must use ZOOMSPRITE
_oZoomSprite( _spr, sprId, _x, _y, OSprSettings.scale)

ELSEIF OSprSettings.scale <> 1 AND OSprSettings.rotation <> 0
// must use ROTOZOOMSPRITE
_oRotoZoomPivot( _spr, sprId, _x, _y, OSprSettings.rotation, OSprSettings.scale)

ELSE
// use DRAWSPRITE
DRAWSPRITE sprId, _x - oSprites[_spr].hx, _y - oSprites[_spr].hy

ENDIF
ENDSELECT
ENDFUNCTION




// TODO : Optimise this
@FUNCTION _oZoomSprite: _spr, _id, _x, _y, _scale
LOCAL sx, sy,  hx,hy

    sx = oSprites[_spr].w / 2
    sy = oSprites[_spr].h / 2

    hx = oSprites[_spr].hx
    hy = oSprites[_spr].hy

    DEC hx, sx
    DEC hy, sy

    hx = hx * _scale
    hy = hy * _scale

ZOOMSPRITE _id,  _x-hx-sx, _y-hy-sx, _scale, _scale

ENDFUNCTION


@FUNCTION _oRotoZoomPivot: _spr, _id, _x,_y, _angle, _scale
LOCAL sx, sy, rx,ry, px,py
LOCAL cosp, sinp

    cosp=QCOS(_angle)
    sinp=QSIN(_angle)

    sx = oSprites[_spr].w / 2
    sy = oSprites[_spr].h / 2

    px = oSprites[_spr].hx
    py = oSprites[_spr].hy

    // reverse rotate pivot point arount center
    // of sprite
    DEC px, sx
    DEC py, sy
    rx = (px*cosp + py*sinp) * _scale
    ry = (py*cosp - px*sinp) * _scale

    // adjust center
    INC rx,sx
    INC ry,sy

    // perform drawing
    ROTOZOOMSPRITE _id, _x-rx, _y-ry, _angle, _scale

ENDFUNCTION

@FUNCTION _oRotoZoomAnimPivot: _spr, _frame, _x,_y, _angle, _scale
LOCAL sx, sy, rx,ry, px,py
LOCAL cosp, sinp

    cosp=QCOS(_angle)
    sinp=QSIN(_angle)

    sx = oSprites[_spr].w / 2
    sy = oSprites[_spr].h / 2

    px = oSprites[_spr].hx
    py = oSprites[_spr].hy

    // reverse rotate pivot point arount center
    // of sprite
    DEC px, sx
    DEC py, sy
    rx = (px*cosp + py*sinp) * _scale
    ry = (py*cosp - px*sinp) * _scale

    // adjust center
    INC rx,sx
    INC ry,sy

    // perform drawing
    ROTOZOOMANIM oSprites[_spr].id, _frame, _x-rx, _y-ry, _angle, _scale

ENDFUNCTION

@FUNCTION _oRotoZoomPoly: _spr, _id, _x,_y, _angle, _scale, _col
LOCAL sinp, cosp, spw, sph, dx, dy
LOCAL hx, hy, dxp, dxm, dyp, dym

    sinp = QSIN(_angle)
    cosp = QCOS(_angle)

    spw = oSprites[_spr].w-0.5
    sph = oSprites[_spr].h-0.5

    dx=spw/2
    dy=sph/2

    hx = (dx - oSprites[_spr].hx)
    hy = (dy - oSprites[_spr].hy)

    dxp = (dx+hx) * _scale
    dxm = (dx-hx) * _scale
    dyp = (dy+hy) * _scale
    dym = (dy-hy) * _scale

    STARTPOLY _id
    POLYVECTOR _x - cosp * dxm - sinp * dym,   _y - cosp * dym + sinp * dxm,   0.5,   0.5,    _col
    POLYVECTOR _x - cosp * dxm + sinp * dyp,   _y + cosp * dyp + sinp * dxm,   0.5,   sph,  _col
    POLYVECTOR _x + cosp * dxp + sinp * dyp,   _y + cosp * dyp - sinp * dxp,   spw, sph,  _col
    POLYVECTOR _x + cosp * dxp - sinp * dym,   _y - cosp * dym - sinp * dxp,   spw, 0.5,    _col
    ENDPOLY

ENDFUNCTION



@FUNCTION _oRotoPivot: _spr, _id, _x, _y, _angle
LOCAL sx, sy, rx,ry, px,py
LOCAL cosp, sinp

    cosp=QCOS(_angle)
    sinp=QSIN(_angle)

    sx = oSprites[_spr].w / 2
    sy = oSprites[_spr].h / 2

    px = oSprites[_spr].hx
    py = oSprites[_spr].hy

    DEC px, sx
    DEC py, sy

    rx = px*cosp + py*sinp
    ry = py*cosp - px*sinp

    INC rx,sx
    INC ry,sy

    ROTOSPRITE _id, _x-rx, _y-ry, _angle

ENDFUNCTION



FUNCTION QSIN: x
IF OSprSettings.mathPre THEN RETURN SIN(x)

    WHILE x>360.0;  DEC x, 360.0; WEND
    WHILE x<0;      INC x, 360.0; WEND
    IF x>180.0 THEN x = 180.0-x

    x = x/57.296
    x = 1.2732 * x -0.4053 * x * ABS(x)

x = 0.225*(x*ABS(x)-x)+x
RETURN x
ENDFUNCTION


FUNCTION QCOS: x
IF OSprSettings.mathPre THEN RETURN COS(x)
RETURN QSIN(x+90)
ENDFUNCTION


INLINE
float qInvSqrt(float x){
   float xhalf = 0.5f * x;
   int i = *(int*)&x; // store floating-point bits in integer
   i = 0x5f3759d5 - (i >> 1); // initial guess for Newton's method
   x = *(float*)&i; // convert new bits into float
   x = x*(1.5f - xhalf*x*x); // One round of Newton's method
   return x;
}
ENDINLINE


FUNCTION QSQR: y
INLINE
return 1.0f / qInvSqrt(y);
ENDINLINE
ENDFUNCTION

D2O

I7 2600K; 8 GB RAM ; Win10 Pro x64 | NVidia GTX 750 TI 2048MB ; Realtec OnBoard Sound;
Lenovo ThinkPad T400: XP Pro
GLB Premium-immer Aktuell