Notifications
Clear all

[Closed] Best way to Draw Boxes in Rollout?

So I want to create a bunch of rectangle objects on top of a square and then move them around.

What’s the best way to approach that? I’m using buttons right now, but they flicker really bad whenever you move them about.

Are .net buttons drawn faster? Is there a .net class I should use? All I really just want are <DIV> boxes rendered at 10fps minimum.

What have other people found works best in the past?

32 Replies

What are you trying to do exactly?

Well the easiest way to describe it would be to imagine there are a bunch of boxes on a floor and the dialogbox is a view looking down on them.

It gets the floor plane’s aspect ratio and creates a group to represent the “floor”

Then on all 4 sides it draws a Slider (In this case a progress bar because vertical sliders are evidently gimped). When you select a box (button) in the viewport it activates the sliders to be float positions (0.0-1.0 of Floor’s X) and (0.0-1.0 of Floor’s Y) of the box’s center within the floor.

You can then move around boxes on the floor using the sliders.

But the redraw performance as a button is moved around in the dialogbox is very very poor –like 2fps. Even worse it disappears completely until it finishes re-rendering. So while tweaking with the sliders it dissappears until you stop moving them for a moment.

So I’m lead to believe that buttons are not the UI element I want to use. I need an element which I can manipulate and is rendered in RT.

Create a bitmap of the size you want and draw everything yourself using a bouble-buffer technique. The one plane will be the backgroud, the other the foreground. You can have a third one that is the pure background without anything.
So if you have many boxes, copy the pure background into the front buffer, draw all boxes except the one that will be moved, copy the resulting bitmap to the backbuffer, copy the backbuffer into the front buffer, draw the moved box into the front buffer, repeat copy/draw. Using the Avguard methods (already in Max 2008) for copying portions of a bitmap into another, you should get at least 30 fps or more and your rectangles could be arbitrary bitmap icons. Use a dialog with mouse handlers for mouse clicks and drags and bounds checking instead of sliders to freely move the boxes around. Kind of like a Do It Yourself Node Editor.

This is how TreeMatoGraph was written:
http://www.scriptspot.com/bobo/darkmoon/tmg/
I don’t think it works in the latest releases of Max though (it was written for R5), but that’s the idea.

Ahhhh! Perfect.

Thank you.

There’s always Flash, too.

Ok I have a performance question. Which is faster? setPixel? or pastebitmap?

It seems like the first time I run it setPixel smokes paste by like 100% faster. But then the second time pastebitmap smokes setPixel by about 400%.

So how can I get pastebitmap to always be 400% faster? Or is it and the times that perpixel wins out simply a fluke. Obviously if I were drawing a large box… say a 100×300 box then no question the paste option should always win. But it seems pretty all over the board in the results I’m getting.

Is there a better way than either?

 
(
 try(destroyDialog BoxDrawing)catch()
 bitmapX = bitmapY = 256
 BackgroundBitmap = (bitmap bitmapX bitmapY color:white)
 rollout BoxDrawing "DrawingBox"
 (
  bitmap theCanvas pos:[0,0] width: bitmapX height: bitmapY bitmap: BackgroundBitmap
  
  fn drawbox canvas: pos: width: height: border:1 bordercolor:black bgcolor:white= 
  ( 
   local bgwidth = (width-(border*2))
   local bgheight = (height-(border*2))
   local borderbitmap = bitmap width height color:bordercolor
   local bgcolor = bitmap bgwidth bgheight color:bgcolor
   pastebitmap borderbitmap canvas (box2 0 0 width height) pos
   pastebitmap bgcolor canvas (box2 0 0 bgwidth bgheight) (pos+border)
  )
  fn drawbox02 canvas: pos: width: height: border:1 bordercolor:black bgcolor:white= 
  ( 
   local width = (width-1)
   local height = (height-1)
   for x = 0 to width do
   (
	for y = 0 to height do
	(
	 if x < (border) or y < (border) or x > (width-border) or y > (height-border) then drawcolor = #(bordercolor) else drawcolor = #(bgcolor)
	 setPixels canvas [pos.x+x,pos.y+y] drawcolor
	)
   )
  )
  
  fn clearcanvas canvas =
  (
   theCanvas.bitmap = (bitmap canvas.width canvas.height color:white)
  )   
  
  on BoxDrawing open do
  (
   start = timestamp()
   for i = 1 to 100 do
   (
	clearcanvas BackgroundBitmap
	drawbox02 canvas:BackgroundBitmap pos:[10,10] width:30 height:5 border:2 bordercolor:blue bgcolor:green
	drawbox02 canvas:BackgroundBitmap pos:[15,15] width:30 height:30
	drawbox02 canvas:BackgroundBitmap pos:[20,20] width:40 height:40 border:5 bordercolor:green bgcolor:black
   )
   theCanvas.bitmap = BackgroundBitmap
   end = timestamp()
   format "PerPixel Processing took %" (end - start)
   start = timestamp()
   for i = 1 to 100 do
   (
	clearcanvas BackgroundBitmap
	drawbox canvas:BackgroundBitmap pos:[10,10] width:30 height:5 border:2 bordercolor:blue bgcolor:green
	drawbox canvas:BackgroundBitmap pos:[15,15] width:30 height:30
	drawbox canvas:BackgroundBitmap pos:[20,20] width:40 height:40 border:5 bordercolor:green bgcolor:black
   )
   theCanvas.bitmap = BackgroundBitmap
   end = timestamp()
   format "BitmapPaste Processing took %" (end - start)
  )
  
 )
 createDialog BoxDrawing bitmapX bitmapY
)

fwiw… first time I ran it on my piece of molasses…


PerPixel Processing took 4676
BitmapPaste Processing took 1062

Subsequent times had similar numbers – 3ds Max 2008, 32bit

Max 9 64 bit:

PerPixel Processing took 2032
BitmapPaste Processing took 593

At the same time, note that both can be heavily optimized.
Especially in the case of the pixels method, drawing single pixels is a waste of time, esp. if the content is monochromatic. Just prepare a single line and draw it multiple times and the performance will get better. (read “How to make it faster” in the Help FAQ).

  fn drawbox03 canvas: pos: width: height: border:1 bordercolor:black bgcolor:white= 
  ( 
	local theBorderLine = for i = 0 to width-1 collect bordercolor
	local theBodyLine = for i = 0 to width-1 collect bgcolor
	for i = 1 to border do (theBodyLine[i] = bordercolor; theBodyLine[width+1-i] = bordercolor)
	for y = border to height-border-1 do
		setPixels canvas [pos.x,pos.y+y] theBodyLine 
	for y = 0 to border-1 do
	(
		setPixels canvas [pos.x,pos.y+y] theBorderLine 
		setPixels canvas [pos.x,pos.y+height-1-y] theBorderLine 
	)	
  )

This method draws between 400 and 500ms on my machine.

Ok one last question to add to this little discussion.

How do I ‘draw’ words? Create a bitmap with my font’s alphabet and copy/paste ala the classic ransom note?

Edit: Note I just opened max and ran the script again and Paste took 14,000ms and perpixel only took 485… Bitmap paste seems very eratic on the ‘first execution of the morning’.

Is there a reason for that? Just curious. Bobo’s function is much much faster anyway. It’s just strange that now all of a sudden after a shut down… nap and reboot it’s pretty consistantly underwhelming.

PerPixel Processing took 594
BitmapPaste Processing took 14218
Bobo’s RLE Processing took 31

1 Reply
(@bobo)
Joined: 11 months ago

Posts: 0

How much is your Heap memory set to? Pasting bitmaps probably hits the memory hard, esp. 100 times in a row. Also, you are creating new bitmaps each time you paste, which are probably never released and the GC is trying to find you more memory from somewhere else, slowing you down. Watch the status line for GC() messages.

I would expect the garbage collection to kick in soon and if you are running with the default 7.5MB Heap (Customize>Preferences>MAXScript tab), it would be slower. I run with 64MB on all machines. When I see the MXS GC() message, my time for your code goes from 490 to about 600 ms.

In TMG, the bitmap was created just once (per update) and then just pasted again and again into the canvas, so it was pretty fast. A LOT faster with Paste than with setPixels.

I would suggest declaring the bitmap variables as local in the beginning of the SCRIPT and reusing them in the drawing function instead of creating new local bitmaps inside the function.
I did that and my Max also stopped garbage collecting because the same variables declared as local in the beginning were overwitten instead of creating new ones. Please note that you used bgcolor as both a color parameter and a bitmap variable, which is a mess and should not be done. I renamed it to bgbitmap:

(
  try(destroyDialog BoxDrawing)catch()
  local borderbitmap 
  local bgbitmap
 
 ...
 
   fn drawbox canvas: pos: width: height: border:1 bordercolor:black bgcolor:white= 
   ( 
    local bgwidth = (width-(border*2))
    local bgheight = (height-(border*2))
    borderbitmap = bitmap width height color:bordercolor
    bgbitmap = bitmap bgwidth bgheight color:bgcolor
    pastebitmap borderbitmap canvas (box2 0 0 width height) pos
    pastebitmap bgbitmap canvas (box2 0 0 bgwidth bgheight) (pos+border)
   )
 
 ...

Replace the code and see if you will get better performance.

Now note that if you are drawing BITMAPS (icons), the paste method will always be faster because it will put all pixels from the source image into the canvas at once, while the setPixels will require a getPixels() call to read from the source before pasting. That’s why I implemented the paste method in TMG, as the nodes were thumbnails for the rendered map and material samples!

Also, the clear canvas function could be optimized by creating one bitmap value for clearing the current canvas size once in the beginning of the script or in a dedicated function called only when the canvas size has changed, and then just put the ready value into theCanvas bitmap. As it is now, if you were to call the clearCanvas() functions multiple times, it could cause even more memory leaks…

Page 1 / 4