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)
// --------------------------------- //
// 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