2012-01-24 13:16:20 +00:00
|
|
|
#include "render.h"
|
2011-12-10 13:25:22 +00:00
|
|
|
|
|
|
|
#include <stdlib.h>
|
2012-05-06 10:13:34 +00:00
|
|
|
#include <stdio.h>
|
2011-12-10 13:25:22 +00:00
|
|
|
#include <math.h>
|
|
|
|
|
2013-01-14 19:07:56 +00:00
|
|
|
#include "renderer.h"
|
2013-06-11 10:07:17 +00:00
|
|
|
#include "camera.h"
|
2012-01-24 13:16:20 +00:00
|
|
|
#include "system.h"
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
struct
|
|
|
|
{
|
|
|
|
unsigned char dirty : 1;
|
|
|
|
unsigned char edge : 1;
|
|
|
|
unsigned char callback : 6;
|
|
|
|
} flags;
|
|
|
|
union
|
|
|
|
{
|
|
|
|
Vector3 location;
|
|
|
|
struct {
|
|
|
|
double r;
|
|
|
|
double g;
|
|
|
|
double b;
|
|
|
|
} color;
|
|
|
|
} data;
|
|
|
|
double z;
|
|
|
|
} RenderFragment;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
int x;
|
|
|
|
int y;
|
|
|
|
Vector3 pixel;
|
|
|
|
Vector3 location;
|
|
|
|
int callback;
|
|
|
|
} ScanPoint;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
f_RenderFragmentCallback function;
|
|
|
|
void* data;
|
|
|
|
} FragmentCallback;
|
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
struct RenderArea
|
|
|
|
{
|
2013-02-05 21:25:30 +00:00
|
|
|
ColorProfile* hdr_mapping;
|
2013-06-11 10:03:50 +00:00
|
|
|
Renderer* renderer;
|
2012-06-13 15:38:11 +00:00
|
|
|
RenderParams params;
|
2012-01-29 21:45:58 +00:00
|
|
|
int pixel_count;
|
2013-03-07 14:55:37 +00:00
|
|
|
int pixel_done;
|
2012-06-15 19:45:41 +00:00
|
|
|
RenderFragment* pixels;
|
2012-06-17 09:40:40 +00:00
|
|
|
int fragment_callbacks_count;
|
|
|
|
FragmentCallback fragment_callbacks[64];
|
2012-01-29 21:45:58 +00:00
|
|
|
Color background_color;
|
|
|
|
volatile int dirty_left;
|
|
|
|
volatile int dirty_right;
|
|
|
|
volatile int dirty_up;
|
|
|
|
volatile int dirty_down;
|
|
|
|
volatile int dirty_count;
|
|
|
|
Mutex* lock;
|
|
|
|
RenderCallbackStart callback_start;
|
|
|
|
RenderCallbackDraw callback_draw;
|
|
|
|
RenderCallbackUpdate callback_update;
|
|
|
|
};
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2013-07-06 22:32:01 +00:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
ScanPoint* up;
|
|
|
|
ScanPoint* down;
|
|
|
|
int left;
|
|
|
|
int right;
|
|
|
|
} RenderScanlines;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
int startx;
|
|
|
|
int endx;
|
|
|
|
int starty;
|
|
|
|
int endy;
|
|
|
|
int finished;
|
|
|
|
int interrupt;
|
2013-03-07 14:55:37 +00:00
|
|
|
int pixel_done;
|
2012-06-17 09:40:40 +00:00
|
|
|
Thread* thread;
|
|
|
|
RenderArea* area;
|
|
|
|
} RenderChunk;
|
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
static void _callbackStart(int width, int height, Color background) {}
|
|
|
|
static void _callbackDraw(int x, int y, Color col) {}
|
2012-06-17 09:40:40 +00:00
|
|
|
static void _callbackUpdate(double progress) {}
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
void renderInit()
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2012-02-12 16:57:29 +00:00
|
|
|
void renderQuit()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-06-11 10:03:50 +00:00
|
|
|
RenderArea* renderCreateArea(Renderer* renderer)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-01-29 21:45:58 +00:00
|
|
|
RenderArea* result;
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
result = malloc(sizeof(RenderArea));
|
2013-06-11 10:03:50 +00:00
|
|
|
result->renderer = renderer;
|
2013-02-05 21:25:30 +00:00
|
|
|
result->hdr_mapping = colorProfileCreate();
|
2012-06-13 15:38:11 +00:00
|
|
|
result->params.width = 1;
|
|
|
|
result->params.height = 1;
|
|
|
|
result->params.antialias = 1;
|
|
|
|
result->params.quality = 5;
|
2012-01-29 21:45:58 +00:00
|
|
|
result->pixel_count = 1;
|
2012-06-15 19:45:41 +00:00
|
|
|
result->pixels = malloc(sizeof(RenderFragment));
|
2012-06-17 09:40:40 +00:00
|
|
|
result->fragment_callbacks_count = 0;
|
2012-01-29 21:45:58 +00:00
|
|
|
result->background_color = COLOR_TRANSPARENT;
|
|
|
|
result->dirty_left = 1;
|
|
|
|
result->dirty_right = -1;
|
|
|
|
result->dirty_down = 1;
|
|
|
|
result->dirty_up = -1;
|
|
|
|
result->dirty_count = 0;
|
|
|
|
result->lock = mutexCreate();
|
|
|
|
result->callback_start = _callbackStart;
|
|
|
|
result->callback_draw = _callbackDraw;
|
|
|
|
result->callback_update = _callbackUpdate;
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
return result;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
void renderDeleteArea(RenderArea* area)
|
2012-01-11 16:11:34 +00:00
|
|
|
{
|
2013-02-05 21:25:30 +00:00
|
|
|
colorProfileDelete(area->hdr_mapping);
|
2012-01-29 21:45:58 +00:00
|
|
|
mutexDestroy(area->lock);
|
2012-02-12 16:57:29 +00:00
|
|
|
free(area->pixels);
|
2012-01-29 21:45:58 +00:00
|
|
|
free(area);
|
2012-01-11 16:11:34 +00:00
|
|
|
}
|
|
|
|
|
2013-02-05 21:25:30 +00:00
|
|
|
static void _setAllDirty(RenderArea* area)
|
|
|
|
{
|
|
|
|
area->dirty_left = 0;
|
|
|
|
area->dirty_right = area->params.width * area->params.antialias - 1;
|
|
|
|
area->dirty_down = 0;
|
|
|
|
area->dirty_up = area->params.height * area->params.antialias - 1;
|
|
|
|
}
|
|
|
|
|
2012-06-13 15:38:11 +00:00
|
|
|
void renderSetParams(RenderArea* area, RenderParams params)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-13 15:38:11 +00:00
|
|
|
int width, height;
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2012-06-13 15:38:11 +00:00
|
|
|
width = params.width * params.antialias;
|
|
|
|
height = params.height * params.antialias;
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-06-13 15:38:11 +00:00
|
|
|
area->params = params;
|
2012-06-15 19:45:41 +00:00
|
|
|
area->pixels = realloc(area->pixels, sizeof(RenderFragment) * width * height);
|
2012-01-29 21:45:58 +00:00
|
|
|
area->pixel_count = width * height;
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
area->dirty_left = width;
|
|
|
|
area->dirty_right = -1;
|
|
|
|
area->dirty_down = height;
|
|
|
|
area->dirty_up = -1;
|
|
|
|
area->dirty_count = 0;
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-06-15 19:45:41 +00:00
|
|
|
renderClear(area);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2013-02-05 21:25:30 +00:00
|
|
|
void renderSetToneMapping(RenderArea* area, ToneMappingOperator tonemapper, double exposure)
|
|
|
|
{
|
|
|
|
colorProfileSetToneMapping(area->hdr_mapping, tonemapper, exposure);
|
|
|
|
_setAllDirty(area);
|
|
|
|
renderUpdate(area);
|
|
|
|
}
|
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
void renderSetBackgroundColor(RenderArea* area, Color* col)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-01-29 21:45:58 +00:00
|
|
|
area->background_color = *col;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
void renderClear(RenderArea* area)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-15 19:45:41 +00:00
|
|
|
RenderFragment* pixel;
|
2011-12-10 13:25:22 +00:00
|
|
|
int x;
|
|
|
|
int y;
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
area->fragment_callbacks_count = 1;
|
|
|
|
area->fragment_callbacks[0].function = NULL;
|
|
|
|
area->fragment_callbacks[0].data = NULL;
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-06-13 15:38:11 +00:00
|
|
|
for (x = 0; x < area->params.width * area->params.antialias; x++)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-13 15:38:11 +00:00
|
|
|
for (y = 0; y < area->params.height * area->params.antialias; y++)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-15 19:45:41 +00:00
|
|
|
pixel = area->pixels + (y * area->params.width * area->params.antialias + x);
|
|
|
|
pixel->z = -100000000.0;
|
2012-06-17 09:40:40 +00:00
|
|
|
pixel->flags.dirty = 0;
|
|
|
|
pixel->flags.callback = 0;
|
|
|
|
pixel->data.color.r = area->background_color.r;
|
|
|
|
pixel->data.color.g = area->background_color.g;
|
|
|
|
pixel->data.color.b = area->background_color.b;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-13 15:38:11 +00:00
|
|
|
area->callback_start(area->params.width, area->params.height, area->background_color);
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-06-13 15:38:11 +00:00
|
|
|
area->dirty_left = area->params.width * area->params.antialias;
|
2012-01-29 21:45:58 +00:00
|
|
|
area->dirty_right = -1;
|
2012-06-13 15:38:11 +00:00
|
|
|
area->dirty_down = area->params.height * area->params.antialias;
|
2012-01-29 21:45:58 +00:00
|
|
|
area->dirty_up = -1;
|
|
|
|
area->dirty_count = 0;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
static inline void _setDirtyPixel(RenderArea* area, int x, int y)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-01-29 21:45:58 +00:00
|
|
|
if (x < area->dirty_left)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-01-29 21:45:58 +00:00
|
|
|
area->dirty_left = x;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2012-01-29 21:45:58 +00:00
|
|
|
if (x > area->dirty_right)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-01-29 21:45:58 +00:00
|
|
|
area->dirty_right = x;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2012-01-29 21:45:58 +00:00
|
|
|
if (y < area->dirty_down)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-01-29 21:45:58 +00:00
|
|
|
area->dirty_down = y;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2012-01-29 21:45:58 +00:00
|
|
|
if (y > area->dirty_up)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-01-29 21:45:58 +00:00
|
|
|
area->dirty_up = y;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
area->dirty_count++;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-06-13 20:38:01 +00:00
|
|
|
static inline Color _getFinalPixel(RenderArea* area, int x, int y)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-13 20:38:01 +00:00
|
|
|
Color result, col;
|
|
|
|
int sx, sy;
|
2012-06-15 19:45:41 +00:00
|
|
|
RenderFragment* pixel_data;
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2012-06-13 20:38:01 +00:00
|
|
|
result.r = result.g = result.b = 0.0;
|
|
|
|
result.a = 1.0;
|
|
|
|
for (sx = 0; sx < area->params.antialias; sx++)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-13 20:38:01 +00:00
|
|
|
for (sy = 0; sy < area->params.antialias; sy++)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-13 20:38:01 +00:00
|
|
|
pixel_data = area->pixels + (y * area->params.antialias + sy) * area->params.width * area->params.antialias + (x * area->params.antialias + sx);
|
2012-06-17 09:40:40 +00:00
|
|
|
if (pixel_data->flags.dirty)
|
|
|
|
{
|
|
|
|
if (pixel_data->flags.edge)
|
|
|
|
{
|
2012-06-17 15:59:20 +00:00
|
|
|
col = COLOR_GREY;
|
2012-06-17 09:40:40 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-06-17 15:59:20 +00:00
|
|
|
col = COLOR_WHITE;
|
2012-06-17 09:40:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
col.r = pixel_data->data.color.r;
|
|
|
|
col.g = pixel_data->data.color.g;
|
|
|
|
col.b = pixel_data->data.color.b;
|
|
|
|
}
|
|
|
|
result.r += col.r / (double)(area->params.antialias * area->params.antialias);
|
|
|
|
result.g += col.g / (double)(area->params.antialias * area->params.antialias);
|
|
|
|
result.b += col.b / (double)(area->params.antialias * area->params.antialias);
|
2013-02-05 21:25:30 +00:00
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2013-03-03 17:05:30 +00:00
|
|
|
return colorProfileApply(area->hdr_mapping, result);
|
2012-06-13 20:38:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _processDirtyPixels(RenderArea* area)
|
|
|
|
{
|
|
|
|
int x, y;
|
|
|
|
int down, up, left, right;
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2012-06-13 20:38:01 +00:00
|
|
|
down = area->dirty_down / area->params.antialias;
|
|
|
|
up = area->dirty_up / area->params.antialias;
|
|
|
|
left = area->dirty_left / area->params.antialias;
|
|
|
|
right = area->dirty_right / area->params.antialias;
|
|
|
|
|
|
|
|
for (y = down; y <= up; y++)
|
|
|
|
{
|
|
|
|
for (x = left; x <= right; x++)
|
|
|
|
{
|
|
|
|
area->callback_draw(x, y, _getFinalPixel(area, x, y));
|
|
|
|
}
|
|
|
|
}
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2013-06-27 10:15:30 +00:00
|
|
|
area->callback_update(area->renderer->render_progress);
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-06-13 15:38:11 +00:00
|
|
|
area->dirty_left = area->params.width * area->params.antialias;
|
2012-01-29 21:45:58 +00:00
|
|
|
area->dirty_right = -1;
|
2012-06-13 15:38:11 +00:00
|
|
|
area->dirty_down = area->params.height * area->params.antialias;
|
2012-01-29 21:45:58 +00:00
|
|
|
area->dirty_up = -1;
|
|
|
|
area->dirty_count = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void renderUpdate(RenderArea* area)
|
|
|
|
{
|
|
|
|
mutexAcquire(area->lock);
|
|
|
|
_processDirtyPixels(area);
|
|
|
|
mutexRelease(area->lock);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
static inline unsigned int _pushCallback(RenderArea* area, FragmentCallback callback)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
int i;
|
|
|
|
for (i = 0; i < area->fragment_callbacks_count; i++)
|
|
|
|
{
|
|
|
|
if (area->fragment_callbacks[i].function == callback.function && area->fragment_callbacks[i].data == callback.data)
|
|
|
|
{
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
if (area->fragment_callbacks_count >= 64)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
area->fragment_callbacks[area->fragment_callbacks_count].function = callback.function;
|
|
|
|
area->fragment_callbacks[area->fragment_callbacks_count].data = callback.data;
|
|
|
|
return area->fragment_callbacks_count++;
|
|
|
|
}
|
|
|
|
}
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
static void _pushFragment(RenderArea* area, int x, int y, double z, int edge, Vector3 location, int callback)
|
|
|
|
{
|
|
|
|
RenderFragment* pixel_data;
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2012-06-13 15:38:11 +00:00
|
|
|
if (x >= 0 && x < area->params.width * area->params.antialias && y >= 0 && y < area->params.height * area->params.antialias && z > 1.0)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-13 15:38:11 +00:00
|
|
|
pixel_data = area->pixels + (y * area->params.width * area->params.antialias + x);
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-06-15 19:45:41 +00:00
|
|
|
if (z > pixel_data->z)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
pixel_data->flags.dirty = (unsigned char)1;
|
|
|
|
pixel_data->flags.edge = (unsigned char)edge;
|
|
|
|
pixel_data->flags.callback = (unsigned char)callback;
|
|
|
|
pixel_data->data.location = location;
|
|
|
|
pixel_data->z = z;
|
|
|
|
_setDirtyPixel(area, x, y);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
static void _scanGetDiff(ScanPoint* v1, ScanPoint* v2, ScanPoint* result)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
result->pixel.x = v2->pixel.x - v1->pixel.x;
|
|
|
|
result->pixel.y = v2->pixel.y - v1->pixel.y;
|
|
|
|
result->pixel.z = v2->pixel.z - v1->pixel.z;
|
2011-12-10 13:25:22 +00:00
|
|
|
result->location.x = v2->location.x - v1->location.x;
|
|
|
|
result->location.y = v2->location.y - v1->location.y;
|
|
|
|
result->location.z = v2->location.z - v1->location.z;
|
|
|
|
result->callback = v1->callback;
|
|
|
|
}
|
|
|
|
|
2013-06-11 10:07:17 +00:00
|
|
|
static void _scanInterpolate(CameraDefinition* camera, ScanPoint* v1, ScanPoint* diff, double value, ScanPoint* result)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2013-06-11 10:07:17 +00:00
|
|
|
double v1depth = cameraGetRealDepth(camera, v1->pixel);
|
|
|
|
double v2depth = cameraGetRealDepth(camera, v3Add(v1->pixel, diff->pixel));
|
2013-05-28 12:56:50 +00:00
|
|
|
double factor = ((1.0 - value) / v1depth + value / v2depth);
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
result->pixel.x = v1->pixel.x + diff->pixel.x * value;
|
|
|
|
result->pixel.y = v1->pixel.y + diff->pixel.y * value;
|
2013-06-13 15:45:26 +00:00
|
|
|
result->pixel.z = v1->pixel.z + diff->pixel.z * value;
|
2013-05-28 12:56:50 +00:00
|
|
|
result->location.x = ((1.0 - value) * (v1->location.x / v1depth) + value * (v1->location.x + diff->location.x) / v2depth) / factor;
|
|
|
|
result->location.y = ((1.0 - value) * (v1->location.y / v1depth) + value * (v1->location.y + diff->location.y) / v2depth) / factor;
|
|
|
|
result->location.z = ((1.0 - value) * (v1->location.z / v1depth) + value * (v1->location.z + diff->location.z) / v2depth) / factor;
|
2011-12-10 13:25:22 +00:00
|
|
|
result->callback = v1->callback;
|
|
|
|
}
|
|
|
|
|
2013-07-06 22:32:01 +00:00
|
|
|
static void _pushScanPoint(RenderArea* area, RenderScanlines* scanlines, ScanPoint* point)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
point->x = (int)floor(point->pixel.x);
|
|
|
|
point->y = (int)floor(point->pixel.y);
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
if (point->x < 0 || point->x >= area->params.width * area->params.antialias)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-07-06 22:32:01 +00:00
|
|
|
if (point->x > scanlines->right)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2013-07-06 22:32:01 +00:00
|
|
|
scanlines->right = point->x;
|
|
|
|
scanlines->up[scanlines->right] = *point;
|
|
|
|
scanlines->down[scanlines->right] = *point;
|
|
|
|
if (point->x < scanlines->left)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2013-07-06 22:32:01 +00:00
|
|
|
scanlines->left = point->x;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
2013-07-06 22:32:01 +00:00
|
|
|
else if (point->x < scanlines->left)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2013-07-06 22:32:01 +00:00
|
|
|
scanlines->left = point->x;
|
|
|
|
scanlines->up[scanlines->left] = *point;
|
|
|
|
scanlines->down[scanlines->left] = *point;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-07-06 22:32:01 +00:00
|
|
|
if (point->y > scanlines->up[point->x].y)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2013-07-06 22:32:01 +00:00
|
|
|
scanlines->up[point->x] = *point;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2013-07-06 22:32:01 +00:00
|
|
|
if (point->y < scanlines->down[point->x].y)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2013-07-06 22:32:01 +00:00
|
|
|
scanlines->down[point->x] = *point;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-06 22:32:01 +00:00
|
|
|
static void _pushScanLineEdge(RenderArea* area, RenderScanlines* scanlines, ScanPoint* point1, ScanPoint* point2)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
double dx, fx;
|
|
|
|
ScanPoint diff, point;
|
|
|
|
int startx = lround(point1->pixel.x);
|
|
|
|
int endx = lround(point2->pixel.x);
|
2011-12-10 13:25:22 +00:00
|
|
|
int curx;
|
|
|
|
|
|
|
|
if (endx < startx)
|
|
|
|
{
|
2013-07-06 22:32:01 +00:00
|
|
|
_pushScanLineEdge(area, scanlines, point2, point1);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2012-06-13 15:38:11 +00:00
|
|
|
else if (endx < 0 || startx >= area->params.width * area->params.antialias)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (startx == endx)
|
|
|
|
{
|
2013-07-06 22:32:01 +00:00
|
|
|
_pushScanPoint(area, scanlines, point1);
|
|
|
|
_pushScanPoint(area, scanlines, point2);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (startx < 0)
|
|
|
|
{
|
|
|
|
startx = 0;
|
|
|
|
}
|
2012-06-13 15:38:11 +00:00
|
|
|
if (endx >= area->params.width * area->params.antialias)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-13 15:38:11 +00:00
|
|
|
endx = area->params.width * area->params.antialias - 1;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
dx = point2->pixel.x - point1->pixel.x;
|
|
|
|
_scanGetDiff(point1, point2, &diff);
|
2011-12-10 13:25:22 +00:00
|
|
|
for (curx = startx; curx <= endx; curx++)
|
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
fx = (double)curx + 0.5;
|
|
|
|
if (fx < point1->pixel.x)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
fx = point1->pixel.x;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2012-06-17 09:40:40 +00:00
|
|
|
else if (fx > point2->pixel.x)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
fx = point2->pixel.x;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2012-06-17 09:40:40 +00:00
|
|
|
fx = fx - point1->pixel.x;
|
2013-06-11 10:07:17 +00:00
|
|
|
_scanInterpolate(area->renderer->render_camera, point1, &diff, fx / dx, &point);
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
/*point.pixel.x = (double)curx;*/
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2013-07-06 22:32:01 +00:00
|
|
|
_pushScanPoint(area, scanlines, &point);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-06 22:32:01 +00:00
|
|
|
static void _renderScanLines(RenderArea* area, RenderScanlines* scanlines)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
int x, starty, endy, cury;
|
2012-06-17 09:40:40 +00:00
|
|
|
ScanPoint diff;
|
|
|
|
double dy, fy;
|
|
|
|
ScanPoint up, down, current;
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2013-07-06 22:32:01 +00:00
|
|
|
if (scanlines->right > 0)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2013-07-06 22:32:01 +00:00
|
|
|
for (x = scanlines->left; x <= scanlines->right; x++)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2013-07-06 22:32:01 +00:00
|
|
|
up = scanlines->up[x];
|
|
|
|
down = scanlines->down[x];
|
2011-12-10 13:25:22 +00:00
|
|
|
|
|
|
|
starty = down.y;
|
|
|
|
endy = up.y;
|
|
|
|
|
2012-06-13 15:38:11 +00:00
|
|
|
if (endy < 0 || starty >= area->params.height * area->params.antialias)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (starty < 0)
|
|
|
|
{
|
|
|
|
starty = 0;
|
|
|
|
}
|
2012-06-13 15:38:11 +00:00
|
|
|
if (endy >= area->params.height * area->params.antialias)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-13 15:38:11 +00:00
|
|
|
endy = area->params.height * area->params.antialias - 1;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
dy = up.pixel.y - down.pixel.y;
|
|
|
|
_scanGetDiff(&down, &up, &diff);
|
2011-12-10 13:25:22 +00:00
|
|
|
|
|
|
|
current.x = x;
|
|
|
|
for (cury = starty; cury <= endy; cury++)
|
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
fy = (double)cury + 0.5;
|
|
|
|
if (fy < down.pixel.y)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
fy = down.pixel.y;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2012-06-17 09:40:40 +00:00
|
|
|
else if (fy > up.pixel.y)
|
|
|
|
{
|
|
|
|
fy = up.pixel.y;
|
|
|
|
}
|
|
|
|
fy = fy - down.pixel.y;
|
|
|
|
|
|
|
|
current.y = cury;
|
2013-06-11 10:07:17 +00:00
|
|
|
_scanInterpolate(area->renderer->render_camera, &down, &diff, fy / dy, ¤t);
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
_pushFragment(area, current.x, current.y, current.pixel.z, (cury == starty || cury == endy), current.location, current.callback);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
void renderPushTriangle(RenderArea* area, Vector3 pixel1, Vector3 pixel2, Vector3 pixel3, Vector3 location1, Vector3 location2, Vector3 location3, f_RenderFragmentCallback callback, void* callback_data)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
FragmentCallback fragment_callback = {callback, callback_data};
|
|
|
|
ScanPoint point1, point2, point3;
|
|
|
|
double limit_width = (double)(area->params.width * area->params.antialias - 1);
|
|
|
|
double limit_height = (double)(area->params.height * area->params.antialias - 1);
|
2011-12-10 13:25:22 +00:00
|
|
|
|
|
|
|
/* Filter if outside screen */
|
2012-06-17 09:40:40 +00:00
|
|
|
if (pixel1.z < 1.0 || pixel2.z < 1.0 || pixel3.z < 1.0 || (pixel1.x < 0.0 && pixel2.x < 0.0 && pixel3.x < 0.0) || (pixel1.y < 0.0 && pixel2.y < 0.0 && pixel3.y < 0.0) || (pixel1.x > limit_width && pixel2.x > limit_width && pixel3.x > limit_width) || (pixel1.y > limit_height && pixel2.y > limit_height && pixel3.y > limit_height))
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2013-07-06 22:32:01 +00:00
|
|
|
/* Prepare fragment callback */
|
|
|
|
mutexAcquire(area->lock);
|
|
|
|
point1.callback = _pushCallback(area, fragment_callback);
|
|
|
|
mutexRelease(area->lock);
|
|
|
|
|
|
|
|
/* Prepare vertices */
|
2012-06-17 09:40:40 +00:00
|
|
|
point1.pixel = pixel1;
|
|
|
|
point1.location = location1;
|
|
|
|
|
|
|
|
point2.pixel = pixel2;
|
|
|
|
point2.location = location2;
|
2013-07-06 22:32:01 +00:00
|
|
|
point2.callback = point1.callback;
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
point3.pixel = pixel3;
|
|
|
|
point3.location = location3;
|
2013-07-06 22:32:01 +00:00
|
|
|
point3.callback = point1.callback;
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2013-07-06 22:32:01 +00:00
|
|
|
/* Prepare scanlines */
|
|
|
|
RenderScanlines scanlines;
|
|
|
|
int x;
|
|
|
|
int width = area->params.width * area->params.antialias;
|
|
|
|
scanlines.left = width;
|
|
|
|
scanlines.right = -1;
|
|
|
|
scanlines.up = malloc(sizeof(ScanPoint) * width);
|
|
|
|
scanlines.down = malloc(sizeof(ScanPoint) * width);
|
|
|
|
for (x = 0; x < width; x++)
|
|
|
|
{
|
|
|
|
/* TODO Do not initialize whole width each time, init only when needed on point push */
|
|
|
|
scanlines.up[x].y = -1;
|
|
|
|
scanlines.down[x].y = area->params.height * area->params.antialias;
|
|
|
|
}
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2013-07-06 22:32:01 +00:00
|
|
|
/* Render edges in scanlines */
|
|
|
|
_pushScanLineEdge(area, &scanlines, &point1, &point2);
|
|
|
|
_pushScanLineEdge(area, &scanlines, &point2, &point3);
|
|
|
|
_pushScanLineEdge(area, &scanlines, &point3, &point1);
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2013-07-06 22:32:01 +00:00
|
|
|
/* Commit scanlines to area */
|
2012-01-29 21:45:58 +00:00
|
|
|
mutexAcquire(area->lock);
|
2013-07-06 22:32:01 +00:00
|
|
|
_renderScanLines(area, &scanlines);
|
2012-01-29 21:45:58 +00:00
|
|
|
mutexRelease(area->lock);
|
2013-07-06 22:32:01 +00:00
|
|
|
|
|
|
|
/* Free scalines */
|
|
|
|
free(scanlines.up);
|
|
|
|
free(scanlines.down);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2013-06-14 11:39:45 +00:00
|
|
|
Color renderGetPixel(RenderArea* area, int x, int y)
|
|
|
|
{
|
|
|
|
Color result;
|
|
|
|
|
|
|
|
mutexAcquire(area->lock);
|
|
|
|
result = _getFinalPixel(area, x, y);
|
|
|
|
mutexRelease(area->lock);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
void* _renderPostProcessChunk(void* data)
|
|
|
|
{
|
2012-06-15 19:45:41 +00:00
|
|
|
int x, y;
|
|
|
|
RenderFragment* fragment;
|
2011-12-10 13:25:22 +00:00
|
|
|
RenderChunk* chunk = (RenderChunk*)data;
|
|
|
|
|
2013-08-17 16:08:37 +00:00
|
|
|
for (x = chunk->startx; x <= chunk->endx; x++)
|
|
|
|
{
|
|
|
|
for (y = chunk->starty; y <= chunk->endy; y++)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-15 19:45:41 +00:00
|
|
|
fragment = chunk->area->pixels + (y * chunk->area->params.width * chunk->area->params.antialias + x);
|
2012-06-17 09:40:40 +00:00
|
|
|
if (fragment->flags.dirty)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
FragmentCallback callback;
|
|
|
|
Color col;
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
callback = chunk->area->fragment_callbacks[fragment->flags.callback];
|
|
|
|
if (callback.function)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2013-06-11 10:03:50 +00:00
|
|
|
col = callback.function(chunk->area->renderer, fragment->data.location, callback.data);
|
2013-01-14 19:07:56 +00:00
|
|
|
/*colorNormalize(&col);*/
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2012-06-17 09:40:40 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
col = COLOR_BLACK;
|
|
|
|
}
|
2013-01-14 19:07:56 +00:00
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
fragment->data.color.r = col.r;
|
|
|
|
fragment->data.color.g = col.g;
|
|
|
|
fragment->data.color.b = col.b;
|
|
|
|
|
2012-06-17 15:59:20 +00:00
|
|
|
mutexAcquire(chunk->area->lock);
|
2012-06-17 09:40:40 +00:00
|
|
|
fragment->flags.dirty = 0;
|
|
|
|
_setDirtyPixel(chunk->area, x, y);
|
2012-06-17 15:59:20 +00:00
|
|
|
mutexRelease(chunk->area->lock);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2013-03-07 14:55:37 +00:00
|
|
|
chunk->area->pixel_done++;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
if (chunk->interrupt)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
chunk->finished = 1;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MAX_CHUNKS 8
|
2013-06-11 10:03:50 +00:00
|
|
|
void renderPostProcess(RenderArea* area, int nbchunks)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
volatile RenderChunk chunks[MAX_CHUNKS];
|
|
|
|
int i;
|
|
|
|
int x, y, dx, dy, nx, ny;
|
|
|
|
int loops, running;
|
|
|
|
|
|
|
|
if (nbchunks > MAX_CHUNKS)
|
|
|
|
{
|
|
|
|
nbchunks = MAX_CHUNKS;
|
|
|
|
}
|
|
|
|
if (nbchunks < 1)
|
|
|
|
{
|
|
|
|
nbchunks = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
nx = 10;
|
|
|
|
ny = 10;
|
2012-06-13 15:38:11 +00:00
|
|
|
dx = area->params.width * area->params.antialias / nx;
|
|
|
|
dy = area->params.height * area->params.antialias / ny;
|
2011-12-10 13:25:22 +00:00
|
|
|
x = 0;
|
|
|
|
y = 0;
|
2013-03-07 14:55:37 +00:00
|
|
|
area->pixel_done = 0;
|
2011-12-10 13:25:22 +00:00
|
|
|
|
|
|
|
for (i = 0; i < nbchunks; i++)
|
|
|
|
{
|
|
|
|
chunks[i].thread = NULL;
|
2012-01-29 21:45:58 +00:00
|
|
|
chunks[i].area = area;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
running = 0;
|
|
|
|
loops = 0;
|
2013-08-17 16:08:37 +00:00
|
|
|
while ((x < nx && !area->renderer->render_interrupt) || running > 0)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2013-06-20 10:33:18 +00:00
|
|
|
timeSleepMs(50);
|
2011-12-10 13:25:22 +00:00
|
|
|
|
|
|
|
for (i = 0; i < nbchunks; i++)
|
|
|
|
{
|
|
|
|
if (chunks[i].thread)
|
|
|
|
{
|
|
|
|
if (chunks[i].finished)
|
|
|
|
{
|
|
|
|
threadJoin(chunks[i].thread);
|
|
|
|
chunks[i].thread = NULL;
|
|
|
|
running--;
|
|
|
|
}
|
2013-06-11 10:03:50 +00:00
|
|
|
else if (area->renderer->render_interrupt)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
chunks[i].interrupt = 1;
|
|
|
|
}
|
|
|
|
}
|
2011-12-18 21:59:33 +00:00
|
|
|
|
2013-06-27 10:15:30 +00:00
|
|
|
area->renderer->render_progress = 0.1 + ((double)area->pixel_done / (double)area->pixel_count) * 0.9;
|
|
|
|
|
2013-08-17 16:08:37 +00:00
|
|
|
if (x < nx && !chunks[i].thread && !area->renderer->render_interrupt)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
chunks[i].finished = 0;
|
|
|
|
chunks[i].interrupt = 0;
|
|
|
|
chunks[i].startx = x * dx;
|
2012-04-23 11:46:35 +00:00
|
|
|
if (x == nx - 1)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-13 15:38:11 +00:00
|
|
|
chunks[i].endx = area->params.width * area->params.antialias - 1;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
chunks[i].endx = (x + 1) * dx - 1;
|
|
|
|
}
|
|
|
|
chunks[i].starty = y * dy;
|
2012-04-23 11:46:35 +00:00
|
|
|
if (y == ny - 1)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-13 15:38:11 +00:00
|
|
|
chunks[i].endy = area->params.height * area->params.antialias - 1;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
chunks[i].endy = (y + 1) * dy - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
chunks[i].thread = threadCreate(_renderPostProcessChunk, (void*)(chunks + i));
|
|
|
|
running++;
|
|
|
|
|
2013-08-17 16:08:37 +00:00
|
|
|
if (++y >= ny)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2013-08-17 16:08:37 +00:00
|
|
|
x++;
|
|
|
|
y = 0;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (++loops >= 10)
|
|
|
|
{
|
2012-01-29 21:45:58 +00:00
|
|
|
mutexAcquire(area->lock);
|
|
|
|
_processDirtyPixels(area);
|
|
|
|
mutexRelease(area->lock);
|
2011-12-10 13:25:22 +00:00
|
|
|
|
|
|
|
loops = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
_processDirtyPixels(area);
|
2012-06-15 09:31:11 +00:00
|
|
|
area->callback_update(1.0);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-05-06 10:13:34 +00:00
|
|
|
int renderSaveToFile(RenderArea* area, const char* path)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-07-19 15:04:27 +00:00
|
|
|
return systemSavePictureFile(path, (PictureCallbackSavePixel)_getFinalPixel, area, area->params.width, area->params.height);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
void renderSetPreviewCallbacks(RenderArea* area, RenderCallbackStart start, RenderCallbackDraw draw, RenderCallbackUpdate update)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-01-29 21:45:58 +00:00
|
|
|
area->callback_start = start ? start : _callbackStart;
|
|
|
|
area->callback_draw = draw ? draw : _callbackDraw;
|
|
|
|
area->callback_update = update ? update : _callbackUpdate;
|
2012-01-22 18:39:42 +00:00
|
|
|
|
2012-06-13 15:38:11 +00:00
|
|
|
area->callback_start(area->params.width, area->params.height, area->background_color);
|
2012-01-22 18:39:42 +00:00
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
_setAllDirty(area);
|
|
|
|
_processDirtyPixels(area);
|
2012-01-18 16:20:14 +00:00
|
|
|
|
2012-01-29 21:45:58 +00:00
|
|
|
area->callback_update(0.0);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|