mohsenph69 / Godot-MTerrain-plugin

A GDExtension plugin which give godot the ability to produce highly optimized Terrain for Open world games
MIT License
478 stars 25 forks source link

How to detect what texture is in the floor? #67

Closed lucasthomaz97 closed 4 days ago

lucasthomaz97 commented 5 days ago

i'm developing a rally game and i'm liking the addon so far! I'm just having dificulty with some stuff: In my game i need to detect the texture each wheel is colliding, and i am not finding anything related to this in the lacking documentation. also about textures, what can i do to increase the resolution of the textures i generate in the layers or another solution, since i am going to a very stylized look to my game, i need the transitions between the textures and colors to be really flatter and not blurry or pixelated, what can i do about it? I also saw a GDC talk about zelda tears of the kingdom that they said that they registered metadata in the terrain depending of the position, it may be a good idea. anyway, thank you for the great addon!

lucasthomaz97 commented 4 days ago

an update: with the help of the chat gpt i've been able to discover how to get the texture by the world position i noticed that this addon uses arrays (images) to texture everything, each layer you paint is a layer in the terrain, you must understand the structure to use optimally. So i am using the function get_pixel in the Mterrain node in the scene (be careful to not use the Mterrain class instead) passing the global x and z position of my node, and the id of the image i want, that you can obtain by calling the function get_image_id and pass the name of the layer you want as a parameter But i also need to know every layer i have in my terrain, so i use the get_layers_info when i start up my game to get this information. After that i compare the color in the scene with the colors of the layers, and i get the layer that is nearer to the pixel color and get the name of the layer, which i can use in my game logic. if you are going to just paint flat colors to the terrain without change it to textures in the terrain, you can just use the get_pixel() to get the color and use straightly in your game, but in my case i needed to use this in my game because i am going to use stylized textures for the road instead of clear colors. this method works for me but i am using the color channels methods to switch the textures in the brush, maybe if you are using the paint 256 or paint 16 you may need to change this logic (i don't tested it in this methods yet but i will)

extends Node3D

@onready var info: Color
@onready var layer_info: Array

func _ready() -> void:
    $MTerrain.create = true
    layer_info = $MTerrain.get_layers_info()
    for rigid in get_tree().get_nodes_in_group("rigid"):
        if rigid is RigidBody3D:
            rigid.freeze = true

func _process(delta: float) -> void:
    $Control/Label.text = str(Engine.get_frames_per_second()) # i was just just testing the performance of this addon in my scene because my notebook is a potato, ignore it (60fps btw, nice performance and optimization!)
    info  = $MTerrain.get_pixel($Node3D.global_position.x, $Node3D.global_position.z, $MTerrain.get_image_id("splatmap"))
    #Node3D is the object i wanted to just get the position in this test, in my game i'll pass the global position in the wheel script
    var closest_layer = find_closest_color(info)
    if closest_layer:
        print("Nearest Layer (code your logic in this branch):", closest_layer["name"])

func find_closest_color(target_color: Color) -> Dictionary:
    var min_distance = INF
    var closest_layer = null
    var layer_dict: Dictionary = layer_info[0] #if you have more than one splatmap or terrain painting layer, you may change this index by code using the function $MTerrain.get_image_id("name_of_layer") as i done in the get_pixel function

    for layer in layer_dict.get("info"):
        var layer_color = layer["icon-color"]
        var distance = color_distance(target_color, layer_color)

        if distance < min_distance:
            min_distance = distance
            closest_layer = layer

    return closest_layer

func color_distance(color1: Color, color2: Color) -> float:
    var dr = color1.r - color2.r
    var dg = color1.g - color2.g
    var db = color1.b - color2.b
    return sqrt(dr * dr + dg * dg + db * db)

This is my test scene, i am using only the godot primitive meshes for the grass and the trees, and i got a grass shader from godot shaders. image

hint: if you are going for a cartoon or minimalistic art style like me, or you just don't want those gradient transitions between different textures or colors, use a quantize shader between the textures, like i am doing in the terrain shader of the print:

void fragment(){
    vec4 splatmap = texture(mterrain_splatmap, region_uv);
    vec3 albedo = vec3(0.0);

    // Apply colors steadily based on threshold
    if (splatmap.r > threshold) {
        albedo = grass_color.rgb;
    } else if (splatmap.g > threshold) {
        albedo = stone_color.rgb;
    } else if (splatmap.b > threshold) {
        albedo = ground_color.rgb;
    }
    ALBEDO = albedo;
}

by using this logic you may get a constant transition, just like the transition between the green and the brown road in this scene.