Tweaks to createSphere() plus some new helper functions

- changed createSphere() to support new NATURAL mode which uses perlin
  noise to create natural color and shape
- added getVoxelCount()
- coding standard cleanup
This commit is contained in:
ZappoMan 2013-05-08 01:02:55 -07:00
parent 146334e4d7
commit 7d6b28dd4d
2 changed files with 140 additions and 68 deletions

View file

@ -22,10 +22,12 @@
#include <fstream> // to load voxels from file
#include "VoxelConstants.h"
#include <glm/gtc/noise.hpp>
using voxels_lib::printLog;
int boundaryDistanceForRenderLevel(unsigned int renderLevel) {
float voxelSizeScale = 500.0*TREE_SCALE;
float voxelSizeScale = 50000.0f;
return voxelSizeScale / powf(2, renderLevel);
}
@ -113,9 +115,7 @@ VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, unsigned char
}
}
int VoxelTree::readNodeData(VoxelNode* destinationNode,
unsigned char* nodeData,
int bytesLeftToRead) {
int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData, int bytesLeftToRead) {
// instantiate variable for bytes already read
int bytesRead = 1;
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
@ -457,87 +457,144 @@ void VoxelTree::createLine(glm::vec3 point1, glm::vec3 point2, float unitSize, r
}
void VoxelTree::createSphere(float radius, float xc, float yc, float zc, float voxelSize,
bool solid, bool wantColorRandomizer, bool debug) {
bool solid, creationMode mode, bool debug) {
// About the color of the sphere... we're going to make this sphere be a gradient
// between two RGB colors. We will do the gradient along the phi spectrum
unsigned char dominantColor1 = randIntInRange(1, 3); //1=r, 2=g, 3=b dominant
unsigned char dominantColor2 = randIntInRange(1, 3);
bool wantColorRandomizer = (mode == RANDOM);
bool wantNaturalSurface = (mode == NATURAL);
bool wantNaturalColor = (mode == NATURAL);
if (dominantColor1 == dominantColor2) {
dominantColor2 = dominantColor1 + 1 % 3;
// About the color of the sphere... we're going to make this sphere be a mixture of two colors
// in NATURAL mode, those colors will be green dominant and blue dominant. In GRADIENT mode we
// will randomly pick which color family red, green, or blue to be dominant. In RANDOM mode we
// ignore these dominant colors and make every voxel a completely random color.
unsigned char r1, g1, b1, r2, g2, b2;
if (wantNaturalColor) {
r1 = r2 = b2 = g1 = 0;
b1 = g2 = 255;
} else if (!wantColorRandomizer) {
unsigned char dominantColor1 = randIntInRange(1, 3); //1=r, 2=g, 3=b dominant
unsigned char dominantColor2 = randIntInRange(1, 3);
if (dominantColor1 == dominantColor2) {
dominantColor2 = dominantColor1 + 1 % 3;
}
r1 = (dominantColor1 == 1) ? randIntInRange(200, 255) : randIntInRange(40, 100);
g1 = (dominantColor1 == 2) ? randIntInRange(200, 255) : randIntInRange(40, 100);
b1 = (dominantColor1 == 3) ? randIntInRange(200, 255) : randIntInRange(40, 100);
r2 = (dominantColor2 == 1) ? randIntInRange(200, 255) : randIntInRange(40, 100);
g2 = (dominantColor2 == 2) ? randIntInRange(200, 255) : randIntInRange(40, 100);
b2 = (dominantColor2 == 3) ? randIntInRange(200, 255) : randIntInRange(40, 100);
}
unsigned char r1 = (dominantColor1 == 1) ? randIntInRange(200, 255) : randIntInRange(40, 100);
unsigned char g1 = (dominantColor1 == 2) ? randIntInRange(200, 255) : randIntInRange(40, 100);
unsigned char b1 = (dominantColor1 == 3) ? randIntInRange(200, 255) : randIntInRange(40, 100);
unsigned char r2 = (dominantColor2 == 1) ? randIntInRange(200, 255) : randIntInRange(40, 100);
unsigned char g2 = (dominantColor2 == 2) ? randIntInRange(200, 255) : randIntInRange(40, 100);
unsigned char b2 = (dominantColor2 == 3) ? randIntInRange(200, 255) : randIntInRange(40, 100);
// We initialize our rgb to be either "grey" in case of randomized surface, or
// the average of the gradient, in the case of the gradient sphere.
// We initialize our rgb to be either "grey" in case of randomized surface, or
// the average of the gradient, in the case of the gradient sphere.
unsigned char red = wantColorRandomizer ? 128 : (r1 + r2) / 2; // average of the colors
unsigned char green = wantColorRandomizer ? 128 : (g1 + g2) / 2;
unsigned char blue = wantColorRandomizer ? 128 : (b1 + b2) / 2;
// Psuedocode for creating a sphere:
//
// for (theta from 0 to 2pi):
// for (phi from 0 to pi):
// x = xc+r*cos(theta)*sin(phi)
// y = yc+r*sin(theta)*sin(phi)
// z = zc+r*cos(phi)
// x = xc+r*cos(theta)*sin(phi)
// y = yc+r*sin(theta)*sin(phi)
// z = zc+r*cos(phi)
// I want to do something smart like make these inside circles with bigger voxels, but this doesn't seem to work.
float thisVoxelSize = voxelSize; // radius / 2.0f;
float thisRadius = 0.0;
if (!solid) {
thisRadius = radius; // just the outer surface
thisVoxelSize = voxelSize;
}
// If you also iterate form the interior of the sphere to the radius, making
// larger and larger spheres you'd end up with a solid sphere. And lots of voxels!
float finalRadius = wantNaturalSurface ? radius : (radius + (voxelSize / 2.0));
bool lastLayer = false;
while (!lastLayer) {
lastLayer = (thisRadius + (voxelSize * 2.0) >= radius);
// assume solid for now
float thisRadius = 0.0;
float thisVoxelSize = radius / 4.0f;
if (!solid) {
thisRadius = radius; // just the outer surface
thisVoxelSize = voxelSize;
}
// If you also iterate form the interior of the sphere to the radius, makeing
// larger and larger sphere'voxelSize you'd end up with a solid sphere. And lots of voxels!
while (thisRadius <= (radius + (voxelSize / 2.0))) {
if (debug) {
printLog("radius: thisRadius=%f thisVoxelSize=%f thisRadius+thisVoxelSize=%f (radius+(voxelSize/2.0))=%f\n",
thisRadius, thisVoxelSize, thisRadius+thisVoxelSize, (radius + (voxelSize / 2.0)));
}
// We want to make sure that as we "sweep" through our angles we use a delta angle that voxelSize
// small enough to not skip any voxels we can calculate theta from our desired arc length
// lenArc = ndeg/360deg * 2pi*R ---> lenArc = theta/2pi * 2pi*R
// lenArc = theta*R ---> theta = lenArc/R ---> theta = g/r
float angleDelta = (thisVoxelSize / thisRadius);
for (float theta=0.0; theta <= 2 * M_PI; theta += angleDelta) {
for (float phi=0.0; phi <= M_PI; phi += angleDelta) {
float x = xc + thisRadius * cos(theta) * sin(phi);
float y = yc + thisRadius * sin(theta) * sin(phi);
float z = zc + thisRadius * cos(phi);
// gradient color data
float gradient = (phi / M_PI);
// only use our actual desired color on the outer edge, otherwise
// use our "average" color
if (thisRadius + (voxelSize * 2.0) >= radius) {
//printLog("painting candy shell radius: thisRadius=%f radius=%f\n",thisRadius,radius);
red = wantColorRandomizer ? randomColorValue(165) : r1 + ((r2 - r1) * gradient);
green = wantColorRandomizer ? randomColorValue(165) : g1 + ((g2 - g1) * gradient);
blue = wantColorRandomizer ? randomColorValue(165) : b1 + ((b2 - b1) * gradient);
}
unsigned char* voxelData = pointToVoxel(x, y, z, thisVoxelSize, red, green, blue);
this->readCodeColorBufferToTree(voxelData);
delete voxelData;
}
}
if (debug) {
int percentComplete = 100 * (thisRadius/radius);
printLog("percentComplete=%d\n",percentComplete);
}
for (float theta=0.0; theta <= 2 * M_PI; theta += angleDelta) {
for (float phi=0.0; phi <= M_PI; phi += angleDelta) {
bool naturalSurfaceRendered = false;
float x = xc + thisRadius * cos(theta) * sin(phi);
float y = yc + thisRadius * sin(theta) * sin(phi);
float z = zc + thisRadius * cos(phi);
// if we're on the outer radius, then we do a couple of things differently.
// 1) If we're in NATURAL mode we will actually draw voxels from our surface outward (from the surface) up
// some random height. This will give our sphere some contours.
// 2) In all modes, we will use our "outer" color to draw the voxels. Otherwise we will use the average color
if (lastLayer) {
if (false && debug) {
printLog("adding candy shell: theta=%f phi=%f thisRadius=%f radius=%f\n",
theta, phi, thisRadius,radius);
}
switch (mode) {
case RANDOM: {
red = randomColorValue(165);
green = randomColorValue(165);
blue = randomColorValue(165);
} break;
case GRADIENT: {
float gradient = (phi / M_PI);
red = r1 + ((r2 - r1) * gradient);
green = g1 + ((g2 - g1) * gradient);
blue = b1 + ((b2 - b1) * gradient);
} break;
case NATURAL: {
glm::vec3 position = glm::vec3(theta,phi,radius);
float perlin = glm::perlin(position) + .25f * glm::perlin(position * 4.f)
+ .125f * glm::perlin(position * 16.f);
float gradient = (1.0f + perlin)/ 2.0f;
red = (unsigned char)std::min(255, std::max(0, (int)(r1 + ((r2 - r1) * gradient))));
green = (unsigned char)std::min(255, std::max(0, (int)(g1 + ((g2 - g1) * gradient))));
blue = (unsigned char)std::min(255, std::max(0, (int)(b1 + ((b2 - b1) * gradient))));
if (debug) {
printLog("perlin=%f gradient=%f color=(%d,%d,%d)\n",perlin, gradient, red, green, blue);
}
} break;
}
if (wantNaturalSurface) {
// for natural surfaces, we will render up to 16 voxel's above the surface of the sphere
glm::vec3 position = glm::vec3(theta,phi,radius);
float perlin = glm::perlin(position) + .25f * glm::perlin(position * 4.f)
+ .125f * glm::perlin(position * 16.f);
float gradient = (1.0f + perlin)/ 2.0f;
int height = (4 * gradient)+1; // make it at least 4 thick, so we get some averaging
float subVoxelScale = thisVoxelSize;
for (int i = 0; i < height; i++) {
x = xc + (thisRadius + i * subVoxelScale) * cos(theta) * sin(phi);
y = yc + (thisRadius + i * subVoxelScale) * sin(theta) * sin(phi);
z = zc + (thisRadius + i * subVoxelScale) * cos(phi);
this->createVoxel(x, y, z, subVoxelScale, red, green, blue);
}
naturalSurfaceRendered = true;
}
}
if (!naturalSurfaceRendered) {
this->createVoxel(x, y, z, thisVoxelSize, red, green, blue);
}
}
}
thisRadius += thisVoxelSize;
thisVoxelSize = std::max(voxelSize, thisVoxelSize / 2.0f);
}
this->reaverageVoxelColors(this->rootNode);
thisVoxelSize = std::max(voxelSize, thisVoxelSize / 2.0f);
}
this->reaverageVoxelColors(this->rootNode);
}
int VoxelTree::searchForColoredNodes(int maxSearchLevel, VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag) {
@ -923,3 +980,13 @@ void VoxelTree::writeToFileV2(const char* fileName) const {
file.close();
}
unsigned long VoxelTree::getVoxelCount() {
unsigned long nodeCount = 0;
recurseTreeWithOperation(countVoxelsOperation, &nodeCount);
return nodeCount;
}
bool VoxelTree::countVoxelsOperation(VoxelNode* node, void* extraData) {
(*(unsigned long*)extraData)++;
return true; // keep going
}

View file

@ -17,6 +17,7 @@
// Callback function, for recuseTreeWithOperation
typedef bool (*RecurseVoxelTreeOperation)(VoxelNode* node, void* extraData);
typedef enum {GRADIENT, RANDOM, NATURAL} creationMode;
class VoxelTree {
public:
@ -49,7 +50,7 @@ public:
VoxelNode* getVoxelAt(float x, float y, float z, float s) const;
void createVoxel(float x, float y, float z, float s, unsigned char red, unsigned char green, unsigned char blue);
void createLine(glm::vec3 point1, glm::vec3 point2, float unitSize, rgbColor color);
void createSphere(float r,float xc, float yc, float zc, float s, bool solid, bool wantColorRandomizer, bool debug = false);
void createSphere(float r,float xc, float yc, float zc, float s, bool solid, creationMode mode, bool debug = false);
void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL);
@ -70,6 +71,8 @@ public:
// these will read/write files that match the wireformat, excluding the 'V' leading
void writeToFileV2(const char* filename) const;
bool readFromFileV2(const char* filename);
unsigned long getVoxelCount();
private:
int encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel,
@ -79,6 +82,8 @@ private:
int searchForColoredNodesRecursion(int maxSearchLevel, int& currentSearchLevel,
VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag);
static bool countVoxelsOperation(VoxelNode* node, void* extraData);
void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData);
VoxelNode* nodeForOctalCode(VoxelNode* ancestorNode, unsigned char* needleCode, VoxelNode** parentOfFoundNode) const;
VoxelNode* createMissingNode(VoxelNode* lastParentNode, unsigned char* deepestCodeToCreate);