Asset - Scripts ColMesh - 3D Collisions Made Easy!

TheSnidr

Heavy metal viking dentist
GMC Elder
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 1 on the Marketplace
Version 2.0.14 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:
levelColmesh = new colmesh();
This creates an empty ColMesh. 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:
Code:
levelColmesh.addMesh(“ColMesh Demo/Demo1Level.obj”);
Now it is already possible to perform collision checks and cast rays against this ColMesh, but if it contains a lot of geometry, that will be very slow.
It’s a better idea to subdivide it into smaller regions first! In order to subdivide a model, you need to supply the desired region size. A region
will always be an axis-aligned bounding cube, and regionSize is the side length of this cube. The smaller the region size, the less geometry will be in
each region, but the more regions and more memory usage there will be. Finding the balance between efficiency and memory usage is its own art!
But in short, subdivide your model like this, and then you’re ready to collide with it:
Code:
var regionSize = 120; //120 is a magic number I chose that fit well for my player size and level complexity. It may have to be different for your game!
levelColmesh.subdivide(regionSize);

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 requires you to 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 = new colmesh_collider_capsule(x, y, z, xup, yup, zup, radius, height, slopeAngle);
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.
There are a couple of optional arguments to this constructor function as well:
[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. This is by default set to 1, which means it will first sort the shapes it collides with, and then perform the collisions that displace the capsule the most first. Setting precision to 0 will skip the sorting step, providing faster collisions at the cost of precision. 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, this is how you can then make it avoid the colmesh:
Code:
//Make sure the collider has the same coordinates as the player
collider.x = x;
collider.y = y;
collider.z = z;

//Make the collider avoid the colmesh
collider.avoid(levelColmesh);

//Copy the coordinates of the collider back to the player
x = collider.x;
y = collider.y;
z = collider.z;
And that's it, now the player is being displaced out of the colmesh!

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 cube
  • Block (can have any orientation and non-uniform scaling, but not shear)
  • Capsule
  • Cylinder
  • Torus
  • Disk
These can be added to the ColMesh using ColMesh.addShape(shape). Here are some examples:
Code:
levelColmesh.addShape(new colmesh_sphere(x, y, z, radius));
levelColmesh.addShape(new colmesh_cube(x, y, z, sideLength));
levelColmesh.addShape(new colmesh_block(colmesh_matrix_build(x, y, z, 0, 0, 0, xSize / 2, ySize / 2, zSize / 2))); //The cube will be twice as big as the given scale
levelColmesh.addShape(new colmesh_capsule(x, y, z, xup, yup, xup, radius, height));
levelColmesh.addShape(new colmesh_cylinder(x, y, z, xup, yup, xup, radius, height));
levelColmesh.addShape(new colmesh_torus(x, y, z, xup, yup, xup, majorRadius, minorRadius));
levelColmesh.addShape(new colmesh_disk(x, y, z, xup, yup, xup, majorRadius, minorRadius));
addShape returns the handle of the new shape. 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:
Code:
M = colmesh_matrix_build(x, y, z, 0, 0, 0, 1, 1, 1);
dynamic = levelColmesh.addDynamic(new colmesh_cube(0, 0, 0, size), M);
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:
Code:
 M = colmesh_matrix_build(x, y, z, 0, current_time / 50, 0, 1, 1, 1);
shape.setMatrix(M, true);
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:
Code:
collider.avoid(levelColmesh);
var D = collider.getDeltaMatrix();
if (is_array(D))
{
    var P = matrix_transform_vertex(D, x, y, z);
    x = P[0];
    y = P[1];
    z = P[2];
}
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 colmesh for just the tree, subdivide it as usual, and then add it by reference to the level colmesh with a dynamic:
Code:
//Create a colmesh for just the tree mesh, and subdivide it
treeColmesh= new colmesh();
treeColmesh.addMesh("Tree.obj");
treeColmesh.subdivide(regionSize);

//Add multiple dynamics referencing the treecolmesh 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
    var shape = levelColmesh.addDynamic(treeColmesh, 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.
}
Casting rays

You can cast rays at the ColMesh using ColMesh.castRay(x1, y1, z1, x2, y2, z2). This function will always return a struct (whereas in previous versions it would only return a struct if the ray hit anything).
The returned struct is pretty similar to what you get when displacing a capsule out of the ColMesh.
ray.hit is true if there was an intersection, or false if not
ray.x/y/z is the point of intersection
ray.nx/ny/nz is the normal at the point of intersection
You’ll often want to cast a ray from the player’s previous position to its current position, just to make sure it never passes through level geometry. Like this:
Code:
//Cast a short-range ray from the previous position to the current position to avoid going through geometry
ray = levelColmesh.castRay(prevX, prevY, prevZ, x, y, z);
if (ray.hit)
{
    //If the ray hit anything, move the player to just outside the point of intersection, and then perform capsule collision as usual
    x = ray.x + ray.nx * .1;
    y = ray.y + ray.ny * .1;
    z = ray.z + ray.nz * .1;
}
This piece of code assumes that the player is spherical. It doesn’t entirely make sense for a capsule though… We may need a smarter solution for a capsule.
We can use the speed vector of the player to figure out the best point along the capsule for which to perform a ray cast. If the player is moving downwards, it makes the most sense to cast from its feet. If it’s moving upwards, we’ll most likely want to prevent its head from going through the ceiling. As such, we can slide the origin of the ray along the capsule like this:
Code:
var d = height * (.5 + .5 * sign(xup * (x - prevX) + yup * (y - prevY) + zup * (z - prevZ)));
var dx = xup * d;
var dy = yup * d;
var dz = zup * d;
ray = levelColmesh.castRay(prevX + dx, prevY + dy, prevZ + dz, x + dx, y + dy, z + dz);
if (ray.hit)
{
    //If the ray hit anything, move the player to just outside the point of intersection, and then perform capsule collision as usual
    x = ray.x + ray.nx * .1;
    y = ray.y + ray.ny * .1;
    z = ray.z + ray.nz * .1;
}
A ray cast should usually be followed by displacing the capsule properly out of the level geometry with collider.avoid(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.
You can add this kind of trigger objects as follows:
Code:
shape = levelColmesh.addTrigger(shape, solid, colFunc*, rayFunc*);
Notice how you have two optional arguments here. These allow you to control what should happen when the player collides with the shape, or when it is hit by a ray.
GMS 2.3 allows you to pass functions by reference. That means that you can create custom functions that should be executed instead of the regular collision response.
Demo 1 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()
{
    global.coins ++;                     //Increment the global variable "coins"
    instance_destroy();                     //This will destroy the current instance of oCoin
    levelColmesh.removeShape(shape);     //"shape" is oCoin's shape variable. Remove it from the ColMesh
    audio_play_sound(sndCoin, 0, false); //Play coin pickup sound
}

//Create a spherical collision shape for the coin
shape = new colmesh_sphere(x, y, z, radius)

//Add the coin to the ColMesh as a trigger
//The collision function will be executed if the player collides with the coin, using colmesh.displaceCapsule.
levelColmesh.addTrigger(shape, colFunc);
There has also been added a new optional argument to colmesh.displaceCapsule, that allows you to turn on or off collision functions. When turned off, the custom collision functions are not executed.
This is useful for objects that should only collide with the world and not trigger game objects.
Non-solid objects are NOT saved when using colmesh.save or colmesh.writeToBuffer.


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 = new colmesh();
if !(levelColmesh.load("ColMeshCache.cm"))
{
    //If a cache does not exist, generate a colmesh from an OBJ file, subdivide it, and save a cache
    levelColmesh.addMesh("Level.obj");
    levelColmesh.subdivide(130);
    levelColmesh.save("ColMeshCache.cm");
}
If your game should be able to run in HTML, you can’t use the ColMesh.save and load functions. Instead, you should save and load a buffer asynchronously, and use ColMesh.writeToBuffer and ColMesh.readFromBuffer.


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?
Potential future additions:
  • 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:
when i loaded the yymps from the market it had 3 errors
script: _mbuffer_init at line 28 :error parsing string x2
script: _load_bmp at line 92 : wrong number of arguments for function buffer_set_surface
 

TheSnidr

Heavy metal viking dentist
GMC Elder
when i loaded the yymps from the market it had 3 errors
script: _mbuffer_init at line 28 :error parsing string x2
script: _load_bmp at line 92 : wrong number of arguments for function buffer_set_surface
Those errors are from an old version... Are you using gms 2.3?
I will have to look into this, thank you for the report!
 

RetroBatter

Member
creating a colmesh seems different in the download scripts?
for example, creating a colmesh with
GML:
levelcolmesh = new colmesh();
throws up an error, but using colmesh_create seems to work fine.
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Okay something definitely is weird here. Are you using GMS 2.3? You seem to have gotten an old version of ColMesh, for whatever reason. It says on the Marketplace that the new version is published.
 

RetroBatter

Member
Okay something definitely is weird here. Are you using GMS 2.3? You seem to have gotten an old version of ColMesh, for whatever reason. It says on the Marketplace that the new version is published.
I have GMS 2.3.1
Just redownloaded it again and the scripts I downloaded from the marketplace are different to the scripts you can see from the preview tab on the marketplace page.

So, it seems like the marketplace is messing up and giving me the older version.
 

TheSnidr

Heavy metal viking dentist
GMC Elder
So it seems like downloading it from the Marketplace webpage is broken and gives you an old version.
This can be fixed by importing it through the "My library" tab in GMS 2.3, then you will get the correct version!
 

RetroBatter

Member
Is there a way to resize a mesh?
In my game I scale up the model's matrix, which seems to make the colmesh not match the model.
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Good suggestion, I will add a matrix as an argument in the function colmesh.addMesh.
For now, the only way to scale a mesh is to create a colmesh, then add your mesh to that colmesh as a dynamic, and scale that! It's a bit convoluted, so I like your suggestion better.
 

RetroBatter

Member
Good suggestion, I will add a matrix as an argument in the function colmesh.addMesh.
For now, the only way to scale a mesh is to create a colmesh, then add your mesh to that colmesh as a dynamic, and scale that! It's a bit convoluted, so I like your suggestion better.
Thanks! Looking forward to the future of this project :)
 

TheSnidr

Heavy metal viking dentist
GMC Elder
There, now colmesh.addMesh has an optional argument, which lets you transform the mesh before it's added to the ColMesh!
 

TheSnidr

Heavy metal viking dentist
GMC Elder
is there going to be SMF support eventually? OBJ files seem to be more of a pain if your adding them.
As in convert from SMF to ColMesh? Sure, that could be added, but it's a bit convoluted. The best way is to just load OBJs, precompute your colmeshes, save them to a custom format, and include that with your game. Loading a colmesh from a buffer is often many many times faster. The level in demo 1 for example takes 660 milliseconds to generate a colmesh for, and 46 milliseconds to load from file. The difference will be even more extreme for larger models!
 

Yal

🐧 *penguin noises*
GMC Elder
I tried downloading this before reading the comment about downloaders getting the wrong version, but the files seem to match the "preview" list 1:1 when I inspect it (same files with the same size). So taking Nocturne's comment about automatic version detection into account, it seems like the Marketplace thinks RetroBatter is still using an old version of Game Maker?
 
Is it possible to use smf along with the obj files. If there is, can you do a small demo of it? Im trying to combine smf with the col mesh and the best way i thought up was to have an invisible col mesh box or pill around it.
 

RetroBatter

Member
Is it possible to use smf along with the obj files. If there is, can you do a small demo of it? Im trying to combine smf with the col mesh and the best way i thought up was to have an invisible col mesh box or pill around it.
So what you could do is have a .smf and .obj of the model. Use the .smf to render the model and then use the .obj to generate a colmesh. Then you could save that colmesh to a buffer for future use and remove the .obj from your project.
 

57E

Member
I have to admit that this is all kinds of impressive.

I tried to do ellipsoid based 3d collision on gml few years ago and while it worked perfectly fine 95% of the time, but then you got an moving ellipsoid hitting an edge of a triangle at just the right way and then everything went straight to NaN hell. So I decided to abandon it for the sake of my mental health

So far I haven't found any of those fatal corner cases in your system, so my hat's off to you!
 

Tyg

Member
Is there a way to resize a mesh?
In my game I scale up the model's matrix, which seems to make the colmesh not match the model.
I have a moving platform i create the colmesh in create ...then in the step event i just assign the colmesh to mat with colmesh_transform and it follows my elevator im sure the same can be done with scaling
looking forward to trying out the new version..thanks for the great asset
 

TheSnidr

Heavy metal viking dentist
GMC Elder
The system is documented in this topic, the included demos, and in the function comments. If something is unclear then I'll also step in to help!
I've updated the ColMesh system, and added a new function for you: colmesh_convert_smf(model)!
This function accepts an SMF model, and outputs a buffer that can be used by colmesh.addMesh(). Use it like this:
Code:
var mesh = colmesh_convert_smf(SMFmodel);
levelColmesh.addMesh(mesh);
buffer_delete(mesh);
I've also fixed a bunch of smaller bugs in the code. Most notably was that you could only collide with triangles from one side, and would go straight through if you walked into them from the other side. This was because the sorting algorithm returned a negative priority for the triangles if you were on the "wrong" side, resulting in them being dropped.
This has been fixed! So remember to download the update :D
 

RetroBatter

Member
The system is documented in this topic, the included demos, and in the function comments. If something is unclear then I'll also step in to help!
I've updated the ColMesh system, and added a new function for you: colmesh_convert_smf(model)!
This function accepts an SMF model, and outputs a buffer that can be used by colmesh.addMesh(). Use it like this:
Code:
var mesh = colmesh_convert_smf(SMFmodel);
levelColmesh.addMesh(mesh);
buffer_delete(mesh);
I've also fixed a bunch of smaller bugs in the code. Most notably was that you could only collide with triangles from one side, and would go straight through if you walked into them from the other side. This was because the sorting algorithm returned a negative priority for the triangles if you were on the "wrong" side, resulting in them being dropped.
This has been fixed! So remember to download the update :D
Just updated and now I'm receiving this error when I load the colmesh buffers:

GML:
___________________________________________
############################################################################################
ERROR in
action number 1
of Create Event
for object oSystem:

Cannot apply sqrt to negative number.
 at gml_Script_anon_colmesh_gml_GlobalScript_colmesh_10300_colmesh_gml_GlobalScript_colmesh (line 345) -               l = 1 / sqrt(l);
############################################################################################
gml_Script_anon_colmesh_gml_GlobalScript_colmesh_10300_colmesh_gml_GlobalScript_colmesh (line 345)
gml_Script_anon_colmesh_gml_GlobalScript_colmesh_44772_colmesh_gml_GlobalScript_colmesh (line 1438) -                                    addTriangle(V);
gml_Script_model_buffers_load (line 32) -                             _colmesh.readFromBuffer(_buffer);
gml_Object_oSystem_Create_0 (line 12) - model_buffers_load();
 

Roa

Member
So honestly, I'm kinda confused how you would return important information from the ray cast.
IE: if you added enemies and their colliders joined the world, how would you be able to tell which dynamic collider you hit?

You should probably be able to return the collider that was hit or "noone" if it didn't. We know the ray didn't hit anything if you simply measure the distance between point 1 and 2, and the returned collision point is less than that. What collider you hit is much more impertinent information than a true or false outright.

The only option without it is to ray cast each and every enemy AND the level geo as separate meshes every single time a bullet is on screen, and that sounds painfully slow and wastefull.... or am I missing something?

[edit] I guess you could also take that ray collision point and compare it to the closest enemy to see if its basically touch, but that's sketchy when around tight level geo. It would still be much much better to be able to return the exact mesh you collided with.
 
Last edited:

TheSnidr

Heavy metal viking dentist
GMC Elder
So honestly, I'm kinda confused how you would return important information from the ray cast.
IE: if you added enemies and their colliders joined the world, how would you be able to tell which dynamic collider you hit?

You should probably be able to return the collider that was hit or "noone" if it didn't. We know the ray didn't hit anything if you simply measure the distance between point 1 and 2, and the returned collision point is less than that. What collider you hit is much more impertinent information than a true or false outright.

The only option without it is to ray cast each and every enemy AND the level geo as separate meshes every single time a bullet is on screen, and that sounds painfully slow and wastefull.... or am I missing something?

[edit] I guess you could also take that ray collision point and compare it to the closest enemy to see if its basically touch, but that's sketchy when around tight level geo. It would still be much much better to be able to return the exact mesh you collided with.
Excellent suggestion! The ray casting script will now return false if there was no intersection, or an array of the following format if there was:
[x, y, z, nx, ny, nz, shape]
To check if the ray hit anything, simply check if the result is an array with is_array(). If not, the ray did not hit anything.
"shape" will be either a struct if it hit a primitive, or a 12-index array containing three vertices and a normal if it hit a triangle.

The asset has been updated in the Marketplace
 
  • Love
Reactions: Roa

rayloi

Member
Hi Snidr ,
Version 1.0.18 ,Tri Mesh Maybe Bug ,can not collision
Thanks


___________________________________________
############################################################################################
ERROR in
action number 1
of Step Event0
for object oColmeshDemo1:

DoConv :1: illegal array use
at gml_Script_anon_colmesh_gml_GlobalScript_colmesh_22958_colmesh_gml_GlobalScript_colmesh (line 765) - if (regionCastRay(region, x1, y1, z1, x1 + ldx * t, y1 + ldy * t, z1 + ldz * t))
############################################################################################
gml_Script_anon_colmesh_gml_GlobalScript_colmesh_22958_colmesh_gml_GlobalScript_colmesh (line 765)
gml_Object_oColmeshDemo1_Step_0 (line 36) - ray = levelColmesh.castRay(prevX + dx, prevY + dy, prevZ + dz, x + dx, y + dy, z + dz);
 

Attachments

Last edited:

TheSnidr

Heavy metal viking dentist
GMC Elder
Oh wow, that broke. Gonna fix it as soon as I can. Seems to be related to the recent change in ray casting. Thank you for the report!
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Okay, ray casting fixed. Sorry for that, it was a silly mistake on my part.
What do you mean by trigger collision function?
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Ohh, yes, excellent suggestion! I've been so focused on making the collision response for this that I've completely forgotten that it could also be used for those kinds of collisions! :D
I will try to find a good way of enabling that kind of trigger collisions. I'm thinking maybe I should add collision "groups", and whenever you add shapes to a ColMesh, you decide which group to add it to. Then it's up to you to keep track of which groups contain what. Group 0 could for example contain solid collision objects, group 1 collectibles like coins or stars, group 2 trigger objects like doorways and the like.

Right now, you gotta do collisions with those kinds of objects manually!

Edit:
I thought of another way..... I will make it possible to mark objects as non-solid. You will be able to give each shape a collision response function, and when colliding with displaceCapsule, it will execute that function.
I'm rambling, will test it out once I have the chance.
 
Last edited:

TheSnidr

Heavy metal viking dentist
GMC Elder
Okay, big update is live on the Marketplace!
This update should not break any existing code or buffers.

There are a number of smaller changes. Here are the biggest:
  • New collision shape: Disk. Similar to a torus, except there's no hole in the middle.
  • Non-solid objects:
    • Now this is a big one! This will let you mark object as non-solid, letting the player walk straight through them.
    • HOWEVER! You can also give the object a custom collision function, that will be executed when the player collides with it using colmesh.displaceCapsule.
    • I've thrown out some coins for you to collect in demo 1. The coins will create a spherical collision shape, set it to non-solid, and give it a collision function that will destroy the coin, play a sound, and increment global.coins by 1:
Code:
//Create a collision function for the coin, telling it to destroy itself and remove its shape from the level ColMesh
colFunc = function()
{
    global.coins ++;                     //Increment the global variable "coins"
    instance_destroy(self);              //"self" in this regard will be the current instance of oCoin
    levelColmesh.removeShape(shape);     //"shape" is oCoin's shape variable. Remove it from the ColMesh
    audio_play_sound(sndCoin, 0, false); //Play coin pickup sound
}

//Create a spherical collision shape for the coin
shape = levelColmesh.addShape(new colmesh_sphere(x, y, z, radius));

//Give the coin the collision function we created.
//The collision function will be executed if the player collides with the coin, using colmesh.displaceCapsule.
shape.setNonsolid(colFunc);
You can also use this to give the shape a ray function, which will execute when the shape is hit by a ray.
You're given control over whether or not these scripts should be executed in the displaceCapsule function, as an added optional argument:
displaceCapsule(x, y, z, xup, yup, zup, radius, height, slopeAngle, fast*, executeColFunc*)

This will let you use the ColMesh system for picking up objects, getting powerups, going through doors and the like!

Here's the function description for shape.setNonsolid:
Code:
/// @func setNonsolid(colFunc*, rayFunc*)
    static setNonsolid = function(_colFunc, _rayFunc)
    {
        /*
            Setting a collision shape to non-solid will enable you to give it a custom collision script.
            This is useful for example for collisions with collectible objects like coins and powerups.
            Non-solid objects are not saved when using colmesh.save or colmesh.writeToBuffer.
          
            colFunc will be executed when executeColFunc is enabled and there is a collision with colmesh.displaceCapsule
                You have access to the following global variables:
                    cmCol - An array containing the current position of the calling object
                    cmCallingObject - The instance that is using colmesh.displaceCapsule
          
            rayFunc will be executed if the object is hit by a ray with colmesh.castRay.
                IMPORTANT: rayFunc should return true if the ray should stop once it hits the shape, or false if the ray should ignore the object, after rayFunc has been executed!
                You have access to the following global variables:
                    cmRay - An array containing the position of intersection with the non-solid object.
                    cmCallingObject - The instance that is using colmesh.castRay
        */
        colFunc = (is_undefined(_colFunc) ? function(){} : _colFunc);
        rayFunc = (is_undefined(_rayFunc) ? function(){} : _rayFunc);
        solid = false;
    }
 

rayloi

Member
Hi Snidr , Thanks for your update : )
addDynamic maybe not work ?

size = 64;
z = size / 2;
M = matrix_build(x, y, z, 0, 0, 0, 1, 1, 1);
shape = levelColmesh.addDynamic(new colmesh_cube(0, 0, 0, size), M);
shape.setNonsolid(colFunc);

SharedScreenshot47839473947.jpg
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Hi Snidr , Thanks for your update : )
addDynamic maybe not work ?

size = 64;
z = size / 2;
M = matrix_build(x, y, z, 0, 0, 0, 1, 1, 1);
shape = levelColmesh.addDynamic(new colmesh_cube(0, 0, 0, size), M);
shape.setNonsolid(colFunc);

[image]
Thank you! This has now been fixed.
A tip: There's also a function for moving non-dynamic objects. They won't update the player's delta matrix if it collides with them, but they will update the subdivision with their new positions.
All shapes now has this function:
Code:
move(colmesh, x, y, z)
Useful for collision objects like coins, not so much for platforms!

EDIT:
I added another function:
Code:
colmesh.addTrigger(shape, colFunc*, rayFunc*);
This is the equivalent of adding a shape with colmesh.addShape and using shape.setNonsolid! Looks better with a single function IMO :D
 
Last edited:

rayloi

Member
Hi Snidr

shape.move(levelColmesh, x , y, z + zz);
debug shape can't update Display position ?
but Trigger Function is work :)
and move Distance more than 220 will error
___________________________________________
############################################################################################
ERROR in
action number 1
of Step Event0
for object oColmeshDemo1:

Unable to find instance for object index 0
at gml_Script_anon_colmesh_gml_GlobalScript_colmesh_13957_colmesh_gml_GlobalScript_colmesh (line 530) - if (!shape.solid && !executeColFunc){continue;}
############################################################################################
gml_Script_anon_colmesh_gml_GlobalScript_colmesh_13957_colmesh_gml_GlobalScript_colmesh (line 530)
gml_Script_anon_colmesh_gml_GlobalScript_colmesh_12564_colmesh_gml_GlobalScript_colmesh (line 405) - return regionDisplaceCapsule(region, x, y, z, xup, yup, zup, radius, height, slopeAngle, fast, executeColFunc);
gml_Object_oColmeshDemo1_Step_0 (line 48) - col = levelColmesh.displaceCapsule(x, y, z, 0, 0, 1, radius, height, 40, fast, executeColfunc);


 

TheSnidr

Heavy metal viking dentist
GMC Elder
Thank you for your thorough testing!
I've reproduced and fixed both bugs in your latest report. The spheres not moving was due to an optimization I added earlier that assumed the shapes would never move xD It only applied to the drawing though, not the actual collisions.
The error was a mistake I did when adding objects to the unordered list. Objects are added to this list if they are outside the boundaries of the ColMesh. I added a real value instead of the correct struct.
The Marketplace asset has been updated.

Again, thanks!

EDIT:
New update available!
I've changed the spatial hashing algorithm so that it does not depend on the boundary of the ColMesh.
This means that you can now subdivide your ColMesh immediately after creating it, and then just keep on adding objects after, and they will add themselves properly to the spatial hash as well, leading to a much higher quality of life!
 
Last edited:

duran can

Member

Script: _load_bmp at line 90 : wrong number of arguments for function buffer_set_surface


IDE: v2.3.15
when i delete 2 argument like that buffer_set_surface(texBuff, s, 0); , working great on windows.

but html5
1614327758870.png
 

Iggig

Member
I've been using the colmesh stuff and it's been super amazing! But, I just updated GameMaker today to IDE v2.3.2.556 and now I've got this compile error I've never seen before

Script: colmesh at line 1357 : Unknown token eStatic in CompileExpression
Script: colmesh at line 1354 : statement in a switch MUST appear after case or default

I tried messing around with the switch statements in the scripts, and also redownloaded the marketplace asset to see if that would help, but nothing really fixed it. I also removed the colmesh library from my project, just to make sure it wasn't anything else, and the rest of my code is compiling just fine without it. Not sure what to do
 

Roa

Member
I've been using the colmesh stuff and it's been super amazing! But, I just updated GameMaker today to IDE v2.3.2.556 and now I've got this compile error I've never seen before

Script: colmesh at line 1357 : Unknown token eStatic in CompileExpression
Script: colmesh at line 1354 : statement in a switch MUST appear after case or default

I tried messing around with the switch statements in the scripts, and also redownloaded the marketplace asset to see if that would help, but nothing really fixed it. I also removed the colmesh library from my project, just to make sure it wasn't anything else, and the rest of my code is compiling just fine without it. Not sure what to do
given there is no refrence to a script or any object called colmesh, nor a script with 1000+ lines, id see how that would be a problem....

How about posting the real error code?
 

RetroBatter

Member
given there is no refrence to a script or any object called colmesh, nor a script with 1000+ lines, id see how that would be a problem....

How about posting the real error code?
There is a colmesh.gml and I'm receiving the same error as them after updating to runtime v2.3.2.420
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Thank you for reporting this!
Though I'm sorry to say I have no idea what's causing this. The error message doesn't make sense to me, the switch statement looks perfectly correct to me.
Since this error appeared after updating the runtime, I'm guessing it's a bug in GM. Gonna try to narrow it down and report to YoYo.

EDIT:
Found the problem!
GM no longer appreciates when you define static variables inside switch statements, it seems!
So the problem is under the cases eColMeshShape.Mesh and eColMeshShape.Block. In these cases I define static arrays V and M. If you move these definitions out of the switch statement, it will compile properly. EDIT: Actually, it may make more sense to replace "static" with "var".
I'll update to the marketplace soon.

EDIT2:
Well, the Marketplace rejected my update without telling me why.

EDIT3:
The fix is live on the Marketplace :D
 
Last edited:

TheSnidr

Heavy metal viking dentist
GMC Elder
New update is now available on the Marketplace! Check first post for link.

The biggest change this time around is with how triggers are used. I've removed the old shape.setCollisionFunction and shape.setRayFunction functions.
Now you can instead mark a shape as a trigger with shape.setTrigger(solid, colFunc, rayFunc), or you can add them directly to the ColMesh as a trigger with colMesh.addTrigger(shape, solid, colFunc, rayFunc).
The ray function no longer has to return a value. If the shape is solid, the ray will stop. If the shape is not solid, the ray will pass through.
This change lets me store colFunc and rayFunc as variables in only the shapes that are also triggers.
Marking a shape as a trigger will change its type variable to eColMeshShape.Trigger, regardless of the shape it was previously. It still keeps all its original functions though, so it'll work the same way.
This makes it easier to identify triggers in the save and load scripts, since methods can't be saved to file.

There have also been various minor changes done here and there, for increased readability and efficiency.
 

TheSnidr

Heavy metal viking dentist
GMC Elder
New update is live!
This one brings with it a major speed improvement :D
I've added a check against each shape's axis-aligned bounding box when adding them to the list of shapes for which collisions are calculated. I should've done this long ago, but I honestly didn't think it would improve the speed this much.
For me, demo 1 used to run at about 800 fps (with drawing and everything enabled), now it runs at above 1100 fps. So we're talking significant speed boost here.

Makes you wonder what other optimizations could be possible??
 
Top