Thats sounds right, although frame rate so far for 1280 patches with 256 tris each is acceptable. Of course I could lower that. The number is based on the hypothetical dream of 1280 mb x 262144 tri's to get a maximum of 335 million triangles in the whole un LODed heightmap (that would be level 13 and each patch on the highest level, which would normally of course never happpen), that would cover the
data available... I know, I'd need heaploads of Ram for that, and it wouldnt even work then. Sometimes I wish I would live a couple of years later.
As I just thought now, once the LOD determiation is done I could stitch one (1) meshbuffer from the selected patches and render that. This way I could also solve the normal discontinuity problem I have between the meshbuffers, plus it would allow for some real real ROAM stitching between patches of different level, if I ever get that far. Unfortunately that seems little bit hard atm. I guess I finish the LOD determination first and see what data presents itself for rendering.
For the LOD determination I thought I would make a simple sorted list of all patches, sorted ascending by distance from camera and find a suitable level according to that data. However camera view direction would be completely disregarded in that case. I tried to get around camera frustum and ray checks. Is the latter the recommended way ?
Thx.
Next, day. I've listened to Bitplane and changed the design to the following.
First I subdivide to the highest Level, which with my Ram is 5,242,880 faces, save the vertices and then subdivide again this time level for level, each time saving the Indices for each of the 1280 patches into an array:
Then I construct one Renderbuffer from the required patches and render that. Currently I can only construct a Renderbuffer for one uniform planet, with all patches of the same level. I'm going to start the LOD determination now and once finished I can construct a Renderbuffer consisting of patches from different levels as required.
Code: Select all
/*
* CWorldSceneNode.h
* creates tessilated Icosahedron.
*
* Created by torleif west on 8/10/10.
* Copyright 2010 doubleclique. All rights reserved.
*
*/
#include <irrlicht.h>
using namespace irr;
class CWorldSceneNode : public scene::ISceneNode
{
public:
CWorldSceneNode(scene::ISceneNode* parent,
scene::ISceneManager* mgr,
s32 id,
const video::IImage *bumpmap = 0)
: scene::ISceneNode(parent, mgr, id)
{
color = (255, 255, 255, 255);
Material.NormalizeNormals = true;
//this->setMaterialFlag(video::EMF_WIREFRAME, true);
//this->setMaterialFlag(video::EMF_LIGHTING, false);
f64 t = (1+sqrt(5.))/2.;
f64 tau = t/sqrt(1.+t*t);
f64 one = 1/sqrt(1.+t*t);
/*
t*= 100;
tau*= 100.;
one*= 100.;
*/
coreVertices = new scene::CVertexBuffer(video::EVT_STANDARD);
coreIndices = new scene::CIndexBuffer(video::EIT_32BIT);
planetBufferVertices = new scene::CVertexBuffer(video::EVT_STANDARD);
planetBufferIndices = new scene::CIndexBuffer(video::EIT_32BIT);
RenderBuffer = new scene::CDynamicMeshBuffer(coreVertices->getType(),coreIndices->getType());
//Alias Maya Platonic Solid Icosahedron vertex Order
coreVertices->push_back(video::S3DVertex( tau, 0, -one, 0,0,0, color, 1, 1));
coreVertices->push_back(video::S3DVertex( tau, -0, one, 0,0,0, color, 1, 1));
coreVertices->push_back(video::S3DVertex(-tau, -0, one, 0,0,0, color, 1, 1));
coreVertices->push_back(video::S3DVertex(-tau, 0, -one, 0,0,0, color, 1, 1));
coreVertices->push_back(video::S3DVertex( 0, -one, tau, 0,0,0, color, 1, 1));
coreVertices->push_back(video::S3DVertex( 0, one, tau, 0,0,0, color, 1, 1));
coreVertices->push_back(video::S3DVertex( 0, one, -tau, 0,0,0, color, 1, 1));
coreVertices->push_back(video::S3DVertex( 0, -one, -tau, 0,0,0, color, 1, 1));
coreVertices->push_back(video::S3DVertex(-one , -tau, -0, 0,0,0, color, 1, 1));
coreVertices->push_back(video::S3DVertex( one , -tau, -0, 0,0,0, color, 1, 1));
coreVertices->push_back(video::S3DVertex( one , tau, 0, 0,0,0, color, 1, 1));
coreVertices->push_back(video::S3DVertex(-one , tau, 0, 0,0,0, color, 1, 1));
/*
11 11 11 11 11
/\ /\ /\ /\ /\
/ \ / \ / \ / \ / \
/ \ / \ / \ / \ / \
/5 \/10 \/6 \/3 \/2 \5
----------------------------------------
\ /\ /\ /\ /\ /\
\ / \ / \ / \ / \ / \
\ / \ / \ / \ / \ / \
\/1 \/0 \/7 \/8 \/4 \1
----------------------------------------
\ /\ /\ /\ /\ /
\ / \ / \ / \ / \ /
\ / \ / \ / \ / \ /
\/9 \/9 \/9 \/9 \/9
This would be clockwise
11,10,5 5,10,1 3,8,7 9,1,0
11,6,10 10,0,1 3,2,8 9,0,7
11,3,6 10,6,0 2,4,8 9,7,8
11,2,3 6,7,0 2,5,4 9,8,4
11,5,2 6,3,7 5,1,4 9,4,1
unfortunately,
irrlicht faces are meant to be counterclockwise !
*/
// indices points for a Icosahedron
coreIndices->push_back(11);coreIndices->push_back(5);coreIndices->push_back(10);
coreIndices->push_back(11);coreIndices->push_back(10);coreIndices->push_back(6);
coreIndices->push_back(11);coreIndices->push_back(6);coreIndices->push_back(3);
coreIndices->push_back(11);coreIndices->push_back(3);coreIndices->push_back(2);
coreIndices->push_back(11);coreIndices->push_back(2);coreIndices->push_back(5);
coreIndices->push_back(5);coreIndices->push_back(1);coreIndices->push_back(10);
coreIndices->push_back(10);coreIndices->push_back(1);coreIndices->push_back(0);
coreIndices->push_back(10);coreIndices->push_back(0);coreIndices->push_back(6);
coreIndices->push_back(6);coreIndices->push_back(0);coreIndices->push_back(7);
coreIndices->push_back(6);coreIndices->push_back(7);coreIndices->push_back(3);
coreIndices->push_back(3);coreIndices->push_back(7);coreIndices->push_back(8);
coreIndices->push_back(3);coreIndices->push_back(8);coreIndices->push_back(2);
coreIndices->push_back(2);coreIndices->push_back(8);coreIndices->push_back(4);
coreIndices->push_back(2);coreIndices->push_back(4);coreIndices->push_back(5);
coreIndices->push_back(5);coreIndices->push_back(4);coreIndices->push_back(1);
coreIndices->push_back(9);coreIndices->push_back(0);coreIndices->push_back(1);
coreIndices->push_back(9);coreIndices->push_back(7);coreIndices->push_back(0);
coreIndices->push_back(9);coreIndices->push_back(8);coreIndices->push_back(7);
coreIndices->push_back(9);coreIndices->push_back(4);coreIndices->push_back(8);
coreIndices->push_back(9);coreIndices->push_back(1);coreIndices->push_back(4);
//store patch center in patchPos for LOD
u16 faces = coreIndices->size() / 3;
for (int i=0; i<faces; i++)
{
u32 index1 = coreIndices->operator[](i * 3);
u32 index2 = coreIndices->operator[](i * 3 + 1);
u32 index3 = coreIndices->operator[](i * 3 + 2);
patchPos[i] = core::plane3d<f32>(coreVertices->operator[](index1).Pos,
coreVertices->operator[](index2).Pos,
coreVertices->operator[](index3).Pos).Normal;
}
//copy base icosahedron for division
for(u32 c=0; c<coreVertices->size(); c++)
{
planetBufferVertices->push_back(coreVertices->operator[](c));
}
for(u32 c=0; c<coreIndices->size(); c++)
{
planetBufferIndices->push_back(coreIndices->operator[](c));
}
subdivide(9,planetBufferVertices,planetBufferIndices);
//Strange Vertex Color Bug
for(u32 cc=0; cc<planetBufferVertices->size(); cc++)
{
planetBufferVertices->operator[](cc).Color = video::SColor(255,255,255,255);
}
//calculate Texture and apply Heightmap
calculateNormals(planetBufferVertices,planetBufferIndices);
calculateTextureCords(planetBufferVertices,planetBufferIndices);
calculateBumpmap(bumpmap,planetBufferVertices,planetBufferIndices);
// bounding box
Box.reset(coreVertices->operator[](0).Pos);
for (s32 i=0; i<coreVertices->size(); ++i)
Box.addInternalPoint(coreVertices->operator[](i).Pos);
//-----------------------------------------------------------------------
//make 1280 base patches
subdivide(3,coreVertices,coreIndices);
//Save level 0
core::array< scene::CIndexBuffer*> levelPatchBuffer;
for(u32 i=0; i<1280; i++)
{
scene::CIndexBuffer *levelIndices = new scene::CIndexBuffer(video::EIT_32BIT);
levelIndices->push_back(coreIndices->operator[](i * 3));
levelIndices->push_back(coreIndices->operator[](i * 3 + 1));
levelIndices->push_back(coreIndices->operator[](i * 3 + 2));
levelPatchBuffer.push_back(levelIndices);
}
patchBuffer.push_back(levelPatchBuffer);
//tile and save concurrent levels
for (int s=1; s<7; s++)
{
subdivide(1,coreVertices,coreIndices);
u32 indiSize = coreIndices->size();
u32 faces = indiSize / 3;
u32 facesPerPatch = (u32)pow(4.,s);
printf("Faces %d\n",faces);
printf("should %d\n",1280*facesPerPatch);
u32 current = 0;
core::array< scene::CIndexBuffer*> levelPatchBuffer;
for (u32 p=0; p<1280; p++)
{
scene::CIndexBuffer *levelIndices = new scene::CIndexBuffer(video::EIT_32BIT);
for(u32 fp=0; fp<facesPerPatch; fp++)
{
levelIndices->push_back(coreIndices->operator[](current * 3));
levelIndices->push_back(coreIndices->operator[](current * 3 + 1));
levelIndices->push_back(coreIndices->operator[](current * 3 + 2));
current++;
}
levelPatchBuffer.push_back(levelIndices);
}
patchBuffer.push_back(levelPatchBuffer);
}
for(u32 k=0; k<patchBuffer.size(); k++)
{
u32 size = patchBuffer[k].size();
u32 eachSize = patchBuffer[k][0]->size()/3;
u32 lastRefFace = (patchBuffer[k][size-1]->operator[](patchBuffer[k][size-1]->size()-1)/3)+1;
printf("planetBuffer %d size = %d with %d each. Last Face %d\n",k,size, eachSize,lastRefFace);
}
coreIndices->drop();
coreVertices->drop();
//debug
for(u32 a=0; a<20; a++)
{
scene::ISceneNode* axis = SceneManager->addCubeSceneNode(0.07);
axis->setPosition(patchPos[a]);
axis->setMaterialFlag(video::EMF_WIREFRAME,true);
//axis->setMaterialFlag(video::EMF_BACK_FACE_CULLING, false);
axis->setMaterialFlag(video::EMF_LIGHTING,false);
}
constructRenderBuffer(2);
}
void constructRenderBuffer(u32 level)
{
scene::CIndexBuffer* RenderIndices = new scene::CIndexBuffer(video::EIT_32BIT);
//scene::CVertexBuffer* RenderVertices = new scene::CVertexBuffer(video::EVT_STANDARD);
u32 num_patches = patchBuffer[level].size();
for(u32 p=0; p<num_patches; p++)
{
u32 num_indices = patchBuffer[level][p]->size();
for(u32 i=0; i<num_indices; i++)
{
RenderIndices->push_back(patchBuffer[level][p]->operator[](i));
}
}
calculateNormals(planetBufferVertices,RenderIndices);
RenderBuffer->setVertexBuffer(planetBufferVertices);
RenderBuffer->setIndexBuffer(RenderIndices);
printf("\nRenderBuffer Debug\n");
u32 should = 1280 * (level)*4;
u32 are = RenderIndices->size()/3;
printf("Should be %d, are %d\n",should,are);
}
/**
* subdivides this Icosahedron
*/
void subdivide(u32 subdivisions, scene::CVertexBuffer *currentVertices, scene::CIndexBuffer *currentIndices) {
for(u32 s=0; s<subdivisions; s++)
{
u32 faces = currentIndices->size() / 3;
for (int i = 0; i < faces; i++) {
u32 index1 = currentIndices->operator[](i * 3);
u32 index2 = currentIndices->operator[](i * 3 + 1);
u32 index3 = currentIndices->operator[](i * 3 + 2);
video::S3DVertex v1 = currentVertices->operator[](index1);
video::S3DVertex v2 = currentVertices->operator[](index2);
video::S3DVertex v3 = currentVertices->operator[](index3);
core::vector3df midpoint1 = v1.Pos.getInterpolated(v2.Pos, .5);
core::vector3df midpoint2 = v2.Pos.getInterpolated(v3.Pos, .5);
core::vector3df midpoint3 = v3.Pos.getInterpolated(v1.Pos, .5);
// each iteration we add three faces, 3 new Vertices
currentVertices->push_back(video::S3DVertex(midpoint1, midpoint1.normalize(), color, core::vector2df (0,0)));
currentVertices->push_back(video::S3DVertex(midpoint2, midpoint2.normalize(), color, core::vector2df (0,0)));
currentVertices->push_back(video::S3DVertex(midpoint3, midpoint3.normalize(), color, core::vector2df (0,0)));
// add the new face
currentIndices->push_back(currentVertices->size()-3);
currentIndices->push_back(currentVertices->size()-2);
currentIndices->push_back(currentVertices->size()-1);
// shrink the orginal poloygon to the new smaller size
currentIndices->setValue(i * 3 + 1, currentVertices->size()-3); //index 2 -> midpoint 1
currentIndices->setValue(i * 3 + 2, currentVertices->size()-1); //index 3 -> midpoint 3
// add the two remaining faces
currentIndices->push_back(index3);
currentIndices->push_back(currentVertices->size()-1);
currentIndices->push_back(currentVertices->size()-2);
currentIndices->push_back(index2);
currentIndices->push_back(currentVertices->size()-2);
currentIndices->push_back(currentVertices->size()-3);
}
}
}
/**
* sets the size of the planet
* @param r <float> the size of the planet
*/
void setRadius(const f32 r) {
setScale(core::vector3df (r,r,r));
radius = r;
}
/**
* returns the size of the planet
* @return <float> the size of the planet
*/
f32 getRadius () const {
return radius;
}
virtual void OnRegisterSceneNode()
{
if (IsVisible)
SceneManager->registerNodeForRendering(this);
ISceneNode::OnRegisterSceneNode();
}
/*
* In the render() method most of the interesting stuff happens: The
* Scene node renders itself. We override this method and draw the
* tetraeder.
*/
virtual void render()
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setTransform (video::ETS_WORLD, core::IdentityMatrix);
driver->setMaterial(this->getMaterial(0));
/*
u32 level = 3;
u32 bSize = planetBuffer[level].size();
for(u32 i = 0; i<bSize; i++)
{
driver->drawMeshBuffer(planetBuffer[level][i]);
}
*/
driver->drawMeshBuffer(RenderBuffer);
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
}
/*
And finally we create three small additional methods.
irr::scene::ISceneNode::getBoundingBox() returns the bounding box of
this scene node, irr::scene::ISceneNode::getMaterialCount() returns the
amount of materials in this scene node (our tetraeder only has one
material), and irr::scene::ISceneNode::getMaterial() returns the
material at an index. Because we have only one material here, we can
return the only one material, assuming that no one ever calls
getMaterial() with an index greater than 0.
*/
virtual const core::aabbox3d<f32>& getBoundingBox() const
{
return Box;
}
virtual u32 getMaterialCount() const
{
return 1;
}
virtual video::SMaterial& getMaterial(u32 i)
{
return Material;
}
/**
* @return longitude of the vector. in radians.
*/
f32 getLongitude(const core::vector3df &pos) {
f64 phi = asin(pos.Y);
return (phi / core::PI64 + 0.5);
}
/**
* @return Latitude of the vector. in radians.
*/
f32 getLatitude(const core::vector3df &pos) {
f64 theta = atan2(pos.Z, pos.X);
return (theta / core::PI64 + 1.0) * 0.5;
}
private:
core::aabbox3d<f32> Box;
video::SMaterial Material;
video::SColor color;
f32 radius;
core::vector3df patchPos[1280];
core::array< core::array<scene::CIndexBuffer*> > patchBuffer;
scene::CVertexBuffer* coreVertices;
scene::CIndexBuffer* coreIndices;
scene::CVertexBuffer* planetBufferVertices;
scene::CIndexBuffer* planetBufferIndices;
scene::CDynamicMeshBuffer* RenderBuffer;
/**
* sets the normals, the texture cords and makes a seam in the
* mesh if the texture cords wrapp around the texture
*/
void calculateTextureCords(scene::CVertexBuffer *currentVertices,
scene::CIndexBuffer *currentIndices)
{
// create the seam & attach the texture cords
u32 isize = currentIndices->size();
u32 vsize = currentVertices->size();
for (u32 i=0; i<isize; i+=3)
{
video::S3DVertex* v1 = ¤tVertices->operator[](currentIndices->operator[](i+0));
video::S3DVertex* v2 = ¤tVertices->operator[](currentIndices->operator[](i+1));
video::S3DVertex* v3 = ¤tVertices->operator[](currentIndices->operator[](i+2));
core::vector3df v1up((getPosition() - v1->Pos).normalize());
core::vector3df v2up((getPosition() - v2->Pos).normalize());
core::vector3df v3up((getPosition() - v3->Pos).normalize());
v1->TCoords = core::vector2df (getLatitude (v1up), getLongitude (v1up));
v2->TCoords = core::vector2df (getLatitude (v2up), getLongitude (v2up));
v3->TCoords = core::vector2df (getLatitude (v3up), getLongitude (v3up));
}
for (u32 i=0; i<isize; i+=3)
{
// only one texture point will cross the texture wrap. We take
// the point, create a seam, then reset the texture point
fixTextureCordSeam(i+0, i+1,currentVertices,currentIndices);
fixTextureCordSeam(i+0, i+2,currentVertices,currentIndices);
fixTextureCordSeam(i+1, i+0,currentVertices,currentIndices);
fixTextureCordSeam(i+2, i+0,currentVertices,currentIndices);
}
}
void calculateBumpmap(const video::IImage *bumpmap,
scene::CVertexBuffer *currentVertices,
scene::CIndexBuffer *currentIndices)
{
// place bump map on world
if (bumpmap)
{
for (u32 i = 0 ; i < currentVertices->size(); i++)
{
core::vector2df cords (currentVertices->operator[](i).TCoords);
f32 cx = cords.X;
f32 cy = cords.Y;
if (cx < 0.f)
cx += 1.f;
if (cy < 0.f)
cy += 1.f;
u32 width = bumpmap->getDimension ().Width;
u32 height = bumpmap->getDimension ().Height;
video::SColor mcolor(bumpmap->getPixel(u32(cx * width) % width, u32(cy * height) % height));
f32 colorvalue = mcolor.getLuminance () / 255.0 / 10;
currentVertices->operator[](i).Pos = currentVertices->operator[](i).Pos.getInterpolated(getPosition(), 1.0 + colorvalue);
}
}
// recalculate normals
for (u32 i=0; i<currentIndices->size(); i+=3)
{
const core::vector3df normal = core::plane3d<f32>(currentVertices->operator[](currentIndices->operator[](i+0)).Pos,
currentVertices->operator[](currentIndices->operator[](i+1)).Pos,
currentVertices->operator[](currentIndices->operator[](i+2)).Pos).Normal;
currentVertices->operator[](currentIndices->operator[](i+0)).Normal = normal;
currentVertices->operator[](currentIndices->operator[](i+1)).Normal = normal;
currentVertices->operator[](currentIndices->operator[](i+2)).Normal = normal;
}
}
void calculateNormals(scene::CVertexBuffer *currentVertices,
scene::CIndexBuffer *currentIndices)
{
// recalculate normals
for (u32 i=0; i<currentIndices->size(); i+=3)
{
const core::vector3df normal = core::plane3d<f32>(currentVertices->operator[](currentIndices->operator[](i+0)).Pos,
currentVertices->operator[](currentIndices->operator[](i+1)).Pos,
currentVertices->operator[](currentIndices->operator[](i+2)).Pos).Normal;
currentVertices->operator[](currentIndices->operator[](i+0)).Normal = normal;
currentVertices->operator[](currentIndices->operator[](i+1)).Normal = normal;
currentVertices->operator[](currentIndices->operator[](i+2)).Normal = normal;
}
}
/**
* checks the cords, and if they are streched too much it will
* split the vertex and make a seam.
*/
void fixTextureCordSeam (const s32 index1,
const s32 index2,
scene::CVertexBuffer *currentVertices,
scene::CIndexBuffer *currentIndices)
{
video::S3DVertex* v1 = ¤tVertices->operator[](currentIndices->operator[](index1));
video::S3DVertex* v2 = ¤tVertices->operator[](currentIndices->operator[](index2));
f32 sensitivity = .7f;
if((v1->TCoords.X - v2->TCoords.X) > sensitivity)
{
core::vector2df newTcords(v1->TCoords);
newTcords.X -= 1.0f;
if(v1->TCoords.Y - v2->TCoords.Y > sensitivity)
{
newTcords.Y -= 1.0f;
}
currentVertices->push_back(video::S3DVertex(v1->Pos,v1->Normal, color, newTcords));
currentIndices->setValue(index1, currentVertices->size() - 1);
return;
}
if((v1->TCoords.Y - v2->TCoords.Y) > sensitivity)
{
core::vector2df newTcords(v1->TCoords);
newTcords.Y -= 1.0f;
currentVertices->push_back(video::S3DVertex(v1->Pos,v1->Normal, color, newTcords));
currentIndices->setValue(index1, currentVertices->size() - 1);
}
}
};