from ursina import *


helper = None


def terraincast(world_position, terrain_entity, height_values=None, return_normals=False):    # uses x and z to return y on terrain.
    global helper
    if not helper:
        helper = Entity()

    helper.parent = terrain_entity.model
    helper.world_position = world_position

    pos = helper.get_position(relative_to=terrain_entity.model) + Vec3(.5,0,.5)

    if height_values is None:
        height_values = terrain_entity.model.height_values

    w, d = len(height_values), len(height_values[0])

    if pos.x >= 0 and pos.x < 1 and pos.z >= 0 and pos.z < 1:
        pos *= Vec3(w-1, 0, d-1)
        helper.y = height_values[int(pos.x)][int(pos.z)]
        x, _, z = pos

        point = height_values[int(floor(x))][int(floor(z))]
        normal = Vec3(0,0,0)

        if ceil(x) - x > 0 and ceil(z) - z > 0:
            point_e =  height_values[int(min(w-1, ceil(x)))][int(floor(z))]
            point_n =  height_values[int(floor(x))][int(min(d-1, ceil(z)))]
            point_ne = height_values[int(min(w-1, ceil(x)))][int(min(d-1, ceil(z)))]

            u0v0 = point * (ceil(x) - x) * (ceil(z) - z) # interpolated (x0, z0)
            u1v0 = point_e * (x - floor(x)) * (ceil(z) - z) # interpolated (x1, z0)
            u0v1 = point_n * (ceil(x) - x) * (z - floor(z)) # interpolated (x0, z1)
            u1v1 = point_ne * (x - floor(x)) * (z - floor(z)) # interpolated (x1, z1)

            point = u0v0 + u1v0 + u0v1 + u1v1  #estimate
            # normal = Vec3(2*(point_e-L), 2*(B-T), -4).Normalize();
            # print(point_e)
            if return_normals:
                normal = Vec3(0,1,0)
                p1 = Vec3(min(w-1, ceil(x)), point_e, floor(z))
                p2 = Vec3(floor(x), point, floor(z))
                p3 = Vec3(floor(x), point_n, min(d-1, ceil(z)))
                normal = (p2 - p1).cross(p3-p1).normalized()
                # print(normal)


        helper.y = point * terrain_entity.scale_y / 255
        if not return_normals:
            return helper.y

        return helper.y, normal

    if not return_normals:
        return None

    return None, None


if __name__ == '__main__':
    app = Ursina()

    terrain_entity = Entity(model=Terrain('heightmap_1', skip=8), scale=(40, 5, 20), texture='heightmap_1')
    player = Entity(model='sphere', color=color.azure, scale=.2, origin_y=-.5)


    hv = terrain_entity.model.height_values

    def update():
        direction = Vec3(held_keys['d'] - held_keys['a'], 0, held_keys['w'] - held_keys['s']).normalized()
        player.position += direction * time.dt * 4
        y = terraincast(player.world_position, terrain_entity, hv)
        if y is not None:
            player.y = y
        # test.world_position = player.world_position
        # test.look_at(test.world_position + normal)

    EditorCamera()
    # from ursina.shaders import ssao_shader
    # camera.shader = ssao_shader
    Sky()

    app.run()
