Windows Warp3D

Samuel Venable

Time Killer
Hi everyone, I've got a quick update, I've made a few improvements to the engine while porting it to gms2.3:

Improved render batching system (slightly faster than before)

Batches are handled separately now,
(a "batch" struct is returned from "batch_create(list of instances)"
instead of only one global batch being possible,
so this allows say security cameras rendering a room the other side of the level in full detail.

Implemented a simple type of visibility culling using "zones"
You create rectangular zones for each say room or small area of the level,
and other zones nearby are flagged as "visible from that zone",
which, in the level init process a whole set of lists are created for instances
visible from each zone in the level, which cuts down draw calls a ton.

Also combined the zones into the collision system for dynamic instances (like the player, enemies, projectiles and anything else that actively checks for collision with things) in which instances only check collision in the zone they're currently in.
While testing collision with static instances(like items, ammo pickups etc.) and the precise triangles of the level model are still handled in the grid like before, as it's the fastest way.

Converted the entire gui to structs instead of instances. Before every button or thing you could click on was a separate instance of obj_gui, as it was way too complicated to do with arrays before, but I was able to convert them to structs in about 2 hours, just had to change a few instance_exists calls and other things where they had to be instances.

Made a few improvements to the model editor and the way "tools" are handled, which has made it easier to create new tools, so the list of tools should keep growing as time goes on.


I think that's it for now, I've mainly been refining all the code and thinking about it being a rock solid system, that's also easy to add things to. I'm very happy with how everything's going anyway.

Also, the new version is almost ready for beta testing. I'm going to invite a handful of people to beta test it, people that I trust and have spoke to before and some that've given constant support throughout:

@Amon
@Micah_DS
@TheSnidr
@JaimitoEs
@RujiK
@Yal
@Apapappa
@FoxyOfJungle
@Samuel Venable
@Xor
@flyingsaucerinvasion

If any of you are interested in testing the beta version, it'd be a HUGEE help, it will help with all sorts of angles to do with other people besides myself using it. Also in return for your help I'll give you lifetime access to the engine for free(via buying it on the marketplace and I then send you the money back on paypal 😆)

In the meantime, I made a cool sky dome shader, it basically uses a flat texture(photoshop clouds in this case) and projects it onto a sphere, created by a single cube, where the position coords are passed to the pixel shader, then normalized to create a sphere out of pixels. Then all that's left is to do a calculation that uses the normal and create a uv, plus some uniforms to affect the curve and scale. It's similar to how the sky worked on Quake 1, but I added a couple of other things to stop it looking so warped around the horizon

I just now noticed you tagged me in this. Is there still a spot open for beta testing? Thanks for thinking of me!

Edit:
fails to read exe demo out now lol
 
Last edited:

Joe Ellis

Member
I've got the ball rolling again! (literally)
This is the first time I've got any gameplay working since I moved to gms2.
I've been having more fun with ball physics and improved them quite a bit, and made a skate park to test it in:


I just love playing with those balls and watching them bash into each other, it brings me great pleasure seeing my dreams come true.

I've improved the collision system to be simpler to work with,
every collision function that uses precise collision (sphere - triangle) or (capsule - triangle)
uses this one function: normal_to_triangle(tri, x, y, z, sphere_radius)
Which returns a vec4, the first 3 being the normal to the intersection point on the triangle,
and the 4th being the distance from the instance position\centre of the sphere to the point on the triangle.
Then different collision functions can use this info in different ways.
So it's cut down a lot of collision code, now the ball's collision function looks like this:

GML:
function collision_sphere_triangle(l)//list of triangles (got from current grid cell)
{ 
    var i = 0, t, n, d;
    repeat l[0]
    {
    t = l[++i]
    n = normal_to_triangle(t, x, y, z, mask_radius)
    if n != 0
    && n[3] < mask_radius
    && dot_product_3d(xspeed, yspeed, zspeed, n[0], n[1], n[2]) <= 0 //check moving towards triangle
    {
    d = mask_radius - n[3] //get the distance from the edge of the sphere to the intersect point on the triangle
    x += n[0] * d //Push the instance back in the direction from the intersect point, at that distance so that the edge of the sphere becomes level with the triangle surface (or edge)
    y += n[1] * d
    z += n[2] * d
    if n[2] <= ground_nz //sort most upwards pointing triangle to be the "ground triangle" which can then be used to make the character rotate based on the angle of the triangle
    {
    ground = true
    ground_nz = n[2]
    ground_tri = t
    }
    instance_get_bbox_sphere() //update the instance's bbox
    }
    }
}
Instead of this: (before)

GML:
///collision_precise_ball(list of triangles)

var
l = argument0, i = 0, t, vx, vy, vz, d, nx, ny, nz;

repeat l[0]
{
t = l[++i]
if rectangle_in_rectangle(bbox_x1, bbox_y1, bbox_x2, bbox_y2, t[_tri.bbox_x1], t[_tri.bbox_y1], t[_tri.bbox_x2], t[_tri.bbox_y2])
&& dot_product_3d(xspeed, yspeed, zspeed, t[_tri.nx], t[_tri.ny], t[_tri.nz]) <= 0
{
vx = x - t[_tri.x1]
vy = y - t[_tri.y1]
vz = z - t[_tri.z1]
d = dot_product_3d(vx, vy, vz, t[_tri.nx], t[_tri.ny], t[_tri.nz])
if abs(d) < mask_radius
{
if dot_product_3d_normalised(vx, vy, vz, t[_tri.t1x], t[_tri.t1y], t[_tri.t1z]) <= 0
{
d = median(0, t[_tri.e1l], dot_product_3d(x - t[_tri.x1], y - t[_tri.y1], z - t[_tri.z1], t[_tri.e1x], t[_tri.e1y], t[_tri.e1z]))
nx = x - (t[_tri.x1] + (t[_tri.e1x] * d))
ny = y - (t[_tri.y1] + (t[_tri.e1y] * d))
nz = z - (t[_tri.z1] + (t[_tri.e1z] * d))
d = point_distance_3d(0, 0, 0, nx, ny, nz)
nx /= d
ny /= d
nz /= d
}
else
{
if dot_product_3d_normalised(x - t[_tri.x2], y - t[_tri.y2], z - t[_tri.z2], t[_tri.t2x], t[_tri.t2y], t[_tri.t2z]) <= 0
{
d = median(0, t[_tri.e2l], dot_product_3d(x - t[_tri.x2], y - t[_tri.y2], z - t[_tri.z2], t[_tri.e2x], t[_tri.e2y], t[_tri.e2z]))
nx = x - (t[_tri.x2] + (t[_tri.e2x] * d))
ny = y - (t[_tri.y2] + (t[_tri.e2y] * d))
nz = z - (t[_tri.z2] + (t[_tri.e2z] * d))
d = point_distance_3d(0, 0, 0, nx, ny, nz)
nx /= d
ny /= d
nz /= d
}
else
{
if dot_product_3d_normalised(x - t[_tri.x3], y - t[_tri.y3], z - t[_tri.z3], t[_tri.t3x], t[_tri.t3y], t[_tri.t3z]) <= 0
{
d = median(0, t[_tri.e3l], dot_product_3d(x - t[_tri.x3], y - t[_tri.y3], z - t[_tri.z3], t[_tri.e3x], t[_tri.e3y], t[_tri.e3z]))
nx = x - (t[_tri.x3] + (t[_tri.e3x] * d))
ny = y - (t[_tri.y3] + (t[_tri.e3y] * d))
nz = z - (t[_tri.z3] + (t[_tri.e3z] * d))
d = point_distance_3d(0, 0, 0, nx, ny, nz)
nx /= d
ny /= d
nz /= d
}
else
{
nx = t[_tri.nx]
ny = t[_tri.ny]
nz = t[_tri.nz]
}
}
}
if d < mask_radius
{
d = mask_radius - d
if nz <= slope_limit
{ground = true}
x += nx * d
y += ny * d
z += nz * d
instance_get_bbox_sphere()
}
}
}
}
So you can see it's a lot easier to see what the code does and how it handles it, and the complex calculations are out of sight and out of mind.

If you're wondering, this is the code for normal_to_triangle:

GML:
function normal_to_triangle(t, x, y, z, max_dist)
{
  
    var
    nx = t[_tri.nx],
    ny = t[_tri.ny],
    nz = t[_tri.nz],
    d = dot_product_3d(x - t[_tri.x1], y - t[_tri.y1], z - t[_tri.z1], nx, ny, nz);
    if abs(d) >= max_dist
    {return 0}

    if dot_product_3d(x - t[_tri.x1], y - t[_tri.y1], z - t[_tri.z1], t[_tri.t1x], t[_tri.t1y], t[_tri.t1z]) <= 0
    {
    if t[_tri.e1_concave]
    {return 0}
    d = median(0, t[_tri.e1l], dot_product_3d(x - t[_tri.x1], y - t[_tri.y1], z - t[_tri.z1], t[_tri.e1x], t[_tri.e1y], t[_tri.e1z]))
    nx = x - (t[_tri.x1] + (t[_tri.e1x] * d))
    ny = y - (t[_tri.y1] + (t[_tri.e1y] * d))
    nz = z - (t[_tri.z1] + (t[_tri.e1z] * d))
    d = point_distance_3d(0, 0, 0, nx, ny, nz)
    var r = 1 / d;
    return [nx * r, ny * r, nz * r, d]
    }

    if dot_product_3d(x - t[_tri.x2], y - t[_tri.y2], z - t[_tri.z2], t[_tri.t2x], t[_tri.t2y], t[_tri.t2z]) <= 0
    {
    if t[_tri.e2_concave]
    {return 0}
    d = median(0, t[_tri.e2l], dot_product_3d(x - t[_tri.x2], y - t[_tri.y2], z - t[_tri.z2], t[_tri.e2x], t[_tri.e2y], t[_tri.e2z]))
    nx = x - (t[_tri.x2] + (t[_tri.e2x] * d))
    ny = y - (t[_tri.y2] + (t[_tri.e2y] * d))
    nz = z - (t[_tri.z2] + (t[_tri.e2z] * d))
    d = point_distance_3d(0, 0, 0, nx, ny, nz)
    var r = 1 / d;
    return [nx * r, ny * r, nz * r, d]
    }

    if dot_product_3d(x - t[_tri.x3], y - t[_tri.y3], z - t[_tri.z3], t[_tri.t3x], t[_tri.t3y], t[_tri.t3z]) <= 0
    {
    if t[_tri.e3_concave]
    {return 0}
    d = median(0, t[_tri.e3l], dot_product_3d(x - t[_tri.x3], y - t[_tri.y3], z - t[_tri.z3], t[_tri.e3x], t[_tri.e3y], t[_tri.e3z]))
    nx = x - (t[_tri.x3] + (t[_tri.e3x] * d))
    ny = y - (t[_tri.y3] + (t[_tri.e3y] * d))
    nz = z - (t[_tri.z3] + (t[_tri.e3z] * d))
    d = point_distance_3d(0, 0, 0, nx, ny, nz)
    var r = 1 / d;
    return [nx * r, ny * r, nz * r, d]
    }

    return [nx, ny, nz, abs(d)]
}
It's so far the fastest method I've found, using "edge tangents", I actually came up with this concept completely by myself which I'm quite proud of, but obviously don't wanna seem like I'm bragging either. If you're interested in this method just message me, I'm happy to share any knowledge or stuff I use.

I've also improved the collision system by making it detect and flag convex\concave edges, so now in the normal_to_triangle function, collisions with concave edges are ignored, as it's impossible for a sphere to collide with them, it'll collide with the flat surface of the triangle before. So this has cut down calculations quite a bit, it no longer has to get the nearest point along a triangle's edge if it's concave.

I've also made a big improvement to how dynamic lights are handled, before in 1.4, I had to render the lighting separately to the main materials using additive blending, because directx9c couldn't use a for loop in a shader with a variable length, ei. you couldn't pass in a uniform integer telling it how many active lights there are. It lagged terribly, even if only one light was used. So I had to make it go up in bands of 8, every lighting shader had to have 4 versions (_x8, _x16, _x24, _x32), and it had to render all the vertex buffers a second time using the lighting shaders that were configured for their main shaders. So it was really complicated and annoying.
But this problem with the for loop no longer happens in directx 11, so only one shader is needed, the lighting no longer has to be rendered separately to the main materials and now it can just be done in the main shaders. (Or not if certain shaders don't use lighting (another advantage))

Also there is now a special node type for a light (Before they worked with instances, and was a bit pointless and wasteful cus most lights are static most of the time) So they're a separate type of object\node now(structs), and for dynamic lights, you'd use an instance that controls it\edits it's values. They can also be created, deleted, activated and deactivated during gameplay with the functions (light_create, light_activate etc.)

@beta testers, I'm gonna upload a new version to test in the next couple of days, and you'll be able to test out these new things, and a few other gameplay things\objects (depending how many I get working in next couple of days) :)
 
Last edited:

Joe Ellis

Member

Got materials working again in the 2.3 version. These are basically structs that can use a shader, inherit certain uniforms from it that're marked as "material uniforms", and then anything using that material will get rendered with the material's specific uniform values.
So you can write shaders with more custom variables for certain effects and basically things that you want to adjust per material, rather than globally that affects everything, like the sun direction or color.

I made a pbr shader a while ago shortly before I released the demo of the 1.4 version, I used it on the weapons mainly to make them look shiny. But I've added dynamic lighting to it this week, and also made a mapped version like the materials you can design on Substance etc, 5 different maps for roughness, metallic, attentuation, ambient occlusion and normalmap. I'm also gonna make a combined map version of this, where you can combine the roughness, metallic, attentuation and ambient occlusion maps into one rgba texture.

I really love the plain pbr without the maps cus you can make every kind of shading you need in the game with just one shader, even for cartoony or retro games.

This is just one eggzample of what you can do with materials though, basically anything you can think of writing in a shader you can make all the variables fully adjustable by setting them as material uniforms.
shd_portal for instance has uniforms for the two colors it blends between, the speed, and the number of rings.



Code:
///shd_portal(render_portal, color)

attribute vec3 in_Position;
attribute vec4 in_Color;

varying vec3 v_pos;
varying float v_merge;
varying float v_alpha;

void main()
{
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(in_Position, 1.0);
    v_pos = vec3(gm_Matrices[MATRIX_WORLD] * vec4(in_Position, 1.0));
    v_merge = in_Color.r;
    v_alpha = in_Color.a;
}


varying vec3 v_pos;
varying float v_merge;
varying float v_alpha;

//Global Uniforms
uniform vec3 fog_color;
uniform float fog_start;
uniform float fog_length;
uniform vec3 view_pos;
uniform float time;

//Material Uniforms (Uniforms are marked as material uniforms by putting "//" followed by the default value)
uniform vec3 color1; //rgb(0, 255, 255)
uniform vec3 color2; //rgb(0, 0, 255)
uniform float speed; //1.0
uniform float num_rings; //3.0

void main()
{
    gl_FragColor = vec4(mix(color1, color2, mix(0.0, 1.0, abs((fract((v_merge * num_rings) + (time * speed)) - 0.5) * 2.0))), v_alpha);

    //Fog
    gl_FragColor.rgb = mix(gl_FragColor.rgb, fog_color,
    min(1.0, max(0.0, distance(view_pos, v_pos) - fog_start) * fog_length));
}
 
Last edited:

Joe Ellis

Member
I've got the spread function working with the mouse in the model editor. Basically where you point the mouse, it has a radius and all the vertices in that radius are affected by what you do, with a soft falloff based on how far they are away from the 3d position the mouse falls on the mesh. This has brought a whole new level to the model editor, sculpting is now possible, soft color painting and it works with several functions(from the function tab on the menubar) so if you repeat a certain function (ctrl F) it applies the function to all the vertices in the radius with soft falloff. So it's really good for things like smoothing the mesh, randomizing\erosion, deform functions, color & alpha functions. Here's a video showing some of the things, and I also threw in some skeletally animated terrain just for sh*ts and gigs (the weights of the bumps were painted with the weight tool using the new soft brush. I'm going to make a tutorial video about rigging\weighting a model soon)

A few other pics:

 

Joe Ellis

Member

I've got some more things working this week,

First is the ability to matrix transform the skeleton and animations, so if you matrix transform a model (scale, center it around zero, or rotate it so it's pointing forwards) the transformation gets applied to the skeleton and all the animation frames aswell.
It was really hard to figure out, or at least, I spent countless hours trying different things and working out ways to do it, but in the end I realized a pretty simple method. (Which always happens)
So each animation frame is a set of dual quaternions for each bone, and they're relative and move the bones from the base pose of the skeleton, so if you just apply another dual quaternion to them from the matrix transform it makes parts that didn't rotate at all rotate.
So I needed to rotate the rotations... like the axis they rotate around, but keep the amount of rotation the same.
But in the end, you just need to invert the rotation\transformation, apply it to the base pose of the skeleton, then multiply that result frame\set of dual quats with each animation frame. So that's a huge weight off my mind.

The second thing is that I've merged the 1st person player and 3rd person player into one, mainly cus it was really bugging me having 2 player objects and having horrible long names, where I just wanted obj_player. So I've combined all the controls and movement so that all styles of controls can be toggled, and the 1st person view happens by default if there isn't a camera instance active at the time. So you can switch between 3rd and 1st person during the game, and toggle control style from either mouselook with wasd, or the old style controls of the arrow keys and a z to look up and down. I've also added usb controller inputs, so if a controller is connected it will use that instead. I still need to set up custom key & button config and the in game option menu for setting them though..

3rd, I've improved the item and inventory system a lot. Items are now basically "global structs" and are saved as files and loaded when the engine starts up. They have basic settings like max amount you can hold, the model & sprite they use and their collect and use functions. Plus they can have custom variables, like for weapons or special things you use that need other variables to control how they do things. These work by assigning an object to them, in the same way that instances placed in the level do, and they use any variables you've defined for that object and are all adjustable per item.
A good example of this is for weapons, so if you've equipped a shotgun, the item's object is obj_gun, which has variables for fire_rate, damage, accuracy, scatter etc. and every different gun item can have different config with these variables. There's obj_melee too, which has things like attack_speed, range, damage, pushback_force.. and you can completely make any object and variables you want for any item. So I'm really happy with how versatile this system is. Any saved games also save all the items currently in the inventory.
Now I just need to make the player be able to equip things again like in the 1.4 demo. That will hopefully be next week.

Finally, I've almost got the game finalizing process sorted out. Which basically saves all of the game files into a single rom. Then when the game is compiled into an exe it loads the files from the rom instead of the individual files. The only part with this that I'm still puzzling over is the textures, cus I could glue them all together into a rom, but then they'd be really easy to rip out, so I'm considering working out how to make an automatic function that edits the project file and adds all of the textures into the project as sprites.

So overall, these new things I've got working were things that I've been trying to work out for months and have really been stressing me out, so I'm really satisfied that I've finally managed to sort them out.
 

Joe Ellis

Member
Hi everyone,
I've been inactive for a while, I've been in a "transitional period" or as some would say "a period of depression", I prefer the term "transitional period" as it sounds more positive but both are accurate. I have found the light and have seemed to have come through the other side of it (Which always happens unless thou kills one's self) and have a fresh new energy vibe that I'm loving so far.
I've started my actual game that I've been wanting to make for years: Silkinstein
He is the cute lovable, lurky and likabley creepy character that's been my avatar for several years, and I've been waiting to get the game engine sorted to make the actual game he's in.
So here is a very early prototype of him and the game:


At the minute I've just got him walking about, but the vibe is right, that's the important thing.
The enemies and plot revolve around "The mice with long yellow hair" which are crossbreeding plants and animals,
like spunions(spider onions), sludders(slug udders), toad dogs(self explanitory),
and Slunion (A slug onion) He's one of the bosses, he has anger\rage, similar to a big gorilla, but in slug onion form.
It will probably visually take on a more industrial theme as you get into the MWLYH's bases and generally will have a dream-like theme throughout. (Including the floating rhabi with the two kids at the top of the hill watching him and commenting on his every move) (I love my dreams)
There are a lot of other things, but I'll leave them till I've made some of them, I'm pretty excited about this game though, it should be a kind of cult game in years to come, if I get everything right, which I will if I focus on the important things like the "vibe" and kind of implementing & translating all the ideas from thoughts\dreams to digital form.. :D

I've made several other improvements and progressions with Warp3D aswell, but my progress was so slow the last few months cus I've been depressed and found it really hard to get energy and motivation to do anything. Any tiny thing felt like such a huge task and required so much effort. But it's passed now and I should get loads of stuff done for at least until next winter..
So hopefully Warp3D will actually be released before then :D
 
Awesome. How is your progress? Do you use external rendering ? (External OpenGL / DX context), what version of OGL, DX? Or everything is done using GM Studio?
Thanks.
 

Joe Ellis

Member
Thanks, everything is done with gm. I used a dll in the gms1.4 version to load textures with mipmaps, but gms2 can do that itself now.
The dll could also pass texture pointers into the vertex shader, which still can't be done in gms2. So I might bring that dll back at some point.

I'm still making good progress, just been fixing bugs so far this week though 😴
 

Joe Ellis

Member
Here's some improvements I've made the last couple of months



Texture blending, for terrain mainly, the material lets you choose 4 textures,
then you paint with the soft brush the rgba values, each component is used for the blending,
so you'd mainly use full red, green, blue, the alpha is a bit confusing though, you need to use black, and 0% alpha..



Geodesic\hex grid terrain. Kind of a nicer look than a grid of quads, plus holes are smoother.
I like doing manual uv shifting aswell to make the edges of the holes look cool.



Expensive functions now do a gradual loading window, I'm going to add a cancel button aswell, cus sometimes subdivision ends up taking over 5 minutes or is gonna crash the game entirely, cus subdividing multiplies the amount of faces by 4, so it can quite easily go over the poly limit.



A screenshot of Silkinstein looking groovy, and the HUD might look abit like this.



Nice smooth lighting using dynamic lights, they can really add a lot of extra smoothness to ambient shading. The scene without them just had basic flat shading.



Concept art of Slunion, one of the bosses on Silkinstein, he's a slug onion. He has the temper of an angry gorilla, he slams and jumps about like one, but his arms and undercarriage are slug based, so he's pretty sticky aswell. He will be for sure interesting to make :)
He will be in similar style to Silkinstein, low poly ps1 character. (He has eyebrows even though he has no eyes)

I've also improved the general node handling system quite a lot, but it's not really much of a talking point.
I've basically just generalized it more, and made all nodes be handled in the same way with a few functions like node_create, node_delete, node_copy, node_paste.
Also I've made a kind of garbage collector, where anything that's deleted or instances destroyed get processed at the end of the step by the main engine object, which has made a lot of problems with instances or nodes not existing cus they've been destroyed\deleted while
code is asking about them. Everything has a variable "deleted" which can be checked before doing things if needed, and is better than "instance_exists" in most cases, plus some things might not be instances but checking ".deleted" works for all things.

Anyway, the engine since porting to 2.3 is pretty solid now, it's almost as solid as the 1.4 demo version.
Once it's at that stage I'm going to release it. Any extra things will be released as free "asset packs".
They'll be for specific things that not everyone will want like 1st person shooter ai, 3d platformer enemies, vehicle physics etc.
Then I haven't got to worry so much about everything being in the core version. People will be able to download the packs through the marketplace and just import them into their project.
 

mikix

Member
This is probably the most ambitious project on GMC. Welcome to the Raid Boss Team. Anyway, I have huge respect for Joe Ellis. If Joe Ellis was a raid boss, definately the hardest one to beat.
 

Joe Ellis

Member
I've worked out how to do vehicle physics and physics shapes with rotation speed and stuff.
This is one of the most complicated thing I've ever done, it's taken me a few months to work out how to do it.

It started with the idea of putting physics balls at each corner of a cuboid, well I actually did it in 2d first, so 4 circles at each corner of a rectangle.
Then each ball runs it's own physics & collision using circular\spherical collision, and they work out the movement speed and rotation by comparing it's new position and it's previous.

Then the balls are held together by a "body", which adds up all the ball's movement speeds and rotation and gets the average amounts.

The rotation wasn't that hard to do in 2d cus I could use angle_difference to get how much each ball had rotated from it's previous position, but in 3d it has to create a quaternion from the 2 vectors from the center of the body to the ball's previous position and current position.
So it gets 8 quaternions for each ball's relative rotation, adds them together and normalizes it to get the average rotation amount. This is then multiplied with the body's "rotation speed" quaternion to add it on and make it build up over time.
Then the rotation speed quat is converted to a matrix and multiplied with the body's absolute rotation matrix (To add the rotation speed onto the rotation of the model)
Then another matrix is made from the body's center position and the 2 are multiplied together to create the body's final world matrix, which is used when rendering the model.

The vehicle object has the following variables:

Rotation Damp - used to reduce\decelerate the rotation speed quat (default 0.97)
Rotation Accel - multiplies with the current rotation quat of the step, to reduce how much gets added to the rotation speed quat (default 0.97)
Accel - acceleration of the vehicle when driving forwards (default 0.5)
Decel - slows the vehicle (0.98)
Turn Accel - the speed of the tyres rotating left or right (default 0.25)
Turn Decel - the speed that the tyres return to the center if the player isn't turning (default 0.25)
Turn Speed - the max amount that is added to the rotation quat (default 0.13)
Wheel Radius - the radius of each physics ball (default 25)
Wheel Weight - default 2
Roof Weight - default 0.5
These are used to make the vehicle not topple over (heavier bottom than the roof)
Turn Threshold - used to reduce the turning amount if it's not moving fast enough (default 15) (this stops the vehicle being able to turn if it's still)
Thrust - Reduces speed based on how different the "forwards vector" and speed direction\vector are. (The forwards vector is got with matrix_transform_vertex(absolute_rotation_matrix, 1, 0, 0) and the speed vector is (x - prevx, y - prevy, z - prevz))
Overall Speed - A final multiplier to affect how much the vehicle moves (default 0.9) ei: x += xspeed * overall_speed; y += yspeed * overall_speed; z += zspeed * overall_speed; (This is useful for if you've got all the variables working nicely together but it looks too fast or slow, you can use it to reduce\increase the speed without altering the balance between the variables)

You can get quite a lot of different types of handling by adjusting all of these, which is cool.

Here's the code for it if anyone's interested:
GML:
function player_step_vehicle()
{

    //Get tyre grip based on how many of them are on the ground
    var grip = 0.25 * (wheel_bl.ground + wheel_br.ground + wheel_fl.ground + wheel_fr.ground);
 
    //Compare forward vector and speed to get friction
    var f = matrix_transform_vertex(rot_matrix, 1, 0, 0);
    var frict = lerp(thrust, 1, abs(dot_product_3d_normalized(f[0], f[1], f[2], x - ox, y - oy, z - oz)));

    //Accel
    if input_move_v != 0
    {
    if input_move_v < 0
    {input_move_v *= reverse_speed}

    var a = input_move_v * accel * frict * (0.5 + (grip * 0.5))
 
    xspeed += f[0] * a
    yspeed += f[1] * a
    zspeed += f[2] * a
    }

    //Jumping
    if keyboard_check_pressed(vk_space)
    {zspeed -= jump_speed}

    //Apply decel (Might change this so it only does it if not accelerating)
    xspeed *= decel
    yspeed *= decel
    zspeed *= decel

    //Apply friction
    if grip != 0
    {
    xspeed *= frict
    yspeed *= frict
    zspeed *= frict
    }
 
    //Clamp speed
    var d = point_distance_3d(0, 0, 0, xspeed, yspeed, zspeed);
    if d > max_speed
    {
    d = max_speed / d
    xspeed *= d
    yspeed *= d
    zspeed *= d
    }

    //Turning
    if input_move_h != 0
    {wheel_dir = clamp(wheel_dir + (wheel_accel * input_move_h), -1, 1)}
    else
    {wheel_dir *= 1 - wheel_decel}

    //Apply rotation from wheel dir
    if grip != 0
    {
    turn_amount = turn_speed * wheel_dir * frict * (0.5 + (grip * 0.5))
 
    var spd = point_distance_3d(0, 0, 0, x - ox, y - oy, z - oz);
 
    //Reduce if not moving fast
    if spd < turn_threshold
    {turn_amount *= spd / turn_threshold}
    }

    //Physics & Collision

    //Get old position
    ox = x
    oy = y
    oz = z
 
    var
    mid_x = x,
    mid_y = y,
    mid_z = z,
    i = 0,
    l = balls,
    r = wheel_radius,

    //Init relative quat
    qx = 0,
    qy = 0,
    qz = 0,
    qw = 1,
    mat = matrix,
    p, vx, vy, vz, cl, j, q, d,
    mx = 0, my = 0, mz = 0;

    //Get list of precise models the vehicle is intersecting
    cl = collision_bbox_get_list(obj_precise)

    repeat l[0]
    {

    //Scope into ball struct
    with l[++i]
    {

    //Store old position for collision checks
    ox = x
    oy = y
    oz = z

    //Get new pos from main matrix
    p = matrix_transform_vertex(mat, base_x, base_y, base_z)
    x = p[0]
    y = p[1]
    z = p[2]
 
    //Inherit speed from body & add gravity\weight
    xspeed = other.xspeed
    yspeed = other.yspeed
    zspeed = other.zspeed + (weight * !ground)
 
    //Store position before speed is applied
    //(for calculating the new speed after collision)
    cx = x
    cy = y
    cz = z

    //Apply speed
    x += xspeed
    y += yspeed
    z += zspeed

    //Get rotation base vector
    vx = x - mid_x
    vy = y - mid_y
    vz = z - mid_z
 
    //Update the ball's bbox
    //(has to be done manually cus it's a struct)
    bbox_left = x - r
    bbox_right = x + r
    bbox_top = y - r
    bbox_bottom = y + r
    bbox_z1 = z - r
    bbox_z2 = z + r
 
    ground = false
 
    if cl != undefined
    {
    j = 0
    repeat cl[0]
    {collision_wheel_precise(cl[++j])}
    }

    //Make a quaternion from the difference of the base vector and the new one after collision
    q = quat_vec_difference(vx, vy, vz, x - mid_x, y - mid_y, z - mid_z)
 
    //Add the quaternion to the total quaternion
    qx += q[0]
    qy += q[1]
    qz += q[2]
    qw += q[3]

    //Get new speed vector and add to the total speed
    mx += x - cx
    my += y - cy
    mz += z - cz

    }
    }

    //Divide total speed by number of balls to get the mean speed
    mx *= 0.125
    my *= 0.125
    mz *= 0.125
 
    //Clamp the mean speed
    d = point_distance_3d(0, 0, 0, mx, my, mz)
    if d > max_speed
    {
    d = max_speed / d
    mx *= d
    my *= d
    mz *= d
    }
 
    //Apply the mean speed to the body position multiplied by overall_speed
    x += mx * overall_speed
    y += my * overall_speed
    z += mz * overall_speed
    instance_update_bbox_z()
 
    //Set the mean speed as the new speed
    xspeed = mx
    yspeed = my
    zspeed = mz

    //Reduce the relative rotation quat
    qx *= rot_accel
    qy *= rot_accel
    qz *= rot_accel
    qw = lerp(1, qw, rot_accel)

    //Normalize the relative rotation quat
    d = 1 / sqrt(sqr(qx) + sqr(qy) + sqr(qz) + sqr(qw))
 
    //Multiply the relative rotation quat with the rotation speed quat
    q = quat_multiply(qrot_x, qrot_y, qrot_z, qrot_w, qx * d, qy * d, qz * d, qw * d)
 
    //Reduce\decelerate the rotation speed quat
    qx = q[0] * rot_speed
    qy = q[1] * rot_speed
    qz = q[2] * rot_speed
    qw = lerp(1, q[3], rot_speed)
 
    //Normalize the rotation speed quat
    d = 1 / sqrt(sqr(qx) + sqr(qy) + sqr(qz) + sqr(qw))
 
    //Get up vector
    var up = matrix_transform_vertex(rot_matrix, 0, 0, -1);
 
    //Create a quat for the turning
    q = quat_create(up[0], up[1], up[2], -turn_amount)

    //Multiply the quat with the rotation speed quat
    q = quat_multiply(qx * d, qy * d, qz * d, qw * d, q[0], q[1], q[2], q[3])
    qx = q[0]
    qy = q[1]
    qz = q[2]
    qw = q[3]
    //Normalize the rotation speed quat
    d = 1 / sqrt(sqr(qx) + sqr(qy) + sqr(qz) + sqr(qw))
    qrot_x = qx * d
    qrot_y = qy * d
    qrot_z = qz * d
    qrot_w = qw * d
 
    //Convert the rotation speed quat to a matrix and add to the absolute rotation matrix
    rot_matrix = matrix_multiply(rot_matrix, matrix_create_from_quat(qrot_x, qrot_y, qrot_z, qrot_w))
 
    //Apply the rotation matrix to the vehicle's world matrix
    matrix = matrix_multiply(rot_matrix, matrix_build(x, y, z, 0, 0, 0, scale, scale, scale))

}

I need to add another collision stage which detects whether the actual cuboid is intersecting any triangles, cus it's able to go through walls along the edges of it where the balls aren't there. (You can see that at 2:16 on the video)
So I think I'd need to do raycasting\lines intersecting triangles tests and push the vehicle out of walls.
I just hope it doesn't interfere with the physics. I think if I put the lines slightly behind the balls, the balls will always hit the triangle first, apart from in the case where they don't collide (at the edges in between them). Then it should only do the edge line push back as an emergency.
Or maybe I could make it use capsules along the edges instead of spheres...

Also for smaller objects colliding with the vehicle I need to make them check if they're intersecting the rotated cuboid. I'm not sure exactly how to do that yet. I think it'll involve applying the inverse of the rotation matrix to them, to rotate them into the model space of the vehicle, then it can just do a simple nearest_point_on_bbox and check if that's intersecting their sphere\capsule.

I might also change some of the ways it handles friction and grip. I'm not sure yet. I just got it working in a nice way yesterday and settled with that for now.
I was also thinking of making the shell of the car separate to the wheels. So the shell will have the wobbly rotation and the wheels will grip precisely to the ground by drawing them at their actual position after collision instead of using the body's rotation.
I think that would give an effect like on a lot of driving games.

You should also be able to make any kind of shape you want with this system, like rigid body physics. And if it's an irregular shape it'd topple over and it'd need spread across the bottom to stay up, like how stools and chairs work.
I haven't actually tested this in the 3d version yet, but the 2d one did so I'm hoping it will.
To make them easier to make I could make a function that builds the body out of the model's skeleton.. Capsule collision would be extra useful there.
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Very cool! So you can use this to connect spheres into more complex shapes? Are you going to make this into a general thing? If so, could you also modify it for making ragdolls?
 

Kentae

Member
That car could use some more grip and power xD But oh my god man that's amazing! It seems to move very naturally, just awesome! ^^
 

Joe Ellis

Member
Thanks dudes!


@TheSnidr
Yeah I'm planning on making a few objects with this, one of them that's a general "physics object" or "obj_rigid_body" and it creates the spheres from the model it's using's bones. That way you can just create bones in the model wherever you want them to be.
And I could add "radius" & "weight" variables for bones.
I was thinking of doing the radius anyway for things like doing headshots and more precise bullet collisions, and the weight for ragdoll physics.

About ragdoll physics, I think it could use the same idea of each sphere doing it's own movement & collision, but I think there would be quite a lot of differences beside that.
I think it would be something like this:

1st stage: Each ball has it's own speed, apply speed to position, do collision pushback, set it's new speed by how much it's moved from previous position. (So this is the same)

2nd stage: all the balls would have to be constrained so that the lengths of the bones stay the same.
I'm not sure exactly how this'd be done.
One way could be to loop through them hierarchically from the root bone, and pull them back:
GML:
repeat num_bones
{
b = bones[++i]

repeat b.num_children
{
c = children[++j]

vx = c.x - b.x
vy = c.y - b.y
vz = c.z - b.z
l = c.length / point_distance_3d(0, 0, 0, vx, vy, vz)

c.x = b.x + (vx * l)
c.y = b.y + (vy * l)
c.z = b.z + (vz * l)

//maybe reduce bone's speed here too

}

}

I don't know if that would give the right effect though.
Maybe doing it backwards from end bones would be better..

Or another way could be with an average type process: (this'd give a stretchy effect though)
GML:
//Init new positions
repeat num_bones
{
with bones[++i]
{
new_x = 0
new_y = 0
new_z = 0
num_movements = 0

ox = x
oy = y
oz = z
}
}

//Pull connected bones back together
i = 0
repeat num_bones
{
b = bones[++i]
l = b.connected
j = 0
repeat c.num_connected
{
c = l[++j]

//Get vector
vx = c.x - b.x
vy = c.y - b.y
vz = c.z - b.z

//Get length
d = point_distance_3d(0, 0, 0, vx, vy, vz)

//Add pullback to connected bone
l = c.length / d
c.new_x = b.x + (vx * l)
c.new_y = b.y + (vy * l)
c.new_z = b.z + (vz * l)
c.num_movements += 1

//Maybe pull the bone towards the connected bone aswell (or do one or the other)
l = b.length / d
b.new_x = c.x - (vx * l)
b.new_y = c.y - (vy * l)
b.new_z = c.z - (vz * l)
b.num_movements += 1
}

}

//Set position to average of new positions
i = 0
repeat num_bones
{
with bones[++i]
{
x = new_x / num_movements
y = new_y / num_movements
z = new_z / num_movements

//Add speed from the movement (maybe)
xspeed += x - ox
yspeed += y - oy
zspeed += z - oz
}
}

It could maybe use all of those together, do the hierarchical ones after the stretchy one.
That might look the most realistic. I think it'd look more realistic than the hierarchical ones alone.

3rd stage, each ball would loop through balls it's connected to and get the average of their speeds (maybe including it's own).
Or maybe it could get the speeds by the length correcting process instead, that might be more realistic.

I think that would be the movement simulation working, but I'm not sure how it'd get the rotation speeds.
Maybe just get the vector difference of (parent pos to prev pos, parent pos to new pos).

Hmm, there's probably a few things I'm missing out at the minute.
It'd also need rotation constraints adding to certain bones, like the shoulders and spine to making sure the ragdoll doesn't get in some kind of impossible pose.

I'm interested to see how this'd work though now you've got me thinking about it haha :D

I'm gonna test out these ideas with a physics rope first, that shouldn't be too hard, and it should give me a good idea of what things it needs to do, and when.
 

Joe Ellis

Member
I just had an idea about writing the manual. Cus I've got some kind of mental block where every time I try to write something for it, my mind goes blank and I can't think where to begin with explaining a certain topic.
So I thought, cus I'm usually good at explaining things when people ask me, if any of you have any questions about using it, how certain parts work etc. you could you ask me here and I can use the answers to get a big chunk of the manual down, then I can fill in the gaps.

Also if any of you have got any advice on writing a manual that'd be really appreciated :)
 

Joe Ellis

Member
Yep, don't worry I've already sorted that out :D I'm using Helpsmith which can compile it as webhelp. So I'm assuming when it's viewed in chrome it can be translated. I don't know about other browsers cus I haven't used them. But I guess most can now.
 

Joe Ellis

Member
Here are some updates I've made the last few weeks

In short:
  • Removed custom collision system and built everything to work with gm's built in collision system.
  • Changed the way nodes are handled to be more general and versatile.
  • Added some cool features to nodes like linking, emitting light and the ability to create custom structs.
  • Improved file format, made error proof.
  • Worked out a fast way of doing indirect lightmapping.

And now an essay about them:

Collision System

The custom 3d collision system it used has been removed. This was basically 2 grids of everything (instances and precise triangles) that spread over the area of the level. But it was quite awkward to set up different objects doing different functions upon collision and wasn't\couldn't be as fast as gm's built it collision system.
So I changed it so instances use one of 2 mask sprites: mask_central(2x2 pixels with origin of 1, 1) or mask_topleft (1x1 pixels with origin of 0, 0) This way the bbox_left, right, top and bottom get updated automatically and the instances just have to update their bbox_z1(top\up) & bbox_z2(bottom\down) each step.
So now everything works with the built in collision functions and best of all, the collision events.
Now you can create collision events between objects and all you have to do is make sure it's also colliding along the z axis with
"if rectangle_in_rectangle(0, bbox_z1, 1, bbox_z2, 0, other.bbox_z1, 1, other.bbox_z2)" before any of the code for the event runs.
This has made everything with collision a lot more organized and easier to manage.
Before you had to assign\define specific functions to run when there is a collision with another object.
eg. object_define_collision(obj_item, item_collect)
And it this was pretty annoying to work with cus you'd always have to make a special function for that collision event.
But now you can put code right in the actual collision event and they're all contained inside each object so much easier to browse through.


Nodes

I've changed the way nodes(objects in the level) are handled to be a bit more general, and preset files can be a preset of any node type instead of just for instances.


Linking

I've made it so you can link nodes together. (By pressing L when the mouse is over them) And the links are displayed as lines between them. (This can be toggled off in the View menu)
So this could be used with triggers and things, like linking a switch to a door, so that when it's pulled it opens the door.
Or an invisible trigger in a doorway that when the player touches it it turns a light on and activates all the instances in that room.
You can just write a function for the trigger that loops through it's list of links and does different things to them depending on what they are. Like if it's a light and the light is off, turn it on. If it's an item and it's invisible, set it to visible. If it's an enemy, alert it and tell it
where the player is.
And you could do things like when an item is collected or an enemy is killed trigger something to happen elsewhere in the level like open a door or spawn something.

The level editor on rayman 1 (Rayman Designer) worked like this and it was a really easy and hands on way of dealing with triggers. Like you could actually see lines for what everything was linked to instead of it being hidden in their properties.


Custom Structs

I've added the ability to create static structs instead of instances in the level, so for any instance that uses a blank object with no events it can be a struct instead. The structs can have all the same properties as instances including having an object assigned to them in which case it will contain a set of variables that were defined for that object.

I don't know if I made it clear before but the variables for each object are defined in the event user 0 of each object and they're all executed once while the engine is starting up.
To define a variable, you just write one of the variable create functions which are all named "var_" + the name of the type you want it to be.
eg. an object's event user 0 could look like this:

//obj_projectile

var_real("Move Speed", 10)
var_real("Grav", 0)
var_preset("Explosion", "explosion_normal")
var_integer("Max Bounces", 3)
var_ratio("Bounce Scatter", 0.3)


Custom Save & Load Functions per object

I've added the option to define a custom save & load function for an object, so any instances or structs using it will run the custom functions when saving and loading it. (after the regular variables are saved)
So this should make it a lot easier to make your own types of things with specific file data
eg. dynamic\heightmap terrain, 3d voxel maps, sector maps(doom style) or something like the player instance having a nested array structure going on that can't be saved with the standard variable save functions.

I've also come up with a nice and easy way to handle each instance being able to have it's own inventory.
So for example a container, you could add an inventory to it with 3 crystals, 4 gems and 2 diamonds.
Then in it's destroy event you could make it spawn the inventory as collectible items.


Light emitters

Any instance or struct can have "emit_light" enabled, in which case it adds the light variables to it and they get updated automatically as they move about.
The light variables are:

light_active
light_x
light_y
light_z
light_color
light_brightness
light_radius

(The xyz values are relative, so they work like an offset)


Summary

With these additions, it doesn't really need the separate node types (lights, points and any other type I'd make in
the future (voxel maps, terrain etc.)) Now nodes are just instances or structs.


Presets

Presets can also work as "global structs", which can be used in a lot of ways. Such as inventory items, like different guns having different variables for fire_rate, damage, accuracy etc. and they can be useful in other cases like different enemy types sharing the same variables. They can all refer to their type struct instead of having identical copies of a set of variables.
This would also make it a lot easier to tweak these variables while testing and it'll update all instances using them instead of only being able to edit 1 instance at a time.


Postface

I think these changes together will make certain things a lot easier and less awkward to do. Most of them were made while I was trying to make certain example objects and something was really awkward.


File Format

The file format has been changed a bit and the way data is saved has been made more secure\error free.
The format before was able to crash in certain cases, like if you changed a variable type from a number to a string. A file saved before the change would still be using the number type and load the wrong type of data, and if the instance used it in it's update event (event user 1)
(which is executed when the level is loading) it could crash due to having the wrong data type for the variable.


Indirect Lightmapping

I worked out a nice and fast way of doing indirect lightmapping mostly on the gpu, without making it cast billions of light rays.
The way it does it is actually quite simple: (similar to light probing)

It uses a 1x1 surface to render to each pixel\texel on the lightmap at a time.
For each texel on the lightmap, it sets the view position to the position it is on the triangle's surface, pointing in the direction of the triangle's normal (+ random scatter). Then it simply renders the whole model so that it renders what that texel can "see". Which simulates light rays bouncing off the other triangles onto that spot on the lightmap.
Then the 1x1 surface is drawn onto the main lightmap surface at the correct position.

I thought this would be really slow at first cus of how it has to submit the whole model vbuffer 512x512(262144) times per pass. But because it's rendering to a 1x1 surface most of the geometry is culled before it reaches the pixel shader. So it can do it about 500 times per step without lagging.

From testing with a 512x512 lightmap, it takes roughly 10 seconds to perform 1 pass. Which is really fast considering what it's doing.
I think for a smooth\none grainy result it probably needs about 32 passes. (5 minutes) so you can just go and make another coffee while it's rendering. And for a final game quality lightmap with 255 passes it would take about 40 minutes.
I'll make a video of this soon once it's set up properly.
It should look the same as unreal at high res, and quake and other early lightmapped games at lower res.
You'll also have control over the scatter amount, curve balance between light and dark. And the lightmap gets saved as a png so you can edit it in an image program.
It also pads by n pixels when finished, by simply drawing itself across multiple times. Cus empty texels that aren't used by a triangle have 0 alpha.

This approach could be used for light probing aswell. And that'd be really fast cus each probe would only need about 30 samples.
So maybe it could be done in realtime ?
I'm not that interested in light probing to be fair but, maybe I will be if I can use this technique with them..
 
Top