루아로 HLSL의 스위즐링, 마스킹 흉내내기

Twitter icon류광, 2007-07-28 17:07
루아에서 HLSL의 스위즐링과 마스킹을 흉내내는 코드 예제입니다. 루아의 메타테이블, 특히 __index와 __newindex 메타메서드를 활용하는 방법을 배우는 데 도움이 될 것입니다.

HLSL의 스위즐링(swizzling)은 HLSL 코드를 간결하게 만드는 데 큰 역할을 하는 독특한 표기법으로, 벡터 배정문에서 성분들의 순서를 임의로 지정할 수 있게 하는 것입니다. 예를 들어 한 벡터의 x 성분과 y 성분이 맞바뀐 벡터가 필요하다면,

a.x = b.y;
a.y = b.x;
a.z = b.z;
a.w = b.w;

라고 하는 대신

a = b.yxzw;

라고 하면 됩니다.

마스킹(masking)은 특정 성분들만 값이 갱신되게 하는 표기법입니다. 예를 들어 어떤 벡터의 y 성분과 w 성분만 갱신하고자 한다면

a.yw = b;

라고 쓰면 됩니다. 이는 a.y = b.y; a.w = b.w;와 동치입니다.

HLSL의 이런 표기법을 루아로 구현해 보았습니다. 코드를 세세히 설명하려고 했는데, 생각해 보니 루아를 모르는 분을 위해서는 블로그에 적합하지 않을 정도로 긴 설명이 필요할 것이고 루아를 아는 분에게는 쓸데없는 지면 낭비일 것이므로 핵심적인 아이디어와 코드만 제시하기로 하겠습니다...(루아라는 언어에 대해서는 최근 나온 프로그래밍 루아를 보시길!) 핵심 아이디어는:

  1. object.member라는 표기는 object["member"]와 동치이다.
  2. object["member"]에 대한 읽기 접근은 __index 메타메서드로 제어할 수 있다(물론 object에 "member"라는 키가 존재하지 않는 경우).
  3. object["member"]에 대한 쓰기 접근은 __newindex 메타메서드로 제어할 수 있다(역시 object에 "member"라는 키가 존재하지 않는 경우).

전체 코드는 다음을 클릭하세요:

[#M_ 클릭! | 숨기기 |
--[[ HLSL의 swizzle, masking 흉내내기
 류광(http://occamsrazr.net/)
]]

local function only_xyzw(k)
    -- x, y, z, w 이외의 글자가 있으면 false를 반환
    return not k:find('[^wxyz]')
end

local function valid_swizzle(k)
    -- x, y, z, w로만 된 네 글자
    return k:len() == 4 and only_xyzw(k)
end

local function get_elements(t, k)
    if valid_swizzle(k) then
        local temp = {}
        local elem
        for i = 1, 4 do
            elem = k:sub(i, i)
            temp[i] = rawget(t, elem)
        end
        return Vector:new{x = temp[1], 
            y = temp[2], z = temp[3], w = temp[4]}
    else
        return rawget(t, k)
    end
end

local function set_elements(t, k, rhs)
    if k:len() > 1 and only_xyzw(k) then
        local elem
        for i = 1, 4 do
            elem = k:sub(i, i)
            if elem ~= '' then
                rawset(t, elem, rhs[elem])
            end
        end
    else
        rawset(t, k, rhs)
    end
end

Vector = {}
function Vector:new (x, y, z, w)
    local obj = {}

    -- 기본값들
    if type(x) == 'table' then
        obj = x
        obj.x = x.x or 0
        obj.y = x.y or 0
        obj.z = x.z or 0
        obj.w = x.w or 0
    else
        obj.x = x or 0
        obj.y = y or 0
        obj.z = z or 0
        obj.w = w or 0
    end

    setmetatable(obj, self)

    self.__index = get_elements
    self.__newindex = set_elements
    return obj
end

function Vector:Print()
    local v = self
    if v then
        print("x =", v.x, "y =", v.y, 
            "z =", v.z, "w =", v.w)
    else
        print("not a table")
    end
end

-----------------------------
--         테스트          --
-----------------------------
-- v = {x = 1, y =2, z = 3, w = 4}
v = Vector:new(1, 2, 3, 4)  

v:Print()  
-- x = 1    y = 2    z = 3    w = 4

v2 = v.wzyy -- 스위즐링
v2:Print() 
-- x = 4    y = 3    z = 2    w = 2

v.yz = v2 -- 마스킹
v:Print()  
-- x = 1    y = 3    z = 2    w = 4

-- v = v2.zy -- 오류: 부분 스위즐링은 지원하지 않음
-- 다음처럼 마스킹과 조합하면 됨
v.xy = v2.zyxx
v:Print()
-- x = 2    y = 3    z = 2    w = 4

v.d = 10 -- 새로운 필드 추가
print(v.d) -- 10

v.x = 11 -- 기존 필드 접근
v:Print()
-- x = 11    y = 3    z = 2    w = 4
_M#]
태그: 프로그래밍 Lua

comments powered by Disqus