Hey all, thought I'd make this for all you people out there with little coding experience but want to make something cool.
Also really sorry for the huge ass wall of text, and I'm 400% camera shy so this will have to do for now. Also big props to Rinnegaramante for making this possible.
This tutorial can also be found on my blog as well.
In this first of possibly many tutorials, you’ll learn how to write a 3DS homebrew app that can load .obj files and display them on the 3DS screen in 3D! If you are like me and have little to no experience in coding c++ (which is the main language for coding homebrew applications on the 3DS), don’t worry! We will be taking advantage of a lua interpreter called LPP-3DS by gbatemp user Rinnegatamante to make it a hell of a lot easier to write our very own applications for the 3DS.
For this tutorial I am on a O3DS with home menu version 10.4.0-29E using themehax with HBL version 1.1.0.
1: Getting LPP-3DS and setting up your Lua IDE
2: Writing a base program
3: Creating the backbones of our render engine
4: Implementing the OBJ Loader
5: …
6: Profit?
There really isn’t much to setting up LPP-3DS. Head over to Rinnegatamante’s Github page to download the latest version of LPP-3DS. I used the nightly build from 1st Feb 2016 but if the latest is different and something breaks, please comment and I will fix up the tutorial.
Once you have downloaded the .3dsx and the .smdh files you can put them in a folder in the SDCARD:\3ds folder just like any other homebrew app. Simple as that.
For this tutorial I used SciTE which comes with the official Lua installer but notepad++ or even just regular notepad can be used.
If you try and open LPP-3DS now it will greet you with a screen that says:
“Error: index.lua file not found”
The way lpp (I’m just going to call it lpp from now on) works is it finds and runs index.lua in its directory, and obviously we don’t have that yet, so we need to make it.
Go ahead and plug your SD card into your computer and open up your chosen IDE and save a blank file as index.lua in the same directory as your lpp file on your SD card.
Now we need to write out our base program to get the ball rolling. Here is what our program will look like:
As you can see this is a little program that writes “Hello World” to the bottom screen using lpp’s included Screen functions, and exits back to the HBL when we press START. There are many more functions and a whole list of them are on the documentation for lpp.
If you now save this as your index.lua file and put it in your 3DS and run LPP-3DS, you should now see “Hello World” written on the bottom screen. You can experiment with changing the color or changing what text is displayed depending on which button is pressed if you would like to help get the hang of it.
So now we have our little program running on the 3DS, but it doesn’t really do that much at the moment.
Now the way lpp screen rendering works is you tell it to draw a line from (x1,y1) to (x2,y2) on the screen in a certain colour and (because we have 3D enabled) for a certain eye. Now when your eyes see things, the reason it looks 3D rather than flat is because our eyes are at slightly different points along the x axis (imagining x axis is flat on your face, y axis is directly up and down, and z axis is in and out of your face). Because however we only have access to 2D drawing methods (and thus no direct way to change the z position of objects), we need to create our own version to handle 3D rendering.
To create this faux sense of distance, points that are further away from the screen on the z axis look further apart when comparing right eye to left, than a point at z=0.
But before all of this, we need some way to store our points, which can then be used to draw lines between.
Here is the “class” we will be using to store our Vertex objects in:
If you were to paste this at the top of your existing program (above the enable3D line), you now have a way of storing vertices. To create a set of vertices, simply add between this function and the enable3D function call:
Calling vert1.x would return 0, vert2.y would return 20, and so on. Simple as that! Now to create our 3 dimensional drawing function. As i said above, objects that are further away look smaller, and are further apart compared to closer objects when we look at each eyes image side by side:
Placing this snippet above our existing program gives us two important functions, the round function and the drawPoint function. The round function was copied and pasted from http://lua-users.org/wiki/SimpleRound and does what it says on the box. The drawPoint function takes an x,y,z and draws a point for each eye, moving the point on the left eye left and the right eye right depending on the z axis and this stereoMultiplier variable. This would normally be set to sync with the 3D slider, but for this tutorial a value of 0.1 was fine for me and the reason for the round function is that the Screen.drawPixel needs integer values (because we can’t draw a pixel half way between two pixels, it doesn’t make sense) so for now we use the round function.
And now we can draw 3D Pixels to the screen . Just add:
in between the Screen.debugPrint() and Screen.flip() functions in our main loop and save and run. You should (if your 3D slider is turned on) see three red pixels in 3D space.
Note: If in the drawPixel function the overall x or y values are smaller or greater than the screen can actually display, you will get an “out of bounds” error. For the moment just make sure that your vertices are inside the screen, we will fix this later.
If you don’t see anything, or your program is spitting out errors, here is a pastebin of our program up to this step. http://pastebin.com/N3T7qZTQ
But we’re still not done yet!
So far,we have created a simple lua program, and added our basic rendering methods to it for rendering vertices in 3d space. But objects are more than just vertices, they have edges too. To render lines, LPP-3DS includes the Screen.drawLine function, which is similar to the Screen.drawPixel function, but has two points instead of one, simple enough right?
To draw a line in 3d space, we need to add this to the top of our program:
And then place this in your main program loop:
And after running this, you should see a dandy white line in 3d between vert1 and vert3.
Easy right?
Now, one thing I haven’t figured out is including libraries while using lpp-3ds. So after editing the Lua OBJ Loader from Karai17 to use lpp’s file loading and other fiddly bits, we have a long piece of text we can paste into the top of our program, which will enable our program to load OBJ files.
So copy and paste this into the top of your program
And now we have a way to load a .OBJ file into a variable. But, we still don’t have a way to convert the point cloud to lines. Now in OBJ models, each face is comprised of three points, and because some faces join at the same vertex, each vertex is stored in a list from 1 to the total vertices so there are not multiple entries of the same vertices, which saves on space. Each face contains the numbers on that list of which vertex is at each of its corners, ie. face 1 will have the vertices at position 5,7, and 12, and looking in the list of vertices, we can access the vertices at position 5, position 7, and position 12 in the vertex list, to find out the corresponding x, y, and z values.
And this is how we do it. (paste this somewhere in your program)
This function takes the object variable (which we will make in a sec), a position vertex, and a size modifier, in case the object is very small.
Now to create an object variable, add:
You need to put your model either in the folder that the index.lua is, or in a subfolder. If your model filename was “model.obj”, you would put loader.load(“/model.obj”) or loader.load(“/subfolder/model.obj”)(Remember the / part or else it won't load). You can do like I did and create a simple cube in Blender and export as an OBJ file, but make sure you select triangulate faces from the export menu or else your model will be missing half of its triangles.
Now, you can remove the
and the
Lines and add a new position vector somewhere near your main loop
And finally in your main loop, add this just above the Screen.flip() line to render your object:
Done! You can now load and display OBJ models in 3D on your 3DS .
If you are getting errors and you don’t know why, here is a copy of the completed file to check against http://pastebin.com/ADG21BRT (or if you can’t be bothered to do the tutorial)
Obviously this isn’t very optimised and displaying multiple high-poly models with movements and such will be very slow (esp. on the O3DS) so if anyone can find a way to make this faster please do let me know in the comments.
If you liked this tutorial, go check out my blog as I will be posting them there before posting them here (If you guys want more that is)
Thank you for reading this far, and good luck with your endevours
-PH
Also really sorry for the huge ass wall of text, and I'm 400% camera shy so this will have to do for now. Also big props to Rinnegaramante for making this possible.
This tutorial can also be found on my blog as well.
In this first of possibly many tutorials, you’ll learn how to write a 3DS homebrew app that can load .obj files and display them on the 3DS screen in 3D! If you are like me and have little to no experience in coding c++ (which is the main language for coding homebrew applications on the 3DS), don’t worry! We will be taking advantage of a lua interpreter called LPP-3DS by gbatemp user Rinnegatamante to make it a hell of a lot easier to write our very own applications for the 3DS.
For this tutorial I am on a O3DS with home menu version 10.4.0-29E using themehax with HBL version 1.1.0.
SECTIONS
1: Getting LPP-3DS and setting up your Lua IDE
2: Writing a base program
3: Creating the backbones of our render engine
4: Implementing the OBJ Loader
5: …
6: Profit?
#1 – GETTING LPP-3DS
AND
SETTING UP YOUR LUA IDE
AND
SETTING UP YOUR LUA IDE
There really isn’t much to setting up LPP-3DS. Head over to Rinnegatamante’s Github page to download the latest version of LPP-3DS. I used the nightly build from 1st Feb 2016 but if the latest is different and something breaks, please comment and I will fix up the tutorial.
Once you have downloaded the .3dsx and the .smdh files you can put them in a folder in the SDCARD:\3ds folder just like any other homebrew app. Simple as that.
For this tutorial I used SciTE which comes with the official Lua installer but notepad++ or even just regular notepad can be used.
#2 – WRITING A BASE PROGRAM
If you try and open LPP-3DS now it will greet you with a screen that says:
“Error: index.lua file not found”
The way lpp (I’m just going to call it lpp from now on) works is it finds and runs index.lua in its directory, and obviously we don’t have that yet, so we need to make it.
Go ahead and plug your SD card into your computer and open up your chosen IDE and save a blank file as index.lua in the same directory as your lpp file on your SD card.
Now we need to write out our base program to get the ball rolling. Here is what our program will look like:
Code:
Screen.enable3D() --Enabling 3D for the top screen
while(true) do
Screen.waitVblankStart() -- Screen related stuff
Screen.refresh() -- Other Screen related stuff
Screen.clear(TOP_SCREEN) -- Clear top screen
Screen.clear(BOTTOM_SCREEN) -- Clear bottom screen
pad = Controls.read() -- Read Controls
if(Controls.check(pad, KEY_START)) then-- check if start is pressed
System.exit()-- Exit back to HBL
end
Screen.debugPrint(0,0,"Hello World",Color.new(255,255,255),BOTTOM_SCREEN) -- Print onto the bottom screen at (0,0), "Hello World" in white
Screen.flip() -- More screen related stuff
end
As you can see this is a little program that writes “Hello World” to the bottom screen using lpp’s included Screen functions, and exits back to the HBL when we press START. There are many more functions and a whole list of them are on the documentation for lpp.
If you now save this as your index.lua file and put it in your 3DS and run LPP-3DS, you should now see “Hello World” written on the bottom screen. You can experiment with changing the color or changing what text is displayed depending on which button is pressed if you would like to help get the hang of it.
#3 – CREATING THE BACKBONES OF OUR RENDER ENGINE
So now we have our little program running on the 3DS, but it doesn’t really do that much at the moment.
Now the way lpp screen rendering works is you tell it to draw a line from (x1,y1) to (x2,y2) on the screen in a certain colour and (because we have 3D enabled) for a certain eye. Now when your eyes see things, the reason it looks 3D rather than flat is because our eyes are at slightly different points along the x axis (imagining x axis is flat on your face, y axis is directly up and down, and z axis is in and out of your face). Because however we only have access to 2D drawing methods (and thus no direct way to change the z position of objects), we need to create our own version to handle 3D rendering.
To create this faux sense of distance, points that are further away from the screen on the z axis look further apart when comparing right eye to left, than a point at z=0.
But before all of this, we need some way to store our points, which can then be used to draw lines between.
Here is the “class” we will be using to store our Vertex objects in:
Code:
Vertex = {};
function Vertex.new(x,y,z)
local self = {}
self.x = x
self.y = y
self.z = z
return self
end
If you were to paste this at the top of your existing program (above the enable3D line), you now have a way of storing vertices. To create a set of vertices, simply add between this function and the enable3D function call:
Code:
vert1 = Vertex.new(0,0,0)
vert2 = Vertex.new(20,20,20)
vert3 = Vertex.new(50,50,50)
Calling vert1.x would return 0, vert2.y would return 20, and so on. Simple as that! Now to create our 3 dimensional drawing function. As i said above, objects that are further away look smaller, and are further apart compared to closer objects when we look at each eyes image side by side:
Code:
function round(num, idp)
local mult = 10^(idp or 0)
return math.floor(num * mult + 0.5) / mult
end
stereoMultiplier = 0.1
function drawPoint(x,y,z)
Screen.drawPixel(round(x-z*stereoMultiplier ,0), round(y,0), Color.new(255,0,0), TOP_SCREEN, LEFT_EYE)
Screen.drawPixel(round(x+z*stereoMultiplier ,0), round(y,0), Color.new(255,0,0), TOP_SCREEN, RIGHT_EYE)
end
Placing this snippet above our existing program gives us two important functions, the round function and the drawPoint function. The round function was copied and pasted from http://lua-users.org/wiki/SimpleRound and does what it says on the box. The drawPoint function takes an x,y,z and draws a point for each eye, moving the point on the left eye left and the right eye right depending on the z axis and this stereoMultiplier variable. This would normally be set to sync with the 3D slider, but for this tutorial a value of 0.1 was fine for me and the reason for the round function is that the Screen.drawPixel needs integer values (because we can’t draw a pixel half way between two pixels, it doesn’t make sense) so for now we use the round function.
And now we can draw 3D Pixels to the screen . Just add:
Code:
drawPoint(vert1.x,vert1.y,vert1.z)
drawPoint(vert2.x,vert2.y,vert2.z)
drawPoint(vert3.x,vert3.y,vert3.z)
in between the Screen.debugPrint() and Screen.flip() functions in our main loop and save and run. You should (if your 3D slider is turned on) see three red pixels in 3D space.
Note: If in the drawPixel function the overall x or y values are smaller or greater than the screen can actually display, you will get an “out of bounds” error. For the moment just make sure that your vertices are inside the screen, we will fix this later.
If you don’t see anything, or your program is spitting out errors, here is a pastebin of our program up to this step. http://pastebin.com/N3T7qZTQ
But we’re still not done yet!
So far,we have created a simple lua program, and added our basic rendering methods to it for rendering vertices in 3d space. But objects are more than just vertices, they have edges too. To render lines, LPP-3DS includes the Screen.drawLine function, which is similar to the Screen.drawPixel function, but has two points instead of one, simple enough right?
To draw a line in 3d space, we need to add this to the top of our program:
Code:
function drawLine(vec1,vec2)
Screen.drawLine(round(vec1.x-vec1.z*stereoMultiplier,0), round(vec2.x-vec2.z*stereoMultiplier,0), round(vec1.y,0),round(vec2.y,0), Color.new(255,255,255),TOP_SCREEN, LEFT_EYE)
Screen.drawLine(round(vec1.x+vec1.z*stereoMultiplier,0), round(vec2.x+vec2.z*stereoMultiplier,0), round(vec1.y,0),round(vec2.y,0), Color.new(255,255,255),TOP_SCREEN, RIGHT_EYE)
end
And then place this in your main program loop:
Code:
drawLine(vert1,vert3)
And after running this, you should see a dandy white line in 3d between vert1 and vert3.
Easy right?
#4 - IMPLEMENTING THE OBJ LOADER
Now, one thing I haven’t figured out is including libraries while using lpp-3ds. So after editing the Lua OBJ Loader from Karai17 to use lpp’s file loading and other fiddly bits, we have a long piece of text we can paste into the top of our program, which will enable our program to load OBJ files.
So copy and paste this into the top of your program
Code:
-- OBJ LOADER LIBRARY --
local path ="."
local loader = {}
local fileString = ""
loader.version = "0.0.2"
function loader.load(file)
local lines = {}
fileStream = io.open(System.currentDirectory()..file,FREAD)
size = io.size(fileStream)
str = io.read(fileStream,0,size)
io.close(fileStream)
local x, a, b = 1;
while x < string.len(str) do
a, b = string.find(str, '.-\n', x);
if not a then
break;
else
if string.sub(str,a,b) then
table.insert(lines,string.sub(str,a,b))
end
end;
x = b + 1;
end;
return loader.parse(lines)
end
function loader.parse(object)
local obj = {
v = {}, -- List of vertices - x, y, z, [w]=1.0
vt = {}, -- Texture coordinates - u, v, [w]=0
vn = {}, -- Normals - x, y, z
vp = {}, -- Parameter space vertices - u, [v], [w]
f = {}, -- Faces
}
for _, line in ipairs(object) do
local l = string_split(line, "%s+")
if l[1] == "v" then
local v = {
x = tonumber(l[2]),
y = tonumber(l[3]),
z = tonumber(l[4]),
w = tonumber(l[5]) or 1.0
}
table.insert(obj.v, v)
elseif l[1] == "vt" then
local vt = {
u = tonumber(l[2]),
v = tonumber(l[3]),
w = tonumber(l[4]) or 0
}
table.insert(obj.vt, vt)
elseif l[1] == "vn" then
local vn = {
x = tonumber(l[2]),
y = tonumber(l[3]),
z = tonumber(l[4]),
}
table.insert(obj.vn, vn)
elseif l[1] == "vp" then
local vp = {
u = tonumber(l[2]),
v = tonumber(l[3]),
w = tonumber(l[4]),
}
table.insert(obj.vp, vp)
elseif l[1] == "f" then
local f = {}
for i=2, #l do
local split = string_split(l[i], "/")
local v = {}
v.v = tonumber(split[1])
if split[2] ~= "" then v.vt = tonumber(split[2]) end
v.vn = tonumber(split[3])
table.insert(f, v)
end
table.insert(obj.f, f)
end
end
return obj
end
-- http://wiki.interfaceware.com/534.html
function string_split(s, d)
local t = {}
local i = 0
local f
local match = '(.-)' .. d .. '()'
if string.find(s, d) == nil then
return {s}
end
for sub, j in string.gmatch(s, match) do
i = i + 1
t[i] = sub
f = j
end
if i ~= 0 then
t[i+1] = string.sub(s, f)
end
return t
end
And now we have a way to load a .OBJ file into a variable. But, we still don’t have a way to convert the point cloud to lines. Now in OBJ models, each face is comprised of three points, and because some faces join at the same vertex, each vertex is stored in a list from 1 to the total vertices so there are not multiple entries of the same vertices, which saves on space. Each face contains the numbers on that list of which vertex is at each of its corners, ie. face 1 will have the vertices at position 5,7, and 12, and looking in the list of vertices, we can access the vertices at position 5, position 7, and position 12 in the vertex list, to find out the corresponding x, y, and z values.
And this is how we do it. (paste this somewhere in your program)
Code:
function renderAsTris(obj,pos,size)
local xt = 1
local n = 1
while(obj.f[n]) do
n = n + 1 --Loops through all of the faces and counts them
end
local numfaces = n
while(xt < numfaces) do
local vertex1ind = obj.f[xt][1]["v"] -- Gets the number in the list of the current vertex
local vertex1 = Vertex.new(pos.x + size * obj.v[vertex1ind].x, pos.y + size * obj.v[vertex1ind].y, pos.z + size * obj.v[vertex1ind].z) -- creates vertex with position and size
local vertex2ind = obj.f[xt][2]["v"]
local vertex2 = Vertex.new(pos.x + size * obj.v[vertex2ind].x, pos.y + size * obj.v[vertex2ind].y, pos.z + size * obj.v[vertex2ind].z)
local vertex3ind = obj.f[xt][3]["v"]
local vertex3 = Vertex.new(pos.x + size * obj.v[vertex3ind].x, pos.y + size * obj.v[vertex3ind].y, pos.z + size * obj.v[vertex3ind].z)
drawLine(vertex1,vertex2) --Draws the triangle
drawLine(vertex2,vertex3)
drawLine(vertex3,vertex1)
xt = xt + 1
end
end
This function takes the object variable (which we will make in a sec), a position vertex, and a size modifier, in case the object is very small.
Now to create an object variable, add:
Code:
local OBJModel = loader.load("/model file name here")
You need to put your model either in the folder that the index.lua is, or in a subfolder. If your model filename was “model.obj”, you would put loader.load(“/model.obj”) or loader.load(“/subfolder/model.obj”)(Remember the / part or else it won't load). You can do like I did and create a simple cube in Blender and export as an OBJ file, but make sure you select triangulate faces from the export menu or else your model will be missing half of its triangles.
Now, you can remove the
Code:
vert1 = Vertex.new(0,0,0)
vert2 = Vertex.new(20,20,20)
vert3 = Vertex.new(50,50,50)
and the
Code:
drawPoint(vert1.x,vert1.y,vert1.z)
drawPoint(vert2.x,vert2.y,vert2.z)
drawPoint(vert3.x,vert3.y,vert3.z)
drawLine(vert1,vert3)
Lines and add a new position vector somewhere near your main loop
Code:
objPos = Vertex.new(120,50,0)
And finally in your main loop, add this just above the Screen.flip() line to render your object:
Code:
renderAsTris(OBJModel,objPos,50)
Done! You can now load and display OBJ models in 3D on your 3DS .
If you are getting errors and you don’t know why, here is a copy of the completed file to check against http://pastebin.com/ADG21BRT (or if you can’t be bothered to do the tutorial)
Obviously this isn’t very optimised and displaying multiple high-poly models with movements and such will be very slow (esp. on the O3DS) so if anyone can find a way to make this faster please do let me know in the comments.
If you liked this tutorial, go check out my blog as I will be posting them there before posting them here (If you guys want more that is)
Thank you for reading this far, and good luck with your endevours
-PH
Last edited by PrintHello,