Blob Blame Raw

#include <cairo.h>

#include "private.h"

#include "sprite.h"
#include "world.h"
#include "group.h"

struct _Sprite {
	double x;
	double y;
	double rotation;
	double width;
	double height;
	double scale;
	
	double vx;
	double vy;
	double rotationSpeed;
	
	HeliCollider collider;
	double bounciness;
	
	int rotateToDirection;
	int mirrorX;
	int mirrorY;
	double depth;
	double lifeTime;
	int visible;
	int debug;
	
	double shapeColor[4];
	double tintColor[4];
	
	int playing;
	int frame;
	
	HeliArray groups;
	
	HeliAnimation *animation;
};

static HeliArray sprites;
static HeliArray spritesSorted;


static void prepareCollider(Sprite sprite, HeliCollider *collider) {
	collider->type = sprite->collider.type;
	collider->x = sprite->collider.x + sprite->x;
	collider->y = sprite->collider.y + sprite->y;
	collider->width = sprite->collider.width >= 0 ? sprite->collider.width : sprite->width;
	collider->height = sprite->collider.height >= 0 ? sprite->collider.height : sprite->height;
	collider->radius = sprite->collider.radius >= 0 ? sprite->collider.radius : collider->width;
	collider->rotation = sprite->collider.rotation + sprite->rotation;
}



Sprite createSpriteEx(double x, double y, double width, double height) {
	Sprite s = calloc(1, sizeof(*s));
	s->x = x;
	s->y = y;
	s->width = width;
	s->height = height;
	
	s->scale = 1;
	s->collider.type = COLLIDER_RECTANGLE;
	s->collider.width = s->collider.height = s->collider.radius = -1;
	
	s->mirrorX = 1;
	s->mirrorY = 1;
	s->lifeTime = -1;
	s->visible = TRUE;
	
	s->shapeColor[0] = s->shapeColor[1] = s->shapeColor[2] = 0.5;
	s->shapeColor[3] = 1;
	s->tintColor[0] = s->tintColor[1] = s->tintColor[2] = s->tintColor[3] = 1;
	
	heliArrayInsert(&sprites, -1, s, NULL);
	heliArrayInsert(&spritesSorted, -1, s, NULL);
	
	return s;
}

Sprite createSprite(double x, double y)
	{ return createSpriteEx(x, y, 100, 100); }

void spriteDestroy(Sprite sprite) {
	while(sprite->groups.count > 0)
		groupRemove((Group)sprite->groups.items[0].value, sprite);
	for(int i = 0; i < sprites.count; ++i)
		if (sprites.items[i].value == sprite)
			{ heliArrayRemove(&sprites, i); break; }
	for(int i = 0; i < spritesSorted.count; ++i)
		if (spritesSorted.items[i].value == sprite)
			{ heliArrayRemove(&spritesSorted, i); break; }
	if (sprite->animation)
		heliAnimationUnref(sprite->animation);
	free(sprite);
}

void spriteDestroyTimer(Sprite sprite, double lifeTime) {
	if (lifeTime <= 0) { spriteDestroy(sprite); return; }
	sprite->lifeTime = lifeTime;
}

double spriteGetX(Sprite sprite) { return sprite->x; }
void spriteSetX(Sprite sprite, double x) { sprite->x = x; }

double spriteGetY(Sprite sprite) { return sprite->y; }
void spriteSetY(Sprite sprite, double y) { sprite->y = y; }

double spriteGetVelocityX(Sprite sprite) { return sprite->vx; }
void spriteSetVelocityX(Sprite sprite, double x)
	{ spriteSetVelocityXY(sprite, x, sprite->vy); }

double spriteGetVelocityY(Sprite sprite) { return sprite->vy; }
void spriteSetVelocityY(Sprite sprite, double y)
	{ spriteSetVelocityXY(sprite, sprite->vx, y); }

double spriteGetScale(Sprite sprite) { return sprite->scale; }
void spriteSetScale(Sprite sprite, double scale) { sprite->scale = scale; }

double spriteGetWidth(Sprite sprite) { return sprite->width; }
void spriteSetWidth(Sprite sprite, double width)
	{ sprite->width = width > 0 ? width : 0; }

double spriteGetHeight(Sprite sprite) { return sprite->height; }
void spriteSetHeight(Sprite sprite, double height)
	{ sprite->height = height > 0 ? height : 0; }

int spriteGetRotateToDirection(Sprite sprite) { return sprite->rotateToDirection; }
void spriteSetRotateToDirection(Sprite sprite, int rotateToDirection) {
	if (rotateToDirection) {
		sprite->rotateToDirection = TRUE;
		sprite->rotation = spriteGetDirection(sprite);
	} else {
		sprite->rotateToDirection = FALSE;
	}
}

double spriteGetRotation(Sprite sprite) { return sprite->rotation; }
void spriteSetRotation(Sprite sprite, double rotation)
	{ if (!sprite->rotateToDirection) sprite->rotation = rotation; }

double spriteGetRotationSpeed(Sprite sprite) { return sprite->rotationSpeed; }
void spriteSetRotationSpeed(Sprite sprite, double rotationSpeed)
	{ sprite->rotationSpeed = rotationSpeed; }

int spriteGetMirrorX(Sprite sprite) { return sprite->mirrorX; }
void spriteSetMirrorX(Sprite sprite, int mirrorX)
	{ sprite->mirrorX = mirrorX < 0 ? -1 : 1; }

int spriteGetMirrorY(Sprite sprite) { return sprite->mirrorY; }
void spriteSetMirrorY(Sprite sprite, int mirrorY)
	{ sprite->mirrorY = mirrorY < 0 ? -1 : 1; }

double spriteGetDepth(Sprite sprite) { return sprite->depth; }
void spriteSetDepth(Sprite sprite, double depth)
	{ sprite->depth = depth; }

int spriteGetVisible(Sprite sprite) { return sprite->visible; }
void spriteSetVisible(Sprite sprite, int visible)
	{ sprite->visible = visible != FALSE; }

int spriteGetDebug(Sprite sprite) { return sprite->debug; }
void spriteSetDebug(Sprite sprite, int debug)
	{ sprite->debug = debug != FALSE; }

int spriteOverlap(Sprite a, Sprite b)
	{ return spriteCollideEx(a, b, TRUE, TRUE, 0); }
int spriteCollide(Sprite a, Sprite b, double bounciness)
	{ return spriteCollideEx(a, b, FALSE, FALSE, bounciness); }
int spriteBounceOff(Sprite sprite, Sprite other, double bounciness)
	{ return spriteCollideEx(sprite, other, FALSE, TRUE, bounciness); }
int spritePush(Sprite sprite, Sprite other, double bounciness)
	{ return spriteCollideEx(sprite, other, TRUE, FALSE, bounciness); }

int spriteCollideEx(Sprite a, Sprite b, int keepVelocityA, int keepVelocityB, double bounciness) {
	#warning "TODO: spriteCollision: not implemented yet"
	return FALSE;
}


double spriteGetBounciness(Sprite sprite) { return sprite->bounciness; }
void spriteSetBounciness(Sprite sprite, double bounciness)
	{ sprite->bounciness = bounciness > 0 ? bounciness : 0; }

void spriteSetColliderEx(Sprite sprite, Collider type, double xOffset, double yOffset,
						 double widthOrRadius, double height, double rotationOffset)
{
	sprite->collider.type = type;
	sprite->collider.x = xOffset;
	sprite->collider.y = yOffset;
	sprite->collider.width = sprite->collider.radius = widthOrRadius;
	sprite->collider.height = height;
	sprite->collider.rotation = rotationOffset;
}

void spriteSetCollider(Sprite sprite, Collider type, double xOffset, double yOffset)
	{ spriteSetColliderEx(sprite, type, xOffset, yOffset, -1, -1, 0); }

void spriteSetAnimation(Sprite sprite, const char *path) {
	HeliAnimation *prev = sprite->animation;
	sprite->animation = heliCreateAnimation(path);
	heliAnimationUnref(prev);
	spriteSetFrame(sprite, sprite->frame);
}

void spritePlay(Sprite sprite) { sprite->playing = TRUE; }
void spritePause(Sprite sprite) { sprite->playing = FALSE; }
void spriteNextFrame(Sprite sprite) { spriteSetFrame(sprite, sprite->frame + 1); }

void spriteSetFrame(Sprite sprite, int frame) {
	sprite->frame = frame >= 0 && sprite->animation && sprite->animation->frames.count > 0
				  ? frame % sprite->animation->frames.count : 0;
}

void spriteSetShapeColor(Sprite sprite, const char *color)
	{ heliParseColor(color, sprite->shapeColor); }
void spriteSetTintColor(Sprite sprite, const char *color)
	{ heliParseColor(color, sprite->tintColor); }

void spriteSetVelocityXY(Sprite sprite, double x, double y) {
	sprite->vx = x;
	sprite->vy = y;
	if (sprite->rotateToDirection)
		sprite->rotation = spriteGetDirection(sprite);
}

void spriteSetSpeedAndDirection(Sprite sprite, double speed, double angle) {
	double a = angle*(PI/180);
	spriteSetVelocityXY(sprite, cos(a)*speed, sin(a)*speed);
}

double spriteGetSpeed(Sprite sprite)
	{ return sqrt(sprite->vx*sprite->vx + sprite->vy*sprite->vy); }
double spriteGetDirection(Sprite sprite) {
	return fabs(sprite->vx) > 1e-6 || fabs(sprite->vy) > 1e-6
	     ? atan2(sprite->vy, sprite->vx)*(180/PI) : 0;
}
void spritePointTo(Sprite sprite, double x, double y)
	{ spriteSetRotation( sprite, atan2(y - sprite->y, x - sprite->x)*(PI/180) ); }

double spriteGetScaledWidth(Sprite sprite)
	{ return sprite->width * sprite->scale; }
double spriteGetScaledHeight(Sprite sprite)
	{ return sprite->height * sprite->scale; }



int worldGetSpriteCount()
	{ return sprites.count; }

Sprite worldGetSprite(int i)
	{ return (Sprite)heliArrayGetValue(&sprites, i); }


static void drawSpriteDebug(cairo_t *cr, Sprite s) {
	cairo_save(cr);
	cairo_translate(cr, s->x, s->y);
	cairo_scale(cr, s->scale*s->mirrorX, s->scale*s->mirrorY);
	
	double hw = s->width*0.5;
	double hh = s->height*0.5;

	cairo_set_source_rgba(cr, 0, 0, 0, 0.75);
	cairo_move_to(cr, -hw, -hh);
	cairo_line_to(cr,  hw, -hh);
	cairo_line_to(cr,  hw,  hh);
	cairo_line_to(cr, -hw,  hh);
	cairo_close_path(cr);
	cairo_stroke(cr);

	cairo_move_to(cr, -hw*0.25, 0);
	cairo_line_to(cr,  hw*0.25, 0);
	cairo_move_to(cr, 0, -hh*0.25);
	cairo_line_to(cr, 0,  hh*0.25);
	cairo_stroke(cr);
	
	char buf[1024];
	snprintf(buf, sizeof(buf)-1, "%f", s->depth);
	
	double s1 = hw*0.5;
	double s2 = hh*0.5;
	cairo_set_font_size(cr, s1 < s2 ? s1 : s2);
	cairo_move_to(cr, -hw*0.9, -hh*0.9);
	cairo_show_text(cr, buf);

	cairo_restore(cr);
}

static void drawSprite(cairo_t *cr, Sprite s) {
	cairo_save(cr);
	cairo_translate(cr, s->x, s->y);
	cairo_scale(cr, s->scale*s->mirrorX, s->scale*s->mirrorY);
	
	double hw = s->width*0.5;
	double hh = s->height*0.5;
	
	cairo_surface_t *frame = s->animation
						   ? (cairo_surface_t*)heliArrayGetValue(&s->animation->frames, s->frame) : NULL;

	cairo_push_group(cr);
	
	if (frame) {
		// image
		double ihw = cairo_image_surface_get_width(frame)*0.5;
		double ihh = cairo_image_surface_get_height(frame)*0.5;
		if (ihw > 1e-6 && ihh > 1e-6) {
			cairo_save(cr);
			cairo_scale(cr, hw/ihw, hh/ihh);
			cairo_set_source_surface(cr, frame, -ihw, -ihh);
			cairo_restore(cr);
		}
	} else {
		// rectangle
		cairo_set_source_rgba(cr, s->shapeColor[0], s->shapeColor[1], s->shapeColor[2], s->shapeColor[3]);
		cairo_move_to(cr, -hw, -hh);
		cairo_line_to(cr,  hw, -hh);
		cairo_line_to(cr,  hw,  hh);
		cairo_line_to(cr, -hw,  hh);
		cairo_close_path(cr);
		cairo_fill(cr);
	}
	
	// tint
	cairo_save(cr);
	cairo_set_source_rgba(cr, s->tintColor[0], s->tintColor[1], s->tintColor[2], s->tintColor[3]);
	cairo_set_operator(cr, CAIRO_OPERATOR_MULTIPLY);
	cairo_move_to(cr, -hw, -hh);
	cairo_line_to(cr,  hw, -hh);
	cairo_line_to(cr,  hw,  hh);
	cairo_line_to(cr, -hw,  hh);
	cairo_close_path(cr);
	cairo_fill(cr);
	cairo_restore(cr);
	
	cairo_pop_group_to_source(cr);
	cairo_paint(cr);
	
	cairo_restore(cr);
}

void drawSprites() {
	// sort
	int found = TRUE;
	while(found) {
		found = FALSE;
		// forward
		for(int i = 1; i < spritesSorted.count; ++i) {
			if ( ((Sprite)spritesSorted.items[i-1].value)->depth
			   < ((Sprite)spritesSorted.items[i  ].value)->depth )
			{
				void *x = spritesSorted.items[i].value;
				spritesSorted.items[i].value = spritesSorted.items[i-1].value;
				spritesSorted.items[i-1].value = x;
				found = TRUE;
			}
		}
		if (!found) break;
		// backward
		found = FALSE;
		for(int i = spritesSorted.count - 1; i > 0; --i) {
			if ( ((Sprite)spritesSorted.items[i-1].value)->depth
			   < ((Sprite)spritesSorted.items[i  ].value)->depth )
			{
				void *x = spritesSorted.items[i].value;
				spritesSorted.items[i].value = spritesSorted.items[i-1].value;
				spritesSorted.items[i-1].value = x;
				found = TRUE;
			}
		}
	}
	
	// draw
	if (heliCairo) {
		for(int i = 0; i < spritesSorted.count; ++i) {
			Sprite s = (Sprite)(spritesSorted.items[i].value);
			if (s->visible) drawSprite(heliCairo, s);
		}
		for(int i = 0; i < spritesSorted.count; ++i) {
			Sprite s = (Sprite)(spritesSorted.items[i].value);
			if (s->debug) drawSpriteDebug(heliCairo, s);
		}
	}
}

int mouseIsOver(Sprite sprite) {
	HeliCollider collider;
	prepareCollider(sprite, &collider);
	return heliPointCollision(&collider, mouseX(), mouseY());
}

void heliSpriteUpdate(double dt) {
	// auto-remove
	for(int i = sprites.count - 1; i > 0; --i) {
		Sprite s = (Sprite)sprites.items[i].value;
		if (s->lifeTime >= 0) {
			s->lifeTime -= dt;
			if (s->lifeTime <= 1e-5)
				spriteDestroy(s);
		}
	}
	
	// update
	for(int i = 0; i < sprites.count; ++i) {
		Sprite s = (Sprite)sprites.items[i].value;
		s->x += s->vx*dt;
		s->y += s->vy*dt;
		if (!s->rotateToDirection)
			s->rotation += s->rotationSpeed*dt;
		if (s->playing) spriteNextFrame(s);
	}
}

HeliArray* heliSpriteGetGroups(Sprite sprite)
	{ return &sprite->groups; }

void heliSpriteFinish() {
	while(worldGetSpriteCount())
		{ spriteDestroy(worldGetSprite(0)); }
	heliArrayDestroy(&sprites);
	heliArrayDestroy(&spritesSorted);
}