Procedurally generated geometry tutorial

From Horde3D Wiki
Revision as of 14:34, 28 November 2009 by Wakko (talk | contribs)
Jump to: navigation, search

Work in progress - Due to the current changes in the Horde API this tutorial will be delayed


Important notes

  • The code used here has been tested with Horde3D 1.0.0 beta2 and beta3 and will NOT work with the latest release (beta4)


Overview

Basic approach

To create and display a procedurally generated mesh that, instead of being loaded from a Horde3D Scene node and a geoemtry file(*.scene.xml, *.geo), will be created in-memory by some algorithm, you have to create a mesh first and then "fake" the .geo-file and the scene node itself and pass it to the engine.

To do that you have to create your procedural geometry first. This is easy for simple shapes like single triangles, squares or triangle-strips, but will become way more difficult when we try to generate complex meshes, e.g. when we have a cloud of points in 3D space and have to describe which points make triangles, and which don't (calculating the triangle indices, texture coordinates and normals).

When we have calculated the geometry itself we need to create a char-stream to make Horde believe the geometry has been read from a file and pass it to Horde as a geometry Resource. The final step is to create a Horde3D Scene Node from our procedurally generated mesh and render it into our scene. [Screenshot-1_small.jpg This is how it could look like]

StreamGenerator

These classes are only for creating the character stream that will contain the geometry resource.

StreamGenerator.h
#pragma once
#include <sstream>
#include <string>
#include <iostream>

class StreamGenerator 
{
public:
	StreamGenerator();
	virtual ~StreamGenerator();
	virtual const char* getStream() = 0;
	virtual const int getStreamSize() = 0;
};
StreamGenerator.cpp
#include "StreamGenerator.h"

StreamGenerator::StreamGenerator() 
{
}

StreamGenerator::~StreamGenerator()
{
}
GeometryStreamGenerator.h
#pragma once
#include "StreamGenerator.h"
#include "CustomGeometry.h"

class GeometryStreamGenerator : public StreamGenerator
{
private:
	std::stringstream* ss;
	void generate();
	int numVertices, numTriangleIndices;
	char* _stream;
	CustomGeometry* _geom;

public:
	GeometryStreamGenerator(CustomGeometry* geom_);
	~GeometryStreamGenerator();
	const char* getStream();
	const std::stringstream* getStringStream() { return ss; };
	const int getStreamSize();
	const int getNumVertices();
	const int getNumTriangleIndices();
	void release();
};
GeometryStreamGenerator.cpp
#include "GeometryStreamGenerator.h"
#include "time.h"
#include "utMath.h"

GeometryStreamGenerator::GeometryStreamGenerator(CustomGeometry* geom_) : _geom(geom_)
{
	ss = new std::stringstream();
	generate();
}

GeometryStreamGenerator::~GeometryStreamGenerator()
{
}

void GeometryStreamGenerator::generate()
{
	numVertices = _geom->getNumVertices();
	numTriangleIndices = _geom->getNumTriangleIndices();
	const std::string _magicHeader = "H3DG";
	const int _version = 5;
	const int _numJoints = 1;
	const float _identityMatrix[] = {	1.0f, 0.0f, 0.0f, 0.0f,  
						0.0f, 1.0f, 0.0f, 0.0f,  
						0.0f, 0.0f, 1.0f, 0.0f,  
						0.0f, 0.0f, 0.0f, 1.0f};
	const int _numVertexStreams = 3;
	const int _magicVertices = 0;
	const int _vertexStreamElementSize = 12;
	const float* _positions = _geom->getPositions();
	const int _magicNormals = 1;
	const int _normalsStreamElementSize = 6;
	const float* _normals = _geom->getNormals();
	const int _magicTextureCoords1 = 6;
	const int _texCoordStreamElementSize = 8;
	const float* _texCoords = _geom->getTexCoords();
	const int* _triangleIndices = _geom->getTriangleIndices();
	const int _numMorphTragets = 0;
	// header
	ss->write(_magicHeader.c_str(), 4*sizeof(char));
	ss->write((char*)&_version, sizeof(int));
	// joints (mandatory)
	ss->write((char*)&_numJoints, sizeof(int));
	for (int i=0; i<16; ++i) {
		ss->write((char*)&_identityMatrix[i], sizeof(float));
	}
	// vertices
	ss->write((char*)&_numVertexStreams, sizeof(int));
	const int numVerts = _geom->getNumVertices();
	ss->write((char*)&numVerts, sizeof(int));
	ss->write((char*)&_magicVertices, sizeof(int));
	ss->write((char*)&_vertexStreamElementSize, sizeof(int));
	for (int i=0; i<_geom->getNumVertices()*3; ++i) {
		ss->write((char*)&_positions[i], sizeof(float));
	}
	// normals
	ss->write((char*)&_magicNormals, sizeof(int));
	ss->write((char*)&_normalsStreamElementSize, sizeof(int));
	for (int i=0; i<_geom->getNumVertices()*3; ++i) {
		// do not forget to mutliply the value with 32767 and 
		// then convert it to short before writing to the stream
		const short f = (short)(_normals[i]*32767);
		ss->write((char*)&f, sizeof(short));
	}
	// texture coordinates
	ss->write((char*)&_magicTextureCoords1, sizeof(int));
	ss->write((char*)&_texCoordStreamElementSize, sizeof(int));
	for (int i=0; i<_geom->getNumVertices()*2; ++i) {
		ss->write((char*)&_texCoords[i], sizeof(float));
	}
	// triangle indices
	const int numTriangleIndices = _geom->getNumTriangleIndices();
	ss->write((char*)&numTriangleIndices, sizeof(int));
	for (int i=0; i<_geom->getNumTriangleIndices(); ++i) {
		ss->write((char*)&_triangleIndices[i], sizeof(int));
	}
	ss->write((char*)&_numMorphTragets, sizeof(int));
	_stream = new char[ss->tellp()];
	ss->read(_stream, ss->tellp());
}

const char* GeometryStreamGenerator::getStream()
{
	return _stream;
}

const int GeometryStreamGenerator::getStreamSize() 
{
	int t = (int)ss->tellp();
	return t;
}

const int GeometryStreamGenerator::getNumVertices()
{
	return numVertices;
}

const int GeometryStreamGenerator::getNumTriangleIndices()
{
	return numTriangleIndices;
}

void GeometryStreamGenerator::release()
{
	ss->flush();
	ss->clear();
	delete ss;
	ss = 0;
	delete[] _stream;
	_stream = 0;
}

CustomGeometry base class

Base class for the custom geometry. This class currently contains lots of deprecated stuff, e.g. the update() function. This function was used to update an in-memory gemoetry resource in the "punk-rock"-way. Until Horde3D Beta3 the geometry resources were read-only, so directmanipulation of vertices was impossible. To modify the vertices I had to unload the geometry resource, re-create it with the new vertex-positions and load it into Horde again. This is not necessary any more because since Beta4 the geometry resources are not read-only anymore.

All custom geometry classes should be derived from this class and implement the abstract functions as needed. (See the examples below)

CustomGeometry.h
#pragma once
/**
 * Abstract class for custom/dynamic geometry
 **/
#include "utMath.h"
#include "Horde3D.h"
#include <string>

class CustomGeometry 
{
protected:
	float _size;
	Vec3f _origin;
	int _numVertices;
	int _numTriangles;
	int _numTriangleIndices;
	float* _positions;
	float* _normals;
	float* _texCoords;
	int* _tIndices;
	std::string _name;
	NodeHandle _model;
	NodeHandle _parent;
	ResHandle _material;
	virtual void generatePositions() = 0;
	virtual void generateNormals() = 0;
	virtual void generateTexCoords() = 0;
	virtual void generateTriangleIndices() = 0;

public:
	CustomGeometry();
	virtual void generate() = 0;
	const int getNumVertices() { return _numVertices; }
	const float* getPositions() { return _positions; }
	const float* getNormals() { return _normals; }
	const float* getTexCoords() { return _texCoords; }
	const int* getTriangleIndices() { return _tIndices; }
	const int getNumTriangles() { return _numTriangles; }
	const int getNumTriangleIndices() { return _numTriangleIndices; }
	void release();
	const Vec3f getInterpolatedNormal(Vec3f n1_, Vec3f n2_);
	// pass -1 as the parameter to leave the parent node unchanged
	void addCustomModelNode(NodeHandle parent_);
	bool removeCustomModelNode();
	void setMaterial(ResHandle material_) { _material = material_; }
	void setParent(NodeHandle parent_) { _parent = parent_; }
	NodeHandle getModel() { return _model; }
	ResHandle getMaterial() { return _material; }
	void update();
	Vec3f getOrigin() { return _origin; }
	void setOrigin(Vec3f origin_) { _origin = origin_; }
	const float getSize() { return _size; }
};
CustomGeometry.cpp
#include "CustomGeometry.h"
#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include "GeometryStreamGenerator.h"
#include "Horde3DUtils.h"

CustomGeometry::CustomGeometry() : _name("default"), _size(1)
{
	_origin = Vec3f(0, 0, 0);
}

void CustomGeometry::release()
{
	delete[] _positions;
	_positions = 0;
	delete[] _normals;
	_normals = 0;
	delete[] _texCoords;
	_texCoords = 0;
	delete[] _tIndices;
	_tIndices = 0;
}

const Vec3f CustomGeometry::getInterpolatedNormal(Vec3f n1_, Vec3f n2_) 
{
	Vec3f newNormal = n1_ + n2_;
	float length = newNormal.length();
	return newNormal/length;
}

void CustomGeometry::addCustomModelNode(NodeHandle parent_)
{
	//std::stringstream* sa = new std::stringstream();
	std::stringstream ss;
	if (parent_ != -1) 
		_parent = parent_;
	ss<< "customGeometry-";
	ss<<_name;
	GeometryStreamGenerator* gsg = new GeometryStreamGenerator(this);
	ResHandle customGeoRes = Horde3D::addResource(ResourceTypes::Geometry, ss.str().c_str(), 0);
	Horde3D::loadResource(customGeoRes, gsg->getStream(), gsg->getStreamSize());
	// model_node_name
	ss<<"-Node";
	_model = Horde3D::addModelNode(_parent, ss.str().c_str(), customGeoRes);
	// mesh_name
	ss<<"-Mesh";
	Horde3D::addMeshNode(_model, ss.str().c_str(), _material, 0, gsg->getNumTriangleIndices(), 0, gsg->getNumVertices()-1);
	Horde3DUtils::dumpMessages();
	ss.str("");
	ss.clear();
	ss.flush();
	gsg->release();
	delete gsg;
	gsg = 0;
	//Horde3D::setNodeTransform(_model, _origin.x, _origin.y, _origin.z, 0, 0, 0, 1, 1, 1);
}

bool CustomGeometry::removeCustomModelNode()
{
	std::stringstream* ss = new std::stringstream();
	*ss<< "customGeometry-";
	*ss<<_name;
	ResHandle rh = Horde3D::findResource(ResourceTypes::Geometry, ss->str().c_str());
	if (Horde3D::isResourceLoaded(rh)) {
		Horde3D::removeResource(rh);
		Horde3D::unloadResource(rh);
		Horde3D::removeNode(_model);
		Horde3D::releaseUnusedResources();
		Horde3DUtils::dumpMessages();
		delete ss;
		return true;
	}
	else 
		return false;
}

void CustomGeometry::update()
{
	removeCustomModelNode();
	addCustomModelNode(-1);
}

Simple Square

A simple hard-coded square. This is pretty straight forward and demonstrates how to derive from the CustomGeometry class and how the triangle indices work.

DynamicSquare.h
/**
 * Class representing a square 
 **/
#pragma once
#include "CustomGeometry.h"

class DynamicSquare : public CustomGeometry
{
protected:
	virtual void generatePositions();
	virtual void generateNormals();
	virtual void generateTexCoords();
	virtual void generateTriangleIndices();

public:
	DynamicSquare();
	virtual void generate();
	DynamicSquare(const float size_, const std::string name_);
};
DynamicSquare.cpp
#include "DynamicSquare.h"

DynamicSquare::DynamicSquare() : CustomGeometry()
{
}

DynamicSquare::DynamicSquare(const float size_, const std::string name_) 
{
	_size = size_;
	_name = name_;
	_numVertices = 4;
	_numTriangles = 2;
	_numTriangleIndices = _numTriangles * 3;
	//generate();
}

void DynamicSquare::generate() 
{
	generatePositions();
	generateNormals();
	generateTexCoords();
	generateTriangleIndices();
}

void DynamicSquare::generatePositions() 
{
	_positions = new float[_numVertices * 3 * 2];
	// front left point (x/y/z)
	_positions[0] = _origin.x * _size - _size/2;
	_positions[1] = _origin.y;
	_positions[2] = _origin.z * _size -_size/2;
	// front right point (x/y/z)
	_positions[3] = _origin.x * _size + _size/2;
	_positions[4] = _origin.y;
	_positions[5] = _origin.z * _size - _size/2;
	// back left point (x/y/z)
	_positions[6] = _origin.x * _size - _size/2;
	_positions[7] = _origin.y;
	_positions[8] = _origin.z * _size + _size/2;
	// back right point (x/y/z)
	_positions[9] = _origin.x * _size + _size/2;
	_positions[10] = _origin.y;
	_positions[11] = _origin.z * _size + _size/2;

}

void DynamicSquare::generateNormals() 
{
	_normals = new float[_numVertices * 3];
	for (int i=0; i< 12; i+=3) {
		_normals[i] = 0;
		_normals[i+1] = 1;
		_normals[i+2] = 0;
	}
}

void DynamicSquare::generateTexCoords()
{
	_texCoords = new float[_numVertices * 2];
	_texCoords[0] = 0.0f; 
	_texCoords[1] = 0.0f;
	_texCoords[2] = 1.0f; 
	_texCoords[3] = 0.0f;
	_texCoords[4] = 0.0f; 
	_texCoords[5] = 1.0f;
	_texCoords[6] = 1.0f; 
	_texCoords[7] = 1.0f;
}

void DynamicSquare::generateTriangleIndices() 
{
	_tIndices = new int[_numTriangleIndices ];
	_tIndices[0] = 0;
	_tIndices[1] = 2;
	_tIndices[2] = 1;
	_tIndices[3] = 1;
	_tIndices[4] = 2;
	_tIndices[5] = 3;
}

Simple Grid

All the work above was made to create my own interactive terrain generator(screenshot on top of this page) for a A* pathfinding demonstrator. The Grid-class actually generates a 2-dimensional array of Vec3f(s) and allows to extrude each point and re-generate the mesh in realtime. The internal representation is writeable and in case of a change of any point the overridden update() function will be called to update the Horde3D geometry resource.

[grid_01_english.png This is how it looks internally]

DynamicGrid.h
/**
 * Class representing a grid
 **/
#pragma once
#include "CustomGeometry.h"

class DynamicGrid : public CustomGeometry
{
protected:
	virtual void generatePositions();
	virtual void generateNormals();
	virtual void generateTexCoords();
	virtual void generateTriangleIndices();
       // generate the internal representation of the grid (writeable to allow updates of single points!)
	void generateGrid();
	int _gridSize;
	Vec3f** _grid;
	std::string _fileName;

public:
	DynamicGrid();
	virtual void generate();
	DynamicGrid(const float size_, const std::string name_, std::string fileName_);
	Vec3f getPointAt(int x_, int y_) { return _grid[y_][x_]; }
	// update a point of our grid (args: index x, index y, new position)
	void setPointAt(int x_, int y_, Vec3f newPos_) { _grid[y_][x_] = newPos_; }
	int getGridSize() { return _gridSize; }
	// overwrite update() from parent class
	void update();
	// read and write grids from/to files
	void saveGridToFile(std::string fileName_);
	bool readGridFromFile(std::string fileName_);

};
DynamicGrid.cpp
#include "DynamicGrid.h"
#include "rng.h"
#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include "util.h"

#define GRIDSIZE 50

DynamicGrid::DynamicGrid() : CustomGeometry()
{
}

DynamicGrid::DynamicGrid(const float size_, const std::string name_, std::string fileName_) : _fileName(fileName_) 
{
	_name = name_;
	_positions = 0;
	_normals = 0;
	_texCoords = 0;
	_tIndices = 0;
	readGridFromFile(_fileName);
	_numVertices = (_gridSize*_gridSize);
	_numTriangles = ((_gridSize - 1) * (_gridSize - 1)) * 2;
	_numTriangleIndices = _numTriangles * 3;
	_origin = Vec3f(-_size/2*_gridSize, 0, _size/2*_gridSize);
	/*
	std::cout<<"numVerts: "<<_numVertices<<std::endl;
	std::cout<<"numTris: "<<_numTriangles<<std::endl;
	std::cout<<"numTInds: "<<_numTriangleIndices<<std::endl;
	std::cout<<"size: "<<_size<<std::endl;
	*/
	//std::cout<<_origin.x<<"/"<<_origin.y<<"/"<<_origin.z<<std::endl;
	//generate();
}

void DynamicGrid::generate() 
{
	if (!_grid)
		generateGrid();
	generatePositions();
	generateNormals();
	generateTexCoords();
	generateTriangleIndices();
}

// the following two functions could be combined into one single loop, but I left it this way for clarity...
/**
 * This function simply generates a 2-dimensional array of Vec3f(s) with slightly random height values
 **/
void DynamicGrid::generateGrid()
{
	RNG rng;
	_grid = new Vec3f*[_gridSize];
	for (int y=0; y<_gridSize; ++y) {
		_grid[y] = new Vec3f[_gridSize];
		for (int x=0; x<_gridSize; ++x) {
			long rand = (abs((int)rng.rand_int31()) %3);
			_grid[y][x] = Vec3f(_origin.x/2+_size/2*x, rand, _origin.z/2-_size/2*y);
			//std::cout<<_grid[y][x].x<<"/"<<_grid[y][x].y<<"/"<<_grid[y][x].z<<std::endl;
		}
	}
}

/**
 * Convert the grid's positions into a Horde3D vertex array.
 **/
void DynamicGrid::generatePositions() 
{
	_positions = new float[_numVertices*3];
	int counter = 0;
	for (int i=0; i<_numVertices*3; i+=3) {
		int y = counter / _gridSize;
		int x = counter % _gridSize;
		_positions[i] = _grid[y][x].x;
		_positions[i+1] = _grid[y][x].y;
		_positions[i+2] = _grid[y][x].z;
		++counter;
	}
}

void DynamicGrid::generateNormals() 
{
	_normals = new float[_numVertices * 3];
	for (int i=0; i< _numVertices*3; i+=3) {
		int pos = i/3;
		int xPos = pos % _gridSize;
		int yPos = pos / _gridSize;
		Vec3f xPred;
		Vec3f xSucc;
		Vec3f yPred;
		Vec3f ySucc;
		Vec3f normal;
		if (xPos > 0 && yPos > 0) {
			// if the current vertex is inside the grid we will calculate
			// two normals from the previous&next point and our current vertex
			if (xPos < _gridSize-1 && yPos < _gridSize-1) {
				xSucc = _grid[yPos][xPos+1];
				xPred = _grid[yPos][xPos-1];
				ySucc = _grid[yPos+1][xPos];
				yPred = _grid[yPos-1][xPos];
				Vec3f v1 = xSucc - _grid[yPos][xPos];
				if (v1.y < 0)
					v1.y *= -1;
				Vec3f v2 = xPred - _grid[yPos][xPos];
				if (v2.y < 0)
					v2.y *= -1;
				v1 += v2;
				Vec3f v3 = ySucc - _grid[yPos][xPos];
				if (v3.y < 0)
					v3.y *= -1;
				Vec3f v4 = yPred - _grid[yPos][xPos];
				if (v4.y < 0)
					v4.y *= -1;
				v3 += v4;
				normal = getInterpolatedNormal(v1, v3);
			}
			// if the vertex is on the grid's border the normal will simply be 0/1/0
			else {
				normal = Vec3f(0,1,0);
			}
		}
		else {
			normal = Vec3f(0,1,0);
		}
		_normals[i] = normal.x;
		_normals[i+1] = normal.y;
		_normals[i+2] = normal.z;
	}
}

void DynamicGrid::generateTexCoords()
{
	_texCoords = new float[_numVertices * 2];
	int counter = 0;
	int line = 0;
	for (int i=0; i<_numVertices*2; i+=2) {
		if ( line % 2 == 0) {
			if (counter %2 == 0) {
				_texCoords[i] = 0.0f;
				_texCoords[i+1] = 0.0f;
			}
			else {
				_texCoords[i] = 0.0f;
				_texCoords[i+1] = 1.0f;
			}
		}
		else {
			if (counter % 2 == 0) {
				_texCoords[i] = 1.0f;
				_texCoords[i+1] = 0.0f;
			}
			else {
				_texCoords[i] = 1.0f;
				_texCoords[i+1] = 1.0f;
			}
		}
		++counter;
		if (counter >= _gridSize) {
			++line;
			counter = 0;
		}
	}
}

void DynamicGrid::generateTriangleIndices() 
{
	_tIndices = new int[ _numTriangleIndices ];
	int counter = 0;
	int lineBreakCounter = 0;
	int counter3 = 0;
	for (int i=0; i<_numTriangleIndices; i+=3) {
		if (i%2 ==0) {
			_tIndices[i] = counter;
			_tIndices[i+1] = counter+1;
			_tIndices[i+2] = counter+_gridSize;
		}
		else {
			_tIndices[i] = counter + _gridSize;
			_tIndices[i+1] = counter + 1;
			_tIndices[i+2] = counter+_gridSize+1;
			++counter;
		}
		++lineBreakCounter;
		if (lineBreakCounter == _gridSize*2 - 2) {
			--counter;
			++counter3;
			lineBreakCounter = 0;
			counter = counter3*_gridSize;
		}
		//std::cout<<_tIndices[i]<<"/"<<_tIndices[i+1]<<"/"<<_tIndices[i+2]<<std::endl;
	}
}

void DynamicGrid::update()
{
	removeCustomModelNode();
	delete[] _positions;
	_positions = 0;
	delete[] _normals;
	_normals = 0;
	generatePositions();
	generateNormals();
	addCustomModelNode(-1);
}

void DynamicGrid::saveGridToFile(std::string fileName_)
{
	//std::stringstream fName;
	//fName<<fileName_;
	//fName<<".d2g";
	std::stringstream ss;
	// magic header
	ss<<"D2SG\n";
	// the gridsize (important to know!)
	ss<<_gridSize;
	ss<<"\n";
	// the quadsize (important to know!)
	ss<<_size;
	ss<<"\n";
	// number of coordinates (most important to know!)
	ss<<(_gridSize*_gridSize*3);
	ss<<"\n";
	// coordinates
	for (int y=0; y<_gridSize; ++y) {
		for (int x=0; x<_gridSize; ++x) {
			ss<<_grid[y][x].x;
			ss<<"\n";
			ss<<_grid[y][x].y;
			ss<<"\n";
			ss<<_grid[y][x].z;
			ss<<"\n";
		}
	}
	ss<<"\n";
	std::ofstream dest(fileName_.c_str());
	if (dest.is_open()) {
		dest<<ss.str();
		dest.close();
	}
	std::cout<<"Grid successfully saved to file: "<<fileName_<<std::endl;
}

bool DynamicGrid::readGridFromFile(std::string fileName_)
{
	int numCoordinates;
	std::ifstream inFile;
	inFile.open(fileName_.c_str());
	bool error = false;
	if (!inFile) {
		std::cout<<"Gridfile "<<fileName_<<" could not be found! Creating new grid."<<std::endl;
		error = true;
	}
	else {
		std::string line;
		if (std::getline(inFile, line)) {
			std::cout<<"Line: "<<line<<std::endl;
			//if (!(line == "D2SG")) {
			if (line.compare("D2SG") != 0){
				std::cout<<"wrong grid-file format "<<line.compare("D2SG")<<std::endl;
				error = true;
			}
			else {
				std::cout<<"Valid grid-file found!"<<std::endl;
			}
		}
		if (std::getline(inFile, line)) {
			_gridSize = stringToInt(line);
			//std::cout<<"gridsize: "<<_gridSize<<std::endl;
		}
		else {
			error = true;
		}
		if (std::getline(inFile, line)) {
			_size = stringToInt(line);
			//std::cout<<"quadsize: "<<_size<<std::endl;
		}
		else {
			error = true;
		}
		if (std::getline(inFile, line)) {
			numCoordinates = stringToInt(line);
			//std::cout<<"numCoords: "<<numCoordinates<<std::endl;
		}
		else {
			error = true;
		}
		if (!error) {
			_grid = new Vec3f*[_gridSize];
			for(int y=0; y<_gridSize; ++y) {
				_grid[y] = new Vec3f[_gridSize];
				for (int x=0; x<_gridSize; ++x) {
					Vec3f v;
					if (std::getline(inFile, line)) {
						v.x = stringToFloat(line);
					}
					if (std::getline(inFile, line)) {
						v.y = stringToFloat(line);
					}
					if (std::getline(inFile, line)) {
						v.z = stringToFloat(line);
					}
					_grid[y][x] = v;
				}
			}
		}
	}
	inFile.close();
	if (error) {
		_gridSize = GRIDSIZE;
		_size = 12;
		_grid = 0;
	}
	_numVertices = (_gridSize*_gridSize);
	_numTriangles = ((_gridSize - 1) * (_gridSize - 1)) * 2;
	_numTriangleIndices = _numTriangles * 3;
	_origin = Vec3f(-_size/2*_gridSize, 0, _size/2*_gridSize);
	return true;
}

Usage example

Finally this is all you have to do to generate the grid and display it in your scene.

app.cpp
	// initialize a new grid (args: quadsize, model-name, filename (if applicable)
	_grid = new DynamicGrid(12.0f, "grid", "grid-filename");
	// do not forget to load a material and apply it to the mesh
	_grid->setMaterial(gridMatRes);
	// generate the grid
	_grid->generate();
	// add the grid-model to the RootNode
	_grid->addCustomModelNode(RootNode);

TODO