Merge branch 'master' of git://github.com/worklist/hifi into 19371

This commit is contained in:
tosh 2013-06-19 16:15:38 +02:00
commit 7342b7a774
29 changed files with 1679 additions and 594 deletions

View file

@ -116,6 +116,8 @@ int main(int argc, const char* argv[]) {
float sumFrameTimePercentages = 0.0f;
int numStatCollections = 0;
stk::StkFrames stkFrameBuffer(BUFFER_LENGTH_SAMPLES_PER_CHANNEL, 1);
// if we'll be sending stats, call the Logstash::socket() method to make it load the logstash IP outside the loop
if (Logstash::shouldSendStats()) {
Logstash::socket();
@ -161,6 +163,9 @@ int main(int argc, const char* argv[]) {
}
for (AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) {
const int PHASE_DELAY_AT_90 = 20;
if (agent->getType() == AGENT_TYPE_AVATAR) {
AvatarAudioRingBuffer* agentRingBuffer = (AvatarAudioRingBuffer*) agent->getLinkedData();
@ -248,7 +253,6 @@ int main(int argc, const char* argv[]) {
glm::normalize(rotatedSourcePosition),
glm::vec3(0.0f, 1.0f, 0.0f));
const int PHASE_DELAY_AT_90 = 20;
const float PHASE_AMPLITUDE_RATIO_AT_90 = 0.5;
// figure out the number of samples of delay and the ratio of the amplitude
@ -279,6 +283,8 @@ int main(int argc, const char* argv[]) {
}
}
int16_t* sourceBuffer = otherAgentBuffer->getNextOutput();
int16_t* goodChannel = (bearingRelativeAngleToSource > 0.0f)
? clientSamples
: clientSamples + BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
@ -290,22 +296,25 @@ int main(int argc, const char* argv[]) {
? otherAgentBuffer->getBuffer() + RING_BUFFER_LENGTH_SAMPLES - numSamplesDelay
: otherAgentBuffer->getNextOutput() - numSamplesDelay;
for (int s = 0; s < BUFFER_LENGTH_SAMPLES_PER_CHANNEL; s++) {
// load up the stkFrameBuffer with this source's samples
stkFrameBuffer[s] = (stk::StkFloat) sourceBuffer[s];
}
// perform the TwoPole effect on the stkFrameBuffer
if (otherAgentTwoPole) {
otherAgentTwoPole->tick(stkFrameBuffer);
}
for (int s = 0; s < BUFFER_LENGTH_SAMPLES_PER_CHANNEL; s++) {
if (s < numSamplesDelay) {
// pull the earlier sample for the delayed channel
int earlierSample = delaySamplePointer[s]
* attenuationCoefficient
* weakChannelAmplitudeRatio;
int earlierSample = delaySamplePointer[s] * attenuationCoefficient * weakChannelAmplitudeRatio;
plateauAdditionOfSamples(delayedChannel[s], earlierSample);
}
if (otherAgentTwoPole) {
otherAgentBuffer->getNextOutput()[s] = otherAgentTwoPole->tick(otherAgentBuffer->getNextOutput()[s]);
}
int16_t currentSample = otherAgentBuffer->getNextOutput()[s] * attenuationCoefficient;
int16_t currentSample = stkFrameBuffer[s] * attenuationCoefficient;
plateauAdditionOfSamples(goodChannel[s], currentSample);
@ -313,6 +322,12 @@ int main(int argc, const char* argv[]) {
plateauAdditionOfSamples(delayedChannel[s + numSamplesDelay],
currentSample * weakChannelAmplitudeRatio);
}
if (s >= BUFFER_LENGTH_SAMPLES_PER_CHANNEL - PHASE_DELAY_AT_90) {
// this could be a delayed sample on the next pass
// so store the affected back in the ARB
otherAgentBuffer->getNextOutput()[s] = (int16_t) stkFrameBuffer[s];
}
}
}
}
@ -395,9 +410,7 @@ int main(int argc, const char* argv[]) {
float percentageOfMaxElapsed = ((float) (usecTimestamp(&endSendTime) - usecTimestamp(&beginSendTime))
/ BUFFER_SEND_INTERVAL_USECS) * 100.0f;
if (percentageOfMaxElapsed > 0) {
sumFrameTimePercentages += percentageOfMaxElapsed;
}
sumFrameTimePercentages += percentageOfMaxElapsed;
numStatCollections++;
}

View file

@ -65,8 +65,8 @@ int main(int argc, const char * argv[])
// with the EC2 IP. Otherwise, we will replace the IP like we used to
// this allows developers to run a local domain without recompiling the
// domain server
bool useLocal = cmdOptionExists(argc, argv, "--local");
if (useLocal) {
bool isLocalMode = cmdOptionExists(argc, argv, "--local");
if (isLocalMode) {
printf("NOTE: Running in Local Mode!\n");
} else {
printf("--------------------------------------------------\n");
@ -103,14 +103,17 @@ int main(int argc, const char * argv[])
int numBytesSocket = unpackSocket(packetData + sizeof(PACKET_HEADER) + sizeof(AGENT_TYPE),
(sockaddr*) &agentLocalAddress);
sockaddr* destinationSocket = (sockaddr*) &agentPublicAddress;
// check the agent public address
// if it matches our local address we're on the same box
// so hardcode the EC2 public address for now
if (agentPublicAddress.sin_addr.s_addr == serverLocalAddress) {
// If we're not running "local" then we do replace the IP
// with the EC2 IP. Otherwise, we use our normal public IP
if (!useLocal) {
if (!isLocalMode) {
agentPublicAddress.sin_addr.s_addr = 895283510; // local IP in this format...
destinationSocket = (sockaddr*) &agentLocalAddress;
}
}
@ -178,9 +181,9 @@ int main(int argc, const char * argv[])
currentBufferPos += packAgentId(currentBufferPos, newAgent->getAgentID());
// send the constructed list back to this agent
agentList->getAgentSocket()->send((sockaddr*) &agentPublicAddress,
broadcastPacket,
(currentBufferPos - startPointer) + 1);
agentList->getAgentSocket()->send(destinationSocket,
broadcastPacket,
(currentBufferPos - startPointer) + 1);
}
}

View file

@ -35,11 +35,12 @@ enum {
};
// Command line parameter defaults
bool loopAudio = true;
bool shouldLoopAudio = true;
bool hasInjectedAudioOnce = false;
float sleepIntervalMin = 1.00;
float sleepIntervalMax = 2.00;
char *sourceAudioFile = NULL;
const char *allowedParameters = ":sb::t::c::a::f::d::r:";
const char *allowedParameters = ":sc::a::f::t::r:";
float floatArguments[4] = {0.0f, 0.0f, 0.0f, 0.0f};
unsigned char volume = DEFAULT_INJECTOR_VOLUME;
float triggerDistance = 0.0f;
@ -47,13 +48,11 @@ float radius = 0.0f;
void usage(void) {
std::cout << "High Fidelity - Interface audio injector" << std::endl;
std::cout << " -s Random sleep mode. If not specified will default to constant loop." << std::endl;
std::cout << " -b FLOAT Min. number of seconds to sleep. Only valid in random sleep mode. Default 1.0" << std::endl;
std::cout << " -t FLOAT Max. number of seconds to sleep. Only valid in random sleep mode. Default 2.0" << std::endl;
std::cout << " -s Single play mode. If not specified will default to constant loop." << std::endl;
std::cout << " -c FLOAT,FLOAT,FLOAT,FLOAT X,Y,Z,YAW position in universe where audio will be originating from and direction. Defaults to 0,0,0,0" << std::endl;
std::cout << " -a 0-255 Attenuation curve modifier, defaults to 255" << std::endl;
std::cout << " -f FILENAME Name of audio source file. Required - RAW format, 22050hz 16bit signed mono" << std::endl;
std::cout << " -d FLOAT Trigger distance for injection. If not specified will loop constantly" << std::endl;
std::cout << " -t FLOAT Trigger distance for injection. If not specified will loop constantly" << std::endl;
std::cout << " -r FLOAT Radius for spherical source. If not specified injected audio is point source" << std::endl;
}
@ -62,16 +61,8 @@ bool processParameters(int parameterCount, char* parameterData[]) {
while ((p = getopt(parameterCount, parameterData, allowedParameters)) != -1) {
switch (p) {
case 's':
::loopAudio = false;
std::cout << "[DEBUG] Random sleep mode enabled" << std::endl;
break;
case 'b':
::sleepIntervalMin = atof(optarg);
std::cout << "[DEBUG] Min delay between plays " << sleepIntervalMin << "sec" << std::endl;
break;
case 't':
::sleepIntervalMax = atof(optarg);
std::cout << "[DEBUG] Max delay between plays " << sleepIntervalMax << "sec" << std::endl;
::shouldLoopAudio = false;
std::cout << "[DEBUG] Single play mode enabled" << std::endl;
break;
case 'f':
::sourceAudioFile = optarg;
@ -97,7 +88,7 @@ bool processParameters(int parameterCount, char* parameterData[]) {
::volume = atoi(optarg);
std::cout << "[DEBUG] Attenuation modifier: " << optarg << std::endl;
break;
case 'd':
case 't':
::triggerDistance = atof(optarg);
std::cout << "[DEBUG] Trigger distance: " << optarg << std::endl;
break;
@ -113,35 +104,6 @@ bool processParameters(int parameterCount, char* parameterData[]) {
return true;
};
bool stopReceiveAgentDataThread;
void *receiveAgentData(void *args) {
sockaddr senderAddress;
ssize_t bytesReceived;
unsigned char incomingPacket[MAX_PACKET_SIZE];
AgentList* agentList = AgentList::getInstance();
while (!::stopReceiveAgentDataThread) {
if (agentList->getAgentSocket()->receive(&senderAddress, incomingPacket, &bytesReceived)) {
switch (incomingPacket[0]) {
case PACKET_HEADER_BULK_AVATAR_DATA:
// this is the positional data for other agents
// pass that off to the agentList processBulkAgentData method
agentList->processBulkAgentData(&senderAddress, incomingPacket, bytesReceived);
break;
default:
// have the agentList handle list of agents from DS, replies from other agents, etc.
agentList->processAgentData(&senderAddress, incomingPacket, bytesReceived);
break;
}
}
}
pthread_exit(0);
return NULL;
}
void createAvatarDataForAgent(Agent* agent) {
if (!agent->getLinkedData()) {
agent->setLinkedData(new AvatarData(agent));
@ -164,9 +126,6 @@ int main(int argc, char* argv[]) {
// create an AgentList instance to handle communication with other agents
AgentList* agentList = AgentList::createInstance(AGENT_TYPE_AUDIO_INJECTOR, AUDIO_UDP_SEND_PORT);
pthread_t receiveAgentDataThread;
pthread_create(&receiveAgentDataThread, NULL, receiveAgentData, NULL);
// start the agent list thread that will kill off agents when they stop talking
agentList->startSilentAgentRemovalThread();
@ -183,42 +142,47 @@ int main(int argc, char* argv[]) {
// register the callback for agent data creation
agentList->linkedDataCreateCallback = createAvatarDataForAgent;
unsigned char broadcastPacket = PACKET_HEADER_INJECT_AUDIO;
timeval thisSend;
long long numMicrosecondsSleep = 0;
timeval lastSend = {};
unsigned char broadcastPacket = PACKET_HEADER_INJECT_AUDIO;
timeval lastDomainServerCheckIn = {};
// the audio injector needs to know about the avatar mixer and the audio mixer
const char INJECTOR_AGENTS_OF_INTEREST[] = {AGENT_TYPE_AVATAR_MIXER, AGENT_TYPE_AUDIO_MIXER};
AgentList::getInstance()->setAgentTypesOfInterest(INJECTOR_AGENTS_OF_INTEREST, sizeof(INJECTOR_AGENTS_OF_INTEREST));
sockaddr senderAddress;
ssize_t bytesReceived;
unsigned char incomingPacket[MAX_PACKET_SIZE];
while (true) {
// the audio injector needs to know about the avatar mixer and the audio mixer
const char INJECTOR_AGENTS_OF_INTEREST[] = {AGENT_TYPE_AUDIO_MIXER, AGENT_TYPE_AVATAR_MIXER};
int bytesAgentsOfInterest = (::triggerDistance > 0)
? sizeof(INJECTOR_AGENTS_OF_INTEREST)
: sizeof(INJECTOR_AGENTS_OF_INTEREST) - 1;
AgentList::getInstance()->setAgentTypesOfInterest(INJECTOR_AGENTS_OF_INTEREST, bytesAgentsOfInterest);
while (true) {
// send a check in packet to the domain server if DOMAIN_SERVER_CHECK_IN_USECS has elapsed
if (usecTimestampNow() - usecTimestamp(&lastDomainServerCheckIn) >= DOMAIN_SERVER_CHECK_IN_USECS) {
gettimeofday(&lastDomainServerCheckIn, NULL);
AgentList::getInstance()->sendDomainServerCheckIn();
}
if (::triggerDistance) {
// update the thisSend timeval to the current time
gettimeofday(&thisSend, NULL);
// find the current avatar mixer
Agent* avatarMixer = agentList->soloAgentOfType(AGENT_TYPE_AVATAR_MIXER);
// make sure we actually have an avatar mixer with an active socket
if (avatarMixer && avatarMixer->getActiveSocket() != NULL) {
// use the UDPSocket instance attached to our agent list to ask avatar mixer for a list of avatars
agentList->getAgentSocket()->send(avatarMixer->getActiveSocket(),
&broadcastPacket,
sizeof(broadcastPacket));
while (agentList->getAgentSocket()->receive(&senderAddress, incomingPacket, &bytesReceived)) {
switch (incomingPacket[0]) {
case PACKET_HEADER_BULK_AVATAR_DATA:
// this is the positional data for other agents
// pass that off to the agentList processBulkAgentData method
agentList->processBulkAgentData(&senderAddress, incomingPacket, bytesReceived);
break;
default:
// have the agentList handle list of agents from DS, replies from other agents, etc.
agentList->processAgentData(&senderAddress, incomingPacket, bytesReceived);
break;
}
}
if (::triggerDistance) {
if (!injector.isInjectingAudio()) {
// enumerate the other agents to decide if one is close enough that we should inject
for (AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) {
@ -226,49 +190,39 @@ int main(int argc, char* argv[]) {
if (avatarData) {
glm::vec3 tempVector = injector.getPosition() - avatarData->getPosition();
float squareDistance = glm::dot(tempVector, tempVector);
if (squareDistance <= ::triggerDistance) {
// look for an audio mixer in our agent list
Agent* audioMixer = AgentList::getInstance()->soloAgentOfType(AGENT_TYPE_AUDIO_MIXER);
if (audioMixer) {
// we have an active audio mixer we can send data to
AudioInjectionManager::threadInjector(&injector);
}
if (glm::dot(tempVector, tempVector) <= ::triggerDistance) {
// use the AudioInjectionManager to thread this injector
AudioInjectionManager::threadInjector(&injector);
}
}
}
}
// sleep for the correct amount of time to have data send be consistently timed
if ((numMicrosecondsSleep = (AVATAR_MIXER_DATA_SEND_INTERVAL_MSECS * 1000) -
(usecTimestampNow() - usecTimestamp(&thisSend))) > 0) {
usleep(numMicrosecondsSleep);
// find the current avatar mixer
Agent* avatarMixer = agentList->soloAgentOfType(AGENT_TYPE_AVATAR_MIXER);
// make sure we actually have an avatar mixer with an active socket
if (avatarMixer && avatarMixer->getActiveSocket() != NULL
&& (usecTimestampNow() - usecTimestamp(&lastSend) > AVATAR_MIXER_DATA_SEND_INTERVAL_MSECS)) {
// update the lastSend timeval to the current time
gettimeofday(&lastSend, NULL);
// use the UDPSocket instance attached to our agent list to ask avatar mixer for a list of avatars
agentList->getAgentSocket()->send(avatarMixer->getActiveSocket(),
&broadcastPacket,
sizeof(broadcastPacket));
}
} else {
// look for an audio mixer in our agent list
Agent* audioMixer = AgentList::getInstance()->soloAgentOfType(AGENT_TYPE_AUDIO_MIXER);
if (audioMixer) {
injector.injectAudio(agentList->getAgentSocket(), audioMixer->getActiveSocket());
if (!injector.isInjectingAudio() && (::shouldLoopAudio || !::hasInjectedAudioOnce)) {
// use the AudioInjectionManager to thread this injector
AudioInjectionManager::threadInjector(&injector);
::hasInjectedAudioOnce = true;
}
float delay = 0;
int usecDelay = 0;
if (!::loopAudio) {
delay = randFloatInRange(::sleepIntervalMin, ::sleepIntervalMax);
usecDelay = delay * 1000 * 1000;
usleep(usecDelay);
}
}
}
// stop the receive agent data thread
stopReceiveAgentDataThread = true;
pthread_join(receiveAgentDataThread, NULL);
// stop the agent list's threads
agentList->stopSilentAgentRemovalThread();
}

View file

@ -986,6 +986,10 @@ void Application::doFalseColorizeInView() {
_voxels.falseColorizeInView(&_viewFrustum);
}
void Application::doFalseColorizeOccluded() {
_voxels.falseColorizeOccluded();
}
void Application::doTrueVoxelColors() {
_voxels.trueColorize();
}
@ -1006,6 +1010,10 @@ void Application::setWantsDelta(bool wantsDelta) {
_myAvatar.setWantDelta(wantsDelta);
}
void Application::setWantsOcclusionCulling(bool wantsOcclusionCulling) {
_myAvatar.setWantOcclusionCulling(wantsOcclusionCulling);
}
void Application::updateVoxelModeActions() {
// only the sender can be checked
foreach (QAction* action, _voxelModeActions->actions()) {
@ -1325,9 +1333,6 @@ void Application::initMenu() {
_frustumOn->setShortcut(Qt::SHIFT | Qt::Key_F);
(_viewFrustumFromOffset = frustumMenu->addAction(
"Use Offset Camera", this, SLOT(setFrustumOffset(bool)), Qt::SHIFT | Qt::Key_O))->setCheckable(true);
(_cameraFrustum = frustumMenu->addAction("Switch Camera"))->setCheckable(true);
_cameraFrustum->setChecked(true);
_cameraFrustum->setShortcut(Qt::SHIFT | Qt::Key_C);
_frustumRenderModeAction = frustumMenu->addAction(
"Render Mode", this, SLOT(cycleFrustumRenderMode()), Qt::SHIFT | Qt::Key_R);
updateFrustumRenderModeAction();
@ -1343,11 +1348,13 @@ void Application::initMenu() {
renderDebugMenu->addAction("FALSE Color Voxel Every Other Randomly", this, SLOT(doFalseRandomizeEveryOtherVoxelColors()));
renderDebugMenu->addAction("FALSE Color Voxels by Distance", this, SLOT(doFalseColorizeByDistance()));
renderDebugMenu->addAction("FALSE Color Voxel Out of View", this, SLOT(doFalseColorizeInView()));
renderDebugMenu->addAction("Show TRUE Colors", this, SLOT(doTrueVoxelColors()));
renderDebugMenu->addAction("FALSE Color Occluded Voxels", this, SLOT(doFalseColorizeOccluded()), Qt::CTRL | Qt::Key_O);
renderDebugMenu->addAction("Show TRUE Colors", this, SLOT(doTrueVoxelColors()), Qt::CTRL | Qt::Key_T);
debugMenu->addAction("Wants Res-In", this, SLOT(setWantsResIn(bool)))->setCheckable(true);
debugMenu->addAction("Wants Monochrome", this, SLOT(setWantsMonochrome(bool)))->setCheckable(true);
debugMenu->addAction("Wants View Delta Sending", this, SLOT(setWantsDelta(bool)))->setCheckable(true);
debugMenu->addAction("Wants Occlusion Culling", this, SLOT(setWantsOcclusionCulling(bool)))->setCheckable(true);
QMenu* settingsMenu = menuBar->addMenu("Settings");
(_settingsAutosave = settingsMenu->addAction("Autosave"))->setCheckable(true);
@ -1758,15 +1765,7 @@ void Application::updateAvatar(float deltaTime) {
//
void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) {
// We will use these below, from either the camera or head vectors calculated above
glm::vec3 position;
// Camera or Head?
if (_cameraFrustum->isChecked()) {
position = camera.getPosition();
} else {
position = _myAvatar.getHeadJointPosition();
}
glm::vec3 position(camera.getPosition());
float fov = camera.getFieldOfView();
float nearClip = camera.getNearClip();
float farClip = camera.getFarClip();
@ -1933,7 +1932,7 @@ void Application::displayOculus(Camera& whichCamera) {
glPopMatrix();
}
void Application::displaySide(Camera& whichCamera) {
// transform by eye offset

View file

@ -107,12 +107,14 @@ private slots:
void doFalseRandomizeVoxelColors();
void doFalseRandomizeEveryOtherVoxelColors();
void doFalseColorizeByDistance();
void doFalseColorizeOccluded();
void doFalseColorizeInView();
void doTrueVoxelColors();
void doTreeStats();
void setWantsMonochrome(bool wantsMonochrome);
void setWantsResIn(bool wantsResIn);
void setWantsDelta(bool wantsDelta);
void setWantsOcclusionCulling(bool wantsOcclusionCulling);
void updateVoxelModeActions();
void decreaseVoxelSize();
void increaseVoxelSize();
@ -144,7 +146,6 @@ private:
void displaySide(Camera& whichCamera);
void displayOverlay();
void displayStats();
void renderViewFrustum(ViewFrustum& viewFrustum);
void setupPaintingVoxel();
@ -203,7 +204,6 @@ private:
QAction* _destructiveAddVoxel; // when doing voxel editing do we want them to be destructive
QAction* _frustumOn; // Whether or not to display the debug view frustum
QAction* _viewFrustumFromOffset; // Whether or not to offset the view of the frustum
QAction* _cameraFrustum; // which frustum to look at
QAction* _fullScreenMode; // whether we are in full screen mode
QAction* _frustumRenderModeAction;
QAction* _settingsAutosave; // Whether settings are saved automatically

View file

@ -22,6 +22,7 @@
#include "Application.h"
#include "Log.h"
#include "VoxelConstants.h"
#include "CoverageMap.h"
#include "InterfaceConfig.h"
#include "renderer/ProgramObject.h"
@ -1156,3 +1157,139 @@ void VoxelSystem::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* dest
_tree->copyFromTreeIntoSubTree(sourceTree, destinationNode);
}
struct FalseColorizeOccludedArgs {
ViewFrustum* viewFrustum;
CoverageMap* map;
VoxelTree* tree;
long totalVoxels;
long coloredVoxels;
long occludedVoxels;
long notOccludedVoxels;
long outOfView;
long subtreeVoxelsSkipped;
long nonLeaves;
long nonLeavesOutOfView;
long nonLeavesOccluded;
long stagedForDeletion;
};
struct FalseColorizeSubTreeOperationArgs {
unsigned char color[NUMBER_OF_COLORS];
long voxelsTouched;
};
bool VoxelSystem::falseColorizeSubTreeOperation(VoxelNode* node, void* extraData) {
FalseColorizeSubTreeOperationArgs* args = (FalseColorizeSubTreeOperationArgs*) extraData;
node->setFalseColor(args->color[0], args->color[1], args->color[2]);
args->voxelsTouched++;
return true;
}
bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraData) {
FalseColorizeOccludedArgs* args = (FalseColorizeOccludedArgs*) extraData;
args->totalVoxels++;
// if this node is staged for deletion, then just return
if (node->isStagedForDeletion()) {
args->stagedForDeletion++;
return true;
}
// If we are a parent, let's see if we're completely occluded.
if (!node->isLeaf()) {
args->nonLeaves++;
AABox voxelBox = node->getAABox();
voxelBox.scale(TREE_SCALE);
VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(args->viewFrustum->getProjectedShadow(voxelBox));
// If we're not all in view, then ignore it, and just return. But keep searching...
if (!voxelShadow->getAllInView()) {
args->nonLeavesOutOfView++;
delete voxelShadow;
return true;
}
CoverageMap::StorageResult result = args->map->checkMap(voxelShadow, false);
if (result == CoverageMap::OCCLUDED) {
args->nonLeavesOccluded++;
delete voxelShadow;
FalseColorizeSubTreeOperationArgs subArgs;
subArgs.color[0] = 0;
subArgs.color[1] = 255;
subArgs.color[2] = 0;
subArgs.voxelsTouched = 0;
args->tree->recurseNodeWithOperation(node, falseColorizeSubTreeOperation, &subArgs );
args->subtreeVoxelsSkipped += (subArgs.voxelsTouched - 1);
args->totalVoxels += (subArgs.voxelsTouched - 1);
return false;
}
delete voxelShadow;
return true; // keep looking...
}
if (node->isLeaf() && node->isColored() && node->getShouldRender()) {
args->coloredVoxels++;
AABox voxelBox = node->getAABox();
voxelBox.scale(TREE_SCALE);
VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(args->viewFrustum->getProjectedShadow(voxelBox));
// If we're not all in view, then ignore it, and just return. But keep searching...
if (!voxelShadow->getAllInView()) {
args->outOfView++;
delete voxelShadow;
return true;
}
CoverageMap::StorageResult result = args->map->checkMap(voxelShadow, true);
if (result == CoverageMap::OCCLUDED) {
node->setFalseColor(255, 0, 0);
args->occludedVoxels++;
} else if (result == CoverageMap::STORED) {
args->notOccludedVoxels++;
//printLog("***** falseColorizeOccludedOperation() NODE is STORED *****\n");
} else if (result == CoverageMap::DOESNT_FIT) {
//printLog("***** falseColorizeOccludedOperation() NODE DOESNT_FIT???? *****\n");
}
}
return true; // keep going!
}
void VoxelSystem::falseColorizeOccluded() {
PerformanceWarning warn(true, "falseColorizeOccluded()",true);
CoverageMap map;
FalseColorizeOccludedArgs args;
args.viewFrustum = Application::getInstance()->getViewFrustum();
args.map = &map;
args.totalVoxels = 0;
args.coloredVoxels = 0;
args.occludedVoxels = 0;
args.notOccludedVoxels = 0;
args.outOfView = 0;
args.subtreeVoxelsSkipped = 0;
args.nonLeaves = 0;
args.stagedForDeletion = 0;
args.nonLeavesOutOfView = 0;
args.nonLeavesOccluded = 0;
args.tree = _tree;
glm::vec3 position = args.viewFrustum->getPosition() * (1.0f/TREE_SCALE);
_tree->recurseTreeWithOperationDistanceSorted(falseColorizeOccludedOperation, position, (void*)&args);
printLog("falseColorizeOccluded()\n total=%ld\n colored=%ld\n occluded=%ld\n notOccluded=%ld\n outOfView=%ld\n subtreeVoxelsSkipped=%ld\n stagedForDeletion=%ld\n nonLeaves=%ld\n nonLeavesOutOfView=%ld\n nonLeavesOccluded=%ld\n",
args.totalVoxels, args.coloredVoxels, args.occludedVoxels,
args.notOccludedVoxels, args.outOfView, args.subtreeVoxelsSkipped,
args.stagedForDeletion,
args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded);
setupNewVoxelsForDrawing();
}

View file

@ -56,6 +56,7 @@ public:
void falseColorizeInView(ViewFrustum* viewFrustum);
void falseColorizeDistanceFromView(ViewFrustum* viewFrustum);
void falseColorizeRandomEveryOther();
void falseColorizeOccluded();
void killLocalVoxels();
void setRenderPipelineWarnings(bool on) { _renderWarningsOn = on; };
@ -120,6 +121,8 @@ private:
static bool removeOutOfViewOperation(VoxelNode* node, void* extraData);
static bool falseColorizeRandomEveryOtherOperation(VoxelNode* node, void* extraData);
static bool collectStatsForTreesAndVBOsOperation(VoxelNode* node, void* extraData);
static bool falseColorizeOccludedOperation(VoxelNode* node, void* extraData);
static bool falseColorizeSubTreeOperation(VoxelNode* node, void* extraData);
int updateNodeInArraysAsFullVBO(VoxelNode* node);
int updateNodeInArraysAsPartialVBO(VoxelNode* node);

122
jenkins/jobs.groovy Normal file
View file

@ -0,0 +1,122 @@
def hifiJob(String targetName, Boolean deploy) {
def JENKINS_URL = 'https://jenkins.below92.com/'
def GITHUB_HOOK_URL = 'https://github.com/worklist/hifi/'
def GIT_REPO_URL = 'git@github.com:worklist/hifi.git'
def HIPCHAT_ROOM = 'High Fidelity'
job {
name "hifi-${targetName}"
logRotator(7, -1, -1, -1)
scm {
git(GIT_REPO_URL, 'master') { node ->
node << includedRegions << "${targetName}/.*\nlibraries/.*"
}
}
configure { project ->
project / 'properties' << {
'com.coravy.hudson.plugins.github.GithubProjectProperty' {
projectUrl GITHUB_HOOK_URL
}
'jenkins.plugins.hipchat.HipChatNotifier_-HipChatJobProperty' {
room HIPCHAT_ROOM
}
}
project / 'triggers' << 'com.cloudbees.jenkins.GitHubPushTrigger' {
spec ''
}
project / 'builders' << 'hudson.plugins.cmake.CmakeBuilder' {
sourceDir targetName
buildDir 'build'
installDir ''
buildType 'RelWithDebInfo'
generator 'Unix Makefiles'
makeCommand 'make'
installCommand 'make install'
preloadScript ''
cmakeArgs ''
projectCmakePath '/usr/bin/cmake'
cleanBuild 'false'
cleanInstallDir 'false'
builderImpl ''
}
}
if (deploy) {
publishers {
publishScp("${ARTIFACT_DESTINATION}") {
entry("**/build/${targetName}", "deploy/${targetName}")
}
}
}
configure { project ->
project / 'publishers' << {
if (deploy) {
'hudson.plugins.postbuildtask.PostbuildTask' {
'tasks' {
'hudson.plugins.postbuildtask.TaskProperties' {
logTexts {
'hudson.plugins.postbuildtask.LogProperties' {
logText '.'
operator 'AND'
}
}
EscalateStatus true
RunIfJobSuccessful true
script "curl -d 'action=deploy&role=highfidelity-live&revision=${targetName}' https://${ARTIFACT_DESTINATION}"
}
}
}
}
'jenkins.plugins.hipchat.HipChatNotifier' {
jenkinsUrl JENKINS_URL
authToken "${HIPCHAT_AUTH_TOKEN}"
room HIPCHAT_ROOM
}
}
}
}
}
def deployTargets = [
'animation-server',
'audio-mixer',
'avatar-mixer',
'domain-server',
'eve',
'pairing-server',
'space-server',
'voxel-server'
]
/* setup all of the deploys jobs that use the above template */
deployTargets.each {
hifiJob(it, true)
}
/* setup the interface job, doesn't deploy */
hifiJob('interface', false)
/* setup the parametrized-build job for builds from jenkins */
parameterizedJob = hifiJob('$TARGET', true)
parameterizedJob.with {
name 'hifi-branch-deploy'
parameters {
stringParam('GITHUB_USER', '', "Specifies the name of the GitHub user that we're building from.")
stringParam('GIT_BRANCH', '', "Specifies the specific branch to build and deploy.")
stringParam('HOSTNAME', 'devel.highfidelity.io', "Specifies the hostname to deploy against.")
stringParam('TARGET', '', "What server to build specifically")
}
scm {
git('git@github.com:/$GITHUB_USER/hifi.git', '$GIT_BRANCH') { node ->
node / 'wipeOutWorkspace' << true
}
}
}

View file

@ -35,6 +35,7 @@ AvatarData::AvatarData(Agent* owningAgent) :
_wantResIn(false),
_wantColor(true),
_wantDelta(false),
_wantOcclusionCulling(false),
_headData(NULL)
{
@ -106,9 +107,10 @@ int AvatarData::getBroadcastData(unsigned char* destinationBuffer) {
// bitMask of less than byte wide items
unsigned char bitItems = 0;
if (_wantResIn) { setAtBit(bitItems,WANT_RESIN_AT_BIT); }
if (_wantColor) { setAtBit(bitItems,WANT_COLOR_AT_BIT); }
if (_wantDelta) { setAtBit(bitItems,WANT_DELTA_AT_BIT); }
if (_wantResIn) { setAtBit(bitItems, WANT_RESIN_AT_BIT); }
if (_wantColor) { setAtBit(bitItems, WANT_COLOR_AT_BIT); }
if (_wantDelta) { setAtBit(bitItems, WANT_DELTA_AT_BIT); }
if (_wantOcclusionCulling) { setAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); }
// key state
setSemiNibbleAt(bitItems,KEY_STATE_START_BIT,_keyState);
@ -192,9 +194,10 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) {
// voxel sending features...
unsigned char bitItems = 0;
bitItems = (unsigned char)*sourceBuffer++;
_wantResIn = oneAtBit(bitItems,WANT_RESIN_AT_BIT);
_wantColor = oneAtBit(bitItems,WANT_COLOR_AT_BIT);
_wantDelta = oneAtBit(bitItems,WANT_DELTA_AT_BIT);
_wantResIn = oneAtBit(bitItems, WANT_RESIN_AT_BIT);
_wantColor = oneAtBit(bitItems, WANT_COLOR_AT_BIT);
_wantDelta = oneAtBit(bitItems, WANT_DELTA_AT_BIT);
_wantOcclusionCulling = oneAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT);
// key state, stored as a semi-nibble in the bitItems
_keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT);

View file

@ -23,6 +23,7 @@ const int WANT_COLOR_AT_BIT = 1;
const int WANT_DELTA_AT_BIT = 2;
const int KEY_STATE_START_BIT = 3; // 4th and 5th bits
const int HAND_STATE_START_BIT = 5; // 6th and 7th bits
const int WANT_OCCLUSION_CULLING_BIT = 7; // 8th bit
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
@ -89,9 +90,11 @@ public:
bool getWantResIn() const { return _wantResIn; }
bool getWantColor() const { return _wantColor; }
bool getWantDelta() const { return _wantDelta; }
bool getWantOcclusionCulling() const { return _wantOcclusionCulling; }
void setWantResIn(bool wantResIn) { _wantResIn = wantResIn; }
void setWantColor(bool wantColor) { _wantColor = wantColor; }
void setWantDelta(bool wantDelta) { _wantDelta = wantDelta; }
void setWantOcclusionCulling(bool wantOcclusionCulling) { _wantOcclusionCulling = wantOcclusionCulling; }
void setHeadData(HeadData* headData) { _headData = headData; }
@ -125,6 +128,7 @@ protected:
bool _wantResIn;
bool _wantColor;
bool _wantDelta;
bool _wantOcclusionCulling;
HeadData* _headData;
private:

View file

@ -3,6 +3,7 @@
// hifi
//
// Created by Brad Hefta-Gaub on 3/29/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
// Poor-man's performance stats collector class. Useful for collecting timing
// details from various portions of the code.

View file

@ -3,6 +3,7 @@
// hifi
//
// Created by Brad Hefta-Gaub on 3/29/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
// Poor-man's performance stats collector class. Useful for collecting timing
// details from various portions of the code.

View file

@ -3,7 +3,7 @@
// hifi
//
// Created by Stephen Birarda on 2/22/13.
//
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <cstdlib>
@ -404,11 +404,11 @@ void printVoxelCode(unsigned char* voxelCode) {
}
#endif
// Inserts the value and key into three arrays sorted by the key array, the first array is the value,
// the second array is a sorted key for the value, the third array is the index for the value in it original
// non-sorted array
// returns -1 if size exceeded
// originalIndexArray is optional
int insertIntoSortedArrays(void* value, float key, int originalIndex,
void** valueArray, float* keyArray, int* originalIndexArray,
int currentCount, int maxCount) {
@ -422,15 +422,19 @@ int insertIntoSortedArrays(void* value, float key, int originalIndex,
// i is our desired location
// shift array elements to the right
if (i < currentCount && i+1 < maxCount) {
memcpy(&valueArray[i + 1], &valueArray[i], sizeof(void*) * (currentCount - i));
memcpy(&keyArray[i + 1], &keyArray[i], sizeof(float) * (currentCount - i));
memcpy(&originalIndexArray[i + 1], &originalIndexArray[i], sizeof(int) * (currentCount - i));
memmove(&valueArray[i + 1], &valueArray[i], sizeof(void*) * (currentCount - i));
memmove(&keyArray[i + 1], &keyArray[i], sizeof(float) * (currentCount - i));
if (originalIndexArray) {
memmove(&originalIndexArray[i + 1], &originalIndexArray[i], sizeof(int) * (currentCount - i));
}
}
}
// place new element at i
valueArray[i] = value;
keyArray[i] = key;
originalIndexArray[i] = originalIndex;
if (originalIndexArray) {
originalIndexArray[i] = originalIndex;
}
return currentCount + 1;
}
return -1; // error case

View file

@ -3,7 +3,7 @@
// hifi
//
// Created by Stephen Birarda on 2/22/13.
//
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__SharedUtil__

View file

@ -3,13 +3,13 @@
// hifi
//
// Created by Philip Rosedale on 3/12/13.
//
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include "StdDev.h"
#include <cmath>
const int MAX_STDEV_SAMPLES = 1000; // Don't add more than this number of samples.
const int MAX_STDEV_SAMPLES = 1000;
StDev::StDev() {
data = new float[MAX_STDEV_SAMPLES];

View file

@ -1,332 +1,353 @@
//
// AABox.h - Axis Aligned Boxes
// hifi
//
// Added by Brad Hefta-Gaub on 04/11/13.
// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards
//
// Simple axis aligned box class.
//
#include "SharedUtil.h"
#include "AABox.h"
#include "GeometryUtil.h"
void AABox::scale(float scale) {
_corner = _corner * scale;
_size = _size * scale;
_center = _center * scale;
}
void AABox::setBox(const glm::vec3& corner, const glm::vec3& size) {
_corner = corner;
_size = size;
// In the event that the caller gave us negative sizes, fix things up to be reasonable
if (_size.x < 0.0) {
_size.x = -size.x;
_corner.x -= _size.x;
}
if (_size.y < 0.0) {
_size.y = -size.y;
_corner.y -= _size.y;
}
if (_size.z < 0.0) {
_size.z = -size.z;
_corner.z -= _size.z;
}
_center = _corner + (_size * 0.5f);
}
glm::vec3 AABox::getVertexP(const glm::vec3 &normal) const {
glm::vec3 res = _corner;
if (normal.x > 0)
res.x += _size.x;
if (normal.y > 0)
res.y += _size.y;
if (normal.z > 0)
res.z += _size.z;
return(res);
}
glm::vec3 AABox::getVertexN(const glm::vec3 &normal) const {
glm::vec3 res = _corner;
if (normal.x < 0)
res.x += _size.x;
if (normal.y < 0)
res.y += _size.y;
if (normal.z < 0)
res.z += _size.z;
return(res);
}
// determines whether a value is within the extents
static bool isWithin(float value, float corner, float size) {
return value >= corner && value <= corner + size;
}
bool AABox::contains(const glm::vec3& point) const {
return isWithin(point.x, _corner.x, _size.x) &&
isWithin(point.y, _corner.y, _size.y) &&
isWithin(point.z, _corner.z, _size.z);
}
// determines whether a value is within the expanded extents
static bool isWithinExpanded(float value, float corner, float size, float expansion) {
return value >= corner - expansion && value <= corner + size + expansion;
}
bool AABox::expandedContains(const glm::vec3& point, float expansion) const {
return isWithinExpanded(point.x, _corner.x, _size.x, expansion) &&
isWithinExpanded(point.y, _corner.y, _size.y, expansion) &&
isWithinExpanded(point.z, _corner.z, _size.z, expansion);
}
// finds the intersection between a ray and the facing plane on one axis
static bool findIntersection(float origin, float direction, float corner, float size, float& distance) {
if (direction > EPSILON) {
distance = (corner - origin) / direction;
return true;
} else if (direction < -EPSILON) {
distance = (corner + size - origin) / direction;
return true;
}
return false;
}
bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const {
// handle the trivial cases where the expanded box contains the start or end
if (expandedContains(start, expansion) || expandedContains(end, expansion)) {
return true;
}
// check each axis
glm::vec3 expandedCorner = _corner - glm::vec3(expansion, expansion, expansion);
glm::vec3 expandedSize = _size + glm::vec3(expansion, expansion, expansion) * 2.0f;
glm::vec3 direction = end - start;
float axisDistance;
return (findIntersection(start.x, direction.x, expandedCorner.x, expandedSize.x, axisDistance) &&
axisDistance >= 0.0f && axisDistance <= 1.0f &&
isWithin(start.y + axisDistance*direction.y, expandedCorner.y, expandedSize.y) &&
isWithin(start.z + axisDistance*direction.z, expandedCorner.z, expandedSize.z)) ||
(findIntersection(start.y, direction.y, expandedCorner.y, expandedSize.y, axisDistance) &&
axisDistance >= 0.0f && axisDistance <= 1.0f &&
isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x) &&
isWithin(start.z + axisDistance*direction.z, expandedCorner.z, expandedSize.z)) ||
(findIntersection(start.z, direction.z, expandedCorner.z, expandedSize.z, axisDistance) &&
axisDistance >= 0.0f && axisDistance <= 1.0f &&
isWithin(start.y + axisDistance*direction.y, expandedCorner.y, expandedSize.y) &&
isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x));
}
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const {
// handle the trivial case where the box contains the origin
if (contains(origin)) {
distance = 0;
return true;
}
// check each axis
float axisDistance;
if ((findIntersection(origin.x, direction.x, _corner.x, _size.x, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _size.y) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _size.z))) {
distance = axisDistance;
face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE;
return true;
}
if ((findIntersection(origin.y, direction.y, _corner.y, _size.y, axisDistance) && axisDistance >= 0 &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _size.x) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _size.z))) {
distance = axisDistance;
face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE;
return true;
}
if ((findIntersection(origin.z, direction.z, _corner.z, _size.z, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _size.y) &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _size.x))) {
distance = axisDistance;
face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE;
return true;
}
return false;
}
bool AABox::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const {
glm::vec4 center4 = glm::vec4(center, 1.0f);
float minPenetrationLength = FLT_MAX;
for (int i = 0; i < FACE_COUNT; i++) {
glm::vec4 facePlane = getPlane((BoxFace)i);
glm::vec3 vector = getClosestPointOnFace(center, (BoxFace)i) - center;
if (glm::dot(center4, getPlane((BoxFace)i)) >= 0.0f) {
// outside this face, so use vector to closest point to determine penetration
return ::findSpherePenetration(vector, glm::vec3(-facePlane), radius, penetration);
}
float vectorLength = glm::length(vector);
if (vectorLength < minPenetrationLength) {
// remember the smallest penetration vector; if we're inside all faces, we'll use that
penetration = (vectorLength < EPSILON) ? glm::vec3(-facePlane) * radius :
vector * ((vectorLength + radius) / -vectorLength);
minPenetrationLength = vectorLength;
}
}
return true;
}
bool AABox::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const {
glm::vec4 start4 = glm::vec4(start, 1.0f);
glm::vec4 end4 = glm::vec4(end, 1.0f);
glm::vec4 startToEnd = glm::vec4(end - start, 0.0f);
float minPenetrationLength = FLT_MAX;
for (int i = 0; i < FACE_COUNT; i++) {
// find the vector from the segment to the closest point on the face (starting from deeper end)
glm::vec4 facePlane = getPlane((BoxFace)i);
glm::vec3 closest = (glm::dot(start4, facePlane) <= glm::dot(end4, facePlane)) ?
getClosestPointOnFace(start4, startToEnd, (BoxFace)i) : getClosestPointOnFace(end4, -startToEnd, (BoxFace)i);
glm::vec3 vector = -computeVectorFromPointToSegment(closest, start, end);
if (glm::dot(vector, glm::vec3(facePlane)) < 0.0f) {
// outside this face, so use vector to closest point to determine penetration
return ::findSpherePenetration(vector, glm::vec3(-facePlane), radius, penetration);
}
float vectorLength = glm::length(vector);
if (vectorLength < minPenetrationLength) {
// remember the smallest penetration vector; if we're inside all faces, we'll use that
penetration = (vectorLength < EPSILON) ? glm::vec3(-facePlane) * radius :
vector * ((vectorLength + radius) / -vectorLength);
minPenetrationLength = vectorLength;
}
}
return true;
}
glm::vec3 AABox::getClosestPointOnFace(const glm::vec3& point, BoxFace face) const {
switch (face) {
case MIN_X_FACE:
return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z),
glm::vec3(_corner.x, _corner.y + _size.y, _corner.z + _size.z));
case MAX_X_FACE:
return glm::clamp(point, glm::vec3(_corner.x + _size.x, _corner.y, _corner.z),
glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z));
case MIN_Y_FACE:
return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z),
glm::vec3(_corner.x + _size.x, _corner.y, _corner.z + _size.z));
case MAX_Y_FACE:
return glm::clamp(point, glm::vec3(_corner.x, _corner.y + _size.y, _corner.z),
glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z));
case MIN_Z_FACE:
return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z),
glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z));
case MAX_Z_FACE:
return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z + _size.z),
glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z));
}
}
glm::vec3 AABox::getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const {
// check against the four planes that border the face
BoxFace oppositeFace = getOppositeFace(face);
bool anyOutside = false;
for (int i = 0; i < FACE_COUNT; i++) {
if (i == face || i == oppositeFace) {
continue;
}
glm::vec4 iPlane = getPlane((BoxFace)i);
float originDistance = glm::dot(origin, iPlane);
if (originDistance < 0.0f) {
continue; // inside the border
}
anyOutside = true;
float divisor = glm::dot(direction, iPlane);
if (fabs(divisor) < EPSILON) {
continue; // segment is parallel to plane
}
// find intersection and see if it lies within face bounds
float directionalDistance = -originDistance / divisor;
glm::vec4 intersection = origin + direction * directionalDistance;
BoxFace iOppositeFace = getOppositeFace((BoxFace)i);
for (int j = 0; j < FACE_COUNT; j++) {
if (j == face || j == oppositeFace || j == i || j == iOppositeFace) {
continue;
}
if (glm::dot(intersection, getPlane((BoxFace)j)) > 0.0f) {
goto outerContinue; // intersection is out of bounds
}
}
return getClosestPointOnFace(glm::vec3(intersection), face);
outerContinue: ;
}
// if we were outside any of the sides, we must check against the diagonals
if (anyOutside) {
int faceAxis = face / 2;
int secondAxis = (faceAxis + 1) % 3;
int thirdAxis = (faceAxis + 2) % 3;
glm::vec4 secondAxisMinPlane = getPlane((BoxFace)(secondAxis * 2));
glm::vec4 secondAxisMaxPlane = getPlane((BoxFace)(secondAxis * 2 + 1));
glm::vec4 thirdAxisMaxPlane = getPlane((BoxFace)(thirdAxis * 2 + 1));
glm::vec4 offset = glm::vec4(0.0f, 0.0f, 0.0f,
glm::dot(glm::vec3(secondAxisMaxPlane + thirdAxisMaxPlane), _size) * 0.5f);
glm::vec4 diagonals[] = { secondAxisMinPlane + thirdAxisMaxPlane + offset,
secondAxisMaxPlane + thirdAxisMaxPlane + offset };
float minDistance = FLT_MAX;
for (int i = 0; i < sizeof(diagonals) / sizeof(diagonals[0]); i++) {
float divisor = glm::dot(direction, diagonals[i]);
if (fabs(divisor) < EPSILON) {
continue; // segment is parallel to diagonal plane
}
minDistance = glm::min(-glm::dot(origin, diagonals[i]) / divisor, minDistance);
}
if (minDistance != FLT_MAX) {
return getClosestPointOnFace(glm::vec3(origin + direction * minDistance), face);
}
}
// last resort or all inside: clamp origin to face
return getClosestPointOnFace(glm::vec3(origin), face);
}
glm::vec4 AABox::getPlane(BoxFace face) const {
switch (face) {
case MIN_X_FACE: return glm::vec4(-1.0f, 0.0f, 0.0f, _corner.x);
case MAX_X_FACE: return glm::vec4(1.0f, 0.0f, 0.0f, -_corner.x - _size.x);
case MIN_Y_FACE: return glm::vec4(0.0f, -1.0f, 0.0f, _corner.y);
case MAX_Y_FACE: return glm::vec4(0.0f, 1.0f, 0.0f, -_corner.y - _size.y);
case MIN_Z_FACE: return glm::vec4(0.0f, 0.0f, -1.0f, _corner.z);
case MAX_Z_FACE: return glm::vec4(0.0f, 0.0f, 1.0f, -_corner.z - _size.z);
}
}
BoxFace AABox::getOppositeFace(BoxFace face) {
switch (face) {
case MIN_X_FACE: return MAX_X_FACE;
case MAX_X_FACE: return MIN_X_FACE;
case MIN_Y_FACE: return MAX_Y_FACE;
case MAX_Y_FACE: return MIN_Y_FACE;
case MIN_Z_FACE: return MAX_Z_FACE;
case MAX_Z_FACE: return MIN_Z_FACE;
}
}
//
// AABox.h - Axis Aligned Boxes
// hifi
//
// Added by Brad Hefta-Gaub on 04/11/13.
// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards
//
// Simple axis aligned box class.
//
#include "SharedUtil.h"
#include "AABox.h"
#include "GeometryUtil.h"
void AABox::scale(float scale) {
_corner = _corner * scale;
_size = _size * scale;
_center = _center * scale;
}
glm::vec3 AABox::getVertex(BoxVertex vertex) const {
switch (vertex) {
case BOTTOM_LEFT_NEAR:
return _corner + glm::vec3(_size.x, 0, 0);
case BOTTOM_RIGHT_NEAR:
return _corner;
case TOP_RIGHT_NEAR:
return _corner + glm::vec3(0, _size.y, 0);
case TOP_LEFT_NEAR:
return _corner + glm::vec3(_size.x, _size.y, 0);
case BOTTOM_LEFT_FAR:
return _corner + glm::vec3(_size.x, 0, _size.z);
case BOTTOM_RIGHT_FAR:
return _corner + glm::vec3(0, 0, _size.z);
case TOP_RIGHT_FAR:
return _corner + glm::vec3(0, _size.y, _size.z);
case TOP_LEFT_FAR:
return _corner + _size;
}
}
void AABox::setBox(const glm::vec3& corner, const glm::vec3& size) {
_corner = corner;
_size = size;
// In the event that the caller gave us negative sizes, fix things up to be reasonable
if (_size.x < 0.0) {
_size.x = -size.x;
_corner.x -= _size.x;
}
if (_size.y < 0.0) {
_size.y = -size.y;
_corner.y -= _size.y;
}
if (_size.z < 0.0) {
_size.z = -size.z;
_corner.z -= _size.z;
}
_center = _corner + (_size * 0.5f);
}
glm::vec3 AABox::getVertexP(const glm::vec3& normal) const {
glm::vec3 result = _corner;
if (normal.x > 0) {
result.x += _size.x;
}
if (normal.y > 0) {
result.y += _size.y;
}
if (normal.z > 0) {
result.z += _size.z;
}
return result;
}
glm::vec3 AABox::getVertexN(const glm::vec3& normal) const {
glm::vec3 result = _corner;
if (normal.x < 0) {
result.x += _size.x;
}
if (normal.y < 0) {
result.y += _size.y;
}
if (normal.z < 0) {
result.z += _size.z;
}
return result;
}
// determines whether a value is within the extents
static bool isWithin(float value, float corner, float size) {
return value >= corner && value <= corner + size;
}
bool AABox::contains(const glm::vec3& point) const {
return isWithin(point.x, _corner.x, _size.x) &&
isWithin(point.y, _corner.y, _size.y) &&
isWithin(point.z, _corner.z, _size.z);
}
// determines whether a value is within the expanded extents
static bool isWithinExpanded(float value, float corner, float size, float expansion) {
return value >= corner - expansion && value <= corner + size + expansion;
}
bool AABox::expandedContains(const glm::vec3& point, float expansion) const {
return isWithinExpanded(point.x, _corner.x, _size.x, expansion) &&
isWithinExpanded(point.y, _corner.y, _size.y, expansion) &&
isWithinExpanded(point.z, _corner.z, _size.z, expansion);
}
// finds the intersection between a ray and the facing plane on one axis
static bool findIntersection(float origin, float direction, float corner, float size, float& distance) {
if (direction > EPSILON) {
distance = (corner - origin) / direction;
return true;
} else if (direction < -EPSILON) {
distance = (corner + size - origin) / direction;
return true;
}
return false;
}
bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const {
// handle the trivial cases where the expanded box contains the start or end
if (expandedContains(start, expansion) || expandedContains(end, expansion)) {
return true;
}
// check each axis
glm::vec3 expandedCorner = _corner - glm::vec3(expansion, expansion, expansion);
glm::vec3 expandedSize = _size + glm::vec3(expansion, expansion, expansion) * 2.0f;
glm::vec3 direction = end - start;
float axisDistance;
return (findIntersection(start.x, direction.x, expandedCorner.x, expandedSize.x, axisDistance) &&
axisDistance >= 0.0f && axisDistance <= 1.0f &&
isWithin(start.y + axisDistance*direction.y, expandedCorner.y, expandedSize.y) &&
isWithin(start.z + axisDistance*direction.z, expandedCorner.z, expandedSize.z)) ||
(findIntersection(start.y, direction.y, expandedCorner.y, expandedSize.y, axisDistance) &&
axisDistance >= 0.0f && axisDistance <= 1.0f &&
isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x) &&
isWithin(start.z + axisDistance*direction.z, expandedCorner.z, expandedSize.z)) ||
(findIntersection(start.z, direction.z, expandedCorner.z, expandedSize.z, axisDistance) &&
axisDistance >= 0.0f && axisDistance <= 1.0f &&
isWithin(start.y + axisDistance*direction.y, expandedCorner.y, expandedSize.y) &&
isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x));
}
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const {
// handle the trivial case where the box contains the origin
if (contains(origin)) {
distance = 0;
return true;
}
// check each axis
float axisDistance;
if ((findIntersection(origin.x, direction.x, _corner.x, _size.x, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _size.y) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _size.z))) {
distance = axisDistance;
face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE;
return true;
}
if ((findIntersection(origin.y, direction.y, _corner.y, _size.y, axisDistance) && axisDistance >= 0 &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _size.x) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _size.z))) {
distance = axisDistance;
face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE;
return true;
}
if ((findIntersection(origin.z, direction.z, _corner.z, _size.z, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _size.y) &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _size.x))) {
distance = axisDistance;
face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE;
return true;
}
return false;
}
bool AABox::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const {
glm::vec4 center4 = glm::vec4(center, 1.0f);
float minPenetrationLength = FLT_MAX;
for (int i = 0; i < FACE_COUNT; i++) {
glm::vec4 facePlane = getPlane((BoxFace)i);
glm::vec3 vector = getClosestPointOnFace(center, (BoxFace)i) - center;
if (glm::dot(center4, getPlane((BoxFace)i)) >= 0.0f) {
// outside this face, so use vector to closest point to determine penetration
return ::findSpherePenetration(vector, glm::vec3(-facePlane), radius, penetration);
}
float vectorLength = glm::length(vector);
if (vectorLength < minPenetrationLength) {
// remember the smallest penetration vector; if we're inside all faces, we'll use that
penetration = (vectorLength < EPSILON) ? glm::vec3(-facePlane) * radius :
vector * ((vectorLength + radius) / -vectorLength);
minPenetrationLength = vectorLength;
}
}
return true;
}
bool AABox::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const {
glm::vec4 start4 = glm::vec4(start, 1.0f);
glm::vec4 end4 = glm::vec4(end, 1.0f);
glm::vec4 startToEnd = glm::vec4(end - start, 0.0f);
float minPenetrationLength = FLT_MAX;
for (int i = 0; i < FACE_COUNT; i++) {
// find the vector from the segment to the closest point on the face (starting from deeper end)
glm::vec4 facePlane = getPlane((BoxFace)i);
glm::vec3 closest = (glm::dot(start4, facePlane) <= glm::dot(end4, facePlane)) ?
getClosestPointOnFace(start4, startToEnd, (BoxFace)i) : getClosestPointOnFace(end4, -startToEnd, (BoxFace)i);
glm::vec3 vector = -computeVectorFromPointToSegment(closest, start, end);
if (glm::dot(vector, glm::vec3(facePlane)) < 0.0f) {
// outside this face, so use vector to closest point to determine penetration
return ::findSpherePenetration(vector, glm::vec3(-facePlane), radius, penetration);
}
float vectorLength = glm::length(vector);
if (vectorLength < minPenetrationLength) {
// remember the smallest penetration vector; if we're inside all faces, we'll use that
penetration = (vectorLength < EPSILON) ? glm::vec3(-facePlane) * radius :
vector * ((vectorLength + radius) / -vectorLength);
minPenetrationLength = vectorLength;
}
}
return true;
}
glm::vec3 AABox::getClosestPointOnFace(const glm::vec3& point, BoxFace face) const {
switch (face) {
case MIN_X_FACE:
return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z),
glm::vec3(_corner.x, _corner.y + _size.y, _corner.z + _size.z));
case MAX_X_FACE:
return glm::clamp(point, glm::vec3(_corner.x + _size.x, _corner.y, _corner.z),
glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z));
case MIN_Y_FACE:
return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z),
glm::vec3(_corner.x + _size.x, _corner.y, _corner.z + _size.z));
case MAX_Y_FACE:
return glm::clamp(point, glm::vec3(_corner.x, _corner.y + _size.y, _corner.z),
glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z));
case MIN_Z_FACE:
return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z),
glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z));
case MAX_Z_FACE:
return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z + _size.z),
glm::vec3(_corner.x + _size.x, _corner.y + _size.y, _corner.z + _size.z));
}
}
glm::vec3 AABox::getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const {
// check against the four planes that border the face
BoxFace oppositeFace = getOppositeFace(face);
bool anyOutside = false;
for (int i = 0; i < FACE_COUNT; i++) {
if (i == face || i == oppositeFace) {
continue;
}
glm::vec4 iPlane = getPlane((BoxFace)i);
float originDistance = glm::dot(origin, iPlane);
if (originDistance < 0.0f) {
continue; // inside the border
}
anyOutside = true;
float divisor = glm::dot(direction, iPlane);
if (fabs(divisor) < EPSILON) {
continue; // segment is parallel to plane
}
// find intersection and see if it lies within face bounds
float directionalDistance = -originDistance / divisor;
glm::vec4 intersection = origin + direction * directionalDistance;
BoxFace iOppositeFace = getOppositeFace((BoxFace)i);
for (int j = 0; j < FACE_COUNT; j++) {
if (j == face || j == oppositeFace || j == i || j == iOppositeFace) {
continue;
}
if (glm::dot(intersection, getPlane((BoxFace)j)) > 0.0f) {
goto outerContinue; // intersection is out of bounds
}
}
return getClosestPointOnFace(glm::vec3(intersection), face);
outerContinue: ;
}
// if we were outside any of the sides, we must check against the diagonals
if (anyOutside) {
int faceAxis = face / 2;
int secondAxis = (faceAxis + 1) % 3;
int thirdAxis = (faceAxis + 2) % 3;
glm::vec4 secondAxisMinPlane = getPlane((BoxFace)(secondAxis * 2));
glm::vec4 secondAxisMaxPlane = getPlane((BoxFace)(secondAxis * 2 + 1));
glm::vec4 thirdAxisMaxPlane = getPlane((BoxFace)(thirdAxis * 2 + 1));
glm::vec4 offset = glm::vec4(0.0f, 0.0f, 0.0f,
glm::dot(glm::vec3(secondAxisMaxPlane + thirdAxisMaxPlane), _size) * 0.5f);
glm::vec4 diagonals[] = { secondAxisMinPlane + thirdAxisMaxPlane + offset,
secondAxisMaxPlane + thirdAxisMaxPlane + offset };
float minDistance = FLT_MAX;
for (int i = 0; i < sizeof(diagonals) / sizeof(diagonals[0]); i++) {
float divisor = glm::dot(direction, diagonals[i]);
if (fabs(divisor) < EPSILON) {
continue; // segment is parallel to diagonal plane
}
minDistance = glm::min(-glm::dot(origin, diagonals[i]) / divisor, minDistance);
}
if (minDistance != FLT_MAX) {
return getClosestPointOnFace(glm::vec3(origin + direction * minDistance), face);
}
}
// last resort or all inside: clamp origin to face
return getClosestPointOnFace(glm::vec3(origin), face);
}
glm::vec4 AABox::getPlane(BoxFace face) const {
switch (face) {
case MIN_X_FACE: return glm::vec4(-1.0f, 0.0f, 0.0f, _corner.x);
case MAX_X_FACE: return glm::vec4(1.0f, 0.0f, 0.0f, -_corner.x - _size.x);
case MIN_Y_FACE: return glm::vec4(0.0f, -1.0f, 0.0f, _corner.y);
case MAX_Y_FACE: return glm::vec4(0.0f, 1.0f, 0.0f, -_corner.y - _size.y);
case MIN_Z_FACE: return glm::vec4(0.0f, 0.0f, -1.0f, _corner.z);
case MAX_Z_FACE: return glm::vec4(0.0f, 0.0f, 1.0f, -_corner.z - _size.z);
}
}
BoxFace AABox::getOppositeFace(BoxFace face) {
switch (face) {
case MIN_X_FACE: return MAX_X_FACE;
case MAX_X_FACE: return MIN_X_FACE;
case MIN_Y_FACE: return MAX_Y_FACE;
case MAX_Y_FACE: return MIN_Y_FACE;
case MIN_Z_FACE: return MAX_Z_FACE;
case MAX_Z_FACE: return MIN_Z_FACE;
}
}

View file

@ -1,72 +1,86 @@
//
// AABox.h - Axis Aligned Boxes
// hifi
//
// Added by Brad Hefta-Gaub on 04/11/13.
// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards
//
// Simple axis aligned box class.
//
#ifndef _AABOX_
#define _AABOX_
#include <glm/glm.hpp>
enum BoxFace {
MIN_X_FACE,
MAX_X_FACE,
MIN_Y_FACE,
MAX_Y_FACE,
MIN_Z_FACE,
MAX_Z_FACE
};
const int FACE_COUNT = 6;
class AABox
{
public:
AABox(const glm::vec3& corner, float size) : _corner(corner), _size(size, size, size) { };
AABox(const glm::vec3& corner, float x, float y, float z) : _corner(corner), _size(x, y, z) { };
AABox(const glm::vec3& corner, const glm::vec3& size) : _corner(corner), _size(size) { };
AABox() : _corner(0,0,0), _size(0,0,0) { }
~AABox() { }
void setBox(const glm::vec3& corner, float x, float y, float z) { setBox(corner,glm::vec3(x,y,z)); };
void setBox(const glm::vec3& corner, const glm::vec3& size);
// for use in frustum computations
glm::vec3 getVertexP(const glm::vec3& normal) const;
glm::vec3 getVertexN(const glm::vec3& normal) const;
void scale(float scale);
const glm::vec3& getCorner() const { return _corner; };
const glm::vec3& getSize() const { return _size; };
const glm::vec3& getCenter() const { return _center; };
bool contains(const glm::vec3& point) const;
bool expandedContains(const glm::vec3& point, float expansion) const;
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const;
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const;
private:
glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const;
glm::vec3 getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const;
glm::vec4 getPlane(BoxFace face) const;
static BoxFace getOppositeFace(BoxFace face);
glm::vec3 _corner;
glm::vec3 _center;
glm::vec3 _size;
};
#endif
//
// AABox.h - Axis Aligned Boxes
// hifi
//
// Added by Brad Hefta-Gaub on 04/11/13.
// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards
//
// Simple axis aligned box class.
//
#ifndef _AABOX_
#define _AABOX_
#include <glm/glm.hpp>
enum BoxFace {
MIN_X_FACE,
MAX_X_FACE,
MIN_Y_FACE,
MAX_Y_FACE,
MIN_Z_FACE,
MAX_Z_FACE
};
enum BoxVertex {
BOTTOM_LEFT_NEAR = 0,
BOTTOM_RIGHT_NEAR = 1,
TOP_RIGHT_NEAR = 2,
TOP_LEFT_NEAR = 3,
BOTTOM_LEFT_FAR = 4,
BOTTOM_RIGHT_FAR = 5,
TOP_RIGHT_FAR = 6,
TOP_LEFT_FAR = 7
};
const int FACE_COUNT = 6;
class AABox
{
public:
AABox(const glm::vec3& corner, float size) : _corner(corner), _size(size, size, size) { };
AABox(const glm::vec3& corner, float x, float y, float z) : _corner(corner), _size(x, y, z) { };
AABox(const glm::vec3& corner, const glm::vec3& size) : _corner(corner), _size(size) { };
AABox() : _corner(0,0,0), _size(0,0,0) { }
~AABox() { }
void setBox(const glm::vec3& corner, float x, float y, float z) { setBox(corner,glm::vec3(x,y,z)); };
void setBox(const glm::vec3& corner, const glm::vec3& size);
// for use in frustum computations
glm::vec3 getVertexP(const glm::vec3& normal) const;
glm::vec3 getVertexN(const glm::vec3& normal) const;
void scale(float scale);
const glm::vec3& getCorner() const { return _corner; };
const glm::vec3& getSize() const { return _size; };
const glm::vec3& getCenter() const { return _center; };
glm::vec3 getVertex(BoxVertex vertex) const;
bool contains(const glm::vec3& point) const;
bool expandedContains(const glm::vec3& point, float expansion) const;
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const;
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const;
private:
glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const;
glm::vec3 getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const;
glm::vec4 getPlane(BoxFace face) const;
static BoxFace getOppositeFace(BoxFace face);
glm::vec3 _corner;
glm::vec3 _center;
glm::vec3 _size;
};
#endif

View file

@ -0,0 +1,193 @@
//
// CoverageMap.cpp -
// hifi
//
// Added by Brad Hefta-Gaub on 06/11/13.
//
#include "CoverageMap.h"
#include <SharedUtil.h>
#include <cstring>
#include "Log.h"
int CoverageMap::_mapCount = 0;
const BoundingBox CoverageMap::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-2.f,-2.f), glm::vec2(4.f,4.f));
CoverageMap::CoverageMap(BoundingBox boundingBox, bool isRoot, bool managePolygons) :
_isRoot(isRoot), _myBoundingBox(boundingBox), _managePolygons(managePolygons) {
_mapCount++;
init();
//printLog("CoverageMap created... _mapCount=%d\n",_mapCount);
};
CoverageMap::~CoverageMap() {
erase();
};
void CoverageMap::erase() {
// If we're in charge of managing the polygons, then clean them up first
if (_managePolygons) {
for (int i = 0; i < _polygonCount; i++) {
delete _polygons[i];
_polygons[i] = NULL; // do we need to do this?
}
}
// Now, clean up our local storage
_polygonCount = 0;
_polygonArraySize = 0;
if (_polygons) {
delete[] _polygons;
_polygons = NULL;
}
if (_polygonDistances) {
delete[] _polygonDistances;
_polygonDistances = NULL;
}
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
if (_childMaps[i]) {
delete _childMaps[i];
_childMaps[i] = NULL;
}
}
/**
if (_isRoot) {
printLog("CoverageMap last to be deleted...\n");
printLog("_mapCount=%d\n",_mapCount);
printLog("_maxPolygonsUsed=%d\n",_maxPolygonsUsed);
printLog("_totalPolygons=%d\n",_totalPolygons);
_maxPolygonsUsed = 0;
_totalPolygons = 0;
_mapCount = 0;
}
**/
}
void CoverageMap::init() {
_polygonCount = 0;
_polygonArraySize = 0;
_polygons = NULL;
_polygonDistances = NULL;
memset(_childMaps,0,sizeof(_childMaps));
}
// 0 = bottom, right
// 1 = bottom, left
// 2 = top, right
// 3 = top, left
BoundingBox CoverageMap::getChildBoundingBox(int childIndex) {
const int LEFT_BIT = 1;
const int TOP_BIT = 2;
// initialize to our corner, and half our size
BoundingBox result(_myBoundingBox.corner,_myBoundingBox.size/2.0f);
// if our "left" bit is set, then add size.x to the corner
if ((childIndex & LEFT_BIT) == LEFT_BIT) {
result.corner.x += result.size.x;
}
// if our "top" bit is set, then add size.y to the corner
if ((childIndex & TOP_BIT) == TOP_BIT) {
result.corner.y += result.size.y;
}
return result;
}
void CoverageMap::growPolygonArray() {
VoxelProjectedPolygon** newPolygons = new VoxelProjectedPolygon*[_polygonArraySize + DEFAULT_GROW_SIZE];
float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE];
if (_polygons) {
memcpy(newPolygons, _polygons, sizeof(VoxelProjectedPolygon*) * _polygonCount);
delete[] _polygons;
memcpy(newDistances, _polygonDistances, sizeof(float) * _polygonCount);
delete[] _polygonDistances;
}
_polygons = newPolygons;
_polygonDistances = newDistances;
_polygonArraySize = _polygonArraySize + DEFAULT_GROW_SIZE;
//printLog("CoverageMap::growPolygonArray() _polygonArraySize=%d...\n",_polygonArraySize);
}
int CoverageMap::_maxPolygonsUsed = 0;
int CoverageMap::_totalPolygons = 0;
// just handles storage in the array, doesn't test for occlusion or
// determining if this is the correct map to store in!
void CoverageMap::storeInArray(VoxelProjectedPolygon* polygon) {
_totalPolygons++;
if (_polygonArraySize < _polygonCount + 1) {
growPolygonArray();
}
// This old code assumes that polygons will always be added in z-buffer order, but that doesn't seem to
// be a good assumption. So instead, we will need to sort this by distance. Use a binary search to find the
// insertion point in this array, and shift the array accordingly
const int IGNORED = NULL;
_polygonCount = insertIntoSortedArrays((void*)polygon, polygon->getDistance(), IGNORED,
(void**)_polygons, _polygonDistances, IGNORED,
_polygonCount, _polygonArraySize);
if (_polygonCount > _maxPolygonsUsed) {
_maxPolygonsUsed = _polygonCount;
//printLog("CoverageMap new _maxPolygonsUsed reached=%d\n",_maxPolygonsUsed);
//_myBoundingBox.printDebugDetails("map._myBoundingBox");
}
}
// possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT
CoverageMap::StorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon, bool storeIt) {
if (_isRoot || _myBoundingBox.contains(polygon->getBoundingBox())) {
// check to make sure this polygon isn't occluded by something at this level
for (int i = 0; i < _polygonCount; i++) {
VoxelProjectedPolygon* polygonAtThisLevel = _polygons[i];
// Check to make sure that the polygon in question is "behind" the polygon in the list
// otherwise, we don't need to test it's occlusion (although, it means we've potentially
// added an item previously that may be occluded??? Is that possible? Maybe not, because two
// voxels can't have the exact same outline. So one occludes the other, they can't both occlude
// each other.
if (polygonAtThisLevel->occludes(*polygon)) {
// if the polygonAtThisLevel is actually behind the one we're inserting, then we don't
// want to report our inserted one as occluded, but we do want to add our inserted one.
if (polygonAtThisLevel->getDistance() >= polygon->getDistance()) {
if (storeIt) {
storeInArray(polygon);
return STORED;
} else {
return NOT_STORED;
}
}
// this polygon is occluded by a closer polygon, so don't store it, and let the caller know
return OCCLUDED;
}
}
// if we made it here, then it means the polygon being stored is not occluded
// at this level of the quad tree, so we can continue to insert it into the map.
// First we check to see if it fits in any of our sub maps
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
BoundingBox childMapBoundingBox = getChildBoundingBox(i);
if (childMapBoundingBox.contains(polygon->getBoundingBox())) {
// if no child map exists yet, then create it
if (!_childMaps[i]) {
_childMaps[i] = new CoverageMap(childMapBoundingBox, NOT_ROOT, _managePolygons);
}
return _childMaps[i]->checkMap(polygon, storeIt);
}
}
// if we got this far, then the polygon is in our bounding box, but doesn't fit in
// any of our child bounding boxes, so we should add it here.
if (storeIt) {
storeInArray(polygon);
return STORED;
} else {
return NOT_STORED;
}
}
return DOESNT_FIT;
}

View file

@ -0,0 +1,54 @@
//
// CoverageMap.h - 2D CoverageMap Quad tree for storage of VoxelProjectedPolygons
// hifi
//
// Added by Brad Hefta-Gaub on 06/11/13.
//
#ifndef _COVERAGE_MAP_
#define _COVERAGE_MAP_
#include <glm/glm.hpp>
#include "VoxelProjectedPolygon.h"
class CoverageMap {
public:
static const int NUMBER_OF_CHILDREN = 4;
static const bool NOT_ROOT=false;
static const bool IS_ROOT=true;
static const BoundingBox ROOT_BOUNDING_BOX;
CoverageMap(BoundingBox boundingBox = ROOT_BOUNDING_BOX, bool isRoot = IS_ROOT, bool managePolygons = true);
~CoverageMap();
typedef enum {STORED, OCCLUDED, DOESNT_FIT, NOT_STORED} StorageResult;
StorageResult checkMap(VoxelProjectedPolygon* polygon, bool storeIt = true);
BoundingBox getChildBoundingBox(int childIndex);
void erase(); // erase the coverage map
private:
void init();
void growPolygonArray();
void storeInArray(VoxelProjectedPolygon* polygon);
bool _isRoot; // is this map the root, if so, it never returns DOESNT_FIT
BoundingBox _myBoundingBox;
bool _managePolygons; // will the coverage map delete the polygons on destruct
int _polygonCount; // how many polygons at this level
int _polygonArraySize; // how much room is there to store polygons at this level
VoxelProjectedPolygon** _polygons;
float* _polygonDistances;
CoverageMap* _childMaps[NUMBER_OF_CHILDREN];
static const int DEFAULT_GROW_SIZE = 100;
static int _mapCount;
static int _maxPolygonsUsed;
static int _totalPolygons;
};
#endif // _COVERAGE_MAP_

View file

@ -115,3 +115,28 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3&
return currentDirection * glm::max(directionalComponent, currentLength) +
newPenetration - (currentDirection * directionalComponent);
}
// Do line segments (r1p1.x, r1p1.y)--(r1p2.x, r1p2.y) and (r2p1.x, r2p1.y)--(r2p2.x, r2p2.y) intersect?
bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2) {
int d1 = computeDirection(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p1.x, r1p1.y);
int d2 = computeDirection(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p2.x, r1p2.y);
int d3 = computeDirection(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p1.x, r2p1.y);
int d4 = computeDirection(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p2.x, r2p2.y);
return (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) &&
((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) ||
(d1 == 0 && isOnSegment(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p1.x, r1p1.y)) ||
(d2 == 0 && isOnSegment(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p2.x, r1p2.y)) ||
(d3 == 0 && isOnSegment(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p1.x, r2p1.y)) ||
(d4 == 0 && isOnSegment(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p2.x, r2p2.y));
}
bool isOnSegment(float xi, float yi, float xj, float yj, float xk, float yk) {
return (xi <= xk || xj <= xk) && (xk <= xi || xk <= xj) &&
(yi <= yk || yj <= yk) && (yk <= yi || yk <= yj);
}
int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk) {
float a = (xk - xi) * (yj - yi);
float b = (xj - xi) * (yk - yi);
return a < b ? -1 : a > b ? 1 : 0;
}

View file

@ -39,4 +39,8 @@ bool findCapsulePlanePenetration(const glm::vec3& penetratorStart, const glm::ve
glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration);
bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2);
bool isOnSegment(float xi, float yi, float xj, float yj, float xk, float yk);
int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk);
#endif /* defined(__interface__GeometryUtil__) */

View file

@ -425,3 +425,116 @@ void ViewFrustum::printDebugDetails() const {
_eyeOffsetOrientation.w );
}
glm::vec2 ViewFrustum::projectPoint(glm::vec3 point, bool& pointInView) const {
// Projection matrix : Field of View, ratio, display range : near to far
glm::mat4 projection = glm::perspective(_fieldOfView, _aspectRatio, _nearClip, _farClip);
glm::vec3 lookAt = _position + _direction;
glm::mat4 view = glm::lookAt(_position, lookAt, _up);
// Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it)
glm::mat4 VP = projection * view; // Remember, matrix multiplication is the other way around
glm::vec4 pointVec4 = glm::vec4(point,1);
glm::vec4 projectedPointVec4 = VP * pointVec4;
pointInView = (projectedPointVec4.w > 0); // math! If the w result is negative then the point is behind the viewer
// what happens with w is 0???
float x = projectedPointVec4.x / projectedPointVec4.w;
float y = projectedPointVec4.y / projectedPointVec4.w;
glm::vec2 projectedPoint(x,y);
// if the point is out of view we also need to flip the signs of x and y
if (!pointInView) {
projectedPoint.x = -x;
projectedPoint.y = -y;
}
return projectedPoint;
}
const int MAX_POSSIBLE_COMBINATIONS = 43;
const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_SHADOW_VERTEX_COUNT+1] = {
// Number of vertices in shadow polygon for the visible faces, then a list of the index of each vertice from the AABox
{0}, // inside
{4, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR}, // right
{4, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR}, // left
{0}, // n/a
{4, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // bottom
{6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR},//bottom, right
{6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR},//bottom, left
{0}, // n/a
{4, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR}, // top
{6, TOP_RIGHT_NEAR, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR}, // top, right
{6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // top, left
{0}, // n/a
{0}, // n/a
{0}, // n/a
{0}, // n/a
{0}, // n/a
{4, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front or near
{6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front, right
{6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // front, left
{0}, // n/a
{6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR}, // front,bottom
{6, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR}, //front,bottom,right
{6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR}, //front,bottom,left
{0}, // n/a
{6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front, top
{6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front, top, right
{6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // front, top, left
{0}, // n/a
{0}, // n/a
{0}, // n/a
{0}, // n/a
{0}, // n/a
{4, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR}, // back
{6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR}, // back, right
{6, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR}, // back, left
{0}, // n/a
{6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // back, bottom
{6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR},//back, bottom, right
{6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR},//back, bottom, left
{0}, // n/a
{6, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR}, // back, top
{6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR}, // back, top, right
{6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // back, top, left
};
VoxelProjectedPolygon ViewFrustum::getProjectedShadow(const AABox& box) const {
glm::vec3 bottomNearRight = box.getCorner();
glm::vec3 topFarLeft = box.getCorner() + box.getSize();
int lookUp = ((_position.x < bottomNearRight.x) ) // 1 = right | compute 6-bit
+ ((_position.x > topFarLeft.x ) << 1) // 2 = left | code to
+ ((_position.y < bottomNearRight.y) << 2) // 4 = bottom | classify camera
+ ((_position.y > topFarLeft.y ) << 3) // 8 = top | with respect to
+ ((_position.z < bottomNearRight.z) << 4) // 16 = front/near | the 6 defining
+ ((_position.z > topFarLeft.z ) << 5); // 32 = back/far | planes
int vertexCount = hullVertexLookup[lookUp][0]; //look up number of vertices
VoxelProjectedPolygon shadow(vertexCount);
bool pointInView = true;
bool allPointsInView = false; // assume the best, but wait till we know we have a vertex
bool anyPointsInView = false; // assume the worst!
if (vertexCount) {
allPointsInView = true; // assume the best!
for(int i = 0; i < vertexCount; i++) {
int vertexNum = hullVertexLookup[lookUp][i+1];
glm::vec3 point = box.getVertex((BoxVertex)vertexNum);
glm::vec2 projectedPoint = projectPoint(point, pointInView);
allPointsInView = allPointsInView && pointInView;
anyPointsInView = anyPointsInView || pointInView;
shadow.setVertex(i, projectedPoint);
}
}
// set the distance from our camera position, to the closest vertex
float distance = glm::distance(getPosition(), box.getCenter());
shadow.setDistance(distance);
shadow.setAnyInView(anyPointsInView);
shadow.setAllInView(allPointsInView);
return shadow;
}

View file

@ -15,6 +15,7 @@
#include <glm/gtc/quaternion.hpp>
#include "Plane.h"
#include "AABox.h"
#include "VoxelProjectedPolygon.h"
const float DEFAULT_KEYHOLE_RADIUS = 2.0f;
@ -87,6 +88,9 @@ public:
void printDebugDetails() const;
glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const;
VoxelProjectedPolygon getProjectedShadow(const AABox& box) const;
private:
// Used for keyhole calculations

View file

@ -0,0 +1,126 @@
//
// VoxelProjectedPolygon.cpp - The projected shadow (on the 2D view plane) for a voxel
// hifi
//
// Added by Brad Hefta-Gaub on 06/11/13.
//
#include "VoxelProjectedPolygon.h"
#include "GeometryUtil.h"
#include "Log.h"
bool BoundingBox::contains(const BoundingBox& box) const {
return (
(box.corner.x >= corner.x) &&
(box.corner.y >= corner.y) &&
(box.corner.x + box.size.x <= corner.x + size.x) &&
(box.corner.y + box.size.y <= corner.y + size.y)
);
};
void BoundingBox::printDebugDetails(const char* label) const {
if (label) {
printLog(label);
} else {
printLog("BoundingBox");
}
printLog("\n corner=%f,%f size=%f,%f\n", corner.x, corner.y, size.x, size.y);
}
void VoxelProjectedPolygon::setVertex(int vertex, const glm::vec2& point) {
_vertices[vertex] = point;
// keep track of our bounding box
if (point.x > _maxX) {
_maxX = point.x;
}
if (point.y > _maxY) {
_maxY = point.y;
}
if (point.x < _minX) {
_minX = point.x;
}
if (point.y < _minY) {
_minY = point.y;
}
};
bool VoxelProjectedPolygon::occludes(const VoxelProjectedPolygon& occludee) const {
// if we are completely out of view, then we definitely don't occlude!
// if the occludee is completely out of view, then we also don't occlude it
//
// this is true, but unfortunately, we're not quite handling projects in the
// case when SOME points are in view and others are not. So, we will not consider
// occlusion for any shadows that are partially in view.
if (!getAllInView() || !occludee.getAllInView() ) {
return false;
}
// first check the bounding boxes, the occludee must be fully within the boounding box of this shadow
if ((occludee.getMaxX() > getMaxX()) ||
(occludee.getMaxY() > getMaxY()) ||
(occludee.getMinX() < getMinX()) ||
(occludee.getMinY() < getMinY())) {
return false;
}
// if we got this far, then check each vertex of the occludee, if all those points
// are inside our polygon, then the tested occludee is fully occluded
for(int i = 0; i < occludee.getVertexCount(); i++) {
if (!pointInside(occludee.getVertex(i))) {
return false;
}
}
// if we got this far, then indeed the occludee is fully occluded by us
return true;
}
bool VoxelProjectedPolygon::pointInside(const glm::vec2& point) const {
// first check the bounding boxes, the point must be fully within the boounding box of this shadow
if ((point.x > getMaxX()) ||
(point.y > getMaxY()) ||
(point.x < getMinX()) ||
(point.y < getMinY())) {
return false;
}
float e = (getMaxX() - getMinX()) / 100.0f; // some epsilon
// We need to have one ray that goes from a known outside position to the point in question. We'll pick a
// start point just outside of our min X
glm::vec2 r1p1(getMinX() - e, point.y);
glm::vec2 r1p2(point);
glm::vec2 r2p1(getVertex(getVertexCount()-1)); // start with last vertex to first vertex
glm::vec2 r2p2;
// Test the ray against all sides
int intersections = 0;
for (int i = 0; i < getVertexCount(); i++) {
r2p2 = getVertex(i);
if (doLineSegmentsIntersect(r1p1, r1p2, r2p1, r2p2)) {
intersections++;
}
r2p1 = r2p2; // set up for next side
}
// If odd number of intersections, we're inside
return ((intersections & 1) == 1);
}
void VoxelProjectedPolygon::printDebugDetails() const {
printf("VoxelProjectedPolygon...");
printf(" minX=%f maxX=%f minY=%f maxY=%f\n", getMinX(), getMaxX(), getMinY(), getMaxY());
printf(" vertex count=%d distance=%f\n", getVertexCount(), getDistance());
for (int i = 0; i < getVertexCount(); i++) {
glm::vec2 point = getVertex(i);
printf(" vertex[%d] = %f, %f \n", i, point.x, point.y);
}
}

View file

@ -0,0 +1,78 @@
//
// VoxelProjectedPolygon.h - The projected shadow (on the 2D view plane) for a voxel
// hifi
//
// Added by Brad Hefta-Gaub on 06/11/13.
//
#ifndef _VOXEL_PROJECTED_SHADOW_
#define _VOXEL_PROJECTED_SHADOW_
#include <glm/glm.hpp>
const int MAX_SHADOW_VERTEX_COUNT = 6;
typedef glm::vec2 ShadowVertices[MAX_SHADOW_VERTEX_COUNT];
class BoundingBox {
public:
BoundingBox(glm::vec2 corner, glm::vec2 size) : corner(corner), size(size) {};
glm::vec2 corner;
glm::vec2 size;
bool contains(const BoundingBox& box) const;
void printDebugDetails(const char* label=NULL) const;
};
class VoxelProjectedPolygon {
public:
VoxelProjectedPolygon(int vertexCount = 0) :
_vertexCount(vertexCount),
_maxX(-FLT_MAX), _maxY(-FLT_MAX), _minX(FLT_MAX), _minY(FLT_MAX),
_distance(0)
{ };
~VoxelProjectedPolygon() { };
const ShadowVertices& getVerices() const { return _vertices; };
const glm::vec2& getVertex(int i) const { return _vertices[i]; };
void setVertex(int vertex, const glm::vec2& point);
int getVertexCount() const { return _vertexCount; };
void setVertexCount(int vertexCount) { _vertexCount = vertexCount; };
float getDistance() const { return _distance; }
void setDistance(float distance) { _distance = distance; }
bool getAnyInView() const { return _anyInView; };
void setAnyInView(bool anyInView) { _anyInView = anyInView; };
bool getAllInView() const { return _allInView; };
void setAllInView(bool allInView) { _allInView = allInView; };
bool occludes(const VoxelProjectedPolygon& occludee) const;
bool pointInside(const glm::vec2& point) const;
float getMaxX() const { return _maxX; }
float getMaxY() const { return _maxY; }
float getMinX() const { return _minX; }
float getMinY() const { return _minY; }
BoundingBox getBoundingBox() const {
return BoundingBox(glm::vec2(_minX,_minY), glm::vec2(_maxX - _minX, _maxY - _minY));
};
void printDebugDetails() const;
private:
int _vertexCount;
ShadowVertices _vertices;
float _maxX;
float _maxY;
float _minX;
float _minY;
float _distance;
bool _anyInView; // if any points are in view
bool _allInView; // if all points are in view
};
#endif // _VOXEL_PROJECTED_SHADOW_

View file

@ -22,6 +22,7 @@
#include "ViewFrustum.h"
#include <fstream> // to load voxels from file
#include "VoxelConstants.h"
#include "CoverageMap.h"
#include <glm/gtc/noise.hpp>
@ -68,7 +69,51 @@ void VoxelTree::recurseNodeWithOperation(VoxelNode* node,RecurseVoxelTreeOperati
}
}
VoxelNode * VoxelTree::nodeForOctalCode(VoxelNode* ancestorNode, unsigned char* needleCode, VoxelNode** parentOfFoundNode) const {
// Recurses voxel tree calling the RecurseVoxelTreeOperation function for each node.
// stops recursion if operation function returns false.
void VoxelTree::recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation,
const glm::vec3& point, void* extraData) {
recurseNodeWithOperationDistanceSorted(rootNode, operation, point, extraData);
}
// Recurses voxel node with an operation function
void VoxelTree::recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation,
const glm::vec3& point, void* extraData) {
if (operation(node, extraData)) {
// determine the distance sorted order of our children
VoxelNode* sortedChildren[NUMBER_OF_CHILDREN];
float distancesToChildren[NUMBER_OF_CHILDREN];
int indexOfChildren[NUMBER_OF_CHILDREN]; // not really needed
int currentCount = 0;
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
VoxelNode* childNode = node->getChildAtIndex(i);
if (childNode) {
// chance to optimize, doesn't need to be actual distance!! Could be distance squared
float distanceSquared = childNode->distanceSquareToPoint(point);
//printLog("recurseNodeWithOperationDistanceSorted() CHECKING child[%d] point=%f,%f center=%f,%f distance=%f...\n", i, point.x, point.y, center.x, center.y, distance);
//childNode->printDebugDetails("");
currentCount = insertIntoSortedArrays((void*)childNode, distanceSquared, i,
(void**)&sortedChildren, (float*)&distancesToChildren,
(int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN);
}
}
for (int i = 0; i < currentCount; i++) {
VoxelNode* childNode = sortedChildren[i];
if (childNode) {
//printLog("recurseNodeWithOperationDistanceSorted() PROCESSING child[%d] distance=%f...\n", i, distancesToChildren[i]);
//childNode->printDebugDetails("");
recurseNodeWithOperationDistanceSorted(childNode, operation, point, extraData);
}
}
}
}
VoxelNode* VoxelTree::nodeForOctalCode(VoxelNode* ancestorNode,
unsigned char* needleCode, VoxelNode** parentOfFoundNode) const {
// find the appropriate branch index based on this ancestorNode
if (*needleCode > 0) {
int branchForNeedle = branchIndexWithDescendant(ancestorNode->getOctalCode(), needleCode);
@ -1012,7 +1057,7 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer,
*outputBuffer = 0; // root
}
} else {
codeLength = bytesRequiredForCodeLength(*node->getOctalCode());
codeLength = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(node->getOctalCode()));
memcpy(outputBuffer, node->getOctalCode(), codeLength);
}
@ -1072,6 +1117,39 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
if (!node->isInView(*params.viewFrustum)) {
return bytesAtThisLevel;
}
// If the user also asked for occlusion culling, check if this node is occluded, but only if it's not a leaf.
// leaf occlusion is handled down below when we check child nodes
if (params.wantOcclusionCulling && !node->isLeaf()) {
//node->printDebugDetails("upper section, params.wantOcclusionCulling... node=");
AABox voxelBox = node->getAABox();
voxelBox.scale(TREE_SCALE);
VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(params.viewFrustum->getProjectedShadow(voxelBox));
// In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion
// culling and proceed as normal
if (voxelShadow->getAllInView()) {
//node->printDebugDetails("upper section, voxelShadow->getAllInView() node=");
CoverageMap::StorageResult result = params.map->checkMap(voxelShadow, false);
delete voxelShadow; // cleanup
if (result == CoverageMap::OCCLUDED) {
//node->printDebugDetails("upper section, non-Leaf is occluded!! node=");
//args->nonLeavesOccluded++;
//args->subtreeVoxelsSkipped += (subArgs.voxelsTouched - 1);
//args->totalVoxels += (subArgs.voxelsTouched - 1);
return bytesAtThisLevel;
}
} else {
//node->printDebugDetails("upper section, shadow Not in view node=");
// If this shadow wasn't "all in view" then we ignored it for occlusion culling, but
// we do need to clean up memory and proceed as normal...
delete voxelShadow;
}
}
}
bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more!
@ -1080,37 +1158,68 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
// is 1 byte for child colors + 3*NUMBER_OF_CHILDREN bytes for the actual colors + 1 byte for child trees. There could be sub trees
// below this point, which might take many more bytes, but that's ok, because we can always mark our subtrees as
// not existing and stop the packet at this point, then start up with a new packet for the remaining sub trees.
const int CHILD_COLOR_MASK_BYTES = 1;
unsigned char childrenExistInTreeBits = 0;
unsigned char childrenExistInPacketBits = 0;
unsigned char childrenColoredBits = 0;
const int CHILD_COLOR_MASK_BYTES = sizeof(childrenColoredBits);
const int BYTES_PER_COLOR = 3;
const int CHILD_TREE_EXISTS_BYTES = 1;
const int CHILD_TREE_EXISTS_BYTES = sizeof(childrenExistInTreeBits) + sizeof(childrenExistInPacketBits);
const int MAX_LEVEL_BYTES = CHILD_COLOR_MASK_BYTES + NUMBER_OF_CHILDREN * BYTES_PER_COLOR + CHILD_TREE_EXISTS_BYTES;
// Make our local buffer large enough to handle writing at this level in case we need to.
unsigned char thisLevelBuffer[MAX_LEVEL_BYTES];
unsigned char* writeToThisLevelBuffer = &thisLevelBuffer[0];
unsigned char childrenExistInTreeBits = 0;
unsigned char childrenExistInPacketBits = 0;
unsigned char childrenColoredBits = 0;
int inViewCount = 0;
int inViewNotLeafCount = 0;
int inViewWithColorCount = 0;
// for each child node, check to see if they exist, are colored, and in view, and if so
// add them to our distance ordered array of children
VoxelNode* sortedChildren[NUMBER_OF_CHILDREN];
float distancesToChildren[NUMBER_OF_CHILDREN];
int indexOfChildren[NUMBER_OF_CHILDREN]; // not really needed
int currentCount = 0;
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
VoxelNode* childNode = node->getChildAtIndex(i);
// if the caller wants to include childExistsBits, then include them even if not in view
if (params.includeExistsBits && childNode) {
childrenExistInTreeBits += (1 << (7 - i));
}
if (params.wantOcclusionCulling) {
if (childNode) {
// chance to optimize, doesn't need to be actual distance!! Could be distance squared
//float distanceSquared = childNode->distanceSquareToPoint(point);
//printLog("recurseNodeWithOperationDistanceSorted() CHECKING child[%d] point=%f,%f center=%f,%f distance=%f...\n", i, point.x, point.y, center.x, center.y, distance);
//childNode->printDebugDetails("");
float distance = params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0;
currentCount = insertIntoSortedArrays((void*)childNode, distance, i,
(void**)&sortedChildren, (float*)&distancesToChildren,
(int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN);
}
} else {
sortedChildren[i] = childNode;
indexOfChildren[i] = i;
distancesToChildren[i] = 0.0f;
currentCount++;
}
}
// for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so
// add them to our distance ordered array of children
for (int i = 0; i < currentCount; i++) {
VoxelNode* childNode = sortedChildren[i];
int originalIndex = indexOfChildren[i];
bool childIsInView = (childNode && (!params.viewFrustum || childNode->isInView(*params.viewFrustum)));
if (childIsInView) {
// Before we determine consider this further, let's see if it's in our LOD scope...
float distance = params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0;
float distance = distancesToChildren[i]; // params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0;
float boundaryDistance = params.viewFrustum ? boundaryDistanceForRenderLevel(*childNode->getOctalCode() + 1) : 1;
if (distance < boundaryDistance) {
@ -1120,16 +1229,48 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
// we don't care about recursing deeper on them, and we don't consider their
// subtree to exist
if (!(childNode && childNode->isLeaf())) {
childrenExistInPacketBits += (1 << (7 - i));
childrenExistInPacketBits += (1 << (7 - originalIndex));
inViewNotLeafCount++;
}
bool childIsOccluded = false; // assume it's not occluded
// If the user also asked for occlusion culling, check if this node is occluded
if (params.wantOcclusionCulling && childNode->isLeaf()) {
// Don't check occlusion here, just add them to our distance ordered array...
AABox voxelBox = childNode->getAABox();
voxelBox.scale(TREE_SCALE);
VoxelProjectedPolygon* voxelShadow = new VoxelProjectedPolygon(params.viewFrustum->getProjectedShadow(voxelBox));
// In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion
// culling and proceed as normal
if (voxelShadow->getAllInView()) {
CoverageMap::StorageResult result = params.map->checkMap(voxelShadow, true);
// In all cases where the shadow wasn't stored, we need to free our own memory.
// In the case where it is stored, the CoverageMap will free memory for us later.
if (result != CoverageMap::STORED) {
delete voxelShadow;
}
// If while attempting to add this voxel's shadow, we determined it was occluded, then
// we don't need to process it further and we can exit early.
if (result == CoverageMap::OCCLUDED) {
childIsOccluded = true;
}
} else {
delete voxelShadow;
}
} // wants occlusion culling & isLeaf()
bool childWasInView = (childNode && params.deltaViewFrustum &&
(params.lastViewFrustum && ViewFrustum::INSIDE == childNode->inFrustum(*params.lastViewFrustum)));
// track children with actual color, only if the child wasn't previously in view!
if (childNode && childNode->isColored() && !childWasInView) {
childrenColoredBits += (1 << (7 - i));
if (childNode && childNode->isColored() && !childWasInView && !childIsOccluded) {
childrenColoredBits += (1 << (7 - originalIndex));
inViewWithColorCount++;
}
}
@ -1189,15 +1330,35 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
//
// we know the last thing we wrote to the outputBuffer was our childrenExistInPacketBits. Let's remember where that was!
unsigned char* childExistsPlaceHolder = outputBuffer-sizeof(childrenExistInPacketBits);
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
if (oneAtBit(childrenExistInPacketBits, i)) {
VoxelNode* childNode = node->getChildAtIndex(i);
// we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the
// final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes,
// and then later reshuffle these sections of our output buffer back into normal order. This allows us to make
// a single recursive pass in distance sorted order, but retain standard order in our encoded packet
int recursiveSliceSizes[NUMBER_OF_CHILDREN];
unsigned char* recursiveSliceStarts[NUMBER_OF_CHILDREN];
unsigned char* firstRecursiveSlice = outputBuffer;
int allSlicesSize = 0;
// for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so
// add them to our distance ordered array of children
for (int indexByDistance = 0; indexByDistance < currentCount; indexByDistance++) {
VoxelNode* childNode = sortedChildren[indexByDistance];
int originalIndex = indexOfChildren[indexByDistance];
if (oneAtBit(childrenExistInPacketBits, originalIndex)) {
int thisLevel = currentEncodeLevel;
// remember this for reshuffling
recursiveSliceStarts[originalIndex] = outputBuffer;
int childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, outputBuffer, availableBytes, bag,
params, thisLevel);
// remember this for reshuffling
recursiveSliceSizes[originalIndex] = childTreeBytesOut;
allSlicesSize += childTreeBytesOut;
// if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space,
// basically, the children below don't contain any info.
@ -1227,15 +1388,40 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
// then we want to remove their bit from the childExistsPlaceHolder bitmask
if (childTreeBytesOut == 0) {
// remove this child's bit...
childrenExistInPacketBits -= (1 << (7 - i));
childrenExistInPacketBits -= (1 << (7 - originalIndex));
// repair the child exists mask
*childExistsPlaceHolder = childrenExistInPacketBits;
// Note: no need to move the pointer, cause we already stored this
} // end if (childTreeBytesOut == 0)
} // end if (oneAtBit(childrenExistInPacketBits, i))
} // end if (oneAtBit(childrenExistInPacketBits, originalIndex))
} // end for
} // end keepDiggingDeeper
// reshuffle here...
if (params.wantOcclusionCulling) {
unsigned char tempReshuffleBuffer[MAX_VOXEL_PACKET_SIZE];
unsigned char* tempBufferTo = &tempReshuffleBuffer[0]; // this is our temporary destination
// iterate through our childrenExistInPacketBits, these will be the sections of the packet that we copied subTree
// details into. Unfortunately, they're in distance sorted order, not original index order. we need to put them
// back into original distance order
for (int originalIndex = 0; originalIndex < NUMBER_OF_CHILDREN; originalIndex++) {
if (oneAtBit(childrenExistInPacketBits, originalIndex)) {
int thisSliceSize = recursiveSliceSizes[originalIndex];
unsigned char* thisSliceStarts = recursiveSliceStarts[originalIndex];
memcpy(tempBufferTo, thisSliceStarts, thisSliceSize);
tempBufferTo += thisSliceSize;
}
}
// now that all slices are back in the correct order, copy them to the correct output buffer
memcpy(firstRecursiveSlice, &tempReshuffleBuffer[0], allSlicesSize);
}
} // end keepDiggingDeeper
return bytesAtThisLevel;
}

View file

@ -13,20 +13,25 @@
#include "ViewFrustum.h"
#include "VoxelNode.h"
#include "VoxelNodeBag.h"
#include "CoverageMap.h"
// Callback function, for recuseTreeWithOperation
typedef bool (*RecurseVoxelTreeOperation)(VoxelNode* node, void* extraData);
typedef enum {GRADIENT, RANDOM, NATURAL} creationMode;
#define NO_EXISTS_BITS false
#define WANT_EXISTS_BITS true
#define NO_COLOR false
#define WANT_COLOR true
#define IGNORE_VIEW_FRUSTUM NULL
#define JUST_STAGE_DELETION true
#define ACTUALLY_DELETE false
#define COLLAPSE_EMPTY_TREE true
#define DONT_COLLAPSE false
#define NO_EXISTS_BITS false
#define WANT_EXISTS_BITS true
#define NO_COLOR false
#define WANT_COLOR true
#define IGNORE_VIEW_FRUSTUM NULL
#define JUST_STAGE_DELETION true
#define ACTUALLY_DELETE false
#define COLLAPSE_EMPTY_TREE true
#define DONT_COLLAPSE false
#define NO_OCCLUSION_CULLING false
#define WANT_OCCLUSION_CULLING true
#define IGNORE_COVERAGE_MAP NULL
#define DONT_CHOP 0
class EncodeBitstreamParams {
public:
@ -37,6 +42,8 @@ public:
int chopLevels;
bool deltaViewFrustum;
const ViewFrustum* lastViewFrustum;
bool wantOcclusionCulling;
CoverageMap* map;
EncodeBitstreamParams(
int maxEncodeLevel = INT_MAX,
@ -45,7 +52,9 @@ public:
bool includeExistsBits = WANT_EXISTS_BITS,
int chopLevels = 0,
bool deltaViewFrustum = false,
const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM) :
const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM,
bool wantOcclusionCulling= NO_OCCLUSION_CULLING,
CoverageMap* map = IGNORE_COVERAGE_MAP) :
maxEncodeLevel (maxEncodeLevel),
viewFrustum (viewFrustum),
@ -53,7 +62,9 @@ public:
includeExistsBits (includeExistsBits),
chopLevels (chopLevels),
deltaViewFrustum (deltaViewFrustum),
lastViewFrustum (lastViewFrustum)
lastViewFrustum (lastViewFrustum),
wantOcclusionCulling(wantOcclusionCulling),
map (map)
{}
};
@ -96,6 +107,8 @@ public:
creationMode mode, bool destructive = false, bool debug = false);
void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL);
void recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation,
const glm::vec3& point, void* extraData=NULL);
int encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag,
EncodeBitstreamParams& params) const;
@ -127,6 +140,10 @@ public:
void copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode);
bool getShouldReaverage() const { return _shouldReaverage; }
void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData);
void recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation,
const glm::vec3& point, void* extraData);
private:
void deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraData);
@ -141,7 +158,6 @@ private:
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);
int readNodeData(VoxelNode *destinationNode, unsigned char* nodeData, int bufferSizeBytes,

View file

@ -14,6 +14,7 @@
#include <AvatarData.h>
#include "VoxelNodeBag.h"
#include "VoxelConstants.h"
#include "CoverageMap.h"
class VoxelAgentData : public AvatarData {
public:
@ -36,6 +37,7 @@ public:
void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; }
VoxelNodeBag nodeBag;
CoverageMap map;
ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; };
ViewFrustum& getLastKnownViewFrustum() { return _lastKnownViewFrustum; };

View file

@ -312,9 +312,13 @@ void deepestLevelVoxelDistributor(AgentList* agentList,
while (packetsSentThisInterval < PACKETS_PER_CLIENT_PER_INTERVAL - (shouldSendEnvironments ? 1 : 0)) {
if (!agentData->nodeBag.isEmpty()) {
VoxelNode* subTree = agentData->nodeBag.extract();
bool wantOcclusionCulling = agentData->getWantOcclusionCulling();
CoverageMap* coverageMap = wantOcclusionCulling ? &agentData->map : IGNORE_COVERAGE_MAP;
EncodeBitstreamParams params(INT_MAX, &agentData->getCurrentViewFrustum(), agentData->getWantColor(),
WANT_EXISTS_BITS, wantDelta, lastViewFrustum);
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
wantOcclusionCulling, coverageMap);
bytesWritten = serverTree.encodeTreeBitstream(subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1,
agentData->nodeBag, params);
@ -375,6 +379,7 @@ void deepestLevelVoxelDistributor(AgentList* agentList,
if (agentData->nodeBag.isEmpty()) {
agentData->updateLastKnownViewFrustum();
agentData->setViewSent(true);
agentData->map.erase();
}