62a291f2c2
git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@3665 8092fc87-c524-0410-9efc-e669fe64eaf9
2251 lines
50 KiB
C
2251 lines
50 KiB
C
/*
|
|
* UQM Starmap image generator
|
|
* By Alex Volkov (codepro@usa.net), 20060220
|
|
*
|
|
* The GPL applies
|
|
*
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <malloc.h>
|
|
#include <math.h>
|
|
#include <ctype.h>
|
|
#include "port.h"
|
|
#include "scriptlib.h"
|
|
#include "unicode.h"
|
|
#include "mapdrv.h"
|
|
|
|
#ifndef M_PI
|
|
# define M_PI 3.1415927
|
|
#endif
|
|
#ifndef SQR
|
|
# define SQR(x) ((x) * (x))
|
|
#endif
|
|
|
|
extern const mg_driver_t sdlpng_drv;
|
|
extern const mg_driver_t sdlsvg_drv;
|
|
|
|
static const mg_driver_t* drvs[] =
|
|
{
|
|
&sdlpng_drv,
|
|
&sdlsvg_drv,
|
|
0 /* term */
|
|
};
|
|
static const mg_driver_t* drv;
|
|
|
|
typedef struct
|
|
{
|
|
const char* infile;
|
|
const char* driver;
|
|
int verbose;
|
|
|
|
} options_t;
|
|
|
|
typedef struct
|
|
{
|
|
mg_image_t img;
|
|
mg_pointf_t hot;
|
|
char color;
|
|
int size;
|
|
double radius;
|
|
mg_color_t rend_color; // alternate rendering
|
|
} star_image_t;
|
|
|
|
typedef struct
|
|
{
|
|
char cluster[128];
|
|
int prefix;
|
|
mg_pointf_t pos;
|
|
char color;
|
|
int size;
|
|
const star_image_t* image;
|
|
} star_t;
|
|
|
|
typedef star_t* cluster_conn_t[2];
|
|
|
|
typedef struct
|
|
{
|
|
int first;
|
|
int cstars;
|
|
int text_dx, text_dy;
|
|
mg_pointf_t center;
|
|
int cconns;
|
|
cluster_conn_t conns[20];
|
|
} cluster_t;
|
|
|
|
typedef struct
|
|
{
|
|
const char* name;
|
|
mg_pointf_t center;
|
|
double radius;
|
|
mg_color_t clr;
|
|
mg_font_t font;
|
|
mg_pointf_t tweak;
|
|
} soi_t;
|
|
|
|
typedef enum
|
|
{
|
|
sht_Null = 0,
|
|
sht_Point,
|
|
sht_Line,
|
|
sht_Rect,
|
|
sht_Ellipse,
|
|
|
|
} shape_type_t;
|
|
|
|
typedef struct
|
|
{
|
|
shape_type_t type;
|
|
mg_pointf_t pt0;
|
|
mg_pointf_t pt1;
|
|
double radiusx;
|
|
double radiusy;
|
|
|
|
} shape_t;
|
|
|
|
typedef enum
|
|
{
|
|
objt_Null = 0,
|
|
objt_Sphere = (1 << 0),
|
|
objt_SphereText = (1 << 1),
|
|
objt_Star = (1 << 2),
|
|
objt_ClusterLine = (1 << 3),
|
|
objt_ClusterText = (1 << 4),
|
|
objt_Designation = (1 << 5),
|
|
|
|
objt_DesigPlacement =
|
|
objt_Star |
|
|
objt_ClusterLine |
|
|
objt_Designation,
|
|
|
|
objt_TextPlacement =
|
|
objt_Star |
|
|
objt_ClusterLine |
|
|
objt_ClusterText |
|
|
objt_Designation,
|
|
|
|
} obj_type_t;
|
|
|
|
typedef struct
|
|
{
|
|
obj_type_t type;
|
|
shape_t shape;
|
|
|
|
} object_t;
|
|
|
|
typedef struct
|
|
{
|
|
unsigned char str[8];
|
|
} desig_char_t;
|
|
|
|
typedef struct
|
|
{
|
|
unsigned char name[32];
|
|
} layer_t;
|
|
|
|
typedef struct
|
|
{
|
|
int dpix;
|
|
int dpiy;
|
|
// all doubles represent inches
|
|
double w;
|
|
double h;
|
|
|
|
mg_color_t backclr;
|
|
|
|
layer_t* layers;
|
|
int clayers;
|
|
|
|
int gridmx; // max grid coordinates
|
|
int gridmy;
|
|
int grids1; // grid steps; 1: numbers
|
|
int grids2; // 2: grid lines
|
|
int grids3; // 3: smaller marks
|
|
mg_rectf_t gridr;
|
|
mg_color_t gridclr;
|
|
mg_color_t gridmclr;
|
|
mg_font_t gridfnt;
|
|
double gridbw; // box weight
|
|
double gridlw; // lines weight
|
|
|
|
const char* starclrs;
|
|
int starsizes;
|
|
star_image_t* starimgs;
|
|
int cstarimgs;
|
|
star_t* stars;
|
|
int cstars;
|
|
|
|
cluster_t* clusters;
|
|
int cclusters;
|
|
mg_color_t clustclr;
|
|
double clustlw; // lines weight
|
|
mg_font_t cnamefnt;
|
|
mg_color_t cnameclr;
|
|
|
|
mg_font_t desigfnt;
|
|
mg_color_t desigclr;
|
|
desig_char_t* desigtab;
|
|
int cdesigtab;
|
|
|
|
soi_t* sois;
|
|
int csois;
|
|
const char* soifont;
|
|
const char* soifontfile;
|
|
double soifontsize;
|
|
|
|
object_t* objs;
|
|
int cobjs;
|
|
int objalloc;
|
|
|
|
} script_t;
|
|
|
|
int verbose_level = 0;
|
|
void mg_verbose(int level, const char* fmt, ...);
|
|
static void parseArguments(int argc, char* argv[], options_t* opts);
|
|
|
|
static const mg_driver_t* findDriver(const char* name);
|
|
|
|
static int readScript(script_t*, FILE*);
|
|
static void freeScript(script_t*);
|
|
static void preprocessStars(script_t* scr);
|
|
static object_t* addObject(script_t*, obj_type_t, shape_type_t, double x0, double y0,
|
|
double x1, double y1, double rx, double ry);
|
|
|
|
static void drawLayers(const mg_driver_t*, script_t*);
|
|
static void drawGrid(const mg_driver_t*, script_t*);
|
|
static void drawStars(const mg_driver_t*, script_t*);
|
|
static void drawStarDesignations(const mg_driver_t*, script_t*);
|
|
static void drawClusterLines(const mg_driver_t*, script_t*);
|
|
static void drawClusterNames(const mg_driver_t*, script_t*);
|
|
static void drawSpheres(const mg_driver_t*, script_t*);
|
|
static void drawSphereNames(const mg_driver_t*, script_t*);
|
|
|
|
static const struct map_layer
|
|
{
|
|
const char* name;
|
|
void (* draw)(const mg_driver_t*, script_t*);
|
|
}
|
|
map_layers[] =
|
|
{
|
|
{"grid", drawGrid},
|
|
{"sois", drawSpheres},
|
|
{"soinames", drawSphereNames},
|
|
{"clusters", drawClusterLines},
|
|
{"cnames", drawClusterNames},
|
|
{"stars", drawStars},
|
|
{"starnums", drawStarDesignations},
|
|
|
|
{0, 0} // term
|
|
};
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int ret = EXIT_FAILURE;
|
|
options_t opts;
|
|
script_t scr;
|
|
FILE* fin = NULL;
|
|
|
|
memset(&scr, 0, sizeof(scr));
|
|
|
|
parseArguments(argc, argv, &opts);
|
|
verbose_level = opts.verbose;
|
|
|
|
if (!opts.driver)
|
|
opts.driver = "sdlpng"; // default
|
|
|
|
drv = findDriver(opts.driver);
|
|
if (!drv)
|
|
{
|
|
mg_verbose(1, "Driver '%s' not found\n", opts.driver);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
mg_verbose(2, "Using driver %s -- %s\n", drv->name, drv->description);
|
|
|
|
do
|
|
{
|
|
if (opts.infile)
|
|
{
|
|
fin = fopen(opts.infile, "rt");
|
|
if (!fin)
|
|
{
|
|
mg_verbose(1, "Cannot open file '%s' -- %s\n", opts.infile, strerror(errno));
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fin = stdin;
|
|
}
|
|
|
|
ret = drv->init(argc, argv);
|
|
if (ret)
|
|
{
|
|
mg_verbose(1, "Driver '%s' failed to initialize\n", drv->name);
|
|
break;
|
|
}
|
|
|
|
if (readScript(&scr, fin))
|
|
{
|
|
mg_verbose(1, "Cannot process input script -- %s\n", strerror(errno));
|
|
break;
|
|
}
|
|
preprocessStars(&scr);
|
|
|
|
ret = drv->begin(scr.w, scr.h);
|
|
if (ret)
|
|
{
|
|
mg_verbose(1, "Driver '%s' could not start\n");
|
|
break;
|
|
}
|
|
|
|
drv->drawFilledRect(NULL, scr.backclr);
|
|
|
|
drawLayers(drv, &scr);
|
|
|
|
ret = drv->write();
|
|
if (ret)
|
|
{
|
|
mg_verbose(1, "Driver could not write to the device\n");
|
|
break;
|
|
}
|
|
|
|
drv->end();
|
|
|
|
ret = EXIT_SUCCESS;
|
|
} while (0);
|
|
|
|
freeScript(&scr);
|
|
|
|
if (fin)
|
|
fclose(fin);
|
|
drv->term();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void mg_verbose(int level, const char* fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
if (verbose_level < level)
|
|
return;
|
|
|
|
va_start(args, fmt);
|
|
vfprintf(stderr, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
static void usage()
|
|
{
|
|
fprintf(stderr,
|
|
"mapgen [-i <infile>] [-d <driver>] [driver options]\n"
|
|
"Options:\n"
|
|
"\t-i input script file; stdin when none\n"
|
|
"\t-d use <driver>; default is sdlpng\n"
|
|
"\t-v increase verbosity level (use more than once)\n"
|
|
"mapgen -d <driver> -h [to see driver options]\n"
|
|
);
|
|
}
|
|
|
|
static void parseArguments(int argc, char *argv[], options_t *opts)
|
|
{
|
|
int ch;
|
|
|
|
opterr = 0;
|
|
memset(opts, 0, sizeof (options_t));
|
|
|
|
while (-1 != (ch = getopt(argc, argv, "-h?vi:d:")))
|
|
{
|
|
switch (ch)
|
|
{
|
|
case 'i':
|
|
opts->infile = optarg;
|
|
break;
|
|
case 'f':
|
|
break;
|
|
case 'd':
|
|
opts->driver = optarg;
|
|
break;
|
|
case 'n':
|
|
break;
|
|
case 'v':
|
|
opts->verbose++;
|
|
break;
|
|
case '?':
|
|
if (optopt != '?')
|
|
{ // unknown option -- let driver handle it
|
|
break;
|
|
}
|
|
case 'h':
|
|
{
|
|
const mg_driver_t* drv = findDriver(opts->driver);
|
|
usage();
|
|
if (drv)
|
|
drv->usage();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
default:
|
|
// non-option -- probably an arg to unknown option
|
|
;
|
|
}
|
|
}
|
|
// TODO: this does not work with all versions of getopt()
|
|
optind = 1; // let the driver handle the rest
|
|
}
|
|
|
|
static const mg_driver_t* findDriver(const char* name)
|
|
{
|
|
const mg_driver_t** d;
|
|
|
|
if (!name)
|
|
return 0;
|
|
|
|
for (d = drvs; *d && strcmp((*d)->name, name) != 0; ++d)
|
|
;
|
|
|
|
return *d;
|
|
}
|
|
|
|
static void parseColor(mg_color_t* clr, const char* key, uint32_t def)
|
|
{
|
|
uint32_t cv;
|
|
const char* v = scr_GetString(key);
|
|
if (!v || 1 != sscanf(v, "%x", &cv))
|
|
cv = def;
|
|
clr->r = (cv >> 16) & 0xff;
|
|
clr->g = (cv >> 8) & 0xff;
|
|
clr->b = (cv ) & 0xff;
|
|
clr->a = 0xff;
|
|
}
|
|
|
|
static int parseFont(mg_font_t* fnt, const char* key)
|
|
{
|
|
char buf[128];
|
|
const char* name;
|
|
const char* filename;
|
|
double size;
|
|
|
|
sprintf(buf, "%s.name", key);
|
|
name = scr_GetString(buf);
|
|
sprintf(buf, "%s.filename", key);
|
|
filename = scr_GetString(buf);
|
|
sprintf(buf, "%s.size", key);
|
|
size = scr_GetFloatDef(buf, 10) / 64.0;
|
|
|
|
*fnt = drv->loadFont(name, size, filename);
|
|
|
|
if (!*fnt)
|
|
mg_verbose(1, "Warning: cannot load font '%s' (%s)\n", name, filename);
|
|
|
|
return *fnt != NULL;
|
|
}
|
|
|
|
static void freeFont(mg_font_t* fnt)
|
|
{
|
|
if (!fnt)
|
|
return;
|
|
|
|
drv->freeFont(*fnt);
|
|
*fnt = NULL;
|
|
}
|
|
|
|
static layer_t* loadLayers(const char* key, int* count)
|
|
{
|
|
layer_t* tab;
|
|
char buf[256];
|
|
int i, max = 32;
|
|
char* sl;
|
|
layer_t* l;
|
|
|
|
*count = 0;
|
|
|
|
strcpy(buf, scr_GetStringDef(key,
|
|
"sois,grid,soinames,clusters,stars,starnums,cnames"));
|
|
|
|
tab = calloc(max, sizeof(*tab));
|
|
if (!tab)
|
|
return 0;
|
|
|
|
for (i = 0, sl = strtok(buf, ","), l = tab; i < max && sl;
|
|
++i, ++l, sl = strtok(0, ","))
|
|
{
|
|
strcpy(l->name, sl);
|
|
}
|
|
|
|
*count = i;
|
|
|
|
return tab;
|
|
}
|
|
|
|
static desig_char_t* loadDesignations(const char* key, int* count)
|
|
{
|
|
const char* name;
|
|
FILE* f;
|
|
char buf[256] = "";
|
|
char* end;
|
|
int len;
|
|
desig_char_t* tab;
|
|
const unsigned char* p;
|
|
int i;
|
|
|
|
*count = 0;
|
|
|
|
name = scr_GetString(key);
|
|
if (!name)
|
|
return 0;
|
|
|
|
f = fopen(name, "rt");
|
|
if (!f)
|
|
{
|
|
mg_verbose(1, "Cannot open '%s' -- %s\n", name, strerror(errno));
|
|
return 0;
|
|
}
|
|
fgets(buf, sizeof(buf), f);
|
|
end = strchr(buf, '\n');
|
|
if (end)
|
|
*end = '\0';
|
|
fclose(f);
|
|
|
|
len = utf8StringCount(buf);
|
|
tab = calloc(len, sizeof(desig_char_t));
|
|
if (!tab)
|
|
{
|
|
mg_verbose(1, "Out of memory\n");
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0, p = buf; i < len; ++i)
|
|
{
|
|
const unsigned char* prev = p;
|
|
int cl;
|
|
|
|
getCharFromString(&p);
|
|
cl = p - prev;
|
|
memcpy(tab[i].str, prev, cl);
|
|
tab[i].str[cl] = '\0';
|
|
}
|
|
|
|
*count = len;
|
|
|
|
return tab;
|
|
}
|
|
|
|
static star_image_t* loadStarImages(const char* key, const char* colors, int sizes, int* count)
|
|
{
|
|
star_image_t* tab;
|
|
int ccolors = strlen(colors);
|
|
const char* c;
|
|
int s;
|
|
star_image_t* img;
|
|
|
|
*count = 0;
|
|
if (!ccolors || !sizes)
|
|
return 0;
|
|
|
|
tab = calloc(ccolors * sizes, sizeof(*tab));
|
|
if (!tab)
|
|
return 0;
|
|
|
|
for (c = colors, img = tab; *c; ++c)
|
|
{
|
|
char starkey[256];
|
|
char buf[300];
|
|
|
|
for (s = 1; s <= sizes; ++s, ++img)
|
|
{
|
|
const char* name;
|
|
mg_rectf_t r;
|
|
|
|
sprintf(starkey, "%s.%c.%d", key, *c, s);
|
|
img->color = *c;
|
|
img->size = s - 1;
|
|
|
|
sprintf(buf, "%s.image", starkey);
|
|
name = scr_GetString(buf);
|
|
if (name)
|
|
{
|
|
img->img = drv->loadImage(name);
|
|
if (img->img)
|
|
{
|
|
drv->getImageSize(img->img, &r);
|
|
img->hot.x = r.w / 2;
|
|
img->hot.y = r.h / 2;
|
|
}
|
|
else
|
|
{
|
|
mg_verbose(1, "Warning: cannot load image '%s'\n", name);
|
|
}
|
|
}
|
|
|
|
sprintf(buf, "%s.color", starkey);
|
|
parseColor(&img->rend_color, buf, 0);
|
|
|
|
sprintf(buf, "%s.radius", starkey);
|
|
img->radius = scr_GetFloatDef(buf, 1) / 64.0;
|
|
}
|
|
}
|
|
|
|
*count = ccolors * sizes;
|
|
|
|
return tab;
|
|
}
|
|
|
|
static int cmpStars(const void* elem1, const void* elem2)
|
|
{
|
|
const star_t* star1 = (const star_t*)elem1;
|
|
const star_t* star2 = (const star_t*)elem2;
|
|
int ret;
|
|
|
|
ret = strcmp(star1->cluster, star2->cluster);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
if (star1->prefix < star2->prefix)
|
|
return -1;
|
|
if (star1->prefix > star2->prefix)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int desigToPrefix(char desig)
|
|
{
|
|
static const char* desigtab = "*ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
const char* cl = strchr(desigtab, toupper(desig));
|
|
if (!cl || *cl == '\0')
|
|
return 0;
|
|
else
|
|
return cl - desigtab;
|
|
}
|
|
|
|
static star_t* loadStars(const char* name, int* count)
|
|
{
|
|
star_t* tab;
|
|
int tabsize;
|
|
int cstars;
|
|
FILE* f;
|
|
|
|
*count = 0;
|
|
if (!name)
|
|
return 0;
|
|
|
|
// grab more than we need to reduce reallocs
|
|
tabsize = 1000;
|
|
tab = malloc(tabsize * sizeof(*tab));
|
|
if (!tab)
|
|
{
|
|
mg_verbose(1, "Out of memory\n");
|
|
return 0;
|
|
}
|
|
memset(tab, 0, tabsize * sizeof(*tab));
|
|
|
|
f = fopen(name, "rt");
|
|
if (!f)
|
|
{
|
|
mg_verbose(1, "Cannot open '%s' -- %s\n", name, strerror(errno));
|
|
free(tab);
|
|
return 0;
|
|
}
|
|
|
|
for (cstars = 0; !feof(f); )
|
|
{
|
|
char buf[256];
|
|
char dbuf[20];
|
|
char cbuf[20];
|
|
star_t* star;
|
|
|
|
if (!fgets(buf, sizeof(buf), f) || !buf[0] || buf[0] == '#')
|
|
continue;
|
|
|
|
if (cstars >= tabsize)
|
|
{
|
|
tabsize += tabsize / 2;
|
|
tab = realloc(tab, tabsize * sizeof(*tab));
|
|
if (!tab)
|
|
{
|
|
mg_verbose(1, "Out of memory\n");
|
|
return 0;
|
|
}
|
|
}
|
|
star = tab + cstars;
|
|
|
|
if (6 != sscanf(buf, "%127[^,\n\r],%19[^,\n\r],%lf,%lf,%19[^,\n\r],%d",
|
|
star->cluster, dbuf, &star->pos.x, &star->pos.y,
|
|
cbuf, &star->size))
|
|
continue;
|
|
|
|
star->prefix = desigToPrefix(dbuf[0]);
|
|
star->color = toupper(cbuf[0]);
|
|
star->size--;
|
|
|
|
++cstars;
|
|
++star;
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
tab = realloc(tab, cstars * sizeof(*tab));
|
|
*count = cstars;
|
|
|
|
// sort the stars
|
|
qsort(tab, cstars, sizeof(*tab), cmpStars);
|
|
|
|
return tab;
|
|
}
|
|
|
|
static cluster_t* initClusters(const star_t* stars, int cstars, int* count)
|
|
{
|
|
int cclusters;
|
|
cluster_t* tab;
|
|
cluster_t* cluster;
|
|
int tabsize;
|
|
int i;
|
|
|
|
*count = 0;
|
|
if (!stars || cstars == 0)
|
|
return 0;
|
|
|
|
// grab more than we need to reduce reallocs
|
|
tabsize = 50;
|
|
tab = malloc(tabsize * sizeof(*tab));
|
|
if (!tab)
|
|
{
|
|
mg_verbose(1, "Out of memory\n");
|
|
return 0;
|
|
}
|
|
memset(tab, 0, tabsize * sizeof(*tab));
|
|
|
|
for (i = 0, cclusters = 0; i < cstars; ++i)
|
|
{
|
|
if (i > 0 && 0 == strcmp(stars[i].cluster, stars[i - 1].cluster))
|
|
continue;
|
|
|
|
// next cluster
|
|
if (cclusters > 0)
|
|
{ // update last one
|
|
cluster = tab + cclusters - 1;
|
|
cluster->cstars = i - cluster->first;
|
|
}
|
|
|
|
if (cclusters >= tabsize)
|
|
{
|
|
int newsize = tabsize + tabsize / 2;
|
|
tab = realloc(tab, newsize * sizeof(*tab));
|
|
if (!tab)
|
|
{
|
|
mg_verbose(1, "Out of memory\n");
|
|
return 0;
|
|
}
|
|
memset(tab + cclusters, 0, tabsize / 2 * sizeof(*tab));
|
|
tabsize = newsize;
|
|
}
|
|
|
|
cluster = tab + cclusters;
|
|
cluster->first = i;
|
|
|
|
++cclusters;
|
|
}
|
|
if (cclusters > 0)
|
|
{ // update last one
|
|
cluster = tab + cclusters - 1;
|
|
cluster->cstars = i - cluster->first;
|
|
}
|
|
|
|
tab = realloc(tab, cclusters * sizeof(*tab));
|
|
*count = cclusters;
|
|
|
|
// calc cluster centers
|
|
for (i = 0; i < cclusters; ++i)
|
|
{
|
|
int s;
|
|
double minx = 1000000000.0f, miny = 1000000000.0f;
|
|
double maxx = 0, maxy = 0;
|
|
|
|
cluster = tab + i;
|
|
|
|
for (s = 0; s < cluster->cstars; ++s)
|
|
{
|
|
const star_t* star = stars + cluster->first + s;
|
|
if (star->pos.x < minx)
|
|
minx = star->pos.x;
|
|
if (star->pos.x > maxx)
|
|
maxx = star->pos.x;
|
|
if (star->pos.y < miny)
|
|
miny = star->pos.y;
|
|
if (star->pos.y > maxy)
|
|
maxy = star->pos.y;
|
|
}
|
|
|
|
cluster->center.x = (minx + maxx) / 2;
|
|
cluster->center.y = (miny + maxy) / 2;
|
|
}
|
|
|
|
return tab;
|
|
}
|
|
|
|
static cluster_t* findCluster(const char* cname, const script_t* scr)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < scr->cclusters &&
|
|
strcmp(cname, scr->stars[scr->clusters[i].first].cluster) != 0;
|
|
++i)
|
|
;
|
|
if (i < scr->cclusters)
|
|
return scr->clusters + i;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static star_t* findStar(const char* cname, char desig, const script_t* scr)
|
|
{
|
|
int i;
|
|
cluster_t* cluster;
|
|
int prefix;
|
|
|
|
cluster = findCluster(cname, scr);
|
|
if (!cluster)
|
|
return 0;
|
|
|
|
prefix = desigToPrefix(desig);
|
|
for (i = cluster->first; i < cluster->first + cluster->cstars &&
|
|
scr->stars[i].prefix != prefix;
|
|
++i)
|
|
;
|
|
if (i < cluster->first + cluster->cstars)
|
|
return scr->stars + i;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int loadClusters(const char* name, script_t* scr)
|
|
{
|
|
FILE* f;
|
|
|
|
if (!name)
|
|
return 0;
|
|
|
|
f = fopen(name, "rt");
|
|
if (!f)
|
|
{
|
|
mg_verbose(1, "Cannot open '%s' -- %s\n", name, strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
for ( ; !feof(f); )
|
|
{
|
|
char buf[256];
|
|
char* cname;
|
|
char* obuf;
|
|
char* pair;
|
|
char* next;
|
|
cluster_t* cluster;
|
|
int cconns;
|
|
|
|
if (!fgets(buf, sizeof(buf), f) || !buf[0] || buf[0] == '#')
|
|
continue;
|
|
|
|
cname = buf;
|
|
next = strchr(cname, ',');
|
|
if (!next)
|
|
continue;
|
|
*next = '\0';
|
|
obuf = next + 1;
|
|
next = strchr(obuf, ',');
|
|
if (next)
|
|
{
|
|
*next = '\0';
|
|
pair = next + 1;
|
|
}
|
|
else
|
|
pair = 0;
|
|
|
|
cluster = findCluster(cname, scr);
|
|
if (!cluster)
|
|
{
|
|
mg_verbose(2, "Cluster '%s' not found\n", cname);
|
|
continue;
|
|
}
|
|
|
|
strupr(obuf);
|
|
if (strcmp(obuf, "N") == 0)
|
|
{
|
|
cluster->text_dx = 0;
|
|
cluster->text_dy = 1;
|
|
}
|
|
else if (strcmp(obuf, "NE") == 0)
|
|
{
|
|
cluster->text_dx = 1;
|
|
cluster->text_dy = 1;
|
|
}
|
|
else if (strcmp(obuf, "NW") == 0)
|
|
{
|
|
cluster->text_dx = -1;
|
|
cluster->text_dy = 1;
|
|
}
|
|
else if (strcmp(obuf, "S") == 0)
|
|
{
|
|
cluster->text_dx = 0;
|
|
cluster->text_dy = -1;
|
|
}
|
|
else if (strcmp(obuf, "SE") == 0)
|
|
{
|
|
cluster->text_dx = 1;
|
|
cluster->text_dy = -1;
|
|
}
|
|
else if (strcmp(obuf, "SW") == 0)
|
|
{
|
|
cluster->text_dx = -1;
|
|
cluster->text_dy = -1;
|
|
}
|
|
else if (strcmp(obuf, "E") == 0)
|
|
{
|
|
cluster->text_dx = 1;
|
|
cluster->text_dy = 0;
|
|
}
|
|
else if (strcmp(obuf, "W") == 0)
|
|
{
|
|
cluster->text_dx = -1;
|
|
cluster->text_dy = 0;
|
|
}
|
|
|
|
for (cconns = 0; pair && cconns < (int)countof(cluster->conns); )
|
|
{
|
|
next = strchr(pair, ',');
|
|
if (next)
|
|
next++;
|
|
|
|
if (pair[0] != '\0' && pair[1] != '\0')
|
|
{
|
|
cluster->conns[cconns][0] = findStar(cname, toupper(pair[0]), scr);
|
|
cluster->conns[cconns][1] = findStar(cname, toupper(pair[1]), scr);
|
|
if (cluster->conns[cconns][0] && cluster->conns[cconns][1])
|
|
++cconns;
|
|
}
|
|
|
|
pair = next;
|
|
}
|
|
cluster->cconns = cconns;
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static soi_t* loadSois(const char* key, int count, script_t* scr)
|
|
{
|
|
soi_t* tab;
|
|
int i;
|
|
soi_t* soi;
|
|
|
|
if (!key || !count)
|
|
return 0;
|
|
|
|
tab = calloc(count, sizeof(*tab));
|
|
if (!tab)
|
|
return 0;
|
|
|
|
for (i = 1, soi = tab; i <= count; ++i, ++soi)
|
|
{
|
|
char buf[256];
|
|
double fntsize;
|
|
|
|
sprintf(buf, "%s.%d.%s", key, i, "name");
|
|
soi->name = scr_GetString(buf);
|
|
sprintf(buf, "%s.%d.%s", key, i, "color");
|
|
parseColor(&soi->clr, buf, 0);
|
|
sprintf(buf, "%s.%d.%s", key, i, "center.x");
|
|
soi->center.x = scr_GetFloatDef(buf, 0);
|
|
sprintf(buf, "%s.%d.%s", key, i, "center.y");
|
|
soi->center.y = scr_GetFloatDef(buf, 0);
|
|
sprintf(buf, "%s.%d.%s", key, i, "radius");
|
|
soi->radius = scr_GetFloatDef(buf, 0);
|
|
sprintf(buf, "%s.%d.%s", key, i, "tweak.x");
|
|
soi->tweak.x = scr_GetFloatDef(buf, 0);
|
|
sprintf(buf, "%s.%d.%s", key, i, "tweak.y");
|
|
soi->tweak.y = scr_GetFloatDef(buf, 0);
|
|
sprintf(buf, "%s.%d.%s", key, i, "font.size");
|
|
fntsize = scr_GetFloatDef(buf, scr->soifontsize) / 64.0;
|
|
soi->font = drv->loadFont(scr->soifont, fntsize, scr->soifontfile);
|
|
if (!soi->font)
|
|
mg_verbose(1, "Warning: cannot load font '%s' (%s)\n", scr->soifont, scr->soifontfile);
|
|
}
|
|
|
|
return tab;
|
|
}
|
|
|
|
static int readScript(script_t* scr, FILE* f)
|
|
{
|
|
int ret = EXIT_SUCCESS;
|
|
|
|
memset(scr, 0, sizeof(*scr));
|
|
|
|
scr_LoadFile(f);
|
|
|
|
scr->dpix = scr_GetIntegerDef("image.dpi.x", 300);
|
|
scr->dpiy = scr_GetIntegerDef("image.dpi.y", 300);
|
|
|
|
ret = drv->setResolution(scr->dpix, scr->dpiy);
|
|
if (ret)
|
|
{
|
|
mg_verbose(1, "Driver '%s' rejected resolution %dx%d\n", scr->dpix, scr->dpiy);
|
|
return ret;
|
|
}
|
|
|
|
scr->w = scr_GetFloatDef("image.width", 297.0f / 25.4f);
|
|
scr->h = scr_GetFloatDef("image.height", 370.0f / 25.4f);
|
|
|
|
parseColor(&scr->backclr, "image.backcolor", 0x200020);
|
|
|
|
scr->layers = loadLayers("image.layers", &scr->clayers);
|
|
|
|
scr->gridmx = scr_GetIntegerDef("grid.extent.x", 1000);
|
|
scr->gridmy = scr_GetIntegerDef("grid.extent.y", 1000);
|
|
scr->grids1 = scr_GetIntegerDef("grid.step.1", 100);
|
|
scr->grids2 = scr_GetIntegerDef("grid.step.2", 50);
|
|
scr->grids3 = scr_GetIntegerDef("grid.step.3", 10);
|
|
scr->gridr.x = scr_GetFloatDef("grid.origin.x", 1.0f);
|
|
scr->gridr.y = scr_GetFloatDef("grid.origin.y", 1.0f);
|
|
scr->gridr.w = scr_GetFloatDef("grid.width", 9.5f);
|
|
scr->gridr.h = scr_GetFloatDef("grid.height", 9.5f);
|
|
scr->gridbw = scr_GetFloatDef("grid.box.weight", 1.0f) / 64.0;
|
|
scr->gridlw = scr_GetFloatDef("grid.lines.weight", 0.75f) / 64.0;
|
|
parseFont(&scr->gridfnt, "grid.font");
|
|
parseColor(&scr->gridclr, "grid.color", 0x9566cb);
|
|
parseColor(&scr->gridmclr, "grid.marks.color", 0x8771b6);
|
|
|
|
scr->starclrs = scr_GetStringDef("stars.colors", "");
|
|
scr->starsizes = scr_GetIntegerDef("stars.sizes", 0);
|
|
|
|
parseColor(&scr->clustclr, "clusters.lines.color", 0x8181a9);
|
|
scr->clustlw = scr_GetFloatDef("clusters.lines.weight", 1.0f) / 64.0;
|
|
parseColor(&scr->cnameclr, "clusters.names.color", 0x7aa2ef);
|
|
parseFont(&scr->cnamefnt, "clusters.names.font");
|
|
|
|
parseColor(&scr->desigclr, "stars.desig.color", 0x989898);
|
|
parseFont(&scr->desigfnt, "stars.desig.font");
|
|
|
|
scr->desigtab = loadDesignations("stars.desig.table", &scr->cdesigtab);
|
|
|
|
scr->starimgs = loadStarImages("stars", scr->starclrs, scr->starsizes, &scr->cstarimgs);
|
|
scr->stars = loadStars(scr_GetString("stars.definition"), &scr->cstars);
|
|
scr->clusters = initClusters(scr->stars, scr->cstars, &scr->cclusters);
|
|
loadClusters(scr_GetString("clusters.definition"), scr);
|
|
|
|
scr->csois = scr_GetIntegerDef("sois.count", 0);
|
|
scr->soifont = scr_GetString("sois.font.name");
|
|
scr->soifontfile = scr_GetString("sois.font.filename");
|
|
scr->soifontsize = scr_GetFloatDef("sois.font.size", 16);
|
|
scr->sois = loadSois("sois", scr->csois, scr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void freeMemory(void** ptr)
|
|
{
|
|
if (*ptr)
|
|
free(*ptr);
|
|
*ptr = NULL;
|
|
}
|
|
|
|
static void freeScript(script_t* scr)
|
|
{
|
|
int i;
|
|
|
|
freeFont(&scr->gridfnt);
|
|
freeFont(&scr->cnamefnt);
|
|
freeFont(&scr->desigfnt);
|
|
|
|
// free SoI fonts
|
|
for (i = 0; i < scr->csois; ++i)
|
|
{
|
|
freeFont(&scr->sois[i].font);
|
|
}
|
|
|
|
// free star images
|
|
for (i = 0; i < scr->cstarimgs; ++i)
|
|
{
|
|
if (scr->starimgs[i].img)
|
|
drv->freeImage(scr->starimgs[i].img);
|
|
scr->starimgs[i].img = 0;
|
|
}
|
|
|
|
freeMemory(&scr->desigtab);
|
|
freeMemory(&scr->starimgs);
|
|
freeMemory(&scr->stars);
|
|
freeMemory(&scr->clusters);
|
|
freeMemory(&scr->sois);
|
|
freeMemory(&scr->objs);
|
|
}
|
|
|
|
static void preprocessStars(script_t* scr)
|
|
{
|
|
// lookup and record corresponding star image definitions
|
|
int i;
|
|
star_t* star;
|
|
|
|
for (i = 0, star = scr->stars; i < scr->cstars; ++i, ++star)
|
|
{
|
|
const char* cl;
|
|
int iclr;
|
|
|
|
cl = strchr(scr->starclrs, star->color);
|
|
if (!cl)
|
|
continue; // cannot draw this color star
|
|
iclr = cl - scr->starclrs;
|
|
|
|
star->image = scr->starimgs + iclr * scr->starsizes + star->size;
|
|
}
|
|
}
|
|
|
|
static void drawLayers(const mg_driver_t* dst, script_t* scr)
|
|
{
|
|
int i;
|
|
layer_t* l;
|
|
|
|
for (i = 0, l = scr->layers; i < scr->clayers; ++i, ++l)
|
|
{
|
|
int j;
|
|
const struct map_layer* map;
|
|
|
|
for (j = 0, map = map_layers;
|
|
map->name && strcmp(map->name, l->name) != 0;
|
|
++j, ++map)
|
|
;
|
|
if (!map->name)
|
|
{
|
|
mg_verbose(1, "Warning: layer '%s' not defined\n", l->name);
|
|
continue;
|
|
}
|
|
|
|
map->draw(dst, scr);
|
|
}
|
|
}
|
|
|
|
static object_t* addObject(script_t* scr, obj_type_t otype, shape_type_t stype,
|
|
double x0, double y0, double x1, double y1, double rx, double ry)
|
|
{
|
|
object_t* obj;
|
|
|
|
if (!scr->objs)
|
|
{ // prealloc something decent
|
|
int count = 2000;
|
|
scr->objs = calloc(count, sizeof(*scr->objs));
|
|
if (!scr->objs)
|
|
return 0;
|
|
scr->objalloc = count;
|
|
}
|
|
else if (scr->cobjs >= scr->objalloc)
|
|
{ // need more room
|
|
int count = scr->objalloc + scr->objalloc / 2;
|
|
object_t* newa = realloc(scr->objs, count * sizeof(*scr->objs));
|
|
if (!newa)
|
|
return 0;
|
|
scr->objs = newa;
|
|
scr->objalloc = count;
|
|
}
|
|
|
|
obj = scr->objs + scr->cobjs;
|
|
scr->cobjs++;
|
|
|
|
obj->type = otype;
|
|
obj->shape.type = stype;
|
|
obj->shape.pt0.x = x0;
|
|
obj->shape.pt0.y = y0;
|
|
obj->shape.pt1.x = x1;
|
|
obj->shape.pt1.y = y1;
|
|
obj->shape.radiusx = rx;
|
|
obj->shape.radiusy = ry;
|
|
|
|
return obj;
|
|
}
|
|
|
|
static void shapeToBox(const shape_t* shp, mg_rectf_t* r)
|
|
{
|
|
switch (shp->type)
|
|
{
|
|
case sht_Point:
|
|
r->w = r->h = 0;
|
|
r->x = shp->pt0.x;
|
|
r->y = shp->pt0.y;
|
|
break;
|
|
|
|
case sht_Line:
|
|
case sht_Rect:
|
|
r->x = shp->pt0.x < shp->pt1.x ? shp->pt0.x : shp->pt1.x;
|
|
r->y = shp->pt0.y < shp->pt1.y ? shp->pt0.y : shp->pt1.y;
|
|
r->w = fabs(shp->pt0.x - shp->pt1.x);
|
|
r->h = fabs(shp->pt0.y - shp->pt1.y);
|
|
break;
|
|
|
|
case sht_Ellipse:
|
|
r->x = shp->pt0.x - shp->radiusx;
|
|
r->y = shp->pt0.y - shp->radiusy;
|
|
r->w = shp->radiusx * 2;
|
|
r->h = shp->radiusy * 2;
|
|
break;
|
|
|
|
default:
|
|
r->w = r->h = 0;
|
|
r->x = r->y = -1000;
|
|
}
|
|
}
|
|
|
|
static int overlapBox(const mg_rectf_t* r1, const mg_rectf_t* r2)
|
|
{
|
|
double dx, dy;
|
|
int overlap = 0;
|
|
|
|
dx = r2->x - r1->x;
|
|
if ((dx >= 0 && dx <= r1->w) || (dx < 0 && -dx <= r2->w))
|
|
overlap = 1;
|
|
|
|
if (overlap)
|
|
{
|
|
dy = r2->y - r1->y;
|
|
if ((dy >= 0 && dy <= r1->h) || (dy < 0 && -dy <= r2->h))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int pointInBox(const mg_rectf_t* r, double x, double y)
|
|
{
|
|
double dx = x - r->x;
|
|
double dy = y - r->y;
|
|
return dx >= 0 && dx < r->w && dy >= 0 && dy < r->h;
|
|
}
|
|
|
|
static inline int coordSame(double c1, double c2)
|
|
{
|
|
#define DIST_THRESH 0.0000001
|
|
return fabs(c1 - c2) < DIST_THRESH;
|
|
}
|
|
|
|
static inline int coordInRange(double c, double l0, double l1)
|
|
{
|
|
double t;
|
|
if (l1 < l0)
|
|
{ // swap ends
|
|
t = l0;
|
|
l0 = l1;
|
|
l1 = t;
|
|
}
|
|
return c >= l0 && c <= l1;
|
|
}
|
|
|
|
static inline int pointOnLine(const shape_t* lin, double x, double y)
|
|
{
|
|
double dx = lin->pt1.x - lin->pt0.x;
|
|
double dy = lin->pt1.y - lin->pt0.y;
|
|
double dl;
|
|
if (dx == 0 && dy == 0)
|
|
{ // line is a point
|
|
return coordSame(x, lin->pt0.x) && coordSame(y, lin->pt0.y);
|
|
}
|
|
else if (dx == 0)
|
|
{ // line is vertical
|
|
dl = (y - lin->pt0.y) / dy;
|
|
return coordSame(x, lin->pt0.x) && (dl >= 0 && dl <= 1.0);
|
|
}
|
|
|
|
dl = (x - lin->pt0.x) / dx;
|
|
return coordSame(y, lin->pt0.y + dl * dy) && (dl >= 0 && dl <= 1.0);
|
|
}
|
|
|
|
static inline int pointInEllipse(const shape_t* ell, double x, double y)
|
|
{
|
|
double fy, radius_2;
|
|
double dist_2;
|
|
|
|
x -= ell->pt0.x;
|
|
y -= ell->pt0.y;
|
|
|
|
radius_2 = ell->radiusx * ell->radiusx;
|
|
fy = ell->radiusx / ell->radiusy;
|
|
|
|
dist_2 = y * y * fy * fy + x * x;
|
|
|
|
return dist_2 <= radius_2;
|
|
}
|
|
|
|
static int overlapLine(const shape_t* lin1, const shape_t* lin2)
|
|
{
|
|
const double dx1 = lin1->pt1.x - lin1->pt0.x;
|
|
const double dy1 = lin1->pt1.y - lin1->pt0.y;
|
|
const double dx2 = lin2->pt1.x - lin2->pt0.x;
|
|
const double dy2 = lin2->pt1.y - lin2->pt0.y;
|
|
const double v1t = dx2 * (lin1->pt0.y - lin2->pt0.y) - dy2 * (lin1->pt0.x - lin2->pt0.x);
|
|
const double v2t = dx1 * (lin1->pt0.y - lin2->pt0.y) - dy1 * (lin1->pt0.x - lin2->pt0.x);
|
|
const double dm = dy2 * dx1 - dx2 * dy1; // slope ratio
|
|
|
|
if (dm == 0)
|
|
{ // al least parallel
|
|
if (v1t == 0 || v2t == 0)
|
|
{ // coinciding
|
|
const double cx1 = (lin1->pt0.x + lin1->pt1.x);
|
|
const double cx2 = (lin2->pt0.x + lin2->pt1.x);
|
|
const double cy1 = (lin1->pt0.y + lin1->pt1.y);
|
|
const double cy2 = (lin2->pt0.y + lin2->pt1.y);
|
|
|
|
return (fabs(cx1 - cx2) <= fabs(dx1) + fabs(dx2) &&
|
|
fabs(cy1 - cy2) <= fabs(dy1) + fabs(dy2) );
|
|
}
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
double v1 = v1t / dm;
|
|
double v2 = v2t / dm;
|
|
|
|
return (v1 >= 0 && v1 <= 1) && (v2 >= 0 && v2 <= 1);
|
|
}
|
|
}
|
|
|
|
static int overlapBoxWithLine(const mg_rectf_t* r, const shape_t* lin)
|
|
{
|
|
double dx, dy, bc;
|
|
|
|
// first the easy cases: obvious contained points
|
|
if (pointInBox(r, lin->pt0.x, lin->pt0.y)
|
|
|| pointInBox(r, lin->pt1.x, lin->pt1.y))
|
|
return 1;
|
|
|
|
dx = lin->pt1.x - lin->pt0.x;
|
|
dy = lin->pt1.y - lin->pt0.y;
|
|
|
|
if (dx == 0 && dy == 0)
|
|
{ // line is a point
|
|
return 0; // point-in-box would catch it
|
|
}
|
|
else if (dx == 0)
|
|
{ // line is vertical
|
|
// check if it crosses the rect completely,
|
|
// otherwise point-in-box would catch it
|
|
return (lin->pt0.x >= r->x) && (lin->pt0.x <= r->x + r->w)
|
|
&& (r->y - lin->pt0.y) * (lin->pt1.y - (r->y + r->h)) >= 0;
|
|
}
|
|
else if (dy == 0)
|
|
{ // line is horizontal
|
|
// check if it crosses the rect completely,
|
|
// otherwise point-in-box would catch it
|
|
return (lin->pt0.y >= r->y) && (lin->pt0.y <= r->y + r->h)
|
|
&& (r->x - lin->pt0.x) * (lin->pt1.x - (r->x + r->w)) >= 0;
|
|
}
|
|
|
|
// now check intercect with each of the 4 box-bounding lines
|
|
// non-parallel lines always intersect at *some* point in space
|
|
// we just check *which* point
|
|
bc = lin->pt0.y + (r->x - lin->pt0.x) / dx * dy;
|
|
if (coordInRange(bc, r->y, r->y + r->h))
|
|
return 1;
|
|
|
|
bc = lin->pt0.y + (r->x + r->w - lin->pt0.x) / dx * dy;
|
|
if (coordInRange(bc, r->y, r->y + r->h))
|
|
return 1;
|
|
|
|
bc = lin->pt0.x + (r->y - lin->pt0.y) / dy * dx;
|
|
if (coordInRange(bc, r->x, r->x + r->w))
|
|
return 1;
|
|
|
|
bc = lin->pt0.x + (r->y + r->h - lin->pt0.y) / dy * dx;
|
|
if (coordInRange(bc, r->x, r->x + r->w))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int overlapEllipseWithLine(const shape_t* ell, const shape_t* lin)
|
|
{
|
|
shape_t l = *lin;
|
|
double dx = l.pt1.x - l.pt0.x;
|
|
double dy = l.pt1.y - l.pt0.y;
|
|
double fx, r, radius_2;
|
|
double a, b, c, d;
|
|
|
|
// first the easy cases: obvious contained points
|
|
if (pointInEllipse(ell, lin->pt0.x, lin->pt0.y)
|
|
|| pointInEllipse(ell, lin->pt1.x, lin->pt1.y))
|
|
return 1;
|
|
|
|
if (dx == 0 && dy == 0)
|
|
{ // line is a point
|
|
return 0; // point-in-ellipse would catch it
|
|
}
|
|
else if (dx == 0)
|
|
{ // line is vertical
|
|
// check if it crosses the ellipse completely,
|
|
// otherwise point-in-ellipse would catch it
|
|
return (lin->pt0.x >= ell->pt0.x - ell->radiusx)
|
|
&& (lin->pt0.x <= ell->pt0.x + ell->radiusx)
|
|
&& ((ell->pt0.y - ell->radiusy) - lin->pt0.y)
|
|
* (lin->pt1.y - (ell->pt0.y + ell->radiusy)) >= 0;
|
|
}
|
|
else if (dy == 0)
|
|
{ // line is horizontal
|
|
// check if it crosses the ellipse completely,
|
|
// otherwise point-in-ellipse would catch it
|
|
return (lin->pt0.y >= ell->pt0.y - ell->radiusy)
|
|
&& (lin->pt0.y <= ell->pt0.y + ell->radiusy)
|
|
&& ((ell->pt0.x - ell->radiusx) - lin->pt0.x)
|
|
* (lin->pt1.x - (ell->pt0.x + ell->radiusx)) >= 0;
|
|
}
|
|
|
|
// make center of ellipse origin
|
|
l.pt0.x -= ell->pt0.x;
|
|
l.pt0.y -= ell->pt0.y;
|
|
l.pt1.x -= ell->pt0.x;
|
|
l.pt1.y -= ell->pt0.y;
|
|
|
|
// the (more or less) classic elliptic equation is
|
|
// (fx*x)^2 + (fy*y)^2 = r^2 (classic: fx=r/a, fy=r/b)
|
|
// we'll take b=r and transform
|
|
// (fx*x)^2 + y^2 = r^2
|
|
r = ell->radiusy;
|
|
radius_2 = r * r;
|
|
fx = r / ell->radiusx;
|
|
// the classic line equation is
|
|
// y = b*x + c
|
|
b = dy / dx;
|
|
c = l.pt0.y - l.pt0.x * b;
|
|
|
|
// (b*x + c)^2 = r^2 - fx^2*x^2
|
|
// b^2*x^2 + 2*b*x*c + c^2 = r^2 - fx^2*x^2
|
|
// (b^2+fx^2)x^2 + (2*b*c)x + (c^2-r^2) = 0
|
|
a = b*b + fx*fx;
|
|
b = 2 * b * c;
|
|
c = c*c - r*r;
|
|
d = b*b - 4*a*c;
|
|
|
|
if (d >= 0)
|
|
{
|
|
double root = sqrt(d);
|
|
double x;
|
|
|
|
x = (-b + root) / (2*a);
|
|
if (coordInRange(x, l.pt0.x, l.pt1.x))
|
|
return 1;
|
|
|
|
x = (-b - root) / (2*a);
|
|
if (coordInRange(x, l.pt0.x, l.pt1.x))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int overlapBoxWithEllipse(const mg_rectf_t* r, const shape_t* ell)
|
|
{
|
|
shape_t lin;
|
|
|
|
// first the easy cases: obvious contained points
|
|
if (pointInBox(r, ell->pt0.x, ell->pt0.y)
|
|
|| pointInEllipse(ell, r->x, r->y)
|
|
|| pointInEllipse(ell, r->x + r->w, r->y)
|
|
|| pointInEllipse(ell, r->x, r->y + r->h)
|
|
|| pointInEllipse(ell, r->x + r->w, r->y + r->h))
|
|
return 1;
|
|
|
|
// now at least one rect-bounding line must intersect
|
|
// the ellipse, or no overlap
|
|
lin.pt0.x = r->x;
|
|
lin.pt1.x = r->x + r->w;
|
|
lin.pt0.y = lin.pt1.y = r->y;
|
|
if (overlapEllipseWithLine(ell, &lin))
|
|
return 1;
|
|
|
|
lin.pt0.y = lin.pt1.y = r->y + r->h;
|
|
if (overlapEllipseWithLine(ell, &lin))
|
|
return 1;
|
|
|
|
lin.pt0.y = r->y;
|
|
lin.pt1.y = r->y + r->h;
|
|
lin.pt0.x = lin.pt1.x = r->x;
|
|
if (overlapEllipseWithLine(ell, &lin))
|
|
return 1;
|
|
|
|
lin.pt0.x = lin.pt1.x = r->x + r->w;
|
|
if (overlapEllipseWithLine(ell, &lin))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int overlapEllipse(const shape_t* el1, const shape_t* el2)
|
|
{
|
|
// This is mostly unfinished
|
|
// but it's not currently necessary for our application
|
|
double mr1, mr2; // major radius
|
|
double dx, dy;
|
|
double dist;
|
|
|
|
// first the easy cases: obvious contained points
|
|
if (pointInEllipse(el1, el2->pt0.x, el2->pt0.y)
|
|
|| pointInEllipse(el2, el1->pt1.x, el1->pt1.y))
|
|
return 1;
|
|
|
|
mr1 = el1->radiusx > el1->radiusy ? el1->radiusx : el1->radiusy;
|
|
mr2 = el2->radiusx > el2->radiusy ? el2->radiusx : el2->radiusy;
|
|
|
|
// see if too far apart
|
|
dx = el2->pt0.x - el1->pt0.x;
|
|
dy = el2->pt0.y - el1->pt0.y;
|
|
dist = sqrt(dx*dx + dy*dy);
|
|
if (dist > mr1 + mr2)
|
|
return 0; // too far apart
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int overlapShapes(const shape_t* obj1, const shape_t* obj2)
|
|
{
|
|
mg_rectf_t r1, r2;
|
|
|
|
shapeToBox(obj1, &r1);
|
|
shapeToBox(obj2, &r2);
|
|
|
|
if (!overlapBox(&r1, &r2))
|
|
return 0;
|
|
|
|
switch (obj1->type)
|
|
{
|
|
case sht_Point:
|
|
if (obj2->type == sht_Line)
|
|
return pointOnLine(obj2, r1.x, r1.y);
|
|
if (obj2->type == sht_Ellipse)
|
|
return pointInEllipse(obj2, r1.x, r1.y);
|
|
// fallthrough
|
|
case sht_Rect:
|
|
if (obj2->type == sht_Point || obj2->type == sht_Rect)
|
|
return 1; // overlap is enough
|
|
if (obj2->type == sht_Line)
|
|
return overlapBoxWithLine(&r1, obj2);
|
|
if (obj2->type == sht_Ellipse)
|
|
return overlapBoxWithEllipse(&r1, obj2);
|
|
break;
|
|
|
|
case sht_Line:
|
|
if (obj2->type == sht_Point)
|
|
return pointOnLine(obj1, r2.x, r2.y);
|
|
if (obj2->type == sht_Line)
|
|
return overlapLine(obj1, obj2);
|
|
if (obj2->type == sht_Rect)
|
|
return overlapBoxWithLine(&r2, obj1);
|
|
if (obj2->type == sht_Ellipse)
|
|
return overlapEllipseWithLine(obj2, obj1);
|
|
break;
|
|
|
|
case sht_Ellipse:
|
|
if (obj2->type == sht_Point)
|
|
return pointInEllipse(obj1, r2.x, r2.y);
|
|
if (obj2->type == sht_Line)
|
|
return overlapEllipseWithLine(obj1, obj2);
|
|
if (obj2->type == sht_Rect)
|
|
return overlapBoxWithEllipse(&r2, obj1);
|
|
if (obj2->type == sht_Ellipse)
|
|
return overlapEllipse(obj1, obj2);
|
|
break;
|
|
|
|
default:
|
|
;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int isCollidingWith(const script_t* scr, const shape_t* shp, obj_type_t witht)
|
|
{
|
|
int i;
|
|
object_t* testobj;
|
|
|
|
for (i = 0, testobj = scr->objs; i < scr->cobjs; ++i, ++testobj)
|
|
{
|
|
if (!(testobj->type & witht))
|
|
continue;
|
|
|
|
if (overlapShapes(shp, &testobj->shape))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void imageToRectShape(const script_t* scr, const mg_image_t img, shape_t* shp)
|
|
{
|
|
const double stepx = scr->gridr.w / scr->gridmx;
|
|
const double stepy = scr->gridr.h / scr->gridmy;
|
|
mg_rectf_t r;
|
|
|
|
drv->getImageSize(img, &r);
|
|
shp->type = sht_Rect;
|
|
shp->pt0.x = 0;
|
|
shp->pt0.y = 0;
|
|
shp->pt1.x = r.w / stepx;
|
|
shp->pt1.y = r.h / stepy;
|
|
}
|
|
|
|
static void boxToRectShape(const script_t* scr, const mg_rectf_t* r, shape_t* shp)
|
|
{
|
|
const double stepx = scr->gridr.w / scr->gridmx;
|
|
const double stepy = scr->gridr.h / scr->gridmy;
|
|
|
|
shp->type = sht_Rect;
|
|
shp->pt0.x = 0;
|
|
shp->pt0.y = 0;
|
|
shp->pt1.x = r->w / stepx;
|
|
shp->pt1.y = r->h / stepy;
|
|
}
|
|
|
|
|
|
static int isAlmostMod0(double x, double y)
|
|
{
|
|
#undef DIST_THRESH
|
|
#define DIST_THRESH 0.000001
|
|
double m = fmod(x, y);
|
|
return (m < DIST_THRESH || m > (y - DIST_THRESH));
|
|
}
|
|
|
|
static void drawGrid(const mg_driver_t* dst, script_t* scr)
|
|
{
|
|
int i;
|
|
double x, y;
|
|
const double gxstep1 = scr->grids1 ? scr->gridr.w / (scr->gridmx / scr->grids1) : 0;
|
|
const double gystep1 = scr->grids1 ? scr->gridr.h / (scr->gridmy / scr->grids1) : 0;
|
|
const double gxstep2 = scr->grids2 ? scr->gridr.w / (scr->gridmx / scr->grids2) : 0;
|
|
const double gystep2 = scr->grids2 ? scr->gridr.h / (scr->gridmy / scr->grids2) : 0;
|
|
const double gxstep3 = scr->grids3 ? scr->gridr.w / (scr->gridmx / scr->grids3) : 0;
|
|
const double gystep3 = scr->grids3 ? scr->gridr.h / (scr->gridmy / scr->grids3) : 0;
|
|
const double gxl1 = scr->gridr.w / 120;
|
|
const double gyl1 = scr->gridr.h / 120;
|
|
const double gxl2 = scr->gridr.w / 180;
|
|
const double gyl2 = scr->gridr.h / 180;
|
|
const double gxl3 = scr->gridr.w / 240;
|
|
const double gyl3 = scr->gridr.h / 240;
|
|
const mg_rectf_t r = scr->gridr;
|
|
|
|
// draw the box
|
|
dst->drawRect(&r, scr->gridclr, scr->gridbw);
|
|
|
|
// draw grid lines
|
|
if (gxstep2)
|
|
{
|
|
for (x = r.x + gxstep2; x < r.x + r.w - gxstep2 / 2; x += gxstep2)
|
|
dst->drawLine(x, r.y, x, r.y + r.h, scr->gridclr, scr->gridlw);
|
|
for (y = r.y + gystep2; y < r.y + r.h - gystep2 / 2; y += gystep2)
|
|
dst->drawLine(r.x, y, r.x + r.w, y, scr->gridclr, scr->gridlw);
|
|
}
|
|
|
|
// draw ruler markers
|
|
if (gxstep3)
|
|
{
|
|
for (x = r.x; x <= r.x + r.w; x += gxstep3)
|
|
{
|
|
if ((gxstep2 && isAlmostMod0(x - r.x, gxstep2)) ||
|
|
(gxstep1 && isAlmostMod0(x - r.x, gxstep1)))
|
|
continue;
|
|
dst->drawLine(x, r.y - gyl3, x, r.y, scr->gridmclr, scr->gridlw);
|
|
dst->drawLine(x, r.y + r.h, x, r.y + r.h + gyl3, scr->gridmclr, scr->gridlw);
|
|
}
|
|
for (y = r.y; y <= r.y + r.h; y += gystep3)
|
|
{
|
|
if ((gystep2 && isAlmostMod0(y - r.y, gystep2)) ||
|
|
(gystep1 && isAlmostMod0(y - r.y, gystep1)))
|
|
continue;
|
|
dst->drawLine(r.x - gxl3, y, r.x, y, scr->gridmclr, scr->gridlw);
|
|
dst->drawLine(r.x + r.w, y, r.x + r.w + gxl3, y, scr->gridmclr, scr->gridlw);
|
|
}
|
|
}
|
|
if (gxstep2)
|
|
{
|
|
for (x = r.x; x <= r.x + r.w; x += gxstep2)
|
|
{
|
|
if (gxstep1 && isAlmostMod0(x - r.x, gxstep1))
|
|
continue;
|
|
dst->drawLine(x, r.y - gyl2, x, r.y, scr->gridmclr, scr->gridlw);
|
|
dst->drawLine(x, r.y + r.h, x, r.y + r.h + gyl2, scr->gridmclr, scr->gridlw);
|
|
}
|
|
for (y = r.y; y <= r.y + r.h; y += gystep2)
|
|
{
|
|
if (gystep1 && isAlmostMod0(y - r.y, gystep1))
|
|
continue;
|
|
dst->drawLine(r.x - gxl2, y, r.x, y, scr->gridmclr, scr->gridlw);
|
|
dst->drawLine(r.x + r.w, y, r.x + r.w + gxl2, y, scr->gridmclr, scr->gridlw);
|
|
}
|
|
}
|
|
if (gxstep1)
|
|
{
|
|
for (x = r.x; x <= r.x + r.w; x += gxstep1)
|
|
{
|
|
dst->drawLine(x, r.y - gyl1, x, r.y, scr->gridmclr, scr->gridlw);
|
|
dst->drawLine(x, r.y + r.h, x, r.y + r.h + gyl1, scr->gridmclr, scr->gridlw);
|
|
}
|
|
for (y = r.y; y <= r.y + r.h; y += gystep1)
|
|
{
|
|
dst->drawLine(r.x - gxl1, y, r.x, y, scr->gridmclr, scr->gridlw);
|
|
dst->drawLine(r.x + r.w, y, r.x + r.w + gxl1, y, scr->gridmclr, scr->gridlw);
|
|
}
|
|
}
|
|
|
|
// draw grid numbers
|
|
if (!scr->gridfnt || !gxstep1)
|
|
{
|
|
mg_verbose(2, "Warning: not enough data to render grid numbers\n");
|
|
return; // nothing else to do
|
|
}
|
|
|
|
for (i = 1, x = r.x + gxstep1; x < r.x + r.w - gxstep1 / 2; x += gxstep1, ++i)
|
|
{
|
|
mg_rectf_t tr;
|
|
char buf[20];
|
|
|
|
sprintf(buf, "%d", i * scr->grids1);
|
|
drv->getTextSize(scr->gridfnt, buf, &tr);
|
|
drv->drawText(scr->gridfnt, x - tr.w / 2, r.y - gyl1 - tr.h, buf, scr->gridclr);
|
|
drv->drawText(scr->gridfnt, x - tr.w / 2, r.y + r.h + gyl1 + tr.h / 6, buf, scr->gridclr);
|
|
}
|
|
for (i = 1, y = r.y + r.h - gystep1; y > r.y + gystep1 / 2; y -= gystep1, ++i)
|
|
{
|
|
mg_rectf_t tr;
|
|
char buf[20];
|
|
|
|
sprintf(buf, "%d", i * scr->grids1);
|
|
drv->getTextSize(scr->gridfnt, buf, &tr);
|
|
drv->drawText(scr->gridfnt, r.x - gxl1 - tr.w * 1.125, y - tr.h / 2, buf, scr->gridclr);
|
|
drv->drawText(scr->gridfnt, r.x + r.w + gxl1 + tr.w * 0.125, y - tr.h / 2, buf, scr->gridclr);
|
|
}
|
|
}
|
|
|
|
static void drawStarsSize(const mg_driver_t* dst, script_t* scr, int size)
|
|
{
|
|
int i;
|
|
const star_t* star;
|
|
const double orgx = scr->gridr.x;
|
|
const double orgy = scr->gridr.y + scr->gridr.h;
|
|
const double width = scr->gridr.w;
|
|
const double height = scr->gridr.h;
|
|
double stepx = width / scr->gridmx;
|
|
double stepy = height / scr->gridmy;
|
|
|
|
for (i = 0, star = scr->stars; i < scr->cstars; ++i, ++star)
|
|
{
|
|
const star_image_t* img = star->image;
|
|
|
|
if (star->size != size || !img)
|
|
continue;
|
|
|
|
if (img->img)
|
|
{ // use image
|
|
dst->drawImage(orgx + star->pos.x * stepx - img->hot.x,
|
|
orgy - star->pos.y * stepy - img->hot.y,
|
|
img->img);
|
|
}
|
|
else
|
|
{ // this star image is not present, or not drawn
|
|
dst->drawFilledEllipse(
|
|
orgx + star->pos.x * stepx,
|
|
orgy - star->pos.y * stepy,
|
|
img->radius, img->radius, img->rend_color);
|
|
}
|
|
|
|
// register an elliptical grid object
|
|
addObject(scr, objt_Star, sht_Ellipse, star->pos.x, star->pos.y,
|
|
0, 0, img->radius / stepx, img->radius / stepy);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static void drawStars(const mg_driver_t* dst, script_t* scr)
|
|
{
|
|
int i;
|
|
int max = 0;
|
|
star_t* star;
|
|
|
|
if (!scr->stars || !scr->starimgs || scr->cstarimgs == 0)
|
|
{
|
|
mg_verbose(2, "Warning: not enough data to render stars\n");
|
|
return;
|
|
}
|
|
|
|
// find max star size
|
|
for (i = 0, star = scr->stars; i < scr->cstars; ++i, ++star)
|
|
{
|
|
// make sure the star is not too big for us
|
|
if (star->size >= scr->starsizes)
|
|
star->size = scr->starsizes - 1;
|
|
|
|
if (star->size > max)
|
|
max = star->size;
|
|
}
|
|
|
|
// draw stars from larger to smaller
|
|
for (i = max; i >= 0; --i)
|
|
drawStarsSize(dst, scr, i);
|
|
}
|
|
|
|
static void drawClusterLines(const mg_driver_t* dst, script_t* scr)
|
|
{
|
|
const double orgx = scr->gridr.x;
|
|
const double orgy = scr->gridr.y + scr->gridr.h;
|
|
const double width = scr->gridr.w;
|
|
const double height = scr->gridr.h;
|
|
double stepx = width / scr->gridmx;
|
|
double stepy = height / scr->gridmy;
|
|
int i1;
|
|
int i2;
|
|
|
|
if (!scr->clusters)
|
|
{
|
|
mg_verbose(2, "Warning: not enough data to render cluster lines\n");
|
|
return;
|
|
}
|
|
|
|
for (i1 = 0; i1 < scr->cclusters; ++i1)
|
|
{
|
|
cluster_t* cluster = scr->clusters + i1;
|
|
|
|
for (i2 = 0; i2 < cluster->cconns; ++i2)
|
|
{
|
|
star_t* star1 = cluster->conns[i2][0];
|
|
star_t* star2 = cluster->conns[i2][1];
|
|
|
|
dst->drawLine(
|
|
orgx + star1->pos.x * stepx, orgy - star1->pos.y * stepy,
|
|
orgx + star2->pos.x * stepx, orgy - star2->pos.y * stepy,
|
|
scr->clustclr, scr->clustlw);
|
|
|
|
// register a line grid object
|
|
addObject(scr, objt_ClusterLine, sht_Line,
|
|
star1->pos.x, star1->pos.y,
|
|
star2->pos.x, star2->pos.y,
|
|
0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int tryPlaceRect(script_t* scr, obj_type_t kind, mg_rectf_t* r, double x, double y)
|
|
{
|
|
shape_t shp;
|
|
|
|
// passed rect considered centered around x,y
|
|
shp.type = sht_Rect;
|
|
shp.pt0.x = x - r->w / 2;
|
|
shp.pt0.y = y - r->h / 2;
|
|
shp.pt1.x = shp.pt0.x + r->w;
|
|
shp.pt1.y = shp.pt0.y + r->h;
|
|
|
|
if (isCollidingWith(scr, &shp, kind))
|
|
return 0;
|
|
|
|
if (shp.pt0.x < 0 || shp.pt1.x >= scr->gridmx || shp.pt0.y < 0 || shp.pt1.y >= scr->gridmy)
|
|
return 0; // out of bounds
|
|
|
|
r->x = shp.pt0.x;
|
|
r->y = shp.pt0.y;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void drawSingularNamesSize(const mg_driver_t* dst, script_t* scr, int size)
|
|
{
|
|
int i;
|
|
const double orgx = scr->gridr.x;
|
|
const double orgy = scr->gridr.y + scr->gridr.h;
|
|
const double width = scr->gridr.w;
|
|
const double height = scr->gridr.h;
|
|
const double stepx = width / scr->gridmx;
|
|
const double stepy = height / scr->gridmy;
|
|
|
|
for (i = 0; i < scr->cclusters; ++i)
|
|
{
|
|
const cluster_t* cluster = scr->clusters + i;
|
|
const star_t* star = scr->stars + cluster->first;
|
|
const star_image_t* img = star->image;
|
|
mg_rectf_t r;
|
|
int placed = 0;
|
|
double radxofs, radyofs;
|
|
double rx, ry;
|
|
|
|
// not interested in clusters or singulars or other sizes
|
|
if (cluster->cstars > 1 || star->size != size)
|
|
continue;
|
|
|
|
if (!img)
|
|
{
|
|
mg_verbose(2, "Warning: star %s has no image\n", star->cluster);
|
|
continue;
|
|
}
|
|
|
|
r.x = r.y = 0;
|
|
dst->getTextSize(scr->cnamefnt, star->cluster, &r);
|
|
// add a little x margin
|
|
r.w += r.h / 12;
|
|
// placement is done in grid coordinates
|
|
r.w /= stepx;
|
|
r.h /= stepy;
|
|
|
|
radxofs = img->radius * 1.1 / stepx;
|
|
radyofs = img->radius * 1.1 / stepy;
|
|
|
|
// search for a good placement spot
|
|
for (rx = radxofs, ry = radyofs;
|
|
!placed && (rx < radxofs + r.h || ry < radyofs + r.h);
|
|
rx += r.h * 0.1, ry += r.h * 0.1)
|
|
{
|
|
// try below
|
|
placed = tryPlaceRect(scr, objt_TextPlacement, &r,
|
|
star->pos.x, star->pos.y - ry - r.h / 2);
|
|
if (placed)
|
|
continue;
|
|
// try above
|
|
placed = tryPlaceRect(scr, objt_TextPlacement, &r,
|
|
star->pos.x, star->pos.y + ry + r.h / 2);
|
|
if (placed)
|
|
continue;
|
|
// try to the left
|
|
placed = tryPlaceRect(scr, objt_TextPlacement, &r,
|
|
star->pos.x - rx - r.w / 2, star->pos.y);
|
|
if (placed)
|
|
continue;
|
|
// try to the right
|
|
placed = tryPlaceRect(scr, objt_TextPlacement, &r,
|
|
star->pos.x + rx + r.w / 2, star->pos.y);
|
|
}
|
|
|
|
if (!placed)
|
|
{
|
|
mg_verbose(2, "Warning: cannot place '%s' w/o overlap\n", star->cluster);
|
|
continue; // oops
|
|
}
|
|
|
|
dst->drawText(scr->cnamefnt,
|
|
orgx + r.x * stepx,
|
|
orgy - (r.y + r.h) * stepy,
|
|
star->cluster, scr->cnameclr);
|
|
|
|
// register a rect grid object
|
|
addObject(scr, objt_ClusterText, sht_Rect,
|
|
r.x, r.y, r.x + r.w, r.y + r.h,
|
|
0, 0);
|
|
}
|
|
}
|
|
|
|
static void drawSingularNames(const mg_driver_t* dst, script_t* scr)
|
|
{
|
|
int i;
|
|
int max = 0;
|
|
star_t* star;
|
|
|
|
// find max star size
|
|
for (i = 0, star = scr->stars; i < scr->cstars; ++i, ++star)
|
|
{
|
|
if (star->size > max)
|
|
max = star->size;
|
|
}
|
|
|
|
// draw names in the order of star size, from smaller to larger
|
|
for (i = 0; i <= max; ++i)
|
|
drawSingularNamesSize(dst, scr, i);
|
|
}
|
|
|
|
static void drawClusteredNames(const mg_driver_t* dst, script_t* scr)
|
|
{
|
|
int i;
|
|
const double orgx = scr->gridr.x;
|
|
const double orgy = scr->gridr.y + scr->gridr.h;
|
|
const double width = scr->gridr.w;
|
|
const double height = scr->gridr.h;
|
|
const double stepx = width / scr->gridmx;
|
|
const double stepy = height / scr->gridmy;
|
|
|
|
// draw cluster names
|
|
for (i = 0; i < scr->cclusters; ++i)
|
|
{
|
|
const cluster_t* cluster = scr->clusters + i;
|
|
const star_t* star = scr->stars + cluster->first;
|
|
const unsigned char* cname = star->cluster;
|
|
mg_rectf_t r;
|
|
int placed = 0;
|
|
int j;
|
|
double maxd;
|
|
double rad, dxrad, ang;
|
|
|
|
// not interested in singular stars
|
|
if (cluster->cstars <= 1)
|
|
continue;
|
|
|
|
// find the farthest star for center
|
|
for (j = 0, maxd = 0; j < cluster->cstars; ++j, ++star)
|
|
{
|
|
double d = sqrt(SQR(cluster->center.x - star->pos.x)
|
|
+ SQR(cluster->center.y - star->pos.y));
|
|
if (d > maxd)
|
|
d = maxd;
|
|
}
|
|
|
|
r.x = r.y = 0;
|
|
dst->getTextSize(scr->cnamefnt, cname, &r);
|
|
// add a little x margin
|
|
r.w += r.h / 12;
|
|
// placement is done in grid coordinates
|
|
r.w /= stepx;
|
|
r.h /= stepy;
|
|
|
|
// stretch out the ellipse over x
|
|
dxrad = r.w / r.h;
|
|
dxrad -= (dxrad - 1) * 0.5; // not so severe
|
|
|
|
for (rad = 0; !placed && rad < maxd + r.h * 3; rad += r.h * 0.1)
|
|
{
|
|
double astep = M_PI / (rad / r.h * 32);
|
|
|
|
for (ang = 0; !placed && ang < M_PI * 2; ang += astep)
|
|
{
|
|
placed = tryPlaceRect(scr, objt_TextPlacement, &r,
|
|
cluster->center.x + cos(ang) * rad * dxrad,
|
|
cluster->center.y + sin(ang) * rad);
|
|
}
|
|
}
|
|
|
|
if (!placed)
|
|
{
|
|
mg_verbose(2, "Warning: cannot place '%s' w/o overlap\n", cname);
|
|
continue;
|
|
}
|
|
|
|
dst->drawText(scr->cnamefnt,
|
|
orgx + r.x * stepx,
|
|
orgy - (r.y + r.h) * stepy,
|
|
cname, scr->cnameclr);
|
|
|
|
// register a rect grid object
|
|
addObject(scr, objt_ClusterText, sht_Rect,
|
|
r.x, r.y, r.x + r.w, r.y + r.h,
|
|
0, 0);
|
|
}
|
|
}
|
|
|
|
static void drawClusterNames(const mg_driver_t* dst, script_t* scr)
|
|
{
|
|
if (!scr->cnamefnt)
|
|
{
|
|
mg_verbose(2, "Warning: not enough data to render cluster names (missing font)\n");
|
|
return;
|
|
}
|
|
if (!scr->stars || !scr->clusters)
|
|
{
|
|
mg_verbose(2, "Warning: not enough data to render cluster names\n");
|
|
return;
|
|
}
|
|
|
|
drawSingularNames(dst, scr);
|
|
drawClusteredNames(dst, scr);
|
|
}
|
|
|
|
static void drawStarDesignation(const mg_driver_t* dst, script_t* scr, const star_t* star)
|
|
{
|
|
const double orgx = scr->gridr.x;
|
|
const double orgy = scr->gridr.y + scr->gridr.h;
|
|
const double width = scr->gridr.w;
|
|
const double height = scr->gridr.h;
|
|
double stepx = width / scr->gridmx;
|
|
double stepy = height / scr->gridmy;
|
|
const star_image_t* img = star->image;
|
|
const unsigned char* sdesig = scr->desigtab[star->prefix - 1].str;
|
|
mg_rectf_t r;
|
|
int placed = 0;
|
|
double rad, ang;
|
|
|
|
if (!img)
|
|
{
|
|
mg_verbose(2, "Warning: star %s has no image\n", star->cluster);
|
|
return;
|
|
}
|
|
|
|
r.x = r.y = 0;
|
|
dst->getTextSize(scr->desigfnt, sdesig, &r);
|
|
// add a little x margin
|
|
r.w += r.h / 8;
|
|
// placement is done in grid coordinates
|
|
r.w /= stepx;
|
|
r.h /= stepy;
|
|
|
|
// search for a good placement spot
|
|
for (rad = r.w * 1.06; !placed && rad < r.h * 2.5; rad += r.h * 0.05)
|
|
{
|
|
double astep = M_PI / (rad / r.h * 32);
|
|
|
|
for (ang = 0; !placed && ang < M_PI * 2; ang += astep)
|
|
{
|
|
placed = tryPlaceRect(scr, objt_DesigPlacement, &r,
|
|
star->pos.x + cos(ang) * rad,
|
|
star->pos.y + sin(ang) * rad);
|
|
}
|
|
}
|
|
|
|
if (!placed)
|
|
{
|
|
mg_verbose(2, "Warning: cannot place %s %d designation w/o overlap\n", star->cluster, star->prefix);
|
|
return; // oops
|
|
}
|
|
|
|
dst->drawText(scr->desigfnt,
|
|
orgx + r.x * stepx,
|
|
orgy - (r.y + r.h) * stepy,
|
|
sdesig, scr->desigclr);
|
|
|
|
// register a rect grid object
|
|
addObject(scr, objt_Designation, sht_Rect,
|
|
r.x, r.y, r.x + r.w, r.y + r.h,
|
|
0, 0);
|
|
|
|
}
|
|
|
|
static void drawStarDesignations(const mg_driver_t* dst, script_t* scr)
|
|
{
|
|
int i;
|
|
|
|
if (!scr->desigfnt)
|
|
{
|
|
mg_verbose(2, "Warning: not enough data to render star designations (missing font)\n");
|
|
return;
|
|
}
|
|
if (!scr->stars || !scr->desigtab || scr->cdesigtab == 0 || !scr->clusters)
|
|
{
|
|
mg_verbose(2, "Warning: not enough data to render star designations\n");
|
|
return;
|
|
}
|
|
|
|
// draw designations
|
|
for (i = 0; i < scr->cclusters; ++i)
|
|
{
|
|
const cluster_t* cluster = scr->clusters + i;
|
|
int s;
|
|
|
|
for (s = 0; s < cluster->cstars; ++s)
|
|
{
|
|
const star_t* star = scr->stars + cluster->first + s;
|
|
|
|
if (!star->prefix)
|
|
continue; // no designation
|
|
|
|
drawStarDesignation(dst, scr, star);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void drawSphereNames(const mg_driver_t* dst, script_t* scr)
|
|
{
|
|
int i;
|
|
soi_t* soi;
|
|
const double orgx = scr->gridr.x;
|
|
const double orgy = scr->gridr.y + scr->gridr.h;
|
|
const double width = scr->gridr.w;
|
|
const double height = scr->gridr.h;
|
|
double stepx = width / scr->gridmx;
|
|
double stepy = height / scr->gridmy;
|
|
|
|
if (!scr->sois)
|
|
{
|
|
mg_verbose(2, "Warning: not enough data to render Spheres of Influence\n");
|
|
return;
|
|
}
|
|
|
|
dst->setClipRect(&scr->gridr);
|
|
|
|
for (i = 0, soi = scr->sois; i < scr->csois; ++i, ++soi)
|
|
{
|
|
shape_t shp;
|
|
mg_rectf_t r;
|
|
double dstx, dsty;
|
|
mg_color_t clr = scr->backclr;
|
|
|
|
if (!soi->name || !soi->font)
|
|
{
|
|
mg_verbose(2, "Warning: not enough data to render SoI name (%d)\n", i + 1);
|
|
continue;
|
|
}
|
|
|
|
r.x = r.y = 0;
|
|
dst->getTextSize(soi->font, soi->name, &r);
|
|
|
|
dstx = orgx + (soi->center.x + soi->tweak.x) * stepx - r.w / 2;
|
|
dsty = orgy - (soi->center.y + soi->tweak.y) * stepy - r.h / 2;
|
|
// make sure the text is all within the grid
|
|
if (dstx < scr->gridr.x + stepx)
|
|
dstx = scr->gridr.x + stepx;
|
|
else if (dstx + r.w > scr->gridr.x + scr->gridr.w - stepx)
|
|
dstx = scr->gridr.x + scr->gridr.w - stepx - r.w;
|
|
if (dsty < scr->gridr.y)
|
|
dsty = scr->gridr.y;
|
|
else if (dsty + r.h > scr->gridr.y + scr->gridr.h)
|
|
dsty = scr->gridr.y + scr->gridr.h - r.h;
|
|
|
|
//soi->clr
|
|
clr.a = 0x80;
|
|
dst->drawText(soi->font, dstx, dsty, soi->name, clr);
|
|
|
|
// register a rect grid object
|
|
boxToRectShape(scr, &r, &shp);
|
|
shp.pt0.x += soi->center.x - shp.pt1.x / 2;
|
|
shp.pt0.y += soi->center.y - shp.pt1.y / 2;
|
|
shp.pt1.x += shp.pt0.x;
|
|
shp.pt1.y += shp.pt0.y;
|
|
addObject(scr, objt_SphereText, sht_Rect,
|
|
shp.pt0.x, shp.pt0.y, shp.pt1.x, shp.pt1.y,
|
|
0, 0);
|
|
}
|
|
|
|
dst->setClipRect(0);
|
|
}
|
|
|
|
static void drawSpheres(const mg_driver_t* dst, script_t* scr)
|
|
{
|
|
int i;
|
|
soi_t* soi;
|
|
const double orgx = scr->gridr.x;
|
|
const double orgy = scr->gridr.y + scr->gridr.h;
|
|
const double width = scr->gridr.w;
|
|
const double height = scr->gridr.h;
|
|
double stepx = width / scr->gridmx;
|
|
double stepy = height / scr->gridmy;
|
|
|
|
if (!scr->sois)
|
|
{
|
|
mg_verbose(2, "Warning: not enough data to render Spheres of Influence\n");
|
|
return;
|
|
}
|
|
|
|
dst->setClipRect(&scr->gridr);
|
|
|
|
for (i = 0, soi = scr->sois; i < scr->csois; ++i, ++soi)
|
|
{
|
|
mg_color_t clr = soi->clr;
|
|
double x, y, xr, yr;
|
|
|
|
clr.a = 0x40;
|
|
x = orgx + soi->center.x * stepx;
|
|
y = orgy - soi->center.y * stepy;
|
|
xr = soi->radius * stepx;
|
|
yr = soi->radius * stepy;
|
|
dst->drawFilledEllipse(x, y, xr, yr, clr);
|
|
dst->drawEllipse(x, y, xr, yr, clr, 1.0 / 128);
|
|
|
|
// register an elliptical grid object
|
|
addObject(scr, objt_Sphere, sht_Ellipse,
|
|
soi->center.x, soi->center.y, 0, 0,
|
|
soi->radius, soi->radius);
|
|
}
|
|
|
|
dst->setClipRect(0);
|
|
}
|