• Hey! Guest! The 40th (!!!) GMC Jam will take place between February 25th, 12:00 UTC to March 1st 12:00 UTC. Why not join in this very special anniversary jam! Click here to find out more!

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:

 
Top