diff --git a/img/icon.png b/img/icon.png new file mode 100644 index 0000000..c78681b Binary files /dev/null and b/img/icon.png differ diff --git a/img/title.psd b/img/title.psd new file mode 100644 index 0000000..b16efbe Binary files /dev/null and b/img/title.psd differ diff --git a/src/Application.cpp b/src/Application.cpp new file mode 100644 index 0000000..9e4d8b5 --- /dev/null +++ b/src/Application.cpp @@ -0,0 +1,31 @@ +#include "MainFrame.h" +#include "Application.h" + +#if __WXMAC__ +#include +#endif + +bool Application::OnInit() { + + wxImage::AddHandler(new wxPNGHandler); + +#if __WXMAC__ + /** + * This code serves no purpose other than debugging. + * A wxWidgets program running outside a bundle is like a ghost, + * it can't receive input and it can't have focus. + * Because I don't want to package my app in a bundle every time I test + * something out I run this code which brings back normal behaviour even without the bundle + */ + ProcessSerialNumber PSN; + GetCurrentProcess(&PSN); + TransformProcessType(&PSN, kProcessTransformToForegroundApplication); +#endif + + //Create the mainframe which creates the rest + new MainFrame(); + + return true; +} + +IMPLEMENT_APP(Application) \ No newline at end of file diff --git a/src/Application.h b/src/Application.h new file mode 100644 index 0000000..986e4e5 --- /dev/null +++ b/src/Application.h @@ -0,0 +1,15 @@ +#ifndef APPLICATION_H +#define APPLICATION_H + +#include + +/** + * The application itself, all it does is launch the main window + */ +class Application : public wxApp { +public: + virtual bool OnInit(); +}; + +#endif /* APPLICATION_H */ + diff --git a/src/EditLocationsDialog.cpp b/src/EditLocationsDialog.cpp new file mode 100644 index 0000000..a1ec7f1 --- /dev/null +++ b/src/EditLocationsDialog.cpp @@ -0,0 +1,321 @@ +#include "EditLocationsDialog.h" +#include "Settings.h" + +enum { + WINDOW_EDITLOCATIONS = wxID_HIGHEST, + LISTBOX_LOCATIONS, + BUTTON_ADD, + BUTTON_EDIT, + BUTTON_REMOVE, + BUTTON_UP, + BUTTON_DOWN, + TEXT_LINK_PASTE +}; + +/** + * Create a new Edit Locations dialog. Edits directly the settings.locations vector + * and saves it upon exit + * @param frame + */ +EditLocationsDialog::EditLocationsDialog(wxWindow *frame) +: wxDialog(frame, WINDOW_EDITLOCATIONS, "Edit Locations", wxDefaultPosition, wxSize(600, 300), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), +editing(false) { + + //GUI controls + list_locations = new wxListCtrl(this, LISTBOX_LOCATIONS, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_EDIT_LABELS | wxLC_SINGLE_SEL); + addButton = new wxButton(this, BUTTON_ADD, "Add location"); + editButton = new wxButton(this, BUTTON_EDIT, "Edit location"); + removeButton = new wxButton(this, BUTTON_REMOVE, "Remove location"); + upButton = new wxButton(this, BUTTON_UP, "Move Up"); + downButton = new wxButton(this, BUTTON_DOWN, "Move Down"); + + //Sizer that divides GUI up in left listbox and right buttons + wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + + //Listbox + sizer->Add(list_locations, 1, wxALL | wxEXPAND, 5); + + //Buttons arranged vertically + wxBoxSizer *buttonSizer = new wxBoxSizer(wxVERTICAL); + buttonSizer->Add(addButton, 0, wxALIGN_CENTER | wxALL, 5); + buttonSizer->Add(editButton, 0, wxALIGN_CENTER | wxALL, 5); + buttonSizer->Add(removeButton, 0, wxALIGN_CENTER | wxALL, 5); + buttonSizer->Add(upButton, 0, wxALIGN_CENTER | wxALL, 5); + buttonSizer->Add(downButton, 0, wxALIGN_CENTER | wxALL, 5); + buttonSizer->AddSpacer(50); + buttonSizer->Add(new wxStaticText(this, wxID_ANY, "Double click on an item to edit the name", wxDefaultPosition, wxSize(100, 200), wxALIGN_CENTRE), 0, wxEXPAND); + sizer->Add(buttonSizer, 0, wxALL | wxEXPAND, 5); + + this->SetSizer(sizer); + + //Event handlers + list_locations->Connect(LISTBOX_LOCATIONS, wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxCommandEventHandler(EditLocationsDialog::OnEditItem), NULL, this); + list_locations->Connect(LISTBOX_LOCATIONS, wxEVT_COMMAND_LIST_END_LABEL_EDIT, wxListEventHandler(EditLocationsDialog::OnEditFinished), NULL, this); + list_locations->Connect(LISTBOX_LOCATIONS, wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxCommandEventHandler(EditLocationsDialog::OnSelectionChanges), NULL, this); + list_locations->Connect(LISTBOX_LOCATIONS, wxEVT_COMMAND_LIST_ITEM_SELECTED, wxCommandEventHandler(EditLocationsDialog::OnSelectionChanges), NULL, this); + + Connect(BUTTON_ADD, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(EditLocationsDialog::OnAdd)); + Connect(BUTTON_EDIT, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(EditLocationsDialog::OnEdit)); + Connect(BUTTON_REMOVE, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(EditLocationsDialog::OnRemove)); + Connect(BUTTON_UP, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(EditLocationsDialog::OnUp)); + Connect(BUTTON_DOWN, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(EditLocationsDialog::OnDown)); + + Connect(WINDOW_EDITLOCATIONS, wxEVT_CLOSE_WINDOW, wxCloseEventHandler(EditLocationsDialog::OnCloseWindow)); + + //ListCtrl columns + wxListItem col0; + col0.SetId(0); + col0.SetText("Name"); + col0.SetWidth(300); + list_locations->InsertColumn(0, col0); + + col0.SetId(1); + col0.SetText("id"); + col0.SetWidth(200); + list_locations->InsertColumn(1, col0); + + RefillList(); +} + +/** + * Save the settings when the dialog is removed + * (It is assumed that the caller deletes the dialog as it is closed + */ +EditLocationsDialog::~EditLocationsDialog() { + settings.saveSettings(); +} + +/** + * Update the button enabliness if the selected location changes + * to prevent the up being on for the topmost and the down being on for the bottommost + * @param event + */ +void EditLocationsDialog::OnSelectionChanges(wxCommandEvent &event) { + UpdateButtons(); +} + +/** + * Event handler for when the user wants to edit a name by activating an item + * + * @param event + */ +void EditLocationsDialog::OnEditItem(wxCommandEvent &event) { + //list_locations->Edit(GetSelected()); + editing = true; + UpdateButtons(); +} + +/** + * Event handler for when the user finished editing a name + * Copies the namestring given by the user back to the settings + * + * @param event + */ +void EditLocationsDialog::OnEditFinished(wxListEvent &event) { + if(event.GetIndex() < 0 || event.GetIndex() >= (int)settings.locations.size()) + return; + + strncpy((char*) &settings.locations[event.GetIndex()].name, (const char*) event.GetLabel(), LOCATION_LENGTH+1); + editing = false; + UpdateButtons(); +} + +/** + * Let the user paste a link and add it as a location + * @param event + */ +void EditLocationsDialog::OnAdd(wxCommandEvent &event) { + wxString link = OpenPasteDialog(); + + //In case the user cancelled + if (link.length() == 0) + return; + + //Create new location + struct pano_location l; + strncpy((char*) &l.name, "Unnamed Location", LOCATION_LENGTH+1); + strncpy((char*) &l.pano_id, (const char*) link, PANOID_LENGTH+1); + settings.locations.push_back(l); + + RefillList(); + + //Let user edit name immediately + //list_locations->Edit(settings.locations.size()-1); + editing = true; + UpdateButtons(); +} + +/** + * User wants to edit the pano_id of the location + * + * @param event + */ +void EditLocationsDialog::OnEdit(wxCommandEvent &event) { + wxString link = OpenPasteDialog(); + if (link.length() == 0) + return; + + //Get the index of the selected item + const unsigned int selected = GetSelected(); + + if(selected < 0 || selected >= settings.locations.size()) + return; + + strncpy(settings.locations[selected].pano_id, (const char*) link, PANOID_LENGTH+1); + + RefillList(selected); +} + +void EditLocationsDialog::OnRemove(wxCommandEvent &event) { + settings.locations.erase(settings.locations.begin() + GetSelected()); + + RefillList(GetSelected()); + UpdateButtons(); +} + +/** + * Swap the selected item with the one above it + * @param event + */ +void EditLocationsDialog::OnUp(wxCommandEvent &event) { + unsigned int i = GetSelected(); + if(i < 0 || i >= settings.locations.size()) + return; + + struct pano_location temp = settings.locations[i - 1]; + settings.locations[i - 1] = settings.locations[i]; + settings.locations[i] = temp; + + RefillList(i - 1); +} + +/** + * Swap the selected item with the one below it + * @param event + */ +void EditLocationsDialog::OnDown(wxCommandEvent &event) { + unsigned int i = GetSelected(); + if(i < 0 || i >= settings.locations.size()) + return; + + struct pano_location temp = settings.locations[i + 1]; + settings.locations[i + 1] = settings.locations[i]; + settings.locations[i] = temp; + + RefillList(i + 1); +} + +/** + * Synchronize the list with items that are currently in the vector + * @param selected + */ +void EditLocationsDialog::RefillList(unsigned int selected) { + list_locations->DeleteAllItems(); + + //Add items + for (unsigned int n = 0; n < settings.locations.size(); n++) { + wxListItem item; + item.SetId(n); + + if (n == selected) + item.SetState(wxLIST_STATE_SELECTED); + + list_locations->InsertItem(item); + list_locations->SetItem(item); //Seems necessary for selected state to catch on + + //Fill in text + list_locations->SetItem(n, 0, settings.locations[n].name); + list_locations->SetItem(n, 1, settings.locations[n].pano_id); + } +} + +/** + * Save the settings when the dialog is closed + * @param event + */ +void EditLocationsDialog::OnCloseWindow(wxCloseEvent &event) { + settings.saveSettings(); + event.Skip(); +} + +/** + * Disable buttons if there is no item selected + */ +void EditLocationsDialog::UpdateButtons() { + const int selectedItem = list_locations->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + + if (selectedItem == -1 || editing) { + //No item selected: disable buttons that need an item + editButton->Enable(false); + removeButton->Enable(false); + upButton->Enable(false); + downButton->Enable(false); + } else { + editButton->Enable(); + removeButton->Enable(); + + //Also disable up / down if item is unable to move that way + upButton->Enable(selectedItem > 0); + downButton->Enable(selectedItem < (int)settings.locations.size() - 1); + } +} + +//These two global variables are only to be used for communication between the two below +//methods. Not sure how to do it another way yet without global variables... +wxString pasted_panoID; +wxDialog *pasteDialog; + +/** + * Open up a dialog in which the user can paste a streetview url + * Method blocks until user pasted or cancelled, output + */ +wxString EditLocationsDialog::OpenPasteDialog() { + //Default value for if the dialog fails + pasted_panoID = wxString(""); + + + pasteDialog = new wxDialog(this, wxID_ANY, "Paste Google StreetView link", wxDefaultPosition, wxSize(500, 100)); + wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); + + wxTextCtrl *text_link = new wxTextCtrl(pasteDialog, TEXT_LINK_PASTE, "Paste link here"); + text_link->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(EditLocationsDialog::OnPasteLink), NULL, this); + sizer->Add(text_link, 1, wxEXPAND | wxALL, 5); + + pasteDialog->SetSizer(sizer); + pasteDialog->ShowModal(); //Method blocks until user pastes a correct link or cancels + delete pasteDialog; + + return pasted_panoID; +} + +/** + * Parse the text for a link containing the panorama id. + * Close the modal dialog if it is found + * @param event + */ +void EditLocationsDialog::OnPasteLink(wxCommandEvent &event) { + event.Skip(); + wxString link = event.GetString(); + + int g = link.find("&panoid="); + if (g == -1) + return; + + wxString id = link.substr(g + strlen("&panoid="), PANOID_LENGTH); + + if(id.length() < PANOID_LENGTH) + return; + + pasted_panoID = id; + pasteDialog->Close(); +} + +/** + * Returns the index of the selected item, or -1 otherwise + * @return + */ +int EditLocationsDialog::GetSelected() { + return list_locations->GetNextItem(-1, + wxLIST_NEXT_ALL, + wxLIST_STATE_SELECTED); +} \ No newline at end of file diff --git a/src/EditLocationsDialog.h b/src/EditLocationsDialog.h new file mode 100644 index 0000000..a505a48 --- /dev/null +++ b/src/EditLocationsDialog.h @@ -0,0 +1,57 @@ +/* + * File: EditLocationsDialog.h + * Author: paulwagener + * + * Created on 22 april 2011, 18:07 + */ + +#ifndef EDITLOCATIONSDIALOG_H +#define EDITLOCATIONSDIALOG_H + +#include +#include + +using namespace std; + +/** + * Dialog box only used to configure the locations in settings.locations + * + * @param frame + */ +class EditLocationsDialog : public wxDialog { +public: + EditLocationsDialog(wxWindow *frame); + ~EditLocationsDialog(); +private: + void OnEditItem(wxCommandEvent &event); + void OnEditFinished(wxListEvent &event); + void OnSelectionChanges(wxCommandEvent &event); + + void OnAdd(wxCommandEvent &event); + void OnEdit(wxCommandEvent &event); + void OnRemove(wxCommandEvent &event); + void OnUp(wxCommandEvent &event); + void OnDown(wxCommandEvent &event); + + void OnCloseWindow(wxCloseEvent &event); + + int GetSelected(); + void UpdateButtons(); + void RefillList(unsigned int selected = -1); + + wxString OpenPasteDialog(); + void OnPasteLink(wxCommandEvent &event); + + bool editing; + + wxListCtrl *list_locations; + + wxButton *addButton; + wxButton *editButton; + wxButton *removeButton; + wxButton *upButton; + wxButton *downButton; +}; + +#endif /* EDITLOCATIONSDIALOG_H */ + diff --git a/src/Explorer.cpp b/src/Explorer.cpp new file mode 100644 index 0000000..cc9f365 --- /dev/null +++ b/src/Explorer.cpp @@ -0,0 +1,343 @@ +/* + * File: Explorer.cpp + * Author: paulwagener + * + * Created on 12 april 2011, 19:24 + */ + +#include "Explorer.h" +#include "gl.h" +#include "stdio.h" +#include "common.h" +#include "Panorama.h" +#include "Settings.h" +#include + +Explorer::Explorer(const char* firstPano) { + requestDownloadThread = false; + downloading = false; + downloadedPano = NULL; + + oldClosestPanorama = NULL; + oldClosestOpacity = 1; + + //Temporarily store the first panorama we should load + //until we have an OpenGL context to actually load them in + strncpy((char*) &firstPanorama, firstPano, PANOID_LENGTH + 1); +} + +/** + * Load a new panorama into the panoramas vector. + * It needs to be called twice if the panorama isn't donloaded yet + * Assumes an OpenGL context to be present + * + * @param panoid + * @param zoom_level + */ +void Explorer::loadPanorama(const char *panoid, int zoom_level) { + if (downloading) + return; + + //File is not cached, request a download procedure + strncpy(downloadPano, panoid, PANOID_LENGTH + 1); + downloading = true; + requestDownloadThread = true; + //downloadThread(); + +} + +void Explorer::init() { + //Kruisstraat (kruising): FKoscBO7bd5LkgMmjGLAeQ + //Kruisstraat (weverstraat): 6B46Si-orLNYnBhDKdPDiw + //Kruisstraat (weverstraat 2): eE1uAIWbo1bDccxVNyE0Jg + //Kruisstraat (weverstraat 3): cCstFkrrJxSI5Ql4OOPb1Q + //Kruisstraat (weverstraat 4): GL6UZFuHTQLQCnAvE2VTHw + //Kruisstraat (115): qdAqxSu085_gynN0R8k4RA + //Times Square: dU1D9CsdYTN-3YDiyyUSnQ +} + +/** + * + */ +void Explorer::downloadThread() { + try { + Panorama *p = new Panorama(downloadPano, settings.zoomLevel); + downloadedPano = p; + } catch (const char* c) { + printf("Exception caught: %s\n", c); + } + downloading = false; +} + +bool Explorer::hasPanorama(const char* pano_id, int zoom_level) { + for (unsigned int i = 0; i < panoramas.size(); i++) { + if (strcmp(panoramas[i]->pano_id, pano_id) == 0 && panoramas[i]->zoom_level == zoom_level) + return true; + } + return false; +} + +bool Explorer::hasPanorama(Panorama *panorama) { + for (unsigned int i = 0; i < panoramas.size(); i++) { + if (panoramas[i] == panorama) + return true; + } + return false; +} + +Panorama* Explorer::getClosestPanorama() { + if (panoramas.size() == 0) + return NULL; + + Panorama *closestPanorama = panoramas[0]; + for (unsigned int i = 0; i < panoramas.size(); i++) { + if (panoramas[i]->distanceTo(player.target_location) < closestPanorama->distanceTo(player.target_location)) + closestPanorama = panoramas[i]; + } + return closestPanorama; + + +} +bool efirst = true; + +/** + * + */ +void Explorer::updatePanoramas() { + settings.numPanoramas = 5; + if (downloading) + return; + + //Load thread got a new panorama for us, add it to the official panorama list + if (downloadedPano != NULL) { + downloadedPano->loadGL(); + panoramas.push_back(downloadedPano); + + if (panoramas.size() == 1) { + referencePoint = downloadedPano->location; + player.initializeLocation(downloadedPano->location); + } + + downloadedPano = NULL; + } + + Panorama *closestPanorama = getClosestPanorama(); + //If no panorama's have yet been downloaded load the panorama that was given in the constructor + if (panoramas.size() == 0) { + loadPanorama(firstPanorama, settings.zoomLevel); + } else if (closestPanorama != NULL) { + + //First delete all panorama's that are not adjacent to the closest panorama + //and are not the closestPanorama itself + for (unsigned int i = 0; i < panoramas.size(); i++) { + if (panoramas[i] != closestPanorama && !closestPanorama->hasAdjacent(panoramas[i]->pano_id)) { + panoramas.erase(panoramas.begin() + i); + i--; + } + } + + //Then see if there are any adjacent panorama's not already loaded + for (unsigned int i = 0; i < closestPanorama->links.size(); i++) { + if (!hasPanorama(closestPanorama->links[i].pano_id, settings.zoomLevel)) + loadPanorama(closestPanorama->links[i].pano_id, settings.zoomLevel); + } + } + +} + +/** + * Find a panorama by its pano_id + * + * @param pano_id + * @return + */ +Panorama* Explorer::getPanoramaById(const char* pano_id) { + for (unsigned int i = 0; i < panoramas.size(); i++) { + if (strcmp(pano_id, panoramas[i]->pano_id) == 0) + return panoramas[i]; + } + return NULL; +} + +int g = 0; + +void Explorer::display(int width, int height) { + updatePanoramas(); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, width, height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(45.0, width / (GLdouble) height, 1, 200.0); + + /** + * Coordinate system: + * y+ North + * y- South + * x+ East + * x- West + * z+ Up + * z- Down + * @param w + * @param h + */ + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + /* Initialize OpenGL */ + glClearColor(1, 1, 1, 1); + glShadeModel(GL_SMOOTH); + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + + //Light is only used to illuminate Streetview Guy, + //The panorama's themselves are drawn at full intensity without lighting. + glEnable(GL_LIGHT0); + + GLfloat ambient[] = {0.8f, 0.8f, 0.8f, 1.0f}; + GLfloat diffuse[] = {0.6f, 0.7f, 0.7f, 1.0f}; + GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f}; + GLfloat position[] = {0.0f, 0.0f, 0.0f, 1.0f}; + + glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse); + glLightfv(GL_LIGHT0, GL_SPECULAR, specular); + glLightfv(GL_LIGHT0, GL_POSITION, position); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + + + player.updatePosition(); + + //Update closestPanorama + //Below this code we can use closestPanorama + + + collision_detection(); + glLoadIdentity(); + + player.targetCamera(referencePoint); + + glColor3f(1, 1, 1); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + + + if (settings.wireframe) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } else { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + + Panorama *closestPanorama = getClosestPanorama(); + + //Ensure oldClosestPanorama ain't dangling + if (!hasPanorama(oldClosestPanorama)) + oldClosestPanorama = NULL; + + //Increase opacity of all Panorama's + for (unsigned int i = 0; i < panoramas.size(); i++) { + panoramas[i]->opacity += 0.03f; + if (panoramas[i]->opacity > 1) + panoramas[i]->opacity = 1; + } + + /** + * Draw the background, this is from the panorama the closest + * to the camera because it has the best perspective + */ + if (closestPanorama != NULL) { + + glColor4f(1, 1, 1, 1); + closestPanorama->draw(referencePoint, true); + glClear(GL_DEPTH_BUFFER_BIT); + + if (oldClosestPanorama == NULL) + oldClosestPanorama = closestPanorama; + + //Draw the old fading panorama + if (oldClosestPanorama != closestPanorama && oldClosestPanorama != NULL) { + glColor4f(1, 1, 1, oldClosestOpacity); + oldClosestPanorama->draw(referencePoint, true); + glColor4f(1, 1, 1, 1); + glClear(GL_DEPTH_BUFFER_BIT); + + //Fade + oldClosestOpacity -= 0.05f; + if (oldClosestOpacity <= 0) { + oldClosestOpacity = 1; + oldClosestPanorama = closestPanorama; + } + + + } + + + + } + + for (unsigned int i = 0; i < panoramas.size(); i++) { + //glClear((GL_DEPTH_BUFFER_BIT)); + //if (panoramas[i] != closestPanorama) + { + // if(i == panoramas.size()-1) { + // start = 80; + // end = 100; + // } + + panoramas[i]->draw(referencePoint); + } + } + + //Dots for debugging + if (false && closestPanorama != NULL) { + glPushMatrix(); + glTranslated(closestPanorama->location.easting - referencePoint.easting, closestPanorama->location.northing - referencePoint.northing, 0); + glRotatef(180 - closestPanorama->pano_yaw_deg, 0, 0, 1); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_DEPTH_TEST); + glPointSize(2); + glColor3f(1, 0, 0); + glBegin(GL_POINTS); + for (int y = 0; y < closestPanorama->mapHeight; y++) { + for (int x = 0; x < closestPanorama->mapWidth; x++) { + + // int index = closestPanorama->depthmapIndices[y * closestPanorama->mapWidth + x]; + int panoindex = closestPanorama->panomapIndices[y * closestPanorama->mapWidth + x]; + if (panoindex == closestPanorama->ownPanomapIndex) + glColor3f(1, 0, 0); + else + glColor3f(0, 0, 1); + + //if(index == theDepthIndex) + //closestPanorama->drawVertexAtAzimuthElevation(x, y); + //glVertex3f(0, 0, 0); + + } + + } + + glEnd(); + glEnable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); + glPopMatrix(); + } + + player.drawPlayer(referencePoint); + +} + +/** + * Collision detection + */ +void Explorer::collision_detection() { +} \ No newline at end of file diff --git a/src/Explorer.h b/src/Explorer.h new file mode 100644 index 0000000..65a0817 --- /dev/null +++ b/src/Explorer.h @@ -0,0 +1,52 @@ +/* + * File: Explorer.h + * Author: paulwagener + * + * Created on 12 april 2011, 19:24 + */ + +#include "Panorama.h" +#include "Player.h" + +#ifndef EXPLORER_H +#define EXPLORER_H + +class Explorer { +public: + Explorer(const char* firstPano); + + char firstPanorama[PANOID_LENGTH+1]; + bool requestDownloadThread; + + char downloadPano[PANOID_LENGTH+1]; + bool downloading; + Panorama *downloadedPano; + + bool hasPanorama(const char *pano_id, int zoom_level); + bool hasPanorama(Panorama *p); + Panorama* getClosestPanorama(); + + std::vector panoramas; + void updatePanoramas(); + Panorama* getPanoramaById(const char* pano_id); + + Panorama *oldClosestPanorama; + + float oldClosestOpacity; + + Player player; + + void downloadThread(); + + struct utmPosition referencePoint; + void display(int width, int height); + + void loadPanorama(const char *panoid, int zoom_level); + void collision_detection(); + void init(); +private: + +}; + +#endif /* EXPLORER_H */ + diff --git a/src/GLCanvas.cpp b/src/GLCanvas.cpp new file mode 100644 index 0000000..0053304 --- /dev/null +++ b/src/GLCanvas.cpp @@ -0,0 +1,182 @@ +#include "GLCanvas.h" +#include + +#if __WXMAC__ +#include +#endif + +/** + * Most simple wxThread subclass, all it does is reset the requestThread flag + * and call the downloadThread. + * @param explorer + */ +ExplorerHelperThread::ExplorerHelperThread(Explorer& explorer) +:explorer(explorer) { +} + +void* ExplorerHelperThread::Entry() { + explorer.requestDownloadThread = false; + explorer.downloadThread(); + + return NULL; +} + +const int args[] = {WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16}; + + +/** + * Set up an OpenGL Canvas which the Explorer class can draw in + * @param parent + * @param pano_id + */ +GLCanvas::GLCanvas(wxWindow* parent, const char* pano_id) : +wxGLCanvas(parent, wxID_ANY, args, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE), +explorer(pano_id) { + m_context = new wxGLContext(this); + timer = new wxTimer(this); + timer->Start(20); + + capture = false; + ignoreMouseEvents = false; +} + +GLCanvas::~GLCanvas() { + timer->Stop(); + delete timer; + delete m_context; +} + +/** + * Mouse move callback used for camera movement by the user + * @param event + */ +void GLCanvas::mouseMoved(wxMouseEvent& event) { + //Ingore mouse movements if thee mouse isn't in capture mode (invisible) + //or there is the possibility that mouse movements may propagate indefinitely + if (!capture) return; + if (ignoreMouseEvents) return; + + //Don't move if the mouse is technically in the same place. + if (event.m_x == trap_x && event.m_y == trap_y) + return; + + explorer.player.moveMouse(event.m_x - trap_x, event.m_y - trap_y); + +#ifdef __WXMAC__ + CGSetLocalEventsSuppressionInterval(0.0); +#endif + + ignoreMouseEvents = true; + WarpPointer(trap_x, trap_y); + ignoreMouseEvents = false; +} + +/** + * Capture the mouse if it isn't already + * @param event + */ +void GLCanvas::mouseDown(wxMouseEvent& event) { + if (!capture) { + this->SetCursor(wxCURSOR_BLANK); + capture = true; + trap_x = event.m_x; + trap_y = event.m_y; + WarpPointer(trap_x, trap_y); + } +} + +/** + * Zoom in and out with the camera + * @param event + */ +void GLCanvas::mouseWheelMoved(wxMouseEvent& event) { + //TODO: ZOOM +} + +/** + * Handle player controls + * @param event + */ +void GLCanvas::keyDown(wxKeyEvent& event) { + if (event.m_keyCode == 'W') explorer.player.keys.forward = true; + if (event.m_keyCode == 'S') explorer.player.keys.backward = true; + if (event.m_keyCode == 'A') explorer.player.keys.strafe_left = true; + if (event.m_keyCode == 'D') explorer.player.keys.strafe_right = true; + if (event.m_keyCode == 'Q') explorer.player.keys.rotate_left = true; + if (event.m_keyCode == 'E') explorer.player.keys.rotate_right = true; + + if (event.m_keyCode == WXK_ESCAPE) { + disableCapture(); + } +} + +void GLCanvas::keyUp(wxKeyEvent& event) { + if (event.m_keyCode == 'W') explorer.player.keys.forward = false; + if (event.m_keyCode == 'S') explorer.player.keys.backward = false; + if (event.m_keyCode == 'A') explorer.player.keys.strafe_left = false; + if (event.m_keyCode == 'D') explorer.player.keys.strafe_right = false; + if (event.m_keyCode == 'Q') explorer.player.keys.rotate_left = false; + if (event.m_keyCode == 'E') explorer.player.keys.rotate_right = false; +} + + +/** + * Give mousecursor control back to the user + */ +void GLCanvas::disableCapture() { + capture = false; + this->SetCursor(wxCURSOR_DEFAULT); +} + +void GLCanvas::OnResize(wxSizeEvent& evt) { + Refresh(); +} + +void GLCanvas::OnTimer(wxTimerEvent& event) { + Refresh(); +} + +bool fffirst = true; + +void GLCanvas::onPaint(wxPaintEvent& evt) { + if (!IsShown()) + return; + + wxGLCanvas::SetCurrent(*m_context); + wxPaintDC(this); // only to be used in paint events. use wxClientDC to paint outside the paint event + + //Check if a download is requested and call downloadThread() in a seperate thread + //if it is. Note that the below is not a memory leak, detached threads (the default) + //automatically delete themselves once they finish running + if(explorer.requestDownloadThread) { + ExplorerHelperThread *thread = new ExplorerHelperThread(explorer); + thread->Create(); + thread->Run(); + } + + + + + if (fffirst) { + explorer.init(); + + + fffirst = false; + } + + wxSize size = GetSize(); + explorer.display(size.GetWidth(), size.GetHeight()); + SwapBuffers(); +} + +BEGIN_EVENT_TABLE(GLCanvas, wxGLCanvas) +EVT_MOTION(GLCanvas::mouseMoved) +EVT_LEFT_DOWN(GLCanvas::mouseDown) +EVT_SIZE(GLCanvas::OnResize) +EVT_KEY_DOWN(GLCanvas::keyDown) +EVT_KEY_UP(GLCanvas::keyUp) +EVT_MOUSEWHEEL(GLCanvas::mouseWheelMoved) +EVT_PAINT(GLCanvas::onPaint) + +EVT_TIMER(wxID_ANY, GLCanvas::OnTimer) +END_EVENT_TABLE() \ No newline at end of file diff --git a/src/GLCanvas.h b/src/GLCanvas.h new file mode 100644 index 0000000..2472ac2 --- /dev/null +++ b/src/GLCanvas.h @@ -0,0 +1,58 @@ +/* + * File: GLCanvas.h + * Author: paulwagener + * + * Created on 16 april 2011, 22:16 + */ + +#ifndef GLCANVAS_H +#define GLCANVAS_H + +#include +#include +#include "Explorer.h" + +class ExplorerHelperThread : public wxThread { +public: + ExplorerHelperThread(Explorer &explorer); + +protected: + void* Entry(); + +private: + Explorer &explorer; +}; + +class GLCanvas : public wxGLCanvas { + wxGLContext* m_context; + + int trap_x, trap_y; + +public: + wxTimer *timer; + GLCanvas(wxWindow* parent, const char *pano_id); + ~GLCanvas(); + bool capture; + void disableCapture(); + bool ignoreMouseEvents; + + Explorer explorer; + + void OnResize(wxSizeEvent& evt); + void onPaint(wxPaintEvent& evt); + + // events + void mouseMoved(wxMouseEvent& event); + void mouseDown(wxMouseEvent& event); + void mouseWheelMoved(wxMouseEvent& event); + void keyDown(wxKeyEvent& event); + void keyUp(wxKeyEvent& event); + + + void OnTimer(wxTimerEvent& event); + + DECLARE_EVENT_TABLE() +}; + +#endif /* GLCANVAS_H */ + diff --git a/src/MainFrame.cpp b/src/MainFrame.cpp new file mode 100644 index 0000000..cc07bd9 --- /dev/null +++ b/src/MainFrame.cpp @@ -0,0 +1,265 @@ +/* + * File: Frames.cpp + * Author: paulwagener + * + * Created on 16 april 2011, 22:20 + */ + +#include "MainFrame.h" + +using namespace std; + +#include "GLCanvas.h" +#include "Panorama.h" +#include "EditLocationsDialog.h" +#include "Settings.h" +#include "PreferencesDialog.h" + +#define DEFAULT_WIDTH 800 +#define DEFAULT_HEIGHT 600 + +const wxEventType myEVT_PANORAMA_START = wxNewEventType(); + +enum { + COMBOBOX_LOCATIONS = wxID_HIGHEST, + MENU_BACKTOMAIN, + BUTTON_EDIT_LOCATIONS, + BUTTON_LAST_LOCATION, + TEXTBOX_PASTE_LINK +}; + +MainFrame::MainFrame() +: wxFrame(NULL, wxID_ANY, "StreetView Explorer", wxDefaultPosition, wxSize(DEFAULT_WIDTH, DEFAULT_HEIGHT)), +isStartingWithPanorama(false) { + + //Set up menu + wxMenu *fileMenu = new wxMenu(); + fileMenu->Append(MENU_BACKTOMAIN, "&Go to main screen"); + fileMenu->Append(wxID_PREFERENCES, "&Preferences"); + fileMenu->Append(wxID_ABOUT, "&About..."); + fileMenu->Append(wxID_EXIT, "E&xit"); + + wxMenuBar *menuBar = new wxMenuBar(); + menuBar->Append(fileMenu, "&File"); + SetMenuBar(menuBar); + + //Event handlers + Connect(MENU_BACKTOMAIN, wxEVT_COMMAND_MENU_SELECTED, wxMenuEventHandler(MainFrame::OnGoToMainScreen)); + Connect(wxID_PREFERENCES, wxEVT_COMMAND_MENU_SELECTED, wxMenuEventHandler(MainFrame::OnPreferences)); + Connect(wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED, wxMenuEventHandler(MainFrame::OnAbout)); + Connect(wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxMenuEventHandler(MainFrame::OnQuit)); + Connect(wxID_ANY, myEVT_PANORAMA_START, wxCommandEventHandler(MainFrame::OnPanoramaStart)); + + ShowMain(); + + //Show window + this->Center(); + this->Show(); + + //StartWithPanorama("dU1D9CsdYTN-3YDiyyUSnQ"); +} + +/** + * Do the actual work callers expect ShowPanorama() to do. Replace the contents of the frame + * with an OpenGL window with the actual game. + */ +void MainFrame::OnPanoramaStart(wxCommandEvent &event) { + glCanvas = new GLCanvas(this, (const char*)event.GetString()); + wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(glCanvas, 1, wxEXPAND); + ReplaceSizer(sizer); + this->Layout(); +} + +/** + * Callback for menu option to go back to the main screen + * + * @param event + */ +void MainFrame::OnGoToMainScreen(wxMenuEvent &event) { + ShowMain(); +} + +/** + * Open up a configuration panel through which users can adjust some parameters + * @param + */ +void MainFrame::OnPreferences(wxMenuEvent& WXUNUSED(event)) { + if (glCanvas != NULL) + glCanvas->disableCapture(); + + PreferencesDialog *prefs = new PreferencesDialog(this); + prefs->ShowModal(); + delete prefs; +} + +/** + * Show information about me + * @param + */ +void MainFrame::OnAbout(wxMenuEvent& WXUNUSED(event)) { + wxMessageBox("This application is brought to you by Paul Wagener.\nhttp://code.google.com/p/streetview-explorer\nSpecial thanks to Ariane and the helpful people at #wxwidgets", + "About StreetView Explorer", + wxOK | wxICON_INFORMATION, this); +} + +/** + * Close the application upon the exit command + * @param + */ +void MainFrame::OnQuit(wxMenuEvent& WXUNUSED(event)) { + Close(true); +} + +/** + * When the user selects a location go directly to the 3D view + * @param event + */ +void MainFrame::OnSelectLocation(wxCommandEvent &event) { + const int selection = combobox->GetSelection(); + + //Ignore first 'pick a destination' entry + if (selection == 0) + return; + + struct pano_location l = settings.locations[selection - 1]; + this->StartWithPanorama(l.pano_id); +} + +/** + * Show a modal dialog where the user can edit his favorite locations + * @param event + */ +void MainFrame::OnEditLocations(wxCommandEvent &event) { + //Edit locations dialog + EditLocationsDialog *editLocations = new EditLocationsDialog(this); + editLocations->ShowModal(); + delete editLocations; + + //Resynchronize locations in combobox + RefillLocations(); +} + +/** + * Button to start where the user left off the last time he explorer StreetView + * @param event + */ +void MainFrame::OnLastLocation(wxCommandEvent &event) { + StartWithPanorama(settings.last_pano); +} + +/** + * Callback for when the user pastes a link into the textfield. + * When it + * @param event + */ +void MainFrame::OnLinkPaste(wxCommandEvent &event) { + //Paste events are usually send multiple times with the same content. + //Ensure that we only start the panorama once + if (isStartingWithPanorama) + return; + + wxString link = event.GetString(); + + int g = link.find("&panoid="); + if (g == -1) + return; + + wxString pasted_panoID = link.substr(g + strlen("&panoid="), PANOID_LENGTH); + + if (pasted_panoID.length() < PANOID_LENGTH) + return; + + isStartingWithPanorama = true; + StartWithPanorama((const char*) pasted_panoID); +} + +/** + * Synchronize the contents of the combobox with the actual locations in the settings + */ +void MainFrame::RefillLocations() { + combobox->Clear(); + + combobox->Append(""); + for (unsigned int i = 0; i < settings.locations.size(); i++) + combobox->Append(settings.locations[i].name); + +} + +/** + * Fill the contents of the frame with an OpenGL canvas with the actual application. + * This method can be safely called from event threads from widgets that are about + * to be deleted through this call. + * + * @param pano_id + */ + void MainFrame::StartWithPanorama(const char* pano_id) { + wxCommandEvent event(myEVT_PANORAMA_START, GetId()); + event.SetString(pano_id); + wxPostEvent(this, event); +} + +/** + * Fill the frame with the mainscreen controls + */ +void MainFrame::ShowMain() { + wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); + + //Space above + sizer->AddStretchSpacer(1); + + //Horizontally centered title image + wxBoxSizer *titleSizer = new wxBoxSizer(wxHORIZONTAL); + titleSizer->AddStretchSpacer(1); + titleSizer->Add(new wxStaticBitmap(this, wxID_ANY, wxBitmap("title.png", wxBITMAP_TYPE_PNG))); + titleSizer->AddStretchSpacer(1); + sizer->Add(titleSizer, 0, wxEXPAND); + + //Some space between combobox and title + sizer->AddSpacer(30); + + //Combobox + combobox = new wxChoice(this, COMBOBOX_LOCATIONS, wxDefaultPosition, wxSize(300, -1)); + sizer->Add(combobox, 0, wxALIGN_CENTER | wxALL, 5); + RefillLocations(); + + //Buttons + wxBoxSizer *buttonsSizer = new wxBoxSizer(wxHORIZONTAL); + buttonsSizer->Add(new wxButton(this, BUTTON_EDIT_LOCATIONS, "Edit destinations")); + + if (strlen(settings.last_pano) > 0) { + buttonsSizer->AddSpacer(40); + buttonsSizer->Add(new wxButton(this, BUTTON_LAST_LOCATION, "Go to last location")); + } + sizer->Add(buttonsSizer, 0, wxALIGN_CENTER | wxALL, 5); + + //TextCtrl user can paste in + sizer->AddSpacer(50); + sizer->Add(new wxTextCtrl(this, TEXTBOX_PASTE_LINK, "Or paste a Google StreetView link here...", wxDefaultPosition, wxSize(400, 50), wxTE_CENTER), 0, wxALIGN_CENTER); + + //Space below + sizer->AddStretchSpacer(1); + + //Replace the (presumable) OpenGL canvas + ReplaceSizer(sizer); + glCanvas = NULL; + + //Event handlers + Connect(COMBOBOX_LOCATIONS, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler(MainFrame::OnSelectLocation)); + Connect(BUTTON_EDIT_LOCATIONS, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainFrame::OnEditLocations)); + Connect(BUTTON_LAST_LOCATION, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainFrame::OnLastLocation)); + Connect(TEXTBOX_PASTE_LINK, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(MainFrame::OnLinkPaste)); +} + +/** + * Delete the old contents and replace with the new + * @param sizer + */ +void MainFrame::ReplaceSizer(wxSizer *sizer) { + wxSizer *oldsizer = GetSizer(); + if (oldsizer != NULL) { + oldsizer->Clear(true); + } + this->SetSizer(sizer); + this->Layout(); +} diff --git a/src/MainFrame.h b/src/MainFrame.h new file mode 100644 index 0000000..718d6c2 --- /dev/null +++ b/src/MainFrame.h @@ -0,0 +1,47 @@ +/* + * File: Frames.h + * Author: paulwagener + * + * Created on 16 april 2011, 22:20 + */ + +#ifndef FRAMES_H +#define FRAMES_H + +#include "GLCanvas.h" +#include + +/** + * The main frame that houses the OpenGL context and the initial main screen + */ +class MainFrame : public wxFrame { +public: + MainFrame(); + +private: + GLCanvas *glCanvas; + wxChoice *combobox; + + bool isStartingWithPanorama; + + void OnGoToMainScreen(wxMenuEvent &event); + void OnAbout(wxMenuEvent& WXUNUSED(event)); + void OnPreferences(wxMenuEvent& WXUNUSED(event)); + void OnQuit(wxMenuEvent& WXUNUSED(event)); + + void OnSelectLocation(wxCommandEvent &event); + void OnEditLocations(wxCommandEvent &event); + void OnLastLocation(wxCommandEvent &event); + void OnLinkPaste(wxCommandEvent &event); + + void OnPanoramaStart(wxCommandEvent &event); + + void StartWithPanorama(const char* pano_id); + void ShowMain(); + + void ReplaceSizer(wxSizer *sizer); + void RefillLocations(); +}; + +#endif /* FRAMES_H */ + diff --git a/src/Panorama.cpp b/src/Panorama.cpp new file mode 100644 index 0000000..b26ef06 --- /dev/null +++ b/src/Panorama.cpp @@ -0,0 +1,759 @@ +/* + * File: Panorama.cpp + * Author: paulwagener + * + * Created on 25 maart 2011, 17:01 + */ + +#include "Panorama.h" +#include "string.h" +#include "stdio.h" +#include "base64.h" +#include +#include +#include +#include +#include "download.h" +#include "Utm.h" +#include "common.h" +#include "Settings.h" +#include + + +#include +using namespace std; + +/** + * Will load the specified panorama either from disk or from Google + * Depending on that it will take either a long time or a VERY long time. + * Do not attempt to make a Panorama in the main thread! + * + * @param pano_id + * @param zoom_level + */ +Panorama::Panorama(const char* pano_id, int zoom_level) +: zoom_level(zoom_level), +glLoaded(false), +compileList(-1), +opacity(0) { + if (strlen(pano_id) > PANOID_LENGTH) + throw "pano_id longer than allowed number of characters"; + + //Download cachefile if it doesn't exist yet + if (!isCached(pano_id, zoom_level)) + Panorama::downloadAndCache(pano_id, zoom_level); + + loadFromCache(pano_id, zoom_level); + + +} + +void Panorama::getCacheFilename(const char* pano_id, int zoom_level, char filename[]) { + sprintf(filename, "cache/%d-%s.pano", zoom_level, pano_id); +} + +/** + * Check if a Panorama happens to be linked from this one via a road + * @param pano_id + * @return + */ +bool Panorama::hasAdjacent(const char *pano_id) { + for (unsigned int i = 0; i < links.size(); i++) { + if (strcmp(pano_id, links[i].pano_id) == 0) + return true; + } + return false; +} + +/** + * returns the pano_id that is closest in the direction specified. + * Or NULL in the unlikely case that this panorama isn't adjacent to anything + * + * @param direction in the range [0-360] + * @return + */ +const char* Panorama::getPanoIdInDirection(float direction) { + if(links.size() == 0) + return NULL; + + float min_degrees_diff = 360; + char *min_panorama = NULL; + for(unsigned int i = 0; i < links.size(); i++) { + float diff = min( fabs(direction-links[i].yaw_deg), fabs(direction - (links[i].yaw_deg -360) )); + + if(diff < min_degrees_diff) { + min_degrees_diff = diff; + min_panorama = (char*)&links[i].pano_id; + } + } + return min_panorama; +} + +/** + * Find out if a panorama was downloaded before + * + * @param pano_id + * @param zoom_level + * @return + */ +bool Panorama::isCached(const char* pano_id, int zoom_level) { + //The cachefile name + char filename[CACHE_FILENAME_LENGTH]; + getCacheFilename(pano_id, zoom_level, filename); + + //Check if the cachefile exists + if (FILE * file = fopen(filename, "rb")) { + fclose(file); + return true; + } + return false; +} + +#include + +struct vec3 { + float x, y, z; +}; + +/** + * Check if a position on the map should be visible on screen. + * A position is visible if it meets one of the following criteria: + * - In the pano_map the position is indicated as to belong to this panorama + * (only if the pixel doesn't have infinite depth, like the sky) + * - The pixel is within the [startFull, endFull] range + * + * @param x + * @param y + * @return + */ +bool Panorama::isVisible(int x, int y) { + + //Make sure the pixel is on the map + if (x < 0) x += mapWidth; + if (y < 0) y += mapHeight; + if (x >= mapWidth) x -= mapWidth; + if (y >= mapHeight) y -= mapHeight; + + int panoIndex = panomapIndices[y * mapWidth + x]; + int depthMapIndex = depthmapIndices[y * mapWidth + x]; + + return panoIndex == ownPanomapIndex && depthMapIndex != 0; +} + +/** + * A vertex is transparant if in the 3x3 area + * around the position there is a pixel that is not drawn + * + * @param x + * @param y + * @return + */ +bool Panorama::isTransparant(int x, int y, int horizontal_step) { + if (y > 0 && y < mapHeight - 1) { + for (int _x = -horizontal_step; _x <= horizontal_step; _x += horizontal_step) { + for (int _y = -1; _y <= 1; _y++) { + if (!isVisible(x + _x, y + _y)) + return true; + + } + } + } + return false; +} + +void Panorama::drawActual(struct utmPosition referencePoint, bool drawAll, struct renderSettings settings) { + glPushMatrix(); + glTranslated(location.easting - referencePoint.easting, location.northing - referencePoint.northing, 0); + glRotated(180 - pano_yaw_deg, 0, 0, 1); + + glBindTexture(GL_TEXTURE_2D, texture_id); + //The environment is basically just a sphere with each vertex at a different distance creating + //The illusion that everything is organized into planes + + /** + * Draw the panorama in long vertical strips + */ + for (int x = 0; x < mapWidth - 1; x += settings.horizontalDetail) { + //printf("%d ", x); + bool drawing = false; + int currentDepthMap = 0; + int currentDepthMap2 = 0; + + const int next_x = x + settings.horizontalDetail; + int map_next_x = next_x; + if (map_next_x >= mapWidth) + map_next_x -= mapWidth; + + //Start drawing immediately at the zenith in the sky + if (drawAll) { + drawing = true; + + glBegin(GL_QUAD_STRIP); + drawVertexAtAzimuthElevation(next_x, 0, settings); + drawVertexAtAzimuthElevation(x, 0, settings); + } + const unsigned int endHeight = mapHeight - 1; + + for (unsigned int y = 0; y < endHeight; y += 1) { + + const int next_y = y + 1; + + //Stop drawing + if (!drawAll && drawing && (!isVisible(x, y) || !isVisible(x + 1, y))) { + + drawing = false; + + drawVertexAtAzimuthElevation(next_x, y, settings); + drawVertexAtAzimuthElevation(x, y, settings); + glEnd(); + } + + /** + * If the depth map changes ensure that extra vertices are placed to create a nice sharp + * edge between planes. + */ + const int depthMapIndex = depthmapIndices[(y + 1) * mapWidth + x]; + const int depthMapIndex2 = depthmapIndices[(y + 1) * mapWidth + map_next_x]; + + if (drawing && (depthMapIndex != currentDepthMap || depthMapIndex2 != currentDepthMap2)) { + currentDepthMap = depthMapIndex; + currentDepthMap2 = depthMapIndex2; + + drawVertexAtAzimuthElevation(next_x, y, settings); + drawVertexAtAzimuthElevation(x, y, settings); + + drawVertexAtAzimuthElevation(next_x, next_y, settings); + drawVertexAtAzimuthElevation(x, next_y, settings); + + //Draw extra perspective corrective lines + } else if (drawing && (y % settings.verticalAccuracy) == 0) { + + drawVertexAtAzimuthElevation(next_x, y, settings); + drawVertexAtAzimuthElevation(x, y, settings); + + //Draw extra lines for transparancy to flow nicely + } else if (!drawAll && drawing && (isTransparant(x, y - 1, settings.horizontalDetail) || isTransparant(next_x, y - 1, settings.horizontalDetail))) { + drawVertexAtAzimuthElevation(next_x, y, settings); + drawVertexAtAzimuthElevation(x, y, settings); + }//Start drawing + else if (!drawing && isVisible(x, y) && isVisible(x + 1, y)) { + drawing = true; + + glBegin(GL_QUAD_STRIP); + + currentDepthMap = depthMapIndex; + currentDepthMap2 = depthMapIndex2; + + drawVertexAtAzimuthElevation(next_x, y, settings); + drawVertexAtAzimuthElevation(x, y, settings); + } + + //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + + + } + //Draw last vertex at the nadir / very bottom of panorama + if (drawing) { + drawVertexAtAzimuthElevation(next_x, endHeight, settings); + drawVertexAtAzimuthElevation(x, endHeight, settings); + + glEnd(); + } + + } + + glPopMatrix(); +} + +/** + * Draws the panorama, the real drawing is in the other draw function. This just tries to create a display + * list out of it as aggresively as it can. If parameters or settings change we are forced to generate a new + * list. + * + * @param referencePoint + * @param startFull + * @param endFull + */ +void Panorama::draw(struct utmPosition referencePoint, bool drawAll) { + /** + * Transparant borders are the fuzzy edges of the panorama. Due to the use of display + * lists it is impossible to both have a continually increasing opacity AND transparant borders. + * + * We solve this in a two step process: during the fade-in sequence we set transparancy globally, + * no glColor calls are allowed in the display list that could corrupt the global transparancy. + * Once faded in we enable the transparant borders and allow glColor calls in the display list + */ + bool transparantBorders = true; + bool settingsChanged = false; + bool firstTime = false; + + //Do the fade in sequence + if (opacity < 1) { + transparantBorders = false; + glColor4f(1, 1, 1, opacity); + } + + if (compileList == -1) { + compileList = glGenLists(1); + threeSixtyCompileList = glGenLists(1); + firstTime = true; + } + + //If any setting changed from the last time we compiled the list we need to recompile it with the new settings + if (compiledRenderSettings.transparantBorders != transparantBorders + || compiledRenderSettings.horizontalDetail != settings.horizontal_accuracy + || compiledRenderSettings.verticalAccuracy != settings.vertical_accuracy) { + settingsChanged = true; + } + + //Recreate the display lists + if (firstTime || settingsChanged) { + struct renderSettings renderSettings; + renderSettings.transparantBorders = transparantBorders; + renderSettings.horizontalDetail = settings.horizontal_accuracy; + renderSettings.verticalAccuracy = settings.vertical_accuracy; + + //Recreate display list for 'only own data' panorama + glNewList(compileList, GL_COMPILE); + drawActual(referencePoint, false, renderSettings); + glEndList(); + + //Recreate display list for 'all data' panorama + //360 panorama only needs to be rendered the first time or when the actual settings changed + if (firstTime || compiledRenderSettings.horizontalDetail != settings.horizontal_accuracy || + compiledRenderSettings.verticalAccuracy != settings.vertical_accuracy) { + struct renderSettings threeSixtyRenderSettings = renderSettings; + threeSixtyRenderSettings.transparantBorders = false; + + glNewList(threeSixtyCompileList, GL_COMPILE); + drawActual(referencePoint, true, threeSixtyRenderSettings); + glEndList(); + } + + compiledRenderSettings = renderSettings; + } + + //Draw the cached version + if (drawAll) { + glCallList(threeSixtyCompileList); + } else { + glCallList(compileList); + } + + + + + +} + +/** + * Draw a single vertex at a specific coordinate on the sphere. + */ +void Panorama::drawVertexAtAzimuthElevation(int x, int y, struct renderSettings settings) { + //Set vertex transparancy + if (settings.transparantBorders) { + glColor4d(1, 1, 1, isTransparant(x, y, settings.horizontalDetail) ? 0 : 1); + } + + float rad_azimuth = x / (float) (mapWidth - 1.0f) * TWICE_PI; + float rad_elevation = y / (float) (mapHeight - 1.0f) * PI; + + //Calculate the cartesian position of this vertex (if it was at unit distance) + vec3 xyz; + xyz.x = sin(rad_elevation) * sin(rad_azimuth); + xyz.y = sin(rad_elevation) * cos(rad_azimuth); + xyz.z = cos(rad_elevation); + float distance = 1; + + //Value that is safe to use to retrieve stuff from the index arrays + const int map_x = x % mapWidth; + + //Calculate distance of point according to the depth map data. + int depthMapIndex = depthmapIndices[y * mapWidth + map_x]; + if (depthMapIndex == 0) { + //Distance of sky + distance = 100; + + } else { + struct depthMapPlane plane = depthmapPlanes[depthMapIndex]; + distance = -plane.d / (plane.x * xyz.x + plane.y * xyz.y + -plane.z * xyz.z); + } + + //Draw point! + glTexCoord2d(x / (float) mapWidth, y / (float) mapHeight); + glVertex3f(xyz.x * distance, xyz.y*distance, xyz.z * distance); +} + +/** + * Loads the panorama from a file as was downloaded from downloadAndCache() + * This method assumes an OpenGL context to be present! + * + * @param pano_id + * @param zoom_level + */ +void Panorama::loadFromCache(const char *pano_id, int zoom_level) { + char cachefile[CACHE_FILENAME_LENGTH]; + getCacheFilename(pano_id, zoom_level, cachefile); + + FILE *f = fopen(cachefile, "rb"); + int xmlOffset, imageOffset; + fread(&xmlOffset, sizeof (xmlOffset), 1, f); + fread(&imageOffset, sizeof (imageOffset), 1, f); + + //Read in XML data + { + fseek(f, xmlOffset, SEEK_SET); + int xmlSize; + fread(&xmlSize, sizeof (xmlSize), 1, f); + + std::vector xmlData(xmlSize); + fread(&xmlData[0], xmlSize, 1, f); + loadXML((const char*)&xmlData[0]); + } + + //Read in image + { + fseek(f, imageOffset, SEEK_SET); + + //Image data + long compressedImageSize; + unsigned int width, height; + fread(&width, sizeof (width), 1, f); + fread(&height, sizeof (height), 1, f); + fread(&compressedImageSize, sizeof (compressedImageSize), 1, f); + + //Read in image + vector compressed_image(compressedImageSize); + fread(&compressed_image[0], compressedImageSize, 1, f); + + //That was the last thing to be read from the file + fclose(f); + + //Uncompress image + unsigned long size = width * height * 3; + uncompressed_image.reserve(size); + texture_width = width; + texture_height = height; + int code = uncompress(&uncompressed_image[0], &size, &compressed_image[0], compressedImageSize); + + if (code != Z_OK) + throw "Unable to uncompress image texture"; + + if (size != width * height * 3) + throw "Unexpected size of uncompressed image"; + } + fclose(f); + /** + * File format: + * int xmlOffset + * int imageOffset + * + * at xmlOffset: + * int size of xml contents + * xml contents of Google panorama description (not \0 terminated) + * + * at imageOffset: + * int width + * int height + * long size size of data to follow + * RGB raw image ( compressed with standard zlib compress() ) + */ +} + +/** + * Load all information from the contents of a url like + * http://maps.google.com/cbk?output=xml&panoid=qdAqxSu085_gynN0R8k4RA&dm=1&pm=1 + * + * @param xml + */ +void Panorama::loadXML(const char *xml) { + //Not sure who the culprit is, but somewhere out there someone changes the locale away from the + //portable "C" locale, I'm suspecting wxWidgets is to blame. + //This has the side effect that floats don't parse anymore because sscanf() expects + //they have comma's instead of dots as the decimal operator + setlocale(LC_ALL, "C"); + + //Scan the data properties + sscanf(strstr(xml, "", + &image_width, &image_height, &tile_width, &tile_height, (char*) &pano_id, &num_zoom_levels, &lat, &lng, &original_lat, &original_lng); + + //Scan the projection properties + sscanf(strstr(xml, " data (just the yaw and id part) + struct link link; + const char *linkString = xml; + while ((linkString = strstr(linkString + 1, ""); + const char* end = strstr(xml, ""); + if (begin == NULL || end == NULL) + throw "No depth map information found in xml data"; + + depth_map_base64 = std::string(begin + strlen(""), end); + } + + //Decode base64 + vector depth_map_compressed(depth_map_base64.length()); + int compressed_length = decode_base64(&depth_map_compressed[0], &depth_map_base64[0]); + + //Uncompress data with zlib + unsigned long length = 512 * 256 + 5000; //TODO: decompress in a loop so we can accept any size + vector depth_map(length); + int zlib_return = uncompress(&depth_map[0], &length, &depth_map_compressed[0], compressed_length); + if (zlib_return != Z_OK) + throw "zlib decompression of the depth map failed"; + + //Load standard data + const int headersize = depth_map[0]; + const int numPanos = depth_map[1] | (depth_map[2] << 8); + mapWidth = depth_map[3] | (depth_map[4] << 8); + mapHeight = depth_map[5] | (depth_map[6] << 8); + const int panoIndicesOffset = depth_map[7]; + + if (headersize != 8 || panoIndicesOffset != 8) + throw "Unexpected depth map header"; + + //Load depthMapIndices + depthmapIndices = vector(mapHeight * mapWidth); + memcpy(&depthmapIndices[0], &depth_map[panoIndicesOffset], mapHeight * mapWidth); + + //Load depthMapPlanes + depthmapPlanes = vector (numPanos); + memcpy(&depthmapPlanes[0], &depth_map[panoIndicesOffset + mapHeight * mapWidth], numPanos * sizeof (struct depthMapPlane)); + } + + + LatLonToUtmWGS84(this->location, this->original_lat, this->original_lng); + + //Decode the pano map + { + //Get the base64 encoded data + std::string pano_map_base64; + { + const char* begin = strstr(xml, ""); + const char* end = strstr(xml, ""); + if (begin == NULL || end == NULL) + throw "No pano map information found in xml data"; + + pano_map_base64 = std::string(begin + strlen(""), end); + } + + //Decode base64 + vector pano_map_compressed(pano_map_base64.length()); + int compressed_length = decode_base64(&pano_map_compressed[0], pano_map_base64.c_str()); + + //Uncompress data with zlib + unsigned long length = mapWidth * mapHeight + 5000; + vector pano_map(length); + int zlib_return = uncompress(&pano_map[0], &length, &pano_map_compressed[0], compressed_length); + if (zlib_return != Z_OK) + throw "zlib decompression of the pano map failed"; + + //Load standard data + const int headersize = pano_map[0]; + const int numPanos = pano_map[1] | (pano_map[2] << 8); + const int panomapWidth = pano_map[3] | (pano_map[4] << 8); + const int panomapHeight = pano_map[5] | (pano_map[6] << 8); + const int panoIndicesOffset = pano_map[7]; + + if (headersize != 8 || panoIndicesOffset != 8 + || panomapWidth != mapWidth || panomapHeight != mapHeight) + throw "Unexpected pano map header"; + + //Load panomap indices + panomapIndices = vector(mapWidth * mapHeight); + memcpy(&panomapIndices[0], &pano_map[panoIndicesOffset], mapWidth * mapHeight); + + //Load pano-ids + const int panoid_offset = panoIndicesOffset + mapWidth*mapHeight; + memset(&panoids, '\0', (PANOID_LENGTH + 1) * numPanos); //Makes sure all strings are properly \0 terminated + for (int i = 0; i < numPanos; i++) { + memcpy(&panoids[i], &pano_map[panoid_offset + PANOID_LENGTH * i], PANOID_LENGTH); + + //All panomap indices have some pixels that refer to themselves, + //Usually the index is 1 or 2 (0 is reserved for sky and other infinite depth spots) + if (memcmp(&panoids[i], &pano_id, PANOID_LENGTH + 1) == 0) + ownPanomapIndex = i + 1; + } + } + +} + +/** + * Initialize this panorama with data straight from Google. + * This method also caches the data to a file called 'cache/.pano' + * @return + */ +void Panorama::downloadAndCache(const char* pano_id, int zoom_level) { + const unsigned int TILE_HEIGHT = 512; + const unsigned int TILE_WIDTH = 512; + + //If you ask for an image of zoom level 0 you get an image of this dimension. + //(actually bigger but the image wraps horizontally and is padded with black vertically) + const int ZOOM0_WIDTH = 416; + const int ZOOM0_HEIGHT = 208; + + //Dimensions of the image at a particular zoom_level + const int width = ZOOM0_WIDTH * 1 << zoom_level; + const int height = ZOOM0_HEIGHT * 1 << zoom_level; + + //Create texture + vector pano_image; + pano_image.reserve(width * height * 3); + + //Download each individual tile and compose them into the big texture + for (int tile_y = 0; tile_y < ceil(height / (float) TILE_HEIGHT); tile_y++) { + for (int tile_x = 0; tile_x < ceil(width / (float) TILE_WIDTH); tile_x++) { + + //Download tile in RGB format + char url[100]; + sprintf((char*) &url, "http://cbk0.google.com/cbk?output=tile&panoid=%s&zoom=%d&x=%d&y=%d", pano_id, zoom_level, tile_x, tile_y); + struct image_block tile = download_jpeg(url); + + if (tile.width != TILE_WIDTH || + tile.height != TILE_HEIGHT) { + throw "Downloaded tile had unexpected dimensions"; + } + + //Copy each pixel from the tile to the global texture + for (unsigned int y = 0; y < tile.height; y++) { + for (unsigned int x = 0; x < tile.width; x++) { + + const int global_x = tile_x * tile.width + x; + const int global_y = tile_y * tile.height + y; + + if (global_x >= width || global_y >= height) + continue; + + pano_image[(global_y * width + global_x)*3 + 0] = tile.data[(y * tile.width + x)*3 + 0]; + pano_image[(global_y * width + global_x)*3 + 1] = tile.data[(y * tile.width + x)*3 + 1]; + pano_image[(global_y * width + global_x)*3 + 2] = tile.data[(y * tile.width + x)*3 + 2]; + } + } + + free(tile.data); + } + } + + //Cache the panorama to a file + { + char cachefile[CACHE_FILENAME_LENGTH]; + getCacheFilename(pano_id, zoom_level, cachefile); + + FILE *f = fopen(cachefile, "wb"); + + if (!f) + throw "Unable to open cache file"; + + //Write offsets + const int xmlOffset = 2 * sizeof (int); //xml data begins after 2 int offsets + fwrite(&xmlOffset, sizeof (xmlOffset), 1, f); + + //Download xml data + char xml_url[100]; + sprintf(xml_url, "http://maps.google.com/cbk?output=xml&panoid=%s&dm=1&pm=1", pano_id); + const std::auto_ptr > xmlData = download(xml_url); + + int imageOffset = 3 * sizeof (int) +xmlData->size(); //Image begins after 3 int offsets plus the size of the xmlData + fwrite(&imageOffset, sizeof (imageOffset), 1, f); + + //Write xml data + { + assert(xmlData->size() < (1ULL << 32)); + const int size = xmlData->size(); + fwrite(&size, sizeof (size), 1, f); + fwrite(&(*xmlData)[0], 1, xmlData->size(), f); + } + + //Write the image data + fwrite(&width, sizeof (width), 1, f); + fwrite(&height, sizeof (height), 1, f); + + + //Compress the image and write it to the file + vector compressed_pano; + compressed_pano.reserve(compressBound(height * width * 3)); + + unsigned long size = height * width * 3; + + compress(&compressed_pano[0], &size, (const Bytef*) &pano_image[0], size); + + fwrite(&size, sizeof (size), 1, f); + fwrite(&compressed_pano[0], size, 1, f); + + //Close stuff that is open + fclose(f); + } + +} + +/** + * Find out how far a position is from this panorama + + * @return distance in meters + */ +float Panorama::distanceTo(struct utmPosition location) { + float horizontalDist = (float)fabs(location.easting - this->location.easting); + float verticalDist = (float)fabs(location.northing - this->location.northing); + + return sqrt(horizontalDist * horizontalDist + verticalDist * verticalDist); +} + +/** + * Function to be called when everything is loaded up in memory ready to go into OpenGL. + * It is designed to do as little processing / loading as possible so it is safe to call in the + * main draw loop. It assumes that the texture has already been put into the uncompressed_image field. + */ +void Panorama::loadGL() { + //Create texture + glGenTextures(1, &texture_id); + glBindTexture(GL_TEXTURE_2D, texture_id); + + //Choose between mipmapping (better quality) and linear interpolation (faster) + if (settings.mipmapping) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); + gluBuild2DMipmaps(GL_TEXTURE_2D, 3, + texture_width, texture_height, + GL_RGB, GL_UNSIGNED_BYTE, &uncompressed_image[0]); + + } else { + const int level = 0; //Mipmap level, must be zero if we want to display anything in GL_LINEAR mode + const int border = 0; //???? + const int channels = 3; //Only load the 3 primary color: red, green, blue + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, level, channels, texture_width, texture_height, border, GL_RGB, GL_UNSIGNED_BYTE, &uncompressed_image[0]); + } + uncompressed_image.clear(); + + glLoaded = true; +} + +Panorama::~Panorama() { + //Delete resources we allocated in OpenGL + if (glLoaded) { + glDeleteTextures(1, &texture_id); + + if (compileList != -1) { + glDeleteLists(compileList, 1); + glDeleteLists(threeSixtyCompileList, 1); + } + } +} \ No newline at end of file diff --git a/src/Panorama.h b/src/Panorama.h new file mode 100644 index 0000000..7f48eaa --- /dev/null +++ b/src/Panorama.h @@ -0,0 +1,107 @@ +/* + * File: Panorama.h + * Author: paulwagener + * + * Created on 25 maart 2011, 17:01 + */ + +#ifndef PANORAMA_H +#define PANORAMA_H + +#include +#include "gl.h" +#include "Utm.h" + +using namespace std; + +#define CACHE_FILENAME_LENGTH 100 +#define PANOID_LENGTH 22 + +struct depthMapPlane { + float x, y, z; + float d; +}; + +struct xy { + int x, y; +}; + +struct link { + float yaw_deg; + char pano_id[PANOID_LENGTH+1]; +}; + +struct renderSettings { + bool transparantBorders; + int verticalAccuracy; + int horizontalDetail; +}; + +class Panorama { +public: + bool isTransparant(int x, int y, int horizontal_step); + bool isVisible(int x, int y); + vector uncompressed_image; + void loadGL(); + int zoom_level; + bool glLoaded; + + int compileList; + int threeSixtyCompileList; + + struct renderSettings compiledRenderSettings; + + float opacity; + + int image_width, image_height; + int tile_width, tile_height; + char pano_id[PANOID_LENGTH+1]; + int num_zoom_levels; + float lat, lng, original_lat, original_lng; + float pano_yaw_deg, tilt_yaw_deg, tilt_pitch_deg; + + bool hasAdjacent(const char *pano_id); + const char* getPanoIdInDirection(float direction); + + //Depth map information + int mapWidth, mapHeight; + vector depthmapIndices; + vector depthmapPlanes; + + //Panomap information + vector panomapIndices; + char panoids[200][PANOID_LENGTH+1]; + int ownPanomapIndex; + + vector links; + + //Texture information + unsigned int texture_width, texture_height; + GLuint texture_id; + + //Position in internal (UTM) coordinate system + struct utmPosition location; + + float startFull; + float endFull; + int rank; +public: + Panorama(const char* xml, int zoom_level); + ~Panorama(); + + static void getCacheFilename(const char* pano_id, int zoom_level, char filename[]); + static bool isCached(const char *pano_id, int zoom_level); + void draw(struct utmPosition referencePosition, bool drawAll=false); + void drawActual(struct utmPosition referencePoint, bool drawAll, struct renderSettings settings); + float distanceTo(struct utmPosition location); + +public: + void drawVertexAtAzimuthElevation(int x, int y, struct renderSettings settings); + + static void downloadAndCache(const char *pano_id, int zoom_level); + void loadFromCache(const char* pano_id, int zoom_level); + void loadXML(const char* url); +}; + +#endif /* PANORAMA_H */ + diff --git a/src/Player.cpp b/src/Player.cpp new file mode 100644 index 0000000..d753300 --- /dev/null +++ b/src/Player.cpp @@ -0,0 +1,208 @@ +#include "Player.h" +#include "gl.h" +#include "common.h" + +#include + +Player::Player() { + keys.forward = false; + keys.backward = false; + keys.rotate_left = false; + keys.rotate_right = false; + keys.strafe_left = false; + keys.strafe_right = false; +} + +void Player::moveMouse(int x, int y) { + rotation += x / (float) 4; + cam.elevation += y / (float) 4; + + const float MAX_CAM_ELEVATION = 90; + const float MIN_CAM_ELEVATION = -90; + if (cam.elevation > MAX_CAM_ELEVATION) + cam.elevation = MAX_CAM_ELEVATION; + if (cam.elevation < MIN_CAM_ELEVATION) + cam.elevation = MIN_CAM_ELEVATION; +} + +#include + +void Player::initializeLocation(struct utmPosition position) { + target_location = position; + location = position; + cam.location = position; + + cam.elevation = 5; + cam.distance = 12; + cam.rotation = 0; + cam.x = 0; + cam.y = 0; + cam.z = 0; + + rotation = 0; + height = -3; +} + +void Player::targetCamera(struct utmPosition reference) { + //Set the camera behind the player + cam.location = location; + cam.location.northing = location.northing + cos((180 + rotation) * RADIAL) * cos(cam.elevation * RADIAL) * cam.distance; + cam.location.easting = location.easting + sin((180 + rotation) * RADIAL) * cos(cam.elevation * RADIAL) * cam.distance; + + //Work out the camera position in local OpenGL coordinates + const float target_x = (float)(cam.location.easting - reference.easting); + const float target_y = (float)(cam.location.northing - reference.northing); + float target_z = sin(cam.elevation * RADIAL) * cam.distance + height; + + //Skimmy along the ground if the camera is looking up + if (target_z < height + 1) + target_z = height + 1; + + //Pivot camera around player location + cam.x = target_x + (cam.x - target_x) / 1.2f; + cam.y = target_y + (cam.y - target_y) / 1.1f; + cam.z = target_z + (cam.z - target_z) / 1.1f; + + gluLookAt(cam.x, cam.y, cam.z, + location.easting - reference.easting, location.northing - reference.northing, height + 4, //Look above the player's head + 0, 0, 1); + +} + +void Player::updatePosition() { + const float FORWARD_SPEED = 0.18f; + const float ROTATION_SPEED = 2.0f; + + /** + * Update player position + */ + float forward_speed = 0; + float right_speed = 0; + if (keys.forward) forward_speed = FORWARD_SPEED; + if (keys.backward) forward_speed = -FORWARD_SPEED; + if (keys.strafe_right) right_speed = FORWARD_SPEED; + if (keys.strafe_left) right_speed = -FORWARD_SPEED; + if (keys.rotate_left) rotation -= ROTATION_SPEED; + if (keys.rotate_right) rotation += ROTATION_SPEED; + + + target_location.northing += cos(rotation * RADIAL) * forward_speed; + target_location.easting += sin(rotation * RADIAL) * forward_speed; + + target_location.northing += cos((rotation + 90) * RADIAL) * right_speed; + target_location.easting += sin((rotation + 90) * RADIAL) * right_speed; + + + //Go to actual player location + location.easting = target_location.easting + (location.easting - target_location.easting) / 1.2; + location.northing = target_location.northing + (location.northing - target_location.northing) / 1.2; + +} + +void Player::drawBox(const float width, const float depth, const float height) { + const float half_height = height / 2; + const float half_width = width / 2; + const float half_depth = depth / 2; + + glBegin(GL_QUADS); + + //Bottom + glNormal3f(0, 0, -1); + glVertex3f(-half_width, -half_depth, -half_height); + glVertex3f(-half_width, half_depth, -half_height); + glVertex3f(half_width, half_depth, -half_height); + glVertex3f(half_width, -half_depth, -half_height); + + //Top + glNormal3f(0, 0, 1); + glVertex3f(half_width, -half_depth, half_height); + glVertex3f(half_width, half_depth, half_height); + glVertex3f(-half_width, half_depth, half_height); + glVertex3f(-half_width, -half_depth, half_height); + + //Left + glNormal3f(-1, 0, 0); + glVertex3f(-half_width, -half_depth, half_height); + glVertex3f(-half_width, half_depth, half_height); + glVertex3f(-half_width, half_depth, -half_height); + glVertex3f(-half_width, -half_depth, -half_height); + + //Right + glNormal3f(1, 0, 0); + glVertex3f(half_width, -half_depth, -half_height); + glVertex3f(half_width, half_depth, -half_height); + glVertex3f(half_width, half_depth, half_height); + glVertex3f(half_width, -half_depth, half_height); + + //Back + glNormal3f(0, -1, 0); + glVertex3f(-half_width, -half_depth, -half_height); + glVertex3f(half_width, -half_depth, -half_height); + glVertex3f(half_width, -half_depth, half_height); + glVertex3f(-half_width, -half_depth, half_height); + + //Front + glNormal3f(0, 1, 0); + glVertex3f(-half_width, half_depth, half_height); + glVertex3f(half_width, half_depth, half_height); + glVertex3f(half_width, half_depth, -half_height); + glVertex3f(-half_width, half_depth, -half_height); + + glEnd(); +} + +/** + * Draw the player representation + * It is supposed to look like the orange streetview guy that you drag across the map + */ +void Player::drawPlayer(struct utmPosition reference) { + + glPushMatrix(); + glTranslated(location.easting - reference.easting, location.northing - reference.northing, height); + glRotatef(rotation, 0, 0, -1); + + glEnable(GL_LIGHTING); + glEnable(GL_COLOR_MATERIAL); + glDisable(GL_TEXTURE_2D); + glColor4d(1, 0.6, 0, 0.8); //Streetview Orange + + const float SHOULDER_HEIGHT = 1.6f; + const float BODY_WIDTH = 0.6f; + const float BODY_DEPTH = 0.4f; + const float HEAD_RADIUS = 0.3f; + const float ARM_WIDTH = 0.2f; + const float ARM_LENGTH = 0.85f; + const float RAYMAN_LENGTH = 0.05f; + + glPushMatrix(); + { + //Body + glTranslatef(0, 0, SHOULDER_HEIGHT / 2); + drawBox(BODY_WIDTH, BODY_DEPTH, SHOULDER_HEIGHT); + + //Head + glTranslatef(0, 0, SHOULDER_HEIGHT / 2 + RAYMAN_LENGTH + HEAD_RADIUS); + glutSolidSphere(HEAD_RADIUS, 8, 8); + } + glPopMatrix(); + + glPushMatrix(); + { + //Right arm + glTranslatef(RAYMAN_LENGTH + (BODY_WIDTH / 2) + (ARM_WIDTH / 2), 0, SHOULDER_HEIGHT - (ARM_LENGTH / 2)); + drawBox(ARM_WIDTH, BODY_DEPTH, ARM_LENGTH); + } + glPopMatrix(); + + glPushMatrix(); + { + //Left arm + glTranslatef(-RAYMAN_LENGTH - (BODY_WIDTH / 2) - (ARM_WIDTH / 2), 0, SHOULDER_HEIGHT - (ARM_LENGTH / 2)); + drawBox(ARM_WIDTH, BODY_DEPTH, ARM_LENGTH); + } + glPopMatrix(); + + + glPopMatrix(); + glDisable(GL_LIGHTING); +} \ No newline at end of file diff --git a/src/Player.h b/src/Player.h new file mode 100644 index 0000000..20ceca7 --- /dev/null +++ b/src/Player.h @@ -0,0 +1,54 @@ +/* + * File: Player.h + * Author: paulwagener + * + * Created on 9 april 2011, 13:17 + */ + +#include "Utm.h" + +#ifndef PLAYER_H +#define PLAYER_H + +class Player { +public: + + struct keys { + bool forward; + bool backward; + bool strafe_left; + bool strafe_right; + bool rotate_left; + bool rotate_right; + } keys; + + //Player position + struct utmPosition target_location; + struct utmPosition location; + float rotation; + float height; + + //Camera position + struct cam { + struct utmPosition location; + float elevation; + float distance; + float rotation; + float x,y,z; + } cam; + + + Player(); + void drawBox(const float width, const float depth, const float height); + void drawPlayer(struct utmPosition reference); + void moveMouse(int x, int y); + void initializeLocation(struct utmPosition position); + void updatePosition(); + + void targetCamera(struct utmPosition reference); +private: + +}; + +#endif /* PLAYER_H */ + diff --git a/src/PreferencesDialog.cpp b/src/PreferencesDialog.cpp new file mode 100644 index 0000000..dd7624b --- /dev/null +++ b/src/PreferencesDialog.cpp @@ -0,0 +1,88 @@ +/* + * File: PreferencesDialog.cpp + * Author: paulwagener + * + * Created on 24 april 2011, 14:56 + */ + +#include "PreferencesDialog.h" +#include "Settings.h" + +#define VERTICAL_ACC_FASTEST 100 +#define VERTICAL_ACC_BEST 1 + +#define HORIZONTAL_ACC_FASTEST 32 +#define HORIZONTAL_ACC_BEST 1 + +#define NUMBER_OF_PANO_FASTEST 1 +#define NUMBER_OF_PANO_BEST 20 + +enum { + SLIDER_VERTICAL_ACC = wxID_HIGHEST, + SLIDER_HORIZONTAL_ACC, + SLIDER_NUMBER_OF_PANO, +}; + +/** + * Create the preferences dialog and fill it with sliders + * + * @param parent + */ +PreferencesDialog::PreferencesDialog(wxWindow *parent) + : wxDialog(parent, wxID_ANY, "Preferences", wxDefaultPosition, wxSize(400, 200), wxCAPTION | wxCLOSE_BOX | wxFRAME_FLOAT_ON_PARENT){ + wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL); + wxFlexGridSizer *fgs = new wxFlexGridSizer(3, 2, 9, 15); + + fgs->Add(new wxStaticText(this, -1, "Vertical texture accuracy")); + fgs->Add(new wxSlider(this, SLIDER_VERTICAL_ACC, settings.vertical_accuracy, VERTICAL_ACC_BEST, VERTICAL_ACC_FASTEST, wxDefaultPosition, wxDefaultSize, wxSL_LABELS | wxSL_INVERSE), 1, wxEXPAND); + + fgs->Add(new wxStaticText(this, -1, "Horizontal model accuracy")); + fgs->Add(new wxSlider(this, SLIDER_HORIZONTAL_ACC, settings.horizontal_accuracy, HORIZONTAL_ACC_BEST, HORIZONTAL_ACC_FASTEST, wxDefaultPosition, wxDefaultSize, wxSL_LABELS | wxSL_INVERSE), 1, wxEXPAND); + + fgs->Add(new wxStaticText(this, -1, "Number of panorama's")); + fgs->Add(new wxSlider(this, SLIDER_NUMBER_OF_PANO, settings.numPanoramas, NUMBER_OF_PANO_FASTEST, NUMBER_OF_PANO_BEST, wxDefaultPosition, wxDefaultSize, wxSL_LABELS), 1, wxEXPAND); + + //Let sliders fill up all available space in the window + fgs->AddGrowableCol(1, 1); + fgs->AddGrowableRow(0, 1); + fgs->AddGrowableRow(1, 1); + fgs->AddGrowableRow(2, 1); + + hbox->Add(fgs, 1, wxALL | wxEXPAND, 5); + this->SetSizer(hbox); + + //Add event handlers + Connect(SLIDER_VERTICAL_ACC, wxEVT_SCROLL_THUMBTRACK, wxScrollEventHandler(PreferencesDialog::OnScroll)); + Connect(SLIDER_HORIZONTAL_ACC, wxEVT_SCROLL_THUMBTRACK, wxScrollEventHandler(PreferencesDialog::OnScroll)); + Connect(SLIDER_NUMBER_OF_PANO, wxEVT_SCROLL_THUMBTRACK, wxScrollEventHandler(PreferencesDialog::OnScroll)); +} + +/** + * Save the settings when the dialog closes + */ +PreferencesDialog::~PreferencesDialog() { + settings.saveSettings(); +} + +/** + * Adjust the settings as the user slides the slider + * All sliders use this same callback. + * + * @param event + */ +void PreferencesDialog::OnScroll(wxScrollEvent& event) { + switch (event.GetId()) { + case SLIDER_HORIZONTAL_ACC: + settings.horizontal_accuracy = event.GetPosition(); + break; + + case SLIDER_VERTICAL_ACC: + settings.vertical_accuracy = event.GetPosition(); + break; + + case SLIDER_NUMBER_OF_PANO: + settings.numPanoramas = event.GetPosition(); + break; + } +} + diff --git a/src/PreferencesDialog.h b/src/PreferencesDialog.h new file mode 100644 index 0000000..cfb8d39 --- /dev/null +++ b/src/PreferencesDialog.h @@ -0,0 +1,22 @@ +/* + * File: PreferencesDialog.h + * Author: paulwagener + * + * Created on 24 april 2011, 14:56 + */ + +#ifndef PREFERENCESDIALOG_H +#define PREFERENCESDIALOG_H + +#include + +class PreferencesDialog : public wxDialog { +public: + PreferencesDialog(wxWindow *parent); + ~PreferencesDialog(); +private: + void OnScroll(wxScrollEvent& event); +}; + +#endif /* PREFERENCESDIALOG_H */ + diff --git a/src/Settings.cpp b/src/Settings.cpp new file mode 100644 index 0000000..d67b191 --- /dev/null +++ b/src/Settings.cpp @@ -0,0 +1,60 @@ +/* + * File: Settings.cpp + * Author: paulwagener + * + * Created on 23 april 2011, 17:11 + */ + +#include "Settings.h" +#include +Settings settings; + +/** + * Load settings + */ +Settings::Settings() { + //Default settings + horizontal_accuracy = 16; + vertical_accuracy = 10; + numPanoramas = 5; + zoomLevel = 2; + wireframe = false; + mipmapping = false; + last_pano[0] = '\0'; + + //Load settings (if they exist) + FILE *f = fopen("settings.txt", "r"); + if (f) { + fscanf(f, "last-location = %s\n", (char*)&last_pano); + fscanf(f, "vertical-acc = %d\n", &vertical_accuracy); + fscanf(f, "horizontal-acc = %d\n", &horizontal_accuracy); + fscanf(f, "num-panos = %d\n", &numPanoramas); + + //Read in the user defined locations + struct pano_location l; + while (fscanf(f, "location = %22[a-zA-Z0-90_-] %99[^\n]\n", (char*) &l.pano_id, (char*) &l.name) > 0) { + locations.push_back(l); + } + fclose(f); + + } +} + +/** + * Save settings + */ +void Settings::saveSettings() { + FILE *f = fopen("settings.txt", "w"); + + if(last_pano[0] != '\0') + fprintf(f, "last-location = %s\n", last_pano); + + fprintf(f, "vertical-acc = %d\n", vertical_accuracy); + fprintf(f, "horizontal-acc = %d\n", horizontal_accuracy); + fprintf(f, "num-panos = %d\n", numPanoramas); + + for(unsigned int i = 0; i < locations.size(); i++) + fprintf(f, "location = %s %s\n", locations[i].pano_id, locations[i].name); + + fclose(f); +} diff --git a/src/Settings.h b/src/Settings.h new file mode 100644 index 0000000..b9b1a6c --- /dev/null +++ b/src/Settings.h @@ -0,0 +1,41 @@ +/* + * File: Settings.h + * Author: paulwagener + * + * Created on 23 april 2011, 17:11 + */ + +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include "Panorama.h" +#define LOCATION_LENGTH 100 + +struct pano_location { + char pano_id[23]; + char name[LOCATION_LENGTH+1]; +}; + +class Settings { +public: + bool mipmapping; + bool wireframe; + int horizontal_accuracy; + int vertical_accuracy; + int numPanoramas; + int zoomLevel; + char last_pano[PANOID_LENGTH+1]; + + std::vector locations; + + Settings(); + void saveSettings(); +private: + +}; + +extern Settings settings; + +#endif /* SETTINGS_H */ + diff --git a/src/Utm.cpp b/src/Utm.cpp new file mode 100644 index 0000000..a73150d --- /dev/null +++ b/src/Utm.cpp @@ -0,0 +1,320 @@ +/************************************************************************ + * + * File: Utm.cpp + * RCS: $Header: /cvsroot/stelvio/stelvio/NavStar/Utm.cpp,v 1.1 2001/03/18 20:07:03 steve_l Exp $ + * Author: Steve Loughran + * Created: 2001 + * Language: C++ + * Package: + * Status: Experimental + * @doc + * + ************************************************************************/ + +/* + This is code to do UTM conversion. + + I took this code from Jason Bevins' GPS thing which blagged the VB algorithms + from the Mapping Datum Transformation Software (MADTRAN) program, + written in PowerBasic. To get the source code for MADTRAN, go to: + + http://164.214.2.59/publications/guides/MADTRAN/index.html + + this version retains the core algorithms as static functions + + */ + +#include "Utm.h" +#include + +static const double deg2rad = 3.14159265358979323846/(double)180; +static const double rad2deg = 180/3.14159265358979323846; + + +// Some constants used by these functions. +static const double fe = 500000.0; +static const double ok = 0.9996; + +// An array containing each vertical UTM zone. +static char cArray[] = "CDEFGHJKLMNPQRSTUVWX"; + + +///////////////////////////////////////////////////////////////////////////// +// Miscellaneous functions for these UTM conversion formulas. + +double CalculateESquared(double a, double b) { + return ((a * a) - (b * b)) / (a * a); +} + +double CalculateE2Squared(double a, double b) { + return ((a * a) - (b * b)) / (b * b); +} + +double denom(double es, double sphi) { + double sinSphi = sin(sphi); + return sqrt(1.0 - es * (sinSphi * sinSphi)); +} + +double sphsr(double a, double es, double sphi) { + double dn = denom(es, sphi); + return a * (1.0 - es) / (dn * dn * dn); +} + +double sphsn(double a, double es, double sphi) { + double sinSphi = sin(sphi); + return a / sqrt(1.0 - es * (sinSphi * sinSphi)); +} + +double sphtmd(double ap, double bp, double cp, double dp, double ep, + double sphi) { + return (ap * sphi) - (bp * sin(2.0 * sphi)) + (cp * sin(4.0 * sphi)) + - (dp * sin(6.0 * sphi)) + (ep * sin(8.0 * sphi)); +} + + +//======================================================================= +// Purpose: +// This function converts the specified lat/lon coordinate to a UTM +// coordinate. +// Parameters: +// double a: +// Ellipsoid semi-major axis, in meters. (For WGS84 datum, use 6378137.0) +// double f: +// Ellipsoid flattening. (For WGS84 datum, use 1 / 298.257223563) +// int& utmXZone: +// Upon exit, this parameter will contain the hotizontal zone number of +// the UTM coordinate. The returned value for this parameter is a number +// within the range 1 to 60, inclusive. +// char& utmYZone: +// Upon exit, this parameter will contain the zone letter of the UTM +// coordinate. The returned value for this parameter will be one of: +// CDEFGHJKLMNPQRSTUVWX. +// double& easting: +// Upon exit, this parameter will contain the UTM easting, in meters. +// double& northing: +// Upon exit, this parameter will contain the UTM northing, in meters. +// double lat, double lon: +// The lat/lon coordinate to convert. +// Notes: +// - The code in this function is a C conversion of some of the source code +// from the Mapping Datum Transformation Software (MADTRAN) program, +// written in PowerBasic. To get the source code for MADTRAN, go to: +// +// http://164.214.2.59/publications/guides/MADTRAN/index.html +// +// and download MADTRAN.ZIP +// - If the UTM zone is out of range, the y-zone character is set to the +// asterisk character ('*'). +//======================================================================= + +void LatLonToUtm(double a, double f, int& utmXZone, char& utmYZone, + double& easting, double& northing, double lat, double lon) { + double recf; + double b; + double eSquared; + double e2Squared; + double tn; + double ap; + double bp; + double cp; + double dp; + double ep; + double olam; + double dlam; + double s; + double c; + double t; + double eta; + double sn; + double tmd; + double t1, t2, t3, t6, t7; + double nfn; + + if (lon <= 0.0) { + utmXZone = 30 + (int) (lon / 6.0); + } else { + utmXZone = 31 + (int) (lon / 6.0); + } + if (lat < 84.0 && lat >= 72.0) { + // Special case: zone X is 12 degrees from north to south, not 8. + utmYZone = cArray[19]; + } else { + utmYZone = cArray[(int) ((lat + 80.0) / 8.0)]; + } + if (lat >= 84.0 || lat < -80.0) { + // Invalid coordinate; the vertical zone is set to the invalid + // character. + utmYZone = '*'; + } + + double latRad = lat * deg2rad; + double lonRad = lon * deg2rad; + recf = 1.0 / f; + b = a * (recf - 1.0) / recf; + eSquared = CalculateESquared(a, b); + e2Squared = CalculateE2Squared(a, b); + tn = (a - b) / (a + b); + ap = a * (1.0 - tn + 5.0 * ((tn * tn) - (tn * tn * tn)) / 4.0 + 81.0 * + ((tn * tn * tn * tn) - (tn * tn * tn * tn * tn)) / 64.0); + bp = 3.0 * a * (tn - (tn * tn) + 7.0 * ((tn * tn * tn) + - (tn * tn * tn * tn)) / 8.0 + 55.0 * (tn * tn * tn * tn * tn) / 64.0) + / 2.0; + cp = 15.0 * a * ((tn * tn) - (tn * tn * tn) + 3.0 * ((tn * tn * tn * tn) + - (tn * tn * tn * tn * tn)) / 4.0) / 16.0; + dp = 35.0 * a * ((tn * tn * tn) - (tn * tn * tn * tn) + 11.0 + * (tn * tn * tn * tn * tn) / 16.0) / 48.0; + ep = 315.0 * a * ((tn * tn * tn * tn) - (tn * tn * tn * tn * tn)) / 512.0; + olam = (utmXZone * 6 - 183) * deg2rad; + dlam = lonRad - olam; + s = sin(latRad); + c = cos(latRad); + t = s / c; + eta = e2Squared * (c * c); + sn = sphsn(a, eSquared, latRad); + tmd = sphtmd(ap, bp, cp, dp, ep, latRad); + t1 = tmd * ok; + t2 = sn * s * c * ok / 2.0; + t3 = sn * s * (c * c * c) * ok * (5.0 - (t * t) + 9.0 * eta + 4.0 + * (eta * eta)) / 24.0; + if (latRad < 0.0) nfn = 10000000.0; + else nfn = 0; + northing = nfn + t1 + (dlam * dlam) * t2 + (dlam * dlam * dlam + * dlam) * t3 + (dlam * dlam * dlam * dlam * dlam * dlam) + 0.5; + t6 = sn * c * ok; + t7 = sn * (c * c * c) * (1.0 - (t * t) + eta) / 6.0; + easting = fe + dlam * t6 + (dlam * dlam * dlam) * t7 + 0.5; + if (northing >= 9999999.0) northing = 9999999.0; +} + +//======================================================================= +// Purpose: +// This function converts the specified lat/lon coordinate to a UTM +// coordinate in the WGS84 datum. (See the comment block for the +// LatLonToUtm() member function.) +//======================================================================= +void LatLonToUtmWGS84(struct utmPosition &utm, double lat, double lon) { + LatLonToUtm(6378137.0, 1 / 298.257223563, utm.zoneX, utm.zoneY, + utm.easting, utm.northing, lat, lon); +} + + + +//======================================================================= +// Purpose: +// This function converts the specified UTM coordinate to a lat/lon +// coordinate. +// Pre: +// - utmXZone must be between 1 and 60, inclusive. +// - utmYZone must be one of: CDEFGHJKLMNPQRSTUVWX +// Parameters: +// double a: +// Ellipsoid semi-major axis, in meters. (For WGS84 datum, use 6378137.0) +// double f: +// Ellipsoid flattening. (For WGS84 datum, use 1 / 298.257223563) +// int utmXZone: +// The horizontal zone number of the UTM coordinate. +// char utmYZone: +// The vertical zone letter of the UTM coordinate. +// double easting, double northing: +// The UTM coordinate to convert. +// double& lat: +// Upon exit, lat contains the latitude. +// double& lon: +// Upon exit, lon contains the longitude. +// Notes: +// The code in this function is a C conversion of some of the source code +// from the Mapping Datum Transformation Software (MADTRAN) program, written +// in PowerBasic. To get the source code for MADTRAN, go to: +// +// http://164.214.2.59/publications/guides/MADTRAN/index.html +// +// and download MADTRAN.ZIP +//======================================================================= + +void UtmToLatLon(double a, double f, int utmXZone, char utmYZone, + double easting, double northing, double& lat, double& lon) { + double recf; + double b; + double eSquared; + double e2Squared; + double tn; + double ap; + double bp; + double cp; + double dp; + double ep; + double nfn; + double tmd; + double sr; + double sn; + double ftphi; + double s; + double c; + double t; + double eta; + double de; + double dlam; + double olam; + + recf = 1.0 / f; + b = a * (recf - 1) / recf; + eSquared = CalculateESquared(a, b); + e2Squared = CalculateE2Squared(a, b); + tn = (a - b) / (a + b); + ap = a * (1.0 - tn + 5.0 * ((tn * tn) - (tn * tn * tn)) / 4.0 + 81.0 * + ((tn * tn * tn * tn) - (tn * tn * tn * tn * tn)) / 64.0); + bp = 3.0 * a * (tn - (tn * tn) + 7.0 * ((tn * tn * tn) + - (tn * tn * tn * tn)) / 8.0 + 55.0 * (tn * tn * tn * tn * tn) / 64.0) + / 2.0; + cp = 15.0 * a * ((tn * tn) - (tn * tn * tn) + 3.0 * ((tn * tn * tn * tn) + - (tn * tn * tn * tn * tn)) / 4.0) / 16.0; + dp = 35.0 * a * ((tn * tn * tn) - (tn * tn * tn * tn) + 11.0 + * (tn * tn * tn * tn * tn) / 16.0) / 48.0; + ep = 315.0 * a * ((tn * tn * tn * tn) - (tn * tn * tn * tn * tn)) / 512.0; + if ((utmYZone <= 'M' && utmYZone >= 'C') + || (utmYZone <= 'm' && utmYZone >= 'c')) { + nfn = 10000000.0; + } else { + nfn = 0; + } + tmd = (northing - nfn) / ok; + sr = sphsr(a, eSquared, 0.0); + ftphi = tmd / sr; + double t10, t11, t14, t15; + for (int i = 0; i < 5; i++) { + t10 = sphtmd(ap, bp, cp, dp, ep, ftphi); + sr = sphsr(a, eSquared, ftphi); + ftphi = ftphi + (tmd - t10) / sr; + } + sr = sphsr(a, eSquared, ftphi); + sn = sphsn(a, eSquared, ftphi); + s = sin(ftphi); + c = cos(ftphi); + t = s / c; + eta = e2Squared * (c * c); + de = easting - fe; + t10 = t / (2.0 * sr * sn * (ok * ok)); + t11 = t * (5.0 + 3.0 * (t * t) + eta - 4.0 * (eta * eta) - 9.0 * (t * t) + * eta) / (24.0 * sr * (sn * sn * sn) * (ok * ok * ok * ok)); + lat = ftphi - (de * de) * t10 + (de * de * de * de) * t11; + t14 = 1.0 / (sn * c * ok); + t15 = (1.0 + 2.0 * (t * t) + eta) / (6 * (sn * sn * sn) * c + * (ok * ok * ok)); + dlam = de * t14 - (de * de * de) * t15; + olam = (utmXZone * 6 - 183.0) * deg2rad; + lon = olam + dlam; + lon *= rad2deg; + lat *= rad2deg; +} + +//======================================================================= +// Purpose: +// This function converts the specified UTM coordinate to a lat/lon +// coordinate in the WGS84 datum. (See the comment block for the +// UtmToLatLon() member function. +//======================================================================= + +void UtmToLatLonWGS84(struct utmPosition utm, double& lat, double& lon) { + UtmToLatLon(6378137.0, 1 / 298.257223563, utm.zoneX, utm.zoneY, + utm.easting, utm.northing, lat, lon); +} diff --git a/src/Utm.h b/src/Utm.h new file mode 100644 index 0000000..1043dbf --- /dev/null +++ b/src/Utm.h @@ -0,0 +1,29 @@ +/************************************************************************ + * + * File: utm.h + * RCS: $Header: /cvsroot/stelvio/stelvio/NavStar/Utm.h,v 1.2 2002/04/23 05:02:00 steve_l Exp $ + * Author: Steve Loughran + * Created: 2001 + * Language: C++ + * Package: + * Status: Experimental + * @doc + * + ************************************************************************/ + +#ifndef UTM_H +#define UTM_H + +struct utmPosition { + int zoneX; + char zoneY; + + double easting; + double northing; +}; + +void LatLonToUtmWGS84(struct utmPosition &utm, double lat, double lon); + +void UtmToLatLonWGS84(struct utmPosition utm, double& lat, double& lon); + +#endif \ No newline at end of file diff --git a/src/base64.cpp b/src/base64.cpp new file mode 100644 index 0000000..490d857 --- /dev/null +++ b/src/base64.cpp @@ -0,0 +1,225 @@ + + +#ifdef HAVE_STDIO_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef HAVE_STRING_H +#include +#endif + + +static int is_base64(char c); +static char encode(unsigned char u); +static unsigned char decode(char c); + +/** + * Implementation of base64 encoding/decoding. + * + * @author Jan-Henrik Haukeland, + * + * @version \$Id: base64.c,v 1.19 2007/07/25 12:54:31 hauk Exp $ + * + * @file + */ + + + +/* ------------------------------------------------------------------ Public */ + +#include +#include + +/** + * Base64 encode and return size data in 'src'. The caller must free the + * returned string. + * @param size The size of the data in src + * @param src The data to be base64 encode + * @return encoded string otherwise NULL + */ +char *encode_base64(int size, unsigned char *src) { + + int i; + char *out, *p; + + if (!src) + return 0; + + if (!size) + size = strlen((char *) src); + + out = (char*) calloc(sizeof (char), size * 4 / 3 + 4); + + p = out; + + for (i = 0; i < size; i += 3) { + + unsigned char b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0, b7 = 0; + + b1 = src[i]; + + if (i + 1 < size) + b2 = src[i + 1]; + + if (i + 2 < size) + b3 = src[i + 2]; + + b4 = b1 >> 2; + b5 = ((b1 & 0x3) << 4) | (b2 >> 4); + b6 = ((b2 & 0xf) << 2) | (b3 >> 6); + b7 = b3 & 0x3f; + + *p++ = encode(b4); + *p++ = encode(b5); + + if (i + 1 < size) { + *p++ = encode(b6); + } else { + *p++ = '='; + } + + if (i + 2 < size) { + *p++ = encode(b7); + } else { + *p++ = '='; + } + + } + + return out; + +} + +/** + * Decode the base64 encoded string 'src' into the memory pointed to by + * 'dest'. The dest buffer is not NUL terminated. + * @param dest Pointer to memory for holding the decoded string. + * Must be large enough to recieve the decoded string. + * @param src A base64 encoded string. + * @return TRUE (the length of the decoded string) if decode + * succeeded otherwise FALSE. + */ +int decode_base64(unsigned char *dest, const char *src) { + + if (src && *src) { + + unsigned char *p = dest; + int k, l = strlen(src) + 1; + unsigned char *buf = (unsigned char*) calloc(sizeof (unsigned char), l); + + + /* Ignore non base64 chars as per the POSIX standard */ + for (k = 0, l = 0; src[k]; k++) { + if (is_base64(src[k])) { + buf[l++] = src[k]; + } + } + + for (k = 0; k < l; k += 4) { + + char c1 = 'A', c2 = 'A', c3 = 'A', c4 = 'A'; + unsigned char b1 = 0, b2 = 0, b3 = 0, b4 = 0; + + c1 = buf[k]; + + if (k + 1 < l) { + + c2 = buf[k + 1]; + + } + + if (k + 2 < l) { + + c3 = buf[k + 2]; + + } + + if (k + 3 < l) { + + c4 = buf[k + 3]; + + } + + b1 = decode(c1); + b2 = decode(c2); + b3 = decode(c3); + b4 = decode(c4); + + *p++ = ((b1 << 2) | (b2 >> 4)); + + if (c3 != '=') { + + *p++ = (((b2 & 0xf) << 4) | (b3 >> 2)); + + } + + if (c4 != '=') { + + *p++ = (((b3 & 0x3) << 6) | b4); + + } + + } + + free(buf); + + return (p - dest); + + } + + return 0; + +} + + +/* ----------------------------------------------------------------- Private */ + +/** + * Base64 encode one byte + */ +static char encode(unsigned char u) { + + if (u < 26) return 'A' + u; + if (u < 52) return 'a' + (u - 26); + if (u < 62) return '0' + (u - 52); + if (u == 62) return '+'; + + return '/'; + +} + +/** + * Decode a base64 character + */ +static unsigned char decode(char c) { + + if (c >= 'A' && c <= 'Z') return (c - 'A'); + if (c >= 'a' && c <= 'z') return (c - 'a' + 26); + if (c >= '0' && c <= '9') return (c - '0' + 52); + if (c == '-') return 62; + + return 63; + +} + +/** + * Return TRUE if 'c' is a valid base64 character, otherwise FALSE + */ +static int is_base64(char c) { + + return 1; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || (c == '+') || + (c == '/') || (c == '=')) { + + return 1; + + } + + return 0; + +} \ No newline at end of file diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 0000000..28a506e --- /dev/null +++ b/src/base64.h @@ -0,0 +1,6 @@ +#ifndef BASE64_H +#define BASE64_H + +int decode_base64(unsigned char *dest, const char *src); + +#endif /* BASE64_H */ \ No newline at end of file diff --git a/src/common.cpp b/src/common.cpp new file mode 100644 index 0000000..254f68c --- /dev/null +++ b/src/common.cpp @@ -0,0 +1,11 @@ +/* + * File: common.cpp + * Author: paulwagener + * + * Created on 9 april 2011, 13:30 + */ + +#include "common.h" + + +const float RADIAL = 1 / (float) 360 * TWICE_PI; \ No newline at end of file diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..159f23b --- /dev/null +++ b/src/common.h @@ -0,0 +1,16 @@ +/* + * File: common.h + * Author: paulwagener + * + * Created on 9 april 2011, 13:30 + */ + +#ifndef COMMON_H +#define COMMON_H + +#define PI 3.1415f +#define TWICE_PI 2*PI +extern const float RADIAL; + +#endif /* COMMON_H */ + diff --git a/src/download.cpp b/src/download.cpp new file mode 100644 index 0000000..ff4bd79 --- /dev/null +++ b/src/download.cpp @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include "download.h" + +size_t write_download_data(void *ptr, size_t size, size_t nmemb, std::vector* b) throw () { + size_t oldSize = b->size(); + try { + b->resize(b->size() + size * nmemb); + } catch (const std::bad_alloc&) { + //errx(EX_UNAVAILABLE, "download failed: out of memory"); + } + memcpy(&(*b)[oldSize], ptr, size * nmemb); + return size * nmemb; +} + +/** + * Download the contents of a url as a block of memory + * + * @param url + * @return + */ +std::auto_ptr > +download(const char *url) { + printf("Downloading %s\n", url); + + std::auto_ptr > b(new std::vector()); + + CURL *curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_download_data); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, b.get()); + + CURLcode res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + + //Throw a human readable string back to the caller if something went wrong + if (res != 0) + throw curl_easy_strerror(res); + + return b; +} + +#include +#include +/* Read JPEG image from a memory segment */ +static void init_source (j_decompress_ptr cinfo) {} +static boolean fill_input_buffer (j_decompress_ptr cinfo) +{ + ERREXIT(cinfo, JERR_INPUT_EMPTY); +return TRUE; +} +static void skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + struct jpeg_source_mgr* src = (struct jpeg_source_mgr*) cinfo->src; + + if (num_bytes > 0) { + src->next_input_byte += (size_t) num_bytes; + src->bytes_in_buffer -= (size_t) num_bytes; + } +} +static void term_source (j_decompress_ptr cinfo) {} +static void jpeg_mem_src (j_decompress_ptr cinfo, void* buffer, long nbytes) +{ + struct jpeg_source_mgr* src; + + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(struct jpeg_source_mgr)); + } + + src = (struct jpeg_source_mgr*) cinfo->src; + src->init_source = init_source; + src->fill_input_buffer = fill_input_buffer; + src->skip_input_data = skip_input_data; + src->resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->term_source = term_source; + src->bytes_in_buffer = nbytes; + src->next_input_byte = (JOCTET*)buffer; +} + + +/** + * Downloads a jpeg from a url and returns it as raw RGB data in memory + * @param url + * @return + */ +struct image_block download_jpeg(const char *url) { + + const std::auto_ptr > jpeg_data = download(url); + + struct image_block image; + + //Initialize jpeg decompression + struct jpeg_decompress_struct info; + struct jpeg_error_mgr err; + info.err = jpeg_std_error(&err); + jpeg_create_decompress(&info); + + //Process jpeg + jpeg_mem_src(&info, &(*jpeg_data)[0], jpeg_data->size()); + jpeg_read_header(&info, TRUE); + jpeg_start_decompress(&info); + + //Read in basic image information + if (info.num_components != 3) + throw ".jpg file has unsupported amount of channels"; + + image.width = info.output_width; + image.height = info.output_height; + image.data = (unsigned char*) malloc(3 * image.width * image.height); + + if (image.data == NULL) + throw "Unable to allocate memory for storing raw .jpg file"; + + //Parse image data per row + for (unsigned int y = 0; y < image.height; y++) { + unsigned char *scanline_pointer = &image.data[y * image.width*3]; + jpeg_read_scanlines(&info, &scanline_pointer, 1); + } + + //Clean up + jpeg_finish_decompress(&info); + + if(info.err->num_warnings > 0) + throw "Corrupt jpeg downloaded"; + + return image; +} diff --git a/src/download.h b/src/download.h new file mode 100644 index 0000000..9098328 --- /dev/null +++ b/src/download.h @@ -0,0 +1,30 @@ +/* + * File: download.h + * Author: paulwagener + * + * Created on 3 april 2011, 12:43 + */ + +#ifndef DOWNLOAD_H +#define DOWNLOAD_H + +#include +#include +#include + +struct block { + unsigned char *data; + int size; +}; + +struct image_block { + unsigned int width; + unsigned int height; + unsigned char *data; +}; + +struct image_block download_jpeg(const char *url); +std::auto_ptr > download(const char *url); + +#endif /* DOWNLOAD_H */ + diff --git a/src/gl.h b/src/gl.h new file mode 100644 index 0000000..d686cba --- /dev/null +++ b/src/gl.h @@ -0,0 +1,12 @@ +#ifdef __WXMAC__ + +#include + +#else + +#include +#include +#include +#include + +#endif \ No newline at end of file