ビットマップ ペイント ツール開発の手順では、3ds Max ビューポート内のシーン オブジェクトに直接ペイントするオプションを追加します。
全体の流れ:
ユーザ インタフェースに、3D ペイントの有効と無効を切り替えるチェックボタンを追加します。
PainterInterface を使用して、ビューポート内でブラシをコントロールする単純な関数をいくつか定義し、ペイントする面の 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.tga") 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 local bary = [0,0,0] local faceIndex = 1 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..." menuItem paint3d_menu "Toggle 3D Painting..." ) on commit_menu picked do copy theCanvasBitmap theBackgroundBitmap on uv_menu picked do MicroPaint_CanvasRollout.unwrapTexture() on paint3d_menu picked do MicroPaint_CanvasRollout.startPainting3D() 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_too lpicked 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 offset:[0,-3] highlightcolor:(color 255 200 200) checkbutton airBrush "AirBrush" width:70 offset:[0,-3] highlightcolor:(color 200 255 200) 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 pickbutton pickMesh "Pick Mesh" width:90 height:30 highlightcolor:(color 200 200 255) 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() ) ) checkbutton paint3D "3D PAINT" width:90 height:50 highlightcolor:(color 200 200 255) pos:[bitmapX+5,180] 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 ) 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_1, 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 ) ) fn StartStroke = ( thePainterInterface.undoStart() ) fn PaintStroke = ( theMesh = theObj.mesh thePainterInterface.getHitFaceData &bary &faceIndex theObj 0 theFace = meshop.getMapFace theMesh theChannel faceIndex vert1= meshop.getMapVert theMesh theChannel theFace.x vert2= meshop.getMapVert theMesh theChannel theFace.y vert3= meshop.getMapVert theMesh theChannel theFace.z thePoint = bary.x*vert1 + bary.y*vert2 + bary.z*vert3 drawStroke [thePoint.x * bitmapx_1, bitmapy_1 - thePoint.y * bitmapy_1] [thePoint.x * bitmapx_1, bitmapy_1 - thePoint.y * bitmapy_1] thePainterInterface.clearStroke() ) fn EndStroke = ( thePainterInterface.undoAccept() if autoSave.checked do ( save theCanvasBitmap try(theObj.material.diffsemap.bitmap = theCanvasBitmap)catch() ) ) fn CancelStroke = (thePainterInterface.undoCancel()) fn SystemEndPaintSession = ( paint3D.checked = false ) fn startPainting3D = ( if thePainterInterface.InPaintMode() or theObj == undefined then ( thePainterInterface.EndPaintSession() paint3D.checked = false ) else ( paint3D.checked = true thePainterInterface.pointGatherEnable = TRUE thePainterInterface.initializeNodes 0 #(theObj) thePainterInterface.offMeshHitType = 2 thePainterInterface.ScriptFunctions startStroke paintStroke endStroke cancelStroke SystemEndPaintSession thePainterInterface.startPaintSession() ) ) on paint3D changed state do startPainting3D() 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 autoSave changed state do if state 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
local temp_bitmap_filename = (getDir #preview +"/microPaint_temp.tga")
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 local bary = [0,0,0]
この変数は、PainterInterface が面内のヒット ポイントの重心座標を返すために使用します。
local faceIndex = 1
この変数は、PainterInterface がヒットした面のインデックスを返すときに使用します。
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..." menuItem paint3d_menu "Toggle 3D Painting..."
新しい[Toggle 3D Painting]のメニュー項目です。メインのユーザ インタフェースにも、よく目立つボタンを追加します。
)
on commit_menu picked do copy theCanvasBitmap theBackgroundBitmap
on uv_menu picked do MicroPaint_CanvasRollout.unwrapTexture() on paint3d_menu picked do MicroPaint_CanvasRollout.startPainting3D()
新しいメニュー項目[Toggle 3D Painting]のハンドラです。PainterInterface のオンとオフを切り替える関数を呼び出します。
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 offset:[0,-3] highlightcolor:(color 255 200 200)
checkbutton airBrush "AirBrush" width:70 offset:[0,-3] highlightcolor:(color 200 255 200)
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
pickbutton pickMesh "Pick Mesh" width:90 height:30 highlightcolor:(color 200 200 255) 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()
)
) checkbutton paint3D "3D PAINT" width:90 height:50 highlightcolor:(color 200 200 255) pos:[bitmapX+5,180]
3D 空間でのペイントを制御する新しいチェックボタンです。有効にすると、青色に変わります。
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
)
on pickMesh picked obj do if obj != undefined do theObj = Obj
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_1, 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
)
)
fn StartStroke = ( thePainterInterface.undoStart() )
この関数はストロークの開始時点で実行されます。詳細は、「PainterInterface の説明」を参照してください。
fn PaintStroke =
(
この関数は、3D でストロークのペイントを行います。
theMesh = theObj.mesh
ペイントするオブジェクトの TriMesh のコピーを取得します。
thePainterInterface.getHitFaceData &bary &faceIndex theObj 0
次にこの関数を呼び出し、ブラシが最後にヒットした面を取得します (最後の引数は 0 という面インデックスで、「最後のヒットを取得する」ことを意味します)。スクリプトの初めの方で定義した変数 bary および faceIndex は、「参照によって」渡されます。 つまり、関数の結果はこれらに書き出されます。呼び出し後、bary には面内のヒット ポイントの重心座標が入り、faceIndex には最後にヒットした面のインデックスが入ります。 theObj は、ペイント対象としてシーン内で選択したオブジェクトを入れる変数です。
theFace = meshop.getMapFace theMesh theChannel faceIndex
このデータを使用し、ヒットしたメッシュの面と同じインデックスを持つチャネル 1 のマップ面を取得します。
vert1= meshop.getMapVert theMesh theChannel theFace.x
vert2= meshop.getMapVert theMesh theChannel theFace.y
vert3= meshop.getMapVert theMesh theChannel theFace.z
次に、マップ面が参照するテクスチャ頂点を読み取ります。
thePoint= bary.x*vert1 + bary.y*vert2 + bary.z*vert3
drawStroke [thePoint.x * bitmapx_1, bitmapy_1 -thePoint.y * bitmapy_1] [thePoint.x * bitmapx_1, bitmapy_1 -thePoint.y * bitmapy_1]
面内のヒット ポイントの重心座標と、その面に対応する テクスチャ頂点の座標を使用して、ヒット ポイントの UV 座標を計算します。
thePainterInterface.clearStroke()
最後に、ストロークをクリアします。
) fn EndStroke = (
この関数はストロークの終了時点で呼び出されます。
thePainterInterface.undoAccept()
アンドゥ システムの変更を受け入れます。
if autoSave.checked do (
ただし自動保存が有効になっている場合です。
save theCanvasBitmap
ペイントをディスクに保存します。
try(theObj.material.diffsemap.bitmap = theCanvasBitmap)catch()
オブジェクトのマテリアルの拡散反射光マップ チャネルのビットマップに、ペイントのビットマップ値を強制設定します。
)
)
fn CancelStroke = (thePainterInterface.undoCancel())
この関数は、ストロークをキャンセルした場合に呼び出されます。
fn SystemEndPaintSession = ( paint3D.checked = false )
この関数は、ペイントのセッションが終了したときに呼び出されます。ペイントが別の理由で終了する場合に、UI ボタンをオフにします。
fn startPainting3D =
(
この関数は、3D ペイントのオンとオフを切り替えます。
if thePainterInterface.InPaintMode() or theObj == undefined then
(
すでにペイント中である場合、またはオブジェクトがまだ選択されていない場合は、
thePainterInterface.EndPaintSession()
paint3D.checked = false
3D ペイントのセッションを終了し、UI のボタンのチェックマークを外します。
)
else
(
3D ペイントが有効になっていないときにメッシュ シーン オブジェクトが選択されている場合は、
paint3D.checked = true
チェックボタンのチェックマークが必ず付いた状態になっているようにします (メイン メニューからチェックマークを付けるのと同じです)。
thePainterInterface.pointGatherEnable = TRUE
ポイント収集モードを有効にします。
thePainterInterface.initializeNodes 0 #(theObj)
選択されている 1 つのオブジェクトだけで PainterInterface を初期化します。
thePainterInterface.offMeshHitType = 2
メッシュがヒットされない場合の動作を 2 に設定します。この動作ではメッシュは必要ではありません。この場合にはヒットされる面はなく、ペイントが行われません。
thePainterInterface.ScriptFunctions startStroke paintStroke endStroke cancelStroke SystemEndPaintSession
事前に定義済みのスクリプト関数をすべて PainterInterface に登録し、
thePainterInterface.startPaintSession()
ペイントを開始します。
)
)
on paint3D changed state do startPainting3D()
ユーザがチェックボタンを押した場合に上の関数を呼び出します。
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 autoSave changed state do if state 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
)
結果:
3D のオブジェクトと 2D のイメージは同時にペイントできます。
前のチュートリアル: