I have been researching for a while and can not find an answer to this.

I would like to generate a mesh from a collection of XYZ data points saved in a text document. From there, I would like to have physics applied to the mesh and have an interactive character (capsule for now) that falls on the mesh and tumbles. The only issue I am running into is that the mesh I have generated does not have a collision. I have tried so many different methods online to generate a collision object, but it either comes up as NULL or says it works, but the capsule passes straight through.

I would like to do all of this in GDScript if possible. What do I have wrong?

Thanks
Frank


func _generateMesh():
	var meshInst = MeshInstance3D.new()
	var st = SurfaceTool.new()
	st.begin(Mesh.PRIMITIVE_TRIANGLES)
	
	#adding vertices, uvs and stuff to SurfaceTool
	

	
	var a_mesh:ArrayMesh
	var surftool = SurfaceTool.new()
	#var surftool = MeshInstance3D.new()
	var mesh_arr = []
	mesh_arr.resize(Mesh.ARRAY_MAX)
	var mesh_normals = PackedVector3Array()
	var mesh_verticies = PackedVector3Array()
	var mesh_indices = PackedInt64Array()
	var uvs = PackedVector2Array()
	var pnt = PackedVector3Array()
	

	
	
	var MakeSurf = true
	var MakePoints = false
	
	
	surftool.begin(Mesh.PRIMITIVE_TRIANGLES)
	
	
	var f = FileAccess.open(text_file, FileAccess.READ)
	var index = 0
	
	while not f.eof_reached():
		var line = f.get_line()
		if index >0:
			
				
			var x = line.get_slice("\t",0)
			var z = line.get_slice("\t",1)
			var y = line.get_slice("\t",2)
			x = x.replace(" ","")
			y = y.replace(" ","")
			z = z.replace(" ","")
			
			var vx = (float(x) - CenterX) * ScaleX
			var vy = (float(y) - CenterY) * ScaleY 
			var vz = (float(z) - CenterZ) * ScaleZ
			

			mesh_verticies.append(Vector3(vz, vy, vx))
			mesh_normals.append(Vector3(0,-1,0))

		index += 1
	f.close
	

	
	if MakePoints:
		for i in mesh_verticies:
			draw_sphere(i)
	if MakeSurf:
		for i in mesh_verticies:
			surftool.add_vertex(i)
		
	
	if MakeSurf:
		

		for xp in xSize-1:
			for zp in zSize-1:
				var n = int(zp+(zSize*xp))
		
				
				#this is gor graphing top side
#
				surftool.add_index(n)
				surftool.add_index(n+1)
				surftool.add_index(n+zSize)

				surftool.add_index(n+zSize)
				surftool.add_index(n+1)
				surftool.add_index(n+zSize+1)
				
				

		surftool.generate_normals()
		
		
		meshInst = surftool.commit()
		
		
		var mat = StandardMaterial3D.new()
		var tex = load("res://aerial_grass_rock_4k.blend/textures/aerial_grass_rock_diff_4k.jpg")
		var roug = load("res://aerial_grass_rock_4k.blend/textures/aerial_grass_rock_rough_4k.jpg")
		mat.albedo_texture = tex
		mat.roughness_texture = roug
		mat.roughness_texture_channel = BaseMaterial3D.TEXTURE_CHANNEL_ALPHA
		mat.roughness = 0.5
		mat.cull_mode = BaseMaterial3D.CULL_DISABLED
		mat.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_ALWAYS
		mat.diffuse_mode = BaseMaterial3D.DIFFUSE_LAMBERT_WRAP
		mat.specular_mode = BaseMaterial3D.SPECULAR_DISABLED
		mat.rim_enabled = true
		
		

		mat.albedo_texture_force_srgb = true
		
		
		
		#mat.shading_mode = BaseMaterial3D.SHADING_MODE_MAX
		mat.uv1_scale = Vector3(0.01,0.01,0.01)
		mat.uv1_triplanar = true
		
		meshInst.surface_set_material(0,mat)
		
		mesh = meshInst
		mesh.shape = mesh.create_trimesh_shape()
  • xyz replied to this.
  • FrankieB Well you must use the correct node path from the node running the script to the CollisionShape3D node. My example was just pseudocode.

    Btw physics bodies should not be parented under mesh instances. A typical setup for a player walking over a terrain, should look something like this:

    top_node (Node3D)
    --- player (CharacterBody3D or RigidBody3D)
    ------ player_collider (CollisionShape3D)
    ------ player_mesh (MeshInstance3D)
    --- terrain (StaticBody3D)
    ------ terrain_collider (CollisionShape3D)
    -> your generated shape is assigned to this.
    ------ terrain_mesh (MeshInstance3D)

    FrankieB Mesh instances do not collide. Only physics objects (having colliders) do. Your capsule is a collider for a physics object. So make another physics object with colliders that approximate the shape of the mesh, and put it where you mesh is. If you want a collider to have the exact shape of your mesh, make a concave mesh collider from your vertex data. However this will only work decently if your collider is in a static body. If you want it to be moved by dynamic forces, you'll have to split your concave mesh into multiple convex mesh colliders and parent them under the same physics body.

      xyz

      make a concave mesh collider from your vertex data

      you'll have to split your concave mesh into multiple convex mesh colliders

      How would I do this? Could you give me an example?

      I am just now learning GDScript and C#, however I have been programming for years in VBA, VB.Net and some C++. Would I need to make a convex mesh collider for every triangle? I would hope not, as that seems very resource demanding.

      • xyz replied to this.

        FrankieB Try to build concave collider first. Simply supply a list of triangle vertices to ConcavePolygonShape3D::set_faces().

        There is a number of helper functions in MeshInstance3D that let you create physics bodies from its mesh data, like MeshInstance3D::create_multiple_convex_collisions(). Look at the class reference.

          xyz

          Thank you for your help, but I am quite frustrated with this. I really think I am just making a stupid mistake somewhere.

          How would I apply this to my current mesh?

          var shape = ConcavePolygonShape3D.new()
          shape.data = mesh_verticies

          • xyz replied to this.

            FrankieB

            var shape = ConcavePolygonShape3D.new()
            shape.set_faces(my_mesh.get_faces())

            I still can not figure this out.

            The terrain is generating, set to Level 1 collision, as is the capsule player (has a MeshInstance3D and CollisionShape3D as children, both assigned as a capsule shape the same size as the character) with the default gravity script included. But the capsule passes straight through the terrain still.

            Here is my updated code. Please advise. I am sure I am just missing one simple thing...

            func _generateMesh():
            	var meshInst = MeshInstance3D.new()
            	var st = SurfaceTool.new()
            	st.begin(Mesh.PRIMITIVE_TRIANGLES)
            
            	var a_mesh:ArrayMesh
            	var surftool = SurfaceTool.new()
            	#var surftool = MeshInstance3D.new()
            	var mesh_arr = []
            	mesh_arr.resize(Mesh.ARRAY_MAX)
            	var mesh_normals = PackedVector3Array()
            	var mesh_verticies = PackedVector3Array()
            	var mesh_indices = PackedInt64Array()
            	
            	var MakeSurf = true
            	var MakePoints = false
            	
            	
            	surftool.begin(Mesh.PRIMITIVE_TRIANGLES)
            	
            	
            	var f = FileAccess.open(text_file, FileAccess.READ)
            	var index = 0
            	
            	while not f.eof_reached():
            		var line = f.get_line()
            		if index >0:
            			
            				
            			var x = line.get_slice("\t",0)
            			var z = line.get_slice("\t",1)
            			var y = line.get_slice("\t",2)
            			x = x.replace(" ","")
            			y = y.replace(" ","")
            			z = z.replace(" ","")
            			
            			var vx = (float(x) - CenterX) * ScaleX
            			var vy = (float(y) - CenterY) * ScaleY 
            			var vz = (float(z) - CenterZ) * ScaleZ
            			
            			mesh_verticies.append(Vector3(vz, vy, vx))
            			mesh_normals.append(Vector3(0,-1,0))
            		index += 1
            	f.close
            	
            
            	
            	if MakePoints:
            		for i in mesh_verticies:
            			draw_sphere(i)
            	if MakeSurf:
            		for i in mesh_verticies:
            			surftool.add_vertex(i)
            		
            	
            	if MakeSurf:
            		
            		for xp in xSize-1:
            			for zp in zSize-1:
            				var n = int(zp+(zSize*xp))
            				
            				#this is gor graphing top side
            
            				surftool.add_index(n)
            				surftool.add_index(n+1)
            				surftool.add_index(n+zSize)
            
            				surftool.add_index(n+zSize)
            				surftool.add_index(n+1)
            				surftool.add_index(n+zSize+1)
            
            		surftool.generate_normals()
            		meshInst = surftool.commit()
            		
            		var mat = StandardMaterial3D.new()
            		var tex = load("res://aerial_grass_rock_4k.blend/textures/aerial_grass_rock_diff_4k.jpg")
            		var roug = load("res://aerial_grass_rock_4k.blend/textures/aerial_grass_rock_rough_4k.jpg")
            		mat.albedo_texture = tex
            		mat.roughness_texture = roug
            		mat.roughness_texture_channel = BaseMaterial3D.TEXTURE_CHANNEL_ALPHA
            		mat.roughness = 0.5
            		mat.cull_mode = BaseMaterial3D.CULL_DISABLED
            		mat.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_ALWAYS
            		mat.diffuse_mode = BaseMaterial3D.DIFFUSE_LAMBERT_WRAP
            		mat.specular_mode = BaseMaterial3D.SPECULAR_DISABLED
            		mat.rim_enabled = true
            		
            		mat.albedo_texture_force_srgb = true
            		
            		
            		mat.uv1_scale = Vector3(0.01,0.01,0.01)
            		mat.uv1_triplanar = true
            		
            		
            		meshInst.surface_set_material(0,mat)
            		meshInst.set
            		
            		var shape = ConcavePolygonShape3D.new()
            		shape.set_faces(meshInst.get_faces())
            		
            		mesh = meshInst
            • xyz replied to this.

              FrankieB The mesh instance node is just for visual representation of meshes. It has nothing to do with collisions. You need to parent a CollisionShape3D node under some type of physics body node so the engine can use it as that body's collider. Then assign the shape resource you created from mesh data to that CollsionShape3D node's shape property. In this case best to use StaticBody3D as your physics body.

              So, node structure like this:
              StaticBody3D
              --- CollisionShape3D

              And then:

              var shape = ConcavePolygonShape3D.new()
              shape.set_faces(mesh.get_faces())
              $StaticBody3D/CollisionShape3D.shape = shape

              Also, for debugging it's useful to enable collision shape visibility to check how the colliders look at runtime: Debug->Visible Collision Shapes

              And last, your generation code seems wonky. Looks like you don't fully understand the difference between a mesh instance node and mesh resource.

                xyz your generation code seems wonky. Looks like you don't fully understand the difference between a mesh instance node and mesh resource

                You are quite correct! I have zero problem admitting I am a novice.

                I am jumping into this with basically no knowledge of the language or software. My resources have been YouTube videos and fumbling around the wiki. My only experience with game engines has been creating my own inside of Excel using shape objects and geometry. So... yes, I am still trying to understand how this all works.

                I have created a StaticBody3D inside of my MeshInstance3D, with a child of CollisionShape3D. So, it looks like this in the structure:

                Node3D
                -WorldEnviroment
                -Camera
                -DirectionalLighting
                -MeshInstance3D
                --StaticBody3D
                ---CollisionShape3D
                -CharacterBody3D
                --MeshInstance3D
                --CollisionShape3D

                After that I have added in the lines as follows at the end, after I assigned the mesh to meshInst (with the vertices, textures):

                		mesh = meshInst
                		
                		var shape = ConcavePolygonShape3D.new()
                		shape.set_faces(mesh.get_faces())
                		$StaticBody3D/CollisionShape3D.shape = shape
                #

                However I am getting the error at $StaticBody3D/CollisionShape3D.shape = shape which says "Invalid set index 'shape' (on base: 'null instance') with value of type 'ConcavePolygonShape3D'.

                • xyz replied to this.

                  FrankieB Well you must use the correct node path from the node running the script to the CollisionShape3D node. My example was just pseudocode.

                  Btw physics bodies should not be parented under mesh instances. A typical setup for a player walking over a terrain, should look something like this:

                  top_node (Node3D)
                  --- player (CharacterBody3D or RigidBody3D)
                  ------ player_collider (CollisionShape3D)
                  ------ player_mesh (MeshInstance3D)
                  --- terrain (StaticBody3D)
                  ------ terrain_collider (CollisionShape3D)
                  -> your generated shape is assigned to this.
                  ------ terrain_mesh (MeshInstance3D)

                  Thank you. I was able to figure it out. You were correct, not only did I have the wrong hierarchy, I also had the script attached to the wrong portion of the objects.

                  For anyone else wondering, this video was helpful in finding my placements.