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
Marketplace link
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
Now, let’s go into the player’s step event. Avoiding a ColMesh can be done in varying levels of complexity. I’m first gonna show you the straightforward way of doing it:
Code:
var slopeAngle = 40; //The threshhold where if the slope is steeper, the player will start to slide
var fast = false; //You’ll usually want to turn off the “fast” collision checking for important objects like the player
col = levelColmesh.displaceCapsule(x, y, z, xup, yup, zup, radius, height, slopeAngle, fast);
if (col[6]) //If we’re touching ground
{
    x = col[0];
    y = col[1];
    z = col[2];
}
So what happens here? Let’s break it down a bit, and look at the first 8 arguments: x, y, z, xup, yup, zup, radius, height. These describe a capsule.
  • xyz are the cordinates of the bottom of the capsule.
  • xup, yup, zup are the up-vector of the player, this can usually be set to [0, 0, 1].
  • Radius is the radius of the capsule, and height is the length of the capsule along the up-direction. Setting height to 0 will remove any calculations needed for capsule and just perform collisions for a sphere instead, which is a bit faster.
  • SlopeAngle is useful for preventing the player from sliding around on even the smallest slopes.
Whenever the angle of the ground beneath the capsule is equal to or lower than the slope angle, the player will only be pushed upwards instead.
Setting it to 0 will make the player slide on all surfaces. Setting it to 90 degrees will break your collisions, divide by zero, and possibly end the universe.

When checking for collisions, the potential collision points are normally sorted so that the collision with the greatest displacement is performed first. This is not the case if fast is set to true,
then it will just push the capsule out on a first-come-first-serve basis. This is useful for stuff like pushing the camera out of level geometry, or for enemies and environment objects.
The function returns an array of the following format:
[x, y, z, nx, ny, nz, collision (true or false)]
The first three indices contain the position of the bottom of the capsule, after it has been displaced out of the colmesh.
The next three indices contain the normal of the slope beneath the player. This can be used for example to figure out if the player should be allowed to jump. If the slope is too steep, maybe you shouldn’t let the player jump all the way to the top of the hill?
The last index is either true or false. If it’s true, there was a collision, and the capsule should be displaced.
Now your player is avoiding the levelColmesh! Of course, you still need to make your player move when you press your input buttons etc, but that’s up to you.

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. You can read from it, but the shape’s parameters should not be changed once it has been created.
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:
 var D = levelColmesh.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 was transformed from the previous step, making the player move and rotate with the cube.
Now, what follows is a bit of a technical explanation, so if you don’t feel ready for that just yet, feel free to skip it:
Okay, excellent, glad you’re interested in the technical details as well! Finding a general solution to this was a bit tricky, but I believe this is as close as I’m gonna get.
When the player (or any other object, for that matter) uses levelColmesh.displaceCapsule to collide with a ColMesh, it “secretly” creates a ds_queue in a local variable of the calling object. They are of course stored in the ColMesh as well, and will be destroyed when the ColMesh is cleared.
If the shape that causes the greatest displacement is a moving object, its current world matrix is added to this queue. It also adds the inverse of the previous world matrix. Multiplying these two matrices together will result in the delta matrix. However, there are some more complications.

The player needs to be transformed *before* colliding with the ColMesh, otherwise the transformation may cause it to end up inside the ColMesh. But we don’t get the delta matrix until *after* the collision has been done. If we multiply the two matrices together here and now, and then use the result next step, that delta matrix will already be outdated, and the player would appear to be “lagging behind” the moving object. This is why it’s so useful to save the matrices as they are – since the matrices are stored in the ds_queue by reference, they will also automatically be updated the next step once the moving object has used shape.setMatrix!

Okay, that kinda makes sense, but why use a ds_queue?
This solves another problem: A dynamic can contain a whole different ColMesh. And that ColMesh can also contain moving objects, or even more ColMeshes. A ColMesh may even contain itself, causing a recursive call (capped at 8 recursions by default, but you can change this manually). The transformations of each dynamic must stack on top of each other, starting at the bottom. As such, the queue could contain any number of matrices that all need to be multiplied together to find the final world-space delta matrix.

So that’s how moving objects work!


Casting rays

You can cast rays at the ColMesh using ColMesh.castRay(x1, y1, z1, x2, y2, z2). It will return the first point of intersection as an array of the following format:
[x, y, z, nx, ny, nz, intersectedShape]
It returns false if there was no intersection.
The returned array is pretty similar to what you get when displacing a capsule out of the ColMesh.
xyz is the point of intersection
nx, ny, nz is the normal at the point of intersection
The last index contains the shape struct that was hit.
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 (is_array(ray))
{
    x = ray[0] - (x - prevX) * .1; //Subtract the original vector multiplied by some small number so that the player ends up just outside of the intersection
    y = ray[1] - (y - prevY) * .1;
    z = ray[2] - (z - prevZ) * .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 (is_array(ray))
{
    x = ray[0] - dx - (x - prevX) * .1;
    y = ray[1] - dy - (y - prevY) * .1;
    z = ray[2] - dz - (z - prevZ) * .1;
}
A ray cast should usually be followed by displacing the capsule properly out of the level geometry with ColMesh.displaceCapsule.


Non-solid 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 mark a shape as non-solid with the following function:
Code:
shape.setNonsolid(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 = 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);
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:

FoxyOfJungle

Kazan Games
Fantastic! I was already looking forward to a more sophisticated collision method, what you do in 3D with Game Maker Studio 2 is sensational 😁

And why did you make this tool?
Because I really wanna use GM for 3D games.
I've tried to explain this to some people in the past lol
 
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:
Top