3

I'm attempting to generate a height map from a noise texture. As far as I understand, in order to call get_pixel() on an image in this context, the image must first be locked. However, when I attempt to run the program, it exits with the error: Invalid call. Nonexistent function 'lock' in base 'StreamTexture'.

If I attempt to run it without locking the image, I get the error: Invalid call. Nonexistent function 'get_pixel' in base 'StreamTexture'.

I am certain that the instructions that I am following are for the same version of Godot I am running (3.1), so why is the engine telling me that lock() and get_pixel() are nonexistent functions?

My code is here:

extends Spatial

var width
var height
var heightData = {}
var vertices = PoolVector3Array()
var drawMesh = Mesh.new()

func _ready():
    var noiseTexture = load("res://noiseTexture.png")
    width = noiseTexture.get_width()
    height = noiseTexture.get_height()

    noiseTexture.lock()
    for x in range(0, width):
        for y in range(0, height):
            heightData[Vector2(x,y)] = noiseTexture.get_pixel(x,y).r
    noiseTexture.unlock()

    for x in range(0, width-1):
        for y in range(0, height-1):
            createQuad(x,y)

    var surfTool = SurfaceTool.new()
    surfTool.begin(Mesh.PRIMITIVE_TRIANGLES)

    for i in vertices.size():
        surfTool.add_vertex(vertices[i])

    surfTool.commit(drawMesh)
    $MeshInstance.mesh = drawMesh

func createQuad(x,y):
    #First half
    vertices.push_back(Vector3(x, heightData[Vector2(x,y)], -y))
    vertices.push_back(Vector3(x, heightData[Vector2(x,y+1)], -y-1))
    vertices.push_back(Vector3(x+1, heightData[Vector2(x+1,y+1)], -y-1))
    #Second Half
    vertices.push_back(Vector3(x, heightData[Vector2(x,y)], -y))
    vertices.push_back(Vector3(x+1, heightData[Vector2(x+1,y+1)], -y-1))
    vertices.push_back(Vector3(x+1, heightData[Vector2(x+1,y)], -y))

Any help is greatly appreciated.

EDIT - I have (tried) to implement the changes that were suggested in the comments (yet I still don't know what to do with the color variable) and have attached a screenshot of my resulting code, as well as some comments I have made to try and explain to myself why the process SHOULD be working (I think). It also shows my node structure, which is why I opted to display this as an image. However, when I try to run this, the program crashes with the error displayed.

HeightMap Fail

Christopher Bennett
  • 803
  • 1
  • 8
  • 20

2 Answers2

3

Check the docs; StreamTexture does not have the method lock.

I think the class you are looking to use is Image. The Texture class is typically intended for drawing on the screen or applying to a Material

var noiseImage = Image.new()
noiseImage.load("res://noiseTexture.png")
noiseImage.lock() # Lock the image here
var color = noiseImage.get_pixel(10, 10) # Replace with your height map population

PS:

Just to let you know, I had a lot of issues with memory usage here so make sure you test that also (C# has bad garbage collector though). You might need to dispose of the image, surface tool, and array mesh (If you remove the terrain object) to maintain optimum performance.

nathanfranke
  • 775
  • 1
  • 9
  • 19
  • Thanks for the reply. I've only been working with Godot for a few months, so bear with me. I was with you until the part about setting a variable with my heightmap population - Do you mean `heightData[Vector2(x,y)]`? I'm also unsure as to what to do with the color variable after I've assigned it. Hence, why I've been trying to learn by example. Unfortunately, my only resource is a solitary youtube video on the subject which was working until this point. Again, forgive my lack of knowledge. – Christopher Bennett Jun 01 '19 at 11:19
  • @ChristopherBennett It's alright, we all start at some point :) When I said population, I just meant the for loop and this line: `heightData[Vector2(x,y)] = noiseTexture.get_pixel(x,y).r`. Keep in mind that `get_pixel` returns a `Color` and `.r` gets the red value of the color. You only need to replace these lines and the rest of your code should work fine – nathanfranke Jun 01 '19 at 17:07
  • Hey, thanks again for your patience. I have tried to implement your code, but I still don't understand why I should map the r color values to a variable instead of directly to a dictionary (my fault, not yours). Regardless, I have tried all sorts of combinations of setting the values to a variable and inputting that to the dictionary, but all my attempts either don't work, or cause the same program crash that I describe in my edit above. Maybe if you take a look at how I butchered it, you can explain what's going on. – Christopher Bennett Jun 02 '19 at 07:23
  • @ChristopherBennett That socket error is unrelated to your code. Check your task manager for other game instances running, or just restart your computer. Godot uses internet sockets to use debug code and if it doesn't work then there is another game instance running (Even if you closed the window) – nathanfranke Jun 02 '19 at 07:39
  • Ok. I restarted, but same problem. However, I also noticed in that i'm getting `WARNING: load: Loaded resource as image file, this will not work on export: 'res://noiseTexture.png'. Instead, import the image file as as an image resource and load it normally as a resource.`. I reimported the image as an image (it was set as a texture) but still doesn't work. Any suggestions (PS, I upvoted your answer because your suggestions at least got Gotdot to try and load the image, which is a significant step farther than I was when I started.) – Christopher Bennett Jun 02 '19 at 07:59
  • EDIT - After reading about similar issues regarding my "image load warning" on github and following the advice given, I tried setting `var noiseTexture = load("res//noiseTexture.png")`, and that gets rid of the image resource warning, but the program still crashes. – Christopher Bennett Jun 02 '19 at 08:38
  • @ChristopherBennett What does your program crash with? Is it the same socket error? – nathanfranke Jun 02 '19 at 23:13
  • Yeah, same one -- 10054 – Christopher Bennett Jun 02 '19 at 23:54
  • Also `ERROR: _get_socket_error: Socket error: 10054 At: drivers/unix/net_socket_posix.cpp:190`. -- This was just a sample project I was doing to teach myself how height generation from an image is handled in Godot. What I'm learning is that it's a royal pain in the ass. At this point, I'm beginning to wonder whether the return is worth all the effort we're putting towards it. I might just put it on hold for now and revisit once I have more experience with GDScript. Thanks a bunch for your help, though, I really do appreciate it. If I happen to stumble upon a solution, I will post it. – Christopher Bennett Jun 03 '19 at 00:23
  • I would recommend checking this [issue page](https://github.com/godotengine/godot/issues/27444) and doing some of the diagnostics there. The problem seems to be related to the imported image, so try deleting `/.import` P.S: I just saw the comment you posted; I recommend to keep trying a bit (I understand it's a pain in the ass). Otherwise, good luck with your future projects. – nathanfranke Jun 03 '19 at 00:25
  • Thanks. I also agree that the import of the image is what's causing the problem, but all my attempts to solve it give the same results. I will keep working on it.. I just don't want to keep wasting your time trying to help me fix something that obviously needs a good deal of investigation and debugging. I have read the issue page and many others like it, but they either yield no solutions, or ones that don't work for me. I'm gonna try a scratch-start, using all the best methods I picked up along the way, and if that doesn't work, then I may just move on. Thanks again. – Christopher Bennett Jun 03 '19 at 00:30
0

I have run into similar issues with generating heightmap terrains.

nathanfranke is correct and his solution will work.

If you for some reason use an ImageTexture you can call get_data() on that to get the underlying Image. Then you call lock() on the Image just like nathan says in his answer.

Take care to check that your coordinates for get_pixel() are correct. You can set a breakpoint by clicking on the very left edge of the line of code. I mention this because I was very frustrated until I realized that my coordinate calculations were all int which resulted in the sampled pixel always being at <0,0>.

Here is part of my code for sampling an image into the HeightMapShape.map_data for Bullet heightmap collisions:

var map_w = shape.map_width
var map_h = shape.map_depth
var img_w = img.get_width()
var img_h = img.get_height()
img.lock()

for y in range(0, map_h):
    py = float(img_h) * (float(y) / float(map_h))

    for x in range(0, map_w):
        px = float(img_w) * (float(x) / float(map_w))
        index = y * img_h + x
        pix = img.get_pixel(px, py)
        shp.map_data[index] = pix.r * heightmap_height + heightmap_offset
Daniklad
  • 945
  • 8
  • 10