eigenmath/draw.cpp

777 lines
12 KiB
C++
Raw Permalink Normal View History

2008-05-23 17:29:09 +02:00
// Draw a graph
2004-03-03 21:24:06 +01:00
#include "stdafx.h"
#include "defs.h"
extern int text_width(int, char *);
extern void shipout(unsigned char *, int, int);
2005-09-02 21:41:19 +02:00
extern struct text_metric text_metric[11];
2004-03-03 21:24:06 +01:00
#define SMALL_FONT 1
#define DEFAULT_FONT 2
#define DRAW_LINE 23
#define DRAW_POINT 24
#define DRAW_BOX 25
#define DIM 300
2008-05-23 17:29:09 +02:00
#define F p3
#define T p4
#define X p5
#define Y p6
#define XT p7
#define YT p8
2004-03-03 21:24:06 +01:00
static double tmin, tmax;
static double xmin, xmax;
static double ymin, ymax;
#define YMAX 10000
static struct {
int x, y;
double t;
} draw_buf[YMAX];
static int draw_count;
void
2004-05-06 20:07:45 +02:00
eval_draw(void)
{
2008-06-14 03:25:58 +02:00
F = cadr(p1);
T = caddr(p1);
2005-08-21 03:19:56 +02:00
2008-06-14 03:25:58 +02:00
if (T == symbol(NIL)) {
push(F);
2008-08-23 22:30:37 +02:00
rewrite();
2008-06-14 03:25:58 +02:00
guess();
T = pop();
F = pop();
}
2008-05-23 17:29:09 +02:00
push(get_binding(T));
push(get_arglist(T));
2008-05-23 17:29:09 +02:00
draw_main();
2008-05-23 17:29:09 +02:00
p2 = pop();
p1 = pop();
2008-05-23 22:10:47 +02:00
set_binding_and_arglist(T, p1, p2);
2008-06-14 03:25:58 +02:00
// return value
2008-05-23 22:39:01 +02:00
2008-06-14 03:25:58 +02:00
push(symbol(NIL));
2008-05-23 18:16:15 +02:00
}
2007-08-04 15:50:16 +02:00
void
2008-05-23 17:29:09 +02:00
draw_main(void)
2004-03-03 21:24:06 +01:00
{
2007-08-04 15:50:16 +02:00
if (draw_flag) {
draw_flag = 0; // so "stop" really stops
stop("draw calls draw");
}
2004-03-03 21:24:06 +01:00
2008-05-23 17:29:09 +02:00
draw_flag++;
2004-03-03 21:24:06 +01:00
setup_trange();
setup_xrange();
setup_yrange();
2008-05-23 17:29:09 +02:00
check_for_parametric_draw();
2004-03-03 21:24:06 +01:00
2007-08-04 15:50:16 +02:00
create_point_set();
2004-03-03 21:24:06 +01:00
emit_graph();
2008-05-23 17:29:09 +02:00
draw_flag--;
}
/* xrange sets the horizontal scale
yrange sets the vertical scale
Normally, the function F is evaluated from xrange[1] to xrange[2].
However, if F returns a vector then parametric drawing is used. In this
case F is evaluated from trange[1] to trange[2].
*/
void
check_for_parametric_draw(void)
{
2008-05-23 21:48:08 +02:00
eval_f(tmin);
2008-05-23 17:29:09 +02:00
p1 = pop();
if (!istensor(p1)) {
tmin = xmin;
tmax = xmax;
}
2004-03-03 21:24:06 +01:00
}
#define N 100
2007-08-04 15:50:16 +02:00
void
create_point_set(void)
2004-03-03 21:24:06 +01:00
{
int i, n;
double t;
draw_count = 0;
for (i = 0; i <= N; i++) {
t = tmin + ((double) i / (double) N) * (tmax - tmin);
new_point(t);
}
n = draw_count;
for (i = 0; i < n - 1; i++)
fill(i, i + 1, 0);
}
2007-08-04 01:27:52 +02:00
void
2004-03-03 21:24:06 +01:00
new_point(double t)
{
double x, y;
if (draw_count >= YMAX)
return;
draw_buf[draw_count].x = -10000;
draw_buf[draw_count].y = -10000;
draw_buf[draw_count].t = t;
draw_count++;
2008-05-23 21:48:08 +02:00
get_xy(t);
2004-03-03 21:24:06 +01:00
if (!isnum(XT) || !isnum(YT))
return;
push(XT);
x = pop_double();
x = (x - xmin) / (xmax - xmin);
x = (double) DIM * x + 0.5; // map 0-1 to 0-DIM, +0.5 so draw(x^3) looks right
push(YT);
y = pop_double();
y = (y - ymin) / (ymax - ymin);
y = (double) DIM * y + 0.5; // map 0-1 to 0-DIM, +0.5 so draw(x^3) looks right
if (x < -10000.0)
x = -10000.0;
if (x > 10000.0)
x = 10000.0;
if (y < -10000.0)
y = -10000.0;
if (y > 10000.0)
y = 10000.0;
draw_buf[draw_count - 1].x = (int) x;
draw_buf[draw_count - 1].y = (int) y;
}
2008-05-23 21:48:08 +02:00
// Evaluate F(t) and return in XT and YT.
2007-08-04 01:27:52 +02:00
void
2008-05-23 21:48:08 +02:00
get_xy(double t)
{
eval_f(t);
p1 = pop();
if (istensor(p1)) {
if (p1->u.tensor->nelem >= 2) {
XT = p1->u.tensor->elem[0];
YT = p1->u.tensor->elem[1];
} else {
XT = symbol(NIL);
YT = symbol(NIL);
}
return;
}
push_double(t);
XT = pop();
YT = p1;
}
// Evaluate F(t) without stopping due to an error such as divide by zero.
void
eval_f(double t)
2004-03-03 21:24:06 +01:00
{
2007-08-20 04:16:50 +02:00
// These must be volatile or it crashes. (Compiler error?)
2007-08-04 15:50:16 +02:00
// Read it backwards, save_tos is a volatile int, etc.
2004-03-03 21:24:06 +01:00
2007-08-20 04:16:50 +02:00
int volatile save_tos;
U ** volatile save_frame;
2004-03-03 21:24:06 +01:00
2007-08-04 15:50:16 +02:00
save();
2007-08-20 04:16:50 +02:00
save_tos = tos;
save_frame = frame;
2008-05-23 21:48:08 +02:00
2007-08-04 01:27:52 +02:00
draw_flag++;
if (setjmp(draw_stop_return)) {
2004-03-03 21:24:06 +01:00
tos = save_tos;
2008-05-23 21:48:08 +02:00
push(symbol(NIL));
2004-03-03 21:24:06 +01:00
frame = save_frame;
restore();
2007-08-04 01:27:52 +02:00
draw_flag--;
2004-03-03 21:24:06 +01:00
return;
}
push_double(t);
2008-05-23 17:29:09 +02:00
p1 = pop();
set_binding(T, p1);
push(F);
2006-10-06 20:28:26 +02:00
eval();
2008-05-18 21:34:03 +02:00
yyfloat();
2008-05-18 22:06:07 +02:00
eval();
2004-03-03 21:24:06 +01:00
restore();
2007-08-04 01:27:52 +02:00
draw_flag--;
2004-03-03 21:24:06 +01:00
}
#define MAX_DEPTH 6
2007-08-04 15:50:16 +02:00
void
2004-03-03 21:24:06 +01:00
fill(int i, int k, int level)
{
int dx, dy, j;
double t;
if (level >= MAX_DEPTH || draw_count >= YMAX)
return;
dx = abs(draw_buf[i].x - draw_buf[k].x);
dy = abs(draw_buf[i].y - draw_buf[k].y);
if (dx < 1 && dy < 1)
return;
t = (draw_buf[i].t + draw_buf[k].t) / 2.0;
j = draw_count;
new_point(t);
fill(i, j, level + 1);
fill(j, k, level + 1);
}
//-----------------------------------------------------------------------------
//
// Normalize x to [0,1]
//
// Example: xmin = -10, xmax = 10, xmax - xmin = 20
//
// x x - xmin (x - xmin) / (xmax - xmin)
//
// -10 0 0.00
//
// -5 5 0.25
//
// 0 10 0.50
//
// 5 15 0.75
//
// 10 20 1.00
//
//-----------------------------------------------------------------------------
2007-08-04 15:50:16 +02:00
void
2004-03-03 21:24:06 +01:00
setup_trange(void)
{
save();
setup_trange_f();
restore();
}
2007-08-04 15:50:16 +02:00
void
2004-03-03 21:24:06 +01:00
setup_trange_f(void)
{
// default range is (-pi, pi)
tmin = -M_PI;
tmax = M_PI;
2005-08-07 16:42:42 +02:00
p1 = usr_symbol("trange");
2004-03-03 21:24:06 +01:00
2005-08-06 22:57:37 +02:00
if (!issymbol(p1))
2004-03-03 21:24:06 +01:00
return;
2007-07-21 21:53:56 +02:00
p1 = get_binding(p1);
2004-03-03 21:24:06 +01:00
// must be two element vector
2005-08-06 22:57:37 +02:00
if (!istensor(p1) || p1->u.tensor->ndim != 1 || p1->u.tensor->nelem != 2)
2004-03-03 21:24:06 +01:00
return;
push(p1->u.tensor->elem[0]);
eval();
2008-05-18 21:34:03 +02:00
yyfloat();
2008-05-18 22:06:07 +02:00
eval();
2004-03-03 21:24:06 +01:00
p2 = pop();
push(p1->u.tensor->elem[1]);
eval();
2008-05-18 21:34:03 +02:00
yyfloat();
2008-05-18 22:06:07 +02:00
eval();
2004-03-03 21:24:06 +01:00
p3 = pop();
if (!isnum(p2) || !isnum(p3))
return;
push(p2);
tmin = pop_double();
push(p3);
tmax = pop_double();
if (tmin == tmax)
stop("draw: trange is zero");
}
2007-08-04 15:50:16 +02:00
void
2004-03-03 21:24:06 +01:00
setup_xrange(void)
{
save();
setup_xrange_f();
restore();
}
2007-08-04 15:50:16 +02:00
void
2004-03-03 21:24:06 +01:00
setup_xrange_f(void)
{
// default range is (-10,10)
xmin = -10.0;
xmax = 10.0;
2005-08-07 16:42:42 +02:00
p1 = usr_symbol("xrange");
2004-03-03 21:24:06 +01:00
2005-08-06 22:57:37 +02:00
if (!issymbol(p1))
2004-03-03 21:24:06 +01:00
return;
2007-07-21 21:53:56 +02:00
p1 = get_binding(p1);
2004-03-03 21:24:06 +01:00
// must be two element vector
2005-08-06 22:57:37 +02:00
if (!istensor(p1) || p1->u.tensor->ndim != 1 || p1->u.tensor->nelem != 2)
2004-03-03 21:24:06 +01:00
return;
push(p1->u.tensor->elem[0]);
eval();
2008-05-18 21:34:03 +02:00
yyfloat();
2008-05-18 22:06:07 +02:00
eval();
2004-03-03 21:24:06 +01:00
p2 = pop();
push(p1->u.tensor->elem[1]);
eval();
2008-05-18 21:34:03 +02:00
yyfloat();
2008-05-18 22:06:07 +02:00
eval();
2004-03-03 21:24:06 +01:00
p3 = pop();
if (!isnum(p2) || !isnum(p3))
return;
push(p2);
xmin = pop_double();
push(p3);
xmax = pop_double();
if (xmin == xmax)
stop("draw: xrange is zero");
}
//-----------------------------------------------------------------------------
//
// Example: yrange=(-10,10)
//
// y d v (vertical pixel coordinate)
//
// 10 0.00 0
//
// 5 0.25 100
//
// 0 0.50 200
//
// -5 0.75 300
//
// -10 1.00 400
//
// We have
//
// d = (10 - y) / 20
//
// = (B - y) / (B - A)
//
// where yrange=(A,B)
//
// To convert d to v, multiply by N where N = 400.
//
//-----------------------------------------------------------------------------
2007-08-04 15:50:16 +02:00
void
2004-03-03 21:24:06 +01:00
setup_yrange(void)
{
save();
setup_yrange_f();
restore();
}
2007-08-04 15:50:16 +02:00
void
2004-03-03 21:24:06 +01:00
setup_yrange_f(void)
{
// default range is (-10,10)
ymin = -10.0;
ymax = 10.0;
2005-08-07 16:42:42 +02:00
p1 = usr_symbol("yrange");
2004-03-03 21:24:06 +01:00
2005-08-06 22:57:37 +02:00
if (!issymbol(p1))
2004-03-03 21:24:06 +01:00
return;
2007-07-21 21:53:56 +02:00
p1 = get_binding(p1);
2004-03-03 21:24:06 +01:00
// must be two element vector
2005-08-06 22:57:37 +02:00
if (!istensor(p1) || p1->u.tensor->ndim != 1 || p1->u.tensor->nelem != 2)
2004-03-03 21:24:06 +01:00
return;
push(p1->u.tensor->elem[0]);
eval();
2008-05-18 21:34:03 +02:00
yyfloat();
2008-05-18 22:06:07 +02:00
eval();
2004-03-03 21:24:06 +01:00
p2 = pop();
push(p1->u.tensor->elem[1]);
eval();
2008-05-18 21:34:03 +02:00
yyfloat();
2008-05-18 22:06:07 +02:00
eval();
2004-03-03 21:24:06 +01:00
p3 = pop();
if (!isnum(p2) || !isnum(p3))
return;
push(p2);
ymin = pop_double();
push(p3);
ymax = pop_double();
if (ymin == ymax)
stop("draw: yrange is zero");
}
#define XOFF 0
#define YOFF 0
#define SHIM 10
static int k;
static unsigned char *buf;
static void emit_box(void);
static void emit_xaxis(void);
static void emit_yaxis(void);
static void emit_xscale(void);
static void emit_yscale(void);
static void emit_xzero(void);
static void emit_yzero(void);
static void get_xzero(void);
static void get_yzero(void);
static int xzero, yzero;
2007-08-04 15:50:16 +02:00
void
2004-03-03 21:24:06 +01:00
emit_graph(void)
{
int h, i, len, x, y;
get_xzero();
get_yzero();
len = 1000 + 5 * draw_count;
buf = (unsigned char *) malloc(len);
h = DIM + SHIM + text_metric[SMALL_FONT].ascent + text_metric[SMALL_FONT].descent;
//buf[0] = (unsigned char) (h >> 8);
//buf[1] = (unsigned char) h;
//buf[2] = (unsigned char) (DIM >> 8);
//buf[3] = (unsigned char) DIM;
k = 0;
emit_box();
emit_xaxis();
emit_yaxis();
emit_xscale();
emit_yscale();
emit_xzero();
emit_yzero();
for (i = 0; i < draw_count; i++) {
x = draw_buf[i].x;
y = DIM - draw_buf[i].y; // flip the y coordinate
2006-10-12 17:57:20 +02:00
if (x < 0 || x > DIM)
2004-03-03 21:24:06 +01:00
continue;
2006-10-12 17:57:20 +02:00
if (y < 0 || y > DIM)
2004-03-03 21:24:06 +01:00
continue;
x += XOFF;
y += YOFF;
buf[k++] = DRAW_POINT;
buf[k++] = (unsigned char) (x >> 8);
buf[k++] = (unsigned char) x;
buf[k++] = (unsigned char) (y >> 8);
buf[k++] = (unsigned char) y;
}
buf[k++] = 0;
shipout(buf, DIM + 1, h);
}
static void
get_xzero(void)
{
double x;
x = -((double) DIM) * xmin / (xmax - xmin) + 0.5;
if (x < -10000.0)
x = -10000.0;
if (x > 10000.0)
x = 10000.0;
xzero = (int) x;
}
static void
get_yzero(void)
{
double y;
y = -((double) DIM) * ymin / (ymax - ymin) + 0.5;
if (y < -10000.0)
y = -10000.0;
if (y > 10000.0)
y = 10000.0;
yzero = DIM - (int) y; // flip the y coordinate
}
static void
emit_box(void)
{
int x, y;
buf[k++] = DRAW_BOX;
x = XOFF;
y = YOFF;
buf[k++] = (unsigned char) (x >> 8);
buf[k++] = (unsigned char) x;
buf[k++] = (unsigned char) (y >> 8);
buf[k++] = (unsigned char) y;
x = XOFF + DIM;
y = YOFF + DIM;
buf[k++] = (unsigned char) (x >> 8);
buf[k++] = (unsigned char) x;
buf[k++] = (unsigned char) (y >> 8);
buf[k++] = (unsigned char) y;
}
static void
emit_xaxis(void)
{
int x, y;
if (yzero < 0 || yzero > DIM)
return;
buf[k++] = DRAW_LINE;
x = XOFF;
y = YOFF + yzero;
buf[k++] = (unsigned char) (x >> 8);
buf[k++] = (unsigned char) x;
buf[k++] = (unsigned char) (y >> 8);
buf[k++] = (unsigned char) y;
x = XOFF + DIM;
y = YOFF + yzero;
buf[k++] = (unsigned char) (x >> 8);
buf[k++] = (unsigned char) x;
buf[k++] = (unsigned char) (y >> 8);
buf[k++] = (unsigned char) y;
}
static void
emit_yaxis(void)
{
int x, y;
if (xzero < 0 || xzero > DIM)
return;
buf[k++] = DRAW_LINE;
x = XOFF + xzero;
y = YOFF;
buf[k++] = (unsigned char) (x >> 8);
buf[k++] = (unsigned char) x;
buf[k++] = (unsigned char) (y >> 8);
buf[k++] = (unsigned char) y;
x = XOFF + xzero;
y = YOFF + DIM;
buf[k++] = (unsigned char) (x >> 8);
buf[k++] = (unsigned char) x;
buf[k++] = (unsigned char) (y >> 8);
buf[k++] = (unsigned char) y;
}
static void emit_xscale_f(int, char *);
static void
emit_xscale(void)
{
static char s[100];
sprintf(s, "%g", xmin);
emit_xscale_f(0, s);
sprintf(s, "%g", xmax);
emit_xscale_f(DIM, s);
}
static void
emit_xscale_f(int xx, char *s)
{
int d, i, len, w, x, y;
// want to center the number w/o sign
w = text_width(SMALL_FONT, s);
if (*s == '-')
d = w - text_width(SMALL_FONT, s + 1);
else
d = 0;
x = XOFF + xx - (w - d) / 2 - d;
y = YOFF + DIM + SHIM;
buf[k++] = SMALL_FONT;
buf[k++] = (unsigned char) (x >> 8);
buf[k++] = (unsigned char) x;
buf[k++] = (unsigned char) (y >> 8);
buf[k++] = (unsigned char) y;
len = (int) strlen(s);
buf[k++] = (unsigned char) len;
for (i = 0; i < len; i++)
buf[k++] = (unsigned char) s[i];
}
static void emit_yscale_f(int, char *);
static void
emit_yscale(void)
{
static char s[100];
sprintf(s, "%g", ymax);
emit_yscale_f(0, s);
sprintf(s, "%g", ymin);
emit_yscale_f(DIM, s);
}
static void
emit_yscale_f(int yy, char *s)
{
int i, len, w, x, y;
w = text_width(SMALL_FONT, s);
x = XOFF - SHIM - w;
y = YOFF + yy - text_metric[SMALL_FONT].ascent / 2;
buf[k++] = SMALL_FONT;
buf[k++] = (unsigned char) (x >> 8);
buf[k++] = (unsigned char) x;
buf[k++] = (unsigned char) (y >> 8);
buf[k++] = (unsigned char) y;
len = (int) strlen(s);
buf[k++] = (unsigned char) len;
for (i = 0; i < len; i++)
buf[k++] = (unsigned char) s[i];
}
// emit the '0' axis label
// make sure it doesn't hit the other labels
static void
emit_xzero(void)
{
int x, y;
if (xzero < DIM / 4 || xzero > 3 * DIM / 4)
return;
x = XOFF + xzero - text_width(SMALL_FONT, "0") / 2;
y = YOFF + DIM + SHIM;
buf[k++] = SMALL_FONT;
buf[k++] = (unsigned char) (x >> 8);
buf[k++] = (unsigned char) x;
buf[k++] = (unsigned char) (y >> 8);
buf[k++] = (unsigned char) y;
buf[k++] = 1;
buf[k++] = '0';
}
// emit the '0' axis label
// make sure it doesn't hit the other labels
static void
emit_yzero(void)
{
int x, y;
if (yzero < DIM / 4 || yzero > 3 * DIM / 4)
return;
x = XOFF - SHIM - text_width(SMALL_FONT, "0");
y = YOFF + yzero - text_metric[SMALL_FONT].ascent / 2;
buf[k++] = SMALL_FONT;
buf[k++] = (unsigned char) (x >> 8);
buf[k++] = (unsigned char) x;
buf[k++] = (unsigned char) (y >> 8);
buf[k++] = (unsigned char) y;
buf[k++] = 1;
buf[k++] = '0';
}