home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 13 / 13.iso / p / p024 / 12.img / ADS3.LIB / MAGNETS.C < prev    next >
Encoding:
Text File  |  1993-02-17  |  54.7 KB  |  1,539 lines

  1. /* Next available MSG number is 114 */
  2.  
  3. /*    MAGNETS.C
  4.       ¬⌐┼v (C) 1990-1992  Autodesk ñ╜Ñq
  5.  
  6.       Ñ╗│n┼ΘºK╢O¿╤▒z╢iªµÑ⌠ª≤Ñ╬│~╗▌¿D¬║½■¿⌐íB¡╫º∩ñ╬╡oªµ, ª²¼O░╚╜╨┐φ┤`ñU¡z
  7.       ¡∞½h :
  8.  
  9.       1)  ñWªC¬║¬⌐┼v│qºi░╚╗▌ÑX▓{ªb¿Cñ@Ñ≈½■¿⌐∙╪íC
  10.       2)  ¼█├÷¬║╗í⌐·ñσÑ≤ñ]Ñ▓╢╖⌐·╕ⁿ¬⌐┼v│qºiñ╬Ñ╗╢╡│\Ñi│qºiíC
  11.  
  12.       Ñ╗│n┼Θ╢╚┤ú¿╤º@¼░└│Ñ╬ñW¬║░╤ª╥, ª╙Ñ╝┴n⌐·⌐╬┴⌠ºtÑ⌠ª≤½O├╥; ╣∩⌐≤Ñ⌠ª≤»S«φ
  13.       Ñ╬│~ñº╛A║┘⌐╩, ÑHñ╬░╙╖~╛P░Γ⌐╥┴⌠ºtÑX¿π¬║½O├╥, ªbª╣ñ@╖ºñ⌐ÑHº_╗{íC
  14.  
  15.  
  16.  
  17.       DESCRIPTION:
  18.  
  19.  
  20.                   S Q U I R M I N G   M A G N E T S   !!!
  21.  
  22.         Magnetically perturbed pendulum chaos demonstrator for AutoCAD
  23.  
  24.         A sample ADS application.
  25.  
  26.         Designed and implemented by John Walker in October of 1989.
  27.  
  28.         The  physics  of  the  simulation  is  abstract,  not precise.
  29.         First, I use arbitrary units for the mass and strength of  the
  30.         magnets,  chosen so that values in the range from -10 to 10 do
  31.         reasonable things.  Second,  I  treat  the  magnets  as  point
  32.         sources  of  magnetic  field  lines--as  if they were magnetic
  33.         monopoles (or  the  whole  simulation  were  of  electrostatic
  34.         charges  rather than magnets).  Since real magnets always have
  35.         two poles, and since magnets are of substantial size  compared
  36.         to  the  scale of the simulation, there are substantial dipole
  37.         and quadupole moments to the force exerted on the pendulum  in
  38.         the actual apparatus.  None of these considerations are really
  39.         relevant to any of the phenomena  of  attractors,  orbits,  or
  40.         chaotic  processes,  only to the fidelity of the simulation to
  41.         an actual pendulum.
  42.  
  43.         The following commands are implemented in this file:
  44.  
  45.         DEMO        Creates  one  of  a series of standard demo models
  46.                     when  executed  in  a  new,  empty drawing.  These
  47.                     models may be modified with the MAGNET and MAGEDIT
  48.                     commands just like models entered manually.
  49.  
  50.         MAGNET      Creates  a  new  magnet.   You're invited to enter
  51.                     its   position   (by   any  means  of  co-ordinate
  52.                     specification),  and  its  strength  in  arbitrary
  53.                     units.  If the strength is positive, it repels the
  54.                     pendulum; if positive, it attracts it.
  55.  
  56.         MAGEDIT     Lets  you  modify  the  strength  of  an  existing
  57.                     magnet.   Pick  a  single  magnet  by pointing.  A
  58.                     dialogue is displayed with the properties  of  the
  59.                     magnet.   Change them as you wish, then pick OK to
  60.                     update  the  properties  of  the  magnet  in   the
  61.                     database.   If  you pick Cancel, the magnet is not
  62.                     modified.
  63.  
  64.         RESET       When  a  simulation  is  run,  it  halts after the
  65.                     specified number of steps or  simulated  time  has
  66.                     elapsed.    A   subsequent  RUN  command  normally
  67.                     resumes the simulation from the point at which the
  68.                     last  stopped.  RESET erases all motion paths from
  69.                     the screen and sets the  simulation  back  to  the
  70.                     start.    RUN   after   a  RESET  will  begin  the
  71.                     simulation from the initial state.
  72.  
  73.         RUN         Starts the simulation.  You're  asked  to  specify
  74.                     the length of the simulation as either a number of
  75.                     motion   steps  or  by  the  number  of  simulated
  76.                     seconds.  If you enter  a  positive  number,  it's
  77.                     taken  as  a  step count.  Negative numbers (which
  78.                     may include decimal fractions)  specify  simulated
  79.                     time  in  seconds.  You can terminate a simulation
  80.                     with the Control C key.  RUN can also  be  invoked
  81.                     as  a  function,  (C:RUN <length>), where <length>
  82.                     specifies the simulation length by a  positive  or
  83.                     negative  number  as described above.  When called
  84.                     as a function, C:RUN returns  the  simulation  end
  85.                     time as its result.
  86.  
  87.         SETMAG      The  magnets  simulator has several variables that
  88.                     control  its operation.  These variables are saved
  89.                     with the drawing and may be inspected and modified
  90.                     with   the  SETMAG  command.   SETMAG  displays  a
  91.                     dialogue in  which  you  may  change  any  of  the
  92.                     following variables:
  93.  
  94.                        Output to display only?
  95.                           This is set to Yes or  No  (True/False,  and
  96.                           1/0  are  also  accepted).   If  set  to the
  97.                           default value of Yes, motion paths are drawn
  98.                           using  temporary  vectors within the current
  99.                           viewport.  If the picture is  REDRAWn,  they
  100.                           disappear.   If  set to No, motion paths are
  101.                           added to the drawing as LINE entities.  This
  102.                           causes paths to appear in all viewports, and
  103.                           you can  ZOOM  on  sections  of  a  path  to
  104.                           examine   it   in   greater  detail.   Paths
  105.                           represented as  LINEs  are  saved  with  the
  106.                           drawing.  Generating LINE entities for paths
  107.                           is much slower than just drawing them on the
  108.                           screen,  so  choose  this mode only when you
  109.                           need it.
  110.  
  111.                        Display step number?
  112.                           If this mode is  "Yes"  (the  default),  the
  113.                           simulation  step  number is displayed in the
  114.                           coordinate status  line  as  the  simulation
  115.                           progresses.
  116.  
  117.                        Display time?
  118.                           If "Yes" (the default) the simulated time in
  119.                           years is displayed in the coordinate  status
  120.                           line.
  121.  
  122.                        Step size?
  123.                           This  real  number specifies the factor used
  124.                           to determine the  size  of  the  integration
  125.                           steps  used in calculating the motion of the
  126.                           magnets.  The  time  step  is  specified  in
  127.                           terms  of seconds.  The default value of 0.1
  128.                           works  well  for   reasonably   well-behaved
  129.                           simulations.   If you find that a simulation
  130.                           is taking too long, you can speed it  up  by
  131.                           increasing  the step size, but be aware that
  132.                           the results you see may  not  be  physically
  133.                           accurate.     Increasing   the   step   size
  134.                           magnifies the  inaccuracies  of  modeling  a
  135.                           continuous    process   such   as   magnetic
  136.                           attraction by discrete steps.   In  general,
  137.                           you  can  increase  the step size as long as
  138.                           you  obtain  the  same  results.   When  the
  139.                           outcome  begins to vary, you've set the step
  140.                           size too large.
  141.  
  142.                        Dual pendulum offset?
  143.                           To  observe  the  sensitive  dependence   on
  144.                           initial conditions that characterises chaos,
  145.                           you can simulate two pendulums  which  start
  146.                           with  a  small  initial  displacement.   The
  147.                           pendulums do not interact with one  another,
  148.                           but  respond  to  the  magnets placed on the
  149.                           baseplate.  If the offset is zero, a  single
  150.                           pendulum  is  simulated, resulting in faster
  151.                           execution.
  152. */
  153.  
  154. #include   <stdio.h>
  155. #include   <string.h>
  156. #include   <ctype.h>
  157. #include   <math.h>
  158. #include   <assert.h>
  159.  
  160. #include   "adslib.h"
  161.  
  162. /*  Standard drawing object names  */
  163.  
  164. /* Utility frozen layer for information */
  165. #define FrozenLayer /*MSG1*/"FROZEN-SOLID"
  166. #define OrbitLayer  /*MSG2*/"PENDPATH"     /* Layer for pendulum path traces */
  167.  
  168. /*  Data types  */
  169.  
  170. typedef enum {False = 0, True = 1} Boolean;
  171.  
  172. #define HANDLEN  18                   /* String long enough to hold a handle */
  173.  
  174. /* Definitions  to  wrap  around  submission  of  AutoCAD commands to
  175.    prevent their being echoed.  */
  176.  
  177. #define Cmdecho  False                /* Make True for debug command output */
  178.  
  179. #define CommandB()  { struct resbuf rBc, rBb, rBu, rBh; \
  180.         ads_getvar(/*MSG0*/"CMDECHO", &rBc); \
  181.         ads_getvar(/*MSG0*/"BLIPMODE", &rBb); \
  182.         ads_getvar(/*MSG0*/"HIGHLIGHT", &rBh); \
  183.         rBu.restype = RTSHORT; \
  184.         rBu.resval.rint = (int) Cmdecho; \
  185.         ads_setvar(/*MSG0*/"CMDECHO", &rBu); \
  186.         rBu.resval.rint = (int) False; \
  187.         ads_setvar(/*MSG0*/"BLIPMODE", &rBu); \
  188.         ads_setvar(/*MSG0*/"HIGHLIGHT", &rBu)
  189.  
  190. #define CommandE()  ads_setvar(/*MSG0*/"CMDECHO", &rBc); \
  191.                     ads_setvar(/*MSG0*/"BLIPMODE", &rBb); \
  192.                     ads_setvar(/*MSG0*/"HIGHLIGHT", &rBh); }
  193.  
  194. /*  Definitions  that permit you to push and pop system variables with
  195.     minimal complexity.  These don't work  (and  will  cause  horrible
  196.     crashes  if  used)  with  string  variables,  but since all string
  197.     variables are read-only, they cannot be saved and restored in  any
  198.     case.  */
  199.  
  200. #define PushVar(var, cell, newval, newtype) { struct resbuf cell, cNeW; \
  201.         ads_getvar(var, &cell); cNeW.restype = cell.restype;             \
  202.         cNeW.resval.newtype = newval; ads_setvar(var, &cNeW)
  203.  
  204. #define PopVar(var, cell) ads_setvar(var, &cell); }
  205.  
  206. /* Set point variable from three co-ordinates */
  207.  
  208. #define Spoint(pt, x, y, z)  pt[X] = (x);  pt[Y] = (y);  pt[Z] = (z)
  209.  
  210. /* Copy point  */
  211.  
  212. #define Cpoint(d, s)   d[X] = s[X];  d[Y] = s[Y];  d[Z] = s[Z]
  213.  
  214. /* Generation parameters for objects */
  215.  
  216. #define PendLength  20.0              /* Pendulum length */
  217.  
  218. /* Utility definition to get an  array's  element  count  (at  compile
  219.    time).   For  example:
  220.  
  221.        int  arr[] = {1,2,3,4,5};
  222.        ...
  223.        printf("%d", ELEMENTS(arr));
  224.  
  225.    would print a five.  ELEMENTS("abc") can also be used to  tell  how
  226.    many  bytes are in a string constant INCLUDING THE TRAILING NULL. */
  227.  
  228. #define ELEMENTS(array) (sizeof(array)/sizeof((array)[0]))
  229.  
  230. /* Utility definitions */
  231.  
  232. #ifndef abs
  233. #define abs(x)      ((x)<0 ? -(x) : (x))
  234. #endif  /* abs */
  235.  
  236. /*  Forward functions  */
  237.  
  238. void    main _((int, char **));
  239. Boolean funcload _((void));
  240. char    *alloc _((unsigned));
  241. void    defmagblk _((void));
  242. struct resbuf *entitem _((struct resbuf *, int));
  243. void    entinfo _((ads_name, char *, ads_point, ads_real *, int *));
  244. void    partext _((void));
  245. void    sumvec _((ads_real *, ads_real *, ads_real, ads_real *));
  246. void    varblockdef _((void));
  247. void    savemodes _((void));
  248. Boolean boolvar _((char *, char *));
  249. void    varset _((void));
  250. Boolean initacad _((Boolean));
  251. void    addmag _((char *, ads_point, ads_real));
  252. void    magnet _((void));
  253. void    demo _((void));
  254. void    magedit _((void));
  255. void    reset _((void));
  256. void    run _((void));
  257. void    setmag _((void));
  258.  
  259. static Boolean functional;            /* C:command is returning result */
  260. static ads_real numsteps = 1000;      /* Default number of steps to run */
  261. static double simtime = 0.0;          /* Simulated time */
  262. static long stepno = 0;               /* Step number */
  263.  
  264. /*  Command definition and dispatch table.  */
  265.  
  266. struct {
  267.         char *cmdname;
  268.         void (*cmdfunc)();
  269. } cmdtab[] = {
  270. /*        Name         Function  */
  271. {/*MSG3*/"DEMO",       demo},         /* Create demo case */
  272. {/*MSG4*/"MAGNET",     magnet},       /* Create new magnet */
  273. {/*MSG5*/"MAGEDIT",    magedit},      /* Edit existing magnet */
  274. {/*MSG6*/"RESET",      reset},        /* Erase motion paths */
  275. {/*MSG7*/"RUN",        run},          /* Run simulation */
  276. {/*MSG8*/"SETMAG",     setmag}        /* Set mode variables */
  277. };
  278.  
  279. /*  Particle structure.  */
  280.  
  281. typedef struct {
  282.         char partname[32];            /* Particle name */
  283.         ads_point position;           /* Location in space */
  284.         ads_point velocity;           /* Velocity vector */
  285.         ads_point acceleration;       /* Acceleration vector */
  286.         ads_point lastpos;            /* Last plotted position */
  287.         ads_real strength;            /* Magnetic strength */
  288.         ads_real radius;              /* Radius */
  289.         int colour;                   /* Entity colour */
  290.         char partblock[HANDLEN];      /* Block defining particle */
  291. } particle;
  292.  
  293. static particle pt;                   /* Static particle structure */
  294. static particle *ptab = NULL;         /* Particle table */
  295. static int nptab = 0;                 /* Number of in-memory particles */
  296.  
  297. /*  Particle definition block attributes.  */
  298.  
  299. #define ParticleBlock  /*MSG9*/"PARTICLE"  /* Particle block name */
  300. struct {                              /* Attribute tag table */
  301.         char *attname;                /* Attribute tag name */
  302.         ads_real *attvar;             /* Variable address */
  303.         char *attprompt;              /* Prompt for attribute */
  304. } partatt[] = {
  305.         {/*MSG10*/"MAGSTRENGTH", &pt.strength, /*MSG11*/"║╧ñO"}
  306.        };
  307.  
  308. /*  Modal  variables.   Default   initial   values   below   are   for
  309.     documentation  only.   The actual defaults are reset in initacad()
  310.     upon entry to the drawing editor, so that they will be placed with
  311.     the   default  values  in  the  modal  variable  block  if  it  is
  312.     subsequently created for a new drawing.  */
  313.  
  314. static Boolean drawonly = True,       /* DRAWONLY: Draw, but don't add entities
  315.                                                    if 1.  Make LINEs if 0. */
  316.                showstep = True,       /* SHOWSTEP: Show step in status line. */
  317.                showtime = True;       /* SHOWTIME: Show time in status line. */
  318. static ads_real stepsize = 0.1,       /* STEPSIZE: Motion step size factor. */
  319.                 dualpend = 0.0;       /* DUALPEND: Offset of dual pendulum */
  320. static int ntestp = 1;                /* Number of test particles */
  321.  
  322. /*  Modal attribute definition.  */
  323.  
  324. #define ModalBlock  /*MSG12*/"GRAVITY_MODES" /* Mode variable block name */
  325. struct {
  326.         char *attname;                /* Attribute tag name */
  327.         int attvari;                  /* Variable index */
  328.         char *attprompt;              /* Prompt for variable */
  329. } varatt[] = {
  330.         {/*MSG13*/"DRAWONLY", 1, /*MSG14*/"╢╚ñ⌐íu┼πÑ▄ív?           "},
  331.         {/*MSG15*/"SHOWSTEP", 3, /*MSG16*/"┼πÑ▄íu¿B╢i╝╞ív?         "},
  332.         {/*MSG17*/"SHOWTIME", 2, /*MSG18*/"┼πÑ▄íu«╔╢íív?           "},
  333.         {/*MSG19*/"STEPSIZE", 4, /*MSG20*/"¿B╢i╢q?                 "},
  334.         {/*MSG21*/"DUALPEND", 5, /*MSG22*/"┬∙║╧┬\134░╛▓╛?             "}
  335.        };
  336.  
  337. /* MAIN -- the main routine */
  338.  
  339. void main(argc, argv)
  340.   int argc;
  341.   char *argv[];
  342. {
  343.     int stat, cindex, scode = RSRSLT;
  344.  
  345.     ads_init(argc, argv);             /* Initialise the application */
  346.  
  347.     /* Main dispatch loop. */
  348.  
  349.     while (True) {
  350.  
  351.         if ((stat = ads_link(scode)) < 0) {
  352.             printf(/*MSG23*/"MAGNETS: Ñ╤ ads_link() ╢╟ª^¬║ñú¿╬¬¼║A = %d\n",
  353.                    stat);
  354.             exit(1);
  355.         }
  356.  
  357.         scode = RSRSLT;               /* Default return code */
  358.  
  359.         switch (stat) {
  360.  
  361.         case RQXLOAD:                 /* Load functions.  Called at the start
  362.                                          of the drawing editor.  Re-initialise
  363.                                          the application here. */
  364.             scode = -(funcload() ? RSRSLT : RSERR);
  365.             break;
  366.  
  367.         case RQSUBR:                  /* Evaluate external lisp function */
  368.             cindex = ads_getfuncode();
  369.             functional = False;
  370.             if (!initacad(False)) {
  371.                 ads_printf(/*MSG24*/"\n╡L¬k▒╥⌐líu└│Ñ╬╡{ªíívíC\n");
  372.             } else {
  373.  
  374.                 /* Execute the command from the command table with
  375.                    the index associated with this function. */
  376.  
  377.                 if (cindex > 0) {
  378.                     cindex--;
  379.                     assert(cindex < ELEMENTS(cmdtab));
  380.                     (*cmdtab[cindex].cmdfunc)();
  381.                 }
  382.             }
  383.             if (!functional)
  384.                 ads_retvoid();        /* Void result */
  385.             break;
  386.  
  387.         default:
  388.             break;
  389.         }
  390.     }
  391. }
  392.  
  393. /* FUNCLOAD  --  Load external functions into AutoLISP */
  394.  
  395. static Boolean funcload()
  396. {
  397.     char ccbuf[40];
  398.     int i;
  399.  
  400.     strcpy(ccbuf, /*MSG0*/"C:");
  401.     for (i = 0; i < ELEMENTS(cmdtab); i++) {
  402.         strcpy(ccbuf + 2, cmdtab[i].cmdname);
  403.         ads_defun(ccbuf, i + 1);
  404.     }
  405.  
  406.     return initacad(True);            /* Reset AutoCAD initialisation */
  407. }
  408.  
  409. /*  ALLOC  --  Allocate storage and fail if it can't be obtained.  */
  410.  
  411. static char *alloc(nbytes)
  412.   unsigned nbytes;
  413. {
  414.     char *cp;
  415.  
  416.     if ((cp = malloc(nbytes)) == NULL) {
  417.         ads_abort(/*MSG25*/"╢WÑX░O╛╨«e╢q");
  418.     }
  419.     return cp;
  420. }
  421.  
  422. /*  DEFMAGBLK  --  Create the magnet definition block. */
  423.  
  424. static void defmagblk()
  425. {
  426.     ads_point centre, ucentre;
  427.     ads_real radius = 0.25;
  428.     ads_point ax, ax1;
  429.     struct resbuf rb1, rb2, ocolour;
  430.     ads_name e2;
  431.     int i;
  432.     ads_name matbss;
  433.     ads_name ename;
  434.     ads_point atx;
  435.  
  436.     Spoint(ucentre, 4, 4, 0);
  437.     rb1.restype = rb2.restype = RTSHORT;
  438.     rb1.resval.rint = 1;              /* From UCS */
  439.     rb2.resval.rint = 0;              /* To world */
  440.     ads_trans(ucentre, &rb1, &rb2, False, centre);
  441.     CommandB();
  442.     ads_getvar(/*MSG0*/"CECOLOR", &ocolour);
  443.  
  444.     /* AutoCAD  currently returns  "human readable" colour strings
  445.        like "1 (red)" for the standard colours.  Trim  the  string
  446.        at  the  first space to guarantee we have a valid string to
  447.        restore the colour later.  */
  448.     if (strchr(ocolour.resval.rstring, ' ') != NULL)
  449.         *strchr(ocolour.resval.rstring, ' ') = EOS;
  450.  
  451.     ads_command(RTSTR, /*MSG0*/"_.ELEV", RTREAL, -0.5, RTREAL, 0.5, RTNONE);
  452.     Spoint(ax, 0.0, 0.0, -0.5);
  453.     ads_command(RTSTR, /*MSG0*/"_.COLOUR", RTSTR, /*MSG0*/"_BLUE", RTNONE);
  454. #define PcX   3
  455.     ads_command(RTSTR, /*MSG0*/"_.Circle",
  456.                 RT3DPOINT, ax, RTREAL, PendLength / PcX,
  457.                 RTNONE);
  458.     ads_command(RTSTR, /*MSG0*/"_.ELEV", RTREAL, 0.0, RTREAL, 0.0, RTNONE);
  459. #define PlX   40.0
  460.     Spoint(ax, -PendLength / PlX, -PendLength / PlX, 0.0);
  461.     Spoint(ax1, PendLength / PlX, PendLength / PlX, 0.0);
  462.     ads_command(RTSTR, /*MSG0*/"_.COLOUR", RTSTR, /*MSG0*/"_WHITE", RTNONE);
  463.     ads_command(RTSTR, /*MSG0*/"_.LINE", RT3DPOINT, ax, RT3DPOINT, ax1,
  464.                 RTSTR, "", RTNONE);
  465.     Spoint(ax, -PendLength / PlX, PendLength / PlX, 0.0);
  466.     Spoint(ax1, PendLength / PlX, -PendLength / PlX, 0.0);
  467.     ads_command(RTSTR, /*MSG0*/"_.LINE", RT3DPOINT, ax, RT3DPOINT, ax1,
  468.                 RTSTR, "", RTNONE);
  469.  
  470.     ads_command(RTSTR, /*MSG0*/"_.COLOUR", RTSTR, /*MSG0*/"_BYBLOCK", RTNONE);
  471.     rb1.resval.rint = 0;              /* From world */
  472.     rb2.resval.rint = 1;              /* To new UCS */
  473.     ads_trans(centre, &rb1, &rb2, False, centre);
  474.     ax[X] = ax1[X] = centre[X];
  475.     ax[Y] = centre[Y] + radius;
  476.     ax[Z] = ax1[Z] = centre[Z];
  477.     ax1[Y] = centre[Y] - radius;
  478.     ads_command(RTSTR, /*MSG0*/"_.ELEV", RTREAL, 0.0, RTREAL, 0.1,
  479.                 RTNONE);
  480.     ads_command(RTSTR, /*MSG0*/"_.Circle", RT3DPOINT, ax, RTREAL, radius,
  481.                 RTNONE);
  482.     ads_command(RTSTR, /*MSG0*/"_.ELEV", RTREAL, 0.0, RTREAL, 0.0, RTNONE);
  483.     ads_command(RTSTR, /*MSG0*/"_.COLOUR",
  484.                 RTSTR, ocolour.resval.rstring, RTNONE);
  485.     free(ocolour.resval.rstring);
  486.     ads_entlast(e2);
  487.  
  488.     /* Create attributes */
  489.  
  490.     Spoint(atx, 4, 2.75, 0);
  491.     ads_ssadd(e2, NULL, matbss);
  492.  
  493.     PushVar(/*MSG0*/"AFLAGS", saflags, 1, rint);  /* Invisible */
  494.     ads_command(RTSTR, /*MSG0*/"_.ATTDEF", RTSTR, "",
  495.                 RTSTR, /*MSG28*/"PARTNAME",
  496.                 RTSTR, /*MSG29*/"Particle name", RTSTR, "", RT3DPOINT, atx,
  497.                 RTREAL, 0.2, RTREAL, 0.0, RTNONE);
  498.     ads_entlast(ename);
  499.     ads_ssadd(ename, matbss, matbss);
  500.  
  501.     for (i = 0; i < ELEMENTS(partatt); i++) {
  502.         atx[Y] -= 0.25;
  503.         ads_command(RTSTR, /*MSG0*/"_.ATTDEF",
  504.                     RTSTR, "", RTSTR, partatt[i].attname,
  505.                     RTSTR, partatt[i].attprompt, RTSTR, "0", RT3DPOINT, atx,
  506.                     RTREAL, 0.2, RTREAL, 0.0, RTNONE);
  507.         ads_entlast(ename);
  508.         ads_ssadd(ename, matbss, matbss);
  509.     }
  510.     PopVar(/*MSG0*/"AFLAGS", saflags);
  511.  
  512.     /* Collect magnet and attributes into a block. */
  513.  
  514.     ads_command(RTSTR, /*MSG0*/"_.BLOCK",
  515.                 RTSTR, ParticleBlock, RT3DPOINT, centre,
  516.                 RTPICKS, matbss, RTSTR, "", RTNONE);
  517.     CommandE();
  518.     ads_ssfree(matbss);
  519. }
  520.  
  521. /*  ENTITEM  --  Search an entity buffer chain and return an item
  522.                  with the specified group code.  */
  523.  
  524. static struct resbuf *entitem(rchain, gcode)
  525.   struct resbuf *rchain;
  526.   int gcode;
  527. {
  528.     while ((rchain != NULL) && (rchain->restype != gcode))
  529.         rchain = rchain->rbnext;
  530.  
  531.     return rchain;
  532. }
  533.  
  534. /*  ENTINFO  --  Obtain information about a particle entity:
  535.  
  536.                        Handle
  537.                        Position
  538.                        Size (from scale factor of unit block)
  539.                        Colour
  540. */
  541.  
  542. static void entinfo(ename, h, p, r, c)
  543.   ads_name ename;
  544.   char *h;
  545.   ads_point p;
  546.   ads_real *r;
  547.   int *c;
  548. {
  549.     struct resbuf *rent, *rh;
  550.  
  551.     rent = ads_entget(ename);
  552.     if ((rh = entitem(rent, 5)) != NULL)
  553.         strcpy(h, rh->resval.rstring);
  554.     else
  555.         h[0] = EOS;
  556.     rh = entitem(rent, 10);
  557.     assert(rh != NULL);
  558.     Cpoint(p, rh->resval.rpoint);
  559.     rh = entitem(rent, 41);
  560.     assert(rh != NULL);
  561.     *r = rh->resval.rreal;
  562.     if ((rh = entitem(rent, 62)) != NULL) {
  563.         *c = rh->resval.rint;
  564.         if (*c == 0)                  /* Naked colour by block? */
  565.             *c = 7;                   /* Forbidden: make it white.  Q.C.D. */
  566.     } else {
  567.         /* This entity derives its colour from the layer.  Get
  568.            the colour from the layer table. */
  569.         *c = 7;
  570.         if ((rh = entitem(rent, 8)) != NULL) {
  571.             if ((rh = ads_tblsearch(/*MSG0*/"LAYER", rh->resval.rstring,
  572.                                     False)) != NULL) {
  573.                 struct resbuf *lh = entitem(rh, 62);
  574.                 if (lh != NULL) {
  575.                     int lc = abs(lh->resval.rint);
  576.                     if (lc > 0 && lc < 256) {
  577.                         *c = lc;
  578.                     }
  579.                 }
  580.                 ads_relrb(rh);
  581.             }
  582.         }
  583.     }
  584.     ads_relrb(rent);
  585. }
  586.  
  587. /*  PARTEXT  --  Extract particles present in drawing and build the
  588.                  in-memory particle table.  */
  589.  
  590. static void partext()
  591. {
  592.     long i, l;
  593.     struct resbuf rbet, rbb;
  594.     struct resbuf *rb, *rent, *vb;
  595.     ads_name ename, vname;
  596.  
  597.     if (ptab != NULL) {
  598.         free(ptab);
  599.     }
  600.     ptab = NULL;
  601.     nptab = 0;
  602.  
  603.     /* Build the SSGET entity buffer chain to filter for block
  604.        insertions of the named block on the named layer. */
  605.  
  606.     rbet.restype = 0;                 /* Entity type */
  607.     rbet.resval.rstring = /*MSG0*/"INSERT";
  608.     rbet.rbnext = &rbb;
  609.     rbb.restype = 2;                  /* Block name */
  610.     rbb.resval.rstring = ParticleBlock;
  611.     rbb.rbnext = NULL;
  612.  
  613.     if (ads_ssget(/*MSG0*/"X", NULL, NULL, &rbet, vname) != RTNORM) {
  614.         return;                       /* No definitions in database */
  615.     }
  616.  
  617.     /* We found one or more definitions.  Process  the  attributes
  618.        that  follow  each, plugging the values into material items
  619.        which get attached to the in-memory definition chain.  */
  620.  
  621.     if (ads_sslength(vname, &l) < 0)
  622.         l = 0;
  623.  
  624.     nptab = l;                        /* Save magnet count */
  625.     ntestp = (dualpend == 0.0) ? 1 : 2;  /* Number of test particles */
  626.     ptab = (particle *) alloc((nptab + ntestp) * sizeof(particle));
  627.     for (i = 0; i < l; i++) {
  628.         ads_name pname;
  629.  
  630.         ads_ssname(vname, i, ename);
  631.         memcpy(pname, ename, sizeof ename);
  632.  
  633.         memset(&pt, 0, sizeof pt);
  634.         while (True) {
  635.             ads_entnext(ename, ename);
  636.             rent = ads_entget(ename);
  637.             if (rent == NULL) {
  638.                 ads_printf(/*MSG30*/"PARTEXT: ╡L¬k┼¬¿·íu─▌⌐╩ívíC\n");
  639.                 break;
  640.             }
  641.             rb = entitem(rent, 0);    /* Entity type */
  642.             if (rb != NULL) {
  643.                 if (strcmp(rb->resval.rstring, /*MSG0*/"ATTRIB") != 0)
  644.                     break;
  645.                 rb = entitem(rent, 2);  /* Attribute tag */
  646.                 vb = entitem(rent, 1);  /* Attribute value */
  647.                 if (rb != NULL && vb != NULL) {
  648.                     if (strcmp(rb->resval.rstring, /*MSG31*/"PARTNAME") == 0) {
  649.                         strcpy(pt.partname, vb->resval.rstring);
  650.                     } else {
  651.                         int j;
  652.  
  653.                         for (j = 0; j < ELEMENTS(partatt); j++) {
  654.                             if (strcmp(rb->resval.rstring,
  655.                                        partatt[j].attname) == 0) {
  656.                                 *partatt[j].attvar = atof(vb->resval.rstring);
  657.                                 break;
  658.                             }
  659.                         }
  660.                     }
  661.                 }
  662.             }
  663.             ads_relrb(rent);
  664.         }
  665.         entinfo(pname, pt.partblock, pt.position, &pt.radius, &pt.colour);
  666.         memcpy(&(ptab[(int) i]), &pt, sizeof(particle));
  667.     }
  668.  
  669.     /* Initialise the test particle(s) to their initial state. */
  670.  
  671.     strcpy(ptab[nptab].partname, /*MSG32*/"║╧┬\134");
  672.     Spoint(ptab[nptab].position, 4.0, 4.0,
  673.            (PendLength + 1.0) - sqrt(PendLength * PendLength - 4.0 * 4.0));
  674.     Spoint(ptab[nptab].velocity, 0.0, 0.0, 0.0);
  675.     Spoint(ptab[nptab].acceleration, 0.0, 0.0, 0.0);
  676.     Cpoint(ptab[nptab].lastpos, ptab[nptab].position);
  677.     ptab[nptab].strength = 1.0;
  678.     ptab[nptab].radius = 0.25;
  679.     ptab[nptab].colour = 1;           /* Red */
  680.  
  681.     if (ntestp > 1) {
  682.         double mactemp = (4.0 * 4.0 + (4.0 + dualpend) * (4.0 + dualpend));
  683.  
  684.         strcpy(ptab[nptab + 1].partname, /*MSG33*/"ª∞▓╛║╧┬\134");
  685.         Spoint(ptab[nptab + 1].position, 4.0, 4.0 + dualpend,
  686.                (PendLength + 1.0) - sqrt(PendLength * PendLength - mactemp));
  687.         Spoint(ptab[nptab + 1].velocity, 0.0, 0.0, 0.0);
  688.         Spoint(ptab[nptab + 1].acceleration, 0.0, 0.0, 0.0);
  689.         Cpoint(ptab[nptab + 1].lastpos, ptab[nptab].position);
  690.         ptab[nptab + 1].strength = 1.0;
  691.         ptab[nptab + 1].radius = 0.25;
  692.         ptab[nptab + 1].colour = 3;   /* Green */
  693.     }
  694.  
  695.     ads_ssfree(vname);                /* Release selection set */
  696. }
  697.  
  698. /*  SUMVEC  --  Add a linear multiple to another vector, a = b + t * c.  */
  699.  
  700. static void sumvec(ap, bp, t, cp)
  701.   ads_real *ap, *bp, *cp;
  702.   ads_real t;
  703. {
  704.     ap[X] = bp[X] + t * cp[X];
  705.     ap[Y] = bp[Y] + t * cp[Y];
  706.     ap[Z] = bp[Z] + t * cp[Z];
  707. }
  708.  
  709. /*  VARBLOCKDEF  --  Create the block that carries the attributes that
  710.                      define the modal variables. */
  711.  
  712. static void varblockdef()
  713. {
  714.     int i;
  715.     ads_name varbss;
  716.     ads_name ename;
  717.     ads_point atx;
  718.  
  719.     atx[X] = 4.0;
  720.     atx[Y] = 4.0;
  721.     atx[Z] = 0.0;
  722.     ads_ssadd(NULL, NULL, varbss);
  723.  
  724.     CommandB();
  725.     PushVar(/*MSG0*/"AFLAGS", saflags, 1, rint);  /* Invisible */
  726.  
  727.     for (i = 0; i < ELEMENTS(varatt); i++) {
  728.         atx[Y] -= 0.25;
  729.         ads_command(RTSTR, /*MSG0*/"_.ATTDEF",
  730.                     RTSTR, "", RTSTR, varatt[i].attname,
  731.                     RTSTR, varatt[i].attprompt, RTSTR, "0", RT3DPOINT, atx,
  732.                     RTREAL, 0.2, RTREAL, 0.0, RTNONE);
  733.         ads_entlast(ename);
  734.         ads_ssadd(ename, varbss, varbss);
  735.     }
  736.     PopVar(/*MSG0*/"AFLAGS", saflags);
  737.  
  738.     ads_command(RTSTR, /*MSG0*/"_.BLOCK", RTSTR, ModalBlock, RTSTR, "4,4",
  739.                 RTPICKS, varbss, RTSTR, "", RTNONE);
  740.     CommandE();
  741.     ads_ssfree(varbss);
  742. }
  743.  
  744. /*  SAVEMODES  --  Save application modes as attributes of the mode
  745.                    block.  */
  746.  
  747. static void savemodes()
  748. {
  749.     int i;
  750.     struct resbuf rbb;
  751.     ads_name ename, vname;
  752.     long l;
  753.  
  754.     /* Build the SSGET entity buffer chain to filter for block
  755.        insertions of the named block on the named layer. */
  756.  
  757.     rbb.restype = 2;                  /* Block name */
  758.     rbb.resval.rstring = ModalBlock;
  759.     rbb.rbnext = NULL;
  760.  
  761.     if (ads_ssget(/*MSG0*/"X", NULL, NULL, &rbb, vname) == RTNORM) {
  762.  
  763.         /* Delete all definitions found. */
  764.  
  765.         if (ads_sslength(vname, &l) < 0)
  766.             l = 0;
  767.  
  768.         if (l > 0) {
  769.             CommandB();
  770.             ads_command(RTSTR, /*MSG0*/"_.ERASE",
  771.                         RTPICKS, vname, RTSTR, "", RTNONE);
  772.             CommandE();
  773.         }
  774.         ads_ssfree(vname);            /* Release selection set */
  775.     }
  776.  
  777.     /* Now insert the modal variable block, attaching the
  778.        mode variables to its attributes. */
  779.  
  780.     CommandB();
  781.     ads_command(RTSTR, /*MSG0*/"_.INSERT", RTSTR, ModalBlock,
  782.                 RTSTR, "0,0", RTSTR, "1", RTSTR, "1", RTSTR, "0",
  783.                 RTNONE);
  784.     for (i = 0; i < ELEMENTS(varatt); i++) {
  785.         char attval[20];
  786.  
  787. #define YorN(x) ((x) ? /*MSG34*/"Yes" : /*MSG35*/"No")
  788.         switch (varatt[i].attvari) {
  789.         case 1:
  790.             strcpy(attval, YorN(drawonly));
  791.             break;
  792.         case 2:
  793.             strcpy(attval, YorN(showtime));
  794.             break;
  795.         case 3:
  796.             strcpy(attval, YorN(showstep));
  797.             break;
  798.         case 4:
  799.             sprintf(attval, "%.12g", stepsize);
  800.             break;
  801.         case 5:
  802.             sprintf(attval, "%.12g", dualpend);
  803.             break;
  804.         }
  805. #undef YorN
  806.         ads_command(RTSTR, attval, RTNONE);
  807.     }
  808.     ads_entlast(ename);
  809.     ads_command(RTSTR, /*MSG0*/"_.CHPROP", RTENAME, ename, RTSTR, "",
  810.                 RTSTR, /*MSG0*/"_layer", RTSTR, FrozenLayer, RTSTR, "",
  811.                 RTNONE);
  812.     CommandE();
  813. }
  814.  
  815. /*  BOOLVAR  --  Determine Boolean value from attribute string.  We
  816.                  recognise numbers (nonzero means True) and the
  817.                  strings "True", "False", "Yes", and "No",
  818.                  in either upper or lower case.  */
  819.  
  820. static Boolean boolvar(varname, s)
  821.   char *varname, *s;
  822. {
  823.     char ch = *s;
  824.  
  825.     if (islower(ch))
  826.         ch = toupper(ch);
  827.     if (isdigit(ch)) {
  828.         return (atoi(s) != 0) ? True : False;
  829.     }
  830.  
  831.     switch (ch) {
  832.     case /*MSG36*/'Y':
  833.     case /*MSG37*/'T':
  834.         return True;
  835.     case /*MSG38*/'N':
  836.     case /*MSG39*/'F':
  837.         return False;
  838.     default:
  839.         ads_printf(/*MSG40*/"ѵÑIíu%sív¬║Ѽ¬L¡╚╡L«─: %s íC\n", varname, s);
  840.         break;
  841.     }
  842.     return False;
  843. }
  844.  
  845. /*  VARSET  --  Set modal variables from the block attributes.  */
  846.  
  847. static void varset()
  848. {
  849.     struct resbuf rbb;
  850.     ads_name vname;
  851.     long l;
  852.  
  853.     /* Build the SSGET entity buffer chain to filter for block
  854.        insertions of the named block on the named layer. */
  855.  
  856.     rbb.restype = 2;                  /* Block name */
  857.     rbb.resval.rstring = ModalBlock;
  858.     rbb.rbnext = NULL;
  859.  
  860.     if (ads_ssget(/*MSG0*/"X", NULL, NULL, &rbb, vname) == RTNORM) {
  861.  
  862.         if (ads_sslength(vname, &l) < 0)
  863.             l = 0;
  864.  
  865.         if (l > 0) {
  866.             ads_name ename;
  867.  
  868.             if (ads_ssname(vname, 0L, ename) == RTNORM) {
  869.                 while (ads_entnext(ename, ename) == RTNORM) {
  870.                     int i;
  871.                     struct resbuf *rb = ads_entget(ename),
  872.                                   *et, *at, *av;
  873.  
  874.                     if (rb == NULL)
  875.                         break;
  876.                     et = entitem(rb, 0);
  877.                     assert(et != NULL);
  878.                     if (strcmp(et->resval.rstring, /*MSG0*/"ATTRIB") == 0) {
  879.                         at = entitem(rb, 2);  /* Attribute tag */
  880.                         av = entitem(rb, 1);  /* Attribute value */
  881.                         if (at == NULL || av == NULL)
  882.                             break;
  883.                         for (i = 0; i < ELEMENTS(varatt); i++) {
  884.                             if (strcmp(at->resval.rstring,
  885.                                        varatt[i].attname) == 0) {
  886.                                 switch (varatt[i].attvari) {
  887.                                 case 1:
  888.                                     drawonly = boolvar(at->resval.rstring,
  889.                                                        av->resval.rstring);
  890.                                     break;
  891.                                 case 2:
  892.                                     showtime = boolvar(at->resval.rstring,
  893.                                                        av->resval.rstring);
  894.                                     break;
  895.                                 case 3:
  896.                                     showstep = boolvar(at->resval.rstring,
  897.                                                        av->resval.rstring);
  898.                                     break;
  899.                                 case 4:
  900.                                     stepsize = 0.1;    /* Default if error */
  901.                                     sscanf(av->resval.rstring, "%lf",
  902.                                            &stepsize);
  903.                                     break;
  904.                                 case 5:
  905.                                     dualpend = 0.0;    /* Default if error */
  906.                                     sscanf(av->resval.rstring, "%lf",
  907.                                            &dualpend);
  908.                                     break;
  909.                                 }
  910.                             }
  911.                         }
  912.                     } else if (strcmp(et->resval.rstring,
  913.                                       /*MSG0*/"SEQEND") == 0) {
  914.                         ads_relrb(rb);
  915.                         break;
  916.                     }
  917.                     ads_relrb(rb);
  918.                 }
  919.             }
  920.         }
  921.         ads_ssfree(vname);            /* Release selection set */
  922.     }
  923. }
  924.  
  925. /*  INITACAD  --  Initialise the required modes in the AutoCAD
  926.                   drawing.  */
  927.  
  928. static Boolean initacad(reset)
  929.   Boolean reset;
  930. {
  931.     static Boolean initdone, initok;
  932.  
  933.     if (reset) {
  934.         initdone = False;
  935.         initok = True;
  936.     } else {
  937.         if (!initdone) {
  938.             struct resbuf rb;
  939.             struct resbuf *ep;
  940.             ads_point pzero;
  941.  
  942.             initok = False;
  943.  
  944.             /* Reset the program modes to standard values upon
  945.                entry to the drawing editor. */
  946.  
  947.             stepno = 0;               /* Step 0 */
  948.             numsteps = 1000;          /* Default number of steps */
  949.             simtime = 0.0;            /* No elapsed time */
  950.             stepsize = 0.1;           /* Default step size */
  951.             dualpend = 0.0;           /* Default 2nd pendulum offset: none */
  952.             drawonly = True;          /* Draw using ads_grdraw() */
  953.             showstep = True;          /* Show step number */
  954.             showtime = True;          /* Show elapsed time */
  955.  
  956.             /* Enable  handles  if  they aren't  already on.  We use
  957.                handles to link  from  our  in-memory  table  to  the
  958.                magnets in  the  AutoCAD  database,  and we also keep
  959.                track of the reference frame entity  by  its  handle. */
  960.  
  961.             ads_getvar(/*MSG0*/"HANDLES", &rb);
  962.             if (!rb.resval.rint) {
  963.                 CommandB();
  964.                 ads_command(RTSTR, /*MSG0*/"_.handles",
  965.                             RTSTR, /*MSG0*/"_on", RTNONE);
  966.                 CommandE();
  967.                 ads_getvar(/*MSG0*/"HANDLES", &rb);
  968.                 if (!rb.resval.rint) {
  969.                     ads_printf(/*MSG41*/"╡L¬k▒╥Ñ╬íu╣╧╜XívíC\n");
  970.                     initdone = True;
  971.                     return (initok = False);
  972.                 }
  973.             }
  974.  
  975.             /* Create required drawing objects. */
  976.  
  977.             /* If there isn't already a "frozen solid" layer, create one. */
  978.  
  979.             if ((ep = ads_tblsearch(/*MSG0*/"LAYER",
  980.                                     FrozenLayer, False)) == NULL) {
  981.                 CommandB();
  982.                 ads_command(RTSTR, /*MSG0*/"_.LAYER",
  983.                             RTSTR, /*MSG0*/"_NEW", RTSTR, FrozenLayer,
  984.                             RTSTR, /*MSG0*/"_FREEZE",
  985.                             RTSTR, FrozenLayer, RTSTR, "", RTNONE);
  986.                 ads_command(RTSTR, /*MSG0*/"_.LAYER",
  987.                             RTSTR, /*MSG0*/"_NEW", RTSTR, OrbitLayer,
  988.                             RTSTR, "", RTNONE);
  989.                 CommandE();
  990.                 defmagblk();          /* Define the particle block */
  991.             } else {
  992.                 ads_relrb(ep);
  993.             }
  994.  
  995.             /* Create the block definition for our modal variables. */
  996.  
  997.             if ((ep = ads_tblsearch(/*MSG0*/"BLOCK",
  998.                                     ModalBlock, False)) == NULL) {
  999.                 varblockdef();
  1000.                 savemodes();
  1001.             } else {
  1002.                 ads_relrb(ep);
  1003.                 varset();             /* Load modals from block */
  1004.             }
  1005.  
  1006.             /* Establish default initial view. */
  1007.  
  1008.             CommandB();
  1009.             Spoint(pzero, 0.0, 0.0, 0.0);
  1010.             ads_command(RTSTR, /*MSG0*/"_.ZOOM",
  1011.                         RTSTR, /*MSG0*/"_C", RT3DPOINT, pzero,
  1012.                         RTREAL, 2.0 * (PendLength / PcX), RTNONE);
  1013.             CommandE();
  1014.  
  1015.             initdone = initok = True;
  1016.         }
  1017.     }
  1018.     return initok;
  1019. }
  1020.  
  1021. /*  ADDMAG  --  Insert magnet block with attributes.  */
  1022.  
  1023. static void addmag(mname, pos, strength)
  1024.   char *mname;
  1025.   ads_point pos;
  1026.   ads_real strength;
  1027. {
  1028.     ads_real size;
  1029.     char smas[32];
  1030.  
  1031.     size = 1.0;
  1032.     CommandB();
  1033.     sprintf(smas, "%.12g", strength);
  1034.     ads_command(RTSTR, /*MSG0*/"_.INSERT", RTSTR, ParticleBlock, RTPOINT, pos,
  1035.                 RTREAL, size, RTREAL, size, RTREAL, 0.0,
  1036.                 RTSTR, mname,
  1037.                 RTSTR, smas, RTNONE);
  1038.     CommandE();
  1039. }
  1040.  
  1041. /*  MAGNET  --  Add magnet to database.  */
  1042.  
  1043. static void magnet()
  1044. {
  1045.     static int magseq = 0;
  1046.     ads_point pos;
  1047.     ads_real strength;
  1048.     char pname[134];
  1049.  
  1050.     sprintf(pname, /*MSG42*/"║╧│⌡ %d", ++magseq);
  1051.     ads_initget(1 + 8 + 16, NULL);
  1052.     if (ads_getpoint(NULL, /*MSG43*/"\nª∞╕m: ", pos) != RTNORM)
  1053.         return;
  1054.     ads_initget(2, NULL);             /* No zero-strength magnets */
  1055.     switch (ads_getreal(/*MSG44*/"\n║╧ñO <1>: ", &strength)) {
  1056.     case RTNONE:
  1057.         strength = 1.0;
  1058.     case RTNORM:
  1059.         break;
  1060.     default:
  1061.         return;
  1062.     }
  1063.     addmag(pname, pos, strength);
  1064. }
  1065.  
  1066. /*  DEMO  --  Load canned demo case.  */
  1067.  
  1068. static void demo()
  1069. {
  1070.     int i, j;
  1071.  
  1072.     typedef struct {                  /* Demo magnet table item */
  1073.        char *dmname;                  /* Magnet name */
  1074.        ads_point dmpos;               /* Position */
  1075.        ads_real dmstrength;           /* Magnetic strength */
  1076.        int dmcolour;                  /* Colour */
  1077.     } dmag;
  1078.  
  1079.     typedef struct {
  1080.        char *dname;                   /* Name of demo case */
  1081.        ads_real dnstep;               /* Default number of steps */
  1082.        ads_real dssize;               /* Step size */
  1083.        ads_real dualp;                /* Dual pendulum offset */
  1084.        dmag *dmtab;                   /* Table of magnets */
  1085.        int dmtabl;                    /* Number of magnets in table */
  1086.     } demodesc;
  1087.  
  1088.     static dmag interlom[] = {
  1089.        {/*MSG45*/"Demo magnet 1", {3, 3, 0}, 1, 2},
  1090.        {/*MSG46*/"Demo magnet 2", {0, 3, 0}, 2, 3},
  1091.        {/*MSG47*/"Demo magnet 3", {-1, -2, 0}, 1, 1},
  1092.        {/*MSG48*/"Demo magnet 3", {2, -3, 0}, 1, 5}
  1093.       };
  1094.     static demodesc dmt[] = {
  1095.        {/*MSG49*/"Capt. Magneto",
  1096.         3000, 0.1, 0.001,
  1097.         interlom, ELEMENTS(interlom)}
  1098.       };
  1099.  
  1100.     partext();
  1101.     if (nptab > 0) {
  1102.         ads_printf(/*MSG50*/"\n\
  1103. ╢╚Ñi⌐≤íu¬┼╣╧ívññ½╪Ñ▀Ñ▄╜díC\n");
  1104.         return;
  1105.     }
  1106.  
  1107. #ifndef MULTIPLE_DEMOS
  1108.     i = 0;                            /* Always use first demo */
  1109. #else
  1110.     ads_textscr();                    /* Flip to text screen */
  1111.     ads_printf(/*MSG51*/"«e│\134¬║▒í¬p:\n");
  1112.     for (i = 0; i < ELEMENTS(dmt); i++) {
  1113.         ads_printf("\n%d.  %s", i + 1, dmt[i].dname);
  1114.     }
  1115.     ads_printf("\n");
  1116.     ads_initget(2 + 4, NULL);
  1117.     switch (ads_getint(/*MSG52*/"\n¿║ñ@╢╡Ñ▄╜d? ", &i)) {
  1118.     case RTNORM:
  1119.         i--;
  1120.         if (i < ELEMENTS(dmt))
  1121.             break;
  1122.     default:
  1123.         return;
  1124.     }
  1125. #endif
  1126.  
  1127.     for (j = 0; j < dmt[i].dmtabl; j++) {
  1128.         CommandB();
  1129.         ads_command(RTSTR, /*MSG0*/"_.COLOUR",
  1130.                     RTSHORT, dmt[i].dmtab[j].dmcolour, RTNONE);
  1131.         CommandE();
  1132.         addmag(dmt[i].dmtab[j].dmname, dmt[i].dmtab[j].dmpos,
  1133.                dmt[i].dmtab[j].dmstrength);
  1134.     }
  1135.  
  1136.     numsteps = dmt[i].dnstep;
  1137.     stepsize = dmt[i].dssize;
  1138.     dualpend = dmt[i].dualp;
  1139.     savemodes();
  1140.     CommandB();
  1141.     ads_command(RTSTR, /*MSG0*/"_.COLOUR", RTSTR, /*MSG0*/"_BYLAYER", RTNONE);
  1142.     CommandE();
  1143.     partext();
  1144. }
  1145.  
  1146. /*  MAGEDIT  --  Edit properties of an existing magnet.  */
  1147.  
  1148. static void magedit()
  1149. {
  1150.     int i;
  1151.     ads_name ename;
  1152.     ads_point ppt;
  1153.  
  1154.     partext();                        /* Update in-memory magnet table */
  1155.     i = nptab;
  1156.     if (ads_entsel(/*MSG53*/"┐∩╛▄╣w│╞╜s┐Φ¬║íu║╧│⌡ív: ", ename, ppt) == RTNORM) {
  1157.         struct resbuf *eb = ads_entget(ename), *ha;
  1158.  
  1159.         if ((ha = entitem(eb, 5)) != NULL) {
  1160.             for (i = 0; i < nptab; i++) {
  1161.                 if (strcmp(ptab[i].partblock, ha->resval.rstring) == 0) {
  1162.                     break;
  1163.                 }
  1164.             }
  1165.         }
  1166.     }
  1167.     if (i < nptab) {
  1168.         if (stepno > 0) {
  1169.             reset();
  1170.         }
  1171.         CommandB();
  1172.         ads_command(RTSTR, /*MSG0*/"_.DDATTE", RTENAME, ename, RTNONE);
  1173.         CommandE();
  1174.         partext();
  1175.     } else {
  1176.         ads_printf(/*MSG54*/"\nÑ╝┐∩¿∞íu║╧│⌡ívíC\n");
  1177.     }
  1178. }
  1179.  
  1180. /*  RESET  --  Reset simulation, erasing all pendulum paths.  */
  1181.  
  1182. void reset()
  1183. {
  1184.     struct resbuf rbb;
  1185.     ads_name vname;
  1186.     long l;
  1187.  
  1188.     /* Build the SSGET entity buffer chain to select all
  1189.        entities on the orbital path layer. */
  1190.  
  1191.     rbb.restype = 8;                  /* Layer name */
  1192.     rbb.resval.rstring = OrbitLayer;
  1193.     rbb.rbnext = NULL;
  1194.  
  1195.     if (ads_ssget(/*MSG0*/"X", NULL, NULL, &rbb, vname) == RTNORM) {
  1196.  
  1197.         /* We found one or more definitions.  Delete them. */
  1198.  
  1199.         if (ads_sslength(vname, &l) < 0)
  1200.             l = 0;
  1201.  
  1202.         if (l > 0) {
  1203.             CommandB();
  1204.             ads_command(RTSTR, /*MSG0*/"_.ERASE",
  1205.                         RTPICKS, vname, RTSTR, "", RTNONE);
  1206.             CommandE();
  1207.         }
  1208.         ads_ssfree(vname);            /* Release selection set */
  1209.     }
  1210.     if (drawonly) {
  1211.         ads_grtext(-3, NULL, 0);      /* Clear time display */
  1212.         ads_redraw(NULL, 0);
  1213.     }
  1214.     stepno = 0;                       /* Reset step number */
  1215. }
  1216.  
  1217. /*  RUN  --  Run simulation.  This can be called as an interactive
  1218.              command, RUN, or functionally as (C:RUN <steps/-time>).  */
  1219.  
  1220. static void run()
  1221. {
  1222.     int i, j, k, n = 0, lastcolour = -1;
  1223.     Boolean resume = (stepno > 0) ? True : False, timelimit;
  1224.     ads_real r, endtime = 0;
  1225.     char rpt[80];
  1226.     struct resbuf clayer, ocolour;
  1227.     struct resbuf *rb = ads_getargs();
  1228.  
  1229.     /* If an argument was supplied, set the simulation length
  1230.        from it rather than asking the user interactively. */
  1231.  
  1232.     if (rb != NULL) {
  1233.         Boolean argok = True;
  1234.  
  1235.         switch (rb->restype) {
  1236.         case RTREAL:
  1237.             numsteps = rb->resval.rreal;
  1238.             break;
  1239.         case RTSHORT:
  1240.             numsteps = rb->resval.rint;
  1241.             break;
  1242.         default:
  1243.             ads_fail(/*MSG55*/"RUN:íuñ▐╝╞├■ºOívñúÑ┐╜T");
  1244.             argok = False;
  1245.             break;
  1246.         }
  1247.         if (argok) {
  1248.             if (numsteps == 0) {
  1249.                 ads_fail(/*MSG56*/"RUN:íuñ▐╝╞ívÑ▓╢╖¼░íu0ív");
  1250.                 argok = False;
  1251.             } else {
  1252.                 if (rb->rbnext != NULL) {
  1253.                     ads_fail(/*MSG57*/"RUN:íuñ▐╝╞ív╣Lªh");
  1254.                     argok = False;
  1255.                 }
  1256.             }
  1257.         }
  1258.         if (!argok)
  1259.             return;
  1260.         functional = True;            /* Mark called as a function */
  1261.     } else {
  1262.  
  1263.         /* Ask user for length of simulation in years or number of
  1264.            integration steps to perform. */
  1265.  
  1266.         sprintf(rpt, /*MSG58*/"íu¿B╢iív⌐╬ (-╝╥└└¼φ╝╞) <%.12g>: ",
  1267.                 numsteps);
  1268.         ads_initget(2, NULL);
  1269.         switch (ads_getreal(rpt, &r)) {
  1270.         case RTCAN:                   /* Control C */
  1271.             return;
  1272.         case RTNONE:                  /* Null input */
  1273.             break;
  1274.         case RTNORM:                  /* Number entered */
  1275.             numsteps = r;
  1276.             break;
  1277.         }
  1278.     }
  1279.  
  1280.     /* If we're starting a new simulation rather than resuming one
  1281.        already underway, reset  the  simulated  time  counter  and
  1282.        extract the current particle information from the database. */
  1283.  
  1284.     if (!resume) {
  1285.         ads_point bpx;
  1286.  
  1287.         simtime = 0.0;
  1288.         partext();
  1289.         ads_initget(1 + 8, NULL);
  1290.         Spoint(bpx, 0.0, 0.0, 0.0);
  1291.         if (ads_getpoint(bpx, /*MSG59*/"íu║╧┬\134ív╢}⌐lª∞╕m: ",
  1292.                          ptab[nptab].position) < 0) {
  1293.             Spoint(ptab[nptab].position, 4.0, 4.0, (PendLength + 1.0) -
  1294.                                                    sqrt(PendLength *
  1295.                                                         PendLength -
  1296.                                                         4.0 * 4.0));
  1297.         } else {
  1298.             ptab[nptab].position[Z] = (PendLength + 1.0) -
  1299.                                       sqrt(PendLength * PendLength -
  1300.                                            (ptab[nptab].position[X] *
  1301.                                             ptab[nptab].position[X] +
  1302.                                             ptab[nptab].position[Y] *
  1303.                                             ptab[nptab].position[Y]));
  1304.         }
  1305.         Cpoint(ptab[nptab].lastpos, ptab[nptab].position);
  1306.         if (ntestp > 1) {
  1307.             Cpoint(ptab[nptab + 1].position, ptab[nptab].position);
  1308.             ptab[nptab + 1].position[Y] += dualpend;
  1309.             ptab[nptab + 1].position[Z] = (PendLength + 1.0) -
  1310.                                           sqrt(PendLength * PendLength -
  1311.                                                (ptab[nptab + 1].position[X] *
  1312.                                                 ptab[nptab].position[X] +
  1313.                                                 ptab[nptab + 1].position[Y] *
  1314.                                                 ptab[nptab].position[Y]));
  1315.             Cpoint(ptab[nptab + 1].lastpos, ptab[nptab + 1].position);
  1316.         }
  1317.     }
  1318.  
  1319.     if (numsteps > 0) {
  1320.         timelimit = False;
  1321.         n = numsteps;
  1322.     } else {
  1323.         timelimit = True;
  1324.         endtime = simtime - numsteps;
  1325.     }
  1326.  
  1327.     /* Save original layer and colour */
  1328.  
  1329.     ads_getvar(/*MSG0*/"CLAYER", &clayer);
  1330.     ads_getvar(/*MSG0*/"CECOLOR", &ocolour);
  1331.     /* AutoCAD  currently returns  "human readable" colour strings
  1332.        like "1 (red)" for the standard colours.  Trim  the  string
  1333.        at  the  first space to guarantee we have a valid string to
  1334.        restore the colour later.  */
  1335.     if (strchr(ocolour.resval.rstring, ' ') != NULL)
  1336.         *strchr(ocolour.resval.rstring, ' ') = EOS;
  1337.  
  1338.     CommandB();
  1339.     ads_command(RTSTR, /*MSG0*/"_.UCS", RTSTR, /*MSG0*/"_World", RTNONE);
  1340.     if (!drawonly) {
  1341.         ads_command(RTSTR, /*MSG0*/"_.LAYER",
  1342.                     RTSTR, /*MSG0*/"_SET", RTSTR, OrbitLayer,
  1343.                     RTSTR, "", RTNONE);
  1344.     }
  1345.  
  1346.     /* Initialise last plotted position for each particle. */
  1347.  
  1348.     for (i = 0; i < nptab; i++) {
  1349.         Cpoint(ptab[i].lastpos, ptab[i].position);
  1350.     }
  1351.  
  1352.     while ((timelimit ? (simtime < endtime) : (n-- > 0)) &&
  1353.            !ads_usrbrk()) {
  1354.  
  1355.         /* Update acceleration for all test bodies. */
  1356.  
  1357.         for (i = nptab; i < (nptab + ntestp); i++) {
  1358.             ads_real dist, pacce;
  1359.             ads_point pzero;
  1360.  
  1361.             /* Initialise acceleration to that resulting from the
  1362.                restoring force exerted by the current position of
  1363.                the pendulum. */
  1364.  
  1365.             Spoint(pzero, 0.0, 0.0, 0.0);
  1366.             dist = ads_distance(ptab[i].position, pzero);
  1367.             pacce = - ptab[i].strength * (dist / PendLength);
  1368.             Spoint(ptab[i].acceleration,
  1369.                    pacce * (ptab[i].position[X] / dist),
  1370.                    pacce * (ptab[i].position[Y] / dist),
  1371.                    0.0);
  1372.             for (j = 0; j < nptab; j++) {
  1373.                 if (i != j) {
  1374.                     ads_real vdist = ads_distance(ptab[i].position,
  1375.                                                   ptab[j].position),
  1376.                              /* F = K (m1 m2) / r^2 */
  1377.                              force = ((ptab[i].strength * ptab[j].strength) /
  1378.                                       (vdist * vdist)),
  1379.                              /* F = ma, hence a = F/m.  We take the mass to
  1380.                                 be 1, so the acceleration is just equal to
  1381.                                 the force. */
  1382.                              accel = force;
  1383.  
  1384.                     if (vdist == 0.0) {
  1385.                         ads_printf(/*MSG61*/"íu%sív╗Píu%sívñº╢í¬║íu╕I╝▓ív\n",
  1386.                                    ptab[i].partname, ptab[j].partname);
  1387.                     } else {
  1388.                         /* Update vector components of acceleration. */
  1389.                         for (k = X; k <= Z; k++) {
  1390.                             ptab[i].acceleration[k] +=
  1391.                                accel * (ptab[i].position[k] -
  1392.                                         ptab[j].position[k]) / vdist;
  1393.                         }
  1394.                     }
  1395.                 }
  1396.             }
  1397.         }
  1398.  
  1399.         /* Update velocity for all bodies. */
  1400.  
  1401.         for (i = nptab; i < (nptab + ntestp); i++) {
  1402.             sumvec(ptab[i].velocity, ptab[i].velocity,
  1403.                    stepsize, ptab[i].acceleration);
  1404.         }
  1405.  
  1406.         /* Update position for all bodies. */
  1407.  
  1408.         for (i = nptab; i < (nptab + ntestp); i++) {
  1409.             ads_real pdx;
  1410.  
  1411.             sumvec(ptab[i].position, ptab[i].position,
  1412.                    stepsize, ptab[i].velocity);
  1413.             pdx = ptab[i].position[X] * ptab[i].position[X] +
  1414.                   ptab[i].position[Y] * ptab[i].position[Y];
  1415.             if (pdx > (PendLength * PendLength)) {
  1416.                 ads_real patx;
  1417.  
  1418.                 ads_printf(/*MSG62*/"\
  1419. ╜╨½÷íu░▒ñεív┴Σ í╨ ║╧ñOñ╙▒jñF!\n");
  1420.                 Spoint(ptab[i].acceleration, 0.0, 0.0, 0.0);
  1421.                 Spoint(ptab[i].velocity, 0.0, 0.0, 0.0);
  1422.                 patx = atan2(ptab[i].position[Y], ptab[i].position[X]);
  1423.                 ptab[i].position[X] = PendLength * cos(patx);
  1424.                 ptab[i].position[Y] = PendLength * sin(patx);
  1425.                 pdx = PendLength * PendLength;
  1426.             }
  1427.             ptab[i].position[Z] = (PendLength + 1.0) -
  1428.                                   sqrt(PendLength * PendLength - pdx);
  1429.         }
  1430.  
  1431.         /* Display motion since last update. */
  1432.  
  1433.         for (i = nptab; i < (nptab + ntestp); i++) {
  1434.             if (drawonly) {
  1435.                 ads_grdraw(ptab[i].lastpos, ptab[i].position,
  1436.                            ptab[i].colour, False);
  1437.             } else {
  1438.                 if (lastcolour != ptab[i].colour) {
  1439.                     ads_command(RTSTR, /*MSG0*/"_.COLOUR",
  1440.                                 RTSHORT, ptab[i].colour,
  1441.                                 RTNONE);
  1442.                     lastcolour = ptab[i].colour;
  1443.                 }
  1444.                 ads_command(RTSTR, /*MSG0*/"_.LINE", RTPOINT, ptab[i].lastpos,
  1445.                             RTPOINT, ptab[i].position, RTSTR, "", RTNONE);
  1446.             }
  1447.             Cpoint(ptab[i].lastpos, ptab[i].position);
  1448.         }
  1449.  
  1450.         /* Update the step number, simulated time, and display
  1451.            the values on the status line if selected. */
  1452.  
  1453.         stepno++;
  1454.         simtime += stepsize;
  1455.         rpt[0] = EOS;
  1456.         if (showtime) {
  1457.             sprintf(rpt, /*MSG63*/"«╔╢í %.2f", simtime);
  1458.         }
  1459.         if (showstep) {
  1460.             if (rpt[0] != EOS)
  1461.                 strcat(rpt, "  ");
  1462.             sprintf(rpt + strlen(rpt), /*MSG64*/"¿B╢i %ld", stepno);
  1463.         }
  1464.         if (rpt[0] != EOS)
  1465.             ads_grtext(-2, rpt, False);
  1466.     }
  1467.  
  1468.     /* Restore original layer, colour, and UCS. */
  1469.  
  1470.     if (!drawonly) {
  1471.         ads_command(RTSTR, /*MSG0*/"_.LAYER", RTSTR, /*MSG0*/"_SET",
  1472.                     RTSTR, clayer.resval.rstring, RTSTR, "", RTNONE);
  1473.         ads_command(RTSTR, /*MSG0*/"_.COLOUR",
  1474.                     RTSTR, ocolour.resval.rstring, RTNONE);
  1475.     }
  1476.     free(clayer.resval.rstring);
  1477.     free(ocolour.resval.rstring);
  1478.     ads_command(RTSTR, /*MSG0*/"_.UCS", RTSTR, /*MSG0*/"_Prev", RTNONE);
  1479.     CommandE();
  1480.  
  1481.     /* If called as a function, return the simulation end time.
  1482.        If called as a command, print the end time and step count. */
  1483.  
  1484.     if (functional)
  1485.         ads_retreal(simtime);
  1486.     else
  1487.         ads_printf(/*MSG65*/"\n╡▓º⌠⌐≤íu¿B╢i %ldív,íu%.2f ¼φívíC\n",
  1488.                    stepno, simtime);
  1489. }
  1490.  
  1491. /*  SETMAG  --  Set modal variables.  */
  1492.  
  1493. static void setmag()
  1494. {
  1495.     struct resbuf rbb;
  1496.     ads_name vname;
  1497.     long l;
  1498.  
  1499.     /* Build the SSGET entity buffer chain to filter for block
  1500.        insertions of the named block on the named layer. */
  1501.  
  1502.     rbb.restype = 2;                  /* Block name */
  1503.     rbb.resval.rstring = ModalBlock;
  1504.     rbb.rbnext = NULL;
  1505.  
  1506.     if (ads_ssget(/*MSG0*/"X", NULL, NULL, &rbb, vname) == RTNORM) {
  1507.  
  1508.         /* Delete all definitions found. */
  1509.  
  1510.         if (ads_sslength(vname, &l) < 0)
  1511.             l = 0;
  1512.  
  1513.         if (l > 0) {
  1514.             ads_name ename;
  1515.  
  1516.             if (ads_ssname(vname, 0L, ename) == RTNORM) {
  1517.                 CommandB();
  1518.                 ads_command(RTSTR, /*MSG0*/"_.DDATTE", RTENAME, ename, RTNONE);
  1519.                 CommandE();
  1520.                 varset();
  1521.             }
  1522.         }
  1523.         ads_ssfree(vname);            /* Release selection set */
  1524.     }
  1525. }
  1526.  
  1527.  
  1528. #ifdef  HIGHC
  1529.  
  1530. /*  Earlier versions of High C put abort() in the same module with exit();
  1531.     ADS defines its own exit(), so we have to define our own abort(): */
  1532.  
  1533. static void abort(void)
  1534. {
  1535.     ads_abort("");
  1536. }
  1537.  
  1538. #endif  /* HIGHC */
  1539.