2018年09月20日

ビットマップペイントツールのチュートリアルをやってみた その168 3dsmax 2018

引き続きMAXScriptマニュアルに載っている「チュートリアル-ビットマップペイントツールを9つの簡単なステップで作成する」 の続きを考えて見たい。

前回「colOnEdge()」内の計算が全部整数で行われていたために結果がおかしくなってるところを突き止めて修正した。その結果が下の画像だ。なんだか斜め方向にはみ出し部分がペイントされている。

fig01

なんだかおかしいので確認のため「colOnEdge()」で参照しているエッジを赤色でペイントしてみた。すると下のように不連続UVエッジ上じゃなくてなんだか斜めのラインになっている。

fig02

よく見たら「colOnEdge()」に渡している(x1,y1)-(x2,y2)が不連続エッジの始点終点じゃなくて塗り潰す矩形の対角点になっていたので呼び出し側を修正した。

for ed in cap do (
  for p = 1 to (ed.count - 1) do (
    if ed[p][3] == 1.0 then (
      if ed[p][1] < ed[p+1][1] then (
        x1 = int(ed[p][1])
        x2 = int(ed[p+1][1])
      ) else (
        x2 = int(ed[p][1])
        x1 = int(ed[p+1][1])
      )
      sy = 0
      if ed[1][2] < center[2] then (
        y1 = int(ed[p][2]) - overpaint
        sy = y2 = int(ed[p][2]) 
      ) else (
        sy = y1 = int(ed[p][2])
        y2 = int(ed[p][2]) + overpaint
      )
      for y = y1 to y2 do (
        for x = x1 to x2 do (
          c = colOnEdge x y x1 sy x2 sy mb2w
          setPixels theBitmap  [x,y] #(c)
        )
      )
    )
  )
)

これが結果。「cap」データに対してはブラシタイプ「circle」までははみ出し塗りが出来るようになった。まだ「circle smooth」も斜めの不連続エッジもこれからだけどね。

fig03

だいぶいろいろいじったので久しぶりに全リストを載せて置くね。

struct objectPaint (
  public
    maxFaceRadius = 40.0,
    brushsize=10,
    BrushShape = 2,
    BrushColor = black,
    theObj,
    brushtransform,
    bitmapX = 512,
    bitmapY = 512,
    temp_bitmap_filename = (getDir #preview +"/microPaint_temp2.tga"),
    theBitmap,
    checkBitmap,
    edgecheck,
    edgeBitmap,
    overpaint = 5,

  private
    sanim = undefined,
    tmpm = undefined,
    fn chckClip p1 p2 w2b direction =
    (
      cplist = #()
      tlist = #()
      hsize = brushsize / 2.0
      bp1 = p1 * w2b
      bp2 = p2 * w2b

      st = 0
      t = 0.0
      case direction of (
        -- +x
        0:  (
            if bp1.x >= hsize then st = 1
            if bp2.x >= hsize then st = st + 2
            if st == 1 or st ==2 then (
              dx = bp2.x - bp1.x
              t = (hsize - bp1.x)/dx
            )
          )
        -- -x
        1:  (
            if bp1.x <= -hsize then st = 1
            if bp2.x <= -hsize then st = st + 2
            if st == 1 or st ==2 then (
              dx = bp2.x - bp1.x
              t = (-hsize - bp1.x)/dx
            )
          )
        -- +y
        2:  (
            if bp1.y >= hsize then st = 1
            if bp2.y >= hsize then st = st + 2
            if st == 1 or st ==2 then ( 
              dy = bp2.y - bp1.y
              t = (hsize - bp1.y)/dy
            )
          )
        -- -y
        3:  (
            if bp1.y <= -hsize then st = 1
            if bp2.y <= -hsize then st = st + 2
            if st == 1 or st ==2 then (
              dy = bp2.y - bp1.y
              t = (-hsize - bp1.y)/dy
            )
          )
        -- +z
        4:  (
            if bp1.z >= hsize then st = 1
            if bp2.z >= hsize then st = st + 2
            if st == 1 or st ==2 then (
              dz = bp2.z - bp1.z
              t = (hsize - bp1.z)/dz
            )
          )
        -- -z
        5:  (
            if bp1.z <= -hsize then st = 1
            if bp2.z <= -hsize then st = st + 2
            if st == 1 or st ==2 then (
              dz = bp2.z - bp1.z
              t = (-hsize - bp1.z)/dz
            )
          )
      )
      return #(st,t)  
    ),

    fn innerpoint p1 p2 t = (
      dx = p2.x - p1.x
      dy = p2.y - p1.y
      dz = p2.z - p1.z
      x = dx * t + p1.x
      y = dy * t + p1.y
      z = dz * t + p1.z
      return [x,y,z]
    ),

    fn clipface &vlist & elist w2b = ( 
      for i = 0 to 5 do (
        vtmp = #()
        etmp = #()
        for j = 1 to (vlist.count - 1) do (
          st = chckClip vlist[j] vlist[j+1] w2b i
          if st[1] == 0 then (
            append vtmp vlist[j] 
            append etmp elist[j]
          ) else if st[1] == 2 then (
            append vtmp vlist[j] 
            append vtmp (innerpoint vlist[j] vlist[j+1] st[2])
            append etmp elist[j]
            append etmp false
          ) else if st[1] == 1 then (
            append vtmp (innerpoint vlist[j] vlist[j+1] st[2])
            append etmp elist[j]
          )
        )
        if vlist.count == 0 then exit
        st = chckClip vlist[vlist.count] vlist[1] w2b i
        if st[1] == 0 then (
            append vtmp vlist[vlist.count]
            append etmp elist[elist.count]
        ) else if st[1] == 2 then (
          append vtmp vlist[vlist.count]
          append vtmp (innerpoint vlist[vlist.count] vlist[1] st[2])
          append etmp elist[elist.count]
          append etmp false
        ) else if st[1] == 1 then (
          append vtmp (innerpoint vlist[vlist.count] vlist[1] st[2])
          append etmp elist[elist.count]
        )
        vlist = vtmp
        elist = etmp
      )
    ),

    fn selectcrossface = (
       searchLength = sqrt(maxFaceRadius^2+brushsize^2) * 1.05
      sanim.length = searchLength 
      sanim.height = searchLength 
      sanim.width = searchLength
      centerpivot sanim  
      sanim.transform = brushtransform
      selfaces = getFaceSelection theObj
      return selfaces
    ),
    
    fn is_unce v1 v2 obj tch= (
      faces1 = meshop.getMapFacesUsingMapVert obj tch #{v1}
      faces2 = meshop.getMapFacesUsingMapVert obj tch #{v2}
      if (faces1 * faces2).numberSet == 1 then return true
      return false
    ),

    fn crossFaceMaps &maps theMesh theChannel mt2b =
    (
      invBrushTransform = inverse( brushtransform)
      crossfaces = selectcrossface()
      for faceIndex in crossfaces do (        
        vlist = #()
        sufFace = getFace theMesh  faceIndex
        append vlist (getVert theMesh sufFace.x)
        append vlist (getVert theMesh sufFace.y)
        append vlist (getVert theMesh sufFace.z)
        mg2w=(matrix3 [vlist[1].x,vlist[1].y,vlist[1].z] [vlist[2].x,vlist[2].y,vlist[2].z] [vlist[3].x,vlist[3].y,vlist[3].z] [0,0,0])
        texFace = meshop.getMapFace theMesh theChannel faceIndex
        tv1= meshop.getMapVert theMesh theChannel texFace.x
        tv2= meshop.getMapVert theMesh theChannel texFace.y
        tv3= meshop.getMapVert theMesh theChannel texFace.z
        mg2t=(matrix3 [tv1.x,tv1.y,1] [tv2.x,tv2.y,1] [tv3.x,tv3.y,1] [0,0,0])
        elist = #()
        elist[1] = is_unce texFace.x texFace.y theMesh theChannel
        elist[2] = is_unce texFace.y texFace.z theMesh theChannel
        elist[3] = is_unce texFace.z texFace.x theMesh theChannel
        
        mg2b = mg2t * mt2b
        mb2g = inverse mg2b
        mb2w = mb2g * mg2w
        
        clipface &vlist &elist invBrushTransform 

        if vlist.count == 0 then continue
        maplist =  #()
        for v in vlist do (
          mp = (meshop.getBaryCoords theMesh faceIndex v) * mg2t
          append maplist mp
        )
        append maps #(maplist , mb2w, elist)
      )
    ),

    fn scanx p1 p2 y = (
      dx = p2[1] - p1[1]
      dy = p2[2] - p1[2]
      return int( dx * (y - p1[2]) / dy + p1[1] )
    ),
    
    fn paintPixel bm x y dx dy cl = (
      for i = 0 to overpaint-1 do (
        setPixels bm [x+dx*i,y+dy*i] cl
      )
    ),
    
    fn pcol px py mb2w = (
      case BrushShape of (
        1: (
          p = [px,py,1.0] * mb2w
          if distance p brushtransform.pos <= BrushSize/2 then (
            return BrushColor
          ) else (
            return undefined
          )
        )
        2: (
          return BrushColor
        )
        3: (
          p = [px,py,1.0] * mb2w
          bcol = (getPixels theBitmap [px,py] 1)[1]
          theFactor = (distance p brushtransform.pos) / (BrushSize / 2.0)
          if theFactor <= 1.0 then (
            theFactor = sin ( 90.0 * theFactor)
            return ((bcol * theFactor) + (BrushColor * (1.0 - theFactor))) 
          ) else (
            return undefined
          )
        )
      )
    ),

    fn is_in px py = (
      x = px - bitmapX/2
      y = bitmapY/2 - py
      dstx = toX.value
      dsty = toY.value
      if dstx == 0.0 then (
        if x<0.0 then  false else true 
      ) else (
        if (y > dsty/dstx * x) then  false else true 
      )
    ),    

    fn colOnEdge px py x1 y1 x2 y2 mb2w = (
      dx = (x2-x1)
      dy = (y2-y1)
      dl = dx * dx +  dy * dy + 0.0
      d1 = dx/dl
      d2 = dy/dl
      t = (py-y1)*d1-(px-x1)*d2  
      ex = dy*t+px
      ey = -dx*t+py
      c = pcol ex ey mb2w
      --setPixels theBitmap  [ex,ey] #((color 255 0 0))
      if c == undefined then c = (getPixels theBitmap [px,py] 1)[1]
      return c
    ),
  
    fn drawline x1 x2 y mb2w = (
      for x = x1 to x2 do (
        if not checkBitmap [x+y*bitmapX] do(
          pcolor = pcol x y mb2w
          if pcolor != undefined then (
            setPixels theBitmap  [x,y] #(pcolor)
            checkBitmap [x+y*bitmapX] = true
          )
        )
      )
    ),
    
    fn delty v1 v2 = (
      dy = v2.y - v1.y
      if dy == 0 then 0
      else if dy > 0 then 1
      else -1
    )
    ,
    fn paintFace vface mb2w = (
      corner_n = #()
      corner_y = #()
      center = [0,0,0]
      n = vface.count

      dlt1 = delty vface[n] vface[1]
      for i = 1 to n do
      (
        nexti = int(mod i n ) + 1
        dlt2 = delty vface[i] vface[nexti]
        chng = dlt1 * dlt2
        if chng <= 0 and (dlt1 !=0 or dlt2 !=0)then (
          append corner_n i
          append corner_y vface[i].y
        )
        dlt1 = dlt2
        center += vface[i]
      )
      center /= n
      append corner_n corner_n[1]  
      append corner_y corner_y[1]
          
      side = #()
      cap = #()
      for i = 1 to (corner_n.count-1) do (
        border = #()
        ptr = corner_n[i]
        s = 0
        do (
          append border vface[ptr]
          ptr = int(mod ptr n ) + 1
          s = s + 1
          if s > 100 then exit
        ) while (ptr != corner_n[i + 1])
        append border vface[ptr]
        if (corner_y[i] - corner_y[i+1]) == 0 then (
          append cap border
        ) else (    
          append side border
        )
      )
      if side.count>=2 then (
        if side[1][2][1] > side[2][2][1] then (
          tmps = side[1]
          side[1] = side[2]
          side[2] = tmps
        )
        s1 = s2 = 0  
        if side[1][1][2] > side[1][side[1].count][2] then (
          s1 = 2
          s2 = 1
        ) else (
          s1 = 1
          s2 = 2
        )
        
        side2ptr = side[s2].count
        for i = 1 to ( side[s1].count - 1) do
        (
          for iy = side[s1][i][2]+1 to side[s1][i+1][2] do
          (
            while iy > side[s2][side2ptr - 1][2] do (
              side2ptr -= 1  
            )
            if side2ptr == 1 then side2ptr = 2
            ix1 = scanx side[s1][i] side[s1][i+1] iy
            ix2 = scanx side[s2][side2ptr] side[s2][side2ptr-1] iy
            drawline ix1 ix2 iy mb2w 
          )
        )

        for ed in cap do (
          for p = 1 to (ed.count - 1) do (
            if ed[p][3] == 1.0 then (
              if ed[p][1] < ed[p+1][1] then (
                x1 = int(ed[p][1])
                x2 = int(ed[p+1][1])
              ) else (
                x2 = int(ed[p][1])
                x1 = int(ed[p+1][1])
              )
              sy = 0
              if ed[1][2] < center[2] then (
                y1 = int(ed[p][2]) - overpaint
                sy = y2 = int(ed[p][2]) 
              ) else (
                sy = y1 = int(ed[p][2])
                y2 = int(ed[p][2]) + overpaint
              )
              for y = y1 to y2 do (
                for x = x1 to x2 do (
                  c = colOnEdge x y x1 sy x2 sy mb2w
                  --setPixels theBitmap  [x,y] #((color 255 0 0))
                  setPixels theBitmap  [x,y] #(c)
                )
              )
              
            )
          )
        )

      )
    ),

    fn delmodifier obj mtype = (
      local index = -1
      for i = 1 to obj.modifiers.count do (
      if (classof obj.modifiers[i]) == mtype then index = i
      )
      if index != -1 then (
        deletemodifier obj index
      )
    ),
  mt2b,
  public
    fn init = (
      theBitmap = bitmap bitmapX bitmapY color:white filename:temp_bitmap_filename
      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
      
      --select theObj
      if theObj.modifiers[#Vol__Select] == undefined do (
        addModifier theObj (volumeselect())
      )
      if (classof theObj) != Editable_mesh do (
        addModifier theObj  (Turn_to_Mesh ())
      )
      vmod = theObj.modifiers[#Vol__Select]
      vmod.level = 2
      vmod.type = 1
      vmod.volume = 3
      --sanim = vmod.gizmo
      sanim = box()
      vmod.node = sanim
      mt2b=(matrix3 [bitmapX,0,0] [0,-bitmapY,0] [0,bitmapY,1] [0,0,0])
    ),
    fn doPaint = (
      local theMesh = snapshotAsMesh theObj
      local theChannel = 1
      local maps = #()
      crossFaceMaps &maps theMesh theChannel mt2b
      checkBitmap = #{}
      edgecheck = #{}
      edgeBitmap = bitmap bitmapX bitmapY color:white

      for map in maps do (
        vface = #()
        for i = 1 to map[1].count do (
          p = map[1][i]
          append vface [int(p.x * bitmapx), int(bitmapy - p.y * bitmapy),int(map[3][i])]
        )
        paintFace vface map[2] 
      )
    ),
    fn redraw = (
      theObj.material.diffusemap.bitmap = theBitmap
    ),
    fn saveimage = (
      save theBitmap
      theObj.material.diffusemap.bitmap = theBitmap
    ),
    fn close = (
      delete sanim
      delmodifier theObj Vol__Select
      delmodifier theObj Turn_to_Mesh
    )
)

(
  ParticlePaint_Rollout = undefined
  pt = objectPaint()
  pf = undefined

  fn lerp t1 t2 w = (
    q1 = t1 as quat
    q2 = t2 as quat
    p1 = t1.pos
    p2 = t2.pos
    q = slerp q1 q2 w
    p = ((p2-p1)*w+p1)
    tr = q as matrix3
    tr[4] = p
    return tr
  )

  fn write_bitmap frame = (
    sf = "00" + (frame as string)
    temp_bitmap_filename = (getDir #preview +"/test"+ substring sf (sf.count - 2) (sf.count)  +".tga")
    pt.theBitmap.filename = temp_bitmap_filename
    pt.saveimage()
  )

  fn stroke tmp_file startframe stopframe write_by = (
    particleTMs = #()
    particleScls = #()
    pt.theBitmap = bitmap pt.bitmapX pt.bitmapY color:ParticlePaint_Rollout.blankColor.color
    LTM = matrix3 1
    frame = 0
    while ((not (eof tmp_file)) and (frame < stopframe)) do 
    (
      frame = readvalue tmp_file
      count = readvalue tmp_file
      sliderTime = frame
      FTM = LTM * pt.theObj.transform
      for i =  1 to count do
      (
        id = readvalue tmp_file
        tm = readvalue tmp_file
        scl = readvalue tmp_file

        if particleTMs[id] == undefined then (
          particleTMs[id] = tm
          particleScls[id] = scl
        ) else (          
          t1 = particleTMs[id] * FTM
          t2 = tm
          s1 = particleScls[id]
          s2 = scl
          d = distance t1.pos t2.pos
          if ParticlePaint_Rollout.BrushSizeRef.state == 2 then (
            div = d/((s1+s2) / 2.0 * (1.0 - ParticlePaint_Rollout.BrushOverlap.value))-1
          ) else (
            div = d/(pt.brushsize *  (1.0 - ParticlePaint_Rollout.BrushOverlap.value))-1
          )
          dcount = (div as integer) 
          if dcount < 0 then dcount = 0
          for j = 0 to dcount do (
            if div ==0.0 then w = 0.0 else w = j / div
            pt.brushtransform = lerp t1 t2 w
            if ParticlePaint_Rollout.BrushSizeRef.state == 2 then (
              pt.brushsize = s1 * (1 - w) + s2 * w
            )
            pt.doPaint()
          )
          particleTMs[id] = tm
          particleScls[id] = scl
        )
      )
      LTM = inverse pt.theObj.transform
      if write_by == #frame do write_bitmap frame
    )
    if write_by == #stroke do write_bitmap frame
  )

  fn myseek fp dest= (
    seek fp 0
    seek fp dest
  )

  fn run = (
    pt.BrushShape = ParticlePaint_Rollout.BrushShape.state
    pt.brushsize = ParticlePaint_Rollout.BrushSize.value
    pt.BrushColor = ParticlePaint_Rollout.inkColor.color 
    pt.init()
    pstart = ParticlePaint_Rollout.StartFrame.value
    pstop =ParticlePaint_Rollout.StopFrame.value

    frames =#()

    filename = (getDir #temp +"/particlePaint_temp.txt")
    tmp_file = createfile filename
    for i = pstart to pstop do (
      sliderTime = i
      count = pf.NumParticles()
      frames[i+1] = filePos tmp_file  
      format "%,%\n" i count to:tmp_file
      for j = 1 to count do (
        pf.particleIndex = j
        id = pf.particleID 
        tm = pf.particleTM
        scl = pf.particleScale
        format "%,%,%\n" id tm scl to:tmp_file
      )
    )
    close tmp_file

    tmp_file = openFile filename
    
    eser = showEndResult
    showEndResult = false

    md=undefined
    if pt.theObj.modifiers[#Unwrap_UVW] == undefined then (
      addModifier pt.theObj (Unwrap_UVW ())
      md = pt.theObj.modifiers[#Unwrap_UVW]
      modPanel.setCurrentObject pt.theObj.modifiers[#Unwrap_UVW]
    )
    else  modPanel.setCurrentObject pt.theObj.modifiers[#Vol__Select]

    if ParticlePaint_Rollout.StorokeType.state == 2 then (
      stl = ParticlePaint_Rollout.FalloffFrame.value
      for cf = pstart to pstop do (
        sf = cf - stl
        if sf < pstart do sf = pstart
        myseek tmp_file (frames[sf+1])
        stroke tmp_file sf cf #stroke
      )
    )
    else (
      stroke tmp_file pstart pstop #frame
    )

    modPanel.setCurrentObject pt.theObj.modifiers[1]
    showEndResult = eser
    if md != undefined then (
      deleteModifier pt.theObj md
    )
    pt.close()
    close tmp_file
  )

  fn edgelength e obj =
  (
    verts = (meshop.getVertsUsingEdge obj e) as array
    v1 = meshop.getvert obj verts[1]
    v2 = meshop.getvert obj verts[2]
    d = distance v1 V2
  )

  fn getMaxFaceRadius = (
    --global ParticlePaint_Rollout
    theMesh = snapshotAsMesh pt.theObj
    maxlength = 0.0
    edgecount = theMesh.edges.count
    for i = 1 to edgecount do (
      l = edgelength i theMesh
      if l > maxlength then  maxlength = l
    )
    br = ParticlePaint_Rollout.BrushSize.value
    pt.maxFaceRadius = sqrt((maxlength^2+3*br^2)/3)
  )
  dialogWidth = 200
  dialogHeight =550
  
  --global ParticlePaint_Rollout
  try(destroyDialog ParticlePaint_Rollout)catch()
  
  fn BitmapSaveAs = (
    theSaveName = getSaveFileName types:"BMP (*.bmp)|*.bmp|Targa (*.tga)|*.tga|JPEG (*.jpg)|*.jpg"
    if theSaveName != undefined do
    (
      ParticlePaint_Rollout.BitmapFiles.caption = theSaveName
    )
  )
  
  rcMenu PaintMenu
  (
    -- ここにメニューの内容を定義
    subMenu"File"
    (
      menuItem SaveAs "Save As..."
      menuItem quit_tool "Quit"  
    )

    subMenu"Help"
    (
      menuItem about_tool "About ParticlePaint..." 
    )
    
    on about_tool picked do messagebox "Particle Paint " title:"About..."
    on quit_tool picked do destroyDialog ParticlePaint_Rollout
    on SaveAs picked do (
      print "pick"
      BitmapSaveAs()
    )
  )
  
  fn mesh_filter obj = superclassof obj == GeometryClass and classof obj != TargetObject and obj != PF_Source
  fn pfs_filter obj = classof obj == PF_Source

  rollout ParticlePaint_Rollout "Partilce Paint"
  (
    pickbutton pickMesh "Pick Mesh"  height:30 filter:mesh_filter autodisplay:true
    pickbutton pickPFS "Pick PFSource"  height:30 filter:pfs_filter autodisplay:true
    colorpicker inkColor "BrushColor" height:16 modal:false color:black 
    colorpicker blankColor "BlankColor" height:16 modal:false color:white 
    spinner StartFrame "Start Frame"  type:#integer
    spinner StopFrame "Stop Frame" type:#integer
    radiobuttons StorokeType "Stroke Type" labels:#("constant", "Falloff")
    radiobuttons BrushSizeRef "Brush Size Reference" labels:#("Brush Size","Particle Size")
    spinner BrushSize "Brush Size" type:#integer
    spinner FalloffFrame "Falloff Frame" type:#integer
    
    spinner BrushOverlap "Brush Overlap" type:#float range:[0.0,0.99,0.7]
    
    radiobuttons BrushShape "Brush Shape" labels:#("Circle","Box","Circle Smooth")
    button BitmapFiles "Bitmap Files" width:150 height:16
    button btnMaxFaceRadius  "Get Max Face Radius" width:100 height:30
    spinner spnMaxFaceRadius "Max Face Radius"  type:#float
    spinner spnbitmapX "bitmapX"  type:#integer range:[0,4096,512]
    spinner spnbitmapY "bitmapY"  type:#integer range:[0,4096,512]
    button Painting  "Paint" width:100 height:30
    on ParticlePaint_Rollout open do (
      StartFrame.value = animationRange.start
      StopFrame.value = animationRange.end
      BrushSize.value = 5
      FalloffFrame.value = 5
      FalloffFrame.enabled = false
      btnMaxFaceRadius.enabled = false
      BitmapFiles.caption = (getDir #preview +"/test000.tga")
      Painting.enabled = false
      spnMaxFaceRadius.value = pt.maxFaceRadius
      spnbitmapX.value = pt.bitmapX
      spnbitmapY.value = pt.bitmapY
    )

    on BitmapFiles pressed do (
      BitmapSaveAs()
    )
    on pickMesh picked obj do (
      if pickPFS.object != undefined  then Painting.enabled = true
      btnMaxFaceRadius.enabled = true
      pt.theObj = obj

    )
    on pickPFS picked obj do (
      if pickMesh.object != undefined then Painting.enabled = true
      pf = obj
    )
    
    on StorokeType changed btn do (
      if btn == 1 then FalloffFrame.enabled = false else FalloffFrame.enabled = true
    )

    on Painting pressed do (
      run()
    )
    on btnMaxFaceRadius pressed do (
      getMaxFaceRadius()
      spnMaxFaceRadius.value = pt.maxFaceRadius
    )
    on spnbitmapX changed val do (
      pt.bitmapX = val
    )
    on spnbitmapY changed val do (
      pt.bitmapY = val
    )
  )
  createDialog ParticlePaint_Rollout dialogWidth dialogHeight menu:PaintMenu
)

続きはまた次回。

maxまとめページ



take_z_ultima at 11:30|この記事のURLComments(0)3ds Max | CG

2018年09月19日

Selection Assembliesについて調べてみた その7 modo12

引き続き「presets」パネルの「Selection Assemblies」にある「Select By Normal」アセンブリを調べてみたい。

fig01

前回調べてこのアセンブリはエレメントの法線方向の一定範囲に「Position」が示すポイントが来ていればそのエレメントが選択される働きをするものだとわかった。そこでこのアセンブリを使って動作を確認してみたい。

今回はロケータの方向を向いている面からパーティクルを発生させてみた。

fig01

まず下のようにシーンに波の形状にしたポリゴンメッシュとロケータを用意した。

fig02

次にからのメッシュアイテムを1つ追加して、「セットアップ」レイアウトで、「メッシュオペレータ」リストでこの「Mesh」アイテムに「オペレータ追加」から「Merge Meshes」を追加し、「ソース」を「Wave」アイテムにする。これでこの「Mesh」アイテムに「Wave」のコピーが取り込まれる。さらに「Delete」オペレータも追加して、「選択タイプ」を「Polygon」にする。

fig03

そしてスケマティックビューにこの「Delete」オペレータと「Locator」と「Select By Normal」を追加して、「Locator」アイテムはチャンネル追加で「ワールド位置」を追加して下のように接続する。

fig04

これで「Locator」の方を向いたポリゴンが削除される事になる。でもこれでは必要なポリゴンが削除されてしまうので、「Delete」の「Selection」に「Invert」を追加して選択を反転して「Locator」の方向を向いているポリゴンだけ残るように切り替える。

fig05

これで「Mesh」アイテムには「Wave」アイテム内のポリゴンの内、「Locator」の方を向いたポリゴンだけが抽出されたものになる。

あとは「Surface」エミッタを追加して、「ソース」としてこの「Mesh」アイテムを設定すれば、

fig06

「Locator」の方向を向いているポリゴンからパーティクルが発生するようになる。下のGifアニメはこのパーティクルに「Sprite」アイテムを適用したものだ。

fig01

続きはまた次回。

modo10-12ブログ目次



take_z_ultima at 11:30|この記事のURLComments(0)modo | CG

2018年09月18日

ビットマップペイントツールのチュートリアルをやってみた その167 3dsmax 2018

引き続きMAXScriptマニュアルに載っている「チュートリアル-ビットマップペイントツールを9つの簡単なステップで作成する」 の続きを考えて見たい。

前回はブラシの形状を「Box」にした時のバグを直した。今度は「Circle」にして試してみたら下の部分で異常が見つかった。

fig01

そこで「Box」の時の結果と重ねてみた。これを見るとペイントする範囲は合っているけど、ペイントするパターンが間違っているのがわかる。はみ出して塗る部分はその点から不連続境界上に降ろした垂線の交点の色をサンプリングするはずなのにそうなっていない。

fig02

つまり「colOnEdge()」が返す色が間違っているわけだ。

そこでコードを見てみたら、扱ってる変数px〜y2まで全部「Integer」型だった。これだと長さを正規化したd1,d2が必ず0になって、tも0になってしまう。

fn colOnEdge px py x1 y1 x2 y2 mb2w = (
  dx = (x2-x1)
  dy = (y2-y1)
  dl = dx * dx +  dy * dy + 0.0
  d1 = dx/dl
  d2 = dy/dl
  t = (py-y1)*d1-(px-x1)*d2  
  ex = dy*t+px
  ey = -dx*t+py
  c = pcol ex ey mb2w
  setPixels theBitmap  [ex,ey] #((color 255 0 0))
  if c == undefined then c = (getPixels theBitmap [px,py] 1)[1]
  return c
),

そこでdlの式に0.0を足してFloatにキャストした。式の中でIntegerとFloatがあったら暗黙的にFloatにキャストされてから計算されるのでこれでOKだ。

これで万事OKかと思ったらまだいろいろと不具合があったので、続きはまた次回。

maxまとめページ



take_z_ultima at 11:30|この記事のURLComments(0)3ds Max | CG

2018年09月13日

ビットマップペイントツールのチュートリアルをやってみた その166 3dsmax 2018

引き続きMAXScriptマニュアルに載っている「チュートリアル-ビットマップペイントツールを9つの簡単なステップで作成する」 の続きを考えて見たい。

前回見つけたおかしなペイント部分について調べてみた。

fig06

異常が出ている部分のUVマップの頂点の座標を「UVWアンラップ」モディファイヤの「UVエディタ」を開いて調べてみた。

fig01

その結果2つの頂点の座標値は(0.883,0.934),(0.905,0.934)だとわかった。これが512X512のビットマップにマッピングされているので、ビットマップ座標値に変換すると、ビットマップの原点は左下でUVWマップは左上なので、Y座標値を反転して、

0.883 x 512 = 452
512 - 0.934 x 512 = 34
0.905 x 512 = 463

なので(452,34),(463,34)になる。「paintFace()」に渡される多角形の頂点座標値の配列「vface」の中からこれらの頂点を持つもので、Z値が1を含むものを探してみると、以下の2つのブロックが見つかった。Z値は不連続UVかどうかのフラグに使われていて、1なら不連続UVエッジと言う事だ。

[452,25,1]
[452,33,0]
[463,33,1]
[463,33,0]
-----
[463,33,1]
[463,33,0]
[463,33,0]

これを見ると2つ目のブロックは3つとも全く同じ位置の頂点になっているからペイントする領域も無く、除去しちゃても良かったものだ。同様に1つ目のブロックも後ろの2つの座標値は同じ[463,33]なのでこれも無視していいものだ。

さらにこれらの多角形データから生成される水平エッジの配列である「cap」配列を調べると、上の多角形でだけ「cap」配列が生成されていて、

#([452,33,0], [463,33,1], [463,33,0])

になっている。これらは確かに水平エッジだけど2本目は長さ0だ。

以上データを見てきたけど、ここまでのところ下の画像の底辺部分のエッジが不連続扱いされる事にはならない。不連続UVエッジは始点のZ座標値が1のものなので、(452,33,0)が始点のエッジはやっぱり連続UVエッジだ。

fig02

そこでこれを処理したプログラム部分をよく確認してみたら・・・あっ!・・・。引数pがいつのまにか定数の1と2になってる・・・。

    for ed in cap do (
      for p = 1 to (ed.count - 1) do (
        if ed[p][3] == 1.0 then (
          if ed[1][1] < ed[2][1] then (
            x1 = int(ed[1][1])
            x2 = int(ed[2][1])
          ) else (
            x2 = int(ed[1][1])
            x1 = int(ed[2][1])
          )
          if ed[1][2] < center[2] then (
            y1 = int(ed[1][2]) - overpaint
            y2 = int(ed[1][2])
          ) else (
            y1 = int(ed[1][2])
            y2 = int(ed[1][2]) + overpaint
          )
          for y = y1 to y2 do (
            for x = x1 to x2 do (
              c = colOnEdge x y x1 y1 x2 y2 mb2w
              --setPixels theBitmap  [x,y] #(c)
              setPixels theBitmap  [x,y] #((color 255 0 0))
            )
          )
        )
      )
    )

これでは「cap」のエッジデータは1と2が固定して使われるだけになっちゃうから確かに赤い線の部分が不連続エッジで現れるはずだ。

#([452,33,0], [463,33,1], [463,33,0])

むしろ他がなんとも無かったのは「cap」データが偶然2点しか含んでなかったからだろうな。

プログラムを下のように直して引数pが反映されるようにした。

    for ed in cap do (
      for p = 1 to (ed.count - 1) do (
        if ed[p][3] == 1.0 then (
          if ed[p][1] < ed[p+1][1] then (
            x1 = int(ed[p][1])
            x2 = int(ed[p+1][1])
          ) else (
            x2 = int(ed[p][1])
            x1 = int(ed[p+1][1])
          )
          if ed[p][2] < center[2] then (
            y1 = int(ed[p][2]) - overpaint
            y2 = int(ed[p][2])
          ) else (
            y1 = int(ed[p][2])
            y2 = int(ed[p][2]) + overpaint
          )
          for y = y1 to y2 do (
            for x = x1 to x2 do (
              c = colOnEdge x y x1 y1 x2 y2 mb2w
              --setPixels theBitmap  [x,y] #(c)
              setPixels theBitmap  [x,y] #((color 255 0 0))
            )
          )
        )
      )
    )

これが実行結果。今度は水平方向のはみ出し塗り部分がちゃんとなった。

fig03

続きはまた次回。

maxまとめページ



take_z_ultima at 11:30|この記事のURLComments(0)3ds Max | CG

2018年09月12日

Selection Assembliesについて調べてみた その6 modo12

引き続き「presets」パネルの「Selection Assemblies」にあるアセンブリを調べてみたい。

今回は「Select By Normal」だ。

fig01

下の画像はアセンブリを展開して開いたものだ。

fig02

基本的な仕組みは前の2つのアセンブリと同じで3つのタイプの「Selection Operator」から各エレメントのパラメータを取得して、そこから条件を満たすものについて「Selection Operator」の「選択」チャンネルをtrueにセットする事でそのエレメントが選択されて、どのタイプのエレメントのオペレータが使われるかは「Selection Type Filter」で制御されて、タイプじゃないものは0が出るので変換マトリクス化して「Matrix Compose」で掛け合わせてやれば、有効な変換マトリクスだけが出力されるようになっている。

今回は「位置」と「法線」がパラメータとして取得されていて、「位置」と「位置」+「法線」が変換マトリクス化されて、

fig03

出力されている。「位置」+「法線」はエレメントの位置から法線ベクトル分だけ移動した位置を指す位置ベクトルになる。

fig04

「位置」は「Measure Angle」ノードの「起点」に入力され、「位置」+「法線」は「終点B」に、「アセンブリ入力」の「Position」は「終点A」に入力されて、3つの点の位置から「終点A」−「起点」−「終点B」の為す角度が算出される。つまりこのノードが出力する角度は、エレメントの起点から「Position」への方向とエレメントの法線の為す角度で、エレメントが向いている方向が「Position」の方向からどれだけずれてるかがわかる。

fig05

「AはBより大きい」は「Minimum Angle」と「Maximum Angle」の大きい方の値がそのまま「出力」されるようになっている。

fig08

この「出力」と「Measure Angle」からの出力が「AはBより小さいもしくは等しい」ノードで比較されて、角度が「Minimum Angle」と「Maximum Angle」の大きい方の値より小さいか等しければtrueが出力される。

また、「Measure Angle」からの出力は「Minimum Angle」と「AはBより大きいもしくは等しい」ノードで比較されて、角度が「Minimum Angle」よりも大きいか等しければtrueが出力される。

fig06

これら2つが「かつ」ノードで論理積が取られて2つともtrueの時に「Selection Operator」の「選択」がtrueになる。

fig07

つまりこのアセンブリを使うとある点(Position)の方向に対して特定の範囲の角度を持つエレメントが選択出来る。

続きはまた次回。

modo10-12ブログ目次



take_z_ultima at 11:30|この記事のURLComments(0)modo | CG
Archives