build static mesh overview

A place to discuss everything related to Newton Dynamics.

Moderators: Sascha Willems, walaber

build static mesh overview

Postby JoeJ » Sat Jul 30, 2022 3:11 am

I'm looking at ndMakeStaticMap.cpp for examples, but i'm confused about there being two 'mesh builders': ndPolygonSoupBuilder and ndMeshEffect. In the function BuildGridPlane they are even used both. So what's the purpose / difference of those two classes? When do i use which or both?

Regarding my geometry, that's quite different form the usual standard. Here's an example made from blending 4 boxes:
remesh.png
remesh.png (162.63 KiB) Viewed 9777 times

This is similar to marching cubes, but it's actually a quad dominant remesh. Because edge flow aligns to curvature, the quality is better so i hope to use this for both terrain and basic architecture, but not for all kinds of details and decorations.

I think this also is good for physics. The remeshing guarantees manifold and no degenerate mesh issues, also no self intersections from kitbashing multiple, overlapping models. Basically i can import any buggy mesh and convert it to robust volume and surface.
The downside is ofc. lots of triangles in comparison to manually optimized models.
I can generate the geometry at any resolution. For physics i guess edge length of 10 - 20 cm may work well, but it's still a lot of triangles.
Let me know if you think a reduction is needed, and if Newton can do such reduction or if i should do this. Ideally, no reduction is needed, assuming a detailed game world has not much flat surfaces anyway.

I'm also worried about about the boundaries. Notice, for a large world, the whole world would be just one single connected huge mesh, but divided into (color coded) blocks. Physics sim will only happen on blocks near the player ofc.
But how would Newton know multiple such blocks are meant to form a seamless, connected surface?
Is it enough to provide matching boundary vertex positions and normals?
Or is there a need to add skirts to the boundaries, eventually adding a row of triangles on the boundary so they represent the duplicated surface of an adjacent block?

Finally, i could provide adjacency information even across blocks, if that helps Newton to build it's data structures faster or better.
User avatar
JoeJ
 
Posts: 1453
Joined: Tue Dec 21, 2010 6:18 pm

Re: build static mesh overview

Postby Julio Jerez » Sat Jul 30, 2022 10:58 am

But how would Newton know multiple such blocks are meant to form a seamless, connected surface? Is it enough to provide matching boundary vertex positions and normals?

it doesn't, it expects a clean vertex, normal, index and shared edge mesh intersecting an aabb box in some region of the space. for that there are some subclasses and some examples how to use them.

In you case since your mesh seem to fall in the category of procedural. you can subclass
from ndShapeStaticProceduralMesh or directly from ndShapeStaticMesh
the collision shape hierarchy looks like this
Untitled.png
Untitled.png (45.92 KiB) Viewed 9766 times


if you decide to subclass form ndShapeStaticProceduralMesh, it is less work, but the class does the house keep for making a runtime manifold mesh for the vertex list index list you pass in the GetPolygon

for that you can check example
Code: Select all
ndBodyKinematic* BuildProceduralMap(ndDemoEntityManager* const scene, ndInt32 grids, ndFloat32 gridSize, ndFloat32 perturbation)



if you want to have control of the manifold information, because you mesha lrwady has normals, and edge shared information and all the stuff, you can look and class
ndShapeStaticProceduralMesh or ndShapeHeightfield

to see how function
void ndShapeHeightfield::GetCollidingFaces(ndPolygonMeshDesc* const data) const

is implemented for the different type of meshes.
I would just subclass from ndShapeStaticProceduralMesh and check example BuildProceduralMap

and after I get that going, if you feel it can be better you can go lower level.
I find that that part if not demanding enough to make super optimized, since is a negligible part of the time per step.
Julio Jerez
Moderator
Moderator
 
Posts: 12249
Joined: Sun Sep 14, 2003 2:18 pm
Location: Los Angeles

Re: build static mesh overview

Postby JoeJ » Sun Jul 31, 2022 4:23 am

I was confused from ndShapeStaticProceduralMesh having it's own range query functions assuming a planar mesh.
But my mesh isn't planar, and it's not really procedural either.
After i'm done with editing the scene in editor, it won't change anymore so it's a static mesh like any other.

So, instead i was looking at your import of the fbx racetrack, which brought me to polygon soup builder.
This is what i have so far:
Code: Select all
ndBodyDynamic* BuildStaticMesh (ndWorld& world, const ndMatrix matrix, const MeshProcessing::HEMesh &mesh, bool optimized)
   {
      ndPolygonSoupBuilder meshBuilder;
      meshBuilder.Begin();
   
      ndVector face[3];
      for (int pI=0; pI<mesh.GetPolyCount(); pI++)
      {
         if (mesh.GetPolyEdge(pI)==-1) continue;

         ndInt32 faceIndex = 0;
         const int first = mesh.GetPolyEdge(pI);
         int eI = first;
         do {
            int vI = mesh.GetEdgeVertex(eI);
            MeshProcessing::vec v = mesh.GetVertexPos(vI);
            eI = mesh.GetEdgeNext(eI);            

            assert(faceIndex<3);
            face[faceIndex++] = ndVector (v[0],v[1],v[2], ndFloat32(1.f));
            if (faceIndex==3)
            {
               ndInt32 materialIndex = 0;//ent->m_fbxMeshEffect->GetFaceMaterial(edge);
               meshBuilder.AddFace(&face[0].m_x, sizeof(ndVector), 3, materialIndex);
               face[1] = face[2];
               faceIndex = 2;
            }

         } while (eI != first);
      }

      meshBuilder.End(true);//optimized);
      ndShapeInstance shape(new ndShapeStatic_bvh(meshBuilder));

      ndBodyDynamic* const body = new ndBodyDynamic();
      //body->SetNotifyCallback(new ndDemoEntityNotify(scene, entity));
      body->SetMatrix(matrix);
      body->SetCollisionShape(shape);
      world.AddBody(body);

      return body;
   }


This works for a closed model, but will fail to calculate correct normals on the boundary.

So i plan to replace the ndPolygonSoupBuilder.End() function.
As i understand, it does the following steps:

1. Merge coplanar triangles to larger, but convex polygons if optimize is on.
That's really cool, but my meshes won't have exact coplanar faces, so i can skip this step.

2. Calculate poly normals. That's no problem.

3. Merge equal / close vertices, and create per face vertex index lists.
I have a half edge data structure without duplicated vertices, so that's easy to generate for me.

So far so good, then the next step is:

Code: Select all
ndShapeInstance shape(new ndShapeStatic_bvh(meshBuilder));
Which brings me to the parts in question:

ndShapeStatic_bvh::ndShapeStatic_bvh(const ndPolygonSoupBuilder& builder)
   :ndShapeStaticMesh(m_boundingBoxHierachy)
   ,ndAabbPolygonSoup()
   ,m_trianglesCount(0)
{
   Create(builder);
   CalculateAdjacendy();


Create seems to build BVH, which i don't have to touch.

CalculateAdjacendy is the problem, because it won't find adjacent polygons across the boundary, which will end up in another ndShapeStatic_bvh.

Actually i can not really figure out which adjacency data you need at all, and where you store it.
I see there are edge normals calculated, but i don't get where they finally end up.
I don't see any handling of vertex normals at all, might just miss it.
And most importantly: I don't see any real adjacency information in form of indices or pointers at at all.

I would expect something like two face indices per edge for example.
That's where i expect the show stopper: If you use indices within the current ndShapeStatic_bvh, i could not set those indices to faces of another, adjacent ndShapeStatic_bvh.
If you use pointers, then i could. But then i'd still need to set those pointers to null in case the adjacent block is currently missing because not yet streamed in. Once it becomes streamed in, i'd need to reset the pointers.

Thus i hope you don't need such adjacency information, but only proper normals precomputed from respecting all blocks, which would be easy to do.

Maybe you confirm this with the quote:
it doesn't, it expects a clean vertex, normal, index and shared edge mesh intersecting an aabb box in some region of the space. for that there are some subclasses and some examples how to use them.

But i'm still unsure about the exact meaning of 'shared edge'.

I hope you can help me further... ;)
User avatar
JoeJ
 
Posts: 1453
Joined: Tue Dec 21, 2010 6:18 pm

Re: build static mesh overview

Postby Julio Jerez » Sun Jul 31, 2022 8:33 am

If you are going to used bvh,
You can disable some of the mesh conditioning passes. By passing false in the optimize flag of the mesh builder.

On the shared edge.
The thing that you have to realize is that a polygon on a mesh either share edges with another polygons or not.
This is a fact.

Once you agree to that, them what matters is what information you want to encode to know if the edge of a face are shared with edged of another face or not.

You can use a half edge structure, and that's elegant and can be used for many thing. In fact newton used it for coditioning the mesh in the mesh builder, and ot g er things.
But you can also use other data structure that encode what it's needed not everything.

In newton, at the end of the queries, what it's needed is a triangle with normal, material ID, and if an edge point to another polygon normal.

So what the bvh store is
An array of vertices
An array of normal,

Each polygon is made of an array of
The indices of each vertex,
The index to the face normal
The indices to the normal of the other face shared by each edge
An index for material ID.
An index to the quatized surface area.

With this information per polygon. The collision system has everything that it needs to know to generate proper contact point for each polygon found by the query.

The collision does not need to know the topology of the mesh, only if the shared edge is concave or convex
And for that, all that is needed is to set indices to concave edge to be open, like asigning a negative one.

So as you can see, a polygon that does not has its edge shared, behave like a concave face of a mesh

So to answer you question, it is two set of indices,
One that point to vertices, and one that point to the normal of the shaded faces.
Julio Jerez
Moderator
Moderator
 
Posts: 12249
Joined: Sun Sep 14, 2003 2:18 pm
Location: Los Angeles

Re: build static mesh overview

Postby JoeJ » Sun Jul 31, 2022 9:41 am

Ok thanks, it should be clear now. :)

I think i can easily solve this by adding face normals representing adjacent but missing faces across the boundary, and updating all open edge (negative) indices accordingly.

Shouldn't be that hard, but i'll come back if i get stuck on some detail.
User avatar
JoeJ
 
Posts: 1453
Joined: Tue Dec 21, 2010 6:18 pm

Re: build static mesh overview

Postby JoeJ » Fri Aug 05, 2022 12:44 pm

I struggle with understanding ndPolygonSoupBuilder.m_normalIndex

I assume it follows the same convention as m_vertexIndex, which seems:
Code: Select all
// 3 vertex indices per triangle, followed by a zero:
6,5,10, 0,
// next triangle:
10,5,8, 0,
...


This works, as i get the correct mesh visualized from the ndShapeDebugNotify.

And i use the same order for adjacent face normals, assuming:
Code: Select all
// face index for edge v0-v1
5, // if this edge was open, we would set -1
// face index for edge v1-v2
6,
// face index for edge v2-v0
15,
// eventually a terminating 0 again?
0,
// next triangle...


But it does not work.
I trace a ray for picking which shows me the hitpoint (works),
and the normal of the hit face (does not work - shows a random normal probably from another face)

Testing a regular mesh built as usual, it seems there are no terminating zeroes in your m_normalIndex array.
And i saw m_normalIndex even has a different array size than m_vertexIndex, which adds to my confusion.
Please clarify ;)

Edit: I'm also confused by the terminating 0 although it works.
What would happen if the second triangle has vertex 0 at it's first? A list might look like this:
1,2,3, 0, 0,4,7, 0,

Instead i would expect a prefix sum over all polygons vertex counts to index the list per triangle:
0,3,6,7...
Per triangle we would calculate the list like so:
int listBegin = prefixSum[faceIndex];
int listEnd = prefixSum[faceIndex+1];

So, besides, i wonder why you need those zeroes?





Code: Select all
class StaticWorldCellBuilder : public ndPolygonSoupBuilder
   {
   public:
      void FromData (const JJ::FileFormats::PhysicsStaticWorldCollisionCell &data)
      {
         ndInt32 vertexCount = data.vertexPositions.size();
         {
            m_vertexPoints.SetCount(vertexCount);
            const JJ::FileFormats::PhysicsStaticWorldCollisionCell::vec *v = data.vertexPositions.data();
            for (ndInt32 i=0; i<vertexCount; i++) {
               m_vertexPoints[i] = ndVector((*v)[0], (*v)[1], (*v)[2], 1.f); v++; }
         }

         ndInt32 normalCount = data.faceNormals.size(); // note: larger than faceCount due to shared boundaries
         {
            m_normalPoints.SetCount(normalCount);
            const JJ::FileFormats::PhysicsStaticWorldCollisionCell::vec *v = data.faceNormals.data();
            for (ndInt32 i=0; i<normalCount; i++) {
               m_normalPoints[i] = ndVector((*v)[0], (*v)[1], (*v)[2], 0.f); v++; }
         }

         ndInt32 faceCount = data.vertexIndicesLists.size() / 3;
         {
            m_faceVertexCount.SetCount(faceCount);
            for (ndInt32 i=0; i<faceCount; i++)
               m_faceVertexCount[i] = 3 + 1;//ok
         }
         
         /*ndInt32 IndicesCount = data.vertexIndicesLists.size();
         
         {
            m_vertexIndex.SetCount(IndicesCount);
            m_normalIndex.SetCount(IndicesCount);
            for (ndInt32 i=0; i<IndicesCount; i++)
            {
               m_vertexIndex[i] = data.vertexIndicesLists[i];
               m_normalIndex[i] = data.adjacencyNormalLists[i];
            }
         }
         */

         // convert to 0 terminated list per triangle
         {
            m_vertexIndex.SetCount(faceCount*4);
            m_normalIndex.SetCount(faceCount*4);
            for (ndInt32 i=0; i<faceCount; i++)
            {
               for (ndInt32 j=0; j<3; j++)
               {
                  m_vertexIndex[i*4+j] = data.vertexIndicesLists[i*3+j];
                  m_normalIndex[i*4+j] = data.adjacencyNormalLists[i*3+j];
               };
               m_vertexIndex[i*4+3] = 0;
               m_normalIndex[i*4+3] = 0;
            }
         }
      }
   };
User avatar
JoeJ
 
Posts: 1453
Joined: Tue Dec 21, 2010 6:18 pm

Re: build static mesh overview

Postby JoeJ » Fri Aug 05, 2022 4:21 pm

I struggle with understanding ndPolygonSoupBuilder.m_normalIndex


Oh, just found out that's not related to adjacency at all.
It only maps faces to their normals. So, never mind.

But unfortunately this means i can not give adjacency information to ndPolygonSoupBuilder, and i have to look deeper down, probably at ndShapeStatic_bvh directly...
User avatar
JoeJ
 
Posts: 1453
Joined: Tue Dec 21, 2010 6:18 pm

Re: build static mesh overview

Postby Julio Jerez » Fri Aug 05, 2022 5:35 pm

there are no zeros, what you see in material index, whis will be set to a default 0, is not set
you can fine the format of the indices.
in file ../\newton-4.00\sdk\dCore\ndAabbPolygonSoup.h

// index format: i0, i1, i2, ... , id, normal, e0Normal, e1Normal, e2Normal, ..., faceSize
#define D_CONCAVE_EDGE_MASK (1<<31)

if a face edge is not adjacent to another face, the e0Normal{i) will be a negative number

example, say a face is a quad, then the index list will be

the vertex indices: i0, i1, i2, i3
next the face id (material): id
next the face normal: n
next the face normal of the adjacent face to that edge: n0, n1, n2, n3
next the area in unit squared of the face: a

that the total number of indices is

4 + 1 + 1 + 4 + 1 = 2 * n + 3

where n in the index vertex count.
Julio Jerez
Moderator
Moderator
 
Posts: 12249
Joined: Sun Sep 14, 2003 2:18 pm
Location: Los Angeles

Re: build static mesh overview

Postby Julio Jerez » Fri Aug 05, 2022 5:39 pm

Oh, just found out that's not related to adjacency at all.
It only maps faces to their normals. So, never mind.


n and n(i) point to the same array of normals. is just and array of vectors of the same types, not need to have them separate.
Julio Jerez
Moderator
Moderator
 
Posts: 12249
Joined: Sun Sep 14, 2003 2:18 pm
Location: Los Angeles

Re: build static mesh overview

Postby JoeJ » Sat Aug 06, 2022 4:49 am

Code: Select all
// index format: i0, i1, i2, ... , id, normal, e0Normal, e1Normal, e2Normal, ..., faceSize

Haha, i was already arriving at the assumption you use some interleaved list format, but i did not spot this comment on top of the file :D
So thanks, that's exactly what i needed to know.

Now i got quickly stuck at the barriers of encapsulation.
At least that's how you guys call it. I call it 'hide your spaghetti while still having the same amount of noodles regardless' :mrgreen:

Seems i have to modify Newton source quite heavily, so probably i'll request some minor changes after i get it to work...
User avatar
JoeJ
 
Posts: 1453
Joined: Tue Dec 21, 2010 6:18 pm

Re: build static mesh overview

Postby JoeJ » Sat Aug 06, 2022 1:48 pm

Wow, i tried all kind of hacks. But got stuck every time. I was desperate.

But then i found a really easy solution, not requiring to change any Newton source.
Basically i first put my data to ndPolygonSoupBuilder, excluding adjacency.
Then make ndShapeStatic_bvh from that as usual.
Finally change adjacency indices.

Tried this yesterday already, but it looked like Newton did reorder my data, so my adjacency information would be no longer no longer valid.
But probably i made some mistake on this observation.

I still need to test it to see if boundary collisions behave as if it would be a single, merged mesh. But looks good so far and i'm optimistic. :D


My only request would be a extra parameter to ndShapeStatic_bvh constructor, to prevent calling CalculateAdjacendy(). I guess that's an expensive function, but redundant in my case, as i set this afterwards from precomputed data.
Though, i see such extra parameter would look pretty confusing to most people.

Here's what i got.
It accesses m_indices which are private, but i'll solve this by tricks of calculating its memory address.
Code: Select all
   ndBodyDynamic* BuildStaticPatch (ndWorld& world, const ndMatrix matrix, const JJ::FileFormats::PhysicsStaticWorldCollisionCell &data)
   {
      ndPolygonSoupBuilder meshBuilder;
      {
         ndInt32 vertexCount = data.vertexPositions.size();
         {
            meshBuilder.m_vertexPoints.SetCount(vertexCount);
            const JJ::FileFormats::PhysicsStaticWorldCollisionCell::vec *v = data.vertexPositions.data();
            for (ndInt32 i=0; i<vertexCount; i++) {
               meshBuilder.m_vertexPoints[i] = ndVector((*v)[0], (*v)[1], (*v)[2], 1.f); v++; }
         }

         ndInt32 normalCount = data.faceNormals.size(); // note: larger than faceCount due to shared boundaries
         {
            meshBuilder.m_normalPoints.SetCount(normalCount);
            const JJ::FileFormats::PhysicsStaticWorldCollisionCell::vec *v = data.faceNormals.data();
            for (ndInt32 i=0; i<normalCount; i++) {
               meshBuilder.m_normalPoints[i] = ndVector((*v)[0], (*v)[1], (*v)[2], 0.f); v++; }
         }

         ndInt32 faceCount = data.vertexIndicesLists.size() / 3;
         meshBuilder.m_faceVertexCount.SetCount(faceCount);
         for (ndInt32 i=0; i<faceCount; i++)
            meshBuilder.m_faceVertexCount[i] = 3 + 1;
         
         meshBuilder.m_vertexIndex.SetCount(faceCount*4);
         for (ndInt32 i=0; i<faceCount; i++)
         {
            for (ndInt32 j=0; j<3; j++)
               meshBuilder.m_vertexIndex[i*4+j] = data.vertexIndicesLists[i*3+j];
            meshBuilder.m_vertexIndex[i*4+3] = data.faceMaterials[i];
         }
         
         meshBuilder.m_normalIndex.SetCount(faceCount);
         for (ndInt32 i=0; i<faceCount; i++)
            meshBuilder.m_normalIndex[i] = i;
      }

      auto *bvh = new ndShapeStatic_bvh(meshBuilder); // todo: CalculateAdjacendy() is wasted work
      ndShapeInstance shape(bvh);

      // adjacency...
      {
         constexpr int VIND = 0;
         constexpr int MAT = 3;
         constexpr int FNORM = 4;
         constexpr int NIND = 5;
         constexpr int AREA = 8;
         constexpr int SIZE = 9;

         ndShapeStatic_bvh *shape = bvh;
         ndInt32* indices = shape->m_indices;
         ndInt32 vertexCount = data.vertexPositions.size();
         ndInt32 faceCount = data.vertexIndicesLists.size() / 3;
               
         ndInt32 minNI = INT_MAX;
         ndInt32 minVI = INT_MAX;
         for (ndInt32 i=0; i<faceCount; i++)
         {
            ndInt32* dstFaceList = indices + i * SIZE;
            ndInt32* dstVertexIndices = dstFaceList + VIND;
            ndInt32* dstAdjNormIndices = dstFaceList + NIND;

            ndInt32 srcFaceIndex = dstFaceList[FNORM] - vertexCount;

            const int* srcVertexIndices = data.vertexIndicesLists.data() + srcFaceIndex * 3;
            const int* srcAdjNormIndices = data.adjacencyNormalLists.data() + srcFaceIndex * 3;

            bool match = (   dstVertexIndices[0] == srcVertexIndices[0] &&
                        dstVertexIndices[1] == srcVertexIndices[1] &&
                        dstVertexIndices[2] == srcVertexIndices[2] );

            assert(match);

            if (match) for (ndInt32 j=0; j<3; j++)
            {
               assert (dstAdjNormIndices[j] < 0 || dstAdjNormIndices[j] == srcAdjNormIndices[j] + vertexCount); // check agreement with CalculateAdjacendy() results

               dstAdjNormIndices[j] = srcAdjNormIndices[j] + vertexCount;
            }
         }
      }

      ndBodyDynamic* const body = new ndBodyDynamic();
      //body->SetNotifyCallback(new ndDemoEntityNotify(scene, entity));
      body->SetMatrix(matrix);
      body->SetCollisionShape(shape);
      world.AddBody(body);

      return body;
   }
User avatar
JoeJ
 
Posts: 1453
Joined: Tue Dec 21, 2010 6:18 pm

Re: build static mesh overview

Postby Julio Jerez » Sat Aug 06, 2022 4:00 pm

I do not understand why you has so much problem trying to secund guess how
ndShapeStatic_bvh works.

that collision shape is designed to take an unstructured array of polygons, where each polygon is an array of vertices that falls on a flat plane. The polygons can be concave.
the class will find out all the information, adjacency, normal and topology.

It will remove degenerate faces and edges,
It will make a Delaney triangular mesh to make each triangle area maximal.
It will optimize flat adjacent phases into large faces if possible.
it will from convex partitions to removed contact across flat edges.
and will make adjacency. and so on.
there is a lot going on, on that class.
So yes, the index list will be very different that when you pass in.

if you want to make a custom collision shape similar but using different algorithm because you have your all method, that is quite possible, but you are going the wrong way.
you have four options.
The base class for static mesh is class. ndShapeStaticMesh

the engine implements three subclasses: ndShapeStatic_bvh, ndShapeHeightfield and ndShapeStaticProceduralMesh

deriving or changing ndShapeStatic_bvh and do not recommend and in fact will be a no, no
because it goes against object objective programing.
deriving from ndShapeHeightfield is non sensical because height filed capitalize on the regular organization of a 2d array of elevation, so it knows fow to make the intersecting faces.

so you are left with two options.

the easiest is to subclass from ndShapeStaticProceduralMesh
this class already provide some of the support function that will be requires if you derive from
ndShapeStaticMesh directly. stuff like ray casting, convex cast, support vertex, multiread queries, and so on, you only need to implement one method.

the more advanced option is to derive from ndShapeStaticMesh, and you will have to do all the work implementing support functions.
This probably makes sense for some special classes of geometries, liek it seems it is you case.
an example would be stuff like iso surfaces, or meshes when everything is very unique.
my suggestion is that is you want to make a shape like the ndShapeStatic_bvh

you sub class from ndShapeStaticMesh and copy and paste the code from ndShapeStatic_bvh

that will give you and class that you can experiment with and build your adjacency the way you think is best. that way you do not have to respect how the engine class does it now, and you can use whatever you think is useful to your class.

unlike Newton 3.14 that new shapes required registration, newton 4 only requires that a new shape is subclass from one of the archetypes: ndShapeConvex or ndShapeStaticMesh

for example, the code to make a flat plane collision shape is this

Code: Select all
ndBodyKinematic* BuildFlatPlane(ndDemoEntityManager* const scene, bool optimized)
{
   ndPhysicsWorld* const world = scene->GetWorld();
   ndVector floor[] =
   {
      { 200.0f, 0.0f,  200.0f, 1.0f },
      { 200.0f, 0.0f, -200.0f, 1.0f },
      { -200.0f, 0.0f, -200.0f, 1.0f },
      { -200.0f, 0.0f,  200.0f, 1.0f },
   };
   ndInt32 index[][3] = { { 0, 1, 2 },{ 0, 2, 3 } };

   ndPolygonSoupBuilder meshBuilder;
   meshBuilder.Begin();
   meshBuilder.AddFaceIndirect(&floor[0].m_x, sizeof(ndVector), 31, &index[0][0], 3);
   meshBuilder.AddFaceIndirect(&floor[0].m_x, sizeof(ndVector), 31, &index[1][0], 3);
   meshBuilder.End(optimized);

   ndShapeInstance plane(new ndShapeStatic_bvh(meshBuilder));

nowhere is assume indices order, normal or adjacency.

the code to make the same flat plane procedural, would be this
Code: Select all
ndBodyKinematic* BuildProceduralMap(ndDemoEntityManager* const scene, ndInt32 grids, ndFloat32 gridSize, ndFloat32 perturbation)
{
   ndPlane planeEquation(ndVector(0.0f, 1.0f, 0.0f, 0.0f));
   ndShapeInstance plane(new ndRegularProceduralGrid(gridSize, 2.0f * grids * gridSize, 1.0f, 2.0f * grids * gridSize, planeEquation));


you see that ndRegularProceduralGrid is a shape make on the application side for demonstration. and that class is designed in file
.../newton-4.00\applications\ndSandbox\toolbox\ndMakeProceduralStaticMap.cpp line 22

I do not know how else to make it clearer.
Julio Jerez
Moderator
Moderator
 
Posts: 12249
Joined: Sun Sep 14, 2003 2:18 pm
Location: Los Angeles

Re: build static mesh overview

Postby JoeJ » Sat Aug 06, 2022 8:39 pm

I do not understand why you has so much problem trying to secund guess how
ndShapeStatic_bvh works.

That's not my problem. The problem is that ndShapeStatic_bvh does not work for me, and that it lacks the flexibility to make it work without hacks.
Not sure if the rules of OOP dictate to make general functionality inaccessible, except for the certain, single specific application which motivated it's initial development, e.g. making BVH over geometry.
Here is what i would expect, now being smarter in retrospect:

Code: Select all
// task: make static mesh as usual, using a geometry builder
builder.AddPolygon()...
builder.DoGeometryPostprocessing(); // optimize

BVH_mesh.TransferDataFromBuilder(builder);
BVH_mesh.BuildBVH(); // notice i propose to split your Create() method into data transfer and building BVH
BVH_mesh.FindAdjacency();


That's almost how your pipeline works.
But if the user has some geometry pipeline to process assets, or to create automated proxy geometry, etc., this user wants to do something like that:

Code: Select all
// task: make static mesh from pre-processed data, already optimized, and respecting Newtons data structures
BVH_mesh.SetInterleavedIndices(data.indices);
BVH_mesh.SetVerticesAndNormals(data.verticesAndNormals);

BVH_mesh.BuildBVH();


Even if this user has no such boundary special cases like me, the second approach will be much faster, and maybe it's not possible to use serialized BVH due to storage limits, so this actually matters.

But it's not possible, due to the rules of OOP?
If so, that's bad rules imo. I mean, all the functionality above is there, but it can't be used.
To make them accessible and as flexible as i want, i'd need to change too much of your code. Multiple files. Too much work to maintain this on future changes on your side, so as you say, that's no option.

the more advanced option is to derive from ndShapeStaticMesh, and you will have to do all the work implementing support functions.


This means i have to implement my own acceleration structure plus ray tracing, range queries, etc.
Although you have all this already, well optimized for your application.
You have to admit that's actually a bad proposal. No - just want to use my geometry with your BVH, ofc.

Although in my case i might indeed try this in the future. Not because i'm not happy with the current hack (i am), but because i already have my BVH for this geometry. So by reusing that, i could half storage requirements by storing just one acceleration structure.
However, i guess your's is faster, so i hope that's not needed.

But all this just said to provide some constructive critique!

Code: Select all
So yes, the index list will be very different that when you pass in.

I've tested with a larger scene made from 15 parts, and so far the order remains consistent.
I bypass optimizations the builder would do, and i hope the bvh creation does no (or finds no) further changes.
If it happens, i can just search to remap the data. All this goes to the preprocessing side, so a slowdown in few cases would not matter.
User avatar
JoeJ
 
Posts: 1453
Joined: Tue Dec 21, 2010 6:18 pm

Re: build static mesh overview

Postby Julio Jerez » Sat Aug 06, 2022 9:40 pm

when I say rules of object-oriented programing. I mean each class is a descendent of some common class, there will share some common properties but also has some unique properties.
For example, say you have Cats and Dogs, they are both descendent from the Carnivora fila,
they share many traits: work in for legs, eat meat, has hair fur and so on.
but there also has unique traits that make them different
Dog Bark, cat do not. Cats climb trees Dogs do not. etc.

So if you are going to make a program that simulate cats, you start with a carnivora class.
and you add the shared traits. them you code the cat as a sub class of Carnivora class.
Them if someone come on later to implement the Dog class,
that person does not take the Cat class and make changes so that it acts as a Cat and also a dog.
that person should start with a Carnivora class and sub class the Dog class.
what the person can do, is that he or she can copy the code from the cat, and change it a so be Dog.

Anyway for what I can see, we I get form what you explain is that class ndShapeStatic_bvh does what you need, but the builder is the problem.

If this is correct, then you only need to override the builder, thsi class below

Code: Select all
class ndPolygonSoupBuilder: public ndClassAlloc
{
   class dgFaceMap;
   class dgFaceInfo;
   class dgFaceBucket;
   class dgPolySoupFilterAllocator;
   public:

   D_CORE_API ndPolygonSoupBuilder ();
   D_CORE_API ndPolygonSoupBuilder (const ndPolygonSoupBuilder& sopurce);
   D_CORE_API virtual ~ndPolygonSoupBuilder ();

   D_CORE_API virtual void Begin();
   D_CORE_API virtual void End(bool optimize);
   D_CORE_API virtual void AddFace(const ndFloat32* const vertex, ndInt32 strideInBytes, ndInt32 vertexCount, const ndInt32 faceId);
   D_CORE_API virtual void AddFaceIndirect(const ndFloat32* const vertex, ndInt32 strideInBytes, ndInt32 faceId, const ndInt32* const indexArray, ndInt32 indexCount);


there you only need to implement these four functions
Code: Select all
virtual void Begin();
virtual void End(bool optimize);
virtual void AddFace(const ndFloat32* const vertex, ndInt32 strideInBytes, ndInt32 vertexCount, const ndInt32 faceId);
virtual void AddFaceIndirect(const ndFloat32* const vertex, ndInt32 strideInBytes, ndInt32 faceId, const ndInt32* const indexArray, ndInt32 indexCount);


to populate the array of data that encode that collision mesh.
them as long as the data is formatted as the engine expect. the collision shape will be built correctly.

tell me if I got that wrong.

if I am right, I added the virtual functionality to ndPolygonSoupBuilder
so that is possible to do that,
you should sync to get later

one of the bad thing about the current builder, is that is no fast.
this class has not changed much since Newton 1.00, other that optimizations and bug fixes.
but that has no made very fast.
as result some user of newton, Penumbra as I remember, use the user mesh, because if was no fast enough for the for using in an editor.
So the end up making one where the build and skip optimization because they already had optimized index list, vertex list mesh.

it seems you are in similar situation; your mesh has way to get the information that the defula has to make form just the polygon list.
so subclassing ndPolygonSoupBuilder can be a good idea.
unless I am getting it all wrong.
Julio Jerez
Moderator
Moderator
 
Posts: 12249
Joined: Sun Sep 14, 2003 2:18 pm
Location: Los Angeles

Re: build static mesh overview

Postby Julio Jerez » Sat Aug 06, 2022 10:20 pm

I was looking at class ndPolygonSoupBuilder
and I see that is even has a save function that save the data in PLY format.
void ndPolygonSoupBuilder::SavePLY(const char* const fileName) const
I forgot about that.

if you decide to do what I said in previous post, you can save your data using that function,
and them load it with a mesh viewer to see if is right. many graphics packages can load PLY files
one that is quite small and easy is https://www.meshlab.net/

also you can see that than function just read the data as it is saved on those vectors.
so that should give you a good idea how to convert you mesh. because you already have it optimized.

do not forget to sync,
Julio Jerez
Moderator
Moderator
 
Posts: 12249
Joined: Sun Sep 14, 2003 2:18 pm
Location: Los Angeles

Next

Return to General Discussion

Who is online

Users browsing this forum: No registered users and 51 guests