Voxel - Visibility

Previous topic - Next topic

WPShadow

Hi,

I've got a little problem with the visibility of cubes. I work on a little experiment, where I try to make a big cube with 16 x 16 x 16 small cube.

Now I need only the cubes that the camera can see, everything else isn't necessary to render. The only problem that I have is to detect which cube is hidden.

I tried X_COLLISIONRAY but I've no idea how to use it right.

This is my code for now...

Code (glbasic) Select

// --------------------------------- //
// Project: voxel
// Start: Tuesday, October 01, 2013
// IDE Version: 11.322


// SETCURRENTDIR("Media") // go to media files

TYPE cube_type
typ
x
y
z
active
ENDTYPE

GLOBAL cube[] AS cube_type
GLOBAL counter, visible, size_total

main()



FUNCTION main:

CreateCube(0, 1, RGB(255, 0, 0))
CreateCube(1, 1, RGB(0, 255, 0))
CreateCube(2, 1, RGB(0, 0, 255))

size_total = 16

create_base_chunk(size_total)



WHILE TRUE

X_MAKE3D 1, 1000, 45



X_CAMERA 25, 25, 20, 0, 0, 0



visible = 0

draw_chunk(size_total)

X_MAKE2D

PRINT "counter: " + counter, 0, 0
PRINT "visible: " + visible, 0, 20



SHOWSCREEN

WEND


ENDFUNCTION

FUNCTION draw_chunk: size

FOREACH c IN cube[]

IF c.active = TRUE
INC visible, 1

X_MOVEMENT c.x - (size/2), c.z - (size/2), c.y - (size/2)
X_DRAWOBJ c.typ, 0
ENDIF

NEXT



ENDFUNCTION


FUNCTION create_base_chunk: size
LOCAL tmp_x, tmp_y, tmp_z
LOCAL c AS cube_type
LOCAL hit

FOR tmp_x = 0 TO size - 1
FOR tmp_y = 0 TO size - 1
FOR tmp_z = 0 TO size - 1

c.typ = RND(2)
c.x = tmp_x
c.y = tmp_y
c.z = tmp_z





c.active = TRUE





DIMPUSH cube[], c


INC counter, 1

NEXT
NEXT
NEXT



ENDFUNCTION




// ------------------------------------------------------------- //
// -=#  CREATECUBE  #=-
// ------------------------------------------------------------- //
FUNCTION CreateCube: num, sz, col
        // Diese Variablen sind als LOCAL definiert:
        // num, sz,
        sz=sz/2
        X_OBJSTART num
                // Front Face
                X_OBJADDVERTEX  sz, -sz,  sz, 1, 0, col
                X_OBJADDVERTEX -sz, -sz,  sz, 0, 0, col
                X_OBJADDVERTEX  sz,  sz,  sz, 1, 1, col
                X_OBJADDVERTEX -sz,  sz,  sz, 0, 1, col
                X_OBJNEWGROUP
                // Back Face
                X_OBJADDVERTEX -sz,  sz, -sz, 1, 1, col
                X_OBJADDVERTEX -sz, -sz, -sz, 1, 0, col
                X_OBJADDVERTEX  sz,  sz, -sz, 0, 1, col
                X_OBJADDVERTEX  sz, -sz, -sz, 0, 0, col
                X_OBJNEWGROUP
                // Top Face
                X_OBJADDVERTEX -sz,  sz,  sz, 0, 0, col
                X_OBJADDVERTEX -sz,  sz, -sz, 0, 1, col
                X_OBJADDVERTEX  sz,  sz,  sz, 1, 0, col
                X_OBJADDVERTEX  sz,  sz, -sz, 1, 1, col
                X_OBJNEWGROUP
                // Bottom Face
                X_OBJADDVERTEX  sz, -sz, -sz, 0, 1, col
                X_OBJADDVERTEX -sz, -sz, -sz, 1, 1, col
                X_OBJADDVERTEX  sz, -sz,  sz, 0, 0, col
                X_OBJADDVERTEX -sz, -sz,  sz, 1, 0, col
                X_OBJNEWGROUP
                // Right face
                X_OBJADDVERTEX  sz,  sz, -sz, 1, 1, col
                X_OBJADDVERTEX  sz, -sz, -sz, 1, 0, col
                X_OBJADDVERTEX  sz,  sz,  sz, 0, 1, col
                X_OBJADDVERTEX  sz, -sz,  sz, 0, 0, col
                X_OBJNEWGROUP
                // Left Face
                X_OBJADDVERTEX -sz, -sz,  sz, 1, 0, col
                X_OBJADDVERTEX -sz, -sz, -sz, 0, 0, co :ol
                X_OBJADDVERTEX -sz,  sz,  sz, 1, 1, col
                X_OBJADDVERTEX -sz,  sz, -sz, 0, 1, col
                X_OBJNEWGROUP
        X_OBJEND

ENDFUNCTION // sz


Any idea how I can implement it right?

CU

W.
AMD X2 4600, 2 GB Ram, ATI X1950 XTX, XP PRO SP2: GLB Premium 10.beta_dingsi, <(´.´<) Kirby Dance (>`.`)>
http://lostrevenant.blogspot.com
alea iacta est

WPShadow

It is more an experiment than a real project. I try to find out, how I can program it by myself.  =D

QuoteAnother question: with assembled voxel objects, shouldn't we only be interested in the outside layer?  That would reduce the amount of data sent to the graphics card enormously...

I know, only to draw the "outside" of the box will reduce the whole thing.

I tried it like this:

Code (glbasic) Select
FUNCTION create_base_chunk: size
LOCAL tmp_x, tmp_y, tmp_z
LOCAL c AS cube_type
LOCAL hit

FOR tmp_x = 0 TO size - 1
FOR tmp_y = 0 TO size - 1
FOR tmp_z = 0 TO size - 1

c.typ = RND(2)
c.x = tmp_x
c.y = tmp_y
c.z = tmp_z




IF c.x = 0 OR c.x = size - 1 OR c.y = 0 OR c.y = size - 1 OR c.z = 0 OR c.z = size - 1
c.active = TRUE
ELSE
c.active = FALSE
ENDIF





DIMPUSH cube[], c


INC anzahl, 1

NEXT
NEXT
NEXT



ENDFUNCTION



But shouldn't it be possible to reduce it more than that? With this I reduced it from 4096 to 1352, but the camera cannot see the backside, so that cubes are not necessary.
AMD X2 4600, 2 GB Ram, ATI X1950 XTX, XP PRO SP2: GLB Premium 10.beta_dingsi, <(´.´<) Kirby Dance (>`.`)>
http://lostrevenant.blogspot.com
alea iacta est

Slydog

#2
In my 3D maze game, each level is dynamically shaped, and not just a simple cube.
The outside walls enclosing the maze can be any shape, so I created a routine that detects what is around each wall segment to determine if a wall face should be drawn or not (and facing which way).  (See photo to understand what I'm referring to).  It dynamically creates a final 3d wall model containing only the quads it requires.

This algorithm is almost exactly what you would want for mindcraft style cube drawing with face exclusion.
Here's the main loop for x, y, z dimensions:

Code (glbasic) Select
// Create 'walls' model
FOR y = 0 TO sy
FOR z = 0 TO sz
FOR x = 0 TO sx
IF level.map[y][z][x].node = MAP_MT
xPos = x * gridSizeXZ
yPos = y * gridSizeY
zPos = z * gridSizeXZ

IF (x = 0)  OR (x > 0  AND level.map[y][z][x-1].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_W], xPos, yPos, zPos, walls) // Draw Wall West?
IF (x = sx) OR (x < sx AND level.map[y][z][x+1].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_E], xPos, yPos, zPos, walls) // Draw Wall East?
IF (z = 0)  OR (z > 0  AND level.map[y][z-1][x].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_N], xPos, yPos, zPos, walls) // Draw Wall South?
IF (z = sz) OR (z < sz AND level.map[y][z+1][x].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_S], xPos, yPos, zPos, walls) // Draw Wall North?
IF (y = 0)  OR (y > 0  AND level.map[y-1][z][x].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_B], xPos, yPos, zPos, walls) // Draw Wall Bottom?
IF (y = sy) OR (y < sy AND level.map[y+1][z][x].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_T], xPos, yPos, zPos, walls) // Draw Wall Top?
ENDIF
NEXT
NEXT
NEXT


The 3d array contains simple ints (but I only use the first 8 bits (a byte)).
The constant MAP_MT means that array position is within the maze area, and MAP_NL means a null space meaning it is outside of the shape of the maze area.

It is therefore just checking which array positions are separating the solid from the empty, and draws only the correct wall faces it needs.

If you want to see how this is used and initialized, here's the entire (?) code for my wall detection, and the final result is one large wall model with only the front facing wall faces displayed:
(It references custom types I created to simplify the code)

Code (glbasic) Select
GLOBAL sp_walls% = 777 // Wall Sprite Texture ID

FUNCTION Walls_Init:
LOCAL V_TNL% = 0 // Vertice Top North Left
LOCAL V_TNR% = 1 // Vertice Top North Right
LOCAL V_TSL% = 2 // Vertice Top South Left
LOCAL V_TSR% = 3 // Vertice Top South Right
LOCAL V_BNL% = 4 // Vertice Bottom North Left
LOCAL V_BNR% = 5 // Vertice Bottom North Right
LOCAL V_BSL% = 6 // Vertice Bottom South Left
LOCAL V_BSR% = 7 // Vertice Bottom South Right

LOCAL Q_N% = 0 // Quad North
LOCAL Q_S% = 1 // Quad South
LOCAL Q_W% = 2 // Quad West
LOCAL Q_E% = 3 // Quad East
LOCAL Q_T% = 4 // Quad Top
LOCAL Q_B% = 5 // Quad Bottom

LOCAL x%,y%,z%,  sx%,sy%,sz%
LOCAL vx[] AS TVertice
LOCAL quads[] AS TQuad
LOCAL is_even%
LOCAL xPos#, yPos#, zPos#

LOCAL uv1 AS TUv
LOCAL uv2 AS TUv
uv1.Set(0, 0)
uv2.Set(1, 1)

DIM vx[8]
DIM quads[6]

// Initialize level
level = _worlds[_world_ix].levels[_level_ix]
sx = level.size.x - 1
sy = level.size.y - 1
sz = level.size.z - 1

ALIAS walls AS _worlds[_world_ix].walls

level.Create()

en_walls.mesh.Clear()
// Set up base vertices
//     o-----o
//    /|    /|
//   / |   / |
//  o-----o  o
//  | /   | /
//  |/    |/
//  0-----o

vx[V_TNL].Set(         0, gridSizeY,          0, RGB(255,255,255))
vx[V_TNR].Set(gridSizeXZ, gridSizeY,          0, RGB(255,255,255))
vx[V_TSL].Set(         0, gridSizeY, gridSizeXZ, RGB(255,255,255))
vx[V_TSR].Set(gridSizeXZ, gridSizeY, gridSizeXZ, RGB(255,255,255))
vx[V_BNL].Set(         0,         0,          0, RGB(255,255,255))
vx[V_BNR].Set(gridSizeXZ,         0,          0, RGB(255,255,255))
vx[V_BSL].Set(         0,         0, gridSizeXZ, RGB(255,255,255))
vx[V_BSR].Set(gridSizeXZ,         0, gridSizeXZ, RGB(255,255,255))

quads[Q_N].Set(vx[V_TNL], vx[V_TNR], vx[V_BNR], vx[V_BNL], uv1, uv2)
quads[Q_S].Set(vx[V_TSR], vx[V_TSL], vx[V_BSL], vx[V_BSR], uv1, uv2)
quads[Q_W].Set(vx[V_TSL], vx[V_TNL], vx[V_BNL], vx[V_BSL], uv1, uv2)
quads[Q_E].Set(vx[V_TNR], vx[V_TSR], vx[V_BSR], vx[V_BNR], uv1, uv2)
quads[Q_T].Set(vx[V_TSL], vx[V_TSR], vx[V_TNR], vx[V_TNL], uv1, uv2)
quads[Q_B].Set(vx[V_BNL], vx[V_BNR], vx[V_BSR], vx[V_BSL], uv1, uv2)

// Create 'walls' model
FOR y = 0 TO sy
FOR z = 0 TO sz
FOR x = 0 TO sx
IF level.map[y][z][x].node = MAP_MT
xPos = x * gridSizeXZ
yPos = y * gridSizeY
zPos = z * gridSizeXZ

IF (x = 0)  OR (x > 0  AND level.map[y][z][x-1].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_W], xPos, yPos, zPos, walls) // Draw Wall West?
IF (x = sx) OR (x < sx AND level.map[y][z][x+1].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_E], xPos, yPos, zPos, walls) // Draw Wall East?
IF (z = 0)  OR (z > 0  AND level.map[y][z-1][x].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_N], xPos, yPos, zPos, walls) // Draw Wall South?
IF (z = sz) OR (z < sz AND level.map[y][z+1][x].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_S], xPos, yPos, zPos, walls) // Draw Wall North?
IF (y = 0)  OR (y > 0  AND level.map[y-1][z][x].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_B], xPos, yPos, zPos, walls) // Draw Wall Bottom?
IF (y = sy) OR (y < sy AND level.map[y+1][z][x].node = MAP_NL) THEN Walls_QuadAdd(quads[Q_T], xPos, yPos, zPos, walls) // Draw Wall Top?
ENDIF
NEXT
NEXT
NEXT

en_walls.mesh.Generate()
ENDFUNCTION

// Offset the 'local' quad into world space (ie, move the quad to 'x,y,z')
FUNCTION Walls_QuadAdd: quad AS TQuad, x#, y#, z#, walls AS TWalls
LOCAL quad_offset AS TQuad
quad_offset = quad.Copy()
quad_offset.Adjust(x-(gridSizeXZ/2.0), y-(gridSizeY/2.0), z-(gridSizeXZ/2.0))
en_walls.mesh.QuadAdd(quad_offset)
ENDFUNCTION


This cross maze preview may make things clear (excuse the unfinished UI!).  Back faces aren't being displayed to allow you to see the maze while previewing (the maze is slowly spinning around so you can see it from all angles).  The walls are shaped into a cross and only the required faces are added.
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Slydog

#3
If it helps, here's my dynamic mesh library I use to make creating custom meshes simpler. (Only quads are possible to add to a mesh for now).  I hope it is self explanatory, with the above code showing its usage.

Code (glbasic) Select
CONSTANT CULLMODE_BOTH% = 0
CONSTANT CULLMODE_FRONT% = 1
CONSTANT CULLMODE_BACK% = -1

//-------------------------------------------------------------------------- U V
TYPE TUv
u#
v#

FUNCTION Set: u#, v#
self.u = u
self.v = v
ENDFUNCTION
ENDTYPE

//---------------------------------------------------------------- V e r t i c e
TYPE TVertice
x
y
z
colour%
uv AS TUv

FUNCTION Set: x, y, z, colour%, u=0.0, v=0.0
self.x = x
self.y = y
self.z = z
self.colour = colour
self.uv.Set(u, v)
ENDFUNCTION

FUNCTION Clear%:
self.x = -9999
self.y = -9999
self.z = -9999
self.colour = -9999
self.uv.Set(0.0, 0.0)
ENDFUNCTION

FUNCTION Adjust%: x#, y#, z#
INC self.x, x
INC self.y, y
INC self.z, z
ENDFUNCTION

ENDTYPE

//---------------------------------------------------------------------- Q u a d
TYPE TQuad
v1 AS TVertice
v2 AS TVertice
v3 AS TVertice
v4 AS TVertice

FUNCTION Clear%:
self.v1.Clear()
self.v2.Clear()
self.v3.Clear()
self.v4.Clear()
ENDFUNCTION

FUNCTION Set%: v1 AS TVertice, v2 AS TVertice, v3 AS TVertice, v4 AS TVertice, uv1 AS TUv, uv2 AS TUv, colour%=-9999
self.v1 = v1
self.v2 = v2
self.v3 = v3
self.v4 = v4
self.SetUv(uv1, uv2)
IF (colour <> -9999) THEN self.ColourSet(colour)
ENDFUNCTION

FUNCTION Adjust%: x#, y#, z#
self.v1.Adjust(x, y, z)
self.v2.Adjust(x, y, z)
self.v3.Adjust(x, y, z)
self.v4.Adjust(x, y, z)
ENDFUNCTION

FUNCTION Copy AS TQuad:
LOCAL dest AS TQuad
dest.Clear()
dest.v1 = self.v1
dest.v2 = self.v2
dest.v3 = self.v3
dest.v4 = self.v4
RETURN dest
ENDFUNCTION

FUNCTION SetUv%: uv1 AS TUv, uv2 AS TUv
self.v1.uv.u = uv1.u; self.v1.uv.v = uv1.v
self.v2.uv.u = uv2.u; self.v2.uv.v = uv1.v
self.v3.uv.u = uv2.u; self.v3.uv.v = uv2.v
self.v4.uv.u = uv1.u; self.v4.uv.v = uv2.v
ENDFUNCTION

FUNCTION ColourSet%: colour%
self.v1.colour = colour
self.v2.colour = colour
self.v3.colour = colour
self.v4.colour = colour
ENDFUNCTION

ENDTYPE



//---------------------------------------------------------------------- M e s h
TYPE TMesh
quads[] AS TQuad

// Must call this before using. It clears any previous quads, plus sets the model id used for displaying this 3d model.
FUNCTION Clear:
IF self.id < 0 THEN self.id = Mesh_CreateID()
DIM self.quads[0]
ENDFUNCTION

//     5-----4
//    /| .  /|
//   / |   /.|
//  0-----1  7
//  | /.  | /
//  |/    |/
//  3-----2
FUNCTION NewCube: width, height, depth, colour%, uv1 AS TUv, uv2 AS TUv
LOCAL w, h, d
LOCAL vx[] AS TVertice

DIM vx[8]
w = width / 2.0
h = height / 2.0
d = depth / 2.0

vx[0].Set(-w, h, d, colour) // FTL
vx[1].Set( w, h, d, colour) // FTR
vx[2].Set( w,-h, d, colour) // FBR
vx[3].Set(-w,-h, d, colour) // FBL
vx[4].Set( w, h,-d, colour) // BTR
vx[5].Set(-w, h,-d, colour) // BTL
vx[6].Set(-w,-h,-d, colour) // BBL
vx[7].Set( w,-h,-d, colour) // BBR

self.QuadAddV4(vx[0], vx[1], vx[2], vx[3], uv1, uv2) // Front
self.QuadAddV4(vx[5], vx[0], vx[3], vx[6], uv1, uv2) // Left
self.QuadAddV4(vx[1], vx[4], vx[7], vx[2], uv1, uv2) // Right
self.QuadAddV4(vx[5], vx[4], vx[1], vx[0], uv1, uv2) // Top
self.QuadAddV4(vx[3], vx[2], vx[7], vx[6], uv1, uv2) // Bottom
self.QuadAddV4(vx[4], vx[5], vx[6], vx[7], uv1, uv2) // Back

self.Generate()

DIM vx[0]
ENDFUNCTION

FUNCTION QuadAdd%: quad AS TQuad
DIMPUSH self.quads[], quad
ENDFUNCTION

FUNCTION QuadAddV4%: v1 AS TVertice, v2 AS TVertice, v3 AS TVertice, v4 AS TVertice, uv1 AS TUv, uv2 AS TUv
LOCAL quad AS TQuad
quad.Set(v1, v2, v3, v4, uv1, uv2)
DIMPUSH self.quads[], quad
ENDFUNCTION

// Call this when all the quads have been set.  This will add the actual vertices to the model.
FUNCTION Generate%:
LOCAL qx%
LOCAL qty%
qty = LEN(self.quads[]) - 1
X_OBJSTART self.id
FOR qx = 0 TO qty
self.QuadGenerate(qx)
IF qx < qty THEN X_OBJNEWGROUP
NEXT
X_OBJEND
ENDFUNCTION

FUNCTION QuadGenerate: qx%, cullmode%=CULLMODE_FRONT
ALIAS quad AS self.quads[qx]
IF (cullmode = CULLMODE_BACK) OR (cullmode = CULLMODE_BOTH)
X_OBJADDVERTEX quad.v1.x, quad.v1.y, quad.v1.z, quad.v1.uv.u, quad.v1.uv.v, quad.v1.colour
X_OBJADDVERTEX quad.v4.x, quad.v4.y, quad.v4.z, quad.v4.uv.u, quad.v4.uv.v, quad.v4.colour
X_OBJADDVERTEX quad.v2.x, quad.v2.y, quad.v2.z, quad.v2.uv.u, quad.v2.uv.v, quad.v2.colour
X_OBJADDVERTEX quad.v3.x, quad.v3.y, quad.v3.z, quad.v3.uv.u, quad.v3.uv.v, quad.v3.colour
ENDIF
IF (cullmode = CULLMODE_FRONT) OR (cullmode = CULLMODE_BOTH)
quad.Adjust(0, -0.0001, 0)
X_OBJADDVERTEX quad.v2.x, quad.v2.y, quad.v2.z, quad.v2.uv.u, quad.v2.uv.v, quad.v2.colour
X_OBJADDVERTEX quad.v3.x, quad.v3.y, quad.v3.z, quad.v3.uv.u, quad.v3.uv.v, quad.v3.colour
X_OBJADDVERTEX quad.v1.x, quad.v1.y, quad.v1.z, quad.v1.uv.u, quad.v1.uv.v, quad.v1.colour
X_OBJADDVERTEX quad.v4.x, quad.v4.y, quad.v4.z, quad.v4.uv.u, quad.v4.uv.v, quad.v4.colour
ENDIF
RETURN
ENDFUNCTION

ENDTYPE

FUNCTION Mesh_CreateID%:
STATIC id% = 1000 // Leave room for other models loaded from a file
INC id
RETURN id
ENDFUNCTION

My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Slydog

#4
I went through the code and attempted to create the exact logic you would need in your situation.  Not tested so expect a bug or two.  And, it uses the above dynamic mesh library.

Code (glbasic) Select
CONSTANT CUBE_AIR = 0
CONSTANT CUBE_DIRT = 1

GLOBAL walls AS TMesh
GLOBAL cubes[] // A 3 dimensional array with integers where 0 is empty (CUBE_AIR), and 1 is where a dirt cube is located (CUBE_DIRT).  For this code, it is assumed to be already dimensioned and filled with data.

FUNCTION Walls_Init:
        LOCAL V_TNL% = 0        // Vertice Top North Left
        LOCAL V_TNR% = 1        // Vertice Top North Right
        LOCAL V_TSL% = 2        // Vertice Top South Left
        LOCAL V_TSR% = 3        // Vertice Top South Right
        LOCAL V_BNL% = 4        // Vertice Bottom North Left
        LOCAL V_BNR% = 5        // Vertice Bottom North Right
        LOCAL V_BSL% = 6        // Vertice Bottom South Left
        LOCAL V_BSR% = 7        // Vertice Bottom South Right

        LOCAL Q_N% = 0          // Quad North
        LOCAL Q_S% = 1          // Quad South
        LOCAL Q_W% = 2          // Quad West
        LOCAL Q_E% = 3          // Quad East
        LOCAL Q_T% = 4          // Quad Top
        LOCAL Q_B% = 5          // Quad Bottom

        LOCAL x%,y%,z%,  sx%,sy%,sz%
        LOCAL vx[]              AS TVertice
        LOCAL quads[]   AS TQuad

        LOCAL uv1 AS TUv
        LOCAL uv2 AS TUv
        uv1.Set(0, 0)
        uv2.Set(1, 1)

        DIM vx[8]
        DIM quads[6]

sx = BOUNDS(cubes[], 0) - 1 // World width
sy = BOUNDS(cubes[], 1) - 1 // World height
sz = BOUNDS(cubes[], 2) - 1 // World depth

        walls.Clear()

        // Set up base vertices
        //     o-----o
        //    /|    /|
        //   / |   / |
        //  o-----o  o
        //  | /   | /
        //  |/    |/
        //  0-----o

// Assuming a cube size of 1,1,1
        vx[V_TNL].Set(0, 1, 0, RGB(255,255,255))
        vx[V_TNR].Set(1, 1, 0, RGB(255,255,255))
        vx[V_TSL].Set(0, 1, 1, RGB(255,255,255))
        vx[V_TSR].Set(1, 1, 1, RGB(255,255,255))
        vx[V_BNL].Set(0, 0, 0, RGB(255,255,255))
        vx[V_BNR].Set(1, 0, 0, RGB(255,255,255))
        vx[V_BSL].Set(0, 0, 1, RGB(255,255,255))
        vx[V_BSR].Set(1, 0, 1, RGB(255,255,255))

        quads[Q_N].Set(vx[V_TNL], vx[V_TNR], vx[V_BNR], vx[V_BNL], uv1, uv2)
        quads[Q_S].Set(vx[V_TSR], vx[V_TSL], vx[V_BSL], vx[V_BSR], uv1, uv2)
        quads[Q_W].Set(vx[V_TSL], vx[V_TNL], vx[V_BNL], vx[V_BSL], uv1, uv2)
        quads[Q_E].Set(vx[V_TNR], vx[V_TSR], vx[V_BSR], vx[V_BNR], uv1, uv2)
        quads[Q_T].Set(vx[V_TSL], vx[V_TSR], vx[V_TNR], vx[V_TNL], uv1, uv2)
        quads[Q_B].Set(vx[V_BNL], vx[V_BNR], vx[V_BSR], vx[V_BSL], uv1, uv2)

        // Create 'walls' model
FOR x = 0 TO sx
FOR y = 0 TO sy
                FOR z = 0 TO sz
IF cubes[x][y][z] <> CUBE_AIR // Evaluate all non-air cubes
IF (x = 0)  OR (x > 0   AND cubes[x-1][y][z] = CUBE_AIR) THEN Walls_QuadAdd(quads[Q_W], x,y,z, walls) // Draw Wall West?
IF (x = sx) OR (x < sx  AND cubes[x+1][y][z] = CUBE_AIR) THEN Walls_QuadAdd(quads[Q_E], x,y,z, walls) // Draw Wall East?
IF (z = 0)  OR (z > 0   AND cubes[x][y][z-1] = CUBE_AIR) THEN Walls_QuadAdd(quads[Q_N], x,y,z, walls) // Draw Wall South?
IF (z = sz) OR (z < sz  AND cubes[x][y][z+1] = CUBE_AIR) THEN Walls_QuadAdd(quads[Q_S], x,y,z, walls) // Draw Wall North?
IF (y = 0)  OR (y > 0   AND cubes[x][y-1][z] = CUBE_AIR) THEN Walls_QuadAdd(quads[Q_B], x,y,z, walls) // Draw Wall Bottom?
IF (y = sy) OR (y < sy  AND cubes[x][y+1][z] = CUBE_AIR) THEN Walls_QuadAdd(quads[Q_T], x,y,z, walls) // Draw Wall Top?
ENDIF
NEXT
NEXT
NEXT

        walls.Generate()  // Now you can display this model using 'walls.id'.
ENDFUNCTION

// Offset the 'local' quad into world space (ie: move the quad to 'x,y,z')
FUNCTION Walls_QuadAdd: quad AS TQuad, x#, y#, z#, walls AS TMesh
        LOCAL quad_offset AS TQuad
        quad_offset = quad.Copy()
        quad_offset.Adjust(x-0.5, y-0.5, z-0.5)
        walls.QuadAdd(quad_offset)
ENDFUNCTION


[Edit] Various changes to improve code
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

mentalthink

I leave some days ago in some part of the forum (sorry I don't know where is, (conversation with Bigsofty Erico and me)), and explains how do a Voxel Engine from Zero... It's C++, but practically all can be traslate without too much problems (I think  :P)

About X_ray coll in Examples folder you have a simple Sample, take a lool it's easy to use, I have a similar code in my 3D Lighting Editor, not is complex to use.

I give you this idea, I'm not sure if works, but perhaps runs, from the camera launch some rays to the target of the camera, in example a matrix of 16 rays, separatex X units, then when you detect some cube draw it, the others you have to hide...

erico

It all looks incredibly sofisticated! Congratulations, love the screen shot! :good:

Slydog

#7
I made quite a few changes to my latest post above to fix things, or simplify things.

If you wanted different cube types, such as grass, rock, dirt, it shouldn't be too difficult to adapt the code.
You would need a texture atlas (sprite sheet) that contains all of the faces of each cube (well, a cube has the four side faces that are the same, perhaps five if you make the bottom the same as the sides, plus a custom top image).  You would need some kind of lookup to find the desired uv locations for each face for each cube.  Then lookup those uvs inside the nested FOR loop, depending on which cube type your are evaluating.

[Edit] Then you would want to implement a chunking system, to break the world model into smaller model units, perhaps 8x8x8, to help with occuling and processing in open gl.
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

WPShadow

Hey,

thanks for the examples, they are really great. It will take a little time to analyse my current code and see how I can use it with your examples.

But I will reply as fast as I can... (I think arround the weekend)

CU

W.

AMD X2 4600, 2 GB Ram, ATI X1950 XTX, XP PRO SP2: GLB Premium 10.beta_dingsi, <(´.´<) Kirby Dance (>`.`)>
http://lostrevenant.blogspot.com
alea iacta est

Slydog

Cool, can't wait to see what you come up with! 
I wanted to try a voxel like render engine for some time now, but never got around to it.
Oh, and just thinking about the code I posted, it may render the cubes inside-out (as my game you were inside the walls).  You would just reverse the vertice winding direction to get the quads to face the other way.
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Schranz0r

Code from me and WP yesterday:

Code (glbasic) Select
// --------------------------------- //
// Project: RL Test Chunks
// Start: Tuesday, October 01, 2013
// IDE Version: 11.414

GLOBAL CHUNKSIZE = 12
GLOBAL CUBESIZE = 1


GLOBAL CUBE_TOP = 1
GLOBAL CUBE_BOTTOM = 2
GLOBAL CUBE_LEFT = 4
GLOBAL CUBE_RIGHT = 8
GLOBAL CUBE_FRONT = 16
GLOBAL CUBE_BACK = 32

GLOBAL Counter = 0

TYPE TCube
typ% // 0 = Air, 1 = dirt
x#;y#;z#
active% // False/True
bin% // 1 = top, 2 = bottom, 4 = left, 8 = right, 16 = front, 32 = back
ENDTYPE


TYPE TChunk
cubes[] AS TCube
x#;y#;z#

FUNCTION Create:
REDIM self.cubes[CHUNKSIZE+2][CHUNKSIZE+2][CHUNKSIZE+2]


FOR x = 1 TO CHUNKSIZE
FOR y = 1 TO CHUNKSIZE
FOR z = 1 TO CHUNKSIZE

self.cubes[x][y][z].typ = RND(1)
self.cubes[x][y][z].x = x*CUBESIZE/2
self.cubes[x][y][z].y = y*CUBESIZE/2
self.cubes[x][y][z].z = z*CUBESIZE/2
self.cubes[x][y][z].active = TRUE
//DEBUG "X:"+x+" Y:"+y+" Z:"+z+" TYP:"+self.cubes[x][y][z].typ+"\n"

NEXT
NEXT
NEXT


ENDFUNCTION


FUNCTION CheckSides:
X_OBJSTART 1
FOR x = 1 TO CHUNKSIZE
FOR y = 1 TO CHUNKSIZE
FOR z = 1 TO CHUNKSIZE


IF self.cubes[x][y][z].typ > 0
// Face left active
IF self.cubes[x-1][y][z].typ = 0; RenderLeft(CUBESIZE,x, y, z, RGB(255,0,0)); INC Counter; ENDIF
// Face right active
IF self.cubes[x+1][y][z].typ = 0; RenderRight(CUBESIZE,x, y, z, RGB(255,0,0)); INC Counter; ENDIF
// Face bottom active
IF self.cubes[x][y-1][z].typ = 0; RenderBottom(CUBESIZE,x, y, z, RGB(0,255,0)); INC Counter; ENDIF
// Face top active
IF self.cubes[x][y+1][z].typ = 0; RenderTop(CUBESIZE,x, y, z, RGB(0,255,0)); INC Counter; ENDIF
// Face front active
IF self.cubes[x][y][z+1].typ = 0; RenderFront(CUBESIZE,x, y, z, RGB(0,0,255)); INC Counter; ENDIF
// Face back active
IF self.cubes[x][y][z-1].typ = 0; RenderBack(CUBESIZE,x, y, z, RGB(0,0,255)); INC Counter; ENDIF
ENDIF

NEXT
NEXT
NEXT
X_OBJEND
ENDFUNCTION

ENDTYPE


LOCAL chunk1 AS TChunk
chunk1.Create()
chunk1.CheckSides()

LOCAL phi

WHILE TRUE

INC phi
X_MAKE3D 1, 1000, 45
X_CAMERA 0, 0, 30, 0, 0, 0


X_ROTATION phi,0,.15,0

X_PUSHMATRIX
X_MOVEMENT -CHUNKSIZE/2,-CHUNKSIZE/2,-CHUNKSIZE/2
X_DRAWOBJ 1, 0
X_POPMATRIX

X_MAKE2D

PRINT Counter,10,10

SHOWSCREEN
WEND
END


FUNCTION RenderFront: sz, x, y, z, col
                // Front Face
                sz=sz/2
                X_OBJADDVERTEX x+ sz, y-sz,  z+sz, 1, 0, col
                X_OBJADDVERTEX x-sz, y-sz,  z+sz, 0, 0, col
                X_OBJADDVERTEX x+ sz, y+ sz,  z+sz, 1, 1, col
                X_OBJADDVERTEX x-sz,  y+sz,  z+sz, 0, 1, col
                X_OBJNEWGROUP
ENDFUNCTION

FUNCTION RenderBack: sz, x, y, z, col
sz=sz/2
                X_OBJADDVERTEX x-sz, y+ sz, z-sz, 1, 1, col
                X_OBJADDVERTEX x-sz, y-sz, z-sz, 1, 0, col
                X_OBJADDVERTEX x+ sz, y+ sz, z-sz, 0, 1, col
                X_OBJADDVERTEX x+ sz, y-sz, z-sz, 0, 0, col
                X_OBJNEWGROUP
ENDFUNCTION

FUNCTION RenderTop: sz, x, y, z, col
                // Top Face
                sz=sz/2
                X_OBJADDVERTEX x-sz, y+ sz,  z+sz, 0, 0, col
                X_OBJADDVERTEX x-sz, y+ sz, z-sz, 0, 1, col
                X_OBJADDVERTEX x+ sz, y+ sz, z+ sz, 1, 0, col
                X_OBJADDVERTEX x+ sz, y+sz, z-sz, 1, 1, col
                X_OBJNEWGROUP
ENDFUNCTION

FUNCTION RenderBottom: sz, x, y, z, col
                // Bottom Face
                sz=sz/2
                X_OBJADDVERTEX  x+sz, y-sz, z-sz, 0, 1, col
                X_OBJADDVERTEX x-sz, y-sz, z-sz, 1, 1, col
                X_OBJADDVERTEX  x+sz, y-sz, z+ sz, 0, 0, col
                X_OBJADDVERTEX x-sz, y-sz,  z+sz, 1, 0, col
                X_OBJNEWGROUP
ENDFUNCTION

FUNCTION RenderRight: sz, x, y, z, col
                // Right face
                sz=sz/2
                X_OBJADDVERTEX  x+sz,  y+sz, z-sz, 1, 1, col
                X_OBJADDVERTEX  x+sz, y-sz, z-sz, 1, 0, col
                X_OBJADDVERTEX  x+sz,  y+sz, z+ sz, 0, 1, col
                X_OBJADDVERTEX  x+sz, y-sz,  z+sz, 0, 0, col
                X_OBJNEWGROUP
ENDFUNCTION

FUNCTION RenderLeft: sz, x, y, z, col
                // Left Face
                sz=sz/2
                X_OBJADDVERTEX x-sz, y-sz, z+ sz, 1, 0, col
                X_OBJADDVERTEX x-sz, y-sz, z-sz, 0, 0, col
                X_OBJADDVERTEX x-sz,  y+sz,  z+sz, 1, 1, col
                X_OBJADDVERTEX x-sz,  y+sz, z-sz, 0, 1, col
                X_OBJNEWGROUP
ENDFUNCTION


someone have improvements?!
I <3 DGArray's :D

PC:
AMD Ryzen 7 3800X 16@4.5GHz, 16GB Corsair Vengeance LPX DDR4-3200 RAM, ASUS Dual GeForce RTX™ 3060 OC Edition 12GB GDDR6, Windows 11 Pro 64Bit, MSi Tomahawk B350 Mainboard

Slydog

Nice!  Looking through your code I see it works basically how mine was structured, with obvious differences.

I like how you were planning to not create the quads in the CheckSides() function, but only update the cube.bin value by adding up CUBE_TOP + CUBE_LEFT + CUBE_BACK (for example) to predetermine which sides this cube has exposed.  I assume you were then going to write a function to actually create the geometry such as CreateSides().  (As of now the .bin value isn't used, nor the CUBE_TOP... constants).  But you combined them, which works just the same.

Any screenshots?!
Are you planning on adding multiple chunks?  Automatic chunking?
Now, get a perlin noise generator to create random MineCraft style worlds with multiple ground types!
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Schranz0r

Yes the first plan was a binarysystem for the sides...
We had some bugs ( display all sides correct ), it was awful!
We fixed it at like 12 pm ^^ horrible time to code...

What's the next plans:
- improve the renderspeed
- improve the CheckSides()  (atm realy realy reeeeeeealy slow :(  )
- "re-implement" the' binarysides' TCube.bin
- experiment with chunks / threading / Perlinnoise in 3D
- give up because we suck :D
I <3 DGArray's :D

PC:
AMD Ryzen 7 3800X 16@4.5GHz, 16GB Corsair Vengeance LPX DDR4-3200 RAM, ASUS Dual GeForce RTX™ 3060 OC Edition 12GB GDDR6, Windows 11 Pro 64Bit, MSi Tomahawk B350 Mainboard

Slydog

#13
Sounds like a great to-do list.
I'm so good, I already skipped to your last step!  Wait, that's a contradiction!?

I notice you dimension your array CHUNKSIZE+2.
And, in your CheckSides() routine, you have checks such as:
Code (glbasic) Select
IF self.cubes[x-1][y][z].typ = 0; RenderLeft(...
I assume the CHUNKSIZE+2 was so your checks didn't go outside of your array bounds, and the outer perimeter of you chunk (in the array) doesn't actually contain any cubes. 

Does your code actually render the very outside faces (where x=1 for example)?  If not, a simple addition should fix this such as:
Code (glbasic) Select
IF (x=1) OR (self.cubes[x-1][y][z].typ = 0); RenderLeft(...

Plus, you could update your array to be exactly CHUNKSIZE (no +2), then start your loops at zero, and add one more check (the x > 0), as I did:
Code (glbasic) Select
// Draw Wall West?
IF (x = 0)  OR (x > 0   AND cubes[x-1][y][z] = CUBE_AIR) THEN Walls_QuadAdd(quads[Q_W], x,y,z, walls)

You would save some memory by not having a blank buffer surrounding the chunk.

And you find it 'reallllly' slow eh?  I wonder why, that's basically how I render my maze walls, and including generating a random maze, calculating the wall faces, and displaying everything, it takes less than a second I think. (I had profiling code to verify, but can't remember the actual results!)

Keep us updated.
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Schranz0r

New Version:

Code (glbasic) Select
// --------------------------------- //
// Project: RL Test Chunks
// Start: Tuesday, October 01, 2013
// IDE Version: 11.414

GLOBAL CHUNKSIZE = 18
GLOBAL CUBESIZE = 1


GLOBAL CUBE_TOP = 1
GLOBAL CUBE_BOTTOM = 2
GLOBAL CUBE_LEFT = 4
GLOBAL CUBE_RIGHT = 8
GLOBAL CUBE_FRONT = 16
GLOBAL CUBE_BACK = 32

GLOBAL FACE_TOP = 10 
GLOBAL FACE_BOTTOM = 11
GLOBAL FACE_LEFT = 12
GLOBAL FACE_RIGHT = 13
GLOBAL FACE_FRONT = 14
GLOBAL FACE_BACK = 15


RenderTop(FACE_TOP,1,RGB(255,0,0))
RenderBottom(FACE_BOTTOM,1,RGB(0,0,255))
RenderLeft(FACE_LEFT,1,RGB(0,255,0))
RenderRight(FACE_RIGHT,1,RGB(255,255,0))
RenderFront(FACE_FRONT,1,RGB(0,255,255))
RenderBack(FACE_BACK,1,RGB(255,255,255))

GLOBAL Counter = 0

TYPE TCube
typ% // 0 = Air, 1 = dirt
x#;y#;z#
active% // False/True
bin% // 1 = top, 2 = bottom, 4 = left, 8 = right, 16 = front, 32 = back
ENDTYPE


TYPE TChunk
cubes[] AS TCube
x#;y#;z#

FUNCTION Create:
REDIM self.cubes[CHUNKSIZE+2][CHUNKSIZE+2][CHUNKSIZE+2]


FOR x = 1 TO CHUNKSIZE
FOR y = 1 TO CHUNKSIZE
FOR z = 1 TO CHUNKSIZE

self.cubes[x][y][z].typ = RND(1)
self.cubes[x][y][z].x = x*CUBESIZE/2
self.cubes[x][y][z].y = y*CUBESIZE/2
self.cubes[x][y][z].z = z*CUBESIZE/2
self.cubes[x][y][z].active = TRUE
//DEBUG "X:"+x+" Y:"+y+" Z:"+z+" TYP:"+self.cubes[x][y][z].typ+"\n"

NEXT
NEXT
NEXT


ENDFUNCTION

FUNCTION CheckSides_BIN:

FOR x = 1 TO CHUNKSIZE
FOR y = 1 TO CHUNKSIZE
FOR z = 1 TO CHUNKSIZE


IF self.cubes[x][y][z].typ > 0
// Face left active
IF self.cubes[x-1][y][z].typ = 0; INC self.cubes[x][y][z].bin, CUBE_LEFT; INC Counter; ENDIF
// Face right active
IF self.cubes[x+1][y][z].typ = 0; INC self.cubes[x][y][z].bin, CUBE_RIGHT; INC Counter; ENDIF
// Face bottom active
IF self.cubes[x][y-1][z].typ = 0; INC self.cubes[x][y][z].bin, CUBE_BOTTOM; INC Counter; ENDIF
// Face top active
IF self.cubes[x][y+1][z].typ = 0; INC self.cubes[x][y][z].bin, CUBE_TOP; INC Counter; ENDIF
// Face front active
IF self.cubes[x][y][z+1].typ = 0; INC self.cubes[x][y][z].bin, CUBE_FRONT; INC Counter; ENDIF
// Face back active
IF self.cubes[x][y][z-1].typ = 0; INC self.cubes[x][y][z].bin, CUBE_BACK; INC Counter; ENDIF
ENDIF

NEXT
NEXT
NEXT

ENDFUNCTION

FUNCTION Render:
X_MOVEMENT -CHUNKSIZE/2-CUBESIZE/2, -CHUNKSIZE/2-CUBESIZE/2, -CHUNKSIZE/2-CUBESIZE/2
X_PUSHMATRIX

FOR x = 1 TO CHUNKSIZE
FOR y = 1 TO CHUNKSIZE
FOR z = 1 TO CHUNKSIZE
IF self.cubes[x][y][z].bin = 0 THEN CONTINUE
LOCAL tmp_bin = self.cubes[x][y][z].bin
LOCAL v, d
//DEBUG "BIN: "+self.cubes[x][y][z].bin+"\n"
// 1 = top, 2 = bottom, 4 = left, 8 = right, 16 = front, 32 = back
IF tmp_bin >= CUBE_BACK

//Draw back
X_MOVEMENT x, y, z
X_DRAWOBJ FACE_BACK, 0
// v = tmp_bin
DEC tmp_bin, CUBE_BACK
// d = tmp_bin
// DEBUG "BACK V: "+v+"N: "+d+"\n"
ENDIF

IF tmp_bin > 0
IF tmp_bin >= CUBE_FRONT
//draw front
X_MOVEMENT x, y, z
X_DRAWOBJ FACE_FRONT, 0
// v = tmp_bin
DEC tmp_bin, CUBE_FRONT
// d = tmp_bin
// DEBUG "FRONT V: "+v+"N: "+d+"\n"
ENDIF
ENDIF

IF tmp_bin > 0
IF tmp_bin >= CUBE_RIGHT
//draw right
X_MOVEMENT x, y, z
X_DRAWOBJ FACE_RIGHT, 0
// v = tmp_bin
DEC tmp_bin, CUBE_RIGHT
// d = tmp_bin
// DEBUG "RIGHT V: "+v+"N: "+d+"\n"
ENDIF
ENDIF

IF tmp_bin > 0
IF tmp_bin >= CUBE_LEFT
//draw left
X_MOVEMENT x, y, z
X_DRAWOBJ FACE_LEFT, 0
// v = tmp_bin
DEC tmp_bin, CUBE_LEFT
// d = tmp_bin
// DEBUG "LEFT V: "+v+"N: "+d+"\n"
ENDIF
ENDIF

IF tmp_bin > 0
IF tmp_bin >= CUBE_BOTTOM
//draw bottom
X_MOVEMENT x, y, z
X_DRAWOBJ FACE_BOTTOM, 0
// v = tmp_bin
DEC tmp_bin, CUBE_BOTTOM
// d = tmp_bin
// DEBUG "BOTTOM V: "+v+"N: "+d+"\n"
ENDIF
ENDIF

IF tmp_bin > 0
IF tmp_bin = CUBE_TOP
//draw top
X_MOVEMENT x, y, z
X_DRAWOBJ FACE_TOP, 0
// v = tmp_bin
DEC tmp_bin, CUBE_TOP
// d = tmp_bin
// DEBUG "BACK V: "+v+"N: "+d+"\n"
ENDIF
ENDIF

NEXT
NEXT
NEXT

X_POPMATRIX

ENDFUNCTION

ENDTYPE


LOCAL chunk1 AS TChunk
chunk1.Create()
chunk1.CheckSides_BIN()


LOCAL phi

WHILE TRUE

INC phi
X_MAKE3D 1, 1000, 45
X_CAMERA 0, 0, 50, 0, 0, 0

X_SPOT_LT -2, 0, 0,0,100, 0,0,0,90

X_ROTATION phi,.25,.5,1
X_PUSHMATRIX
chunk1.Render()
X_POPMATRIX


X_MAKE2D

PRINT Counter,10,10

SHOWSCREEN
WEND
END


FUNCTION RenderFront: num, sz, col
                sz=sz/2
X_OBJSTART num
                // Front Face
                X_OBJADDVERTEX  sz, -sz,  sz, 1, 0, col
                X_OBJADDVERTEX -sz, -sz,  sz, 0, 0, col
                X_OBJADDVERTEX  sz,  sz,  sz, 1, 1, col
                X_OBJADDVERTEX -sz,  sz,  sz, 0, 1, col
                X_OBJNEWGROUP
    X_OBJEND
ENDFUNCTION

FUNCTION RenderBack: num, sz, col
                sz=sz/2
X_OBJSTART num
                // Back Face
                X_OBJADDVERTEX -sz,  sz, -sz, 1, 1, col
                X_OBJADDVERTEX -sz, -sz, -sz, 1, 0, col
                X_OBJADDVERTEX  sz,  sz, -sz, 0, 1, col
                X_OBJADDVERTEX  sz, -sz, -sz, 0, 0, col
                X_OBJNEWGROUP
    X_OBJEND
ENDFUNCTION

FUNCTION RenderTop: num, sz, col
                sz=sz/2
X_OBJSTART num
                // Top Face
                X_OBJADDVERTEX -sz,  sz,  sz, 0, 0, col
                X_OBJADDVERTEX -sz,  sz, -sz, 0, 1, col
                X_OBJADDVERTEX  sz,  sz,  sz, 1, 0, col
                X_OBJADDVERTEX  sz,  sz, -sz, 1, 1, col
                X_OBJNEWGROUP
    X_OBJEND
ENDFUNCTION

FUNCTION RenderBottom: num, sz, col
                sz=sz/2
X_OBJSTART num
                // Bottom Face
                X_OBJADDVERTEX  sz, -sz, -sz, 0, 1, col
                X_OBJADDVERTEX -sz, -sz, -sz, 1, 1, col
                X_OBJADDVERTEX  sz, -sz,  sz, 0, 0, col
                X_OBJADDVERTEX -sz, -sz,  sz, 1, 0, col
                X_OBJNEWGROUP
    X_OBJEND
ENDFUNCTION

FUNCTION RenderRight: num, sz, col
                sz=sz/2
X_OBJSTART num
                // Right face
                X_OBJADDVERTEX  sz,  sz, -sz, 1, 1, col
                X_OBJADDVERTEX  sz, -sz, -sz, 1, 0, col
                X_OBJADDVERTEX  sz,  sz,  sz, 0, 1, col
                X_OBJADDVERTEX  sz, -sz,  sz, 0, 0, col
                X_OBJNEWGROUP
    X_OBJEND
ENDFUNCTION

FUNCTION RenderLeft: num, sz, col
                sz=sz/2
X_OBJSTART num
                // Left Face
                X_OBJADDVERTEX -sz, -sz,  sz, 1, 0, col
                X_OBJADDVERTEX -sz, -sz, -sz, 0, 0, col
                X_OBJADDVERTEX -sz,  sz,  sz, 1, 1, col
                X_OBJADDVERTEX -sz,  sz, -sz, 0, 1, col
                X_OBJNEWGROUP
    X_OBJEND
ENDFUNCTION


See screenshot!
I <3 DGArray's :D

PC:
AMD Ryzen 7 3800X 16@4.5GHz, 16GB Corsair Vengeance LPX DDR4-3200 RAM, ASUS Dual GeForce RTX™ 3060 OC Edition 12GB GDDR6, Windows 11 Pro 64Bit, MSi Tomahawk B350 Mainboard