Jump to content
Sign in to follow this  

The 3D book

3dBookman

1996 views

After a break of several years the 3D book project is back on.   A few short words now on what this blog is about.  I have to deliver my wife to the bus station in a few minutes, then a week alone so may have the time then to explain things.  But the 3D book is something I started in 014 and put several years into, then the break, now on again.  A win32 app with a text window and an ogl window.

I just remembered I had something written on this so here it is

I write to see if anyone in this community of game developers, programmers, enthusiasts,  may be interested in a project I have been developing[off and on] for several years now.

So follows a short description of this project, which I call the 3D-Book project. 

The 3D-Format Reader: A new format of media. Imagine opening a book, the left page is conventional formatted text - on the right page a 3D animation of the subject of the text on the left hand page.  The text page with user input from mouse and keyboard, the 3D page with user intput from a game pad.

    An anatomy text for a future surgeon, with the a beating heart in 3D animation.
    
    A childrens story adventure book with a 3D fantasy world to enter on the right page.   
    ...

    Currently 3D-Format Reader consists of a C++ Windows program: Two "child" windows in a main window frame. Two windows: a text-2D rendering window and a 3D-rendering window.  The text-2D window, as its' name implies, displays text and 2D graphics; it is programmed using Microsoft's DirectWrite text formatting API and Microsoft's Direct2D API for 2D graphics.  The 3D-rendering window uses the OpenGL API.

    A 3DE-Book page is formatted in one of two possible modes: DW_MODE or GL_MODE.  In GL_MODE both windows are shown; the text-2D rendering window is on the left and the 3D OpenGL window is on the right.  In DW_MODE, only the text-2D rendering window is shown, the OpenGL window is hidden (Logically it is still there, it has just been given zero width).

    The 3D-Format Reader reads text files, which consists of the text of the book, control character for the formatting of text, (bold, underline, ...), display of tables,  loading of images(.jpg .png ...), and control of 2D and 3D routines.      

    3D-Reader programming is based on a Model-View-Controller (MVC) architecture.  The MVC design is modular: The Controller component handles user input from the operating system , the Model component processes the input, and the View component sends output back to the user on the display.  Typical Parent-Child windows programs have multiple "call back" window procedures(winProcs): One for the parent window and one for child window.  The MVC model, simplifies message routing by using a call-back window procedure which receives Windows messages for the main window, the text-2D window and the OGL window.   
    
    A sample MVC program by Song Ho Ahn was used as a template for the 3DE-Reader.

Rushed for time now, so a hasty sign off and thanks for reading.

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

8 - 21 -18

I spent the last few days working on procedural mesh generation.  First looking to find a bit of code to do what I had in mind.  Which begs the question: What 
did I have in mind?   I just wanted a cube mesh generator such that...  

Requirements

Input:    An integer  n = units from origin to cube face.

Output: 

  • The vertices for a unit cube centered on the origin.                                                                    
  • 8n² triangles per cube face.
  • 3 times 8n² verts in clockwise winding order (from the outside of the cube) ready for the rendering pipeline.

Screenshot of some cubes generated with the procedural cube mesh generator.

 3_Cubes.png                         

 

That was about it for the output requirements.   I did not want to hand code even a single vertex and did not want to load a mesh file.   I was sure the 
code was out there somewhere, but was not finding it.  So, a bit reluctantly at first, I started coding the mesh generator. I started enjoying creating this thing and stopped searching for the "out-there-somewhere" code; although still curious how others did this.

Analysis

First question: How do we number the verts?  It would be great to conceive of some concise algorithm to put out the cube face verts all in clockwise
order for the outside faces of the cube directly.  That seemed beyond me so I plodded along step by step. 

I decided to just use a simple nested loop to generate the cube face verts and number them in the order they were produced.  The hope(and the presumption) was: The loop code was in some order, running thru the x y and z coordinates in order, from -n to +n, therefore the output would be a recognizable pattern.

The simple nested loop vert generator did not let us down: It gave us a recognizable pattern, at least for this face. It turned out (as expected now) that all six faces have similar recognizable patterns. Plotting the first row or two of verts you can easily see how to run the rest of the pattern. 

Plot of the first(of six) cube faces verts output by the vert generator:  Input of n:  There are (2n+1)² verts per cube face, or 25 verts for n = 2.

                                                        image.png.aad774f4568d6c34f4a1f48667b44537.png

This is looking at the x = -n face from the outside of the cube.  To simplify the math it helps to define s = 2n.  Then there are 

                                                                                     (s + 1)²  verts,  or 25 for s = 4

                                                                                      s²  cells on the face, or 16 for 4 = 2.

We are going divide each cell into 2 triangles, so there are 2s² triangles per face, or 32 for s = 4.

Second question: What pattern for the triangles?  How to number the 2s² = 32 triangles? What we want in the end is a bit of code such that... for triangles T[0] thru T[2s²-1]  or T[0] thru T[31]( for n = 4), we have T[N] = f0(N), f1(N), f2(N).  Where f0(N) gives the first vertex of T[N] as a function of N. and f1 and f2 give the second and third verts, all in CW winding order looking into the cube of course. 

Here the choice is a bit arbitrary, but it would seem to make things easier if we can manage to have the order of triangles follow the order of verts to a degree.

 

Numbering the triangles.

                                               image.png.b6354525f76f02f4223123a45e7a311d.png

 

And now the problem becomes: Look at the triangle vert list,  T0 - T8...T31 in the image, and try to discern some pattern leading us to the sought after functions f0(N), f1(N), f2(N) where N is the number of the triangle, 0 thru 2s²-1. This really is the holy grail of this whole effort; then we have T[N] = f0(N), f1(N), f2(N) and that list of verts can be sent directly to the rendering pipeline.  Of course we want these functions to work for all six faces and all 12s² triangles to cover the cube.  But first let's see if we can just do this one face, 0 thru 2s²-1..

Thru a bit of trial and error the 32 triangles(T0 - T31) were ordered as shown.  

Now we have an ordered list of the triangles and the
verts from our loop.

T0 = 0 5 6
T1 = 6 1 0
T2 = 1 6 7
T3 = 7 2 1
T4 = 2 7 8
T5 = 8 3 2
T6 = 3 8 9
T7 = 9 4 3
T8 = 5 10 11 ... T30 T31.

If we can find a pattern in the verts on the right side
of this list; we can implement it in an algorithm and
the rest is just coding.

Pattern recognition:

It appears

T2 = T0 with 1 added to each component
T3 = T1 with 1 added to each component

In general T[N+2] = T[N] with 1 added to each component, until we come to T8 at least.  Also it is hard to recognize a relation between the even and odd  triangles,To see what is happening here it helps to look at an image of the generalized case where n can take on any integer value n > 0.

image.png.538eaac59c1edc7d6303b3aca911e16f.png

Looking for patterns in this generalized(for any n) vert plot we see...

We have defined s = 2n.

The 4 corner coordinates(+-n,+-n) of the x = - n cube face, one at each corner (+-n,+-n). 

There are (s+1)² verts/face numbered (0 thru (s+1)² -1).

There are 2s² triangles/face numbered (0 thru 2s² -1). They are indicated in red.

It's not as bad as it looks iff you break it down.  Let's look at the even triangles only and just the 0th vert of these triangles.  For any row we see the number of that first vert of the even triangles just increases by one going down the row. We can even try a relation such as T[N].0 = N/2.  Here  T[N].0 denotes the 0th vert of th Nth triangle. Which works until we have to jump to the next row. Every time we jump a row we T[N+1].0  = T[N].0 + 2 for the first triangle in the higher row.  So we need a corrective term to the T[N].0 = N/2 relation that adds 1 every time we jump a row. We can use computer integer division to generate such a term and N/2s is such a term.  It only changes value when we jump rows and we get our first function ...

                                                                                   f0(N) = N/2 + N/2s.  (even triangles)

Remember the integer division will discard any remainder from the terms and check this works for the entire cube face, but only for the even triangles. What about the odd triangles?  Going back to the triangle vs vert list for the specific case n = 2, s = 4 for the first row; we see for the odd triangles T[N].0  = T[N-1].0 + s  + 2.  And adding this term, s + 2 to the formula for the even triangle 0th vert we get f0[N] for the odd triangles.

                                                                                   f0(N) = N/2 + N/2s + s  + 2. (odd triangles)

Continuing this somewhat tedious analysis for the remaining functions f1(N), f2(N) we eventually have these relations for the x = -n cube face triangles.

                                                                               for N = 0  thru N = 2s² - 1.

                                                                               defining   m = N/2 + N/2s.

                                                                               T[N] = m,  m + s + 1,  m + s + 2       T[N]   =    f0(N), f1(N), f2(N).  (even N)

                                                                               T[N] = m + s + 2,  m + 1,  m              T[N]   =   f0'(N), f1'(N), f2'(N) (odd N)

So it turns out we have two sets of functions for the verts, fn(N) for the even triangles and fn'(N) for the odd.

To recap here; we now have formulae for all the T[N] verts as functions of N and the input parameter n:

Input:    An integer  n = units from origin to cube face.

But this is only for the first face x = -n, we have five more faces to determine.  So the question is: Do these formulae work for the other faces? And the answer is no they do not, but going through a similar analysis for the remaining face gives similar T[N] = f0(N), f1(N), f2(N)  for them. There is still the choice of how to number the remaining triangles and verts on the remaining five faces, and the f0(N), f1(N), f2(N) will depend on the somewhat arbitrary choice of how we do the numbering.  For the particular choice of a numbering scheme I ended up making, it became clear how to determine the f0(N), f1(N), f2(N) for the remaining faces.  It required making generalized vert plots for the remaining five face similar to the previous image. Then these relation emerged...

For face x = -n         T[N]   N(0 thru 2²-1)  we have the f0(N), f1(N), f2(N), even and odd

For face x =  n         T[N]   N(2s² thru 4s²-1)       add (s+1)² to the x=-n face components and reverse the winding order

For face y = -n         T[N]   N(4s² thru 6s²-1)       add 2(s+1)² to the x=-n face components and reverse the winding order

For face y =  n         T[N]   N(6s² thru 8s²-1)       add 3(s+1)² to the x=-n face components 

For face z = -n         T[N]   N(8s²0 thru 10s²-1)   add 4(s+1)² to the x=-n face components 

For face z =  n         T[N]   N(10s²0 thru 12s²-1)  add 5(s+1)² to the x=-n face components and reverse the winding order

And these are enough to allow us to write explicit expressions for all 12n² triangles for all 6 faces T[N] and what remains to be done is to implement these expression in code.  Which turned out to be a much simpler task than finding the f0(N), f1(N), f2(N) and resulted in a surprisingly short bit of code.

Implementation

I have attempted to make this C++ snippet of code as generic as possible and have removed any dev-platform specific #includes and the like.  GLM, a C++ mathematics library for graphics developed by Christophe Riccio is used.  It is a header only library.

https://github.com/g-truc/glm/releases/download/0.9.9.0/glm-0.9.9.0.zip

 That is the only outside dependency.

 

//  Procedural cube face verticies generator

#include <vector>
#include <glm/gtc/matrix_transform.hpp>


struct Triangle
{
    glm::vec3 vert[3]; // the three verts of the triangle
};

/*

std::vector<Triangle> cube_Faces(int n)

Input:        integer 'n'; the units from origin to cube face.

Output:        vector<Triangle> glTriangle; container for the 
            12*(2*n)² triangles covering the 6 cube faces.

*/

std::vector<Triangle> cube_Faces(int n){

    size_t number_of_triangles(12*(2*n )*(2*n));
    size_t number_of_face_verts(6*(2*n +1 )*(2*n+1));
    std::vector<glm::vec3> face_verts(number_of_face_verts);
    std::vector<Triangle> glTriangle(number_of_triangles);

//  Generate the 6*(2n +1 )² face verts -------------------------------

    int l(0);
    for(int i = 0; i < 6; i++){
        for(int j = -n; j <= n; j++){
            for(int k = -n; k <= n; k++){
                // Below "ifS" strip out all interior cube verts.
                if( i == 0){ // do yz faces
                    face_verts[l].x = (float)(-n); //x
                    face_verts[l].y = (float)j; //y
                    face_verts[l].z = (float)k;}//z
                if( i == 1){ // do yz faces
                    face_verts[l].x = (float)(n); //x
                    face_verts[l].y = (float)j; //y
                    face_verts[l].z = (float)k;}//z            
                if( i == 2){ // do zx faces
                    face_verts[l].x = (float)j; //x
                    face_verts[l].y = (float)(-n); //y
                    face_verts[l].z = (float)k;}//z
                if( i == 3){ // do zx faces
                    face_verts[l].x = (float)j; //x
                    face_verts[l].y = (float)(n); //y
                    face_verts[l].z = (float)k;}//z
                if( i == 4){ // do xy faces
                    face_verts[l].x = (float)j; //x
                    face_verts[l].y = (float)k; //y
                    face_verts[l].z = (float)(-n);}//z
                if( i == 5){ // do xy faces
                    face_verts[l].x = (float)j; //x
                    face_verts[l].y = (float)k; //y
                    face_verts[l].z = (float)(n);}//z
                l++;
            }
        }
    }

//  Generate the 12*(2*n)² triangles from the face verts -------

    int s = 2*n;    int q = 2*s*s;    int a = (s+1)*(s+1);
    int f(0);        int r(0);        int h(0);

    for( int N=0; N < number_of_triangles; ){ 
        // triangles already in CW winding 
        if( N <  q || N <  5*q && N > 3*q - 1  ){ 
            // do the even indicies
            f= q*(N/q); r = a*(N/q); h = (N-f)/2 + (N-f)/(2*s) + r;
            glTriangle[N].vert[0] = face_verts[h];
            glTriangle[N].vert[1] = face_verts[s + 1 + h];
            glTriangle[N].vert[2] = face_verts[s + 2 + h];
            N++; f= q*(N/q); r = a*(N/q); h = (N-f)/2 + (N-f)/(2*s) + r;
            // do the odd indicies
            glTriangle[N].vert[0] = face_verts[s + 2 + h];
            glTriangle[N].vert[1] = face_verts[ 1 + h];
            glTriangle[N].vert[2] = face_verts[h];
            N++; f= q*(N/q); r = a*(N/q); h = (N-f)/2 + (N-f)/(2*s) + r;
        }
        // triangles needing reverse order for CW winding
        if( N >  5*q - 1 || N <  3*q && N > q - 1 ){ 
            // do the even indicies
            glTriangle[N].vert[0] = face_verts[s + 2 + h];
            glTriangle[N].vert[1] = face_verts[s + 1 + h];
            glTriangle[N].vert[2] =  face_verts[h];
            N++; f= q*(N/q); r = a*(N/q); h = (N-f)/2 + (N-f)/(2*s) + r;
            // do the odd indicies
            glTriangle[N].vert[0] = face_verts[h];
            glTriangle[N].vert[1] = face_verts[1 + h];
            glTriangle[N].vert[2] = face_verts[s + 2 + h];
            N++; f= q*(N/q); r = a*(N/q); h = (N-f)/2 + (N-f)/(2*s) + r;
        }
    }

//  Normalize the cube to side = 1 ------------------------------

    for(int i = 0; i < number_of_triangles; i++){
        glTriangle[i].vert[0].x = glTriangle[i].vert[0].x/(2.0*(float)n);
        glTriangle[i].vert[0].y = glTriangle[i].vert[0].y/(2.0*(float)n);
        glTriangle[i].vert[0].z = glTriangle[i].vert[0].z/(2.0*(float)n);
        glTriangle[i].vert[1].x = glTriangle[i].vert[1].x/(2.0*(float)n);
        glTriangle[i].vert[1].y = glTriangle[i].vert[1].y/(2.0*(float)n);
        glTriangle[i].vert[1].z = glTriangle[i].vert[1].z/(2.0*(float)n);
        glTriangle[i].vert[2].x = glTriangle[i].vert[2].x/(2.0*(float)n);
        glTriangle[i].vert[2].y = glTriangle[i].vert[2].y/(2.0*(float)n);
        glTriangle[i].vert[2].z = glTriangle[i].vert[2].z/(2.0*(float)n);
    };

    return glTriangle;
}

 

The rendering was done using OpenGl.

//  OGL render call to the cube mesh generator - PSUEDOCODE

int n(2);
int cube_triangle_Count = (12*(2*n)*(2*n));
std::vector<Triangle> cube_Triangles(cube_triangle_Count);
cube_Triangles = cube_Faces(n);
glBindBuffer(GL_ARRAY_BUFFER, uiVBO[0]);
glBufferData(GL_ARRAY_BUFFER, cube_Triangles.size()*sizeof(Triangle), &cube_Triangles[0], GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), 0);
glEnableVertexAttribArray(0);
glDrawArray(GL_TRIANGLES,0,3*cube_triangle_Count);

 

This just gets the position attribute of the cube face triangle verts; for the color and other attributes there are a couple of options:  Use separate GL_ARRAY_BUFFERS for the color and other attributes.  Or add attributes to the Triangle struct...

struct Triangle
{
	glm::vec3 vert[3]; // the three verts of the triangle
	attribute1;
	attribute2;
	...
};

Screenshot of the spherified cube.

image.png.915711f0c22a757de90ad9f03ac4e83e.png

What's next?

Now that we have the cube mesh what we can do with with it practically unlimited.  The first thing I did was turn it into a sphere.  Playing with tesselating the cube or sphere or stellating it with different patterns;  might do.  Ended up trying a few matrix transformations on the cube mesh.  These are shown in the image below.

image.thumb.png.738a84d7118dd94d27088c987e9f7075.png

 

These shapes are result short bits of code like the code for the column shape below.

//Column
    for(int i = 0; i < number_of_triangles; i++){
        for(int j = 0; j < 3; j++){
            if( glTriangle[i].vert[j].y < 0.5f  && glTriangle[i].vert[j].y  > -0.5f  ){ 
            float length_of_v = sqrt((glTriangle[i].vert[j].x * glTriangle[i].vert[j].x) + (glTriangle[i].vert[j].z * glTriangle[i].vert[j].z));
            glTriangle[i].vert[j].x = 0.5f*glTriangle[i].vert[j].x/length_of_v;
            glTriangle[i].vert[j].z = 0.5f*glTriangle[i].vert[j].z/length_of_v;
            }
        }
    }

 

Doing this; the blacksmith at his forge analogy soon presents.  The mesh is the ingot, hammer matricies stretch, round and bend it against the fixed geometry of the anvil - coordinate system. I am the smith.   
 

 

 

 

image.png



0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
  • Advertisement
  • Blog Entries

  • Similar Content

    • By _WeirdCat_
      so I have 3 classes
      main class which stores a pointer
      struct Main { int * p; A subclass1, subclass2; void init() { subclass1.init(p); subclass2.init(p); } }; and two subclasses struct A { int * p; void init(int * ap) { p = ap; } }; Now I would like to create a pointer from subclass1 or subclass2 that will actually create a pointer in Main class, so subclasses can use the same int from main class
      heres what im trying to achieve
      in first scenario you see same windows so you see that on phone1 and phone2 theres a blue wireframed object that im trying to share between multiple windows, but when I change projection window I cant see it, so I need a shared pointer
      https://youtu.be/9HLDsrm7R2w
       
      I already messed with int ** p; for subclasses but im missing something (maybe because int * p in main class is 0)
      and I cant assign it by int ** subp = (*mainclass.p);
       
       
      if someone tells me that I could use shared ptr then I ask him to provide some example code for me cause I never used it and have no xp with it.
      However pointer to pointer c style would be the best approach for me thus theres something wrong in the code I dont fully understand so far, int * p in main class has to be 0 until its not created by subclass...
      maybe I somewhere use it where I shouldnt, well I just changed the code from * to ** and just switched from if (p ==0) return;
      to if ( (*p) == 0) return; etc.
      but theres few thousands lines I need to check so a short brief of handling such types would be appreciated..
    • By Kamal Wafi
      Hi there,
      i recently start learning unity and im working in my first game ,
       
      I was wondering if unity had functions to support the motion control effect (tilting screen to move character) you see
      in doodle jump (which is 2d game) ? If it exists, what are they called? and how it works ?

      Thanks
    • By 3dmodelerguy
      For reference I am use Unity as my game engine and the A* Pathfinding Project for path finding as there is no chance I would be able to create anything close to as performant as that in any reasonable amount of time.
      So I am looking to build a game that is going to have a very similar style as Prison Architect / Rim World / SimAirport / etc. One of the things that I assume is going to effect performance is path finding. Decisions about the game I have already made that I think relate to this are:
      1. While I am going to be using Colliders, all of them will be trigger colliders so everything can pass through each other and I will not be use physics for anything else as it has no relevance for my game
      2. I am going to want to have a soft cap at the map size being 300x300 (90,000 tiles), I might allow bigger sizes but do something like Rim World does in warning the player about possible side effect (whether it be performance or gameplay)
      3. The map will be somewhat dynamic in that the user will be able to build / gather stuff from the map but outside of that, it should not change very much
      Now I am going to build my game around the idea that users would be in control of no more than 50 pawns at any given time (which is something I can probably enforce through the game play) but I am also going to want to have number other pawns that are AI controlled on the map (NPCs, animals, etc.) that would also need path finding enabled. Now I did a basic test in which I have X number of pawns pick a random location in the 300 x 300 map. move towards it, and then change the location every 3-5 seconds. My initial test was pretty slow (not surprising as I was calculating the path every frame for each pawn) so I decided to cache the calculated path results and only update it ever 2 seconds which got me:
      100 pawns: 250 - 450 FPS
      150 pawns: 160 - 300 FPS
      200 pawns: 90 - 150 FPS
      250 pawns: 50 - 100 FPS
      There is very little extra happening in the game outside of rendering the tilemap.
      I would imagine the most pawns on the map at a given time that need path finding might be a 1000 (and I would probably be able to make due with like 500 - 600). Now obviously I would not need all the pawn to be calculation paths every 2 seconds nor would they need to be calculating paths that are so long but even at a 5 second path refresh rate and paths that are up to 10 tiles long, I am still only able to get to about 400 pawns before I start to see some big performance issues. The issue with reducing the refresh rate is that there are going to be cases where maybe a wall is built before the pawns path is refreshed having them walk through the wall but not sure if there is a clean way to update the path only when needed.
      I am sure when I don't run the game in the Unity editor I will see increase performance but I am just trying to figure out what things I could be doing to make sure path finding is as smaller of a performance hit as possible as there is a lot of other simulation stuff I am going to want to run on top of the path finding.
    • By phil67rpg
      well I am able to get my sprites to rotate and move in all directions, I have drawn two plane sprites, I am also able to shoot a bullet in the up direction, I want to shoot bullets in all directions just like my plane rotates, I just need a hint on how to proceed, go easy on me this is new stuff to me. However I am making progress.
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!