このビットマップ ペイント ツール開発の手順では、ペイント ツール内にあるオブジェクトの UV 座標のグラフィック表示を生成するオプションを追加し、それを拡散反射光マップとしてシーン オブジェクトに自動的に割り当てます。
全体の流れ:
作業対象のメッシュ オブジェクトを格納するためのローカル変数を追加します。
メッシュ オブジェクトだけを選択するための新しいフィルタ関数を追加します。
作業対象オブジェクトを選択するための新しい pickbutton を UI に追加します。 この pickbutton でフィルタ関数を使用します。
テクスチャ座標を取得するためのオプションを[編集] (Edit)メニューに追加します。
選択したメッシュから UV 座標を読み込み、テクスチャ空間の頂点位置に基づいてストロークを描画する、新しい関数を追加します。
ペイントの既定の一時ファイル名を定義し、オプションでそれを各ストロークの後に保存します。
ビットマップの一時ディスク コピーは、選択したオブジェクトの拡散反射光チャネルに割り当てられます。
ユーザ インタフェースに、自動保存の有効と無効を切り替えるための新しいチェックボックスを追加します。有効にすると、ビットマップはツールでペイントが行われるのに並行してリアルタイムで更新されます。
スクリプト:
macroScript MicroPaint category: "HowTo" ( global MicroPaint_CanvasRollout try (destroyDialog MicroPaint_CanvasRollout) catch() local isErasing = isDrawing = false local bitmapX = bitmapY = 512 local bitmapx_1 = bitmapx-1 local bitmapy_1 = bitmapy-1 local temp_bitmap_filename = (getDir #preview +"/microPaint_temp.bmp") local theCanvasBitmap = bitmap bitmapX bitmapY color:white filename:temp_bitmap_filename local theBackgroundBitmap = bitmap bitmapX bitmapY color:white local currentPos = lastPos = [0,0] local theChannel = 1 local theObj = undefined rcMenu CanvasMenu ( subMenu "File" ( menuItem new_menu "New" menuItem open_menu "Open..." menuItem save_as "Save As..." separator file_menu_1 menuItem quit_tool "Quit" ) subMenu "Edit" ( menuItem commit_menu "Commit Changes" separator edit_menu_1 menuItem uv_menu "Get UV Coordinates..." ) on commit_menu picked do copy theCanvasBitmap theBackgroundBitmap on uv_menu picked do MicroPaint_CanvasRollout.unwrapTexture() subMenu "Help" ( menuItem about_tool "About MicroPaint..." ) on new_menu picked do ( theBackgroundBitmap = theCanvasBitmap = bitmap bitmapX bitmapY color:MicroPaint_CanvasRollout.paperColor.color filename:temp_bitmap_filename MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap ) on open_menu picked do ( theOpenBitmap= selectBitmap() if theOpenBitmap != undefined do ( copy theOpenBitmap theCanvasBitmap copy theOpenBitmap theBackgroundBitmap close theOpenBitmap MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap ) ) on save_as picked do ( theSaveName = getSaveFileName types:"BMP (*.bmp)|*.bmp|Targa (*.tga)|*.tga|JPEG (*.jpg)|*.jpg" if theSaveName != undefined do ( theCanvasBitmap.filename = theSaveName save theCanvasBitmap theCanvasBitmap.filename = temp_bitmap_filename ) ) on about_tool picked do messagebox "MicroPaint\nMAXScript Tutorial" title:"About..." on quit_tool picked do destroyDialog MicroPaint_CanvasRollout ) fn mesh_filter obj = superclassof obj == GeometryClass and classof obj != TargetObject rollout MicroPaint_CanvasRollout "MicroPaint" ( bitmap theCanvas pos:[0,0] width:bitmapX height:bitmapY bitmap:theCanvasBitmap colorpicker inkColor height:16 modal:false color:black across:6 colorpicker paperColor height:16 modal:false color:white checkbutton autoSave "AutoSave" width:70 checkbutton airBrush "AirBrush" width:70 spinner AirBrushSpeed "Speed" range:[0.1,50,10] fieldwidth:30 spinner BrushSize "Size" range:[1,50,1] type:#integer fieldwidth:40 listbox BrushShape items:#("Circle","Box","Circle Smooth") pos:[bitmapX+5,0] width:90 --NEW PICK BUTTON AND HANDLER: pickbutton pickMesh "Pick Mesh" width:90 height:30 pos:[bitmapX+5,140] filter:mesh_filter autodisplay:true on pickMesh picked obj do ( if obj != undefined do ( theObj = Obj try ( copy theObj.material.diffusemap.bitmap theCanvasBitmap copy theObj.material.diffusemap.bitmap theBackgroundBitmap theCanvas.bitmap = theCanvasBitmap )catch() ) ) --END NEW PICK BUTTON AND HANDLER fn paintBrush pos = ( if isErasing then thePaintColor = (getPixels theBackgroundBitmap pos 1)[1] else thePaintColor = inkColor.color if thePaintColor == undefined then thePaintColor = white case BrushShape.selection of ( 1: ( if distance pos currentPos <= BrushSize.value/2 do setPixels theCanvasBitmap pos #(thePaintColor) ) 2: setPixels theCanvasBitmap pos #(thePaintColor) 3: ( theFactor = (distance pos currentPos) / (BrushSize.value/2.0) if theFactor <= 1.0 do ( theFactor = sin ( 90.0 * theFactor) thePixels = getPixels theCanvasBitmap pos 1 if thePixels[1] != undefined do ( thePixels[1] = (thePixels[1] * theFactor) + (thePaintColor * (1.0 - theFactor)) setPixels theCanvasBitmap pos thePixels ) ) )--end case 3 )--end case )--end fn fn drawStroke lastPos pos drawIt: = ( currentPos = lastPos deltaX = pos.x - lastPos.x deltaY = pos.y - lastPos.y maxSteps = amax #(abs(deltaX),abs(deltaY)) deltaStepX = deltaX / maxSteps deltaStepY = deltaY / maxSteps for i = 0 to maxSteps do ( if airBrush.checked then for b = 1 to (BrushSize.value / AirBrushSpeed.value) do paintBrush (currentPos + (random [-BrushSize.value/2,-BrushSize.value/2] [BrushSize.value/2,BrushSize.value/2] )) else for b = -BrushSize.value/2 to BrushSize.value/2 do for c = -BrushSize.value/2 to BrushSize.value/2 do paintBrush (currentPos + [c,b]) currentPos += [deltaStepX, deltaStepY] ) if drawIt== true or drawIt == unsupplied do theCanvas.bitmap = theCanvasBitmap ) --NEW UNWRAP TEXTURE FUNCTION STARTS HERE fn unwrapTexture = ( if theObj != undefined then ( theMesh = snapshotAsMesh theObj if meshop.getMapSupport theMesh theChannel do ( faceCount = meshop.getNumMapFaces theMesh theChannel for f = 1 to faceCount do ( theFace = meshop.getMapFace theMesh theChannel f vert1= meshop.getMapVert theMesh theChannel theFace.x vert2= meshop.getMapVert theMesh theChannel theFace.y vert3= meshop.getMapVert theMesh theChannel theFace.z drawStroke [vert1.x * bitmapx_1, bitmapy_1 - vert1.y * bitmapy_1] [vert2.x * bitmapx_1, bitmapy_1 - vert2.y * bitmapy_1] drawIt:false drawStroke [vert1.x * bitmapx_1, bitmapy_1 - vert1.y * bitmapy_1] [vert3.x * bitmapx_1, bitmapy_1 - vert3.y * bitmapy_1] drawIt:false drawStroke [vert3.x * bitmapx_1, bitmapy_1 - vert3.y * bitmapy_1] [vert2.x * bitmapx, bitmapy_1 - vert2.y * bitmapy_1] drawIt:false ) ) theCanvas.bitmap = theCanvasBitmap save theCanvasBitmap if theObj.material == undefined do theObj.material = Standard() if theObj.material.diffusemap == undefined do theObj.material.diffusemap = bitmapTexture filename:temp_bitmap_filename showTextureMap theObj.material true autoSave.checked = true ) ) --NEW UNWRAP TEXTURE FUNCTION ENDS HERE on autoSave changed state do if state do save theCanvasBitmap on MicroPaint_CanvasRollout lbuttondown pos do ( lastPos = pos isDrawing = true isErasing = false drawStroke lastPos pos ) on MicroPaint_CanvasRollout rbuttondown pos do ( lastPos = pos isErasing = isDrawing = true drawStroke lastPos pos ) on MicroPaint_CanvasRollout lbuttonup pos do ( isErasing = isDrawing = false if autoSave.checked do save theCanvasBitmap ) on MicroPaint_CanvasRollout rbuttonup pos do ( isErasing = isDrawing = false if autoSave.checked do save theCanvasBitmap ) on MicroPaint_CanvasRollout mousemove pos do ( if isDrawing do drawStroke lastPos pos lastPos = pos ) ) createDialog MicroPaint_CanvasRollout (bitmapx+100) (bitmapy+30) menu:CanvasMenu MicroPaint_CanvasRollout.theCanvas.bitmap = theBackgroundBitmap )
--Code in italic has no changes since the previous version. macroScript MicroPaint category:"HowTo"
(
global MicroPaint_CanvasRollout
try(destroyDialog MicroPaint_CanvasRollout)catch()
local isErasing = isDrawing = false
local bitmapX = bitmapY = 512
local bitmapx_1 = bitmapx-1
local bitmapy_1 = bitmapy-1
ビットマップ内の UV 座標の位置を計算するには、ピクセルをビットマップのサイズ - 1 としてカウントする必要があります (たとえば 1 ~ 512 ではなく、0 ~ 511)。簡単にするために、これらの正しい値を格納する変数を 2 つ定義します。
local temp_bitmap_filename = (getDir #preview +"/microPaint_temp.tga")
local theCanvasBitmap = bitmap bitmapX bitmapY color:white filename:temp_bitmap_filename
現在のペイントをディスク上の一時ファイルに保存できるようにするには、ファイル名を定義してビットマップに割り当てる必要があります。ファイル名はローカル変数に格納され、スクリプトの他の箇所でそのファイルにアクセスする場合にはそれが使用されます。BMP よりは TGA で保存した方が速くできるようですが、保存はどの形式で行ってもかまいません。
local theBackgroundBitmap = bitmap bitmapX bitmapY color:white
local currentPos = lastPos = [0,0] localtheChannel = 1
読み込み対象のマップ チャネルです。この値をコントロールするための、ユーザ インタフェース スピナーを追加することができます。この時点ではチャンネル 1 のみを扱います。
local theObj = undefined
この変数は、UV 座標にアクセスするメッシュ オブジェクトを格納します。
rcMenu CanvasMenu
(
subMenu "File"
(
menuItem new_menu "New"
menuItem open_menu "Open..."
menuItem save_as "Save As..."
separator file_menu_1
menuItem quit_tool "Quit"
)
subMenu "Edit"
(
menuItem commit_menu "Commit Changes"
separator edit_menu_1
menuItem uv_menu "Get UV Coordinates..."
)
シーン オブジェクトからテクスチャ座標を取得するために使用する、新しいメニュー項目を追加します。
on commit_menu picked do copy theCanvasBitmap theBackgroundBitmap on uv_menu picked do MicroPaint_CanvasRollout.unwrapTexture()
ユーザがこのメニュー項目を選択した場合、新しい関数を呼び出します。新しい関数はロールアウト内に定義します(下記参照)。
subMenu "Help"
(
menuItem about_tool "About MicroPaint..."
)
on new_menu picked do
(
theBackgroundBitmap = theCanvasBitmap = bitmap bitmapX bitmapY color:MicroPaint_CanvasRollout.paperColor.color filename:temp_bitmap_filename
MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap
)
on open_menu picked do
(
theOpenBitmap= selectBitmap()
if theOpenBitmap != undefined do
(
copy theOpenBitmap theCanvasBitmap
copy theOpenBitmap theBackgroundBitmap
close theOpenBitmap
MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap
)
)
on save_as picked do
(
theSaveName = getSaveFileName types:"BMP (*.bmp)|*.bmp|Targa (*.tga)|*.tga|JPEG (*.jpg)|*.jpg"
if theSaveName != undefined do
(
theCanvasBitmap.filename = theSaveName
save theCanvasBitmap theCanvasBitmap.filename = temp_bitmap_filename
新しいファイル名で保存した後、ファイル名を元の一時的な場所に戻します。こうしないと、自動保存により新しいファイルに書き出しが行われてしまいます。
)
)
on about_tool picked do messagebox "MicroPaint\nMAXScript Tutorial" title:"About..."
on quit_tool picked do destroyDialog MicroPaint_CanvasRollout
)
fn mesh_filter obj = superclassof obj == GeometryClass and classof obj != TargetObject
この関数により、ジオメトリ オブジェクトだけが選択できるようになります。 ただし、ターゲット オブジェクトは選択できません。この関数は新しいユーザ インタフェース コントロールである pickbutton でフィルタ関数として使用されます。
rollout MicroPaint_CanvasRollout "MicroPaint"
(
bitmap theCanvas pos:[0,0] width:bitmapX height:bitmapY bitmap:theCanvasBitmap
colorpicker inkColor height:16 modal:false color:black across:6
新しい自動保存オプションが収まるように、across パラメータを 6 に増やします。
colorpicker paperColor height:16 modal:false color:white
checkbutton autoSave "AutoSave" width:70
この新しいチェックボタンでは、ペイントが各ストロークの後で常にディスクに保存されるかどうかをコントロールします。
checkbutton airBrush "AirBrush" width:70
spinner AirBrushSpeed "Speed" range:[0.1,50,10] fieldwidth:30 spinner BrushSize "Size" range:[1,50,1] type:#integer fieldwidth:40
既定のブラシ サイズは 1 になりました。10 の厚さでアンラップしたテクスチャの描画を避けるためです。10 の厚さではテクスチャ アンラップの描画にとても時間がかかる可能性があります。
listbox BrushShape items:#("Circle", "Box", "Circle Smooth") pos:[bitmapX+5,0] width:90
pickbutton pickMesh "Pick Mesh" width:90 height:30 pos:[bitmapX+5,140] filter:mesh_filter autodisplay:true
この pickbutton は、テクスチャ座標をアンラップするシーン オブジェクトを選択するときに使用するものです。3ds Max 7 で追加された新しいオプションのキーワード、autoDisplay を使用します。true に設定されると、選択されたオブジェクトの名前が自動的にボタンに表示されます。3ds Max 6 以前のリリースでは、このオプションは無視されますが、それでもスクリプトは正しく機能します。
on pickMesh picked obj do
(
これは、pickbutton のイベント ハンドラです。ユーザがオブジェクトを選択した場合、obj 変数のハンドラに渡されます。
if obj != undefined do (
obj 変数に有効な値が入っている場合(ユーザが選択をキャンセルしなかった場合)、
theObj = Obj
まず選択オブジェクトを新しい変数に割り当てます。この変数はペイント セッションの間、選択を保存します。
try
(
copy theObj.material.diffusemap.bitmap theCanvasBitmap
copy theObj.material.diffusemap.bitmap theBackgroundBitmap
theCanvas.bitmap = theCanvasBitmap
次に、オブジェクトの diffusemap ビットマップを読み込み、それをペイント ツールのキャンバスおよびバックグラウンドのビットマップにコピーして UI に割り当てます。
ビットマップがない場合、エラーはトラップされ何も起こりません。
)catch()
)
)
fn paintBrush pos =
(
if isErasing then
thePaintColor = (getPixels theBackgroundBitmap pos 1)[1]
else
thePaintColor = inkColor.color
if thePaintColor == undefined then thePaintColor = white
case BrushShape.selection of
(
1: (
if distance pos currentPos <= BrushSize.value/2 do
setPixels theCanvasBitmap pos #(thePaintColor)
)
2: setPixels theCanvasBitmap pos #(thePaintColor)
3: (
theFactor = (distance pos currentPos) / (BrushSize.value/2.0)
if theFactor <= 1.0 do
(
theFactor = sin ( 90.0 * theFactor)
thePixels = getPixels theCanvasBitmap pos 1
if thePixels[1] != undefined do
(
thePixels[1] = (thePixels[1] * theFactor) + (thePaintColor * (1.0 - theFactor))
setPixels theCanvasBitmap pos thePixels
)
)
)--end case 3
)--end case
)--end fn
fn drawStroke lastPos pos drawIt: =
(
drawStroke 関数に新しいオプションのキーワードを追加します。このキーワードが false に設定された場合、ストロークは描画されますが、ロールアウト内のビットマップは更新されません。このキーワードが true の場合、またはこのキーワードを省略した場合は、ビットマップはストロークの後に更新されます。これは、テクスチャ面ごとにビューを更新せずに、アンラップされたテクスチャをより速く描画できるようにするためです。
currentPos = lastPos
deltaX = pos.x - lastPos.x
deltaY = pos.y - lastPos.y
maxSteps = amax #(abs(deltaX),abs(deltaY))
deltaStepX = deltaX / maxSteps
deltaStepY = deltaY / maxSteps
for i = 0 to maxSteps do
(
if airBrush.checked then
for b = 1 to (BrushSize.value / AirBrushSpeed.value) do
paintBrush (currentPos + (random [-BrushSize.value/2,-BrushSize.value/2] [BrushSize.value/2,BrushSize.value/2] ))
else
for b = -BrushSize.value/2 to BrushSize.value/2 do
for c = -BrushSize.value/2 to BrushSize.value/2 do
paintBrush (currentPos + [c,b])
currentPos += [deltaStepX, deltaStepY]
) if drawIt== true or drawIt == unsupplied do theCanvas.bitmap = theCanvasBitmap
新しいスイッチが true に設定された場合、または何も設定されない場合、前と同じように表示を更新します。それ以外の場合、更新はスキップされます。
)
fn unwrapTexture =
(
オブジェクトの選択、テクスチャ座標のアンラップ、およびマテリアル/テクスチャ割り当てを実行する新しい関数です。
if theObj != undefined then
(
ユーザが有効なオブジェクトをクリックした場合、
theMesh =snapshotAsMeshtheObj
スタックの最上位からメッシュを抽出します。
if meshop.getMapSupport theMesh theChannel do
(
選択オブジェクトが指定されたチャネルをサポートする場合、
faceCount = meshop.getNumMapFaces theMesh theChannel
そのチャネル内のテクスチャ面の数を読み込み、
for f = 1 to faceCount do
(
メッシュ内の面のそれぞれについて、次のコードを繰り返します。
theFace = meshop.getMapFace theMesh theChannel f
これは、マップ面の定義です。Point3 値を返します。x、y、z はその面が使用するテクスチャ頂点を指すインデックスです。
vert1= meshop.getMapVert theMesh theChannel theFace.x
vert2= meshop.getMapVert theMesh theChannel theFace.y
vert3= meshop.getMapVert theMesh theChannel theFace.z
このデータを使用して、面の 3 つの頂点を読み込みます。それぞれが、実際の U、V、W の値を含む Point3 値です。ただし、描画するのは UV だけで、W は破棄されます。
drawStroke [vert1.x * bitmapx_1, bitmapy_1- vert1.y * bitmapy_1] [vert2.x * bitmapx_1, bitmapy_1- vert2.y * bitmapy_1] drawIt:false
最初と 2 つ目のテクスチャ頂点の座標をビットマップのサイズで乗じたものを使用して drawStroke 関数を呼び出します。drawIt フラグは、イメージ生成を加速するために false に設定されます。フラグを true に設定すると、関数がイメージに対してインタラクティブに描画されますが、かなり時間がかかる場合があります。
座標の計算は、UV 空間の左下隅が 0,0 で、右上隅が 1,1 という座標であるという事実に基づいて行われます。
もう一方の手のビットマップの左上隅は 0,0 で、右下隅は bitmapx-1,bitmapy-1 です (この例では 511,511)。
UV 座標をピクセル座標に変換するには、ビットマップの幅および高さを U および V の値でそれぞれ乗算し、高さから引くことで垂直方向の高さを逆にします。
drawStroke [vert1.x * bitmapx_1, bitmapy_1- vert1.y * bitmapy_1] [vert3.x * bitmapx_1, bitmapy_1- vert3.y * bitmapy_1] drawIt:false
drawStroke [vert3.x * bitmapx_1, bitmapy_1- vert3.y * bitmapy_1] [vert2.x * bitmapx_1, bitmapy_1- vert2.y * bitmapy_1] drawIt:false
残りの 2 つの頂点についても drawStroke 関数を呼び出します。こうして面のすべてのエッジを描画します。
)
)
theCanvas.bitmap = theCanvasBitmap
ここでロールアウトのビットマップを更新し、結果を表示します。
save theCanvasBitmap
また、ビットマップを一時的な場所に保存します。
if theObj.material == undefined do theObj.material = Standard()
選択したオブジェクトにマテリアルがない場合、新しいマテリアルを割り当てます。
try
(if theObj.material.diffusemap == undefined do
theObj.material.diffusemap = bitmapTexture filename:temp_bitmap_filename
選択したオブジェクトの diffusemap がまだ定義されていない場合は、ペイントの一時コピーを指す新しいテクスチャ マップを割り当てます。オブジェクトに標準的なマテリアルが割り当てられていない場合、diffusemap チャネルにアクセスを試みるとエラーが発生します。これは try()catch() コンテキストで防止します。
showTextureMap theObj.material true
マテリアルのオブジェクト レベルで[ビューポート内でマップを表示](Show Map In Viewport)を押します。テクスチャの割り当てに成功した場合、ビットマップがビューポートに表示されます。
autoSave.checked = true
すべて問題なく進んだ場合、新しい自動保存オプションをオンにして、選択したオブジェクトが描画に並行してビットマップで強制的に更新されるようにします。
)catch()
)
)
on autoSave changed state do if state do save theCanvasBitmap
ユーザが新しいチェックボタンを選択した場合、ビットマップはただちに一時的な保存場所に保存されます。
on MicroPaint_CanvasRollout lbuttondown pos do
(
lastPos = pos
isDrawing = true
isErasing = false
drawStroke lastPos pos
)
on MicroPaint_CanvasRollout rbuttondown pos do
(
lastPos = pos
isErasing = isDrawing = true
drawStroke lastPos pos
)
on MicroPaint_CanvasRollout lbuttonup pos do
(
isErasing = isDrawing = false if autoSave.checked do save theCanvasBitmap
新しいチェックボタンが押されている場合、ビットマップは各ストロークの後に一時的な保存場所に保存されます。
)
on MicroPaint_CanvasRollout rbuttonup pos do
(
isErasing = isDrawing = false
if autoSave.checked do save theCanvasBitmap
新しいチェックボタンが押されている場合、ビットマップは各ストロークの消去後に一時的な保存場所に保存されます。
)
on MicroPaint_CanvasRollout mousemove pos do
(
if isDrawing do drawStroke lastPos pos
lastPos = pos
)
)
createDialog MicroPaint_CanvasRollout (bitmapx+100) (bitmapy+30) menu:CanvasMenu
MicroPaint_CanvasRollout.theCanvas.bitmap = theBackgroundBitmap
)
結果:
この例では、ティーポットに[UVW アンラップ](Unwrap UVW)モディファイヤが適用され、新しいテクスチャ座標を作成するためにフラッテン関数が使用されています。
その後、MicroPaint でオブジェクトが選択され、サイズ 5 の円形のスムーズ ブラシと、オンになって[Airbrush Speed]が 1.0 に設定されたエアブラシを使用してテクスチャ座標が描画されています。
ティーポットは、ビューポート内に次のように表示されます。
ここで、利用可能なブラシをどれでも使用して UV 座標の上にペイントすることができます。[AutoSave]ボタンが選択されていれば、ストロークはすべてビューポート内で更新されます。
前のチュートリアル:
チュートリアル - ビットマップ ペイント ツールの開発 - 変更の消去
次のチュートリアル: