TexturedPolygon (日本語)
この関数はテクスチャによりテクスチャを施されたポリゴン (多角形) の Image を作成します。
Contents
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
)。平滑化された画像の見た目は良くなりますが、計算速度は遅くなります。標準で smooth
は true
に設定されています。
返値
関数は描画を行うのに非常に簡単なオブジェクトを返します:
{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 関数内で使用しようとはしないでください。まだ必要な場合は、 smooth
を false
に設定してから何が発生するのかを確認してください。
これ自体に関する詳細情報はソースコード本文を参照してください。それは同様に使用できる補助関数の一部に関しても有しています (まさに用例は正しく、勇敢なる巻物です)。
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
このテクスチャを使用しています:
動作状態: