ColMesh - 3D Collisions Made Easy!
License
The ColMesh system is licensed under a CreativeCommons Attribution 4.0 International License.
This means you are free to use it in both personal and commercial projects, free of charge.
Appropriate credit is required.
What is a ColMesh?
A ColMesh is a collection of 3D primitives and triangle meshes against which you can cast rays and do collision checks. It is basically an easy-to-use 3D collision system for GMS 2.3.
What does it do?
It will push your player out of level geometry.
It handles slope calculations for you so that your player doesn’t slide down slopes.
It lets you move platforms around and gives you everything you need to make sure your player moves the same way.
It lets you cast rays so that your player can shoot bullets or laser beams, and by casting a ray from the player’s previous coordinate to the new coordinate before doing collision checking, you can make sure the player never falls through level geometry.
How does it do it?
It’s all GML, so it should work on any platform. Any object that should check for collisions against level geometry must be represented by either a sphere or a capsule.
The level can be subdivided into smaller regions so that your player only has to check for collisions with nearby primitives.
For the actual collisions it uses this thing, you might have heard of it, it's called math.
But why would I make a 3D game in GameMaker?
That’s entirely up to you. There are better tools out there, with branch standard 3D physics dlls and the like.
Only use GM for 3D games if you really wanna use GM for 3D games
And why did you make this tool?
Because I really wanna use GM for 3D games.
What you see here is a ColMesh that has been placed inside itself, causing a recursive effect. Notice how collisions even works properly against the smaller ColMeshes
Okay!
With that out of the way, let’s get into how you can actually use this tool.
Download it from here:
Join Discord channel
Executable demo
Version 2.0.17 download
The zip contains an executable demo that has been compiled with the YYC. Since all the calculations are done CPU-side, the collision system sees a massive boost when compiling with YYC compared to VM.
Getting started
No initialization necessary, just import the ColMesh scripts, and it’s ready to be used.
Create a new ColMesh like this:
This creates an empty ColMesh that automatically subdivides your level into smaller regions into a structure called an octree. Now populate it with some geometry! There are a bunch of primitives that can be used.
For now, let’s just add a single triangle mesh by giving it the path to an OBJ file:
Now it is already possible to perform collision checks and cast rays against this ColMesh. The cm_add_obj function also has a bunch of optional arguments that give you more control, like a matrix argument for transforming the mesh before adding it, and arguments for specifying whether the triangles should be single- or double sided, but we'll get into that later.
Colliding with the ColMesh
A popular way of doing 2D collisions in GameMaker is to basically step towards the target position, checking for collisions at each step and exiting the loop if it finds a collision.
In contrast, the ColMesh system works best if you move your collider first, and then displace it out of the level geometry.
In the player's create event, you can create a collider capsule. Only capsule colliders are supported at this time. Give the capsule some properties like radius and height, an up vector, and the maximum slope angle that can be walked on without sliding down:
SlopeAngle is given in degrees. If set to 0, the capsule will slide on all surfaces. Setting it to 90 degrees will break your collisions, divide by zero and possibly end the universe. But anything between 0 and 90 will make your player not slide as long as the normal vector of the ground beneath the player is sufficiently parallel to the player's up vector.
[precision]: The maximum number of times to repeat collision calculations for this capsule. If the algorithm is sure it has found the optimal place of the capsule it will exit early. Setting precision to 0 will skip the sorting step, providing faster collisions at the cost of precision, useful for things like making the camera avoid geometry. You can set the precision to 10 if you'd like, and it'll still run mostly the same, but the algorithm will have more room to adjust the position of the capsule.
[mask]: This is an advanced feature letting you define which collision groups this capsule should check for collisions with. It'll compare the mask of the collider to the group of each shape, and only perform collisions if they match.
In the player's step event, we can make the player move while avoiding the colmesh. I like using verlet integration for player movement, this is shown in all the included demos. Assuming you've already made your player move this step by changing its x, y and z values, this is how you can then make it avoid the colmesh:
And that's it, now the player is being displaced out of the colmesh!
There's also the cm_displace function, which does not make the player move in smaller steps, but this has a higher risk of allowing the player to move through level geometry if it moves too fast.
How to add primitives
At the moment, these are the supported primitives that can be put into a ColMesh, in addition to triangle meshes:
The shapes can then be added to the colmesh like so:
In addition, all shape constructor functions have an optional argument [group], which lets you define the collision groups of this shape. More on this later.
If you want your primitives to move, you should add what’s called a dynamic. What’s that? Well, just keep reading…
Dynamic shapes
If you want your objects to move, you should add them as “dynamics”. A dynamic is a container for a primitive, or even a whole ColMesh, that can be moved around inside the ColMesh.
Adding new dynamics to a ColMesh is a pretty similar process to adding regular primitives:
Notice how the cube itself is added to local coordinates (0, 0, 0), and then its world-space position is stored in a matrix called M. This will center the cube at (x, y, z), and if you apply any rotations, it will rotate around its own center. If you had done it the other way, adding the cube to (x, y, z) and setting the matrix coordinates to (0, 0, 0), the pivot point of the cube will be at the world-space (0, 0, 0), which in most cases will not make sense.
Here’s how you can make it move. This little piece of code will make the cube rotate around the worldspace Y axis:
Now the cube is rotating, but the player will not rotate with it just yet. We need a way to figure out how the cube the player is standing on has moved, rotated, or been scaled since the previous step.
And for that, we have this useful function:
This will transform the player’s coordinates in the same manner that the cube it’s standing on has been transformed, making the player move and rotate with the cube.
Dynamics can also be used to store more complex objects by reference, instead of adding all their subshapes to the colmesh every time. Say for example you want to create a forest with a bunch of trees that all have the same collision shape, only rotated and scaled slightly differently. In this case, you can make another octree for just the tree, and then add it by reference to the level colmesh with a dynamic:
Casting rays
You can cast rays at the ColMesh like this:
This function will always return an array containing info about the ray.
The returned struct is pretty similar to what you get when displacing a capsule out of the ColMesh:
Trigger objects
So far we've only covered solid objects.
But a 3D world also contains non-solid objects, for example collectible objects like coins and powerups, and trigger objects like doors and portals. An object can be given a trigger function like this:
shape = levelColmesh.addTrigger(shape, solid, colFunc*, rayFunc*);[/code]
The included demo showcases how you can add collectible coins to a 3D level. The coins will send their collision shape a function that will increment global.coins by 1, play a sound, delete the coin object and remove the shape from the ColMesh.
The code for this looks like this:
In order to make the collider actually activate triggers, you can run this function on the collider:
This will check for collisions with triggers, and activate their functions. So now we've made trigger objects that execute a custom function when collided with! But so far they're all completely solid. A game needs triggers that the player can walk through. In order to do this, we'll need to introduce a new concept:
Masks and groups
In order to allow colliders to react differently to different kinds of objects, we need to utilize groups. Objects can be added to any number of groups, and then when collision checks are performed, the mask specifies which groups to do collision checking against. The built-in list of groups can be modified and expanded upon, and is found in the script cm_init:
Say for example you want your triggers to be non-solid, yet the trigger activation function should still register a collision when the collider touches the object. All shape creation functions have an optional argument for defining the group. If not specified, all objects are added to the solid group by default. Say we want to make a non-solid trigger in the shape of a disk:
Then when doing collisions between a collider and the level, make sure to specify the CM_GROUP_SOLID group as a mask to avoid colliding with the non-solid disk:
And then when checking for collisions with triggers, specify the trigger mask to avoid unnecessarily checking for collisions with objects that don't have trigger functions:
Groups can also be combined! If you want an object to be in the solid group, the trigger group and the metal group for example, you can combine them like this:
Saving and loading
You’ll typically want to precompute your ColMeshes. Generating and subdividing a ColMesh can be a slow process, so luckily you can easily save and load them to and from buffers.
If you don’t want to include the precomputed ColMeshes, you could generate them the first time they’re needed, and then cache them in local files like this:
Planned features
These are some shapes that are planned:
Thank you!
Feedback is always appreciated.
Snidr
License
The ColMesh system is licensed under a CreativeCommons Attribution 4.0 International License.
This means you are free to use it in both personal and commercial projects, free of charge.
Appropriate credit is required.
What is a ColMesh?
A ColMesh is a collection of 3D primitives and triangle meshes against which you can cast rays and do collision checks. It is basically an easy-to-use 3D collision system for GMS 2.3.
What does it do?
It will push your player out of level geometry.
It handles slope calculations for you so that your player doesn’t slide down slopes.
It lets you move platforms around and gives you everything you need to make sure your player moves the same way.
It lets you cast rays so that your player can shoot bullets or laser beams, and by casting a ray from the player’s previous coordinate to the new coordinate before doing collision checking, you can make sure the player never falls through level geometry.
How does it do it?
It’s all GML, so it should work on any platform. Any object that should check for collisions against level geometry must be represented by either a sphere or a capsule.
The level can be subdivided into smaller regions so that your player only has to check for collisions with nearby primitives.
For the actual collisions it uses this thing, you might have heard of it, it's called math.
But why would I make a 3D game in GameMaker?
That’s entirely up to you. There are better tools out there, with branch standard 3D physics dlls and the like.
Only use GM for 3D games if you really wanna use GM for 3D games
And why did you make this tool?
Because I really wanna use GM for 3D games.
What you see here is a ColMesh that has been placed inside itself, causing a recursive effect. Notice how collisions even works properly against the smaller ColMeshes
Okay!
With that out of the way, let’s get into how you can actually use this tool.
Download it from here:
Join Discord channel
Executable demo
Version 2.0.17 download
The zip contains an executable demo that has been compiled with the YYC. Since all the calculations are done CPU-side, the collision system sees a massive boost when compiling with YYC compared to VM.
Getting started
No initialization necessary, just import the ColMesh scripts, and it’s ready to be used.
Create a new ColMesh like this:
Code:
var regionsize = 100;
levelColmesh = cm_octree(regionsize );
For now, let’s just add a single triangle mesh by giving it the path to an OBJ file:
Code:
cm_add_obj(levelColmesh, “ColMesh Demo/Demo1Level.obj”);
Colliding with the ColMesh
A popular way of doing 2D collisions in GameMaker is to basically step towards the target position, checking for collisions at each step and exiting the loop if it finds a collision.
In contrast, the ColMesh system works best if you move your collider first, and then displace it out of the level geometry.
In the player's create event, you can create a collider capsule. Only capsule colliders are supported at this time. Give the capsule some properties like radius and height, an up vector, and the maximum slope angle that can be walked on without sliding down:
Code:
collider = cm_collider(x, y, z, xup, yup, zup, radius, height, slopeAngle, precision, mask);
[precision]: The maximum number of times to repeat collision calculations for this capsule. If the algorithm is sure it has found the optimal place of the capsule it will exit early. Setting precision to 0 will skip the sorting step, providing faster collisions at the cost of precision, useful for things like making the camera avoid geometry. You can set the precision to 10 if you'd like, and it'll still run mostly the same, but the algorithm will have more room to adjust the position of the capsule.
[mask]: This is an advanced feature letting you define which collision groups this capsule should check for collisions with. It'll compare the mask of the collider to the group of each shape, and only perform collisions if they match.
In the player's step event, we can make the player move while avoiding the colmesh. I like using verlet integration for player movement, this is shown in all the included demos. Assuming you've already made your player move this step by changing its x, y and z values, this is how you can then make it avoid the colmesh:
Code:
//-----------------------------------------------------
// Avoid level geometry by stepping towards the target position
if cm_collider_step_towards(collider, levelColmesh, x, y, z) //This returns true if the player touched the level geometry
{
x = collider[CM.X];
y = collider[CM.Y];
z = collider[CM.Z];
}
There's also the cm_displace function, which does not make the player move in smaller steps, but this has a higher risk of allowing the player to move through level geometry if it moves too fast.
How to add primitives
At the moment, these are the supported primitives that can be put into a ColMesh, in addition to triangle meshes:
- Sphere
- Axis-aligned box
- Oriented box (can have any orientation and non-uniform scaling, but not shear)
- Capsule
- Cylinder
- Torus
- Disk
Code:
sphere = cm_sphere(x, y, z, radius);
capsule = cm_capsule(x1, y1, z1, x2, y2, z2, radius);
cylinder = cm_cylinder(x1, y1, z1, x2, y2, z2, radius);
aab = cm_aab(x, y, z, size, size, size); //Axis-aligned box
box = cm_box(cm_matrix_build(x, y, z, xangle, yangle, zangle, xscale, yscale, zscale)); //cm_matrix_build scales first, then rotates, which makes more sense for a box than what matrix_build does.
disk = cm_disk(x, y, z, nx, ny, nz, majorRadius, minorRadius); //nx, ny, nz is the normal vector of the disk
torus = cm_torus(x, y, z, nx, ny, nz, majorRadius, minorRadius); //nx, ny, nz is the normal vector of the ring
Code:
cm_add(levelColmesh, sphere);
cm_add(levelColmesh, capsule);
cm_add(levelColmesh, cylinder);
cm_add(levelColmesh, aab);
cm_add(levelColmesh, box);
cm_add(levelColmesh, disk);
cm_add(levelColmesh, torus);
If you want your primitives to move, you should add what’s called a dynamic. What’s that? Well, just keep reading…
Dynamic shapes
If you want your objects to move, you should add them as “dynamics”. A dynamic is a container for a primitive, or even a whole ColMesh, that can be moved around inside the ColMesh.
Adding new dynamics to a ColMesh is a pretty similar process to adding regular primitives:
Code:
M = colmesh_matrix_build(x, y, z, 0, 0, 0, 1, 1, 1);
cube = cm_aab(0, 0, 0, size, size, size);
dynamic = cm_add(levelColmesh, cm_dynamic(cube, M));
Here’s how you can make it move. This little piece of code will make the cube rotate around the worldspace Y axis:
Code:
//Create a matrix
M = colmesh_matrix_build(x, y, z, 0, current_time / 50, 0, 1, 1, 1);
//Set the transformation of the dynamic to the matrix. Also set the "moving" argument to true. This will enable us to get the change in transformation later, useful for making the player move along with moving platforms when standing on them!
var moving = true;
cm_dynamic_set_matrix(dynamic, M, moving);
//Since we've moved the dynamic, we need to update all the containers that contain it, otherwise they'll keep references to them in regions they no longer occupy.
cm_dynamic_update_container(dynamic, levelColmesh);
And for that, we have this useful function:
Code:
//-----------------------------------------------------
// Apply the previous step's delta matrix
var D = cm_collider_get_delta_matrix(collider);
if (is_array(D))
{
var P = matrix_transform_vertex(D, x, y, z);
x = P[0];
y = P[1];
z = P[2];
}
Dynamics can also be used to store more complex objects by reference, instead of adding all their subshapes to the colmesh every time. Say for example you want to create a forest with a bunch of trees that all have the same collision shape, only rotated and scaled slightly differently. In this case, you can make another octree for just the tree, and then add it by reference to the level colmesh with a dynamic:
Code:
//Create a colmesh for just the tree mesh
treeMesh= cm_octree(100)
cm_add_obj(treeMesh, "SmallTree.obj");
//Add multiple dynamics referencing the treemesh to the level
repeat 10
{
var M = matrix_build(random(room_width), random(room_height), 0, 0, 0, random(360), 1, 1, 1); //Build a matrix for a random position in the room
cm_add(levelColmesh, cm_dynamic(treeMesh, M));
//Now the tree colmesh has been added by reference, instead of being copied over. Another useful fact here is that now each tree can have their own trigger functions.
}
You can cast rays at the ColMesh like this:
Code:
ray = cm_cast_ray(colmesh, cm_ray(x1, y1, z1, x2, y2, z2));
The returned struct is pretty similar to what you get when displacing a capsule out of the ColMesh:
Code:
ray[CM_RAY.HIT]; //This is true if the ray hit anything, false if it didn't
ray[CM_RAY.T] //This is the ratio along the ray where an intersection was found. 1 means the end of the ray, 0 means the start
ray[CM_RAY.X], ray[CM_RAY.Y], ray[CM_RAY.Z] //These are the position of intersection
ray[CM_RAY.NX], ray[CM_RAY.NY], ray[CM_RAY.NZ] //These are the normal of intersection
ray[CM_RAY.OBJECT] //This is the object the ray intersects
Trigger objects
So far we've only covered solid objects.
But a 3D world also contains non-solid objects, for example collectible objects like coins and powerups, and trigger objects like doors and portals. An object can be given a trigger function like this:
Code:
cm_trigger_set(object, triggerfunction);
The included demo showcases how you can add collectible coins to a 3D level. The coins will send their collision shape a function that will increment global.coins by 1, play a sound, delete the coin object and remove the shape from the ColMesh.
The code for this looks like this:
Code:
//Create a collision function for the coin, telling it to destroy itself and remove its shape from the level ColMesh
colFunc = function()
{
instance_destroy(); //This will destroy the current instance of oCoin
cm_remove(levelColmesh, shape); //"shape" is oCoin's shape variable. Remove it from the ColMesh
audio_play_sound(sndColmeshDemo2Coin, 0, false); //Play coin pickup sound
}
//Create a spherical collision shape for the coin
//Give the coin the collision function we created.
//The collision function will be executed if the player collides with the coin, using colmesh.displaceCapsule.
sphere = cm_add(levelColmesh, cm_sphere(x, y, z, radius, CM_GROUP_TRIGGER));
cm_trigger_set(sphere, colFunc);
Code:
cm_collider_activate_triggers(collider, object);
Masks and groups
In order to allow colliders to react differently to different kinds of objects, we need to utilize groups. Objects can be added to any number of groups, and then when collision checks are performed, the mask specifies which groups to do collision checking against. The built-in list of groups can be modified and expanded upon, and is found in the script cm_init:
Code:
//Groups
#macro CM_GROUP_SOLID (1 << 0) //This is the only group that must NOT be removed! The rest can be modified.
#macro CM_GROUP_TRIGGER (1 << 1)
#macro CM_GROUP_OTHER (1 << 2)
#macro CM_GROUP_GRASS (1 << 3)
#macro CM_GROUP_METAL (1 << 4)
Code:
var bigradius = 200;
var smallradius = 30;
var group = CM_GROUP_TRIGGER;
disk = cm_disk(x, y, z, nx, ny, nz, bigradius, smallradius, group);
cm_add(levelColmesh, disk);
Code:
var mask = CM_GROUP_SOLID;
if cm_collider_step_towards(collider, levelColmesh, x, y, z, mask) //This returns true if the player touched the level geometry
{
x = collider[CM.X];
y = collider[CM.Y];
z = collider[CM.Z];
}
Code:
cm_collider_activate_triggers(collider, levelColmesh, CM_GROUP_TRIGGER);
Code:
var group = CM_GROUP_SOLID | CM_GROUP_TRIGGER | CM_GROUP_METAL;
var shape = cm_sphere(x, y, z, radius, group);
Saving and loading
You’ll typically want to precompute your ColMeshes. Generating and subdividing a ColMesh can be a slow process, so luckily you can easily save and load them to and from buffers.
If you don’t want to include the precomputed ColMeshes, you could generate them the first time they’re needed, and then cache them in local files like this:
Code:
levelColmesh = cm_load("ColMeshCache.ini");
if (is_undefined(levelColmesh))
{
levelColmesh = cm_octree(regionsize);
cm_add_obj(levelColmesh, "Level.obj");
cm_save(levelColmesh, "ColMeshCache.ini");
}
Planned features
These are some shapes that are planned:
- Halfpipe/partial hollow cylinder
- Bowl/partial hollow sphere
- Planar heightmap
- Spherical heightmap
- Other kinds of heightmaps?
- Subtractive shapes, so that you can “cut away” parts of primitives.
- Physics simulations
- Combining ColMesh and SMF to create ragdolls
Thank you!
Feedback is always appreciated.
Snidr
Last edited:

