Voxel Slicer (Fake 3D in Max2D)

Community Forums/Showcase/Voxel Slicer (Fake 3D in Max2D)

Hezkore(Posted 2016) [#1]
I started playing around with MagicaVoxel and wanted to import the .VOX files to BlitzMax but not use any 3D engine.
A friend of mine showed me this method to get "3D" in GameMaker.
So I decided to use a similar method to get the .VOX files into BlitzMax using nothing but BlitzMax's standard Max2D module.
Here's the result:





Basically I just load the .VOX file and create slices, like a CRT scan, and render those ontop of each other.
You can load the .VOX files directly, or you can output a PNG file and load that, which is a lot faster!
You can also use palettes and change the camera angle of the voxel model.
The shadow I put in there is just for show, you can use it but it's pretty "cheap".

Here's the final module: https://bitbucket.org/Hezkore/vox-loader
The module comes with some examples and .VOX files to get you started.
Though it's so simple to use, I doubt anyone will need the examples heh.
In the end you just use it like a normal TImage.
SetRotation, SetScale, SetColor etc. all works like normal with it, just that rotation is now 3D.

Here's a module for Monkey X that can load PNG slices: https://bitbucket.org/Hezkore/monkey-vox-slice-loader

I hope someone uses this to make something cool!


RustyKristi(Posted 2016) [#2]
Cool and interesting mod Hezkore. Any chance of handling incbin files?

I wonder how would that handle animations, as a separate sprite?


Bobysait(Posted 2016) [#3]
Very nice stuff !


Hezkore(Posted 2016) [#4]
@RustyKristi The downside to voxels have always been the animations.
You could do, as you said, separate images for each frame, but It's mostly used for static things.
I might be able to get incbin working for PNG slices, probably not VOX files though.

@Bobysait Thank you! :)

I've added a VOX viewer you can use to save PNG slices directly from VOX files, then import the slices to your project.


Hezkore(Posted 2016) [#5]
@RustyKristi I've made LoadSlices work with IncBin now.
Check out example 7 if you want to know how it works.


RustyKristi(Posted 2016) [#6]
Awesome!


Hezkore(Posted 2016) [#7]
I've updated the module.
The shading is now much nicer and drawing has been optimized.
It now also comes with a simple demo showing everything in action.
You can grab the Windows EXE for that demo right here: https://dl.dropboxusercontent.com/u/2842751/vox_demo.zip
Hold left mouse button to move.
Hold right mouse button to rotate the camera and change angle.



AdamStrange(Posted 2016) [#8]
Superb coding. very sneaky and clever! Well impressed...


RustyKristi(Posted 2016) [#9]
Awesome Hezkore! Source code please? :B


Hezkore(Posted 2016) [#10]
@RustyKristi check my first post. :)


Naughty Alien(Posted 2016) [#11]
..hey..this is very nice..thanks man..


RustyKristi(Posted 2016) [#12]
Ah yes, thanks!


Hezkore(Posted 2016) [#13]
It looks like MagicaVoxel's got a big update and now supports animations, built-in palettes and different material types per voxel.
I'll try to add support for all of this but it might take some time as I'll have to redo a big part of the module.
You can still use the module of course, it still loads the newest VOX format, it just doesn't use the new data.


Guy Fawkes(Posted 2016) [#14]
I would absolutely LOVE to see a Blitz3D version of this!

Thanks alot bro!

~GF


Hezkore(Posted 2016) [#15]
@Guy Fawkes If you just want to load the sliced PNG files (which you should anyways) it's super simple to use in any language.
It's built to support the standard LoadImage function frame system.
All you do is load for example "obj_tree3_10x10.png" and in this case each frame/slice is 10 width and 10 in height.
Then just draw those ontop of each other.
Rotate the frames/slices from the middle and voila, it's 3D!
Though with Blitz3D I would probably recommend loading the voxels and generating a proper 3D mesh.
I only did this because BlitzMax has no official 3D engine.


RustyKristi(Posted 2016) [#16]
as Hezkore mentioned..

Though with Blitz3D I would probably recommend loading the voxels and generating a proper 3D mesh.

I only did this because BlitzMax has no official 3D engine.




I would absolutely LOVE to see a Blitz3D version of this!


Fake 3D in Blitz3D?? yea what's the point? :-)


Guy Fawkes(Posted 2016) [#17]
The point is to have a little bit of fun! That's all! I think it would be really AWESOME to see something like this in Blitz3D! I mean. Think about it. We could make Chrono Trigger-like games with this type of function! The point that this can also save ALOT of memory by it being Fake 3D anyway!

Thanks alot!

~GF


Hezkore(Posted 2016) [#18]
@Guy Fawkes Just load the slices and get to it! :)
But since you're not using 3D, maybe you could just use BlitzMax?
Notice that animations are rather difficult when it comes to voxels.
Though I now have the new animation system MagicaVoxel uses working in the module, so things are a bit easier now atleast.


Guy Fawkes(Posted 2016) [#19]
I'm not going to change my program of choice! I am a blitz3D programmer & that's that! Why can't there be like an example not necessarily THIS library, but an example showing an animated character LIKE this in Blitz3D?

I think it would be fun & could bring back Super Nintendo graphics which are the BEST!

Thanks alot Hekzore!

~GF


Hezkore(Posted 2016) [#20]
@Guy Fakwes I haven't used Blitz3D, so I don't think I'll ever get around to porting it, sorry :/

In other news.
I've gotten most of the new features working and updated the source code, so grab it while it's hot!
Here's an animation example for Windows (works in Linux too): example9.exe
ADDED: Support for VOX animations/frames
ADDED: Support for built-in palettes
FIXED: Junk pixels when saving PNG slices
CHANGED: Depth and Frame count is now included in PNG name
ADDED: Frame slider in VOX Viewer
ADDED: TVox now uses already loaded VOX files instead of loading new ones
ADDED: Example 9 showing animations
I'm not sure if I should support "materials".
The only thing that would actually work in 2D would be the glass material which would make voxels transparent.


RustyKristi(Posted 2016) [#21]
I'm not going to change my program of choice! I am a blitz3D programmer & that's that!


GF, we're not questioning your loyalty with B3D lol, but it helps if you have a little bit of knowledge in BlitzMax because they are related language, in fact a successor of B3D w/o the 3D. It's really not that hard (I have converted some small snippets myself) esp if the code base is not that big.

So now is your chance.. ;-)

@Hezkore

COOL ANIMATION DEMO! :D

I'm not sure if I should support "materials"

yea why not? at least make it experimental or optional?


Hezkore(Posted 2016) [#22]
@RustyKristi Thanks! :)

I've added some very basic glass material support.
It's hard to do transparency when the slices add to the overall alpha.
But things do become a bit see-through at least heh.
Not sure if I should add an example for it or just include it as sort of an unsupported feature.

I'm going to port the Slice loading part over to Monkey X (And Monkey 2 eventually) if anyone's interested.


AdamStrange(Posted 2016) [#23]
@Hezakore
Been tinkering with the core drawing routines to optimize a bit and improve the shadows.

The shadows now take an alpha from 0 to 1 and fade them out as they are drawn so you get nicer effects.

the drawing code has been tidied up and precalcs added and if's removed giving a speed boost

Here's the augmented code:
	Method DrawShadow(dX:Float, dY:Float, angleX:Float, angleY:Float, Frame:Int = 0)
		If Not _Shadow Then Return
		
		GetScale(_LastScale[0], _LastScale[1])
		dX = ScreenX(dX, GetRotation())
		dY = ScreenY(dY, GetRotation())
		brl.max2d.SetRotation(_Rotation + _LastRotation)
		
		Frame:Mod _ModelCount
		If Frame < 0 Then Frame:+_ModelCount
		
		local inAlpha:float = GetAlpha()
		local alpha:float = inAlpha
		local alphadiff:float = (1.0 / _MaxZ) * .5
		setcolor 0,0,0
		
		For Local i:Int = 0 Until _MaxZ
			setalpha alpha
			alpha :- alphadiff
			
			dX:+_LastScale[0] * angleX * i
			dY:+_LastScale[1] * angleY * i
			
			'Don't draw things we don't see!
			If dX < - _MaxX * _LastScale[0] Then dX:-_LastScale[0] * angleX * i;dY:-_LastScale[1] * angleY * i;Continue
			If dX > GraphicsWidth() + _MaxX * _LastScale[0] Then dX:-_LastScale[0] * angleX * i;dY:-_LastScale[1] * angleY * i;Continue
			If dY < - _MaxY * _LastScale[1] Then dX:-_LastScale[0] * angleX * i;dY:-_LastScale[1] * angleY * i;Continue
			If dY > GraphicsHeight() + _MaxY * _LastScale[1] Then dX:-_LastScale[0] * angleX * i;dY:-_LastScale[1] * angleY * i;Continue
			
			DrawImage(_Shadow, dX, dY, i + (_MaxZ * Frame))
			DrawImage(_Shadow, dX, dY - _LastScale[1] * 0.5, i + (_MaxZ * Frame))
			If i + 1 < _MaxZ Then DrawImage(_Shadow, dX, dY - _LastScale[1] * 1, i + (_MaxZ * Frame))
			
			dX:-_LastScale[0] * angleX * i
			dY:-_LastScale[1] * angleY * i
		Next
		
		SetAlpha inAlpha
		brl.max2d.SetRotation(_LastRotation)
	EndMethod


	
	Method Draw(dX:Float, dY:Float, Frame:Int = 0)
		If Not _Slices Then Return
		
		GetScale(_LastScale[0], _LastScale[1])
		dX = ScreenX(dX, GetRotation())
		dY = ScreenY(dY, GetRotation())
		brl.max2d.SetRotation(_Rotation + _LastRotation)
		
		Frame:Mod _ModelCount
		If Frame < 0 Then Frame:+_ModelCount
		
		local dxi:float
		local itilt:float

		local tiltdiff:float = _Tilt * .25
		local tilt:float = 0

		local dyi:float
		local iangle:float = 0

		local anglediff:float = _Angle * .25
		local angle:float = 0
		
		local _mframe:int = _MaxZ * Frame
		
		SetColorBaseShade()
		if _Quality then
			For Local i:Int = 0 Until _MaxZ
				'precalc
				dxi = dX - itilt * _LastScale[0]
				tilt = tiltdiff
				dyi = dY - iangle * _LastScale[0]
				angle = angleDiff
				
				'Don't draw things we don't see!
				If dX < - _MaxX * _LastScale[0] Then Continue
				If dX > GraphicsWidth() + _MaxX * _LastScale[0] Then Continue
				If dyi * _Angle < - _MaxY * _LastScale[1] Then Continue
				If dyi * _Angle > GraphicsHeight() + _MaxY * _LastScale[1] Then Continue
	
				'Draw the slice itself
				SetColorShade(i)
				DrawImage(_Slices, dxi - _LastScale[0] * tilt, dyi - _LastScale[0] * angle, _mframe)
				tilt :+ tiltdiff
				angle :+ anglediff
				
				'Draw more slices to fill in the gaps above
				SetColorShade(i + 0.25)
				DrawImage(_Slices, dxi - _LastScale[0] * tilt, dyi - _LastScale[0] * angle, _mframe)
				tilt :+ tiltdiff
				angle :+ anglediff
				
				SetColorShade(i + 0.5)
				DrawImage(_Slices, dxi - _LastScale[0] * tilt, dyi - _LastScale[0] * angle, _mframe)
				tilt :+ tiltdiff
				angle :+ anglediff
	
				SetColorShade(i + 0.75)
				DrawImage(_Slices, dxi - _LastScale[0] * tilt, dyi - _LastScale[0] * angle, _mframe)
	
				_mframe :+ 1
				itilt :+ _Tilt
				iangle :+ _Angle
			next
		else
			For Local i:Int = 0 Until _MaxZ
				'precalc
				dxi = dX - itilt * _LastScale[0]
				tilt = tiltdiff
				dyi = dY - iangle * _LastScale[0]
				angle = angleDiff
				
				'Don't draw things we don't see!
				If dX < - _MaxX * _LastScale[0] Then Continue
				If dX > GraphicsWidth() + _MaxX * _LastScale[0] Then Continue
				If dyi * _Angle < - _MaxY * _LastScale[1] Then Continue
				If dyi * _Angle > GraphicsHeight() + _MaxY * _LastScale[1] Then Continue
	
				'Draw the slice itself
				SetColorShade(i)
				DrawImage(_Slices, dxi - _LastScale[0] * tilt, dyi - _LastScale[0] * angle, _mframe)
				tilt :+ tiltdiff + tiltdiff
				angle :+ anglediff + anglediff
				
				'Draw more slices to fill in the gaps above
				SetColorShade(i + 0.5)
				DrawImage(_Slices, dxi - _LastScale[0] * tilt, dyi - _LastScale[0] * angle, _mframe)
	
				_mframe :+ 1
				itilt :+ _Tilt
				iangle :+ _Angle
			next
		end if

		If _Shade Then SetColor(_LastColor[0], _LastColor[1], _LastColor[2])
		brl.max2d.SetRotation(_LastRotation)
	EndMethod
	
	



Hezkore(Posted 2016) [#24]
@AdamStrange Cool stuff!
I've been meaning to add fading shadows, though I feel like that should be optional.
Have you measured the improved drawing time?
I'm not sure I like having two drawing methods but if it gives a speed boost I guess it's okay haha.


Steve Elliott(Posted 2016) [#25]
Yes, great work as always Adam...I'm sure I'm not the only one thinking, be great too see a game of his on Steam.


Hezkore(Posted 2016) [#26]
@AdamStrange Alright I've done some testing and it takes about 4ms (in debug mode) for me to render a frame of the start area from demo.bmx
With your code I get exactly the same 4ms render time.
Got an example of where the render time would be improved?


Hezkore(Posted 2016) [#27]
I've made a quick version of this for Monkey X.
It only loads sliced PNGs though.
You can find the module in my first post in this thread.

The BMax module can now save padded PNG slices which you should use with Monkey X.
I've also fixed some bugs.


AdamStrange(Posted 2016) [#28]
maybe do a stress test with very a large number?

what's a padded slice?


Hezkore(Posted 2016) [#29]
If you use SaveSlices with the Padded parameter set to true you'll get a one pixel border around each slice.


AdamStrange(Posted 2016) [#30]
aha - brilliant!

Just been working on a magicavoxel import for monkey2. needed to go back to the magic site and get the palette reference and file format.
Here's the initial result:

No 3d was harmed in this process

It is a full vexed editor as well working on vertical slices


Hezkore(Posted 2016) [#31]
Very nice!
That doesnt look like the slice hack though.
How are you rendering this?


Blitzplotter(Posted 2016) [#32]
oh my, this thread is VERY interesting, might get my blitz on tomorrow ;)


AdamStrange(Posted 2016) [#33]
it's a 2d voxel renderer.
based on the concepts here:\
http://www.blitzbasic.com/Community/posts.php?topic=106157#1305128

import magica voxel = no problem, custom palette = no problem. Oh wait...
bugger, color 0 is never changed load from 1 - fixed!


AdamStrange(Posted 2016) [#34]
ok, all stuff fixed and added support for up to 8 frames and improved the ui


the right ghosted version is the editor working on a vertical slice

I can add an exported to the png slice format next ;)


Hezkore(Posted 2016) [#35]
Nice work Adam!
Official editor for the module haha.
Something I'd really like and is missing from MagicaVoxel is the ability to edit several models at once, sort of in layers.
For example, I'm working on a game for a client and you play as a truck and the cargo you pull is a separate model, and as it is now, it's very hard to make the two models fit each other.
I had to make the model as one and then delete the truck and save as cargo, reload the model and remove the cargo and save the truck.


Steve Elliott(Posted 2016) [#36]
Great work guys. I do like the voxel look. Plenty of life left in 2d yet :)


AdamStrange(Posted 2016) [#37]
@hezakore it's not using anything from the module - its monkey2 code.
ok.
how about a feature that shows multiple frames at once?

currently the size is 50x50x25


AdamStrange(Posted 2016) [#38]
ok, latest
added ability to view more than one frame at a time. There are 8 available frames.

Here it is showing all four frames from the deer model



AdamStrange(Posted 2016) [#39]
I'm now working on reversing the process so I can import voxels from the saved slices


AdamStrange(Posted 2016) [#40]
sorted. Can now read in exported slices and construct the original voxel model with animation.

If the filename does not have the correct format name_XxYxZxF.png It will load the png as an image and construct the voxel frame from that.


Here you can see a simple image of a green monster thing has been imported and the voxels created. this should make editing more like a paint program.

I've also allowed for painting of existing voxels. so you can take an imported model and repaint the colors without adding new voxels


AdamStrange(Posted 2016) [#41]
Before I get into writing the slice exporter. here's a shot of the new pop-up menu showing the color choices, and quick tool selection


You can also see that new tools have appeared.
- pencil is draw a voxel in the current color
- brush is color any existing voxels with the current color
- eyedropper will pick the color of an existing voxel

the pop-up colors are darker and lighter (available) color variations of the current color


AdamStrange(Posted 2016) [#42]
after some weird little stuff Here's the first result loaded, exported and loaded as slices:


There was some issues with the way HezKore dealt with multiple frames. so unfortunately I have had to drop slice support in his original format.
The new format has the slices grouped in frames horizontally. this makes for very fast referencing with multiple frame exports.

The draw routines can be written as you want. I've implemented a quality setting of 1 to 4 as shown above. quality 1 just draws the individual slices, 2 draws 2, 3, 3 and 4 draws 4 slices


AdamStrange(Posted 2016) [#43]
And here with some shadows and a bit of artistic licence


this isn't a moving or blurred shot. it is a static image.

I suppose you could use it for a depth of field effect?


AdamStrange(Posted 2016) [#44]
Finally a quick app to show all the frames


and the slice data that created it:



Hezkore(Posted 2016) [#45]
That's cool and all AdamStrange but maybe you should start your own thread for the editor. :)
Especially now that the editor has nothing to do with my module or even uses the same slice format.
And regarding frames and animations, my format just tries to create a square picture and fit all the layers next to each other for each frame.
I've made some very detailed voxel models with a lot of frames and it's worked great.

In other news!
I'm using the Monkey X version of my module for 2 client projects.
I'm glad people got interested in this heh.


AdamStrange(Posted 2016) [#46]
good point.
What I will do is add support for import and export both formats.
Yours will be _XxYxZxF.png mine will be _XxYxZfF.png so they don't get in the way of each other :)