mirror of
https://github.com/overte-org/overte.git
synced 2025-04-25 22:16:39 +02:00
1596 lines
53 KiB
C++
1596 lines
53 KiB
C++
//
|
|
// Interface
|
|
//
|
|
// Allows you to connect to and see/hear the shared 3D space.
|
|
// Optionally uses serialUSB connection to get gyro data for head movement.
|
|
// Optionally gets UDP stream from transmitter to animate controller/hand.
|
|
//
|
|
// Usage: The interface client first attempts to contact a domain server to
|
|
// discover the appropriate audio, voxel, and avatar servers to contact.
|
|
// Right now, the default domain server is "highfidelity.below92.com"
|
|
// You can change the domain server to use your own by editing the
|
|
// DOMAIN_HOSTNAME or DOMAIN_IP strings in the file AgentList.cpp
|
|
//
|
|
//
|
|
// Welcome Aboard!
|
|
//
|
|
//
|
|
// Keyboard Commands:
|
|
//
|
|
// / = toggle stats display
|
|
// spacebar = reset gyros/head position
|
|
// h = render Head facing yourself (mirror)
|
|
// l = show incoming gyro levels
|
|
//
|
|
|
|
#include "InterfaceConfig.h"
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#include <sstream>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#ifdef _WIN32
|
|
#include "Syssocket.h"
|
|
#include "Systime.h"
|
|
#else
|
|
#include <sys/time.h>
|
|
#include <arpa/inet.h>
|
|
#include <ifaddrs.h>
|
|
#endif
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <glm/glm.hpp>
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
|
|
#include "Field.h"
|
|
#include "world.h"
|
|
#include "Util.h"
|
|
#ifndef _WIN32
|
|
#include "Audio.h"
|
|
#endif
|
|
|
|
#include "FieldOfView.h"
|
|
#include "Stars.h"
|
|
|
|
#include "MenuRow.h"
|
|
#include "MenuColumn.h"
|
|
#include "Menu.h"
|
|
#include "Camera.h"
|
|
#include "Head.h"
|
|
#include "Particle.h"
|
|
#include "Texture.h"
|
|
#include "Cloud.h"
|
|
#include <AgentList.h>
|
|
#include <AgentTypes.h>
|
|
#include "VoxelSystem.h"
|
|
#include "Oscilloscope.h"
|
|
#include "UDPSocket.h"
|
|
#include "SerialInterface.h"
|
|
#include <PerfStat.h>
|
|
#include <SharedUtil.h>
|
|
#include <PacketHeaders.h>
|
|
|
|
#include "ViewFrustum.h"
|
|
|
|
using namespace std;
|
|
|
|
void reshape(int width, int height); // will be defined below
|
|
|
|
|
|
pthread_t networkReceiveThread;
|
|
bool stopNetworkReceiveThread = false;
|
|
|
|
int packetCount = 0;
|
|
int packetsPerSecond = 0;
|
|
int bytesPerSecond = 0;
|
|
int bytesCount = 0;
|
|
|
|
int headMirror = 1; // Whether to mirror own head when viewing it
|
|
|
|
int WIDTH = 1200; // Window size
|
|
int HEIGHT = 800;
|
|
int fullscreen = 0;
|
|
|
|
bool wantColorRandomizer = true; // for addSphere and load file
|
|
|
|
Oscilloscope audioScope(256,200,true);
|
|
|
|
ViewFrustum viewFrustum; // current state of view frustum, perspective, orientation, etc.
|
|
|
|
Head myAvatar; // The rendered avatar of oneself
|
|
Camera myCamera; // My view onto the world (sometimes on myself :)
|
|
Camera viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode
|
|
|
|
// Starfield information
|
|
char starFile[] = "https://s3-us-west-1.amazonaws.com/highfidelity/stars.txt";
|
|
FieldOfView fov;
|
|
Stars stars;
|
|
#ifdef STARFIELD_KEYS
|
|
int starsTiles = 20;
|
|
double starsLod = 1.0;
|
|
#endif
|
|
|
|
bool showingVoxels = true;
|
|
|
|
glm::vec3 box(WORLD_SIZE,WORLD_SIZE,WORLD_SIZE);
|
|
|
|
ParticleSystem balls(0,
|
|
box,
|
|
false, // Wrap?
|
|
0.02f, // Noise
|
|
0.3f, // Size scale
|
|
0.0 // Gravity
|
|
);
|
|
|
|
Cloud cloud(0, // Particles
|
|
box, // Bounding Box
|
|
false // Wrap
|
|
);
|
|
|
|
VoxelSystem voxels;
|
|
Field field;
|
|
|
|
#ifndef _WIN32
|
|
Audio audio(&audioScope, &myAvatar);
|
|
#endif
|
|
|
|
#define IDLE_SIMULATE_MSECS 8 // How often should call simulate and other stuff
|
|
// in the idle loop?
|
|
|
|
float yaw = 0.f; // The yaw, pitch for the avatar head
|
|
float pitch = 0.f;
|
|
float startYaw = 122.f;
|
|
float renderPitch = 0.f;
|
|
float renderYawRate = 0.f;
|
|
float renderPitchRate = 0.f;
|
|
|
|
// Where one's own agent begins in the world (needs to become a dynamic thing passed to the program)
|
|
glm::vec3 start_location(6.1f, 0, 1.4f);
|
|
|
|
int statsOn = 0; // Whether to show onscreen text overlay with stats
|
|
bool starsOn = false; // Whether to display the stars
|
|
bool paintOn = false; // Whether to paint voxels as you fly around
|
|
VoxelDetail paintingVoxel; // The voxel we're painting if we're painting
|
|
unsigned char dominantColor = 0; // The dominant color of the voxel we're painting
|
|
bool perfStatsOn = false; // Do we want to display perfStats?
|
|
|
|
int noiseOn = 0; // Whether to add random noise
|
|
float noise = 1.0; // Overall magnitude scaling for random noise levels
|
|
|
|
int displayLevels = 0;
|
|
int displayHead = 0;
|
|
int displayField = 0;
|
|
|
|
int displayHeadMouse = 1; // Display sample mouse pointer controlled by head movement
|
|
int headMouseX, headMouseY;
|
|
|
|
int mouseX, mouseY; // Where is the mouse
|
|
|
|
// Mouse location at start of last down click
|
|
int mouseStartX;// = WIDTH / 2;
|
|
int mouseStartY;// = HEIGHT / 2;
|
|
int mousePressed = 0; // true if mouse has been pressed (clear when finished)
|
|
|
|
Menu menu; // main menu
|
|
int menuOn = 1; // Whether to show onscreen menu
|
|
|
|
//
|
|
// Serial USB Variables
|
|
//
|
|
|
|
SerialInterface serialPort;
|
|
|
|
glm::vec3 gravity;
|
|
|
|
// Frame Rate Measurement
|
|
|
|
int frameCount = 0;
|
|
float FPS = 120.f;
|
|
timeval timerStart, timerEnd;
|
|
timeval lastTimeIdle;
|
|
double elapsedTime;
|
|
timeval applicationStartupTime;
|
|
bool justStarted = true;
|
|
|
|
|
|
// Every second, check the frame rates and other stuff
|
|
void Timer(int extra)
|
|
{
|
|
gettimeofday(&timerEnd, NULL);
|
|
FPS = (float)frameCount / ((float)diffclock(&timerStart, &timerEnd) / 1000.f);
|
|
packetsPerSecond = (float)packetCount / ((float)diffclock(&timerStart, &timerEnd) / 1000.f);
|
|
bytesPerSecond = (float)bytesCount / ((float)diffclock(&timerStart, &timerEnd) / 1000.f);
|
|
frameCount = 0;
|
|
packetCount = 0;
|
|
bytesCount = 0;
|
|
|
|
glutTimerFunc(1000,Timer,0);
|
|
gettimeofday(&timerStart, NULL);
|
|
|
|
// if we haven't detected gyros, check for them now
|
|
if (!serialPort.active) {
|
|
serialPort.pair();
|
|
}
|
|
}
|
|
|
|
void displayStats(void)
|
|
{
|
|
int statsVerticalOffset = 50;
|
|
if (::menuOn == 0) {
|
|
statsVerticalOffset = 8;
|
|
}
|
|
// bitmap chars are about 10 pels high
|
|
char legend[] = "/ - toggle this display, Q - exit, H - show head, M - show hand, T - test audio";
|
|
drawtext(10, statsVerticalOffset + 15, 0.10f, 0, 1.0, 0, legend);
|
|
|
|
char legend2[] = "* - toggle stars, & - toggle paint mode, '-' - send erase all, '%' - send add scene";
|
|
drawtext(10, statsVerticalOffset + 32, 0.10f, 0, 1.0, 0, legend2);
|
|
|
|
glm::vec3 avatarPos = myAvatar.getPos();
|
|
|
|
char stats[200];
|
|
sprintf(stats, "FPS = %3.0f Pkts/s = %d Bytes/s = %d Head(x,y,z)= %4.2f, %4.2f, %4.2f ",
|
|
FPS, packetsPerSecond, bytesPerSecond, avatarPos.x,avatarPos.y,avatarPos.z);
|
|
drawtext(10, statsVerticalOffset + 49, 0.10f, 0, 1.0, 0, stats);
|
|
if (serialPort.active) {
|
|
sprintf(stats, "ADC samples = %d, LED = %d",
|
|
serialPort.getNumSamples(), serialPort.getLED());
|
|
drawtext(300, statsVerticalOffset + 30, 0.10f, 0, 1.0, 0, stats);
|
|
}
|
|
|
|
std::stringstream voxelStats;
|
|
voxelStats << "Voxels Rendered: " << voxels.getVoxelsRendered();
|
|
drawtext(10, statsVerticalOffset + 70, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
|
|
|
voxelStats.str("");
|
|
voxelStats << "Voxels Created: " << voxels.getVoxelsCreated() << " (" << voxels.getVoxelsCreatedRunningAverage()
|
|
<< "/sec in last "<< COUNTETSTATS_TIME_FRAME << " seconds) ";
|
|
drawtext(10, statsVerticalOffset + 250, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
|
|
|
voxelStats.str("");
|
|
voxelStats << "Voxels Colored: " << voxels.getVoxelsColored() << " (" << voxels.getVoxelsColoredRunningAverage()
|
|
<< "/sec in last "<< COUNTETSTATS_TIME_FRAME << " seconds) ";
|
|
drawtext(10, statsVerticalOffset + 270, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
|
|
|
voxelStats.str("");
|
|
voxelStats << "Voxels Bytes Read: " << voxels.getVoxelsBytesRead()
|
|
<< " (" << voxels.getVoxelsBytesReadRunningAverage() << "/sec in last "<< COUNTETSTATS_TIME_FRAME << " seconds) ";
|
|
drawtext(10, statsVerticalOffset + 290,0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
|
|
|
voxelStats.str("");
|
|
long int voxelsBytesPerColored = voxels.getVoxelsColored() ? voxels.getVoxelsBytesRead()/voxels.getVoxelsColored() : 0;
|
|
long int voxelsBytesPerColoredAvg = voxels.getVoxelsColoredRunningAverage() ?
|
|
voxels.getVoxelsBytesReadRunningAverage()/voxels.getVoxelsColoredRunningAverage() : 0;
|
|
|
|
voxelStats << "Voxels Bytes per Colored: " << voxelsBytesPerColored
|
|
<< " (" << voxelsBytesPerColoredAvg << "/sec in last "<< COUNTETSTATS_TIME_FRAME << " seconds) ";
|
|
drawtext(10, statsVerticalOffset + 310, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
|
|
|
|
|
if (::perfStatsOn) {
|
|
// Get the PerfStats group details. We need to allocate and array of char* long enough to hold 1+groups
|
|
char** perfStatLinesArray = new char*[PerfStat::getGroupCount()+1];
|
|
int lines = PerfStat::DumpStats(perfStatLinesArray);
|
|
int atZ = 150; // arbitrary place on screen that looks good
|
|
for (int line=0; line < lines; line++) {
|
|
drawtext(10, statsVerticalOffset + atZ, 0.10f, 0, 1.0, 0, perfStatLinesArray[line]);
|
|
delete perfStatLinesArray[line]; // we're responsible for cleanup
|
|
perfStatLinesArray[line]=NULL;
|
|
atZ+=20; // height of a line
|
|
}
|
|
delete []perfStatLinesArray; // we're responsible for cleanup
|
|
}
|
|
}
|
|
|
|
void initDisplay(void)
|
|
{
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glShadeModel (GL_SMOOTH);
|
|
glEnable(GL_LIGHTING);
|
|
glEnable(GL_LIGHT0);
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
if (fullscreen) glutFullScreen();
|
|
}
|
|
|
|
void init(void)
|
|
{
|
|
voxels.init();
|
|
voxels.setViewerHead(&myAvatar);
|
|
myAvatar.setRenderYaw(startYaw);
|
|
|
|
headMouseX = WIDTH/2;
|
|
headMouseY = HEIGHT/2;
|
|
|
|
stars.readInput(starFile, 0);
|
|
|
|
// Initialize Field values
|
|
field = Field();
|
|
|
|
if (noiseOn) {
|
|
myAvatar.setNoise(noise);
|
|
}
|
|
myAvatar.setPos(start_location );
|
|
myCamera.setPosition( start_location );
|
|
|
|
|
|
#ifdef MARKER_CAPTURE
|
|
if(marker_capture_enabled){
|
|
marker_capturer.position_updated(&position_updated);
|
|
marker_capturer.frame_updated(&marker_frame_available);
|
|
if(!marker_capturer.init_capture()){
|
|
printf("Camera-based marker capture initialized.\n");
|
|
}else{
|
|
printf("Error initializing camera-based marker capture.\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
gettimeofday(&timerStart, NULL);
|
|
gettimeofday(&lastTimeIdle, NULL);
|
|
}
|
|
|
|
void terminate () {
|
|
// Close serial port
|
|
//close(serial_fd);
|
|
|
|
#ifndef _WIN32
|
|
audio.terminate();
|
|
#endif
|
|
stopNetworkReceiveThread = true;
|
|
pthread_join(networkReceiveThread, NULL);
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
void reset_sensors()
|
|
{
|
|
//
|
|
// Reset serial I/O sensors
|
|
//
|
|
myAvatar.setRenderYaw(startYaw);
|
|
|
|
yaw = renderYawRate = 0;
|
|
pitch = renderPitch = renderPitchRate = 0;
|
|
myAvatar.setPos(start_location);
|
|
headMouseX = WIDTH/2;
|
|
headMouseY = HEIGHT/2;
|
|
|
|
myAvatar.reset();
|
|
|
|
if (serialPort.active) {
|
|
serialPort.resetTrailingAverages();
|
|
}
|
|
}
|
|
|
|
void simulateHand(float deltaTime) {
|
|
// If mouse is being dragged, send current force to the hand controller
|
|
if (mousePressed == 1)
|
|
{
|
|
// NOTE--PER: Need to re-implement when ready for new avatar hand movements
|
|
|
|
const float MOUSE_HAND_FORCE = 1.5;
|
|
float dx = mouseX - mouseStartX;
|
|
float dy = mouseY - mouseStartY;
|
|
glm::vec3 vel(dx*MOUSE_HAND_FORCE, -dy*MOUSE_HAND_FORCE*(WIDTH/HEIGHT), 0);
|
|
//myAvatar.hand->addVelocity(vel*deltaTime);
|
|
}
|
|
}
|
|
|
|
void simulateHead(float frametime)
|
|
// Using serial data, update avatar/render position and angles
|
|
{
|
|
// float measured_pitch_rate = serialPort.getRelativeValue(PITCH_RATE);
|
|
// float measured_yaw_rate = serialPort.getRelativeValue(YAW_RATE);
|
|
|
|
float measured_pitch_rate = 0;
|
|
float measured_yaw_rate = 0;
|
|
|
|
//float measured_lateral_accel = serialPort.getRelativeValue(ACCEL_X);
|
|
//float measured_fwd_accel = serialPort.getRelativeValue(ACCEL_Z);
|
|
|
|
myAvatar.UpdatePos(frametime, &serialPort, headMirror, &gravity);
|
|
|
|
// Update head_mouse model
|
|
const float MIN_MOUSE_RATE = 30.0;
|
|
const float MOUSE_SENSITIVITY = 0.1f;
|
|
if (powf(measured_yaw_rate*measured_yaw_rate +
|
|
measured_pitch_rate*measured_pitch_rate, 0.5) > MIN_MOUSE_RATE)
|
|
{
|
|
headMouseX += measured_yaw_rate*MOUSE_SENSITIVITY;
|
|
headMouseY += measured_pitch_rate*MOUSE_SENSITIVITY*(float)HEIGHT/(float)WIDTH;
|
|
}
|
|
headMouseX = max(headMouseX, 0);
|
|
headMouseX = min(headMouseX, WIDTH);
|
|
headMouseY = max(headMouseY, 0);
|
|
headMouseY = min(headMouseY, HEIGHT);
|
|
|
|
// Update render direction (pitch/yaw) based on measured gyro rates
|
|
const int MIN_YAW_RATE = 100;
|
|
const int MIN_PITCH_RATE = 100;
|
|
|
|
const float YAW_SENSITIVITY = 0.02;
|
|
const float PITCH_SENSITIVITY = 0.05;
|
|
|
|
// Update render pitch and yaw rates based on keyPositions
|
|
const float KEY_YAW_SENSITIVITY = 2.0;
|
|
if (myAvatar.getDriveKeys(ROT_LEFT)) renderYawRate -= KEY_YAW_SENSITIVITY*frametime;
|
|
if (myAvatar.getDriveKeys(ROT_RIGHT)) renderYawRate += KEY_YAW_SENSITIVITY*frametime;
|
|
|
|
if (fabs(measured_yaw_rate) > MIN_YAW_RATE)
|
|
{
|
|
if (measured_yaw_rate > 0)
|
|
renderYawRate += (measured_yaw_rate - MIN_YAW_RATE) * YAW_SENSITIVITY * frametime;
|
|
else
|
|
renderYawRate += (measured_yaw_rate + MIN_YAW_RATE) * YAW_SENSITIVITY * frametime;
|
|
}
|
|
if (fabs(measured_pitch_rate) > MIN_PITCH_RATE)
|
|
{
|
|
if (measured_pitch_rate > 0)
|
|
renderPitchRate += (measured_pitch_rate - MIN_PITCH_RATE) * PITCH_SENSITIVITY * frametime;
|
|
else
|
|
renderPitchRate += (measured_pitch_rate + MIN_PITCH_RATE) * PITCH_SENSITIVITY * frametime;
|
|
}
|
|
|
|
renderPitch += renderPitchRate;
|
|
|
|
// Decay renderPitch toward zero because we never look constantly up/down
|
|
renderPitch *= (1.f - 2.0*frametime);
|
|
|
|
// Decay angular rates toward zero
|
|
renderPitchRate *= (1.f - 5.0*frametime);
|
|
renderYawRate *= (1.f - 7.0*frametime);
|
|
|
|
// Update own head data
|
|
myAvatar.setRenderYaw(myAvatar.getRenderYaw() + renderYawRate);
|
|
myAvatar.setRenderPitch(renderPitch);
|
|
|
|
// Get audio loudness data from audio input device
|
|
float loudness, averageLoudness;
|
|
#ifndef _WIN32
|
|
audio.getInputLoudness(&loudness, &averageLoudness);
|
|
myAvatar.setLoudness(loudness);
|
|
myAvatar.setAverageLoudness(averageLoudness);
|
|
#endif
|
|
|
|
// Send my stream of head/hand data to the avatar mixer and voxel server
|
|
char broadcastString[200];
|
|
int broadcastBytes = myAvatar.getBroadcastData(broadcastString);
|
|
const char broadcastReceivers[2] = {AGENT_TYPE_VOXEL, AGENT_TYPE_AVATAR_MIXER};
|
|
|
|
AgentList::getInstance()->broadcastToAgents(broadcastString, broadcastBytes, broadcastReceivers, 2);
|
|
|
|
// If I'm in paint mode, send a voxel out to VOXEL server agents.
|
|
if (::paintOn) {
|
|
|
|
glm::vec3 avatarPos = myAvatar.getPos();
|
|
|
|
// For some reason, we don't want to flip X and Z here.
|
|
::paintingVoxel.x = avatarPos.x/10.0;
|
|
::paintingVoxel.y = avatarPos.y/10.0;
|
|
::paintingVoxel.z = avatarPos.z/10.0;
|
|
|
|
unsigned char* bufferOut;
|
|
int sizeOut;
|
|
|
|
if (::paintingVoxel.x >= 0.0 && ::paintingVoxel.x <= 1.0 &&
|
|
::paintingVoxel.y >= 0.0 && ::paintingVoxel.y <= 1.0 &&
|
|
::paintingVoxel.z >= 0.0 && ::paintingVoxel.z <= 1.0) {
|
|
|
|
if (createVoxelEditMessage(PACKET_HEADER_SET_VOXEL, 0, 1, &::paintingVoxel, bufferOut, sizeOut)){
|
|
AgentList::getInstance()->broadcastToAgents((char*)bufferOut, sizeOut, &AGENT_TYPE_VOXEL, 1);
|
|
delete bufferOut;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////
|
|
// render_view_frustum()
|
|
//
|
|
// Description: this will render the view frustum bounds for EITHER the head
|
|
// or the "myCamera".
|
|
//
|
|
// Frustum rendering mode. For debug purposes, we allow drawing the frustum in a couple of different ways.
|
|
// We can draw it with each of these parts:
|
|
// * Origin Direction/Up/Right vectors - these will be drawn at the point of the camera
|
|
// * Near plane - this plane is drawn very close to the origin point.
|
|
// * Right/Left planes - these two planes are drawn between the near and far planes.
|
|
// * Far plane - the plane is drawn in the distance.
|
|
// Modes - the following modes, will draw the following parts.
|
|
// * All - draws all the parts listed above
|
|
// * Planes - draws the planes but not the origin vectors
|
|
// * Origin Vectors - draws the origin vectors ONLY
|
|
// * Near Plane - draws only the near plane
|
|
// * Far Plane - draws only the far plane
|
|
#define FRUSTUM_DRAW_MODE_ALL 0
|
|
#define FRUSTUM_DRAW_MODE_VECTORS 1
|
|
#define FRUSTUM_DRAW_MODE_PLANES 2
|
|
#define FRUSTUM_DRAW_MODE_NEAR_PLANE 3
|
|
#define FRUSTUM_DRAW_MODE_FAR_PLANE 4
|
|
#define FRUSTUM_DRAW_MODE_COUNT 5
|
|
|
|
// These global scoped variables are used by our render_view_frustum() function below, but are also available as globals
|
|
// so that the keyboard and menu can manipulate them.
|
|
|
|
int frustumDrawingMode = FRUSTUM_DRAW_MODE_ALL; // the mode we're drawing the frustum in, see notes above
|
|
|
|
bool frustumOn = false; // Whether or not to display the debug view frustum
|
|
bool cameraFrustum = true; // which frustum to look at
|
|
|
|
bool viewFrustumFromOffset=false; // Wether or not to offset the view of the frustum
|
|
float viewFrustumOffsetYaw = -90.0; // the following variables control yaw, pitch, roll and distance form regular
|
|
float viewFrustumOffsetPitch = 7.5; // camera to the offset camera
|
|
float viewFrustumOffsetRoll = 0.0;
|
|
float viewFrustumOffsetDistance = 0.0;
|
|
float viewFrustumOffsetUp = 0.0;
|
|
|
|
void render_view_frustum() {
|
|
|
|
// We will use these below, from either the camera or head vectors calculated above
|
|
glm::vec3 position;
|
|
glm::vec3 direction;
|
|
glm::vec3 up;
|
|
glm::vec3 right;
|
|
float fov, nearClip, farClip;
|
|
|
|
// Camera or Head?
|
|
if (::cameraFrustum) {
|
|
position = ::myCamera.getPosition();
|
|
direction = ::myCamera.getOrientation().getFront() * glm::vec3(1,1,-1);
|
|
up = ::myCamera.getOrientation().getUp() * glm::vec3(1,1,1);
|
|
right = ::myCamera.getOrientation().getRight() * glm::vec3(1,1,-1);
|
|
fov = ::myCamera.getFieldOfView();
|
|
nearClip = ::myCamera.getNearClip();
|
|
farClip = ::myCamera.getFarClip();
|
|
} else {
|
|
position = ::myAvatar.getHeadPosition();
|
|
direction = ::myAvatar.getHeadLookatDirection();
|
|
up = ::myAvatar.getHeadLookatDirectionUp();
|
|
right = ::myAvatar.getHeadLookatDirectionRight() * glm::vec3(-1,1,-1);
|
|
|
|
// NOTE: we use the same lens details if we draw from the head
|
|
fov = ::myCamera.getFieldOfView();
|
|
nearClip = ::myCamera.getNearClip();
|
|
farClip = ::myCamera.getFarClip();
|
|
}
|
|
|
|
/*
|
|
printf("position.x=%f, position.y=%f, position.z=%f\n", position.x, position.y, position.z);
|
|
printf("direction.x=%f, direction.y=%f, direction.z=%f\n", direction.x, direction.y, direction.z);
|
|
printf("up.x=%f, up.y=%f, up.z=%f\n", up.x, up.y, up.z);
|
|
printf("right.x=%f, right.y=%f, right.z=%f\n", right.x, right.y, right.z);
|
|
printf("fov=%f\n", fov);
|
|
printf("nearClip=%f\n", nearClip);
|
|
printf("farClip=%f\n", farClip);
|
|
*/
|
|
|
|
// Set the viewFrustum up with the correct position and orientation of the camera
|
|
viewFrustum.setPosition(position);
|
|
viewFrustum.setOrientation(direction,up,right);
|
|
|
|
// Also make sure it's got the correct lens details from the camera
|
|
viewFrustum.setFieldOfView(fov);
|
|
viewFrustum.setNearClip(nearClip);
|
|
viewFrustum.setFarClip(farClip);
|
|
|
|
// Ask the ViewFrustum class to calculate our corners
|
|
viewFrustum.calculate();
|
|
|
|
//viewFrustum.dump();
|
|
|
|
// Get ready to draw some lines
|
|
glDisable(GL_LIGHTING);
|
|
glColor4f(1.0, 1.0, 1.0, 1.0);
|
|
glLineWidth(1.0);
|
|
glBegin(GL_LINES);
|
|
|
|
if (::frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || ::frustumDrawingMode == FRUSTUM_DRAW_MODE_VECTORS) {
|
|
// Calculate the origin direction vectors
|
|
glm::vec3 lookingAt = position + (direction * 0.2f);
|
|
glm::vec3 lookingAtUp = position + (up * 0.2f);
|
|
glm::vec3 lookingAtRight = position + (right * 0.2f);
|
|
|
|
// Looking At = white
|
|
glColor3f(1,1,1);
|
|
glVertex3f(position.x, position.y, position.z);
|
|
glVertex3f(lookingAt.x, lookingAt.y, lookingAt.z);
|
|
|
|
// Looking At Up = purple
|
|
glColor3f(1,0,1);
|
|
glVertex3f(position.x, position.y, position.z);
|
|
glVertex3f(lookingAtUp.x, lookingAtUp.y, lookingAtUp.z);
|
|
|
|
// Looking At Right = cyan
|
|
glColor3f(0,1,1);
|
|
glVertex3f(position.x, position.y, position.z);
|
|
glVertex3f(lookingAtRight.x, lookingAtRight.y, lookingAtRight.z);
|
|
}
|
|
|
|
if (::frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || ::frustumDrawingMode == FRUSTUM_DRAW_MODE_PLANES
|
|
|| ::frustumDrawingMode == FRUSTUM_DRAW_MODE_NEAR_PLANE) {
|
|
// Drawing the bounds of the frustum
|
|
// viewFrustum.getNear plane - bottom edge
|
|
glColor3f(1,0,0);
|
|
glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z);
|
|
glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z);
|
|
|
|
// viewFrustum.getNear plane - top edge
|
|
glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z);
|
|
glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z);
|
|
|
|
// viewFrustum.getNear plane - right edge
|
|
glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z);
|
|
glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z);
|
|
|
|
// viewFrustum.getNear plane - left edge
|
|
glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z);
|
|
glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z);
|
|
}
|
|
|
|
if (::frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || ::frustumDrawingMode == FRUSTUM_DRAW_MODE_PLANES
|
|
|| ::frustumDrawingMode == FRUSTUM_DRAW_MODE_FAR_PLANE) {
|
|
// viewFrustum.getFar plane - bottom edge
|
|
glColor3f(0,1,0); // GREEN!!!
|
|
glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z);
|
|
glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z);
|
|
|
|
// viewFrustum.getFar plane - top edge
|
|
glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z);
|
|
glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z);
|
|
|
|
// viewFrustum.getFar plane - right edge
|
|
glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z);
|
|
glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z);
|
|
|
|
// viewFrustum.getFar plane - left edge
|
|
glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z);
|
|
glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z);
|
|
}
|
|
|
|
if (::frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || ::frustumDrawingMode == FRUSTUM_DRAW_MODE_PLANES) {
|
|
// RIGHT PLANE IS CYAN
|
|
// right plane - bottom edge - viewFrustum.getNear to distant
|
|
glColor3f(0,1,1);
|
|
glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z);
|
|
glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z);
|
|
|
|
// right plane - top edge - viewFrustum.getNear to distant
|
|
glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z);
|
|
glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z);
|
|
|
|
// LEFT PLANE IS BLUE
|
|
// left plane - bottom edge - viewFrustum.getNear to distant
|
|
glColor3f(0,0,1);
|
|
glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z);
|
|
glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z);
|
|
|
|
// left plane - top edge - viewFrustum.getNear to distant
|
|
glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z);
|
|
glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z);
|
|
}
|
|
|
|
glEnd();
|
|
glEnable(GL_LIGHTING);
|
|
}
|
|
|
|
|
|
void display(void)
|
|
{
|
|
PerfStat("display");
|
|
|
|
glEnable(GL_LINE_SMOOTH);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
|
|
glPushMatrix(); {
|
|
glLoadIdentity();
|
|
|
|
// Setup 3D lights
|
|
glEnable(GL_COLOR_MATERIAL);
|
|
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
|
|
|
|
GLfloat light_position0[] = { 1.0, 1.0, 0.0, 0.0 };
|
|
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
|
|
GLfloat ambient_color[] = { 0.7, 0.7, 0.8 }; //{ 0.125, 0.305, 0.5 };
|
|
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient_color);
|
|
GLfloat diffuse_color[] = { 0.8, 0.7, 0.7 }; //{ 0.5, 0.42, 0.33 };
|
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_color);
|
|
GLfloat specular_color[] = { 1.0, 1.0, 1.0, 1.0};
|
|
glLightfv(GL_LIGHT0, GL_SPECULAR, specular_color);
|
|
|
|
glMaterialfv(GL_FRONT, GL_SPECULAR, specular_color);
|
|
glMateriali(GL_FRONT, GL_SHININESS, 96);
|
|
|
|
//--------------------------------------------------------
|
|
// camera settings
|
|
//--------------------------------------------------------
|
|
if ( displayHead ) {
|
|
//-----------------------------------------------
|
|
// set the camera to looking at my own face
|
|
//-----------------------------------------------
|
|
myCamera.setTargetPosition ( myAvatar.getPos() );
|
|
myCamera.setYaw ( - myAvatar.getBodyYaw() );
|
|
myCamera.setPitch ( 0.0 );
|
|
myCamera.setRoll ( 0.0 );
|
|
myCamera.setUp ( 0.53 );
|
|
myCamera.setDistance ( 0.03 );
|
|
myCamera.setTightness ( 100.0f );
|
|
myCamera.update ( 1.f/FPS );
|
|
} else {
|
|
//----------------------------------------------------
|
|
// set the camera to third-person view behind my av
|
|
//----------------------------------------------------
|
|
myCamera.setTargetPosition ( myAvatar.getPos() );
|
|
myCamera.setYaw ( 180.0 - myAvatar.getBodyYaw() );
|
|
myCamera.setPitch ( 0.0 ); // temporarily, this must be 0.0 or else bad juju
|
|
myCamera.setRoll ( 0.0 );
|
|
myCamera.setUp ( 0.45 );
|
|
myCamera.setDistance ( 0.5 );
|
|
myCamera.setTightness ( 10.0f );
|
|
myCamera.update ( 1.f/FPS );
|
|
}
|
|
|
|
// Note: whichCamera is used to pick between the normal camera myCamera for our
|
|
// main camera, vs, an alternate camera. The alternate camera we support right now
|
|
// is the viewFrustumOffsetCamera. But theoretically, we could use this same mechanism
|
|
// to add other cameras.
|
|
//
|
|
// Why have two cameras? Well, one reason is that because in the case of the render_view_frustum()
|
|
// code, we want to keep the state of "myCamera" intact, so we can render what the view frustum of
|
|
// myCamera is. But we also want to do meaningful camera transforms on OpenGL for the offset camera
|
|
Camera whichCamera = myCamera;
|
|
Camera viewFrustumOffsetCamera = myCamera;
|
|
|
|
if (::viewFrustumFromOffset && ::frustumOn) {
|
|
//----------------------------------------------------
|
|
// set the camera to third-person view but offset so we can see the frustum
|
|
//----------------------------------------------------
|
|
viewFrustumOffsetCamera.setYaw ( 180.0 - myAvatar.getBodyYaw() + ::viewFrustumOffsetYaw );
|
|
viewFrustumOffsetCamera.setPitch ( 0.0 + ::viewFrustumOffsetPitch );
|
|
viewFrustumOffsetCamera.setRoll ( 0.0 + ::viewFrustumOffsetRoll );
|
|
viewFrustumOffsetCamera.setUp ( 0.2 + ::viewFrustumOffsetUp );
|
|
viewFrustumOffsetCamera.setDistance ( 0.5 + ::viewFrustumOffsetDistance );
|
|
viewFrustumOffsetCamera.update(1.f/FPS);
|
|
whichCamera = viewFrustumOffsetCamera;
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// transform view according to whichCamera
|
|
// could be myCamera (if in normal mode)
|
|
// or could be viewFrustumOffsetCamera if in offset mode
|
|
//---------------------------------------------
|
|
glRotatef ( whichCamera.getPitch(), 1, 0, 0 );
|
|
glRotatef ( whichCamera.getYaw(), 0, 1, 0 );
|
|
glRotatef ( whichCamera.getRoll(), 0, 0, 1 );
|
|
glTranslatef( -whichCamera.getPosition().x, -whichCamera.getPosition().y, -whichCamera.getPosition().z );
|
|
|
|
|
|
|
|
if (::starsOn) {
|
|
// should be the first rendering pass - w/o depth buffer / lighting
|
|
stars.render(fov);
|
|
}
|
|
|
|
glEnable(GL_LIGHTING);
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
|
|
/*
|
|
// Test - Draw a blue sphere around a body part of mine!
|
|
|
|
glPushMatrix();
|
|
glColor4f(0,0,1, 0.7);
|
|
glTranslatef(myAvatar.getBonePosition(AVATAR_BONE_RIGHT_HAND).x,
|
|
myAvatar.getBonePosition(AVATAR_BONE_RIGHT_HAND).y,
|
|
myAvatar.getBonePosition(AVATAR_BONE_RIGHT_HAND).z);
|
|
glutSolidSphere(0.03, 10, 10);
|
|
glPopMatrix();
|
|
*/
|
|
|
|
// draw a red sphere
|
|
float sphereRadius = 0.25f;
|
|
glColor3f(1,0,0);
|
|
glPushMatrix();
|
|
//glTranslatef( 0.0f, sphereRadius, 0.0f );
|
|
glutSolidSphere( sphereRadius, 15, 15 );
|
|
glPopMatrix();
|
|
|
|
//draw a grid gound plane....
|
|
drawGroundPlaneGrid( 5.0f, 9 );
|
|
|
|
|
|
// Draw cloud of dots
|
|
if (!displayHead) cloud.render();
|
|
|
|
// Draw voxels
|
|
if ( showingVoxels )
|
|
{
|
|
voxels.render();
|
|
}
|
|
|
|
// Draw field vectors
|
|
if (displayField) field.render();
|
|
|
|
// Render avatars of other agents
|
|
AgentList *agentList = AgentList::getInstance();
|
|
for(std::vector<Agent>::iterator agent = agentList->getAgents().begin();
|
|
agent != agentList->getAgents().end();
|
|
agent++) {
|
|
if (agent->getLinkedData() != NULL) {
|
|
Head *agentHead = (Head *)agent->getLinkedData();
|
|
glPushMatrix();
|
|
glm::vec3 pos = agentHead->getPos();
|
|
glTranslatef(-pos.x, -pos.y, -pos.z);
|
|
agentHead->render(0, 0);
|
|
glPopMatrix();
|
|
}
|
|
}
|
|
|
|
if ( !displayHead ) balls.render();
|
|
|
|
// Render the world box
|
|
if (!displayHead && statsOn) render_world_box();
|
|
|
|
// brad's frustum for debugging
|
|
if (::frustumOn) render_view_frustum();
|
|
|
|
|
|
//Render my own avatar
|
|
myAvatar.render( true, 1 );
|
|
}
|
|
|
|
glPopMatrix();
|
|
|
|
// Render 2D overlay: I/O level bar graphs and text
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
gluOrtho2D(0, WIDTH, HEIGHT, 0);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_LIGHTING);
|
|
|
|
#ifndef _WIN32
|
|
audio.render(WIDTH, HEIGHT);
|
|
if (audioScope.getState()) audioScope.render();
|
|
#endif
|
|
|
|
if (displayHeadMouse && !displayHead && statsOn) {
|
|
// Display small target box at center or head mouse target that can also be used to measure LOD
|
|
glColor3f(1.0, 1.0, 1.0);
|
|
glDisable(GL_LINE_SMOOTH);
|
|
const int PIXEL_BOX = 20;
|
|
glBegin(GL_LINE_STRIP);
|
|
glVertex2f(headMouseX - PIXEL_BOX/2, headMouseY - PIXEL_BOX/2);
|
|
glVertex2f(headMouseX + PIXEL_BOX/2, headMouseY - PIXEL_BOX/2);
|
|
glVertex2f(headMouseX + PIXEL_BOX/2, headMouseY + PIXEL_BOX/2);
|
|
glVertex2f(headMouseX - PIXEL_BOX/2, headMouseY + PIXEL_BOX/2);
|
|
glVertex2f(headMouseX - PIXEL_BOX/2, headMouseY - PIXEL_BOX/2);
|
|
glEnd();
|
|
glEnable(GL_LINE_SMOOTH);
|
|
}
|
|
|
|
// Show detected levels from the serial I/O ADC channel sensors
|
|
if (displayLevels) serialPort.renderLevels(WIDTH,HEIGHT);
|
|
|
|
// Display miscellaneous text stats onscreen
|
|
if (statsOn) {
|
|
glLineWidth(1.0f);
|
|
glPointSize(1.0f);
|
|
displayStats();
|
|
}
|
|
|
|
// Show menu
|
|
if (::menuOn) {
|
|
glLineWidth(1.0f);
|
|
glPointSize(1.0f);
|
|
menu.render(WIDTH,HEIGHT);
|
|
}
|
|
|
|
// Draw number of nearby people always
|
|
glPointSize(1.0f);
|
|
char agents[100];
|
|
sprintf(agents, "Agents: %ld\n", AgentList::getInstance()->getAgents().size());
|
|
drawtext(WIDTH-100,20, 0.10, 0, 1.0, 0, agents, 1, 0, 0);
|
|
|
|
if (::paintOn) {
|
|
|
|
char paintMessage[100];
|
|
sprintf(paintMessage,"Painting (%.3f,%.3f,%.3f/%.3f/%d,%d,%d)",
|
|
::paintingVoxel.x,::paintingVoxel.y,::paintingVoxel.z,::paintingVoxel.s,
|
|
(unsigned int)::paintingVoxel.red,(unsigned int)::paintingVoxel.green,(unsigned int)::paintingVoxel.blue);
|
|
drawtext(WIDTH-350,50, 0.10, 0, 1.0, 0, paintMessage, 1, 1, 0);
|
|
}
|
|
|
|
glPopMatrix();
|
|
|
|
|
|
glutSwapBuffers();
|
|
frameCount++;
|
|
|
|
// If application has just started, report time from startup to now (first frame display)
|
|
if (justStarted) {
|
|
printf("Startup Time: %4.2f\n",
|
|
(usecTimestampNow() - usecTimestamp(&applicationStartupTime))/1000000.0);
|
|
justStarted = false;
|
|
}
|
|
}
|
|
|
|
// int version of setValue()
|
|
int setValue(int state, int *value) {
|
|
if (state == MENU_ROW_PICKED) {
|
|
*value = !(*value);
|
|
} else if (state == MENU_ROW_GET_VALUE) {
|
|
return *value;
|
|
} else {
|
|
*value = state;
|
|
}
|
|
return *value;
|
|
}
|
|
|
|
// bool version of setValue()
|
|
int setValue(int state, bool *value) {
|
|
if (state == MENU_ROW_PICKED) {
|
|
*value = !(*value);
|
|
} else if (state == MENU_ROW_GET_VALUE) {
|
|
return *value;
|
|
} else {
|
|
*value = state;
|
|
}
|
|
return *value;
|
|
}
|
|
|
|
int setHead(int state) {
|
|
return setValue(state, &displayHead);
|
|
}
|
|
|
|
int setField(int state) {
|
|
return setValue(state, &displayField);
|
|
}
|
|
|
|
int setNoise(int state) {
|
|
int iRet = setValue(state, &noiseOn);
|
|
if (noiseOn) {
|
|
myAvatar.setNoise(noise);
|
|
} else {
|
|
myAvatar.setNoise(0);
|
|
}
|
|
return iRet;
|
|
}
|
|
|
|
int setVoxels(int state) {
|
|
return setValue(state, &::showingVoxels);
|
|
}
|
|
|
|
int setStars(int state) {
|
|
return setValue(state, &::starsOn);
|
|
}
|
|
|
|
int setStats(int state) {
|
|
return setValue(state, &statsOn);
|
|
}
|
|
|
|
int setMenu(int state) {
|
|
return setValue(state, &::menuOn);
|
|
}
|
|
|
|
int setMirror(int state) {
|
|
return setValue(state, &headMirror);
|
|
}
|
|
|
|
int setDisplayFrustum(int state) {
|
|
return setValue(state, &::frustumOn);
|
|
}
|
|
|
|
int setFrustumOffset(int state) {
|
|
int value = setValue(state, &::viewFrustumFromOffset);
|
|
|
|
// reshape so that OpenGL will get the right lens details for the camera of choice
|
|
if (state == MENU_ROW_PICKED) {
|
|
reshape(::WIDTH,::HEIGHT);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
int setFrustumOrigin(int state) {
|
|
return setValue(state, &::cameraFrustum);
|
|
}
|
|
|
|
int quitApp(int state) {
|
|
if (state == MENU_ROW_PICKED) {
|
|
::terminate();
|
|
}
|
|
return 2; // non state so menu class doesn't add "state"
|
|
}
|
|
|
|
int setFrustumRenderMode(int state) {
|
|
if (state == MENU_ROW_PICKED) {
|
|
::frustumDrawingMode = (::frustumDrawingMode+1)%FRUSTUM_DRAW_MODE_COUNT;
|
|
}
|
|
return ::frustumDrawingMode;
|
|
}
|
|
|
|
const char* modeAll = " - All ";
|
|
const char* modeVectors = " - Vectors ";
|
|
const char* modePlanes = " - Planes ";
|
|
const char* modeNear = " - Near ";
|
|
const char* modeFar = " - Far ";
|
|
|
|
const char* getFrustumRenderModeName(int state) {
|
|
const char * mode;
|
|
switch (state) {
|
|
case FRUSTUM_DRAW_MODE_ALL:
|
|
mode = modeAll;
|
|
break;
|
|
case FRUSTUM_DRAW_MODE_VECTORS:
|
|
mode = modeVectors;
|
|
break;
|
|
case FRUSTUM_DRAW_MODE_PLANES:
|
|
mode = modePlanes;
|
|
break;
|
|
case FRUSTUM_DRAW_MODE_NEAR_PLANE:
|
|
mode = modeNear;
|
|
break;
|
|
case FRUSTUM_DRAW_MODE_FAR_PLANE:
|
|
mode = modeFar;
|
|
break;
|
|
}
|
|
return mode;
|
|
}
|
|
|
|
void initMenu() {
|
|
MenuColumn *menuColumnOptions, *menuColumnTools, *menuColumnDebug, *menuColumnFrustum;
|
|
// Options
|
|
menuColumnOptions = menu.addColumn("Options");
|
|
menuColumnOptions->addRow("(H)ead", setHead);
|
|
menuColumnOptions->addRow("Field (f)", setField);
|
|
menuColumnOptions->addRow("(N)oise", setNoise);
|
|
menuColumnOptions->addRow("Mirror", setMirror);
|
|
menuColumnOptions->addRow("(V)oxels", setVoxels);
|
|
menuColumnOptions->addRow("Stars (*)", setStars);
|
|
menuColumnOptions->addRow("(Q)uit", quitApp);
|
|
|
|
// Tools
|
|
menuColumnTools = menu.addColumn("Tools");
|
|
menuColumnTools->addRow("Stats (/)", setStats);
|
|
menuColumnTools->addRow("(M)enu", setMenu);
|
|
|
|
// Frustum Options
|
|
menuColumnFrustum = menu.addColumn("Frustum");
|
|
menuColumnFrustum->addRow("Display (F)rustum", setDisplayFrustum);
|
|
menuColumnFrustum->addRow("Use (O)ffset Camera", setFrustumOffset);
|
|
menuColumnFrustum->addRow("Switch (C)amera", setFrustumOrigin);
|
|
menuColumnFrustum->addRow("(R)ender Mode", setFrustumRenderMode, getFrustumRenderModeName);
|
|
|
|
// Debug
|
|
menuColumnDebug = menu.addColumn("Debug");
|
|
|
|
}
|
|
|
|
void testPointToVoxel()
|
|
{
|
|
float y=0;
|
|
float z=0;
|
|
float s=0.1;
|
|
for (float x=0; x<=1; x+= 0.05)
|
|
{
|
|
std::cout << " x=" << x << " ";
|
|
|
|
unsigned char red = 200; //randomColorValue(65);
|
|
unsigned char green = 200; //randomColorValue(65);
|
|
unsigned char blue = 200; //randomColorValue(65);
|
|
|
|
unsigned char* voxelCode = pointToVoxel(x, y, z, s,red,green,blue);
|
|
printVoxelCode(voxelCode);
|
|
delete voxelCode;
|
|
std::cout << std::endl;
|
|
}
|
|
}
|
|
|
|
void sendVoxelServerEraseAll() {
|
|
char message[100];
|
|
sprintf(message,"%c%s",'Z',"erase all");
|
|
int messageSize = strlen(message) + 1;
|
|
AgentList::getInstance()->broadcastToAgents(message, messageSize, &AGENT_TYPE_VOXEL, 1);
|
|
}
|
|
|
|
void sendVoxelServerAddScene() {
|
|
char message[100];
|
|
sprintf(message,"%c%s",'Z',"add scene");
|
|
int messageSize = strlen(message) + 1;
|
|
AgentList::getInstance()->broadcastToAgents(message, messageSize, &AGENT_TYPE_VOXEL, 1);
|
|
}
|
|
|
|
void shiftPaintingColor()
|
|
{
|
|
// About the color of the paintbrush... first determine the dominant color
|
|
::dominantColor = (::dominantColor+1)%3; // 0=red,1=green,2=blue
|
|
::paintingVoxel.red = (::dominantColor==0)?randIntInRange(200,255):randIntInRange(40,100);
|
|
::paintingVoxel.green = (::dominantColor==1)?randIntInRange(200,255):randIntInRange(40,100);
|
|
::paintingVoxel.blue = (::dominantColor==2)?randIntInRange(200,255):randIntInRange(40,100);
|
|
}
|
|
|
|
void setupPaintingVoxel() {
|
|
glm::vec3 avatarPos = myAvatar.getPos();
|
|
|
|
::paintingVoxel.x = avatarPos.z/-10.0; // voxel space x is negative z head space
|
|
::paintingVoxel.y = avatarPos.y/-10.0; // voxel space y is negative y head space
|
|
::paintingVoxel.z = avatarPos.x/-10.0; // voxel space z is negative x head space
|
|
::paintingVoxel.s = 1.0/256;
|
|
|
|
shiftPaintingColor();
|
|
}
|
|
|
|
void addRandomSphere(bool wantColorRandomizer)
|
|
{
|
|
float r = randFloatInRange(0.05,0.1);
|
|
float xc = randFloatInRange(r,(1-r));
|
|
float yc = randFloatInRange(r,(1-r));
|
|
float zc = randFloatInRange(r,(1-r));
|
|
float s = 0.001; // size of voxels to make up surface of sphere
|
|
bool solid = false;
|
|
|
|
printf("random sphere\n");
|
|
printf("radius=%f\n",r);
|
|
printf("xc=%f\n",xc);
|
|
printf("yc=%f\n",yc);
|
|
printf("zc=%f\n",zc);
|
|
|
|
voxels.createSphere(r,xc,yc,zc,s,solid,wantColorRandomizer);
|
|
}
|
|
|
|
|
|
const float KEYBOARD_YAW_RATE = 0.8;
|
|
const float KEYBOARD_PITCH_RATE = 0.6;
|
|
const float KEYBOARD_STRAFE_RATE = 0.03;
|
|
const float KEYBOARD_FLY_RATE = 0.08;
|
|
|
|
void specialkeyUp(int k, int x, int y) {
|
|
if (k == GLUT_KEY_UP) {
|
|
myAvatar.setDriveKeys(FWD, 0);
|
|
myAvatar.setDriveKeys(UP, 0);
|
|
}
|
|
if (k == GLUT_KEY_DOWN) {
|
|
myAvatar.setDriveKeys(BACK, 0);
|
|
myAvatar.setDriveKeys(DOWN, 0);
|
|
}
|
|
if (k == GLUT_KEY_LEFT) {
|
|
myAvatar.setDriveKeys(LEFT, 0);
|
|
myAvatar.setDriveKeys(ROT_LEFT, 0);
|
|
}
|
|
if (k == GLUT_KEY_RIGHT) {
|
|
myAvatar.setDriveKeys(RIGHT, 0);
|
|
myAvatar.setDriveKeys(ROT_RIGHT, 0);
|
|
}
|
|
|
|
}
|
|
|
|
void specialkey(int k, int x, int y)
|
|
{
|
|
if (k == GLUT_KEY_UP || k == GLUT_KEY_DOWN || k == GLUT_KEY_LEFT || k == GLUT_KEY_RIGHT) {
|
|
if (k == GLUT_KEY_UP) {
|
|
if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) myAvatar.setDriveKeys(UP, 1);
|
|
else myAvatar.setDriveKeys(FWD, 1);
|
|
}
|
|
if (k == GLUT_KEY_DOWN) {
|
|
if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) myAvatar.setDriveKeys(DOWN, 1);
|
|
else myAvatar.setDriveKeys(BACK, 1);
|
|
}
|
|
if (k == GLUT_KEY_LEFT) {
|
|
if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) myAvatar.setDriveKeys(LEFT, 1);
|
|
else myAvatar.setDriveKeys(ROT_LEFT, 1);
|
|
}
|
|
if (k == GLUT_KEY_RIGHT) {
|
|
if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) myAvatar.setDriveKeys(RIGHT, 1);
|
|
else myAvatar.setDriveKeys(ROT_RIGHT, 1);
|
|
}
|
|
#ifndef _WIN32
|
|
audio.setWalkingState(true);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
void keyUp(unsigned char k, int x, int y) {
|
|
if (k == 'e') myAvatar.setDriveKeys(UP, 0);
|
|
if (k == 'c') myAvatar.setDriveKeys(DOWN, 0);
|
|
if (k == 'w') myAvatar.setDriveKeys(FWD, 0);
|
|
if (k == 's') myAvatar.setDriveKeys(BACK, 0);
|
|
if (k == 'a') myAvatar.setDriveKeys(ROT_LEFT, 0);
|
|
if (k == 'd') myAvatar.setDriveKeys(ROT_RIGHT, 0);
|
|
|
|
}
|
|
|
|
void key(unsigned char k, int x, int y)
|
|
{
|
|
|
|
// Process keypresses
|
|
if (k == 'q' || k == 'Q') ::terminate();
|
|
if (k == '/') statsOn = !statsOn; // toggle stats
|
|
if (k == '*') ::starsOn = !::starsOn; // toggle stars
|
|
if (k == 'V' || k == 'v') ::showingVoxels = !::showingVoxels; // toggle voxels
|
|
if (k == 'F') ::frustumOn = !::frustumOn; // toggle view frustum debugging
|
|
if (k == 'C') ::cameraFrustum = !::cameraFrustum; // toggle which frustum to look at
|
|
if (k == 'O' || k == 'G') setFrustumOffset(MENU_ROW_PICKED); // toggle view frustum offset debugging
|
|
|
|
if (k == '[') ::viewFrustumOffsetYaw -= 0.5;
|
|
if (k == ']') ::viewFrustumOffsetYaw += 0.5;
|
|
if (k == '{') ::viewFrustumOffsetPitch -= 0.5;
|
|
if (k == '}') ::viewFrustumOffsetPitch += 0.5;
|
|
if (k == '(') ::viewFrustumOffsetRoll -= 0.5;
|
|
if (k == ')') ::viewFrustumOffsetRoll += 0.5;
|
|
if (k == '<') ::viewFrustumOffsetDistance -= 0.5;
|
|
if (k == '>') ::viewFrustumOffsetDistance += 0.5;
|
|
if (k == ',') ::viewFrustumOffsetUp -= 0.05;
|
|
if (k == '.') ::viewFrustumOffsetUp += 0.05;
|
|
|
|
// if (k == '|') ViewFrustum::fovAngleAdust -= 0.05;
|
|
// if (k == '\\') ViewFrustum::fovAngleAdust += 0.05;
|
|
|
|
if (k == 'R') setFrustumRenderMode(MENU_ROW_PICKED);
|
|
|
|
if (k == '&') {
|
|
::paintOn = !::paintOn; // toggle paint
|
|
::setupPaintingVoxel(); // also randomizes colors
|
|
}
|
|
if (k == '^') ::shiftPaintingColor(); // shifts randomize color between R,G,B dominant
|
|
if (k == '-') ::sendVoxelServerEraseAll(); // sends erase all command to voxel server
|
|
if (k == '%') ::sendVoxelServerAddScene(); // sends add scene command to voxel server
|
|
if (k == 'n' || k == 'N')
|
|
{
|
|
noiseOn = !noiseOn; // Toggle noise
|
|
if (noiseOn)
|
|
{
|
|
myAvatar.setNoise(noise);
|
|
}
|
|
else
|
|
{
|
|
myAvatar.setNoise(0);
|
|
}
|
|
|
|
}
|
|
|
|
if (k == 'h') {
|
|
displayHead = !displayHead;
|
|
#ifndef _WIN32
|
|
audio.setMixerLoopbackFlag(displayHead);
|
|
#endif
|
|
}
|
|
|
|
if (k == 'm' || k == 'M') setMenu(MENU_ROW_PICKED);
|
|
|
|
if (k == 'f') displayField = !displayField;
|
|
if (k == 'l') displayLevels = !displayLevels;
|
|
if (k == 'e') myAvatar.setDriveKeys(UP, 1);
|
|
if (k == 'c') myAvatar.setDriveKeys(DOWN, 1);
|
|
if (k == 'w') myAvatar.setDriveKeys(FWD, 1);
|
|
if (k == 's') myAvatar.setDriveKeys(BACK, 1);
|
|
if (k == ' ') reset_sensors();
|
|
if (k == 't') renderPitchRate -= KEYBOARD_PITCH_RATE;
|
|
if (k == 'g') renderPitchRate += KEYBOARD_PITCH_RATE;
|
|
#ifdef STARFIELD_KEYS
|
|
if (k == 'u') stars.setResolution(starsTiles += 1);
|
|
if (k == 'j') stars.setResolution(starsTiles = max(starsTiles-1,1));
|
|
if (k == 'i') if (starsLod < 1.0) starsLod = stars.changeLOD(1.01);
|
|
if (k == 'k') if (starsLod > 0.01) starsLod = stars.changeLOD(0.99);
|
|
if (k == 'r') stars.readInput(starFile, 0);
|
|
#endif
|
|
if (k == 'a') myAvatar.setDriveKeys(ROT_LEFT, 1);
|
|
if (k == 'd') myAvatar.setDriveKeys(ROT_RIGHT, 1);
|
|
}
|
|
|
|
// Receive packets from other agents/servers and decide what to do with them!
|
|
void *networkReceive(void *args)
|
|
{
|
|
sockaddr senderAddress;
|
|
ssize_t bytesReceived;
|
|
char *incomingPacket = new char[MAX_PACKET_SIZE];
|
|
|
|
while (!stopNetworkReceiveThread) {
|
|
if (AgentList::getInstance()->getAgentSocket().receive(&senderAddress, incomingPacket, &bytesReceived)) {
|
|
packetCount++;
|
|
bytesCount += bytesReceived;
|
|
|
|
switch (incomingPacket[0]) {
|
|
case PACKET_HEADER_TRANSMITTER_DATA:
|
|
myAvatar.processTransmitterData(incomingPacket, bytesReceived);
|
|
break;
|
|
case PACKET_HEADER_VOXEL_DATA:
|
|
case PACKET_HEADER_Z_COMMAND:
|
|
case PACKET_HEADER_ERASE_VOXEL:
|
|
voxels.parseData(incomingPacket, bytesReceived);
|
|
break;
|
|
case PACKET_HEADER_BULK_AVATAR_DATA:
|
|
AgentList::getInstance()->processBulkAgentData(&senderAddress, incomingPacket, bytesReceived, sizeof(float) * 11);
|
|
break;
|
|
default:
|
|
AgentList::getInstance()->processAgentData(&senderAddress, incomingPacket, bytesReceived);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete[] incomingPacket;
|
|
pthread_exit(0);
|
|
return NULL;
|
|
}
|
|
|
|
void idle(void) {
|
|
timeval check;
|
|
gettimeofday(&check, NULL);
|
|
|
|
// Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time
|
|
|
|
if (diffclock(&lastTimeIdle, &check) > IDLE_SIMULATE_MSECS) {
|
|
// If mouse is being dragged, update hand movement in the avatar
|
|
//if ( mousePressed == 1 )
|
|
|
|
if ( myAvatar.getMode() == AVATAR_MODE_COMMUNICATING ) {
|
|
float leftRight = ( mouseX - mouseStartX ) / (float)WIDTH;
|
|
float downUp = ( mouseY - mouseStartY ) / (float)HEIGHT;
|
|
float backFront = 0.0;
|
|
glm::vec3 handMovement( leftRight, downUp, backFront );
|
|
myAvatar.setHandMovement( handMovement );
|
|
}
|
|
else {
|
|
mouseStartX = mouseX;
|
|
mouseStartY = mouseY;
|
|
//mouseStartX = (float)WIDTH / 2.0f;
|
|
//mouseStartY = (float)HEIGHT / 2.0f;
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
// when the mouse is being pressed, an 'action' is being
|
|
// triggered in the avatar. The action is context-based.
|
|
//--------------------------------------------------------
|
|
if ( mousePressed == 1 ) {
|
|
myAvatar.setTriggeringAction( true );
|
|
}
|
|
else {
|
|
myAvatar.setTriggeringAction( false );
|
|
}
|
|
|
|
// Simulation
|
|
simulateHead( 1.f/FPS );
|
|
|
|
|
|
//test
|
|
/*
|
|
for(std::vector<Agent>::iterator agent = agentList.getAgents().begin(); agent != agentList.getAgents().end(); agent++)
|
|
{
|
|
if (agent->getLinkedData() != NULL)
|
|
{
|
|
Head *agentHead = (Head *)agent->getLinkedData();
|
|
agentHead->simulate(1.f/FPS);
|
|
}
|
|
}
|
|
*/
|
|
|
|
simulateHand(1.f/FPS);
|
|
|
|
field.simulate(1.f/FPS);
|
|
myAvatar.simulate(1.f/FPS);
|
|
balls.simulate(1.f/FPS);
|
|
cloud.simulate(1.f/FPS);
|
|
|
|
glutPostRedisplay();
|
|
lastTimeIdle = check;
|
|
|
|
}
|
|
|
|
// Read serial data
|
|
if (serialPort.active) {
|
|
serialPort.readData();
|
|
}
|
|
}
|
|
|
|
|
|
void reshape(int width, int height)
|
|
{
|
|
WIDTH = width;
|
|
HEIGHT = height;
|
|
float aspectRatio = ((float)width/(float)height); // based on screen resize
|
|
|
|
float fov;
|
|
float nearClip;
|
|
float farClip;
|
|
|
|
// get the lens details from the current camera
|
|
if (::viewFrustumFromOffset) {
|
|
fov = ::viewFrustumOffsetCamera.getFieldOfView();
|
|
nearClip = ::viewFrustumOffsetCamera.getNearClip();
|
|
farClip = ::viewFrustumOffsetCamera.getFarClip();
|
|
} else {
|
|
fov = ::myCamera.getFieldOfView();
|
|
nearClip = ::myCamera.getNearClip();
|
|
farClip = ::myCamera.getFarClip();
|
|
}
|
|
|
|
//printf("reshape() width=%d, height=%d, aspectRatio=%f fov=%f near=%f far=%f \n",
|
|
// width,height,aspectRatio,fov,nearClip,farClip);
|
|
|
|
// Tell our viewFrustum about this change
|
|
::viewFrustum.setAspectRatio(aspectRatio);
|
|
|
|
|
|
glViewport(0, 0, width, height); // shouldn't this account for the menu???
|
|
|
|
glMatrixMode(GL_PROJECTION); //hello
|
|
|
|
// XXXBHG - Note: this is Tobias's code for loading the perspective matrix. At Philip's suggestion, I'm removing
|
|
// it and putting back our old code that simply loaded the fov, ratio, and near/far clips. But I'm keeping this here
|
|
// for reference for now.
|
|
//fov.setResolution(width, height)
|
|
// .setBounds(glm::vec3(-0.5f,-0.5f,-500.0f), glm::vec3(0.5f, 0.5f, 0.1f) )
|
|
// .setPerspective(0.7854f);
|
|
//glLoadMatrixf(glm::value_ptr(fov.getViewerScreenXform()));
|
|
|
|
glLoadIdentity();
|
|
|
|
// XXXBHG - If we're in view frustum mode, then we need to do this little bit of hackery so that
|
|
// OpenGL won't clip our frustum rendering lines. This is a debug hack for sure! Basically, this makes
|
|
// the near clip a little bit closer (therefor you see more) and the far clip a little bit farther (also,
|
|
// to see more.)
|
|
if (::frustumOn) {
|
|
nearClip -= 0.01f;
|
|
farClip += 0.01f;
|
|
}
|
|
|
|
// On window reshape, we need to tell OpenGL about our new setting
|
|
gluPerspective(fov,aspectRatio,nearClip,farClip);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
|
|
|
|
}
|
|
|
|
void mouseFunc( int button, int state, int x, int y )
|
|
{
|
|
if( button == GLUT_LEFT_BUTTON && state == GLUT_DOWN )
|
|
{
|
|
if (!menu.mouseClick(x, y)) {
|
|
mouseX = x;
|
|
mouseY = y;
|
|
mousePressed = 1;
|
|
//mouseStartX = x;
|
|
//mouseStartY = y;
|
|
}
|
|
}
|
|
if( button == GLUT_LEFT_BUTTON && state == GLUT_UP ) {
|
|
mouseX = x;
|
|
mouseY = y;
|
|
mousePressed = 0;
|
|
}
|
|
|
|
}
|
|
|
|
void motionFunc( int x, int y)
|
|
{
|
|
mouseX = x;
|
|
mouseY = y;
|
|
}
|
|
|
|
void mouseoverFunc( int x, int y)
|
|
{
|
|
menu.mouseOver(x, y);
|
|
mouseX = x;
|
|
mouseY = y;
|
|
if (mousePressed == 0)
|
|
{}
|
|
}
|
|
|
|
void attachNewHeadToAgent(Agent *newAgent) {
|
|
if (newAgent->getLinkedData() == NULL) {
|
|
newAgent->setLinkedData(new Head());
|
|
}
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
void audioMixerUpdate(in_addr_t newMixerAddress, in_port_t newMixerPort) {
|
|
audio.updateMixerParams(newMixerAddress, newMixerPort);
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, const char * argv[])
|
|
{
|
|
AgentList::createInstance(AGENT_TYPE_INTERFACE);
|
|
|
|
gettimeofday(&applicationStartupTime, NULL);
|
|
const char* domainIP = getCmdOption(argc, argv, "--domain");
|
|
if (domainIP) {
|
|
strcpy(DOMAIN_IP,domainIP);
|
|
}
|
|
|
|
// Handle Local Domain testing with the --local command line
|
|
if (cmdOptionExists(argc, argv, "--local")) {
|
|
printf("Local Domain MODE!\n");
|
|
int ip = getLocalAddress();
|
|
sprintf(DOMAIN_IP,"%d.%d.%d.%d", (ip & 0xFF), ((ip >> 8) & 0xFF),((ip >> 16) & 0xFF), ((ip >> 24) & 0xFF));
|
|
}
|
|
|
|
// the callback for our instance of AgentList is attachNewHeadToAgent
|
|
AgentList::getInstance()->linkedDataCreateCallback = &attachNewHeadToAgent;
|
|
|
|
#ifndef _WIN32
|
|
AgentList::getInstance()->audioMixerSocketUpdate = &audioMixerUpdate;
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
WSADATA WsaData;
|
|
int wsaresult = WSAStartup( MAKEWORD(2,2), &WsaData );
|
|
#endif
|
|
|
|
// start the agentList threads
|
|
AgentList::getInstance()->startSilentAgentRemovalThread();
|
|
AgentList::getInstance()->startDomainServerCheckInThread();
|
|
AgentList::getInstance()->startPingUnknownAgentsThread();
|
|
|
|
glutInit(&argc, (char**)argv);
|
|
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
|
|
glutInitWindowSize(WIDTH, HEIGHT);
|
|
glutCreateWindow("Interface");
|
|
|
|
#ifdef _WIN32
|
|
glewInit();
|
|
#endif
|
|
|
|
// Before we render anything, let's set up our viewFrustumOffsetCamera with a sufficiently large
|
|
// field of view and near and far clip to make it interesting.
|
|
//viewFrustumOffsetCamera.setFieldOfView(90.0);
|
|
viewFrustumOffsetCamera.setNearClip(0.1);
|
|
viewFrustumOffsetCamera.setFarClip(500.0);
|
|
|
|
printf( "Created Display Window.\n" );
|
|
|
|
initMenu();
|
|
initDisplay();
|
|
printf( "Initialized Display.\n" );
|
|
|
|
glutDisplayFunc(display);
|
|
glutReshapeFunc(reshape);
|
|
glutKeyboardFunc(key);
|
|
glutKeyboardUpFunc(keyUp);
|
|
glutSpecialFunc(specialkey);
|
|
glutSpecialUpFunc(specialkeyUp);
|
|
glutMotionFunc(motionFunc);
|
|
glutPassiveMotionFunc(mouseoverFunc);
|
|
glutMouseFunc(mouseFunc);
|
|
glutIdleFunc(idle);
|
|
|
|
init();
|
|
printf( "Init() complete.\n" );
|
|
|
|
// Check to see if the user passed in a command line option for randomizing colors
|
|
if (cmdOptionExists(argc, argv, "--NoColorRandomizer")) {
|
|
wantColorRandomizer = false;
|
|
}
|
|
|
|
// Check to see if the user passed in a command line option for loading a local
|
|
// Voxel File. If so, load it now.
|
|
const char* voxelsFilename = getCmdOption(argc, argv, "-i");
|
|
if (voxelsFilename) {
|
|
voxels.loadVoxelsFile(voxelsFilename,wantColorRandomizer);
|
|
printf("Local Voxel File loaded.\n");
|
|
}
|
|
|
|
// create thread for receipt of data via UDP
|
|
pthread_create(&networkReceiveThread, NULL, networkReceive, NULL);
|
|
printf("Network receive thread created.\n");
|
|
|
|
glutTimerFunc(1000, Timer, 0);
|
|
glutMainLoop();
|
|
|
|
printf("Normal exit.\n");
|
|
::terminate();
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|