TheWorldSpins
TheWorldSpins

Reputation: 103

Godot repeating breaks script

I'm making a mode 7/perspective projection project in Godot. When I run it, it produces the expected effect, displaying a 2d image as if it were a 3d plane. Code:

func _ready():
map.load("res://map2.png")
perspective.load("res://map2.png")
for px in 1:
    self.texture = display
    for y in map.get_height():
        _y = (y + py - 1)/z
        for x in map.get_width():
            _x = (x + px)/z
            map.lock()
            pix = map.get_pixel(_x, _y)
            map.unlock()
            perspective.lock()
            perspective.set_pixel(x, y, pix)
            perspective.unlock()
        display.create_from_image(perspective)
        z += 1

Image:

enter image description here

However, I have a problem. I have the code in the ready function, in a for loop. I want it to be called every frame, but when I increase the number of repeats from one to two, it turns the entire image red. I don't know what's causing this. one guess was that I wasn't locking and unlocking the images properly, but that is most likely not the case. Another guess was that the x and y variables were not resetting each time, but that was also working fine. I don't think the loop itself is the problem, but I have no idea what's wrong.

Upvotes: 1

Views: 307

Answers (1)

Theraot
Theraot

Reputation: 40170

I struggled to make your code run. I half way gave up, and implemented the logic from my prior answer using lock bits instead. This is the code:

extends Sprite

export(Transform) var matrix:Transform
var sampler:Image
var buffer:Image
var size:Vector2
var center:Vector2

func _ready():
    sampler = texture.get_data()
    var err = sampler.decompress()
    if err != OK:
        push_error("Failed to decompress texture")
        return

    size = Vector2(texture.get_width(), texture.get_height())
    center = size * 0.5

    buffer = Image.new()
    buffer.create(int(size.x), int(size.y), false, Image.FORMAT_RGBA8)

func _process(_delta):
    #matrix = matrix.rotated(Vector3.RIGHT, 0.01)
    
    sampler.lock()
    buffer.lock()
    for y in size.x:
        for x in size.y:
            var uv:Vector3 = matrix * Vector3(x - center.x, y - center.y, 1.0)
            
            if uv.z <= 0.0:
                buffer.set_pixel(x, y, Color.transparent)
                continue
            
            var _x = (uv.x / uv.z) + center.x
            var _y = (uv.y / uv.z) + center.y
            if _x < 0.0 or _x >= size.x or _y < 0.0 or _y >= size.y:
                buffer.set_pixel(x, y, Color.transparent)
                continue

            #buffer.set_pixel(x, y, Color(_x / size.x, y / size.y, 0.0))
            buffer.set_pixel(x, y, sampler.get_pixel(_x, _y))
    buffer.unlock()
    sampler.unlock()

    var display = ImageTexture.new()
    display.create_from_image(buffer, 0)
    self.texture = display

As you can see, I'm exporting a Transfrom to be available on the editor. That is a proper 3D Transform. There is a commented line on _process that does a rotation, try it out.

The sampler Image is a copy of the Texture of the Sprite (the copy is made on _ready). And the buffer Image is where what is to be displayed is constructed.

The code is creating an ImageTexture from buffer and replacing the current texture with it, each frame (on _process). I'm setting flags to 0, because FLAG_REPEAT plus FLAG_FILTER blurred the border to the opposite side of the Sprite.

The vector Vector2 size holds the size of the texture. And the Vector2 Center is the coordinates of the center.


As I said at the start, this is the logic from my prior answer. This line:

vec3 uv = matrix * vec3(UV - 0.5, 1.0);

Is equivalent to (except I'm not scaling the coordinates to the range from 0 to 1):

var uv:Vector3 = matrix * Vector3(x - center.x, y - center.y, 1.0)

Then I had this line:

if (uv.z < 0.0) discard;

Which turned out like this:

if uv.z <= 0.0:
    buffer.set_pixel(x, y, Color.transparent)
    continue

I'm setting transparent because I do not recreate the buffer, nor clear it before hand.

Finally this line:

COLOR = texture(TEXTURE, (uv.xy / uv.z) + 0.5);

Turned out like this:

var _x = (uv.x / uv.z) + center.x
var _y = (uv.y / uv.z) + center.y
if _x < 0.0 or _x >= size.x or _y < 0.0 or _y >= size.y:
    buffer.set_pixel(x, y, Color.transparent)
    continue

buffer.set_pixel(x, y, sampler.get_pixel(_x, _y))

As per the result, here is the Godot Icon "rotating in 3D" (not really, but that is the idea):

Result

Please disregard visual artifact due to GIF encoding.

I'm not sure if you want to stay with the logic of my prior answer. However, I believe this one should not be too hard to modify for your needs.


Addendum

I used a Transform because there is no convenient Matrix type available. However, the Transform uses a Matrix internally. See also Transformation matrix.

The Mode 7 formula according to Wikipedia works with a 2 by 2 Matrix, which is simpler that what I have here. However, you are going to need the product of a Matrix and a Vector anyway. You cannot compute the components independently.

This is the formula according to Wikipedia:

r' = M*(r - r_0) + r_0

That is:

var rp = mult(M, r - r_0) + r_0

Where mult would look like this:

func mult(matrix, vector:Vector2) -> Vector2:
    var x = vector.x * matrix.a + vector.y * matrix.b
    var y = vector.x * matrix.c + vector.y * matrix.d
    return Vector2(x, y)

However, as I said, there is no convenient matrix type. If we export a, b, c, and d, we have:

var rp = mult(a, b, c, d, r - r_0) + r_0

And mult looks like this:

func mult(a:float, b:float, c:float, d:float, vector:Vector2) -> Vector2:
    var x = vector.x * a + vector.y * b
    var y = vector.x * c + vector.y * d
    return Vector2(x, y)

We can easily use modify the code to do that. First export a, b, c and d as I said:

export(float) var a:float
export(float) var b:float
export(float) var c:float
export(float) var d:float

And this is _process modified:

func _process(_delta):
    sampler.lock()
    buffer.lock()
    for y in size.x:
        for x in size.y:
            var rp = mult(a, b, c, d, Vector2(x, y) - center) + center
            if rp.x < 0.0 or rp.x >= size.x or rp.y < 0.0 or rp.y >= size.y:
                buffer.set_pixel(x, y, Color.transparent)
                continue

            buffer.set_pixel(x, y, sampler.get_pixel(rp.x, rp.y))
    buffer.unlock()
    sampler.unlock()

    var display = ImageTexture.new()
    display.create_from_image(buffer, 6)
    self.texture = display

Of course, mult is the one I showed above. I'm assuming here that r_0 is what I called center.

I'm not sure how to interpret a, b, c and d, so here is a = 1, b = 2, c = 3 and d = 4:

Godot Icon stretched and skewed

Upvotes: 1

Related Questions