How to convert TextureRect to Image via code?

Godot Version

4.2.1

Question

Hello everybody! I was following a tutorial on how to make a MeshInstance3D have terrain using an heightmap. The tutorial: “https://www.youtube.com/watch?v=fEG_cnRQ1HI&list=LL&index=4&ab_channel=mohsenzare

I fiddled with some parts of it, and I changed the shader’s heightmap to be a NoiseTexture2D. It changes how the MeshInstance looks, but not the collision.

I suck at coding these images stuff.

Here is my terrain.gd code, not shader:

extends MeshInstance3D

##terrain generation

@onready var col_shape = $StaticBody3D/CollisionShape3D
@export var chunk_size = 100.0
@export var height_ratio = 1.0
@export var colShape_size_ratio = 0.1

var ing = Image.new()
var shape = HeightMapShape3D.new()

@onready var noise = $Noise

func _ready():
	col_shape.shape = shape
	mesh.size = Vector2(chunk_size, chunk_size)
	update_terrain(height_ratio, colShape_size_ratio)

func update_terrain(_height_ratio, _colShape_size_ratio):
	
	material_override.set("shader_param/height_ratio", _height_ratio)
	ing = noise.get_texture() ##fixed
	ing.convert(Image.FORMAT_RF) ##broken
	ing.resize(ing.get_width()*_colShape_size_ratio, ing.get_height()*_colShape_size_ratio) ##also probally broken
	var data = ing.get_data().to_float32_array()
	
	for i in range(0, data.size()):
		data[i] *= _height_ratio
		
	shape.map_width = ing.get_width()
	shape.map_depth = ing.get_height()
	shape.map_data = data
	var scale_ratio = chunk_size/float(ing.get_width())
	
	col_shape.scale = Vector3(scale_ratio, 1, scale_ratio)


And here is my Terrain node.

image

If you would like any more detail feel free to ask.

Try:
ing = noise.get_image()

Line 24:

Invalid call. Nonexistent function ‘get_image’ in base ‘TextureRect’.

Sorry. Assumed wrongly what ‘noise’ was. Lemme look again.

Try:

ing = noise.texture.get_image()

You also don’t have to do the var ing = Image.new() you can just do var ing:Image

hth?

1 Like

It works!

But… in line 25 it says

Cannot call method ‘convert’ on a null value.

Is there an actual image in the TextureRect node?
You can also debug with print to see what stuff is:
print("ing is:", ing) for example.

I will try this later, thanks.

1 Like

Sorry for the late reply,

ing is:<Object#null>

Hence me asking if your TectureRect (#Noise)has an actual texture (file)?

It has a FastNoiseLite, it doesn’t use an image file ( I think )

Okay, had a moment to try it. Here’s code that runs for me:

extends Node3D

var ing : Image

func _ready():
	## Ok - TextureRect.texture is a "NoiseTexture2D" class.
	## Be sure to read the docs on that!
	## Also has useful methods you may like.
	##
	## DOCS:
	##	Uses the FastNoiseLite library or other noise generators to fill the texture data of
	## your desired size.NoiseTexture2D can also generate normal map textures.
	## The class uses Threads to generate the texture data internally, so
	## Texture2D.get_image() may return null !!!!
	##  if the generation process has not completed yet.
	## In that case, you need to wait for the texture to be generated before
	## accessing the image and the generated byte data:

	# I renamed the TextureRect to TR to be less confusing on my brain.
	await $TR.texture.changed # <-- NB await here !!!!!

	ing = $TR.texture.get_image()
	ing.resize(ing.get_width(), ing.get_height())
	ing.convert(Image.FORMAT_RF)
	var data = ing.get_data().to_float32_array() # your way
	for i in range(0, data.size()):
		print(data[i])

hth

Thanks, It works now!*

Sorry, but the collision shape is not changing, not sure.

extends MeshInstance3D

##terrain generation

@onready var col_shape = $StaticBody3D/CollisionShape3D
@export var chunk_size = 100.0
@export var height_ratio = 1.0
@export var colShape_size_ratio = 0.1

var ing : Image
var shape = HeightMapShape3D.new()

@onready var noise = $Noise


func _ready():
	col_shape.shape = shape
	mesh.size = Vector2(chunk_size, chunk_size)
	update_terrain(height_ratio, colShape_size_ratio)

func update_terrain(_height_ratio, _colShape_size_ratio):
	
	material_override.set("shader_param/height_ratio", _height_ratio)
	await $Noise.texture.changed # <-- NB await here !!!!!

	ing = $Noise.texture.get_image()
	ing.convert(Image.FORMAT_RF)
	ing.resize(ing.get_width()*_colShape_size_ratio, ing.get_height()*_colShape_size_ratio)
	var data = ing.get_data().to_float32_array() # your way
	
	for i in range(0, data.size()):
		data[i] *= _height_ratio
		
	shape.map_width = ing.get_width()
	shape.map_depth = ing.get_height()
	shape.map_data = data
	var scale_ratio = chunk_size/float(ing.get_width())
	
	
	col_shape.scale = Vector3(scale_ratio, 1, scale_ratio)


Should I start a new topic?

Maybe. I didn’t look at that side of things. Was just solving the null texture problem. Check what numbers you are getting vs. what you expect. Assumptions are always where bugs live.

1 Like