TexturedPolygon (日本語)

この関数はテクスチャによりテクスチャを施されたポリゴン (多角形) の Image を作成します。

function TexturedPolygon(polygon,texture, xx,yy,rr,sx,sy,ox,oy, smooth)

引数

polygon テクスチャを地点座標形式のテーブルで多角形にテクスチャを施します: {x1,y1, x2,y2, ...}

texture ImageData オブジェクトまたは画像のファイル名です。垂直・水平方向の加工を行い、多角形全体を塗り潰すために使用されます。

それ以外全ての引数は選択制です:

xx,yy,r,... は、ずらし、回転、および多角形に適用される前段階でのテクスチャに対する尺度変更を行う使用されます。それに関しては一枚の神であると考えることができ、変形されたテクスチャは反復された画像により塗り潰されており、ハサミで特定の図形を切り取るために多角形の地点座標を使用します。例えば、多角形自体がその場所で停止している間に、 xx の値を増加させると、テクスチャは右側へ移動します。

xx,yy テクスチャの座標 (原点)。

rr 次数による (原点と関連する) テクスチャの回転角度。

sx,xy x および x 軸に対するテクスチャの尺度係数 (原点と関連します)。

ox,oy テクスチャにおける原点の地点。それは回転および尺度の適用方法に影響を与えます。

標準では、変換を一切適用しません。この意味は: xx,yy,rr,sx,sy,ox,oy=0,0,0,1,1,0,0

smooth は画像の平滑化を行うかどうかを指定します (true または false)。平滑化された画像の見た目は良くなりますが、計算速度は遅くなります。標準で smoothtrue に設定されています。

返値

関数は描画を行うのに非常に簡単なオブジェクトを返します:

{img, x,y, w,h, imgData}

ここでは:

img Image オブジェクトであり、 love.graphics.draw 関数により描画されます。

x,y どこに画像を描画するかどうか。このように使用します: love.graphics.draw(obj.img, obj.x,obj.y)

w,h 画像の幅と高さ。

imgData ImageData オブジェクトです。これを使用したい場合は、それに対する専門知識が必要です。

実装

関数は結果の画像としてピクセル(画素)単位で描画を行うために ImageData:mapPixel を使用します。多角形の領域外は完全に透過されたままになります。

全てのピクセルをすぐに計算する必要がある場合は、関数の処理時間はかなりかかります。 それ故に、例えばゲームまたは新しいレベルを読み込む上では、可能な限り使用は稀にしたほうが良いです。これは love.update 関数内で使用しようとはしないでください。まだ必要な場合は、 smoothfalse に設定してから何が発生するのかを確認してください。


これ自体に関する詳細情報はソースコード本文を参照してください。それは同様に使用できる補助関数の一部に関しても有しています (まさに用例は正しく、勇敢なる巻物です)。

function TexturedPolygon(polygon,texture, xx,yy,rr,sx,sy,ox,oy, smooth) -- 画像の配列を返します。全て描画する場合は、テクスチャを施された多角形を表現します。
    if type(polygon)~="table" or #polygon%2~=0 then return nil end  -- 多角形は x1, y1, x2, y2 ... 形式のテーブルにする必要があります。
    if #polygon<6 then return nil end -- この行では nil を返します: ここではなにも描画しません。

    -- 必要であればテクスチャを読み込みます。
    if type(texture)=="string" then
        texture=love.image.newImageData(texture)
    end

    -- テクスチャ位置の引数に対する既定値。
    if xx==nil then xx=0 end
    if yy==nil then yy=0 end
    if rr==nil then rr=0 end
    if sx==nil then sx=1 end
    if sy==nil then sy=1 end
    if ox==nil then ox=0 end
    if oy==nil then oy=0 end

    if sx==0 then sx=1 end -- 悪い状況の回避。
    if sy==0 then sy=1 end

    rr=rr/180*3.14159 -- 次数を弧度へ変換します。

    if smooth==nil then smooth=true end -- 標準で綺麗なものを描画します。

    local bb=PolygonBoundingBox(polygon) -- 多角形の境界ボックス。

    -- simple==0 の意味は単純ではなく、1 の意味はテクスチャの変換を行ない、 2 の意味は x, y の値に対して丸め込み不要であることです。
    local simple=0
    if xx==0 and yy==0 and rr==0 and sx==1 and sy==1 and ox==0 and oy==0 then
        simple=1
        if bb.x==math.floor(bb.x) and bb.y==math.floor(bb.y) then
            simple=2
        end
    end
    -- さらに、 simple==3 の意味はテクスチャの変換は必要だが、利用者は平滑化された画像は不要であることです。
    if simple==0 and not smooth then 
        simple=3
    end

    -- 素晴らしいコードを最適化するために頻繁に使用される一部の数値を計算します。
    local cr,sr=math.cos(rr), math.sin(rr)
    local tw,th=texture:getWidth(), texture:getHeight()

    -- triangles は三角形の配列です (これは案内人です!)
    local triangles=nil
    if #polygon==6 then 
        triangles={polygon}
    else
        triangles=love.math.triangulate(polygon)
    end
    
    local imageData=love.image.newImageData(math.ceil(bb.w),math.ceil(bb.h)) -- ImageData を描画するために "空" にします。
                                                                             -- "空" の意味は全てのピクセル値が 0, 0, 0, 0 を有することを意味します。

    for i,triangle in ipairs(triangles) do -- 全ての三角形に対して...

        -- ピクセルが三角形内に存在するか 3 種類の尺度で調べるために必要です。
        -- 3 種類の線形関数を定義します y=a#+b#*(x-c#) or x=a#+b#*(y-c#) (側面の傾斜角に依存します)
        --m# は方式です: 上部 (1), 下部 (2), 右部 (3) または 左部 (4)
        --e#: この線が原点にある多角形の端であるかどうか: nil (false) あるいは x か y の範囲内 (true)
        local a0={}
        local b0={}
        local c0={}
        local m0={}
        local e0={}

        -- 三角形の地点にある組上に閉路を作成することを望みますが、 3 番目の組は (3,4) ではなく (1,3) です。

        -- 1 番目にある地点の組に関して。
        -- triangle[1]->x1, [2]->y1, [3]->x2, [4]->y2, [5]->x3, [6]->y3 であることに留意してください。
        if triangle[1]==triangle[3] or math.abs( (triangle[4]-triangle[2])/(triangle[3]-triangle[1]) ) > 1 then -- 傾斜角は垂直に近いです。
            m0[1]=2
        else
            m0[1]=1
        end
        if m0[1]==1 then -- 水平尺度
            a0[1],b0[1],c0[1] = triangle[2], (triangle[4]-triangle[2])/(triangle[3]-triangle[1]), triangle[1]
            if triangle[6] > a0[1] + b0[1]*(triangle[5]-c0[1]) then -- この尺度を別の地点にある三角形へ渡す方法を調べます。
                m0[1]=1 -- 上部の尺度
            else
                m0[1]=2 -- 下部の尺度
            end
        else -- 垂直尺度
            a0[1],b0[1],c0[1] = triangle[1], (triangle[3]-triangle[1])/(triangle[4]-triangle[2]), triangle[2]
            if triangle[5] > a0[1] + b0[1]*(triangle[6]-c0[1]) then -- この尺度を別の地点にある三角形へ渡す方法を調べます。
                m0[1]=3 -- 右部の尺度
            else
                m0[1]=4 -- 左部の尺度
            end
        end
        if PolygonEdgeLine(polygon, triangle[1],triangle[2],triangle[3],triangle[4]) then -- この線が多角形の端であるかどうかを調べます。
            if m0[1]==1 or m0[1]==2 or m0[1]==5 or m0[1]==6 then
                e0[1]={math.min(triangle[1],triangle[3]), math.max(triangle[1],triangle[3])} -- x の範囲内ならば true です。
            else
                e0[1]={math.min(triangle[2],triangle[4]), math.max(triangle[2],triangle[4])} -- y の範囲内ならば true です。
            end
        else
            e0[1]=nil -- いいえ、内部分割線です。
        end
        -- など...

        -- 2 番目にある地点の組に関して。
        if triangle[3]==triangle[5] or math.abs( (triangle[6]-triangle[4])/(triangle[5]-triangle[3]) ) > 1 then -- 傾斜角は垂直に近いです。
            m0[2]=2
        else
            m0[2]=1
        end
        if m0[2]==1 then -- 水平尺度
            a0[2],b0[2],c0[2] = triangle[4], (triangle[6]-triangle[4])/(triangle[5]-triangle[3]), triangle[3]
            if triangle[2] > a0[2] + b0[2]*(triangle[1]-c0[2]) then
                m0[2]=1 -- 上部の尺度
            else
                m0[2]=2
            end
        else -- 垂直尺度
            a0[2],b0[2],c0[2] = triangle[3], (triangle[5]-triangle[3])/(triangle[6]-triangle[4]), triangle[4]
            if triangle[1] > a0[2] + b0[2]*(triangle[2]-c0[2]) then
                m0[2]=3
            else
                m0[2]=4
            end
        end
        if PolygonEdgeLine(polygon, triangle[3],triangle[4],triangle[5],triangle[6]) then
            if m0[2]==1 or m0[2]==2 or m0[2]==5 or m0[2]==6 then
                e0[2]={math.min(triangle[3],triangle[5]), math.max(triangle[3],triangle[5])}
            else
                e0[2]={math.min(triangle[4],triangle[6]), math.max(triangle[4],triangle[6])}
            end
        else
            e0[2]=nil
        end

        -- 3 番目にある地点の組に関して。
        if triangle[1]==triangle[5] or math.abs( (triangle[6]-triangle[2])/(triangle[5]-triangle[1]) ) > 1 then -- 傾斜角は垂直に近いです。
            m0[3]=2
        else
            m0[3]=1
        end
        if m0[3]==1 then -- 水平尺度
            a0[3],b0[3],c0[3] = triangle[2], (triangle[6]-triangle[2])/(triangle[5]-triangle[1]), triangle[1]
            if triangle[4] > a0[3] + b0[3]*(triangle[3]-c0[3]) then
                m0[3]=1
            else
                m0[3]=2
            end
        else -- 垂直尺度
            a0[3],b0[3],c0[3] = triangle[1], (triangle[5]-triangle[1])/(triangle[6]-triangle[2]), triangle[2]
            if triangle[3] > a0[3] + b0[3]*(triangle[4]-c0[3]) then
                m0[3]=3
            else
                m0[3]=4
            end
        end
        if PolygonEdgeLine(polygon, triangle[1],triangle[2],triangle[5],triangle[6]) then
            if m0[3]==1 or m0[3]==2 or m0[3]==5 or m0[3]==6 then
                e0[3]={math.min(triangle[1],triangle[5]), math.max(triangle[1],triangle[5])}
            else
                e0[3]={math.min(triangle[2],triangle[6]), math.max(triangle[2],triangle[6])}
            end
        else
            e0[3]=nil
        end

        local function func(x,y,r,g,b,a) -- ImageData の多角形に mapPixel メソッドを適用する関数です。
            if r~=0 or g~=0 or b~=0 or a~=0 then return r,g,b,a end -- ピクセルは既に描画されているため、処理を飛ばします。

            x=x+bb.x -- 結果として画像の座標から内部テクスチャの座標まで進みます。
            y=y+bb.y

            local alpha=1 -- 円滑化された角の透過 (if smooth==true の使用時のみ)

            -- 全ての第 3 尺度を確認します。
            for j=1,3 do

                if m0[j]==1 then
                    local y0=a0[j]+b0[j]*(x-c0[j]) -- なにと比較を行うかどうか。
                    if smooth and e0[j]~=nil and x>=e0[j][1] and x<e0[j][2] then -- 角を平滑化する必要があるかどうか。
                        local z=y0-y -- 理想的な端の位置から離れています。
                        if z>1 then -- 離れすぎている。
                            return 0,0,0,0
                        elseif z>0 then -- 平滑化されたピクセルの端にある領域内にあります。
                            alpha=alpha*(1-z)
                        end
                    elseif y<y0 then -- 三角形の範囲外にあります。
                        return 0,0,0,0
                    end
                    -- など...

                elseif m0[j]==2 then
                    local y0=a0[j]+b0[j]*(x-c0[j])
                    if smooth and e0[j]~=nil and x>=e0[j][1] and x<e0[j][2] then
                        local z=y0-y
                        if z<0 then
                            return 0,0,0,0
                        elseif z<1 then
                            alpha=alpha*z
                        end
                    elseif y>=y0 then
                        return 0,0,0,0
                    end

                elseif m0[j]==3 then
                    local x0=a0[j]+b0[j]*(y-c0[j])
                    if smooth and e0[j]~=nil and y>=e0[j][1] and y<e0[j][2] then
                        local z=x0-x
                        if z>1 then
                            return 0,0,0,0
                        elseif z>0 then
                            alpha=alpha*(1-z)
                        end
                    elseif x<x0 then
                        return 0,0,0,0
                    end

                elseif m0[j]==4 then
                    local x0=a0[j]+b0[j]*(y-c0[j])
                    if smooth and e0[j]~=nil and y>=e0[j][1] and y<e0[j][2] then
                        local z=x0-x
                        if z<0 then
                            return 0,0,0,0
                        elseif z<1 then
                            alpha=alpha*z
                        end
                    elseif x>=x0 then
                        return 0,0,0,0
                    end

                end
            end

            if simple==0 then -- 複雑な事例。
                r,g,b,a=getAveragePixel(texture, ((x+0.5-xx)*cr+(y+0.5-yy)*sr)/sx+ox, ((y+0.5-yy)*cr-(x+0.5-xx)*sr)/sy+oy, tw,th)
            elseif simple==1 then -- 単純な事例。
                r,g,b,a=texture:getPixel( math.floor(x)%tw, math.floor(y)%th)
            elseif simple==2 then -- さらに単純な事例。
                r,g,b,a=texture:getPixel( x%tw, y%th)
            else--if simple==3 then -- 汚いが、早い事例。
                r,g,b,a=texture:getPixel( math.floor(((x-xx)*cr+(y-yy)*sr)/sx+ox)%tw, math.floor(((y-yy)*cr-(x-xx)*sr)/sy+oy)%th)
            end

            if smooth then a=a*alpha end -- 端を綺麗にすることができます。
            --return texture:getPixel( (x)%texture:getWidth(), (y)%texture:getHeight())
            return r,g,b,a
        end

        imageData:mapPixel(func) -- ImageData に三角形の描画を一つ以上適用します。
    end

    local image=love.graphics.newImage(imageData) -- 描画可能なオブジェクトの作成。

    return {img=image, x=bb.x,y=bb.y,w=bb.w,h=bb.h, imgData=imageData}
end

function PolygonBoundingBox(polygon) -- 多角形に対して最大・最小 x, y 座標の判定、および BoundingBox オブジェクト形式 {x,y,w,h} で返す単純な関数です。
    if type(polygon)~="table" or #polygon<2 or #polygon%2~=0 then return nil end

    local minX=polygon[1]
    local maxX=polygon[1]
    local minY=polygon[2]
    local maxY=polygon[2]

    for i=1,#polygon,2 do
        if polygon[i]<minX then minX=polygon[i] end
        if polygon[i]>maxX then maxX=polygon[i] end
        if polygon[i+1]<minY then minY=polygon[i+1] end
        if polygon[i+1]>maxY then maxY=polygon[i+1] end
    end

    return {x=minX,y=minY,w=maxX-minX,h=maxY-minY}
end

function PolygonEdgeLine(polygon, x1,y1,x2,y2) -- 既存の地点にある組がある多角形の端に属するかどうかを調べるために特定を行う関数です。
    local length=#polygon
    if type(polygon)~="table" or length<4 or length%2~=0 then return nil end

    local buf={} -- これらの値が丸め込まれている場合に限り (例えば、二桁まで)、非整数値の比較は安全です。
    for i,v in ipairs(polygon) do
        buf[i]=math.floor(v*100)/100
    end

    local x1=math.floor(x1*100)/100
    local y1=math.floor(y1*100)/100
    local x2=math.floor(x2*100)/100
    local y2=math.floor(y2*100)/100

    for i=1,length-2,2 do -- 最後の線以外をすべて比較します。
        if buf[i]==x1 and buf[i+1]==y1 and buf[i+2]==x2 and buf[i+3]==y2 then
            return true
        elseif buf[i]==x2 and buf[i+1]==y2 and buf[i+2]==x1 and buf[i+3]==y1 then
            return true
        end
    end

    -- 最後の線を別に比較します。
    if buf[1]==x1 and buf[2]==y1 and buf[length-1]==x2 and buf[length]==y2 then
        return true
    elseif buf[1]==x2 and buf[2]==y2 and buf[length-1]==x1 and buf[length]==y1 then
        return true
    end

    -- 全ての検査を以前に合格した場合は、失敗します:
    return false
end

function getAveragePixel(texture, x,y,w,h) --テクスチャ周辺の x, y 座標と近接するピクセルに対して平均化された色を返します。コードの高速化のために w, h は事前計算されます。
    local x1,x2,y1,y2 = math.floor(x), math.ceil(x), math.floor(y), math.ceil(y) -- 索引化されたピクセルの隣接値整数を検出します。
    if x2==x1 then x2=x2+1 end
    if y2==y1 then y2=y2+1 end

    local d1,d2,d3,d4 = (x2-x)*(y2-y), (x-x1)*(y2-y), (x2-x)*(y-y1), (x-x1)*(y-y1) -- 結果としてピクセルある少数部を検出します。

    x1=x1%w -- テクスチャが範囲内にあることを保証します (周囲の加工)。
    x2=x2%w
    y1=y1%h
    y2=y2%h

    local r1,g1,b1,a1 = texture:getPixel(x1,y1) -- 生の 4 色を取得します。
    local r2,g2,b2,a2 = texture:getPixel(x2,y1)
    local r3,g3,b3,a3 = texture:getPixel(x1,y2)
    local r4,g4,b4,a4 = texture:getPixel(x2,y2)

    --...そして全ての平均を返します。
    return d1*r1+d2*r2+d3*r3+d4*r4, d1*g1+d2*g2+d3*g3+d4*g4, d1*b1+d2*b2+d3*b3+d4*b4, d1*a1+d2*a2+d3*a3+d4*a4
end

動作状態を確認するには用例を実行します:

require "textured_polygon" -- TexturedPolygon 関数のあるファイルを呼び出すか、またはこの下に内容を貼り付けてください。

function love.load()
    texture=love.image.newImageData('texture.png')
    bkg=love.graphics.newImage(texture)
    polygon={100,100, 200,100, 300,200, 400,100, 520,100, 530,200, 400,200, 300,300, 200,200, 100,200}

    t_start=love.timer.getTime()
    
    image=TexturedPolygon(polygon,'texture.png')

    t_end=love.timer.getTime()

    love.graphics.setBackgroundColor(128,50,100)
end
 
function love.draw()
    love.graphics.draw( bkg, 0,20, 0, 10,10)
    love.graphics.draw( image.img, image.x, image.y)

    love.graphics.print("Spent "..(t_end-t_start).." seconds to create textured polygon")
    love.graphics.print("<- see theese pixels smoothed!", 530,140)
    love.graphics.print("| works with concave polygons as well!", 302,80)
    love.graphics.print("V                                     ", 300,90)
    love.graphics.print("^                           ", 420,200)
    love.graphics.print("| semi-transparent textures!", 422,205)
end

このテクスチャを使用しています:

texture.png


動作状態:

screenshot1.png