home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fred Fish Collection 1.5
/
ffcollection-1-5-1992-11.iso
/
ff_disks
/
200-299
/
ff280.lzh
/
Graph
/
object
/
f_of_x.c
< prev
next >
Wrap
C/C++ Source or Header
|
1989-11-20
|
15KB
|
481 lines
/*
* GRAPH, Version 1.00 - 4 August 1989
*
* Copyright 1989, David Gay. All Rights Reserved.
* This software is freely redistrubatable.
*/
#include <exec/types.h>
#include <intuition/intuition.h>
#include <graphics/text.h>
#include <math.h>
#include <string.h>
#include "object.h"
#include "object/function.h"
#include "object/default.h"
#include "file.h"
#include "graph.h"
#include "uio.h"
#include "coords.h"
#include "list.h"
#include "grph.h"
#include "user/eval.h"
#include "user/gadgets.h"
#include "tracker.h"
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
/* (private) class f_of_x, inherited from function */
struct f_of_x {
struct function f;
char expr[EXPRLEN]; /* the function */
double oldxmin, oldxmax; /* limits used at last calculation */
int waslog; /* previous x axis type */
value function, derivee; /* the compiled function & its differential */
};
/*-------------------------------------------------------------------------*/
/* f_of_x class implementation */
/*-------------------------------------------------------------------------*/
/* Return TRUE if f is displayable */
static int f_of_x_ok(const struct f_of_x *this)
{
return (this->f.min == NOVAL || this->f.max == NOVAL || this->f.min < this-
>f.max) &&
(this->f.steps == INOVAL || this->f.steps >= 3);
}
/* free resources used by this */
static void destroy_f_of_x(struct f_of_x *this)
{
free_var_list(&this->f.used);
if (this->f.calc) free_list(&this->f.pts, this->f.sizept);
this->f.calc = FALSE;
if (this->function) free_expr(this->function);
if (this->derivee) free_expr(this->derivee);
this->function = this->derivee = NULL;
}
/* Initialise dependent parts of f_of_x */
static int create_f_of_x(struct f_of_x *this)
{
this->f.calc = FALSE;
this->f.var.name = this->f.vname;
this->function = compile(this->expr);
if (eval_error != 0)
{
message(this->f.o.g, "Compilation error:", eval_messages[eval_error], (
char *)NULL);
return FALSE;
}
this->derivee = differentiate(this->function, this->f.vname);
if (eval_error != NOT_DIFFERENTIABLE && eval_error != 0)
{
message(this->f.o.g, "Differentiation error:", eval_messages[eval_error
], (char *)NULL);
return FALSE;
}
if (!make_var_list(this->function, &this->f.used))
init_var_list(&this->f.used);
return TRUE;
}
/* Allow the user to edit this function (ref: area to refresh) */
static int edit_f_of_x(struct f_of_x *this, struct Region **ref)
{
struct Requester *req;
struct Memory *m;
struct Gadget *gl = NULL, *sd, *nd;
char from[NBLEN], to[NBLEN], steps[INTLEN], expr[EXPRLEN], xname[VARLEN], c
olour[INTLEN];
int ret = FALSE;
/* Create requester */
double2str(from, this->f.min);
double2str(to, this->f.max);
int2str(steps, this->f.steps);
int2str(colour, this->f.colour);
strcpy(expr, this->expr);
strcpy(xname, this->f.vname);
*ref = NULL;
if ((m = NewMemory()) &&
(req = InitReq(50, 20, 255, 145, m)) &&
SetReqBorder(req, 1, m) &&
AddIntuiText(&req->ReqText, "Function", 95, 6, m) &&
AddText(&gl, 0, "f(", FALSE, xname, VARLEN, TRUE, 0, RELVERIFY, 25, 20,
25, 10, TRUE, m) &&
AddText(&gl, 0, ")=", FALSE, expr, EXPRLEN, TRUE, 0, RELVERIFY, 81, 20,
160, 10, TRUE, m) &&
AddText(&gl, 0, "from ", FALSE, from, NBLEN, TRUE, 0, RELVERIFY, 49, 40
, 80, 10, TRUE, m) &&
AddText(&gl, 0, "to ", FALSE, to, NBLEN, TRUE, 0, RELVERIFY, 167, 40, 8
0, 10, TRUE, m) &&
AddText(&gl, 0, "steps ", FALSE, steps, INTLEN, TRUE, 0, RELVERIFY, 57,
60, 32, 10, TRUE, m) &&
AddText(&gl, 0, "colour ", FALSE, colour, INTLEN, TRUE, 0, RELVERIFY, 1
56, 60, 32, 10, TRUE, m) &&
(sd = AddOption(&gl, 0, "Show discontinuities", TRUE, this->f.showdisc
* SELECTED, 0, 9, 80, 10, 10, m)) &&
(nd = AddOption(&gl, 0, "Allow flat discontinuities", TRUE, this->f.nic
edisc * SELECTED, 0, 9, 100, 10, 10, m)) &&
AddBox(&gl, TRUE, "Ok", 0, RELVERIFY | ENDGADGET, 40, 120, 65, 15, FALS
E, m) &&
AddBox(&gl, FALSE, "Cancel", 0, RELVERIFY | ENDGADGET, 145, 120, 65, 15
, FALSE, m))
{
SetReqGadgets(req, gl);
if (ret = DoRequest(req, this->f.o.g, std_ghandler))
{
*ref = full_refresh(this->f.o.g); /* Redraw everything */
/* Extract typed info */
this->f.min = str2double(from);
this->f.max = str2double(to);
this->f.steps = str2int(steps);
if ((this->f.colour = str2int(colour)) == INOVAL) this->f.colour =
1;
this->f.showdisc = (sd->Flags & SELECTED) != 0;
this->f.nicedisc = (nd->Flags & SELECTED) != 0;
strcpy(this->expr, expr);
strcpy(this->f.vname, xname);
/* Calc new dependent info */
destroy_f_of_x(this);
if (this->f.o.ok = f_of_x_ok(this)) this->f.o.ok = create_f_of_x(th
is);
}
}
Free(m);
return ret;
}
/* Calculate the points of the function */
static int calc_f_of_x(struct f_of_x *this, int allow_mes)
{
double x;
int i;
struct graph *const g = this->f.o.g;
/* Use graph limits if none given */
double const xmin = this->f.min == NOVAL ? g->a.x.min : this->f.min;
double const xmax = this->f.max == NOVAL ? g->a.x.max : this->f.max;
int const xlog = g->a.x.log;
int const steps = (this->f.steps == INOVAL ? DEFSTEPS : this->f.steps) - 1;
double const step = xlog ? pow(xmax / xmin, 1.0 / steps) : (xmax - xmin) /
steps;
char func[FNAMELEN + 30];
new_list(&this->f.pts);
strcpy(func, "Can't calculate points for ");
strcat(func, this->f.o.name);
strcat(func, ":");
if (xmin >= xmax)
{
if (allow_mes) message(g, func, "xmin >= xmax", (char *)NULL);
else alert(g->io.win, "xmin >= xmax", NULL);
return FALSE;
}
if (!create_quick(&this->f.var))
{
if (allow_mes) message(g, func, "Couldn't create variable", (char *)NUL
L);
else alert(g->io.win, func, "Couldn't create variable");
return FALSE;
}
/* For all steps x values (evenly spaced *on screen*) */
for (i = 0, x = xmin; i <= steps; i++, x = xlog ? x * step : x + step)
{
point *pt = alloc_node(this->f.sizept);
if (!pt)
{ /* No mem */
free_list(&this->f.pts, this->f.sizept);
free_quick(&this->f.var);
if (allow_mes) message(g, func, "No memory", (char *)NULL);
return FALSE;
}
add_tail(&this->f.pts, pt);
pt->x = x;
set_quick(&this->f.var, x);
pt->y = quick_eval(this->function);
pt->state = (eval_error == 0) ? EXISTS : 0;
}
free_quick(&this->f.var);
return TRUE;
}
/* Draw function */
static void draw_f_of_x(struct f_of_x *this, int allow_mes)
{
struct graph *g = this->f.o.g;
/* If xmax or xmin not specified, track values in graph */
/* ==> function may need recalculating */
if (this->f.calc)
if ((this->f.min == NOVAL && this->oldxmin != g->a.x.min) ||
(this->f.max == NOVAL && this->oldxmax != g->a.x.max) ||
this->waslog != g->a.x.log)
{
this->f.calc = FALSE;
free_list(&this->f.pts, this->f.sizept);
}
if (!this->f.calc) this->f.calc = calc_f_of_x(this, allow_mes);
if (this->f.calc)
{
this->oldxmin = g->a.x.min;
this->oldxmax = g->a.x.max;
this->waslog = g->a.x.log;
display_function(&this->f);
}
}
/* Try to improve look of function by adding points. If fails, decides that
there is a discontinuity */
static struct Region *improve_f_of_x(struct f_of_x *this)
{
struct graph *const g = this->f.o.g;
point *pt, *next;
int ok = FALSE, abort = FALSE, iter;
/*what y step constitutes a "flat" segment ? Based on window height, could
be better */
double flat = FLAT * (g->a.y.max - g->a.y.min) / g->io.win->Height;
char msg[FNAMELEN + 30];
char pass[20];
struct Requester *req;
struct Region *full = NULL;
/* Flat has no meaning when graph incorrect */
if (!this->f.o.g->ok) flat = 0.0;
if (!this->f.calc)
{
strcpy(msg, this->f.o.name);
strcpy(msg, "not calculated!");
message(g, msg, (char *)NULL);
return NULL;
}
if (!this->derivee)
{
strcpy(msg, this->f.o.name);
strcat(msg, " wasn't differentiable");
message(g, msg, (char *)NULL);
return NULL;
}
if (!create_quick(&this->f.var))
{
message(g, "Couldn't create variable", (char *)NULL);
return NULL;
}
/* Allow user to abort (can take a very long time !) */
if (!(req = abort_request(g, "Improve: Pass 1")))
message(g, "No Memory !", (char *)NULL);
else
{
/* Whole graph will need redrawing */
full = full_refresh(this->f.o.g);
/* Do MAXITER passes, or until every point ok */
for (iter = 1; iter <= MAXITER && !ok && !abort; iter++)
{
sprintf(pass, "Improve: Pass %d", iter);
set_abort_msg(req, pass);
ok = TRUE; /* True as long as no improvements made this pass */
/* Scan all but last point */
for (pt = first(&this->f.pts); succ(next = succ(pt)); pt = next)
{
if (aborted(req)) { abort = TRUE; break; }
if ((pt->state & (EXISTS | OK)) == EXISTS) /* Only exists. Igno
re points who's segment is "ok" */
{
double dx;
pt->state |= OK;
pt->state &= ~DISC;
/* Idea: check if the differential at this point provides
a good approximation of the next point. If not, add an
extra one.
Bad idea: Use the differential at the mid point of the
segment. Allows "angles" to remain in the output.
Remark: I've tried various other schemes. This one was
the best.
*/
set_quick(&this->f.var, pt->x);
dx = quick_eval(this->derivee);
if (eval_error == 0)
{
double ecart = next->y - pt->y;
/* error: difference between first order taylor approx.
and actual point. */
double error = fabs(ecart - (next->x - pt->x) * dx);
/* Should we add a point ? error compared with
difference (on y axis) between the two points, if
nicedisc, small errors (<2 pixels) are accepted
without this check */
if (error > fabs(ecart) * MAXERROR && (!this->f.nicedis
c || error > flat))
{
/* Add ONE extra point between the two */
pt->state &= ~OK;
ok = FALSE; /* We've added a point */
if (iter == MAXITER) pt->state |= DISC; /* This is
(maybe) a discontinuity */
else /* currently ignores BREAKUP(Extension: add mo
re than one point) */
{
point *newpt = alloc_node(this->f.sizept);
if (!newpt)
{
message(g, "No memory for point !", (char *
)NULL);
abort = TRUE;
break; /* Exit from loop ! */
}
newpt->x = (pt->x + next->x) / 2;
set_quick(&this->f.var, newpt->x);
newpt->y = quick_eval(this->function);
newpt->state = (eval_error == 0) ? EXISTS : 0;
insert(&this->f.pts, newpt, pt);
}
}
}
}
}
}
end_abort_request(req);
}
free_quick(&this->f.var);
return full;
}
/* Provide quick textual form of function */
static char *f2str_f_of_x(struct f_of_x *this, char *buf, int maxlen)
{
buf[maxlen - 1] = '\0';
strncpy(buf, this->f.o.name, maxlen - 1);
strncat(buf, "(", maxlen - strlen(buf) - 1);
strncat(buf, this->f.vname, maxlen - strlen(buf) - 1);
strncat(buf, ")=", maxlen - strlen(buf) - 1);
strncat(buf, this->expr, maxlen - strlen(buf) - 1);
return buf;
}
/* Did user select us ? */
static int down_f_of_x(struct f_of_x *this)
{
struct graph *g = this->f.o.g;
if (this->f.o.ok && this->f.calc) /* visible ? */
{
int inside;
if (!create_quick(&this->f.var))
{
message(g, "Couldn't create variable", (char *)NULL);
return FALSE;
}
set_quick(&this->f.var, g->s.x);
/* Calculate y = f(x click pos) and compare with y click pos */
inside = fabs(g->io.rw->sy(g->io.rw, quick_eval(this->function)) - g->i
o.rw->sy(g->io.rw, g->s.y)) < FDIST &&
eval_error == 0;
free_quick(&this->f.var);
return inside;
}
return FALSE;
}
/* Write f_of_x specific info */
static int save_f_of_x(struct f_of_x *this, FILE *f)
{
short tag = F_OF_X_TAG;
short end = F_OF_X_END;
return WRITE(f, tag) &&
WRITE(f, this->expr) &&
WRITE(f, end);
}
/* Delete a member of class f_of_x */
static struct Region *delete_f_of_x(struct f_of_x *this)
{
struct Region *full = full_refresh(this->f.o.g);
destroy_f_of_x(this);
FreeMem(this, sizeof(struct f_of_x));
return full;
}
/* Create a new instance of f_of_x */
struct f_of_x *new_f_of_x(struct graph *g, char *name)
{
struct f_of_x *this = AllocMem(sizeof(struct f_of_x), MEMF_CLEAR);
if (this)
{
/* Standard init */
init_function(&this->f, g, name);
/* Setup methods */
this->f.save = (void *)save_f_of_x;
this->f.o.delete = (void *)delete_f_of_x;
this->f.o.down = (void *)down_f_of_x;
this->f.o.draw = (void *)draw_f_of_x;
this->f.o.edit = (void *)edit_f_of_x;
this->f.o.improve = (void *)improve_f_of_x;
this->f.o.f2str = (void *)f2str_f_of_x;
this->f.sizept = sizeof(point);
return this;
}
message(g, "Couldn't create function:", "No memory", (char *)NULL);
return NULL;
}
/* Load f_of_x from a file */
struct f_of_x *load_f_of_x(struct graph *g, FILE *f)
{
struct f_of_x *this = new_f_of_x(g, "");
if (this)
{
short end;
if (READ(f, this->expr) &&
READ(f, end) &&
end == F_OF_X_END)
{
/* Load standard part */
load_rest(&this->f, f);
if (this->f.o.ok = f_of_x_ok(this)) this->f.o.ok = create_f_of_x(th
is);
return this;
}
delete_f_of_x(this);
}
return NULL;
}