Difference between revisions of "Libs3D Mesh"
| Line 16: | Line 16: | ||
As seen above, first we set our application 3D: ''configureFrustum''. | As seen above, first we set our application 3D: ''configureFrustum''. | ||
| − | + | Then, the '''Mesh''' class is used to create and display custom constructed triangles (triangle meshes). | |
A triangle is: | A triangle is: | ||
Latest revision as of 05:30, 26 January 2026
I am 3D
First and foremost, for 3D, we set our application as such. There are many ways to tell our application be 3D:
- application configureFrustum
- Matrix orthographicProjection
- Matrix perspectiveProjection
- Viewport setProjection
Let's see some of those making our first steps in Gideros 3D.
Flat 3D
A Triangle
As seen above, first we set our application 3D: configureFrustum.
Then, the Mesh class is used to create and display custom constructed triangles (triangle meshes).
A triangle is:
- an array of 3 vertices with an x and y position: setVertexArray
- those 3 vertices connect to make a face (ClockWise): setIndexArray
- optionally coloring each vertex (color and alpha): setColorArray
The code:
application:setBackgroundColor(0x323232)
-- Configures the field of view (fov) and far clipping plane for 3D projection
application:configureFrustum(45, -100) -- (fov, farplane)
local mesh = Mesh.new()
mesh:setVertexArray(
50, 0, -- 1. vertex position
100, 100, -- 2. vertex position
0, 100 -- 3. vertex position
)
mesh:setIndexArray(
1,2,3 -- 1. triangle from 1, 2 and 3 vertex
)
mesh:setColorArray(
0xff0000,0.5, -- 1. vertex color and alpha
0x00ff00,0.7, -- 2. vertex color and alpha
0x0000ff,1.0 -- 3. vertex color and alpha
)
-- ok
mesh:setPosition(12*16, 8*16, -1*16)
stage:addChild(mesh)
mesh:addEventListener(Event.ENTER_FRAME, function(e)
mesh:setRotationX(mesh:getRotationX()+1)
mesh:setRotationY(mesh:getRotationY()+1)
mesh:setRotation(mesh:getRotation()+1)
end)
A Rectangle
Take the triangle code above, add one vertex and you have two triangles that make a rectangle.
application:setBackgroundColor(0x323232)
-- Configures the field of view (fov) and far clipping plane for 3D projection
application:configureFrustum(45, -100) -- (fov, farplane)
local mesh = Mesh.new()
mesh:setVertexArray(
0, 0, -- 1. vertex position
100, 0, -- 2. vertex position
100, 150, -- 3. vertex position
0, 150 -- -- 4. vertex position
)
mesh:setIndexArray(
1,2,3, -- 1. triangle from 1, 2 and 3 vertex
1,3,4 -- 2. triangle from 1, 3 and 4 vertex
)
mesh:setColorArray(
0xff0000,0.5, -- 1. vertex color and alpha
0x00ff00,0.7, -- 2. vertex color and alpha
0x0000ff,1.0, -- 3. vertex color and alpha
0xffff00,0 -- 4. vertex color and alpha
)
-- ok
mesh:setPosition(12*16, 8*16, -1*16)
stage:addChild(mesh)
mesh:addEventListener(Event.ENTER_FRAME, function(e)
mesh:setRotationX(mesh:getRotationX()+1)
mesh:setRotationY(mesh:getRotationY()+1)
mesh:setRotation(mesh:getRotation()+1)
end)
A Cube
In Gideros, a simple Sprite can be cube:
- we set the application 3D
- we create a local function to make the faces for our cube using Shape
- we add six faces (with appropriate rotation) to a parent Sprite
- and we are cube ;-)
application:setBackgroundColor(0x323232)
-- configure the field of view for 3D projection
application:configureFrustum(45) -- (fov, farplane)
-- create faces for a cube
local function face(color, rx, ry)
local s = Shape.new()
s:setFillStyle(Shape.SOLID, color, 0.7) -- translucent color
s:beginPath()
s:moveTo(-1,-1)
s:lineTo(-1,1)
s:lineTo(1,1)
s:lineTo(1,-1)
s:lineTo(-1,-1)
s:endPath()
-- orientation
s:setRotationX(rx)
s:setRotationY(ry)
-- offset position -1 along its local Z axis
s:setPosition(s:getMatrix():transformPoint(0, 0, -1))
return s
end
-- a cube with 6 faces
cube = Sprite.new()
cube:addChild(face(0xFF8080, 0,0)) -- color, rx, ry
cube:addChild(face(0xFFFF00, 90,0)) -- color, rx, ry
cube:addChild(face(0xFF00FF, -90,0)) -- color, rx, ry
cube:addChild(face(0x80FF80, 180,0)) -- color, rx, ry
cube:addChild(face(0x00FFFF, 0,90)) -- color, rx, ry
cube:addChild(face(0x8080FF, 0,-90)) -- color, rx, ry
-- enable depth sorting for translucency
cube:setAutoSort(true)
cube:setScale(2*16, 2*16, 2*16)
cube:setPosition(12*16, 8*16, 1*16)
stage:addChild(cube)
-- Rotate our cube
cube:addEventListener(Event.ENTER_FRAME,function(e)
cube:setRotationX(cube:getRotationX()+1)
cube:setRotation(cube:getRotation()+1.3)
end)
Let's get real
A 3D Plane
So far we managed to be 3D using only the x and y axes. Let's get real and add the z axis:
- Mesh.new(true)
Initializing a Mesh with true makes the mesh expect a Z coordinate in its vertex array. Now this is real 3D 😉.
The principles are the same as flat 3D but with an extra axis to deal with. Let's introduce Texturing as well.
The raw
application:setBackgroundColor(0x323232)
-- configure the field of view for 3D projection
application:configureFrustum(45) -- (fov, farplane)
local plane3d = Mesh.new(true) -- true = this mesh expects a Z coordinate in its vertex array
local w, h, d = 5*8, 1*8, 10*8 -- 3d plane dimensions
local va = { -- vertex array
-w,h,-d, -- 1. vertex position
w,h,-d, -- 2. vertex position
w,h,d, -- 3. vertex position
-w,h,d, -- 4. vertex position
-- -w,h-30,d, -- 4. vertex position, test another position
}
local ia = { -- index array
1,2,3, -- 1. triangle from 1, 2 and 3 vertex
1,3,4, -- 2. triangle from 1, 3 and 4 vertex
}
plane3d:setVertexArray(va)
plane3d:setIndexArray(ia)
-- texture it
local tex = Texture.new("gfx/Aurichalcite Deposit.jpg", false, { wrap=Texture.REPEAT, } )
local tw,th = tex:getWidth()*0.5, tex:getHeight()*1 -- 0.5 and 1 are the texture scale (uv)
plane3d:setTextureCoordinateArray {
0,0, -- 1. vertex texture coordinate
tw,0, -- 2. vertex texture coordinate
tw,th, -- 3. vertex texture coordinate
0,th, -- 4. vertex texture coordinate
}
plane3d:setTexture(tex)
-- why no color
plane3d:setColor(1, 0xffffff, 1)
plane3d:setColor(2, 0x5500ff, 1)
plane3d:setColor(3, 0x000000, 1)
plane3d:setColor(4, 0xaaff00, 1)
-- ok
plane3d:setPosition(12*16, 10*16, -1*16)
stage:addChild(plane3d)
-- rotate it
plane3d:addEventListener(Event.ENTER_FRAME,function(e)
plane3d:setRotationX(plane3d:getRotationX()+1)
plane3d:setRotation(plane3d:getRotation()+1.3)
end)
The Class
Almost the same as above but we are getting real here, aren't we?
local Plane3D = Core.class(Mesh)
function Plane3D:init(w,h,d)
w=w or 1 h=h or 1 d=d or 1
self._va = { -- vertex array
-w,h,-d,
w,h,-d,
w,h,d,
-w,h,d,
}
self._ia = { -- index array
1,2,3, 1,3,4,
}
self:setVertexArray(self._va)
self:setIndexArray(self._ia)
end
function Plane3D:mapTexture(texture,sw,sh)
local tw,th = texture:getWidth()*(sw or 1), texture:getHeight()*(sh or 1)
self:setTextureCoordinateArray {
0, 0,
tw, 0,
tw, th,
0, th,
}
self:setTexture(texture)
end
function Plane3D:mapColor(color,alpha)
for i = 1, #self._ia do
self:setColor(i, color, alpha or 1)
end
end
-- ***************************************
application:setBackgroundColor(0x323232)
-- configure the field of view for 3D projection
application:configureFrustum(45) -- (fov, farplane)
local planes3d = {
Plane3D.new(5*8, 1*8, 8*8), -- (w,h,d)
Plane3D.new(4*8, 1*8, 10*8), -- (w,h,d)
Plane3D.new(5*8, 1*8, 2*8), -- (w,h,d)
}
local tex = Texture.new("gfx/Aurichalcite Deposit.jpg", false, { wrap=Texture.REPEAT, } )
for i = 1, #planes3d do
planes3d[i]:mapTexture(tex, 0.5, 1) -- texture,sw,sh
planes3d[i]:mapColor(math.random(0xffffff), math.random()+0.1) -- color,alpha
planes3d[i]:setPosition(i*8*16, 10*16, -1*16)
stage:addChild(planes3d[i])
-- Rotate our 3D planes
planes3d[i]:addEventListener(Event.ENTER_FRAME,function(e)
planes3d[i]:setRotationX(planes3d[i]:getRotationX()+1)
planes3d[i]:setRotation(planes3d[i]:getRotation()+1.3)
end)
end
Cubez
Cuboid = Core.class(Mesh)
function Cuboid:init(w,h,d)
w=w or 1 h=h or 1 d=d or 1
self._va = { -- vertex array
-w,h,-d, w,h,-d, w,-h,-d, -w,-h,-d,
w,h,d, -w,h,d, -w,-h,d, w,-h,d,
-w,-h,-d, w,-h,-d, w,-h,d, -w,-h,d,
-w,h,d, w,h,d, w,h,-d, -w,h,-d,
-w,h,d, -w,h,-d, -w,-h,-d, -w,-h,d,
w,h,-d, w,h,d, w,-h,d, w,-h,-d,
}
self._ia = { -- index array
1,2,3,1,3,4,
5,6,7,5,7,8,
9,10,11,9,11,12,
13,14,15,13,15,16,
17,18,19,17,19,20,
21,22,23,21,23,24,
}
self:setVertexArray(self._va)
self:setIndexArray(self._ia)
end
function Cuboid:mapTexture(texture,sw,sh)
local tw, th = texture:getWidth()*(sw or 1), texture:getHeight()*(sh or 1)
self:setTextureCoordinateArray {
0,0,tw,0,tw,th,0,th,
0,0,tw,0,tw,th,0,th,
0,0,tw,0,tw,th,0,th,
0,0,tw,0,tw,th,0,th,
0,0,tw,0,tw,th,0,th,
0,0,tw,0,tw,th,0,th,
}
self:setTexture(texture)
end
function Cuboid:mapColor(color,alpha)
for i = 1, #self._ia do
self:setColor(i, color, alpha or 1)
end
end
-- ***************************************
application:setBackgroundColor(0x323232)
-- configure the field of view for 3D projection
application:configureFrustum(45) -- (fov, farplane)
local cubes = {
Cuboid.new(5*8, 10*8, 5*8), -- (w,h,d)
Cuboid.new(2*8, 2*8, 8*8), -- (w,h,d)
Cuboid.new(5*8, 5*8, 5*8), -- (w,h,d)
}
local tex = Texture.new("gfx/Aurichalcite Deposit.jpg", false, { wrap=Texture.REPEAT, } )
for i = 1, #cubes do
cubes[i]:mapTexture(tex, 0.5, 1) -- texture,sw,sh
cubes[i]:mapColor(math.random(0xffffff), 1) -- color,alpha
cubes[i]:setPosition(i*8*16, 10*16, -1*16)
stage:addChild(cubes[i])
-- Rotate
cubes[i]:addEventListener(Event.ENTER_FRAME,function(e)
cubes[i]:setRotationX(cubes[i]:getRotationX()+1)
cubes[i]:setRotation(cubes[i]:getRotation()+1.3)
end)
end
SphereZ
Doing 3D manually can quickly get ugly (unless you use a 3D software like Blender). Show me your spherez!
Sphere = Core.class(Mesh)
function Sphere:init(steps,radius)
local va, ia = {}, {}
local rs = (2*math.pi)/steps
local idx, ni = 4, 1
-- Vertices
va[1]=0 va[2]=radius va[3]=0
for iy = 1, (steps//2)-1 do
local y = math.cos(iy*rs)*radius
local r = math.sin(iy*rs)*radius
-- local y = math.cos(iy*rs)*radius -- test me
-- local r = math.cos(iy*rs)*radius -- test me
for ix = 0, steps do
local x = r*math.cos(ix*rs)
local z = r*math.sin(ix*rs)
va[idx]=x idx+=1
va[idx]=y idx+=1
va[idx]=z idx+=1
end
end
va[idx]=0 va[idx+1]=-radius va[idx+2]=0
local lvi = idx//3+1
-- Indices
-- a) top and bottom fans
for i = 1, steps do
ia[ni]=1 ni+=1 ia[ni]=i+1 ni+=1 ia[ni]=i+2 ni+=1
ia[ni]=lvi ni+=1 ia[ni]=lvi-i ni+=1 ia[ni]=lvi-i-1 ni+=1
end
-- b) quads
for iy = 1,(steps//2)-2 do
local b = 1+(steps+1)*(iy-1)
for ix = 1, steps do
ia[ni]=b+ix ni+=1 ia[ni]=b+ix+1 ni+=1 ia[ni]=b+ix+steps+1 ni+=1
ia[ni]=b+ix+steps+1 ni+=1 ia[ni]=b+ix+1 ni+=1 ia[ni]=b+ix+steps+2 ni+=1
end
end
self:setVertexArray(va)
self:setIndexArray(ia)
self._va=va self._ia=ia
self._steps=steps
end
function Sphere:mapTexture(texture,sw,sh)
local tw, th = texture:getWidth()*(sw or 1), texture:getHeight()*(sh or 1)
local va = {}
local i = 3
-- TexCoords
va[1]=tw/2 va[2]=0
for iy = 1, (self._steps//2)-1 do
local y = th*(iy*2/self._steps)
for ix = 0, self._steps do
local x = tw*(ix/self._steps)
va[i]=x i+=1
va[i]=y i+=1
end
end
va[i]=tw/2 va[i+1]=th
self:setTextureCoordinateArray(va)
self:setTexture(texture)
end
function Sphere:mapColor(color,alpha)
for i = 1, #self._ia do
self:setColor(i, color, alpha or 1)
end
end
-- ***************************************
application:setBackgroundColor(0x323232)
-- configure the field of view for 3D projection
application:configureFrustum(45) -- (fov, farplane)
local spheres = {
Sphere.new(3*8, 8*8), -- (steps,radius)
Sphere.new(1*8, 4*8), -- (steps,radius)
Sphere.new(2*8, 6*8), -- (steps,radius)
}
local tex = Texture.new("gfx/Aurichalcite Deposit.jpg", false, { wrap=Texture.REPEAT, } )
for i = 1, #spheres do
spheres[i]:mapTexture(tex, 0.2*8, 0.2*8) -- texture,sw,sh
spheres[i]:setPosition(i*8*16, 10*16, -1*16)
stage:addChild(spheres[i])
-- Rotate
spheres[i]:addEventListener(Event.ENTER_FRAME,function(e)
spheres[i]:setRotationX(spheres[i]:getRotationX()+1)
spheres[i]:setRotation(spheres[i]:getRotation()+1.3)
end)
end
Please stop
We can go on and on with all those shapes but there is an easier way...
Blender
The easiest way to build shapes is using Blender.
The steps:
- in Blender (or any 3D software), make a shape
- add the triangulate modifier to your shape and apply the modifier
- export your model as .obj
- open the .obj file using a text editor (I use Notepad++)
- copy the values for the vertex array (v)
- remove the v and replace the spaces with commas
- copy the values for the index array (f)
- remove the f and replace the / with commas
- that's it!
Let's see an example with a pyramid shape. The .obj file looks like this:
# Blender 5.0.1
# www.blender.org
mtllib pyramid.mtl
o Pyramid
v 0.000000 2.000000 0.000000
v 1.000000 0.000000 -1.000000
v 1.000000 0.000000 1.000000
v -1.000000 0.000000 -1.000000
v -1.000000 0.000000 1.000000
vn -0.0000 0.4472 0.8944
vn -0.8944 0.4472 -0.0000
vn -0.0000 -1.0000 -0.0000
vn 0.8944 0.4472 -0.0000
vn -0.0000 0.4472 -0.8944
vt -1.197056 2.182914
vt 0.500000 -1.890021
vt 2.197056 2.182914
vt 2.338880 2.067107
vt -1.442107 -1.713880
vt 2.338880 -1.713880
vt -1.442107 2.067106
s 0
usemtl SIDES
f 3/1/1 1/2/1 5/3/1
f 5/1/2 1/2/2 4/3/2
f 2/1/4 1/2/4 3/3/4
f 4/1/5 1/2/5 2/3/5
usemtl BASE
f 2/4/3 5/5/3 4/6/3
f 2/4/3 3/7/3 5/5/3
In Gideros you copy the vs in the obj_v table, remove the vs and add the commas:
local obj_v = { -- obj vertices position
0.000000, 2.000000, 0.000000,
1.000000, 0.000000, -1.000000,
1.000000, 0.000000, 1.000000,
-1.000000, 0.000000, -1.000000,
-1.000000, 0.000000, 1.000000,
}
We do the same with f. You copy the fs in the obj_f table, remove the fs, add the commas and comment the usemtl lines:
local obj_f = { -- .obj faces
--usemtl SIDES
3,1,1, 1,2,1, 5,3,1,
5,1,2, 1,2,2, 4,3,2,
2,1,4, 1,2,4, 3,3,4,
4,1,5, 1,2,5, 2,3,5,
--usemtl BASE
2,4,3, 5,5,3, 4,6,3,
2,4,3, 3,7,3, 5,5,3,
}
In the .obj format, the face index is the first values of 3, so we need to skip the values which are not index array (remember index array form the faces of the model). In the following example we need only the bold indices 3, 1, 5:
--usemtl SIDES
3,1,1, 1,2,1, 5,3,1,
In Gideros it's like this:
local _ia = {}
for i = 1, #obj_f, 3 do -- skip every values which are not face
_ia[#_ia+1] = obj_f[i]
end
That's it, we use almost the same code we did for the other shapes and we have this:
application:setBackgroundColor(0x323232)
-- Configures the field of view (fov) and far clipping plane for 3D projection
application:configureFrustum(45) -- (fov, farplane)
local mesh = Mesh.new(true)
local obj_v = { -- .obj vertices position
0,2,0,
1,0,-1,
1,0,1,
-1,0,-1,
-1,0,1,
}
local obj_f = { -- .obj faces
--usemtl SIDES
3,1,1, 1,2,1, 5,3,1,
5,1,2, 1,2,2, 4,3,2,
2,1,4, 1,2,4, 3,3,4,
4,1,5, 1,2,5, 2,3,5,
--usemtl BASE
2,4,3, 5,5,3, 4,6,3,
2,4,3, 3,7,3, 5,5,3,
}
local _ia = {}
for i = 1, #obj_f, 3 do -- skip every values which are not face
_ia[#_ia+1] = obj_f[i]
end
mesh:setVertexArray(obj_v)
mesh:setIndexArray(_ia)
-- color
for i = 1, #_ia do
mesh:setColor(i, math.random(0xffffff), 1.2)
end
-- ready?
mesh:setAutoSort(true)
mesh:setPosition(14*16, 12*16, -1*16)
mesh:setRotationX(190)
local scale = 4*16
mesh:setScale(scale, scale, scale)
stage:addChild(mesh)
mesh:addEventListener(Event.ENTER_FRAME, function(e)
mesh:setRotationX(mesh:getRotationX()+0.4)
mesh:setRotationY(mesh:getRotationY()+0.5)
end)
An example of a Torus but I won't show the code, it's too long for the wiki.
Please note: I used LibreOffice calc to help replace stuff around
I am out
Here are your first steps with Gideros and 3D.
You are strongly encouraged to mess with the code.
See ya!



