Files
CgeScanline/Scanline.c
T
2026-09-15 14:37:22 +03:00

297 lines
8.3 KiB
C

#include "CgeScanline.h"
#include <stdlib.h>
#define AET_INIT_SIZE 256
#define SPAN_INIT_SIZE 256
#define SWAP(x, y) \
do { uint32_t t = (x); (x) = (y); (y) = t; } while (0)
#define SETC(x, value) \
do { if (x) *(x) = (value); } while(0)
typedef size_t (*SpanGeneratorCb)(CgeScanlineEdge *, CgeScanlineEdgeTable *,
size_t, CgeScanlineSpan *, size_t);
static void aetStep(CgeScanlineEdge *edge, CgeScanlineEdgeTable *aet,
size_t size) {
CgeScanlineEdgeTable *current, *end;
for (current = aet, end = aet + size; current != end; ++current) {
CgeScanlineEdge *lookupEdge = edge + current->index;
uint32_t overflow;
current->x += lookupEdge->step;
current->error += lookupEdge->error;
overflow = (current->error >= lookupEdge->threshold) ? 0xFFFFFFFFul : 0;
current->x += lookupEdge->dir & overflow;
current->error -= lookupEdge->threshold & overflow;
}
}
static void aetSort(CgeScanlineEdgeTable *aet, size_t aetSize) {
CgeScanlineEdgeTable tmp;
size_t i, j;
for (i = 1; i < aetSize; ++i) {
tmp = aet[i];
for (j = i; j > 0 && aet[j - 1].x > tmp.x; --j)
aet[j] = aet[j - 1];
aet[j] = tmp;
}
}
static size_t aetRemove(CgeScanlineEdge *edge, CgeScanlineEdgeTable *aet,
size_t aetSize, uint32_t y) {
size_t writeIdx, i;
for (writeIdx = 0, i = 0; i < aetSize; ++i) {
if (edge[aet[i].index].endY != y)
aet[writeIdx++] = aet[i];
}
return writeIdx;
}
static int aetAdd(CgeScanlineEdge *edge, size_t edgeSize,
CgeScanlineEdgeTable *aet, size_t *aetSize,
size_t aetCapacity, size_t *edgeCursor, uint32_t y) {
for (; *edgeCursor < edgeSize && edge[*edgeCursor].startY == y; ++*edgeCursor) {
if (*aetSize >= aetCapacity)
return 0;
aet[*aetSize].index = *edgeCursor;
aet[*aetSize].x = edge[*edgeCursor].startX;
aet[*aetSize].error = 0;
++*aetSize;
}
return 1;
}
static int edgesCmp(const void *lhs, const void *rhs) {
CgeScanlineEdge *left = (CgeScanlineEdge *)lhs;
CgeScanlineEdge *right = (CgeScanlineEdge *)rhs;
return left->startY < right->startY ? -1 : (left->startY == right->startY ? 0 : 1);
}
int CgeScanlineEdgeAdd(CgeScanlineEdge *edge, uint32_t aX, uint32_t aY,
uint32_t bX, uint32_t bY) {
uint32_t dX;
if (aY == bY)
return 0;
edge->winding = 1;
if (bY < aY) {
SWAP(aX, bX);
SWAP(aY, bY);
edge->winding = -1;
}
edge->threshold = bY - aY;
edge->startY = aY;
edge->endY = bY;
edge->startX = aX;
dX = (aX < bX) ? bX - aX : aX - bX;
edge->step = dX / edge->threshold;
edge->error = dX % edge->threshold;
edge->dir = 1;
if (bX < aX) {
edge->dir = -1;
edge->step *= -1;
}
return 1;
}
static size_t spansOddEven(CgeScanlineEdge *edge, CgeScanlineEdgeTable *aet,
size_t aetSize, CgeScanlineSpan *span,
size_t spanSize) {
size_t i, size;
(void)edge;
for (i = 0, size = 0; i + 1 < aetSize; i += 2) {
if (aet[i].x < aet[i + 1].x) {
if (size >= spanSize)
return SIZE_MAX;
span[size].start = aet[i].x;
span[size].end = aet[i + 1].x;
++size;
}
}
return size;
}
static size_t spansNonZero(CgeScanlineEdge *edge, CgeScanlineEdgeTable *aet,
size_t aetSize, CgeScanlineSpan *span,
size_t spanSize) {
int winding, inSpan;
uint32_t spanStart;
size_t i, size;
winding = 0;
inSpan = 0;
spanStart = 0;
size = 0;
for (i = 0; i < aetSize; ++i) {
winding += edge[aet[i].index].winding;
if (!inSpan && winding != 0) {
spanStart = aet[i].x;
inSpan = 1;
} else if (inSpan && winding == 0) {
if (spanStart < aet[i].x) {
if (size >= spanSize)
return SIZE_MAX;
span[size].start = spanStart;
span[size].end = aet[i].x;
++size;
}
inSpan = 0;
}
}
return size;
}
int CgeScanlineStateInit(CgeScanlineState *state, CgeScanlineEdge *edge,
size_t edgeSize, CgeScanlineEdgeTable *aet,
size_t aetCapacity, CgeScanlineSpan *span,
size_t spanCapacity, CgeScanlineRowCallback callback,
void *userData, int flags) {
uint32_t yMin, yMax;
size_t i;
if (!edgeSize || !aetCapacity || !spanCapacity || !callback)
return 0;
state->edge = edge;
state->edgeSize = edgeSize;
state->aet = aet;
state->aetCapacity = aetCapacity;
state->span = span;
state->spanCapacity = spanCapacity;
state->callback = callback;
state->userData = userData;
state->flags = flags;
yMin = state->edge[0].startY;
yMax = state->edge[0].endY;
for (i = 1; i < edgeSize; ++i) {
if (state->edge[i].startY < yMin)
yMin = state->edge[i].startY;
if (state->edge[i].endY > yMax)
yMax = state->edge[i].endY;
}
qsort(state->edge, state->edgeSize, sizeof(*state->edge), edgesCmp);
state->yCurrent = yMin;
state->yMax = yMax;
state->edgeCursor = 0;
state->aetSize = 0;
return 1;
}
int CgeScanlineStateNext(CgeScanlineState *state, int *result) {
SpanGeneratorCb generate;
size_t rowSpans;
generate = (state->flags & CGE_SCANLINE_NON_ZERO) ? spansNonZero
: spansOddEven;
if (state->yCurrent >= state->yMax) {
SETC(result, CGE_SCANLINE_EDONE);
return 0;
}
state->aetSize = aetRemove(state->edge, state->aet, state->aetSize,
state->yCurrent);
if (!aetAdd(state->edge, state->edgeSize, state->aet, &state->aetSize,
state->aetCapacity, &state->edgeCursor, state->yCurrent)) {
SETC(result, CGE_SCANLINE_EAET);
return 0;
}
rowSpans = 0;
if (state->aetSize != 0) {
aetSort(state->aet, state->aetSize);
rowSpans = generate(state->edge, state->aet, state->aetSize,
state->span, state->spanCapacity);
if (rowSpans == SIZE_MAX) {
SETC(result, CGE_SCANLINE_ESPANS);
return 0;
}
aetStep(state->edge, state->aet, state->aetSize);
}
state->callback(state->yCurrent, state->span, rowSpans, state->userData);
state->yCurrent++;
return 1;
}
int CgeScanlineSpans(CgeScanlineEdge *edge, size_t edgeSize,
CgeScanlineRowCallback callback, void *userData,
int flags) {
CgeScanlineEdgeTable *aet;
CgeScanlineSpan *spans;
CgeScanlineState state;
int result;
aet = malloc(sizeof(*aet) * AET_INIT_SIZE);
spans = malloc(sizeof(*spans) * SPAN_INIT_SIZE);
if (!aet || !spans) {
free(aet);
free(spans);
return 0;
}
if (!CgeScanlineStateInit(&state, edge, edgeSize, aet, AET_INIT_SIZE, spans,
SPAN_INIT_SIZE, callback, userData, flags)) {
free(aet);
free(spans);
return 0;
}
while (1) {
if (CgeScanlineStateNext(&state, &result))
continue;
switch (result) {
case CGE_SCANLINE_EDONE:
free(state.aet);
free(state.span);
return 1;
case CGE_SCANLINE_EAET:
aet = realloc(state.aet, sizeof(*aet) * state.aetCapacity * 2);
if (aet) {
state.aetCapacity *= 2;
state.aet = aet;
} else {
free(state.aet);
free(state.span);
return 0;
}
break;
case CGE_SCANLINE_ESPANS:
spans = realloc(state.span, sizeof(*spans) * state.spanCapacity * 2);
if (spans) {
state.spanCapacity *= 2;
state.span = spans;
} else {
free(state.aet);
free(state.span);
return 0;
}
break;
}
}
}