2018-08-14 03:58:18

I should point out that the orientation vectors used here are ... lazy, and so might result in problems (especially with form::rotate).
3dio: Contains 2d/3d geometry, now with groups, holes, and a file format that I think is compatible with the JQ3D editor. The biggest change to the engine itself is that the form interface now has a method for intersection with boxes, instead of just spheres, and attempts at making it play nicer with orientation vectors.

Includes2018: Updates to my other includes. The biggest additions here are keyconfig and BGT2py. The former is supposed to make it easier to do Swamp-style keyconfig, including the user-editable file and the ability to identify keys by name (for instance, for a controls menu or in-game instructions). BGT2py is a quick-and-dirty script for converting bgt files to python. It makes no attempt at perfection, just takes care of the most common differences in syntax. You will still have to edit the resulting python file for things that BGT2py missed. It's more a time-saver than a proper converter.

Oh, I completely forgot to mention the fpsound_pool, which wound up with 3dio because it was supposed to allow bounding shapes (that part doesn't work). It basically tries to do Swamp-style first person sound in the sound_pool, instead of the top-down perspective of the normal sound_pool. I think it still has some bugs,—some of my tests had forward and backward reversed when facing along the z axis, but I'm not sure if that's the sound or if it's the movement code I was using.
The confusing part will be the orientation vectors. In 2d, it's just the unit vector corresponding to the angle, effectively (cos, sin). In 3d, it's messier, because I was too confused by rotation and addition of 3d vectors and instead went with (cos(theta), sin(phi), sin(theta)). Which is to say, x and z are for theta, and y is for elevation (phi). Since audio games almost never use elevation, an orientation vector might just be (cosine(theta), 0, sine(theta)). Since this was such a headache with the sum_angle and dif_angle functions, I included variations on those that take the numbers directly, instead of the vectors, but it still returns a 2d vector you'd need to convert
Math contains several new functions for all these angle and rotation shenanigans. See sum_angle, dif_angle, and rotate3d. I found that using sum_angle on very small angles didn't seem to have any effect, so it's certainly still somewhat buggy.

And there are some additions to stdlib, an nv(x, y, z) function for quicker vector creation, whatever.


Thumbs up +1

2018-08-14 04:14:28

Wow, BGT into python? I never thought I'd see the day. Now if we just had a tool to write code for us, so we don't have to do the work!
Really neat includes!

What has been created in the laws of nature holds true in the laws of magic as well. Where there is light, there is darkness,  and where there is life, there is also death.
Aerodyne: first of the wizard order

Thumbs up

2018-08-14 05:00:33

Thanks smile


Thumbs up

2018-08-14 16:31:47

are you still shipping soundtrack2 with it? I made a modification of that and added an additive framework but I was working on a slightly out of date version and edited syntax so... If you could help me add bending to it and maybe get this framework into your include as soundtrack3 or soundtrackX or something like that that'd be nice. Use forum email to contact me privately. Currently my note to frequency function is nuts and my generate harmonics doesn't support bending so, and of course I'd want the bend to go all at once and not fuck up the disposition during the slide so yeah, not quite that easy, least for me. I've changed the syntax to mml type, with lowewrcase notes and p to r and the ability to use the default length with rests and things like that.

An anomaly in the matrix. An error in existence. A being who cannot get inside the goddamn box! A.K.A. Me.

2018-08-14 17:20:04

very good

Thumbs up

2018-08-14 21:10:08

4: IIRC, soundtrack2 is in includes2018, yes.
5: Thanks smile.


Thumbs up

2018-08-15 09:32:29

Hi there. I think you would better provide a read me for some of those new added codes, Including that key config include. For example i don't know what I should write in the parsed file that loads as key config's data. Like
or move_forward:w
How does this one work please?

Add me on skype: kianoosh.shakeri2
Or follow me on twitter @kianoosh shakeri

2019-01-27 09:36:39

When trying to download, I get an error.  The file appears to be gone.  Can you fix, please?

Thumbs up

2019-01-27 10:28:53

Hopefully this is the right one:
https://www.dropbox.com/s/bg42b9ssgyejp … 8.zip?dl=0
Some minor changes have been made since this was last updated, but uploading them has proven strenuous.


Thumbs up

2019-01-27 10:34:00

Since my explanation regarding keyconfig didn't survive the forum rollback:
- If you use a global keyconfig, it needs to be a handle. Apparently, the need for initializing the key dictionary doesn't work in global scope before main, making this the first time I've encountered a problem with global variables. Make it a handle, or a member of a class, and there shouldn't be any problems.
- The format of config files is the same as Swamp's. IDR if this version handles shift/alt/ctrl correctly, but there is a version that does, if I can find / upload it.


Thumbs up

2019-01-28 03:38:58

Nope.  Error 404 when going to that link in post 9.

Thumbs up

2019-01-28 08:20:47

What is this I don't even... How about this?


Thumbs up

2019-01-28 08:28:57

I'm going to go ahead and paste the most recent version of keyconfig from my Dropbox. I forget what all changed, but I think this version handles modifiers better, and might have fixed some bugs that would cause it to crash.

dictionary keynames;

// Constants for auxiliary commands:
const int KC_MOUSE1=-2, KC_MOUSE2=-3, KC_MOUSE3=-4, KC_MOUSE4=-5, KC_MOUSE5=-6, KC_MOUSE6=-7, KC_MOUSE7=-8, KC_MOUSE8=-9, KC_MOUSE9=-10;

// Modifier constants:
const uint8 alt=1, shift=2, ctrl=4;

class keyconfig {
int[] keys;
bool[] flags;
uint8[] modifiers; // 2:43 AM 8/26/2018.
string[] names;
keyconfig() {
keyconfig(int size) {
for(uint i=0; i<size; i++) this.modifiers[i]=0;
keyconfig(string txt) {

int getKey(int k) {
return this.keys.find(k);
int press(int k, uint8 mods=0) {
int index=this.keys.find(k);
if(index>=0 and this.modifiers[index]!=mods) {
for(uint i=index+1; i<this.keys.length(); i++) {
if(this.keys[i]==k and this.modifiers[i]==mods) {
}// match found.
}// With modifiers.
if(index>=0) {
if(this.flags[index]) return -1;
else {
return index;
return index;
int release(int k, uint8 mods=0) {
int index=this.keys.find(k);
if(index<0) return index;
// Modifiers are important!
/*else if(this.flags[index]) {
return index;
else return -1;
for(uint i=index; i<this.keys.length(); i++) {
if(this.keys[i]==k and this.flags[i] and (mods==0 or mods==this.modifiers[i])) {
else if(index==i) index=-1; // So we can return -1 if nothing was released.
return index;

bool key_down(int index) {
return (index>=0 and index<this.flags.length() and this.flags[index]);
bool key_up(int index) {
return !this.key_down(index);
int getKey(string name) {
return this.names.find(name);

bool load(string txt) {
string[] lines=string_split(string_replace(txt, "\r", "\n", true), "\n", true);
uint index=0;
while(index<lines.length() and !string_is_digits(lines[index])) {index++;}
if(index>=lines.length()) {
alert("Error in keyconfig.load", "Number of elements was not found.");
return false;
uint l=string_to_number(lines[index]);
for(uint i=1; i<=l; i++) {
if(index+i-1>=lines.length()) {
alert("Error in keyconfig.load", "Index out of bounds. Total lines: " + lines.length() + ", expected entries: " + l + ", starting index: " + index + ", i:" + i + ".");
return false;
string[] parts=string_split(lines[index+i-1], ":", true);
if(parts.length()<2) continue;
if(parts[1].length()>1 and string_contains("+-=", string_right(parts[1], 1), 1)>=0) {
string c=string_right(parts[1], 1);
if(c=="+") this.modifiers[i-1]=shift;
else if(c=="-") this.modifiers[i-1]=ctrl;
else this.modifiers[i-1]=alt;
else this.modifiers[i-1]=0;
return true;
string to_string(string header="") {
uint l=this.keys.length();
string ret=header + "\r\n" + l + "\r\n";
for(uint i=0; i<l; i++) ret += this.names[i] + ":" + key_name(this.keys[i]) + mod_name(this.modifiers[i]) + "\r\n";
return ret;
bool save(string filename, string header="") {
file fout;
fout.open(filename, "w");
return true;
void update() {
int k;
for(uint i=0; i<this.keys.length(); i++) {
if(k<0) {
if(k>=-10 and k<-1) {
k=absolute(k+1); // Or is it +2?
else if(k==-1) continue;
}// Mouse, etc.
else if(this.modifiers[i]==0 and key_pressed(k)) this.press(k);
else if(this.modifiers[i]!=0 and key_down(k) and !this.flags[i]) {
uint8 m=0;
if(key_down(KEY_LSHIFT) or key_down(KEY_RSHIFT)) m|=shift;
if(key_down(KEY_LCONTROL) or key_down(KEY_RCONTROL)) m|=ctrl;
if(key_down(KEY_LMENU) or key_down(KEY_RMENU)) m|=alt;
if(((this.modifiers[i])&m)==this.modifiers[i]) this.flags[i]=true;
else if(key_released(k)) this.flags[i]=false; //this.release(k);
else if(key_down(k) and !this.flags[i]) this.press(k);
else if(key_up(k) and this.flags[i]) this.flags[i]=false; //this.release(k);

void init_keynames() {
if(keynames.exists("A")) return;
// Let's do this the somewhat easy way:
string[] descs={"left", "right", "up", "down", "space", "lctrl", "rcrtl", "lalt", "ralt", "lshift", "rshift", ",", ".", "/", "back", "\\", "enter", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "1", "0", "2", "3", "4", "5", "6", "7", "8", "9", "-", "+", "#0", "#1", "#2", "#3", "#4", "#5", "#6", "#7", "#8", "#9", "home", "pgup", "pgdn", "end", "del", "[", "]", "tab", "=", "*", "#/", "esc", ";", "f1", "f2", "f3", "F4", "f5", "F6", "F7", "f8", "F9", "f10", "f11", "f12", "MOUSE1", "MOUSE2", "MOUSE3", "MOUSE4", "MOUSE5", "MOUSE6", "MOUSE7", "MOUSE8", "MOUSE9"};
//string txt="Key mappings, for what it\'s worth:\r\n";
for(uint i=0; i<codes.length(); i++) {//txt += codes[i] + ", " + descs[i] + "\r\n";
keynames.set(string_to_upper_case(descs[i]), codes[i]);

int key_code(string n) {
int ret=-1;
if(n.length()>1 and string_contains("+-=", string_right(n, 1), 1)>=0) n=string_trim_right(n, 1);
if(keynames.exists(n)) keynames.get(n, ret);
return ret;

string key_name(int k) {
// this one is going to hurt.
string[] keys=keynames.get_keys();
for(uint i=0; i<keys.length(); i++) {
int cur;
keynames.get(keys[i], cur);
if(cur==k) return keys[i];
return "";

[email protected] load_keyconfig(string filename) {
if(!file_exists(filename)) {
alert("Error in load_keyconfig", "The \'" + filename + "\' file could not be found. Check the spelling and try again.");
return null;
file fin;
fin.open(filename, "r");
keyconfig ret(fin.read(0));
return ret;

// 3:04 AM 8/26/2018: Update to support modifiers.
string mod_name(uint8 m) {
if(m==shift) return "+";
else if(m==ctrl) return "-";
else if(m==alt) return "=";
return "";

Thumbs up

2019-01-28 20:47:56 (edited by JLove 2019-01-28 22:10:50)

That worked.  Thanks.  Includes 2018.zip is what it shows.  Although I do not see any file call 3dio after extraction of the zip file, so not sure whether there's a second set of files, and the link above for dropbox says "error 429," too many requests.  Fucking Dropbox and their stupid fucking limits.  It shouldn't matter to them how many times a file is downloaded, especially when it's being downloaded by someone with a paid account.

Thumbs up

2019-01-28 22:31:58 (edited by CAE_Jones 2019-01-28 22:38:20)

I forgot there were two O_O
Here's 3dio:
Which I believe has some bugs that made it through testing without manifesting, even though that doesn't make sense. If anything weird happens related to cylinders, it's most likely a bug.
Actually, if you open group3d.bgt, and ctrl+f "c=cast<cylinder>(c);", that's probably a bugged line that somehow hid until the past week. It should be casting f to c, not c to c, so if you ever use that function on a cylinder, it will crash.
Not sure how old this copy is. There was a bug in the cylinder class, I think with the get_bounds method, but I'd have to look it up to be sure. I made a bunch of forgetful mistakes with cylinders, for some reason. sad


Thumbs up

2019-02-09 03:29:03

I've got a couple of questions about your math code.  First, are you using theta as the angle of elevation in the code, or are you using theta to represent degrees as degrees?  ?  From what I understand, unless I am reading incorrectly, if R is the distance from origin to point, theta is the measure of the angle of elevation, and phi is the azimuth angle, such that theta is equal to arcos(z/R).  Why do you have the circular degrees in arrays?  Does it need to be like that for BGT to calculate correctly, or does it give an advantage that I'm just not seeing?  Why wouldn't something like this work as well?

double deg;
double rads;
double theta;

//convert degrees to radians, adjusting to use the 12-o'clock system due to the way sounds are handled in the programming language.
double DTR(double deg)
return rads = (90-deg)*pi/180;
To get the value of r:

double GetDist(vector v1, vector v2)
double r;
v.x = v1.x - v2.x;
v.y = v1.y - v2.y;
v.z = v1.z - v2.z;
return R = square_root(power(v.x,2)+power(v.y,2)+power(v.z,2));
Then, to get theta:

double theta(pos.z, r)
return theta = DTR(arc_cosine(pos.z/r));
Wouldn't that work as well, without putting the degrees into arrays?  Or is there something in BGT that makes the arrays necessary?  Just curious.

Thumbs up

2019-02-09 04:38:29 (edited by CAE_Jones 2019-02-09 04:49:13)

If you're referring to the sin / cos arrays in math.bgt, those were an attempt at optimizing for speed. It turned out, after testing, that this was somehow slower than the built-in functions, I think because CPUs started doing this optimization in hardware. So you can ignore those and use the normal sine / cosine functions.
I'm using the coordinate system found in most 3d engines, where +x is right, +y is up, and +z is out from the screen toward the viewer. At least, that's how it's supposed to work. So x=cos (theta) * cos (phi), y=sin (phi), z = sin (theta) * cos (phi). I'm pretty sure something in fpsound_pool gets that wrong, somewhere, though.
Theta and phi are radians. I didn't include any handling for degrees.

Also, BGT's vectors allow you to put the example for getting r into a single statement:
double r = (v2-v1).length();
BGT's vector operations are so convenient and simple to implement that I was surprised when I realized how few times I've seen anything similar elsewhere. Still needs negation and normalization, though. -v won't compile, and without a function for normalizing, you'd have to have divide by 0 checks everywhere.


Thumbs up

2019-02-09 07:20:53

grate update! keep it up! continu!

Thumbs up

2019-02-09 07:45:45 (edited by JLove 2019-02-09 08:17:59)

Ok, now I am confused.  According to someone I talked to who is in physics, it's actually:
x = r * sin θ * cos φ
y = r * sin θ * sin φ
z = r * cos φ
Re normalization, yeah, surprised that there's not a method.  Wouldn't this do it?

vector normalize([email protected] u)
u.x = 1*sine(theta)*cosine(phi);
u.y = 1*sine(theta)*sine(phi);
u.z = 1*cosine(phi);
That way you could call it wherever:
u.normalize(whatever vector you please)

Thumbs up

2019-02-09 19:25:03

Now I'm confused. I want to think I've seen that arrangement of phi somewhere, but the sin theta for x has me baffled.
For triangles, sin = opposite / hypotenuse, and cos = adjacent / hypotenuse. If you're in the x/y plane, positive angles rotate from the positive x axis toward the positive y axis. In the x/z plane, same thing, but swap y and z.
So if theta is azimuth in the x/y plane, and phi is elevation in the z direction, then
x = r * cos(theta) * cos(phi) <— because the x axis is adjacent to r, and when phi = 0, cos(phi) = 1, so even ignoring that the adjacent for phi is the vector projected into x/y, it can't be anything else.
y = r * sin(theta) * cos(phi) <— y is opposite theta, so we use sin, but still use cos(phi) because the x/y projection is still the adjacent.
z = r * sin(phi) <— elevation doesn't care about theta. It's sin and not cos, because z is the distance from the x/y plane to the point of our vector, and that is opposite phi.
If we're using theta for the angle in the x/z plane, and phi for elevation into y, then we swap y and z in the above, but the calculations otherwise stay the same.
x = r * cos(theta) * cos(phi)
y = r * sin(phi) <— remember that when phi = 0, sin(phi) = 0, which is what we expect for y if there is no elevation.
z = r * sin(theta) * cos(phi)

The equations you've given confuse me and remind me of what happened the first time I tried messing with 3d. x kinda makes sense, if we're rotating from y or z toward +x - and I can imagine reasons you might want to do that in game development (see the stereo wonkiness in fpsound_pool). However, y and z seem like they'd give gibberish, aiui. Since there's only phi for z, I'll assume that z is vertical. So, if we're facing forward, at theta=0 and phi=0, then the resulting vector would be
(0, 0, r)
Because sin(0) is 0, and cos(0) is 1. So you're pointing straight up, or straight z-ward, whichever makes more sense.
And if theta is 45degrees == pi/2, and phi is still 0, we get:
(r * 0.707..., 0, r)
So x, and only x, changes. Also, that breaks the Pythagorean theor'em, since we need sqrt (x^2+y^2+z^2) to equal r, and that 0.707... is sqrt(2)/2, meaning we get sqrt(r^2 + r^2/2) = sqrt (3/2 * r^2) = r * sqrt(3/2), which is greater than r.
Trying it for theta = 90degrees == pi/2, we get (r, 0, r), which makes the total length 1.414... * r.
Maybe I did all of that wrong. It's been over 12 years since I was in a math class, and that wasn't even trig. And I got all my 3d from the internet.
I suppose the thing to do is to test it.


Thumbs up

2019-02-09 23:57:34 (edited by JLove 2019-02-10 00:10:55)

A couple of things.  So, this is exactly what he said:

In spherical coordinates, we can describe a vector by specifying the following components:
• r which is the distance from the origin to the point
• θ which is the angle the radial vector makes with respect to the z-axis (i.e. up / down)
• φ which is the azimuthal angle that the radial vector makes with respect to the y-axis (i.e. rotation about the z-axis)
We can get spherical coordinates from cartesian coordinates (and vice-versa) using the transformation:
x = r * sin θ * cos φ
y = r * sin θ * sin φ
z = r * cos φ
r = sqrt(x^2 + y^2 + z^2)
φ = arctan(y / x)
θ = arccos(z / r)
So, it looks like he is saying that θ is used for elevation instead of φ.  If that's the case, then x being sin(theta)*cos(phi) makes sense.
On a related mathematical but different note, One thing I have noticed about BGT is that if I declare a vector like so:
vector VF((IV.x+acc.x)*DT*sine(theta)*cosine(phi), (IV.y+acc.y)*DT*sine(theta)*sine(phi), (pos.z+IV*DT)+(0.5*9.81)*power(DT, 2)*cosine(phi));
it will declare the vector, but the values within the vector do not assign correctly.  in other words, values do not change correctly as they should, as if the math isn't actually completed.  In order to get them to assign correctly, I had to do this:

vector UpdateVel()
VF.x = (IV.x+acc.x)*DT*sine(theta)*cosine(phi);
VF.y = (IV.y+acc.y)*DT*sine(theta)*sine(phi);
VF.z = (pos.z+IV*DT)+(0.5*9.81)*power(DT, 2)*cosine(phi);
return VF;
Once I did that, the vector values assigned correctly.  So if the only way to get the values to assign correctly is to return the vector in a function, is there a way to have one function retirn all the vectors so that I don't have to make one for each one of them?  That would suck,  and, quite frankly, seems counterintuitive.

Thumbs up

2019-02-10 15:26:29

The vectors are assigned only once, with the values that exist at the time. They have to be updated whenever something changes. So long as the function is in the same scope as the vectors you want to change, you can put all the assignments in one function. I normally wouldn't even bother initializing a vector that's going to change in every frame, just letting it default to (0,0,0), and set the values in the function that updates everything else. The exception being if you have an orientation vector, which would need to default to something with length 1, but iirc, you aren't using those.

Re: Spherical coordinates, the explanation they gave just confused me even more, so I wrote a program to test it both ways, with theta and phi swapping roles, for a total of four tests. V1 and V2 are done my way, and V3 and V4 are done their way, with one angle being pi/4, and the other being 0:

  • V1: (0.70710676908493, 0.70710676908493, 0), length = 0.999999940395355

  • V2: (0.70710676908493, 0, 0.70710676908493), length = 0.999999940395355

  • V3: (0, 0, 0.70710676908493), length = 0.70710676908493

  • V4: (0.70710676908493, 0, 1), length = 1.22474491596222

Maybe I'm going crazy, but v1 and v2 are the only ones that make sense to me. A length other than ~1 should be invalid, based on the fact that length == r, and we're talking about a unit sphere.
Here's the code I used to test it:

const double pi=3.14159265;
string vecstr(vector v) {
    return "(" + v.x + ", " + v.y + ", " + v.z + "), length = " + v.length() + "\r\n";
void main() {
    // My way:
    double theta=pi/4, phi=0;
    // I think this other person swaps theta and phi, so we'll do that, too.
    // But first:
    vector v1(
    // This is supposed to give us (0.707, 0.707, 0).
    string text="V1: " + vecstr(v1);
    // Now, let's swap phi and theta and see what happens. Result should be (0.707, 0, 0.707).
    double temp=phi;
    vector v2(
    text += "V2: " + vecstr(v2);
    // Now, let's try it their way:
    vector v3(
    // Which I'm predicting gives us a length other than 1.
    text += "V3: " + vecstr(v3);
    // One more time, swapping phi and theta back:
    vector v4(
    text += "V4: " + vecstr(v4);
    alert("Results", text);

Thumbs up

2019-02-10 23:12:13 (edited by JLove 2019-02-10 23:32:04)

The problem that I can't figure out how to get around is that if I am not mistaken, a function to return vectors can only return one value.  So in other words, I can't return 5 or 6 vectors, I don't think.  So, using the above function to update velocity, once that vector is returned, it would exit the function, I believe, without updating the rest.  I've got multiple vectors:  displacement, force, acceleration, initial velocity, final velocity, etc.  Maybe I'm wrong and I sound like a moron, and maybe that's due to the less than 3 hours of sleep I got last night.  But if I'm right, if a function to return the vector can only return the one value, then how do I implement an update function that can update all of them at once?  Ideally, this would be done before the ball moves.  So, something like:

vector update()
v.x = blah;
v.y = blah;
v.z = blah;
return v;
returns one vector.  If I were to put another vector under the initial return, I don't think it would execute.  And If I did something like:
vector update()
v1.x = blah;
v1.y = blah;
v1.z = blah;
v2.x = blah;
v2.y = blah;

return v1;
return v2;

I think it would just exit the function after the initial return value and not execute the second return line of code, if I remember correctly.  So not sure how to get around that.  Or hell, maybe I'm totally off base at the moment and I'm a fucking idiot.  If that's the case, then I apologize.

Thumbs up

2019-02-11 00:02:58

If the function is a method of the ball class, for example, it can change the variables associated with that ball, without having to return everything. If it's a global function changing global variables, the same applies. This is where the concepts of  scope and namespaces become relevant. This can get confusing if you aren't use to it, especially since different languages can handle it differently (see: the chaos of addressing this in Javascript). If you want to be sure about what a variable is associated with, you can try prepending "this." If this.vel doesn't compile, either the function is outside of the class, or vel is. If it does work, you know you're affecting the variables associated with the class.


Thumbs up

2019-02-11 00:17:21 (edited by JLove 2019-02-11 00:19:04)

Yeah, I know that part.  But math isn't a class in this case.  I didn't think making it a class was necessary, but I always could in the future.  I've got a math.bgt file, and all of the vectors and mathematical calculations are stored there, so that I don't have math in 50 different places in my code.  Makes bugs related to the math portion easier to track down.  What that means is that any vector and variable stored in math are treated as global, I believe.

Thumbs up