Login [Register]
Don't have an account? Register now to chat, post, use our tools, and much more.
Hi ACagliano,

I think there are formulas to write arctan with others trigonometric functions.

Maybe it can help you.

Resolved the arctangent bug. Posting the code used for all three trig routines for others to optimize.

const char cosLUT[72] = {127, 126, 125, 122, 119, 115, 109, 104, 97, 89, 81, 72, 63, 53, 43, 32, 22, 11, 0, -11, -22, -32, -43, -53, -63, -72, -81, -89, -97, -104, -109, -115, -119, -122, -125, -126, -127, -126, -125, -122, -119, -115, -109, -104, -97, -89, -81, -72, -63, -53, -43, -32, -22, -11, 0, 11, 22, 32, 43, 53, 63, 72, 81, 89, 97, 104, 109, 115, 119, 122, 125, 126};
const char sinLUT[72] = {0, 11, 22, 32, 43, 53, 63, 72, 81, 89, 97, 104, 109, 115, 119, 122, 125, 126, 127, 126, 125, 122, 119, 115, 109, 104, 97, 89, 81, 72, 63, 53, 43, 32, 22, 11, 0, -11, -22, -32, -43, -53, -63, -72, -81, -89, -97, -104, -109, -115, -119, -122, -125, -126, -127, -126, -125, -122, -119, -115, -109, -104, -97, -89, -81, -72, -63, -53, -43, -32, -22, -11};
const int arctanLUT[18] = {0, 11, 22, 34, 46, 59, 73, 88, 106, 126, 151, 181, 219, 272, 348, 473, 720, 1451};

signed char byteCos(unsigned char x){
    return cosLUT[x];

signed char byteSin(unsigned char x){
    return sinLUT[x];

char byteATan(long non_x, long x){
    char index = 17, quadrant;
    int value;
    if(x == 0){     // handle infinity
        if(non_x > 0) return 18;    // 90 degrees
        if(non_x < 0) return 54;    // 270 degrees
    if(non_x == 0){
        if(x > 0) return 0;
        if(x < 0) return 36;
    if(non_x > 0 && x > 0) quadrant = 0;
    else if(non_x > 0 && x < 0) quadrant = 1;
    else if(non_x < 0 && x < 0) quadrant = 2;
    else quadrant = 3;
    if(!(quadrant & 1))  value = abs(127 * non_x / x);
    else value = abs(127 * x / non_x);
    while(index >= 0){
        if(value >= arctanLUT[index]) break;
   return 18 * quadrant + index;

So, officially all that remains before the demo release of this game is:
1) Fix bug where objects do not render at some orientations (Code will be posted below).
2) Fix bug where projectiles move slower as they approach the Z axis, and freeze almost entirely on the Z (Code will also be posted below).
3) Code a collision detection algorithm that interpolates points between a projectile's current position and next position (or current and last, depending on how coded), and determines if the projectile will collide with any object.

Rendering Code:
** sqrt is done as a temporary fix for the fact that RENDER_DISTANCE^2 - distance^2 != remaining distance. Could I just do sqrt(remaining) though?
** Issue is that sprites dont render at some angles of rotation (of your ship).

void GUI_PrepareFrame(MapData_t *map, renderitem_t *renderbuffer, Position_t *playerpos, framedata_t* frame){
    char i, count = 0, scale;
    double val = 180/M_PI;
    unsigned long player_x = playerpos->coords.x;
    unsigned long player_y = playerpos->coords.y;
    unsigned long player_z = playerpos->coords.y;
    unsigned long item_x, item_y, item_z;
    long distance_x, distance_y, distance_z;
    unsigned long distance;
    memset(renderbuffer, 0, sizeof(renderitem_t) * 20);
    for(i=0; i<20; i++){
        MapData_t *item = &map[i];
        item_x = item->position.coords.x;
        item_y = item->position.coords.y;
        item_z = item->position.coords.z;
        distance_x = item_x - player_x;
        distance_y = item_y - player_y;
        distance_z = item_z - player_z;
        distance = (long)sqrt(r_GetDistance(distance_x, distance_y, distance_z));
        if(distance < RENDER_DISTANCE){
            char objectvect_xz = byteATan(distance_z, distance_x);
            char objectvect_y = byteATan(distance_y, distance_x);
            char diff_xz = objectvect_xz - playerpos->angles.xz;
            char diff_y = objectvect_y - playerpos->angles.y;
            //objectvect_xz = AngleOpsBounded(objectvect_xz, -1 * playerpos->angles.xz);
            //objectvect_y = AngleOpsBounded(objectvect_y, -1 * playerpos->angles.y);
            //vectordiff_xz = AngleOpsBounded(objectvect_xz, playerpos->angles.xz);
           // vectordiff_y = AngleOpsBounded(objectvect_y, playerpos->angles.y);
            if((abs(diff_xz) <= 9) && (abs(diff_y) <= 9)){
                int vcenter = vHeight>>1 + yStart;
                renderitem_t *render = &renderbuffer[count++];
                render->spriteid = item->entitytype-1;
                render->distance = (RENDER_DISTANCE - distance) * 100 / RENDER_DISTANCE;
                diff_xz += 10;
                render->x = vWidth * diff_xz / 19;
                diff_y += 10;
                render->y = (vHeight * diff_y / 19);
    frame->count = count;
        heapsort(renderbuffer, count);
        for(i = 0; i < count; i++){
            renderitem_t *render = &renderbuffer[i];
                gfx_sprite_t* sprite = (gfx_sprite_t*)trekvfx[render->spriteid];
                gfx_sprite_t* uncompressed;
                gfx_sprite_t* scaled;
                char scale = render->distance;
                int width, height;
                    case et_ship-1:
                        uncompressed = gfx_MallocSprite(64, 32);
                        uncompressed = gfx_MallocSprite(32, 32);
                zx7_Decompress(uncompressed, sprite);
                //if(scale != -1){
                width = uncompressed->width * scale / 100;
                height = uncompressed->height * scale / 100;
                scaled = gfx_MallocSprite(width, height);
                scaled->width = width;
                scaled->height = height;
                gfx_ScaleSprite(uncompressed, scaled);
                gfx_TransparentSprite(scaled, render->x - (scaled->width>>1), render->y - (scaled->height>>1));
                // }
                //render->type; // use this to locate sprite

Movement Code:
** Issue is that projectiles slow down to 0 speed as you approach the Z axis (+ and -). They have full speed at X. This code also uses the trig above. Strangely enough, you ship, which uses the same movement routine (MoveEntity) is unaffected by this bug.

void map_MoveObjects(MapData_t* map, char tick){
    char i;
    for(i = 0; i < 20; i++){
        MapData_t *entity = &map[i];
                if(!tick) proc_MoveEntity(&entity->position, entity->speed>>1);
                else proc_MoveEntity(&entity->position, entity->speed % 2);
                if(!entity->entitystats.weapon.range--) memset(entity, 0, sizeof(MapData_t));

void proc_MoveEntity(Position_t *pos, char speed){
    int coord_x = pos->coords.x, coord_y = pos->coords.y, coord_z = pos->coords.z;
    int vector_x = pos->vectors.x, vector_y = pos->vectors.y, vector_z = pos->vectors.z;
    coord_x += (speed * vector_x);
    coord_y += (speed * vector_y);
    coord_z += (speed * vector_z);
    pos->coords.x = coord_x;
    pos->coords.y = coord_y;
    pos->coords.z = coord_z;

void AnglesToVectors(Position_t *pos){
// this is how i convert angles to vectors
// x and z are divided by 127 to ensure the value remains N/127
// a byte overflow doesn't guarantee this
// y is not /127 because it's return will always be in range
    unsigned char xzangle = pos->angles.xz, yangle = pos->angles.y;
    pos->vectors.x = byteCos(xzangle) * byteCos(yangle) / 127;
    pos->vectors.z = byteSin(xzangle) * byteCos(yangle) / 127;
    pos->vectors.y = byteSin(yangle);
    //pos->vectors[2] = z vector
Update -- 0.75 alpha

Made a few minor adjustments to the game. As you might be able to tell, graphics have accelerated a good deal from my last screenshot share. In addition you are now able to, via the Sensors tab, select any object that is within sensor range to view its distance from your ship and it's angles relative to origin. This means that to navigate your ship towards an object you have selected, you turn your ship until your rotational gauge mirrors the angles you are shown for that object, then move in that direction.

Graphics have speed up mostly because I have stopped decompressing every rendered sprite in every iteration, every time and stopped mallocating/freeing space for said sprites for each object per iteration. Before I get heaped on for even doing this at all... I set it up that way initially for ease of testing one single sprite and had no reason to change it until the entire sprite set was implemented. Now that it is... I have changed that. Now, sprites are unpacked to RAM when the program starts, and 2 RAM areas are allocated for each of the two sprite functions... scaling and rotating. Those areas are repeatedly used until freed at the end of the program.

Before the demo release I have yet to fix the one, annoying bug that has annoyed me really more than any other bug I've encountered programming... the fact that projectiles while moving normally on the X axis move progressively slower as you move closer to the Z axis in both directions, and stop moving completely on the Z axis. Even more mind numbing is that your ship does not exhibit this behavior... despite it using the same trig functions (angles=>vectors), and the same movement routines.

Also before the demo release, I plan to implement saving/reloading your game, as well as create a few default ship stat files that you can load in to try to fight the demo AI. Each stat file will load you a ship that has different characteristics. Additionally, I will also be adding a settings menu that will eventually allow you to control different, technical, aspects of your game.
Ok, help needed. This is related to the Z bug, but also unrelated, as it contributes to why some things don't render at some angles. It is the arctangent routine I wrote to get angles from the 3d vectors.

The Lookup Table

const int arctanLUT[64] = {0, 3, 6, 9, 12, 15, 18, 22, 25, 28, 31, 35, 38, 41, 45, 48, 52, 56, 60, 63, 67, 71, 76, 80, 84, 89, 94, 99, 104, 109, 115, 120, 126, 133, 140, 147, 154, 162, 171, 180, 190, 200, 211, 224, 237, 252, 268, 286, 306, 329, 354, 384, 418, 458, 507, 565, 638, 731, 856, 1029, 1289, 1721, 2585, 5173};

The Algorithm:

unsigned char byteATan(long non_x, long x){
    char index = 63, quadrant;
    int value;
    if(x == 0){     // handle infinity
        if(non_x > 0) return 63;    // 90 degrees
        if(non_x < 0) return 191;    // 270 degrees
    if(non_x == 0){
        if(x >= 0) return 0;
        if(x < 0) return 127;
    if(non_x > 0 && x > 0) quadrant = 0;
    else if(non_x > 0 && x < 0) quadrant = 1;
    else if(non_x < 0 && x < 0) quadrant = 2;
    else quadrant = 3;
    if(!(quadrant & 1))  value = abs(127 * non_x / x);
    else value = abs(127 * x / non_x);
    while(index > 0){
        if(value >= arctanLUT[index]) break;
   return 64 * quadrant + index;

How it works:
Input to the routine will have two arguments, the magnitude of the translation on the non-x axis, and the magnitude of the translation on the x-axis (because both xy and z angles are relative to X). As the formula for arctan is the non-x component of the vector divided by the x component (in this case).

First I handle the infinite cases. If the X value is 0, the result is actually infinite, so I manually return the equivalent of 90 degrees or 270 degrees, depending on whether the non-X axis vector magnitude is positive or negative.
Then I handle the zero cases. If the non-X value is 0, the result is 0 or 180 depending on whether the X axis vector magnitude is positive or negative.

That ends the special cases. For the rest of the algorithm, I am calculating the quadrant, then stepping through a lookup table, top down, returning the first INDEX at which the value of the LUT at that index is less than the ratio of the non-X to the X vector (inverted for quadrants 1 & 3). That is then mapped to the proper quadrant via 64 * quadrant + index.

Where It Breaks:
If both the X and the non-X component of the vector are zero. This *should* return ... well I think zero, but I'm not entirely sure (trig was never my strength and I'm honestly shocked I was able to devise an algo that seems to only break in this case). The first thing we hit and trigger is the infinite case... if(x == 0). We enter that, but the only available options are if(non_x > 0) and if(non_x < 0). Neither are true here, so we exit that having done nothing. The next thing we enter is the if(non_x == 0) statement, which is true, but again, both of its children conditions are false, so we exit that. All of the quadrant detectors are false, so we evaluate the "else", which sets quadrant to 3. Then we parse the Lookup Table, and loop down to index 0. The loop exits with index == 0, having not found a value to break with, and the return statement evaluates to quadrant (3) * 64 + index (0) = 192, or 270 degrees. This is obviously wrong.

So let me ask all of you... is there a fix, or even a better way to do this? My first thought was to just add an if(x == 0) return 0; line within the infinity case handlers, but that might break other things.

Edit: Upon further testing, this algorithm works when both angles are between 192 degrees and 63 degrees, for positive X values, inclusively. It breaks (by returning +-127 for the other angle) when either the rotation and the pitch are between 63 and 192, for negative X values.

const int arctanLUT[64] = {0, 3, 6, 9, 12, 15, 18, 22, 25, 28, 31, 35, 38, 41, 45, 48, 52, 56, 60, 63, 67, 71, 76, 80, 84, 89, 94, 99, 104, 109, 115, 120, 126, 133, 140, 147, 154, 162, 171, 180, 190, 200, 211, 224, 237, 252, 268, 286, 306, 329, 354, 384, 418, 458, 507, 565, 638, 731, 856, 1029, 1289, 1721, 2585, 5173};

unsigned char byteATan(long non_x, long x){
    char index = 63, quadrant;
    int value;
    if(!x && !non_x) return 0;
    if(!x){     // handle infinity
        if(non_x > 0) return 63;    // 90 degrees
        if(non_x < 0) return 191;    // 270 degrees
    if(non_x >= 0 && x > 0) quadrant = 0;
    else if(non_x > 0 && x < 0) quadrant = 1;
    else if(non_x < 0 && x < 0) quadrant = 2;
    else quadrant = 3;
    if(!(quadrant & 1))  value = abs(127 * non_x / x);
    else value = abs(127 * x / non_x);
    while(index > 0){
        if(value >= arctanLUT[index]) break;
   return 64 * quadrant + index;

This routine breaks for any values between 64 and 192, where X is <0. It also seems to break when your ship is not at origin (0,0,0). The first angle is correct, but the second one seems to become 192, which is weird. Can someone please recommend any fixes? I'm not too good with trig, so while I know *when* this code is breaking, I'm not sure how to fix it.
Also, for the angle of rotation on X,Y, I can use arctangent of Y / X, but:
For the angle of pitch (Z axis), I only have the Z vector. Should I use an arcsine algorithm, or can I use arctangent with Z vector / distance?

Once I fix this, I need to only implement saving/loading and collision.

EDIT: Even when I use the built-in arctan routine, the second angle still returns one that becomes 128 (180 degrees) when the X value becomes negative. Thus, even if your ship is at angle 65, 0, once the X component of the vector becomes negative, the arctan of the second angle returns 128+angle. So a projectile being shot at 65, 0 actually is seen as being at 65, 128, and thus not rendered on screen.

EDIT2: I am perfectly open to having a custom arctan routine, and indeed that is the end goal. I'll try to write a better one myself eventually unless someone else beats me to it (my old one is above for anyone wishing to fix it up). For anyone interested, the data we have is: (1) the vector magnitude (distance), (2) The component of the vector on all 3 axis (X,Y = horizontal, Z = vertical). The intent is to return two angles within the range [0,255], corresponding to the true angle degrees [0, 359].

EDIT3: atan2(y/x) works perfectly fine, wherein the signage of x and y comprise the proper quadrant of the angle. However, doing atan2(z/x) causes atan2(0/-X) to return 128 (180 degrees), which is correct, yet wrong in my case. How can I stop this from occurring when the Z component of the vector is 0, but yet treat the angle itself properly when the Z component of the vector is non-zero?
It's been a while, hasn't it.

Quick progress update. After a busy few months, and probably more time than i should have spent working on minecraft builds on the server, I've resumed coding on this. Looking back at the code, i noticed it was very bloated. I saw many instances of recurring or repetitive code. To that end, I've began a partial core rewrite. While keeping the routines to deal with menus and keypresses, I've started to re-write the code that handles ship modules, and restructured the modules themselves. They now follow a very class-esque structure. Segments of the setup shown below:

#ifndef ships_h
#define ships_h

#include "tech.h"
#define DRAW_CORE 0
#define DRAW_AUX 1
#define DRAW_RESERVE 2
typedef struct {
    char priority;          // determines which module draws power first
    signed int capacity;        // how much power a module can store
    signed int current;         // the amount of power the module currently has
    signed int baseusage;           // baseline power usage
    signed int usage;          // how much power the module expends when active
    bool alwaysUse;          // boolean to specify if the module is always using power when active
    char drawFrom;
} power_t;
// Related Functions
signed int power_GetBatteryPercent(power_t* power);
signed int power_GetExpendPercent(power_t* power);
//bool power_ExpendThisCycle(power_t* power);
//bool power_ReserveThisCycle(power_t* power);
void power_SetDraw(power_t* power, char source);
#define power_SetDrawCore(power_t* power) \
    power_SetDraw(power_t* power, DRAW_CORE)
#define power_SetDrawAux(power_t* power) \
    power_SetDraw(power_t* power, DRAW_AUX)
#define power_SetDrawReserve(power_t* power) \
    power_SetDraw(power_t* power, DRAW_RESERVE)

typedef struct {
    signed int max;
    signed int current;
} health_t;
// Related Functions
signed int health_GetHealthPercent(health_t* health);
void health_DamageModule(health_t* health, int amount);
#define health_RepairModule(health_t* health, int amount) \
    health_DamageModule(health_t* health, (-amount));

// Module Online States
#define STATE_ONLINE 1
// Module Set State Error Codes
// Module Effectiveness Step Values
#define STEP_DEFAULT 20
#define STEP_MID 10
#define STEP_LOW 5
typedef struct {
    unsigned char techclass;    // locked, determines compatible module classes
    unsigned char type;     // locked, determines compatible modules
    unsigned char techid;   // corresponds to equipped tech id (tech.h)
    char online;            // is module online
    power_t power;          // power control
    health_t health;        // health monitor
    stats_t stats;
} module_t;
char module_GetOnlineState(module_t* module);   // return online, offline, or repairing
char module_SetOnlineState(module_t* module, char state);   // set module state or return no power or no health
int module_GetEffectiveness(module_t* module, char steps);  // get % effectiveness of module
#define module_CopyData(module_t* module, module_t* source) \
    memcpy((module), (source), (sizeof(module_t));

typedef struct {
    module_t integrity;
    module_t warpcore;
    module_t warpdrive;
    module_t impulsedrive;
    module_t lifesupport;
    module_t sensors;
    module_t transporters;
    module_t tactical[6];    // tactical or shield modules
    module_t misc[3];       // miscellaneous modules
} ship_t;
// Core system defines
#define integrity (module_t*)&ship->integrity;
#define warpcore (module_t*)&ship->warpcore;
#define warpdrive (module_t*)&ship->warpdrive;
#define impulsedrive (module_t*)&ship->impulsedrive;
#define lifesupport (module_t*)&ship->lifesupport;
#define sensors (module_t*)&ship->sensors;
#define transporters (module_t*)&ship->transporters;

With the new system, not only can I deal with modules in a more streamlined fashion, but I can also create multiple ship instances (not quite sure that'll be needed, but still).

As you also might notice, how modules handle power is changing. Modules now have 3 power usage settings. They can draw from:
(1) The warp core (primary power system... will provide all systems a slight boost when using).
(2) Auxiliary power (backup power system, warp drive will not function, but should sustain ship in combat and repairing).
(3) Reserve power (when all else fails, modules can draw from their own internal power reserves. Module effectiveness is based on power remaining / capacity, so modules that are using their reserves will lose effectiveness)
Unlike before, where the entire ship had to be swapped over to either main power or auxiliary, it will now be possible to set modules individually to use different power sources.
Last but not least, it will also be possible to set priority on certain modules. This will cause these modules to draw power before others (in the event power supply is unable to keep up with all modules, you can alter priority to keep combat systems powered).
Progress Update

It's been a while, so I figured I would share some of the recent progress with this project so people know the project isn't dead. I haven't had as much time as I wanted to have to work on it, so progress has been slow, but it's still coming along.

First, I redesigned the GUI completely. Or more accurately, I attempted to, created an image that shattered a few mirrors, and then asked EaghanScarlette for help sprucing it up. She went way above and beyond and produced something so amazing that I almost did a backflip when I saw it. The background, as well as the in-progress remakes of the status screens are here:

Much of the core programming of this game is being redone, as I have completely restructured the ship "module" setup. Rather than having fairly large unions for the 3 major module types (system, tactical, miscellaneous), there is one main union for those three, each containing a separate union for each module sub-type (for instance warp drive, transporters, warp core within the system module; shields, phasers, torpedoes within the tactical modules). This makes each module occupy less RAM when a ship instance is loaded. To help me streamline accessing each module's data, I use the following functions:


void* module_GetDataPtr(module_t* module);
void* module_GetSysDataPtr(module_t* module, unsigned char type);
void* module_GetTactDataPtr(module_t* module, unsigned char type);

When loading a module, you call GetDataPtr(), which then determines the type of the module, then calls the secondary functions, GetSysDataPtr() or GetTactDataPtr() and later for Misc(). These handle figuring out the subtype and then accessing the correct data structure for that module, where either max speed, generator output, etc (different data for different modules) are stored. That is then returned as a (void*), so that I have one single routine. Later, depending on what module type/sub type it actually is, I can cast it back to it's correct type. It just seemed repetitive to keep repeating the same code to retrieve the data structures, when I can just use void* as a passthrough.

Secondly, the screenshot above shows unloaded modules because I need to re-make the ship appvars for testing, so for now, the modules being read out by the program are empty.

In other news, I've now got commandblockguy and I think GregsAStar looking into the USB/server-end aspects of this while I work on data management, and beckadam helping with my more-unstable-than-my-brain rendering engine. So in all, progress is happening.

Another planned feature of this game is the availability of the Time Drive, an extremely overpowered piece of technology. This tech can only be acquired through completing very difficult quests, and it has very steep power requirements when installed. The Drive may be configured to affect certain aspects of the the server-state... weapons locations, other ship locations, damage to other ships, damage to your own ship, the location of your own ship, planet states. When activated, the Time Drive causes the states of any selected entities to begin to revert every tick (so each forward tick moves those states one tick further backward). The more entities the Drive is set to affect, the more energy it draws... to the point where it will be impossible to run the Time Drive for more than a few seconds at a time. Additionally, the more physical entities on the server have their positions changed by the Drive, the more "temporal violation" points you get. Higher numbers of points cause Temporal Agent ships to spawn and attack you. Chroniton torpedoes are an effective response to this technology, are they are immune to the Drive's effects altogether, and their impact with a ship running a Time Drive disables said drive.

Updates to the project's website on ClrHome will be coming soon.
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 5 of 5
» All times are GMT - 5 Hours
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum