/***************************************************************************** This source code is made available to you under the terms of the Open Development License. This Package should be accompanied by a file that contains the associated licensing terms. If you are unable to find this file, please refer to its location on the Internet at www.rocklyte.com/license.html. ****************************************************************************** ###CLASS### Name: Rotoplex Version: 1.0 Status: Stable Date: April 2003 ID: ID_ROTOPLEX Category: Effect Author: Rocklyte Systems Copyright: Rocklyte Systems, 1999-2003. All rights reserved. Short: This class displays spinning 3D dot based objects in realtime. Keywords: rotoplex, rotate, morph, 3D ###DESCRIPTION### <p>The Rotoplex class demonstrates and tests the real-time capabilities and drawing speeds of the graphics system. It features a number of different 3D objects (Cube, Helix, Enneper and Sphere), and has support for the scaling and rotation of each object. The objects are drawn as a cluster of pixels randomly arranged into the shape of the selected 3D object. While the Rotoplex class was created for use in object scripts, it can also be used by standard programs for more flexible options (such as creating your own 3D models for rotation).</p> <p>This class uses a timer based event to achieve real time rotation, and can be expected to use a reasonable amount of processor time while it is active. The class will not clear the background, so you will be expected to provide a graphical object to use as the background under the Rotoplex object. Here is an example of how a Rotoplex object could be set up:</p> <pre> &lt;render width="300" height="200"/&gt; &lt;box colour="#000000"/&gt; &lt;rotoplex maxelements="1000" shape="cube" xspeed="4" yspeed="7"/&gt; &lt;/render&gt; </pre> <p>It is possible to attach scrollbars to the Rotoplex object so that the user can move its position on the X, Y and Z axis. To set this up, the DScroll, HScroll and VScroll fields can be set to refer to the Scroll object of individual scrollbars. This is demonstrated in the Rotoplex script (scripts:demos/rotoplex.dml).</p> <p>The speed of rotation can be altered by setting the XSpeed, YSpeed and ZSpeed fields. The 3-Dimensional position of the object can be altered by setting the XCoord, YCoord and ZCoord fields (which are considered to be offsets from the center of each axis). Finally, the current angles of the object can be set or read from the AngleX, AngleY and AngleZ fields.</p> ###END### *****************************************************************************/ #include <pandora/system/all.h> #include <pandora/graphics/bitmap.h> #include <pandora/graphics/render.h> #include <pandora/graphics/scroll.h> #include <math.h> static ERROR CMDInit(OBJECTPTR, struct KernelBase *); static ERROR CMDExpunge(void); #define VER_ROTOPLEX 1.0 EXPORT struct ModHeader ModHeader = { MODULE_HEADER_V1, CMDInit, NULL, NULL, CMDExpunge, JMP_DEFAULT, 0, CPU_PC, VER_ROTOPLEX, VER_KERNEL, "Rocklyte Systems", "Rocklyte Systems (c) 1999-2003. All rights reserved.", "April 2003", "Rotoplex" }; static struct KernelBase *KernelBase = 0; static OBJECTPTR RotoplexClass = 0; static FLOAT *sine = 0; /* Pointer to our sine table */ static FLOAT *cosine = 0; /* Pointer to our cosine table */ /***************************************************************************** ** Rotoplex class. */ #define DEFAULT_WIDTH 100 #define DEFAULT_HEIGHT 100 #define DEFAULT_DOTS 1000 #define MORPH_SPEED 100 /* Low values give fast morphing, higher values slower */ struct Rotoplex { OBJECT_HEADER /* */ OBJECTID DrawableID; /* The graphical object that contains the Rotoplex graphic */ LONG MaxElements; /* Total amount of dots in use */ FLOAT AngleX; /* X angle */ FLOAT AngleY; /* Y angle */ FLOAT AngleZ; /* Z angle */ FLOAT XSpeed; /* X Speed */ FLOAT YSpeed; /* Y Speed */ FLOAT ZSpeed; /* Z Speed */ LONG Shape; /* Predefined object number */ FLOAT XCoord; /* Current X coordinate of the object */ FLOAT YCoord; /* Current Y coordinate of the object */ FLOAT ZCoord; /* Current Z coordinate of the object */ OBJECTID DScroll; /* Depth scrollbar */ OBJECTID HScroll; /* Horizontal scrollbar */ OBJECTID VScroll; /* Vertical scrollbar */ /*** Field store ***/ struct DotPixel *Object; /* Pointer to 3D dot structure */ struct DotPixel *Morph; MEMORYID ObjectMID; OBJECTID TimerID; /* Timer Event */ LONG Morphing; LONG MorphPos; FLOAT fAngleX; FLOAT fAngleY; FLOAT fAngleZ; FLOAT HPosition; FLOAT VPosition; LONG MorphCount; }; struct DotPixel { FLOAT X,Y,Z; }; /*** Rotoplex->Shape ***/ #define RP_ENNEPER 1 #define RP_HELIX 2 #define RP_SPHERE 3 #define RP_CUBE 4 #define RP_START 1 #define RP_END 5 static struct FieldDef RotoplexShape[] = { { "ENNEPER", RP_ENNEPER }, { "HELIX", RP_HELIX }, { "SPHERE", RP_SPHERE }, { "CUBE", RP_CUBE }, { NULL, NULL } }; /***************************************************************************** ** These structures are only used when a program sets the Object field. */ struct DotObject { LONG TotalPixels; struct UserPixels *Pixels; }; struct UserPixels { FLOAT X, Y, Z; }; static ERROR SET_DScroll(struct Rotoplex *, OBJECTID *); static ERROR SET_HScroll(struct Rotoplex *, OBJECTID *); static ERROR SET_Object(struct Rotoplex *, struct DotObject *); static ERROR SET_VScroll(struct Rotoplex *, OBJECTID *); static struct FieldArray RotoplexFields[] = { { "Drawable", 0, FDF_OBJECTID|FDF_RW, 0, NULL, NULL }, { "MaxElements", 0, FDF_LONG|FDF_RI, 0, NULL, NULL }, { "AngleX", 0, FDF_FLOAT|FDF_R, 0, NULL, NULL }, { "AngleY", 0, FDF_FLOAT|FDF_R, 0, NULL, NULL }, { "AngleZ", 0, FDF_FLOAT|FDF_R, 0, NULL, NULL }, { "XSpeed", 0, FDF_FLOAT|FDF_RW, 0, NULL, NULL }, { "YSpeed", 0, FDF_FLOAT|FDF_RW, 0, NULL, NULL }, { "ZSpeed", 0, FDF_FLOAT|FDF_RW, 0, NULL, NULL }, { "Shape", 0, FDF_LONG|FDF_LOOKUP|FDF_RI,(LONG)&RotoplexShape, NULL, NULL }, { "XCoord", 0, FDF_FLOAT|FDF_RW, 0, NULL, NULL }, { "YCoord", 0, FDF_FLOAT|FDF_RW, 0, NULL, NULL }, { "ZCoord", 0, FDF_FLOAT|FDF_RW, 0, NULL, NULL }, { "DScroll", 0, FDF_OBJECTID|FDF_RW, 0, NULL, SET_DScroll }, { "HScroll", 0, FDF_OBJECTID|FDF_RW, 0, NULL, SET_HScroll }, { "VScroll", 0, FDF_OBJECTID|FDF_RW, 0, NULL, SET_VScroll }, /*** Virtual fields ***/ { "Object", 0, FDF_POINTER|FDF_RI, 0, NULL, SET_Object }, END_FIELD }; static ERROR ROTOPLEX_ActionNotify(struct Rotoplex *, struct acActionNotify *); static ERROR ROTOPLEX_Free(struct Rotoplex *, APTR); static ERROR ROTOPLEX_ReleaseObject(struct Rotoplex *, APTR); static ERROR ROTOPLEX_Init(struct Rotoplex *, APTR); static ERROR ROTOPLEX_Move(struct Rotoplex *, struct acMove *); static ERROR ROTOPLEX_NewOwner(struct Rotoplex *, struct acNewOwner *); static ERROR ROTOPLEX_NewObject(struct Rotoplex *, APTR); static ERROR ROTOPLEX_ScrollToPoint(struct Rotoplex *, struct acScrollToPoint *); static ERROR ROTOPLEX_Timer(struct Rotoplex *, struct acTimer *); static struct ActionArray RotoplexActions[] = { { AC_ActionNotify, ROTOPLEX_ActionNotify }, { AC_Free, ROTOPLEX_Free }, { AC_ReleaseObject, ROTOPLEX_ReleaseObject }, { AC_Init, ROTOPLEX_Init }, { AC_Move, ROTOPLEX_Move }, { AC_NewOwner, ROTOPLEX_NewOwner }, { AC_NewObject, ROTOPLEX_NewObject }, { AC_ScrollToPoint, ROTOPLEX_ScrollToPoint }, { AC_Timer, ROTOPLEX_Timer }, { NULL, NULL } }; static void CalculateObject(struct Rotoplex *, struct DotPixel *, LONG, LONG, LONG, LONG); /***************************************************************************** ** Command: Init() */ ERROR CMDInit(OBJECTPTR argModule, struct KernelBase *argKernelBase) { LONG i; FLOAT angle; KernelBase = argKernelBase; if (AllocMemory(sizeof(FLOAT) * 360, MEM_DATA|MEM_NOCLEAR, (APTR *)&sine, NULL) IS ERR_Okay) { if (AllocMemory(sizeof(FLOAT) * 360, MEM_DATA|MEM_NOCLEAR, (APTR *)&cosine, NULL) IS ERR_Okay) { for (i=0; i < 360; i++) { angle = (i * 3.141592654) / 180.0; cosine[i] = cos(angle); sine[i] = sin(angle); } } else return(ObjectError(argModule, ERH_InitModule, ERR_Memory)); } else return(ObjectError(argModule, ERH_InitModule, ERR_Memory)); return(CreateObject(ID_CLASS, NULL, (OBJECTPTR *)&RotoplexClass, NULL, FID_BaseClassID|TLONG, ID_ROTOPLEX, FID_Version|TFLOAT, VER_ROTOPLEX, FID_Name|TSTRING, "Rotoplex", FID_Category|TLONG, CCF_EFFECT, FID_Actions|TPTR, RotoplexActions, FID_Fields|TPTR, RotoplexFields, FID_Size|TLONG, sizeof(struct Rotoplex), TAGEND)); } /***************************************************************************** ** Command: Expunge() */ ERROR CMDExpunge(void) { if (sine) { FreeMemory(sine); sine = NULL; } if (cosine) { FreeMemory(cosine); cosine = NULL; } if (RotoplexClass) { Action(AC_Free, RotoplexClass, NULL); RotoplexClass = NULL; } return(ERR_Okay); } /***************************************************************************** ** Rotoplex: ActionNotify */ static ERROR ROTOPLEX_ActionNotify(struct Rotoplex *Self, struct acActionNotify *NotifyArgs) { if ((NotifyArgs->ActionID IS AC_Draw) AND (NotifyArgs->Error IS ERR_Okay)) { struct mtDrawRGBPixel pixel; struct Bitmap *bitmap; OBJECTPTR drawable; FLOAT temp, x2, y2, z2; LONG width, height, offsetx, offsety, colour, i; LONG (*QDrawRGBPixel)(struct Bitmap *, struct mtDrawRGBPixel *); if (!Self->Object) { if (AccessMemory(Self->ObjectMID, MEM_READWRITE, (void **)&Self->Object) != ERR_Okay) { return(ObjectError(Self, ERH_ActionNotify, ERR_AccessMemory)); } Self->Morph = Self->Object + Self->MaxElements; } if (AccessObject(Self->DrawableID, 0, &drawable) != ERR_Okay) { return(ObjectError(Self, ERH_Draw, ERR_ExclusiveDenied)); } if ((GetField(drawable, FID_Bitmap, FT_POINTER, &bitmap) != ERR_Okay) OR (!bitmap)) { ReleaseObject(drawable); return(ObjectError(Self, ERH_Draw, ERR_FieldNotSet)); } if (ObtainMethod(bitmap, MT_DrawRGBPixel, (void **)&QDrawRGBPixel) != ERR_Okay) return(ERR_Failed); GetFields(drawable, FID_Width|TLONG, &width, FID_Height|TLONG, &height, TAGEND); offsetx = Self->XCoord + (width / 2); offsety = Self->YCoord + (height / 2); if (Self->ZCoord < 0) Self->ZCoord = 0.0; if (Self->ZCoord > 100) Self->ZCoord = 100.0; for (i = 0; i < Self->MaxElements; i++) { x2 = Self->Object[i].X; y2 = Self->Object[i].Y; z2 = Self->Object[i].Z; /* Rotate the X axis */ temp = z2; z2 = z2 * cosine[(WORD)Self->fAngleX] - y2 * sine[(WORD)Self->fAngleX]; y2 = y2 * cosine[(WORD)Self->fAngleX] + temp * sine[(WORD)Self->fAngleX]; /* Rotate the Y axis */ temp = z2; z2 = z2 * cosine[(WORD)Self->fAngleY] - x2 * sine[(WORD)Self->fAngleY]; x2 = x2 * cosine[(WORD)Self->fAngleY] + temp * sine[(WORD)Self->fAngleY]; /* Calculate colour based on new Z position (-1 < Z < 1) */ colour = (((z2 + 1) / 1) * 255) / 2; if (colour < 0) colour = 0; if (colour > 255) colour = 255; pixel.Red = colour; pixel.Green = colour; pixel.Blue = colour; /* Finally scale the (x,y) coordinates to enlarge or shrink the object */ pixel.XCoord = (LONG)(x2 * (Self->ZCoord)) + offsetx; pixel.YCoord = (LONG)(y2 * (Self->ZCoord)) + offsety; QDrawRGBPixel(bitmap, &pixel); } ReleaseObject(drawable); } return(ERR_Okay); } /***************************************************************************** ** Rotoplex: Free */ static ERROR ROTOPLEX_Free(struct Rotoplex *Self, APTR Void) { OBJECTPTR drawable; UnsubscribeTimer(Self->Head.UniqueID); /*** Remove all subscriptions to our drawable container ***/ if (Self->DrawableID) { if (AccessObject(Self->DrawableID, 5000, &drawable) IS ERR_Okay) { UnsubscribeAction(drawable, NULL, GetUniqueID(Self)); ReleaseObject(drawable); } } if (Self->Object) { ReleaseMemory(Self->Object); Self->Object = NULL; Self->Morph = NULL; } if (Self->ObjectMID) { FreeMemoryID(Self->ObjectMID); Self->ObjectMID = NULL; } return(ERR_Okay); } /***************************************************************************** ** Rotoplex: Init */ static ERROR ROTOPLEX_Init(struct Rotoplex *Self, APTR Void) { OBJECTPTR drawable; WORD nextshape; if (!Self->DrawableID) return(ObjectError(Self, ERH_Init, ERR_UnsupportedOwner)); if (!Self->MaxElements) Self->MaxElements = DEFAULT_DOTS; if (!Self->Shape) Self->Shape = RandomNumber(RP_END-1)+1;; /*** Calculate the 3D object ***/ if (!Self->Object) { if (AllocMemory(sizeof(struct DotPixel) * Self->MaxElements * 2, Self->Head.MemFlags, (APTR *)&Self->Object, &Self->ObjectMID) IS ERR_Okay) { Self->Morph = Self->Object + Self->MaxElements; CalculateObject(Self, Self->Object, Self->Shape, 0, Self->MaxElements, FALSE); } else return(ObjectError(Self, ERH_Init, ERR_AllocMemory)); } nextshape = Self->Shape; while (nextshape IS Self->Shape) nextshape = RandomNumber(RP_END-1)+1; Self->Shape = nextshape; Self->fAngleX = Self->AngleX; Self->fAngleY = Self->AngleY; Self->fAngleZ = Self->AngleZ; Self->Morphing = FALSE; Self->MorphPos = 0; /*** Subscribe to our container's Resize action ***/ if (AccessObject(Self->DrawableID, 5000, &drawable) IS ERR_Okay) { SubscribeAction(drawable, AC_Draw, Self); ReleaseObject(drawable); } /*** Initialise the Timer ***/ return(SubscribeTimer(Self->Head.UniqueID, 20)); } /***************************************************************************** ** Rotoplex: Move */ static ERROR ROTOPLEX_Move(struct Rotoplex *Self, struct acMove *Args) { if (Args) { Self->XCoord += Args->XChange; Self->YCoord += Args->YChange; Self->ZCoord += Args->ZChange; return(ERR_Okay); } else return(ERR_Args); } /***************************************************************************** ** Rotoplex: NewObject */ static ERROR ROTOPLEX_NewObject(struct Rotoplex *Self, APTR Args) { Self->ZCoord = 50.0; Self->MaxElements = DEFAULT_DOTS; Self->Shape = RandomNumber(RP_END-1)+1;; Self->XSpeed = 4.0; Self->YSpeed = 4.0; Self->ZSpeed = 4.0; return(ERR_Okay); } /***************************************************************************** ** Rotoplex: NewOwner */ static ERROR ROTOPLEX_NewOwner(struct Rotoplex *Self, struct acNewOwner *Args) { OBJECTID owner; if (!(Self->Head.ObjectFlags & NF_INITIALISED)) { owner = Args->NewOwnerID; while ((owner) AND (GetClassID(owner) != ID_RENDER)) { owner = GetOwnerID(owner); } if (owner) Self->DrawableID = owner; } return(ERR_Okay); } /***************************************************************************** ** Rotoplex: ReleaseObject */ static ERROR ROTOPLEX_ReleaseObject(struct Rotoplex *Self, APTR Void) { if (Self->Object) { ReleaseMemory(Self->Object); Self->Object = NULL; Self->Morph = NULL; } return(ERR_Okay); } /***************************************************************************** ** Action: Scroll */ static ERROR ROTOPLEX_ScrollToPoint(struct Rotoplex *Self, struct acScrollToPoint *Args) { if (Args->Flags & STF_X) Self->XCoord = Args->XCoord; if (Args->Flags & STF_Y) Self->YCoord = Args->YCoord; if (Args->Flags & STF_Z) Self->ZCoord = Args->ZCoord; if (Self->ZCoord < 0) Self->ZCoord = 0.0; else if (Self->ZCoord > 100) Self->ZCoord = 100.0; return(ERR_Okay); } /***************************************************************************** ** Action: Timer */ static ERROR ROTOPLEX_Timer(struct Rotoplex *Self, struct acTimer *Args) { LONG CalcPixels, newshape, i; /*** Gain access to the 3D object definition if we have not already ***/ if (!Self->Object) { if (AccessMemory(Self->ObjectMID, MEM_READWRITE, (void **)&Self->Object) != ERR_Okay) { return(ObjectError(Self, ERH_ActionNotify, ERR_AccessMemory)); } Self->Morph = Self->Object + Self->MaxElements; } /* Calculate the next object that we are going to morph into. Note that ** we calculate the next object in stages rather than all-at-once, as ** it will suck up too much processor time otherwise. */ if (Self->Morphing IS FALSE) { CalcPixels = Self->MaxElements/100; CalculateObject(Self, Self->Morph, Self->Shape, Self->MorphPos, Self->MorphPos + CalcPixels, TRUE); Self->MorphPos += CalcPixels; if (Self->MorphPos >= Self->MaxElements) { /*** If we have calculated all the elements, reset the morphing process ***/ Self->Morphing = TRUE; Self->MorphPos = 0; Self->MorphCount = 0; newshape = Self->Shape; while (newshape IS Self->Shape) newshape = RandomNumber(RP_END-1)+1; Self->Shape = newshape; } } /*** Spin the object on each angle ***/ Self->fAngleX += Self->XSpeed; if (Self->fAngleX >= 360) Self->fAngleX = 0.0; Self->fAngleY += Self->YSpeed; if (Self->fAngleY >= 360) Self->fAngleY = 0.0; Self->fAngleZ += Self->ZSpeed; if (Self->fAngleZ >= 360) Self->fAngleZ = 0.0; /*** Morph the object into the next shape ***/ if (Self->Morphing IS TRUE) { for (i = 0; i < Self->MaxElements; i++) { Self->Object[i].X += Self->Morph[i].X; Self->Object[i].Y += Self->Morph[i].Y; Self->Object[i].Z += Self->Morph[i].Z; } Self->MorphCount++; if (Self->MorphCount >= MORPH_SPEED) Self->Morphing = FALSE; } DelayMsg(AC_Draw, Self->DrawableID, NULL); return(ERR_Okay); } /***************************************************************************** ** Function: CalculateObject() ** ** The purpose of this function is to calculate the 3D coordinates of our ** objects. The coordinates must be from -1.0 to +1.0, so some scaling takes ** place for some of the calculated objects. */ void CalculateObject(struct Rotoplex *Self, struct DotPixel *Dots, LONG Shape, LONG Start, LONG End, LONG Distance) { FLOAT u, v, max; LONG i, longtitude, latitude; #define PI 3.141592654 if (End > Self->MaxElements) End = Self->MaxElements; if (Shape IS RP_HELIX) { for (i=Start; i < End; i++) { u = ((FLOAT)RandomNumber(12566 - 1) + 1) / 1000; /* 0 < u < (4 * PI) */ v = ((FLOAT)RandomNumber(6283 - 1) + 1) / 1000; /* 0 < v < (2 * PI) */ Dots[i].X = cos(u) * (2 + cos(v)); Dots[i].Y = sin(u) * (2 + cos(v)); Dots[i].Z = (u - (2 * PI)) + sin(v); } /*** Scale down to values of -1.0 to +1.0 ***/ max = ((2 * PI) + sin(2*PI)); for (i=Start; i < End; i++) { Dots[i].X = Dots[i].X / max; Dots[i].Y = Dots[i].Y / max; Dots[i].Z = Dots[i].Z / max; } } else if (Shape IS RP_ENNEPER) { for (i=Start; i < End; i++) { u = ((FLOAT)RandomNumber(28000 - 1) + 1) / 10000 - 1.4; /* -1.4 < u < 1.4 */ v = ((FLOAT)RandomNumber(28000 - 1) + 1) / 10000 - 1.4; /* -1.4 < v < 1.4 */ Dots[i].X = u * (1 + (v * v)) - (u * u * u); Dots[i].Y = v * (1 + (u * u)) - (v * v * v); Dots[i].Z = (u * u) - (v * v); } /*** Scale down to values of -1.0 to +1.0 ***/ for (i=Start; i < End; i++) { Dots[i].X = Dots[i].X / 1.96; Dots[i].Y = Dots[i].Y / 1.96; Dots[i].Z = Dots[i].Z / 1.96; } } else if (Shape IS RP_CUBE) { for (i=Start; i < End; i++) { #define CUBE_MAX 200000 Dots[i].X = 0.0; Dots[i].Y = 0.0; Dots[i].Z = 0.0; while ((Dots[i].X < 0.95) AND (Dots[i].X > -0.95) AND (Dots[i].Y < 0.95) AND (Dots[i].Y > -0.95) AND (Dots[i].Z < 0.95) AND (Dots[i].Z > -0.95)) { Dots[i].X = ((FLOAT)(RandomNumber(20000)-10000)) * 0.0001; Dots[i].Y = ((FLOAT)(RandomNumber(20000)-10000)) * 0.0001; Dots[i].Z = ((FLOAT)(RandomNumber(20000)-10000)) * 0.0001; } } } else if (Shape IS RP_SPHERE) { for (i=Start; i < End; i++) { longtitude = RandomNumber(180); latitude = RandomNumber(10); Dots[i].X = sin(longtitude) * cos(latitude); Dots[i].Y = sin(longtitude) * sin(latitude); Dots[i].Z = cos(longtitude); } } /*** Convert coordinates to morphing distance values if requested ***/ if (Distance IS TRUE) { for (i=Start; i < End; i++) { Dots[i].X = ((Dots[i].X - Self->Object[i].X) / MORPH_SPEED); Dots[i].Y = ((Dots[i].Y - Self->Object[i].Y) / MORPH_SPEED); Dots[i].Z = ((Dots[i].Z - Self->Object[i].Z) / MORPH_SPEED); } } } /***************************************************************************** ###FIELD### Name: AngleX Short: Defines the angle of rotation on the X axis. Type: FLOAT Status: Read/Init ###DESCRIPTION### <p>The AngleX field defines the current rotation of the 3D object on the X axis. When the field is set to 0, the object will appear in its default non-rotated state on the X axis.</p> ###SEE ALSO### Fields: AngleY, AngleZ, XSpeed, YSpeed, ZSpeed ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Name: AngleY Short: Defines the angle of rotation on the Y axis. Type: FLOAT Status: Read/Init ###DESCRIPTION### <p>The AngleY field defines the current rotation of the 3D object on the Y axis. When the field is set to 0, the object will appear in its default non-rotated state on the Y axis.</p> ###SEE ALSO### Fields: AngleX, AngleZ, XSpeed, YSpeed, ZSpeed ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Name: AngleZ Short: Defines the angle of rotation on the Z axis. Type: FLOAT Status: Read/Init ###DESCRIPTION### <p>The AngleZ field defines the current rotation of the 3D object on the Z axis. When the field is set to 0, the object will appear in its default non-rotated state on the Z axis.</p> ###SEE ALSO### Fields: AngleX, AngleY, XSpeed, YSpeed, ZSpeed ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Name: DScroll Short: Reference to a scrollbar for depth based movement. Type: OBJECTID Status: Read/Write ###DESCRIPTION### <p>If you want to attach a positioning scrollbar to the Z coordinate of a Rotoplex object, set this field to an object belonging to the Scroll class. So long as the scroll object is set up to provide full scrollbar functionality, the user will be able to manipulate the position of the Rotoplex object along the Z axis.</p> ###SEE ALSO### Class: Scroll Fields: HScroll, VScroll ###END### *****************************************************************************/ static ERROR SET_DScroll(struct Rotoplex *Self, OBJECTID *ScrollID) { struct mtUpdateScroll scroll; OBJECTPTR scrollbar; if (GetClassID(*ScrollID) IS ID_SCROLL) { if (AccessObject(*ScrollID, 5000, &scrollbar) IS ERR_Okay) { SetFields(scrollbar, FID_Object|TLONG, Self->Head.UniqueID, FID_Axis|TLONG, AXIS_Z, TAGEND); Self->DScroll = *ScrollID; scroll.ViewSize = 20; scroll.PageSize = 100; scroll.Position = Self->ZCoord; scroll.Unit = 0; Action(MT_UpdateScroll, scrollbar, &scroll); ReleaseObject(scrollbar); return(ERR_Okay); } else return(ObjectError(Self, ERH_SetField, ERR_AccessObject)); } else { DPrintF("@SetField:","[Rotoplex] Attempt to set the DScroll field with an invalid object."); return(ERR_Failed); } } /***************************************************************************** ###FIELD### Name: HScroll Short: Reference to a scrollbar for horizontal movement. Type: OBJECTID Status: Read/Write ###DESCRIPTION### <p>If you want to attach a positioning scrollbar to the X coordinate of a Rotoplex object, set this field to an object belonging to the Scroll class. So long as the Scroll object is set up to provide full scrollbar functionality, the user will be able to manipulate the position of the Rotoplex object along the horizontal axis.</p> ###SEE ALSO### Class: Scroll Fields: DScroll, VScroll ###END### *****************************************************************************/ static ERROR SET_HScroll(struct Rotoplex *Self, OBJECTID *Scroll) { struct mtUpdateScroll scroll; OBJECTPTR scrollbar; if (GetClassID(*Scroll) IS ID_SCROLL) { if (AccessObject(*Scroll, 5000, &scrollbar) IS ERR_Okay) { SetField(scrollbar, FID_Object, FT_LONG, &Self->Head.UniqueID); ReleaseObject(scrollbar); Self->HScroll = *Scroll; Self->HPosition = 500.0; scroll.PageSize = 1000; scroll.Position = Self->HPosition; scroll.ViewSize = 100; scroll.Unit = 0; return(ActionMsg(MT_UpdateScroll, Self->HScroll, &scroll)); } else return(ObjectError(Self, ERH_SetField, ERR_ExclusiveDenied)); } else { DPrintF("@SetField:","[Rotoplex] Attempt to set the HScroll field with an invalid object."); return(ERR_Failed); } } /***************************************************************************** ###FIELD### Name: MaxElements Short: The total number of dots used to make up the object. Type: LONG Status: Read/Init ###DESCRIPTION### <p>You can set the total amount of dots that the Rotoplex object should use by setting this field. If you do not set this field, the Rotoplex object will set a default value here before creating the 3D pixel array.</p> ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Name: Object Short: Allows custom objects to be created. Type: struct DotObject * Status: Read/Init ###DESCRIPTION### <p>This field points to an array of 3D dots that specify the makeup of the rotating object. Writing to the field is useful if you wish to create your own 3-Dimensional object rather than use a pre-generated one.</p> <p>The DotObject structure is defined as follows:</p> <pre> struct Pixels { LONG TotalPixels; struct DotPixel *Pixels; }; </pre> <p>Keep in mind that you will also need to set the TotalDots field so that the Rotoplex class knows how large the array is.</p> ###END### *****************************************************************************/ static ERROR SET_Object(struct Rotoplex *Self, struct DotObject *Pixels) { LONG i; if (!Pixels) return(ERR_Failed); if (Pixels->TotalPixels < 1) return(ERR_BadData); if (Self->ObjectMID) { if (Self->Object) { ReleaseMemory(Self->Object); Self->Object = NULL; } FreeMemoryID(Self->ObjectMID); Self->ObjectMID = NULL; } if (AllocMemory(sizeof(struct DotPixel) * Pixels->TotalPixels * 2, Self->Head.MemFlags, (APTR *)&Self->Object, &Self->ObjectMID) IS ERR_Okay) { Self->Morph = Self->Object + Pixels->TotalPixels; Self->MaxElements = Pixels->TotalPixels; for (i=0; i < Pixels->TotalPixels; i++) { Self->Object[i].X = Pixels->Pixels[i].X; Self->Object[i].Y = Pixels->Pixels[i].Y; Self->Object[i].Z = Pixels->Pixels[i].Z; } return(ERR_Okay); } else return(ObjectError(Self, ERH_Init, ERR_Memory)); } /***************************************************************************** ###FIELD### Name: Shape Short: A pre-calculated object can be selected by writing to this field. Type: LONG Status: Read/Init ###DESCRIPTION### <p>You can define what 3D object you want the Rotoplex to use by selecting one of the available types as listed here:</p> <blockquote> RP_ENNEPER<br> RP_HELIX<br> RP_SPHERE<br> RP_CUBE </blockquote> <p>If the field is not set, an object type will be randomly chosen from the available list on initialisation.</p> ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Names: XSpeed Short: Determines the speed of rotation along the X axis. Type: FLOAT Status: Read/Init ###DESCRIPTION### <p>You can set the speed of the object's rotation along the X axis by writing to this field. The acceptable range for the value is between -1000 and +1000, but typically you will get the best results by using values between -5 and +5.</p> ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Names: YSpeed Short: Determines the speed of rotation along the Y axis. Type: FLOAT Status: Read/Init ###DESCRIPTION### <p>You can set the speed of the object's rotation along the Y axis by writing to this field. The acceptable range for the value is between -1000 and +1000, but typically you will get the best results by using values between -5 and +5.</p> ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Names: ZSpeed Short: Determines the speed of rotation along the Z axis. Type: FLOAT Status: Read/Init ###DESCRIPTION### <p>You can set the speed of the object's rotation along the Z axis by writing to this field. The acceptable range for the value is between -1000 and +1000, but typically you will get the best results by using values between -5 and +5.</p> ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Name: VScroll Short: Reference to a scrollbar for vertical movement. Type: OBJECTID Status: Read/Write ###DESCRIPTION### <p>If you want to attach a positioning scrollbar to the Y coordinate of a Rotoplex object, set this field to an object belonging to the Scroll class. So long as the Scroll object is set up to provide full scrollbar functionality, the user will be able to manipulate the position of the Rotoplex object along the vertical axis.</p> ###END### *****************************************************************************/ static ERROR SET_VScroll(struct Rotoplex *Self, OBJECTID *Scroll) { struct mtUpdateScroll scroll; OBJECTPTR scrollbar; if (GetClassID(*Scroll) IS ID_SCROLL) { if (AccessObject(*Scroll, 5000, &scrollbar) IS ERR_Okay) { SetField(scrollbar, FID_Object, FT_LONG, &Self->Head.UniqueID); ReleaseObject(scrollbar); Self->VScroll = *Scroll; Self->VPosition = 500.0; scroll.PageSize = 1000; scroll.Position = Self->VPosition; scroll.ViewSize = 100; scroll.Unit = 0; return(ActionMsg(MT_UpdateScroll, Self->VScroll, &scroll)); } else return(ObjectError(Self, ERH_SetField, ERR_ExclusiveDenied)); } else { DPrintF("@SetField:","[Rotoplex] Attempt to set the VScroll field with an invalid object."); return(ERR_Failed); } } /***************************************************************************** ###FIELD### Name: XCoord Short: Defines the horizontal coordinate of the Rotoplex object. Type: FLOAT Status: Read/Write ###DESCRIPTION### <p>The horizontal position of the Rotoplex object can be altered by writing to this field. The new position will be reflected on the next drawing cycle, which will typically be within the next 1/25th of a second.</p> ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Name: YCoord Short: Defines the vertical coordinate of the Rotoplex object. Type: FLOAT Status: Read/Write ###DESCRIPTION### <p>The vertical position of the Rotoplex object can be altered by writing to this field. The new position will be reflected on the next drawing cycle, which will typically be within the next 1/25th of a second.</p> ###END### *****************************************************************************/ /***************************************************************************** ###FIELD### Name: ZCoord Short: Defines the depth-based coordinate of the Rotoplex object. Type: FLOAT Status: Read/Write ###DESCRIPTION### <p>The Z coordinate of the Rotoplex object can be altered by writing to this field. The range is limited between -100 and +100. 0 is considered to be the center of the Z plane, while -100 is the furthest possible point away from the field of view. A value of +100 is the closest possible point to the beginning of the field of view.</p> ###END### *****************************************************************************/