|
25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more... |
Home Projects Up Sitemap Search Blog Forum+Chat About Us Privacy Terms of Use Feedback FAQ Images Services Ads Donate Humor |
|
|
wshowfs.cpp - Transform setsThis page shows additional support code for the WShowfs Iterated Function Set fractal display program that didn't fit on the project's main web page. The first listing shows code for the STransform class, which holds a single transform, and for STransformSet, which contains an array of STransforms and has the routines for managing them as a set. These two classes were intended to be platform independent, and I think they turned out that way. Also shown is the .RC file that defines the Windows resources for the program, and a library module that pulls other needed routines into the program. That listing has links to other site pages containing additional code required for this large project. At the bottom of this page are screenshots of the program's dialog boxes. |
|
/* transfrm.h 12-14-01
This file is part of the WSHOWFS project.
Copyright (C)1995-1999, 2001 Steven Whitney.
Initially published by http://25yearsofprogramming.com.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
----
Declaration for the STransform class, which holds a single transformation that will be
part of an IFS/PFS set.
----
IsDirty and its usage looks good, but has more uses. watch for problems.
a transform can be dirty if pgm had to modify it to make it usable.
a set can be dirty if user edited its text, or it resulted from a merger,
OR if any transform is dirty.
dirtyflag and IsDirty() encode all current reasons.
some dirty levels can be ignored when file is discarded,
others (USEREDIT), user MUST ok discarding the changes.
some (IFS2PFS) are useful at different times, such as: if user wants to save an IFS AS PFS,
you must know if it's possible.
See OWL TBitSet for possible use here, managing the bits.
NOTDIRTY
USEREDITED
INPUTADJUSTED most sets from disk have this (int to decimal)
IFS2PFS
ANGLESADJUSTED trivial
MERGEDSET
*/
#ifndef __TRANSFRM_H
#define __TRANSFRM_H
#if defined(_Windows)
#include <owl\owlpch.h>
#include <mmsystem.h>
#endif
#include <classlib\arrays.h>
#include <math.h>
#include <float.h> // _fpreset()
#include <dir.h> // MAXPATH
#pragma hdrstop
#include "c:\bcs\my.h"
// bit definitions for IsDirty flags
// set at Set level
#define NOTDIRTY 0 // you should SET to this.
#define USEREDITED 1 // you should |= the rest to turn ON
#define MERGEDSET 2 // &= ~xxx to turn OFF
// set at Transform level
#define INPUTADJUSTED 4
#define IFS2PFS 8
#define ANGLESADJUSTED 16
#define TRANSFORMFLAGS (INPUTADJUSTED | IFS2PFS | ANGLESADJUSTED)
#define MAXFILECHARS 2000 // maximum allowed size of an IFS/PFS file
//////////////////////////////////////////////////////////////////////////////
// STransform holds a single transformation that will be part of an IFS/PFS set.
// It can be created with either random or specified parameters.
//----------------------------------------------------------------------------
class STransform
{
public:
STransform(BOOL xysamerotation = TRUE, BOOL xysamescale = FALSE); // default random
// either PFS or IFS
STransform(double xs, double ys, double xr, double yr, double xt, double yt,
double pr, BOOL polarflag);
STransform(const STransform&); // copy constructor
STransform(STransform*, STransform*, int mutationrate); // mating constructor
BOOL operator == (const STransform&) const;
friend istream& operator >> (istream&, STransform&); // read
friend ostream& operator << (ostream&, STransform&); // write
void apply(double& x, double& y); // calculate new points
void mutate(BOOL changeall = FALSE);
BOOL polar; // polar (PFS) data is available
BOOL RoundedOutput; // formatting flag for <<
int prob; // 0-100% chance of being chosen
int IsDirty; // 0=FALSE, TRUE=bit-coded
protected:
void initrandompolar(BOOL,BOOL); // initialize with random polar values
void calcrectangular(); // calculate rectangular values from polar
BOOL calcpolar();
void Normalize(); // adjust input values into usable range
double xscale, yscale; // scaling : -1.00 to +1.00 (-100% to +100%)
double xrot, yrot; // rotation : 0 to 359.99... degrees
double xtrans, ytrans; // translation: -100 to 100 units
// rectangular factors for equations: (much faster for calculations)
// newx = a * oldx + b * oldy + xtrans
// newy = c * oldx + d * oldy + ytrans
double a, b, c, d;
};
//----------------------------------------------------------------------------
// end class STransform
//////////////////////////////////////////////////////////////////////////////
// An STransformSet contains an array of Transforms and routines for managing them.
//
// This must never contain any code that requires a window: platform independence,
// plus you should be able to create a set when no window is valid.
//
// the basic document format is text format because any text is legal, and thus editable.
// IF it describes a calculatable, viewable fractal, then the set is also loaded for display.
// If it doesn't, the set remains empty, which is a flag that something is wrong.
//
// You could refuse to disk-save a set that isn't valid.
// Bif, Henon, and maybe others, also use iteration of a given function.
// Consider pulling commmon functions and variables out into a base class for use by all.
// and/or allowing the function to be provided at run time, instead of being hard-wired.
// how does Fractint do it?
class STransformSet : public TArrayAsVector<STransform>
{
public:
STransformSet(); // constructor
~STransformSet(); // destructor
friend istream& operator >> (istream& is, STransformSet& s); // read
friend ostream& operator << (ostream& os, const STransformSet& s); // write
// overloaded array functions
void Flush(string errormsg = "");
// functions
int SetRandomTransformCount(int newcount);
int GetRandomTransformCount() { return(numtransforms); }
const string& GetLastErrorText() { return(lasterrortext); }
const string& GetFileName() { return(filesource); }
string GetDirtyText(int signif); // text desc of changed flags
long id() { return idno; }
BOOL ReadFile(const char* filename, BOOL append = FALSE);
BOOL WriteFile(const char* filename);
BOOL NewRandomSet(BOOL xysamerotation = random(2), BOOL xysamescale = random(2));
int CalcNext(); // calculate the next point in the series
int loadfromtext(); // try to load set from filechars buffer
void calcxyrange(); // use min,max to calculate xrange,yrange
BOOL XYIsOutOfBounds(); // current x,y is outside the precomputed minx, etc values
int Append(const STransformSet& other); // append the other's transforms
int CreateFrom(const STransformSet& first, const STransformSet& second,
BOOL cull = TRUE, BOOL mutate = FALSE);
// pointers
// other variables
char filechars[MAXFILECHARS]; // holds the transform set in text format
// an array, not ptr, so sizeof() can be used.
BOOL polar; // true if all transforms contain polar data.
int LastSelection; // index of transform used for the last transformation
double x, y; // screen coordinates (currently plotted point)
double minx, maxx, miny, maxy; // boundaries of the design as computed raw
double xrange, yrange; // used in fitting image to screen
BOOL TEditControlOutput; // if TRUE, operator << uses \r\n instead of endl
int dirtyflag; // composite for IsDirty()
// 0 if nothing in set has been changed in any way.
// bitmapped if the underlying text in filechars has changed
// or if any other modifications.
// set internally, but also available for outside use;
// you can set as circumstances require.
// #error it should be protected.
int IsDirty(); // recompile and report. always use this for inquiries.
protected:
// functions
int selecttransform(); // use transform's weights to select one of them
int calcextent(); // calc set's approx min and max raw x,y values
int totprob(); // total of probabilities, needed by >>
void AdjustProbabilities(); // assign equally to total 100, ONLY if they don't already
BOOL writetotext(); // write set to filechars buffer
// variables
int numtransforms; // # of transforms in random sets, if 0, that's random, too
string filesource; // filename of the current set, or EMPTY if random
string lasterrortext; // last error for retrieval, keeps target independence
string comment; // any leftover trailing text not used while loading the set
// needed for when pgm writes the result of a conversion
// to a new file. i.e. it can write the transforms,
// but must have the comment available to write at the end,
// esp. if it contains any unused transforms (which
// unfortunately will have escaped the conversion).
long idno; // unique id# (and count) for random sets
static long nextid; // next set number to be assigned
};
#endif // __TRANSFRM_H
/* transfrm.cpp 12-11-01
This file is part of the WSHOWFS project.
Copyright (C)1995-2001 Steven Whitney.
Initially published by http://25yearsofprogramming.com.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Code for the STransform class, which holds a single transformation that will be
part of an IFS/PFS set.
*/
#include "transfrm.h"
//////////////////////////////////////////////////////////////////////////////
// STransform holds a single transformation that will be part of an IFS/PFS set.
//----------------------------------------------------------------------------
// constructor with random variables
// the equivalent to the old autorand defaults is xysamerotation = TRUE, xysamescale = FALSE
STransform::STransform(BOOL xysamerotation, BOOL xysamescale)
{
RoundedOutput = FALSE;
IsDirty = NOTDIRTY;
initrandompolar(xysamerotation, xysamescale);
} // random constructor
//----------------------------------------------------------------------------
// constructor for POLAR (PFS) or rectangular values (IFS)
STransform::STransform(double xs, double ys, double xr, double yr, double xt, double yt,
double pr, BOOL polarflag)
{
polar = polarflag;
RoundedOutput = FALSE;
IsDirty = NOTDIRTY;
// pr comes in as a double so we can interpret it, but prob is used internally as an int
// to allow faster integer math when selecting a transform.
// This means the smallest assignable probability is 1%, which could be a problem in huge sets.
// adjust so prob is an integer from 0 to 100 inclusive.
// if pr == 1.0, it means 1%. Only pr == 100 can mean prob = 100%.
pr = fabs(pr); // prevent neg.
if(pr < 1.0)
pr = pr * 100 + .5; // round because decimal portion truncated on conversion to int
prob = pr;
// whether polar or not, Normalize will find the proper values in the proper locations
// to operate on.
a = xscale = xs;
b = yscale = ys;
c = xrot = xr;
d = yrot = yr;
// same whether polar or not, and won't get adjusted because they have no standard range.
xtrans = xt;
ytrans = yt;
Normalize();
} //constructor
//--------------------------------------------------------------------------
// mating constructor, mate 2 transforms to create a new one
// selects each variable from one parent or the other
// may turn out not that useful, or at least not all that's needed.
// This creates a new SINGLE transform from 2 existing ones. You're more likely
// to want to create a new SET using whole transforms pulled from an existing pool.
// (untested and not yet used)
STransform::STransform(STransform* first, STransform* second, int mutationrate)
{
RoundedOutput = FALSE;
IsDirty = NOTDIRTY; // new one is NOT, even if either parent was
if(!first || !second) // one or both pointers is null
{
if(!first && !second) // both null. random values at least
{ // guarantee a valid transform
initrandompolar(FALSE,FALSE);
}
else // only one is null. use the valid one as both parents
{
if(first)
second = first;
else
first = second;
}
}
if(first && second) // if there was at least 1 parent (now 2)
{ // (if no parents, all these variables were already set above)
polar = first->polar && second->polar; // polar (PFS) data is available from both
if(polar)
{
xscale = (random(2) ? first->xscale : second->xscale);
yscale = (random(2) ? first->yscale : second->yscale);
xrot = (random(2) ? first->xrot : second->xrot);
yrot = (random(2) ? first->yrot : second->yrot);
xtrans = (random(2) ? first->xtrans : second->xtrans);
ytrans = (random(2) ? first->ytrans : second->ytrans);
calcrectangular();
}
else
{
a = (random(2) ? first->a : second->a);
b = (random(2) ? first->b : second->b);
c = (random(2) ? first->c : second->c);
d = (random(2) ? first->d : second->d);
xtrans = (random(2) ? first->xtrans : second->xtrans);
ytrans = (random(2) ? first->ytrans : second->ytrans);
calcpolar();
}
}
if(!random(mutationrate)) // introduce a random mutation
{
mutate(FALSE);
}
prob = 100; // to be useful in a multi-set, must change after creation
} // mating constructor
//--------------------------------------------------------------------------
// mutate one or all of the constants by giving them random values.
// changeall may be useless
void STransform::mutate(BOOL changeall)
{
if(!polar) // pointless to mutate IFS
return;
int settables = 6; // # of settable members, for easy changing
for(int i = 0 ; i < settables ; i++)
{
// if setting all, do them in order. otherwise choose one at random.
int choice = (changeall ? i : random(settables));
switch(choice)
{
// when mutating, xysame values are ignored, assumed FALSE.
case 0: xscale = (random(201) - 100) / 100.; break;
case 1: yscale = (random(201) - 100) / 100.; break;
case 2: xrot = random(360); break;
case 3: yrot = random(360); break;
case 4: xtrans = random(201)-100; break;
case 5: ytrans = random(201)-100; break;
}
if(!changeall) // if only changing one, quit after first pass
break;
}
calcrectangular();
} // mutate
//----------------------------------------------------------------------------
// copy constructor
STransform::STransform(const STransform& other)
{
xscale = other.xscale;
yscale = other.yscale;
xrot = other.xrot;
yrot = other.yrot;
xtrans = other.xtrans;
ytrans = other.ytrans;
polar = other.polar;
a = other.a;
b = other.b;
c = other.c;
d = other.d;
prob = other.prob;
RoundedOutput = other.RoundedOutput;
IsDirty = other.IsDirty; // ??
} // copy constructor
//----------------------------------------------------------------------------
// initialize with random polar values
// it now allows scaling -100 to 100. This makes infinitely expanding sets possible
// IF the net xtrans and ytrans values in the SET don't cancel each other out to <= 0.
// e.g. if scaling is 100% and xtrans is 10, the magnitude of the x values will grow
// without limit unless other xtrans values total -10 to cancel the growth.
void STransform::initrandompolar(BOOL xysamerotation, BOOL xysamescale)
{
// do not use mutate(changeall = TRUE); it's different
polar = TRUE;
// xscale = (random(199) - 99) / 100.; // -99 to 99 (percent)
xscale = (random(201) - 100) / 100.; // -100 to 100 (percent)
yscale = xysamescale ? xscale : (random(201) - 100) / 100.;
xrot = random(360);
yrot = xysamerotation ? xrot : random(360);
xtrans = random(201)-100; // -100 to +100
ytrans = random(201)-100; // represents percent of whole design
calcrectangular();
prob = 100; // to be useful in a multi-set, must change after creation
} // initrandompolar
//----------------------------------------------------------------------------
// adjust input values into usable range. required by a ctor and op >>.
// you must already have set polar.
// #error interpretation of the input values allows files with nonstandard
// formats to work, but there is currently no provision for writing the adjusted values
// INTO a new standard format file.
// the current file standard is:
// xs ys xr yr xtrans ytrans prob
// 0. to 1.0 0 to 359.9999 deg any values, int 0-100
// or 0. to 1.0 (IFS) prefer 1.0 = 100% of whole
void STransform::Normalize()
{
// prob adjustment is trivial here because its member is an int. you must adjust
// it from double wherever you encounter it in that form.
prob = max(prob,1); // disallow 0
if(polar)
{
// scaling of 1.0 is legal in both systems (double 0-1) and (int 0-100).
// if either xscale,yscale is in higher range, assume they both are.
// interpret 1,1 as 1% each. only 100,100 can mean 100% scaling.
if((fabs(xscale) > 1) || (fabs(yscale) > 1))
{
xscale /= 100;
yscale /= 100;
IsDirty |= INPUTADJUSTED;
}
// angle adj not really required. negatives and out-of-range angles do work.
while(xrot >= 360.) { xrot -= 360.; IsDirty |= ANGLESADJUSTED; }
while(yrot >= 360.) { yrot -= 360.; IsDirty |= ANGLESADJUSTED; }
while(xrot <= -360.) { xrot += 360.; IsDirty |= ANGLESADJUSTED; }
while(yrot <= -360.) { yrot += 360.; IsDirty |= ANGLESADJUSTED; }
// keep negatives: easier to work with in the files
// while(xrot < 0.) { xrot += 360.; IsDirty |= ANGLESADJUSTED; }
// while(yrot < 0.) { yrot += 360.; IsDirty |= ANGLESADJUSTED; }
calcrectangular();
}
else
{
// if any of a,b,c,d is in higher range, assume they all are.
if((fabs(a) > 1) || (fabs(b) > 1) || (fabs(c) > 1) || (fabs(d) > 1))
{
a /= 100;
b /= 100;
c /= 100;
d /= 100;
IsDirty |= INPUTADJUSTED;
}
calcpolar();
}
} //Normalize
//----------------------------------------------------------------------------
// calculate the rectangular values from the polar ones
void STransform::calcrectangular()
{
a = xscale * cosine(xrot); // convert to rectangular
b = -yscale * sine(yrot); // because it's faster
c = xscale * sine(xrot); // for calculations.
d = yscale * cosine(yrot);
}
//----------------------------------------------------------------------------
// determines tolerance for considering two doubles equal during IFS-PFS conversion.
BOOL closeto(double a, double b)
{
return (fabs(a - b) < .0001) ? TRUE : FALSE;
}
//----------------------------------------------------------------------------
// try to calculate the polar values from the rectangular ones
// see paper notes for additional detail and calcs.
BOOL STransform::calcpolar()
{
// need to do each of these calcs twice, for the + and - sqrts? But this works pretty well.
xscale = sqrt(a * a + c * c);
if(xscale == 0) // both a and c are zero, and xrot is unknowable,
xrot = 0; // so give it a value
else
if(c == 0) // c is 0, a is NOT, so sin(xrot) is 0,
xrot = 0; // and xrot is 0 or 180.
else
if(a == 0) // a is 0, c is NOT, so cos(xrot) is 0,
xrot = 90; // and xrot is 90 or 270.
else // else xrot is something normal, and calculable...
{
xrot = acos(a / xscale) * 180 / M_PI; // get as degrees
// there are 2 possibile angles for each acos; the function only returns
// the one between 0 and 180, and we might need the other.
// if a/c is positive, cos(xrot)/sin(xrot) also is,
// which tells us which quadrant the angle really should be in.
switch((int)signum(a / c))
{
// a/c is positive, but xrot is in quadrant II.
// flip down to quadrant III
case 1:
if((xrot >= 90) && (xrot < 180))
xrot = 360 - xrot;
break;
// a/c is negative, but xrot is in quadrant I.
// flip down to quadrant IV
case -1:
if((xrot > 0) && (xrot < 90))
xrot = 360 - xrot;
break;
default:
break;
}
}
yscale = sqrt(b * b + d * d);
if(yscale == 0)
yrot = 0;
else
if(b == 0)
yrot = 0;
else
if(d == 0)
yrot = 90;
else
{
yrot = acos(d / yscale) * 180 / M_PI; // get degrees
// if b/d is positive, cos(xrot)/sin(xrot) is negative,
// which tells us which quadrant the angle really should be in.
switch((int)signum(b / d))
{
case -1:
if((yrot >= 90) && (yrot < 180))
yrot = 360 - yrot;
break;
case 1:
if((yrot > 0) && (yrot < 90))
yrot = 360 - yrot;
break;
default:
break;
}
}
// see if the numbers we got can reproduce the original set:
// new rectangular variables
double a2 = (xscale * cosine(xrot));
double b2 = (-yscale * sine(yrot));
double c2 = (xscale * sine(xrot));
double d2 = (yscale * cosine(yrot));
if(closeto(a,a2) && closeto(b,b2) && closeto(c,c2) && closeto(d,d2))
{
IsDirty |= IFS2PFS;
return(TRUE);
}
else
{
return(FALSE);
}
} //calcpolar
//----------------------------------------------------------------------------
// comparing 2 different ones may be useful. don't use &other == this.
BOOL STransform::operator == (const STransform& r) const
{
// test may be obsolete if types are interchangeable
if(r.polar != polar)
return(FALSE);
// all sets have rectangular values
return( (r.a == a) && (r.b == b) && (r.c == c) && (r.d == d) &&
(r.xtrans == xtrans) && (r.ytrans == ytrans)
// && (r.prob == prob) // ignore probability
);
}
//----------------------------------------------------------------------------
// write formatted transform set to an ostream, without trailing newline.
// you can supply either endl or /r/n, depending on need.
ostream& operator << (ostream& os, STransform& r)
{
if(r.RoundedOutput)
{
if(r.polar) // polar data is available
{
// #error setw() doesn't force rounding to nearest int, find what does.
os << setw(3) << (r.xscale * 100.) << " "
<< setw(3) << (r.yscale * 100.) << " "
<< setw(3) << (r.xrot) << " "
<< setw(3) << (r.yrot) << " "
<< setw(4) << (r.xtrans) << " "
<< setw(4) << (r.ytrans) << " "
<< setw(3) << (r.prob);
}
else // IFS file
{
// IFS data could only have come from a file to begin with,
// but remember this is for screen output, too, so keep it.
os << setw(4) << (r.a * 100.) << " "
<< setw(4) << (r.b * 100.) << " "
<< setw(4) << (r.c * 100.) << " "
<< setw(4) << (r.d * 100.) << " "
<< setw(4) << (r.xtrans) << " "
<< setw(4) << (r.ytrans) << " "
<< setw(3) << (r.prob);
}
}
else // normal, not rounded, output
{
if(r.polar) // polar data is available
{
os << (r.xscale) << " "
<< (r.yscale) << " "
<< (r.xrot) << " "
<< (r.yrot) << " "
<< (r.xtrans) << " "
<< (r.ytrans) << " "
<< (r.prob);
}
else // IFS
{
os << (r.a) << " "
<< (r.b) << " "
<< (r.c) << " "
<< (r.d) << " "
<< (r.xtrans) << " "
<< (r.ytrans) << " "
<< (r.prob);
}
}
// flag stays in effect for only 1 call
r.RoundedOutput = FALSE;
return(os);
} //operator <<
//----------------------------------------------------------------------------
// read from a stream. you must preset s.polar BEFORE calling this, so it
// knows how to interpret the values read.
// unused and untested
istream& operator >> (istream& is, STransform& s)
{
s.RoundedOutput = FALSE;
s.IsDirty = NOTDIRTY;
double pr;
is >> s.xscale >> s.yscale >> s.xrot >> s.yrot >> s.xtrans >> s.ytrans >> pr;
// ensure Normalize finds the proper values in the proper locations to operate on.
s.a = s.xscale;
s.b = s.yscale;
s.c = s.xrot;
s.d = s.yrot;
// pr comes in as a double so we can interpret it, but prob is used internally as an int
// to allow faster integer math when selecting a transform.
// This means the smallest assignable probability is 1%, which shouldn't be a problem.
// adjust so prob is an integer from 0 to 100 inclusive.
// if pr == 1.0, it means 1%. Only pr == 100 can mean prob = 100%.
pr = fabs(pr); // prevent neg.
if(pr < 1.0)
pr = pr * 100 + .5; // round for when
s.prob = pr; // decimal portion truncated on conversion to int
s.Normalize();
return(is);
} // operator >>
//----------------------------------------------------------------------------
// apply this transformation to a point (x,y)
void STransform::apply(double& x, double& y)
{
double newx = (a * x) + (b * y) + xtrans;
double newy = (c * x) + (d * y) + ytrans;
x = newx; // reference variables ARE changed,
y = newy; // replacing old values.
}
//----------------------------------------------------------------------------
// end class STransform
//////////////////////////////////////////////////////////////////////////////
// STransformSet contains an array of Transforms and routines for managing them.
//----------------------------------------------------------------------------
// initialize static members
long STransformSet::nextid = 1L;
//----------------------------------------------------------------------------
// constructor
STransformSet::STransformSet() : TArrayAsVector<STransform>(5,0,5)
{
filechars[0] = 0;
idno = nextid; // only incremented for random sets, in NewRandomSet
Flush(); // already empty, but it initializes several members
LastSelection = 0; // although even 0 is meaningless here
numtransforms = 0; // if 0, random sets have random number of transforms
dirtyflag = NOTDIRTY;
TEditControlOutput = FALSE; // see operator <<
} // constructor
//----------------------------------------------------------------------------
// destructor
STransformSet::~STransformSet()
{
Flush();
} // destructor
//----------------------------------------------------------------------------
// output the transform set to any ostream
// set TEditControlOutput before each call.
// this could always write from filechars. (?)
// but check where used: for screen display, you need the even reformatting this provides.
ostream& operator << (ostream& os, STransformSet& s)
{
int count = s.GetItemsInContainer();
if(count) // if none, write nothing
{
os << count;
if(s.TEditControlOutput) // choose a terminator
os << "\r";
os << endl;
for(int i = 0 ; i < count ; i++) // write each transform
{
s[i].RoundedOutput = TRUE; // for now only: pending file format change
os << s[i]; // write the transform
if(s.TEditControlOutput) // choose a terminator
os << "\r";
os << endl;
}
}
return os;
} // operator <<
//----------------------------------------------------------------------------
// read and load from any stream EXCEPT filechars, (which has its own function to do so).
// be sure to set ALL members, even those not read from the stream.
// BEFORE CALLING THIS, YOU MUST HAVE DETERMINED IF POLAR OR NOT (CAN USE FILEEXT),
// AND SET POLAR ACCORDINGLY.
// PROBABLY SHOULD PUT THAT INFO IN THE FILES.
// do NOT use it to append multiple files: it overwrites filechars.
// you can never append, anyway, because comments would get sandwiched between #s.
istream& operator >> (istream& is, STransformSet& s)
{
// read INTO the text buffer
// #error you should test-read it into a string, if the string length > sizeof(filechars),
// delete[] filechars and reallocate a sufficient array. then re-read into it.
// see editview.cpp for tellg() usage for file size
is.get(s.filechars,sizeof(s.filechars),'\0');
s.dirtyflag = NOTDIRTY; // loaded the text, so it is a fresh set
// then try to load the set FROM the buffer
s.loadfromtext(); // might become dirty here
// stream will always return as failed, since the whole file is read,
// which means that a single stream can only contain a single transform SET.
// ok for now, since it always does.
return(is);
} // operator >>
//----------------------------------------------------------------------------
// try to load set from filechars buffer
// you must already have set polar appropriately for this set.
int STransformSet::loadfromtext()
{
Flush();
istrstream is(filechars);
int count;
double a, b, c, d, e, f, g;
is >> count; // transform count
for(int i = 0 ; i < count ; i++)
{
// use the new STransform >>. or not; maybe this is better.
is >> a >> b >> c >> d >> e >> f >> g;
Add(STransform(a,b,c,d,e,f,g,polar));
}
if(!is)
Flush("Text is corrupt, does not define a valid transform set.");
comment.remove(0); // delete any old text
comment.read_file(is); // everything left is a comment
calcextent(); // also flushes if error
return GetItemsInContainer();
} //loadfromtext
//----------------------------------------------------------------------------
// write set to filechars buffer
// ONLY a brand new set can do this. Once you've loaded from disk, the PROGRAM can't
// change filechars without user OKing math adjustments in the SET.
BOOL STransformSet::writetotext()
{
TEditControlOutput = TRUE;
ostrstream(filechars,sizeof(filechars)) << *this << ends;
return TRUE;
} //writetotext
//----------------------------------------------------------------------------
// empty set, and ensure data members have reasonable values even though the set is invalid.
// e.g. if accidentally called upon to calculate when empty, the result will be
// a repeated single point at the center of the screen.
// this is essentially a reset function.
// always make sure all data members that need resetting are reset here.
void STransformSet::Flush(string errormsg)
{
TArrayAsVector<STransform>::Flush();
dirtyflag &= ~TRANSFORMFLAGS; // transforms start clean even if set isn't
if(errormsg.length())
lasterrortext = errormsg;
// else // if emptying, any old error messages are outdated
// lasterrortext = ""; // but check carefully before enabling.
x = y = 2;
minx = miny = 0;
maxx = maxy = 4;
xrange = yrange = 4;
// polar = TRUE; // left as a reminder that you must NOT reset polar here.
// or IFS file is interpreted as PFS.
} // Flush
//----------------------------------------------------------------------------
// set number of random transforms to use when generating random sets,
// IF the requested value is acceptable.
int STransformSet::SetRandomTransformCount(int newcount)
{
// 1-transform sets can cause fatal math errors
if((newcount > -1) && (newcount != 1) && (newcount < 21))
numtransforms = newcount;
return(numtransforms);
} //SetRandomTransformCount
//----------------------------------------------------------------------------
// returns text desc of changed flags, for insertion into a string destined for a msgbox.
// signif is bitmapped like dirtyflag, indicating the types of changes you want to
// be informed about. you should only inform user about the changes that will be
// made permanent by the actual save being contemplated: most other than USEREDITED
// will only be made permanent if you write from the ARRAY to disk (i.e.
// saving PFS as new IFS file or vice versa, which is not currently implemented, anyway).
// minimal, unused, untested
string STransformSet::GetDirtyText(int signif)
{
string s;
signif &= IsDirty(); // leaves on only bits we care about
if(signif & USEREDITED) s += "USEREDITED\n";
if(signif & INPUTADJUSTED) s += "INPUTADJUSTED\n";
if(signif & IFS2PFS) s += "IFS2PFS\n";
if(signif & ANGLESADJUSTED) s += "ANGLESADJUSTED\n";
if(signif & MERGEDSET) s += "MERGEDSET\n";
return s;
} //GetDirtyText
//----------------------------------------------------------------------------
// current x,y is outside the precomputed minx, etc values
BOOL STransformSet::XYIsOutOfBounds()
{
if((x < minx) || (x > maxx) || (y < miny) || (y > maxy))
return TRUE;
return FALSE;
}
//----------------------------------------------------------------------------
// select one of the transforms, taking into account each's probability of being chosen
// returns the index of the transform used, or -1 if set is empty.
int STransformSet::selecttransform()
{
int k = GetItemsInContainer();
if(!k)
return(-1);
// start with random 0-99. Must be 99 so a 1-transform set (100%) will cause r < 0 below.
// also 0-99 = 100 possible numbers.
int r = random(100);
for(LastSelection = 0 ; LastSelection < k ; LastSelection++)
{
// subtract each transform's probability. when r < 0, we know "whose" section
// of the interval the original r was in, and thus which transform to use.
r -= (*this)[LastSelection].prob;
if(r < 0)
break;
}
return(LastSelection); // 0 if set is empty (which is an illegal index in that case)
} //selecttransform
//----------------------------------------------------------------------------
// calculate next point in the series begun by calcextent() (i.e. transforms member x,y)
// this is what you should always use:
// 1. this series HAS been brought onto the attractor, while new outside points haven't
// 2. multiple users sharing this set can get identical points, staying coordinated.
// returns the index of the transform used, or -1 if set is empty.
int STransformSet::CalcNext()
{
if(selecttransform() == -1)
return(-1);
(*this)[LastSelection].apply(x,y);
return(LastSelection);
} //CalcNext
//----------------------------------------------------------------------------
// sum transform probabilities
int STransformSet::totprob()
{
int N = GetItemsInContainer();
int cumprob = 0;
for(int i = 0 ; i < N ; i++)
cumprob += (*this)[i].prob;
return(cumprob);
} //totprob
//----------------------------------------------------------------------------
// readjust so tot is 100, only if necessary.
// a legal set whose transforms have unequal weights won't be changed.
void STransformSet::AdjustProbabilities()
{
if(totprob() == 100) // ok
return;
int N = GetItemsInContainer(); // not ok. apportion probability equally.
int probability = 100./N;
int cumprob = 0;
for(int i = 0 ; i < N ; i++)
{
// if last transform, give it all the leftover so set total = exactly 100%
if(i == (N - 1))
probability = 100 - cumprob;
(*this)[i].prob = probability;
(*this)[i].IsDirty |= INPUTADJUSTED;
cumprob += probability;
}
} //AdjustProbabilities
//----------------------------------------------------------------------------
// tabulate whether any transform(s) in set is/are dirty, and reasons why.
int STransformSet::IsDirty()
{
int N = GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
dirtyflag |= (*this)[i].IsDirty;
return dirtyflag;
} //IsDirty
//----------------------------------------------------------------------------
// #error maybe could use an overflow catcher, but wait for any errors to occur.
void STransformSet::calcxyrange()
{
xrange = maxx - minx;
yrange = maxy - miny;
} //calcxyrange
//----------------------------------------------------------------------------
// calculate enough undrawn points to determine the size of the design:
// the largest and smallest RAW x and y values that the design produces:
// NOTE: This only gives you an idea of the likely size, provided a roughly equal mix
// of transform selection. But if one of the transforms *can* run to infinity, then a
// very long series of points will eventually produce a run where that one transform is
// chosen so many times in a row that it DOES.
// returns GetItemsInContainer(). if 0: not initialized or numbers got too large
// this is essentially a validator, and could be appropriately renamed.
int STransformSet::calcextent()
{
if(IsEmpty()) // container must be filled
return(0);
AdjustProbabilities(); // ensure they total 100, if they don't already
long throwout = 20L; // points merely bring near attractor
long nonpts = throwout + 20000L; // additional nondisplayed, for setting screen margins.
// was 5000 for a long time, but now need a more accurate
// estimate because while drawing, any point outside
// the min, max values aborts immediately.
// when a new design is started, these are the very first x,y in the series
x = y = 2.; // was 0, but 0 and 1 can behave strangely: 0*x=0, 1*x=x.
// 1-transform requires preset window because it will go to either a single value
// or infinity in only a few points, so you can't calculate unplotted points.
// however, because you can't scale and screen them, they also can cause
// fatal math errors, so they're prohibited when setting transform count.
// to reinstate them, there has to be some way to check them. should be
// possible, since DOS version worked.
// #error for 1-transform sets, calculate anyway. on overflow, just quit calculating.
// set ranges, etc.
// then, before returning, reset x = y = 2 so the series you just calculated
// is reused instead of continued. use a bright color (or suggest in Help to do so)
// so drawing shows up (they draw and autoabort quickly).
if(GetItemsInContainer() == 1)
{
minx = miny = -30.;
maxx = maxy = 30.;
return(1);
}
// everything after this is only for multi-transform sets
minx = miny = MAXDOUBLE; // init. screen limits
maxx = maxy = -MAXDOUBLE;
for(long n = 0L ; n < nonpts ; n++)
{
if(CalcNext() == -1) // select a transform & calculate new point
{
Flush("Current set is empty.");
break;
}
// overflow test: if SIGFPE problems persist, reduce limits until it prevents them.
// Help|Runtime Errors says underflows are converted to zero.
if((fabs(x) > 1e50) || (fabs(y) > 1e50))
{
Flush("Current set would cause math errors.");
break;
}
if(n <= throwout) // just loop if working on throwouts
continue;
minx = min(minx,x); // else tally the maximum and minimum
maxx = max(maxx,x); // values seen so far.
miny = min(miny,y);
maxy = max(maxy,y);
}
// compute ranges and bounding values. ok to do even if set was flushed.
calcxyrange();
if((xrange == 0) || (yrange == 0)) // would cause divide by zero later
Flush("Current set would cause divide by zero.");
else
{
minx -= (.10 * xrange); // enlarge window
maxx += (.10 * xrange); // by 20%
miny -= (.10 * yrange); // in each axis
maxy += (.10 * yrange);
calcxyrange(); // recalculate for the new size
}
return(GetItemsInContainer());
} // calcextent
//----------------------------------------------------------------------------
// Initialize a random transform set.
// all transforms in the new set will have the same xysame.. values.
// you must check IsDirty before calling.
BOOL STransformSet::NewRandomSet(BOOL xysamerotation, BOOL xysamescale)
{
int num = numtransforms;
if(!num) // zero means also use a random number of transforms
num = 2 + random(2); // 2 or 3 work best, 4 sometimes ok
filesource = ""; // reset. it's not from a file.
polar = TRUE;
do // keep trying until you get a set that doesn't cause SIGFPE
{
Flush(); // no harm if already empty
for(int i = 0 ; i < num ; i++) // create the transforms
Add(STransform(xysamerotation,xysamescale));
}
while(!calcextent()); // returns 0 if set causes math overflow
idno = nextid++;
writetotext(); // copy new set to the text buffer
dirtyflag = NOTDIRTY; // a random contents set is considered expendable
return(TRUE);
} // NewRandomSet
//----------------------------------------------------------------------------
// Initialize a new set, read from a file.
// returns TRUE if successful. FALSE if not, and puts reason in lasterrortext.
BOOL STransformSet::ReadFile(const char* filename, BOOL append)
{
if(!append) // reading new file, start fresh
Flush();
string buf(filename); // create string containing file name
buf.to_upper(); // toupper for error messages
if(buf.contains(".PFS")) // make sure it's an IFS or PFS file
{
if(append && GetItemsInContainer() && !polar)
{
lasterrortext = buf + "\nCannot merge .PFS file with existing .IFS set";
return(FALSE);
}
polar = TRUE;
}
else
if(buf.contains(".IFS"))
{
// you really can merge them: all sets have IFS data, so set polar=FALSE,
// and only the IFS portion of the existing set will be used.
// But discourage use of IFS.
if(append && GetItemsInContainer() && polar)
{
lasterrortext = buf + "\nCannot merge .IFS file with existing .PFS set";
return(FALSE);
}
polar = FALSE;
}
else
{
lasterrortext = buf + "\nFile extension must be .IFS or .PFS";
return(FALSE);
}
// binary for reading into string buf for TEdit, so CRs from file not stripped.
ifstream infile(buf.c_str(),ios::binary);
if(!infile)
{
lasterrortext = "File\n" + buf + "\nnot found.";
return(FALSE);
}
infile >> *this; // read into filechars AND try to load the set array
// also sets dirtyflag NOTDIRTY
if(GetItemsInContainer())
{
if(append)
filesource = ""; // not file-based anymore until resaved
else
filesource = buf; // everything succeeded, so this is now our file
}
else // several possible errors didn't have filename available, so add it
lasterrortext.prepend(to_upper(string(filename)) + "\n");
return(GetItemsInContainer() ? TRUE : FALSE);
} // ReadFile
//----------------------------------------------------------------------------
// write set to a file.
// returns TRUE if successful. FALSE if not, and puts reason in lasterrortext.
// #error should check filename for IFS/PFS and enforce match between ext and polar setting.
// actually, it looks like both IFS and PFS will be available for all files,
// so you can choose write format based on file ext.
// polar may merely become an indicator of which MODE the user wants to interact
// with the file in.
BOOL STransformSet::WriteFile(const char* filename)
{
string outfilename = filename;
outfilename.to_upper();
BOOL writepolar; // for choosing which format to write: not yet used
if(outfilename.contains(".PFS")) // make sure it's IFS or PFS
writepolar = TRUE;
else
if(outfilename.contains(".IFS"))
writepolar = FALSE;
else
{
lasterrortext = outfilename +
"\nFile extension must be .IFS or .PFS, to specify format.";
return(FALSE);
}
// #error unfinished: decide WHAT to write based on format chosen and what's available.
// if chosen format is DIFFERENT from the format in filechars, you must write
// the array in the chosen format, then the comment, then read back IN. ?
// write from filechars so comments are preserved. (filechars, not the array, IS the document.)
// binary so an extra CR is not added.
// (whether from TEdit or originally from file, each LF already has a CR)
ofstream(filename,ios::binary) << filechars;
filesource = outfilename; // file is now whatever name we saved it AS
filesource.to_upper();
dirtyflag &= ~USEREDITED; // not NOTDIRTY, just not edited anymore.
return(TRUE);
} //WriteFile
//----------------------------------------------------------------------------
// append the other's transforms.
// returns the number added.
int STransformSet::Append(const STransformSet& other)
{
int N = other.GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
{
Add(other[i]);
// questionable? for SOME uses, an appended set might be dirty,
// for random merges, it can be reset to NOTDIRTY (as it is in CreateFrom)
dirtyflag |= MERGEDSET;
}
return N;
} //Append
//----------------------------------------------------------------------------
// mate: merge 2 given sets into a composite, then optionally randomly cull,
// because large sets are seldom very interesting.
// returns GetItemsInContainer(), which is 0 on failure.
// if it fails, you CAN try again, since each try culls different transforms,
// and one might succeed.
int STransformSet::CreateFrom(const STransformSet& first,const STransformSet& second,
BOOL cull, BOOL mutate)
{
Flush();
Append(first);
Append(second);
if(cull && (GetItemsInContainer() > 2)) // minimum 2
{
int delcount = random(GetItemsInContainer() - 1); // preserves at least 2
for(int i = 0 ; i < delcount ; i++)
Destroy(random(GetItemsInContainer())); // destroy at randomly chosen index
}
// can only mutate a transform if there are any
if(mutate && GetItemsInContainer())
(*this)[random(GetItemsInContainer())].mutate();
calcextent();
writetotext(); // copy new set to the text buffer
filesource = ""; // reset. it's not from a file.
dirtyflag = NOTDIRTY; // merged set is considered fresh, random, expendable
// (overrides default Append setting)
idno = nextid++;
return GetItemsInContainer();
} //CreateFrom
//----------------------------------------------------------------------------
// end class STransformSet
//////////////////////////////////////////////////////////////////////////////
/* library.cpp 12-14-01 This file is part of the WSHOWFS project. Copyright (C)1996-2001 Steven Whitney. Initially published by http://25yearsofprogramming.com. Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY. Library file for wshowfs.ide that pulls into the project the various library routines needed. This is a separate project node in the IDE. */ #include <owl\owlpch.h> #include <owl\chooseco.h> #include <owl\opensave.h> #include <owl\radiobut.h> #include <owl\edit.h> #include <owl\statusba.h> #include <owl\controlb.h> #include <owl\inputdia.h> #include <mmsystem.h> #include <classlib\arrays.h> #include <math.h> #include <float.h> // _fpreset() #include <dir.h> // MAXPATH #pragma hdrstop #include "c:\bcs\library\sdibwin.h" #include "c:\bcs\my.h" #include "c:\bcs\mylib.cpp" #include "c:\bcs\library\filearay.cpp" #include "c:\bcs\library\sdib.cpp" #include "c:\bcs\library\doubrect.cpp" #include "c:\bcs\library\stopwatc.cpp" #include "wshowfs.rh" //////////////////////////////////////////////////////////////////////////////
/****************************************************************************
WSHOWFS.RC
This file is part of the WSHOWFS project.
Copyright (C)1996 Steven Whitney.
Initially published by http://25yearsofprogramming.com.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
produced by Borland Resource Workshop
*****************************************************************************/
#include "wshowfs.rh"
OPTIONSDIALOG DIALOG 72, 49, 206, 169
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Fractal Generator Options"
FONT 8, "MS Sans Serif"
{
AUTOCHECKBOX "&1 Show Equations", IDC_SHOWEQUATS, 6, 9, 96, 12, BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP
AUTOCHECKBOX "&2 Use Sound (Beep)", IDC_BEEPON, 6, 21, 99, 12, BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP
AUTOCHECKBOX "&3 Autoabort Tests", IDC_AUTOABORTTESTS, 6, 33, 96, 12, BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP
AUTOCHECKBOX "&4 Upside Down", IDC_UPSDWN, 6, 45, 93, 12, BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP
AUTOCHECKBOX "&5 AutoRandomize Colors", IDC_AUTORANDCOLORS, 6, 57, 102, 12, BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP
AUTOCHECKBOX "&6 XY Same Rotation", IDC_XYSAMEROT, 6, 69, 102, 12, BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP
AUTOCHECKBOX "&7 XY Same Scale", IDC_XYSAMESCALE, 6, 81, 99, 12, BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP
AUTORADIOBUTTON "&Number of hits", IDC_COLORBYHITS, 119, 23, 60, 12, BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP
AUTORADIOBUTTON "Transform Number", IDC_COLORBYTRANSFORM, 119, 35, 78, 12
AUTORADIOBUTTON "Monochrome", IDC_COLORMONOCHROME, 119, 47, 60, 12
LTEXT "# &Transforms:", -1, 117, 75, 48, 12
EDITTEXT IDC_NUMTRANSFORMS, 168, 75, 18, 12, WS_BORDER | WS_GROUP | WS_TABSTOP
LTEXT "&Duplicate Limit:", -1, 111, 93, 60, 12
EDITTEXT IDC_DUPLIMIT, 168, 91, 30, 12, WS_BORDER | WS_GROUP | WS_TABSTOP
DEFPUSHBUTTON "OK", IDOK, 27, 145, 50, 14, BS_DEFPUSHBUTTON | WS_GROUP | WS_TABSTOP
PUSHBUTTON "Cancel", IDCANCEL, 81, 145, 50, 14, WS_GROUP | WS_TABSTOP
PUSHBUTTON "Help", IDHELP, 135, 145, 50, 14, WS_GROUP | WS_TABSTOP
GROUPBOX "Point Coloring Method:", IDC_DISPMODEBOX, 111, 6, 87, 60, BS_GROUPBOX | WS_GROUP
AUTOCHECKBOX "&8 Show this dialog for each new window", IDC_VIEWOPTIONSATSTARTUP, 6, 108, 192, 12, BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP
AUTOCHECKBOX "&9 Mutate transforms when mating", IDC_MUTATEONMATE, 6, 120, 159, 12, BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP
}
TRANSETEDITOR DIALOG 193, 80, 186, 173
STYLE WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
CAPTION "Transform Set Editor"
FONT 10, "courier new"
{
EDITTEXT IDC_TRANSETEDIT, 3, 20, 180, 150, ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP
DEFPUSHBUTTON "OK", IDOK, 3, 3, 30, 14
PUSHBUTTON "Cancel", IDCANCEL, 37, 3, 30, 14
PUSHBUTTON "Help", IDHELP, 71, 3, 30, 14
}
MENU_1 MENU
{
POPUP "&File"
{
MENUITEM "&Load (list)...\tL", CM_FILELOAD
MENUITEM "Edit L&ist...", CM_FILEEDITLIST
MENUITEM "Show Nex&t in List, or Random\tN", CM_FILENEXT
MENUITEM SEPARATOR
MENUITEM "Create &New", CM_FILENEW
MENUITEM "&Open Existing...\tCtrl+O", CM_FILEOPEN
MENUITEM "&Save as .PFS or .IFS...\tCtrl+S", CM_FILESAVE
MENUITEM "Save as .&BMP (or .PIC)...", CM_FILESAVEAS
MENUITEM SEPARATOR
MENUITEM "&Edit current set as text\t<Enter>", CM_FILEEDIT
MENUITEM SEPARATOR
MENUITEM "E&xit", CM_FILEEXIT
}
POPUP "&Colors"
{
MENUITEM "Load .&MAP File...\tM", CM_COLORSLOAD
MENUITEM "Save&As .MAP File...", CM_COLORSSAVEAS
MENUITEM SEPARATOR
MENUITEM "&Next .MAP File\t]", CM_COLORSNEXTMAP
MENUITEM "&Previous .MAP File\t[", CM_COLORSPREVIOUSMAP
MENUITEM "&Edit .MAP File List...", CM_COLORSEDITLIST
MENUITEM SEPARATOR
MENUITEM "&Randomize\tR", CM_VIEWRANDOMIZECOLORS
MENUITEM "Re&verse\tV", CM_COLORSREVERSE
MENUITEM "&Complement\tC", CM_COLORSCOMPLEMENT
MENUITEM "&Lighten\tCtrl+L", CM_COLORSLIGHTEN
MENUITEM "&Darken\tCtrl+D", CM_COLORSDARKEN
MENUITEM SEPARATOR
MENUITEM "Rotate &Forward\t>", CM_COLORSROTATEFORWARD
MENUITEM "Rotate &Backward\t<", CM_COLORSROTATEBACKWARD
}
POPUP "&View"
{
MENUITEM "&Options (all)...\tO", CM_VIEWOPTIONS
MENUITEM SEPARATOR
MENUITEM "&Draw continuously (all windows)\tD", CM_VIEWDRAW, CHECKED
MENUITEM "Show &Equations\tE", CM_VIEWSHOWEQUATIONS, CHECKED
MENUITEM "&Upside Down\tU", CM_VIEWUPSIDEDOWN
MENUITEM "Auto-abort &Tests\tT", CM_VIEWAUTOABORTTESTS, CHECKED
MENUITEM "&AutoRandomizeColors\tA", CM_VIEWAUTORANDOMIZECOLORS, CHECKED
MENUITEM "Auto&RotateColors\t= or +", CM_VIEWAUTOROTATECOLORS
MENUITEM SEPARATOR
MENUITEM "Show &Number of points (once)\tCtrl+N", CM_VIEWNUMBEROFPOINTS
}
POPUP "&Genetics"
{
MENUITEM "Select As &Parent\tP", CM_SELECTASPARENT
MENUITEM "&Zero parents (Reset)\tZ", CM_ZEROPARENTS
}
POPUP "&Window"
{
MENUITEM "&Cascade", CM_CASCADECHILDREN
MENUITEM "&Tile", CM_TILECHILDREN
MENUITEM "Arrange &Icons", CM_ARRANGEICONS
MENUITEM "C&lose All", CM_CLOSECHILDREN
}
POPUP "&Help"
{
MENUITEM "&Index\tF1", CM_HELPINDEX
MENUITEM SEPARATOR
MENUITEM "&About...", CM_HELPABOUT
}
}
STRINGTABLE
{
CM_FILENEXT, "Display next file in this window's queue, or a new random design if queue is empty"
CM_FILELOAD, "Add files to this window's queue, for SEQEUENTIAL display with each File|Next"
CM_FILESAVE, "Save current set to disk"
CM_FILEEDIT, "Edit this window's current transform set in an editor window"
CM_FILENEW, "New RANDOM fractal in a NEW window"
CM_FILEOPEN, "Open one or more files, each in a NEW window"
CM_VIEWDRAW, "Toggles automatic drawing (affects all windows)"
CM_VIEWOPTIONS, "Set viewing and calculation options"
CM_VIEWRANDOMIZECOLORS, "Randomize the color table once"
CM_VIEWSHOWEQUATIONS, "Toggle showing equations in display window"
CM_VIEWAUTOABORTTESTS, "Turn auto-abort tests ON/OFF"
CM_VIEWNUMBEROFPOINTS, "Show point count for this fractal so far"
CM_VIEWUPSIDEDOWN, "Turn this fractal upside down"
CM_VIEWAUTORANDOMIZECOLORS, "Randomize colors before each new display"
CM_CASCADECHILDREN, "Cascade open windows"
CM_TILECHILDREN, "Tile open windows vertically"
CM_ARRANGEICONS, "Arrange iconic windows along bottom"
CM_CLOSECHILDREN, "Close all open windows"
CM_HELPINDEX, "Go to Help | Contents page"
CM_HELPABOUT, "Information about this application"
CM_SELECTASPARENT, "Select the current window as one of 2 parents"
CM_ZEROPARENTS, "Reset to: No parents designated, and restore arrow cursor"
}
MENU_1 ACCELERATORS
{
"n", CM_FILENEXT
"N", CM_FILENEXT
"^O", CM_FILEOPEN
"^S", CM_FILESAVE
"s", CM_FILESAVE
"S", CM_FILESAVE
"l", CM_FILELOAD
"L", CM_FILELOAD
VK_RETURN, CM_FILEEDIT, VIRTKEY
"o", CM_VIEWOPTIONS
"O", CM_VIEWOPTIONS
"d", CM_VIEWDRAW, ASCII
"D", CM_VIEWDRAW, ASCII
"r", CM_VIEWRANDOMIZECOLORS
"R", CM_VIEWRANDOMIZECOLORS
"t", CM_VIEWAUTOABORTTESTS
"T", CM_VIEWAUTOABORTTESTS
"^N", CM_VIEWNUMBEROFPOINTS
"u", CM_VIEWUPSIDEDOWN
"U", CM_VIEWUPSIDEDOWN
"A", CM_VIEWAUTORANDOMIZECOLORS
"a", CM_VIEWAUTORANDOMIZECOLORS
"+", CM_VIEWAUTOROTATECOLORS
"=", CM_VIEWAUTOROTATECOLORS
"E", CM_VIEWSHOWEQUATIONS
"e", CM_VIEWSHOWEQUATIONS
"^L", CM_COLORSLIGHTEN
"^D", CM_COLORSDARKEN
"]", CM_COLORSNEXTMAP
"[", CM_COLORSPREVIOUSMAP
".", CM_COLORSROTATEFORWARD
">", CM_COLORSROTATEFORWARD
",", CM_COLORSROTATEBACKWARD
"<", CM_COLORSROTATEBACKWARD
"m", CM_COLORSLOAD
"M", CM_COLORSLOAD
"v", CM_COLORSREVERSE
"V", CM_COLORSREVERSE
"c", CM_COLORSCOMPLEMENT
"C", CM_COLORSCOMPLEMENT
"p", CM_SELECTASPARENT, ASCII
"P", CM_SELECTASPARENT, ASCII
VK_F1, CM_HELPINDEX, VIRTKEY
"z", CM_ZEROPARENTS, ASCII
"Z", CM_ZEROPARENTS
}
CM_FILENEW BITMAP "new.bmp"
CM_FILEOPEN BITMAP "open.bmp"
CM_FILESAVE BITMAP "save.bmp"
CM_HELPINDEX BITMAP "help.bmp"
CM_FILELOAD BITMAP "fileload.bmp"
CM_FILENEXT BITMAP "filenext.bmp"
CM_SELECTASPARENT BITMAP "selparnt.bmp"
CM_ZEROPARENTS BITMAP "zerparnt.bmp"
CM_TILECHILDREN BITMAP "tilevert.bmp"
1) This is the OPTIONSDIALOG in the .RC file above.

2) This is the TRANSETEDITOR in the .RC file above. Its custom monospaced Courier font makes the numbers align better.

|
|
|
|
|
|