Volumetric Model to Isometric Sprite
BlitzMax Forums/BlitzMax Programming/Volumetric Model to Isometric Sprite
| ||
Here's a program I wrote back in February that creates 3/4 perspective isometric sprites from 32x32x32 volumetric images. It does this by rendering 32 horizontal slices of the volumetric model with Y scaled by half. Then it renders the next slice up, but a pixel higher vertically on the screen. This was originally for a Syndicate-like game I was working on, to make it feasible to draw sprites of human figures with lots of directional facings, otherwise prohibitive in terms of time. I should really do something with this because it's kinda neat but I want to put it out there for other people to enjoy. I'm also putting this in the code archives. This even has some rudimentary lighting. It wouldn't be hard to add direction-sourced raymarching for ambient light or even specular to this. Ideally you would use this for an isometric image editor to produce spritesheets using grabimage, I don't think it's fast enough to do realtime voxel-model display, but it might be. I understand this was how Red Alert II did things but I understand the display was realtime and used hand-tuned assembly, so maybe it's possible, that ran nice on some really shabby hardware. You could also try the SMAC/Alpha Centauri method where they raymarch straight lines through the volumetric data. That's how old-style medical volumetric imaging was possible in real time in the 80s and there are a lot of things you could do with it today for constrained-perspective games, like maybe platformers... Type TColor 'color by RGB value Field r:Int, g:Int, b:Int Method Setrgb(myr:Int,myg:Int, myb:Int) r=myr g = myg b = myb End Method Method getr() Return myr End Method Method getg() Return g End Method Method getb() Return b End Method Method printrgb() ' For debug. Print r + " " + g + " " + b End Method Method setcolorfromtcolor() ' Set the global draw color according to tcolor rgb. SetColor r, g, b End Method Method addrgb(nr:Int, ng:Int, nb:Int) ' Add given rgb value to this rgb. r = r + nr g = g + ng b = b + nb End Method End Type Type imagegrid ' This is the voxel model structure. 'imagegrid - array of RGB values. Z is positive upward. Field colorarray : TColor [32,32,32] ' Define the voxel array Field imagearray : TImage [32] Method New() ' constructor initializes entire array-- we have to do this because ' Max doesn't ordinarily initialize an array of objects and it produces ' bizarre errors if you don't. For Local i:Int = 0 To 31 For Local j:Int = 0 To 31 For Local k:Int = 0 To 31 colorarray[i,j,k]=New tcolor Next Next Next For i:Int = 0 To 31 imagearray[i]=CreateImage(45,69, 1, DYNAMICIMAGE|MASKEDIMAGE) Next End Method Method setvoxel(x:Int, y:Int, z: Int, r:Int, g:Int, b:Int) Assert invoxel(x,y,z) Else "Voxel out of bounds." colorarray[x,y,z].Setrgb(r, g, b) End Method Method drawslice(x:Int, y:Int, z:Int) ' draws a square slice z at screencoords x and y Assert testconstraint(x,0,31) Else "Slice out of bounds in drawslice()" For Local i:Int = 0 To 31 For Local j:Int = 0 To 31 colorarray[i,j,z].setcolorfromtcolor() Plot x+j, y+i Next Next End Method Method fill(r:Int, g:Int, b:Int) For Local i:Int = 0 To 31 For Local j:Int = 0 To 31 For Local k:Int = 0 To 31 setpoint(i,j,k, r,g,b) Next Next Next End Method Method fillrect(ux:Int, uy:Int, lx:Int, ly:Int, z:Int, r:Int, g:Int, b:Int) Assert invoxel(ux, uy, z) And invoxel(lx, ly, z) Else "Rect out of bounds in fillrect()" For Local i:Int = ux To lx For Local j:Int = uy To ly setpoint(i,j,z, r,g,b) Next Next End Method Method fillbox(ux:Int, uy:Int, lx:Int, ly:Int, lz:Int, uz:Int, r:Int, g:Int, b:Int) ' coordinates of box, then bottom to top extents Assert invoxel(ux, uy, uz) And invoxel(lx, ly, lz) Else "Box out of bounds in fillbox" For Local k:Int = lz To uz fillrect(ux, uy, lx, ly, k, r, g, b) Next End Method Method setpoint(i:Int, j:Int, k:Int, r:Int, g:Int, b:Int) Assert invoxel(i,j,k) Else "Voxel out of bounds in setpoint()" colorarray[i,j,k].setrgb(r,g,b) End Method Method isoccupied(x:Int, y:Int, z:Int) Assert invoxel(x,y,z) Else "Voxel out of bounds in isoccupied()" Local tempcolor:tcolor = colorarray[x,y,z] If tempcolor.r + tempcolor.g + tempcolor.b > 0 Return True Else Return False EndIf End Method Method toplight(lightr:Int, lightg:Int, lightb:Int) ' toplights entire boxel For Local i:Int = 0 To 31 For Local j:Int = 0 To 31 Local k:Int = 31 ' descend from top of boxel to first occupied voxel While k > 0 And isoccupied(i,j,k)=False k = k - 1 Wend If k > -1 And isoccupied(i,j,k)=True colorarray[i,j,k].addrgb(lightr:Int, lightg:Int, lightb:Int) EndIf Next Next End Method Method leftlight(lightr:Int, lightg:Int, lightb:Int) For Local j:Int = 0 To 31 For Local k:Int = 0 To 31 Local i:Int = 31 'enter from the left and move toward first occupied voxel While i>0 And isoccupied(i,j,j)=False i=i-1 Wend If i>-1 And isoccupied(i,j,k)=True colorarray[i,j,k].addrgb(lightr:Int, lightg:Int, lightb:Int) EndIf Next Next End Method Method grabsliceimages() Local tempimage:TImage = CreateImage(45,69, 1, DYNAMICIMAGE|MASKEDIMAGE) For Local i:Int = 0 To 31 Cls 'SetRotation 45 'SetScale 1,.5 SetColor 255,255,255 drawslice (100,100,i) GrabImage tempimage, 100,100, 0 Cls SetRotation 45 DrawImage tempimage, 100,100 GrabImage imagearray[i],77,77,0 Next SetRotation 0 End Method Method drawimagestack(x, y) Local tempimage:TImage = CreateImage(45,69, 1, DYNAMICIMAGE|MASKEDIMAGE) SetScale 1, .5 For Local i:Int = 0 To 31 tempimage = imagearray[i] DrawImage tempimage,x,y-i Next End Method End Type Type voxgrid ' A grid of 32 x 32 x 32 points, and associated isometric sprite images Field image:TImage = CreateImage(45,69, 1, DYNAMICIMAGE|MASKEDIMAGE) Field imagestack:TImage = CreateImage(45,69,32,DYNAMICIMAGE|MASKEDIMAGE) Method setimage(myimage:TImage) image = myimage End Method Method setimageslice(i:Int, myimage:TImage) Assert testconstraint(i,0,31) Else "Slice out of bounds in setimageslice" End Method Method drawgrid(x:Int, y:Int) For Local i:Float = 1 To 31 DrawImage image, x,y-i,0 Next End Method End Type Function testconstraint(x:Int, lowerbound:Int, upperbound:Int) ' Test whether an input integer x is between lowerbound and upperbound. If x < lowerbound Or x > upperbound Return False Else Return True EndIf End Function Function invoxel(x:Int, y:Int, z:Int) ' Test whether an input point x, y, z is within a 32 by 32 by 32 voxel. If testconstraint(x, 0, 31) And testconstraint(y, 0, 31) And testconstraint (z, 0, 31) Return True Else Return False EndIf End Function Function boxinvoxel(ux:Int,uy:Int, lx:Int, ly:Int, lz:Int, uz:Int) If invoxel(ux, uy, uz) And invoxel (lx, ly, lz) Return True Else Return False EndIf End Function Global testcolor:tcolor = New tcolor Global testgrid:imagegrid = New imagegrid Global testgrid2:imagegrid = New imagegrid testcolor.Setrgb(100,101,102) testcolor.printrgb() Graphics 640, 480 For i = 1 To 1000 testgrid.setvoxel (jRand(32),jRand(32),jRand(32), jRand(255),Rand(255),jRand(255)) Next testgrid2.fill(75,75,100) 'testgrid2.fillrect(10, 10, 20, 20, 5, 0, 0, 0) testgrid2.toplight(45,45,45) testgrid2.leftlight(100,100,100) Cls testgrid2.grabsliceimages() While Not KeyDown (KEY_ESCAPE) Cls testgrid.drawslice(100,100,10) testgrid2.drawslice(140,100,10) testgrid2.drawslice(180,100,5) testgrid2.drawslice(240,100,31) testgrid2.drawimagestack(300,100) testgrid2.drawimagestack(344,100) testgrid2.drawimagestack(322,120) Flip Wend Function jrand(x:Int) Local retval:Int = Rand(x)-1 Return retval End Function |