diff --git a/depthmapX/GraphDoc.cpp b/depthmapX/GraphDoc.cpp index 4c874a84..2bfcd85d 100644 --- a/depthmapX/GraphDoc.cpp +++ b/depthmapX/GraphDoc.cpp @@ -167,6 +167,9 @@ void QGraphDoc::UpdateMainframestatus() else if ((state & MetaGraph::DATAMAPS) && m_meta_graph->getViewClass() & MetaGraph::VIEWDATA) { n = (int) m_meta_graph->getDisplayedDataMap().getShapeCount(); } + else if ((state & MetaGraph::TRACEMAPS) && m_meta_graph->getViewClass() & MetaGraph::VIEWTRACES) { + n = (int) m_meta_graph->getDisplayedTraceMap().getShapeCount(); + } // either showing or constructing the VGA graph else if ((state & MetaGraph::POINTMAPS) && m_meta_graph->getViewClass() & MetaGraph::VIEWVGA) { n = (int) m_meta_graph->getDisplayedPointMap().getFilledPointCount(); @@ -885,6 +888,53 @@ void QGraphDoc::OnFileExportLinks() stream.close(); } +void QGraphDoc::OnFileExportTraces() +{ + if (m_communicator) { + QMessageBox::warning(this, tr("Notice"), tr("Sorry, cannot export as another process is running"), QMessageBox::Ok, QMessageBox::Ok); + return; // Locked + } + if (!m_meta_graph->viewingProcessedTraces()) { + QMessageBox::warning(this, tr("Notice"), tr("Sorry, can only export traces maps"), QMessageBox::Ok, QMessageBox::Ok); + return; // No graph to export + } + + QFilePath path(m_opened_name); + QString defaultname = path.m_path + (path.m_name.isEmpty() ? windowTitle() : path.m_name); + + QString template_string = tr("Traceset XML file (*.xml)\n"); + + QFileDialog::Options options = 0; + QString selectedFilter; + QString outfile = QFileDialog::getSaveFileName( + 0, tr("Save Output As"), + defaultname, + template_string, + &selectedFilter, + options); + if(outfile.isEmpty()) + { + return; + } + + FILE* fp = fopen(outfile.toLatin1(), "wb"); + fclose(fp); + + QFilePath filepath(outfile); + QString ext = filepath.m_ext; + + std::ofstream stream(outfile.toLatin1()); + if (stream.fail() || stream.bad()) { + QMessageBox::warning(this, tr("Notice"), tr("Sorry, unable to open file for export"), QMessageBox::Ok, QMessageBox::Ok); + stream.close(); + return; + } + + m_meta_graph->getDisplayedTraceMap().writeTracesToXMLFile(stream); + + stream.close(); +} + void QGraphDoc::OnAxialConnectionsExportAsDot() { if (m_communicator) { @@ -925,6 +975,7 @@ void QGraphDoc::OnAxialConnectionsExportAsDot() if (stream.fail() || stream.bad()) { QMessageBox::warning(this, tr("Notice"), tr("Sorry, unable to open file for export"), QMessageBox::Ok, QMessageBox::Ok); + stream.close(); return; } shapeGraph.writeAxialConnectionsAsDotGraph(stream); @@ -972,6 +1023,7 @@ void QGraphDoc::OnAxialConnectionsExportAsPairCSV() if (stream.fail() || stream.bad()) { QMessageBox::warning(this, tr("Notice"), tr("Sorry, unable to open file for export"), QMessageBox::Ok, QMessageBox::Ok); + stream.close(); return; } shapeGraph.writeAxialConnectionsAsPairsCSV(stream); @@ -1019,6 +1071,7 @@ void QGraphDoc::OnSegmentConnectionsExportAsPairCSV() if (stream.fail() || stream.bad()) { QMessageBox::warning(this, tr("Notice"), tr("Sorry, unable to open file for export"), QMessageBox::Ok, QMessageBox::Ok); + stream.close(); return; } shapeGraph.writeSegmentConnectionsAsPairsCSV(stream); @@ -1076,6 +1129,7 @@ void QGraphDoc::OnPointmapExportConnectionsAsCSV() if (stream.fail() || stream.bad()) { QMessageBox::warning(this, tr("Notice"), tr("Sorry, unable to open file for export"), QMessageBox::Ok, QMessageBox::Ok); + stream.close(); return; } pointMap.outputConnectionsAsCSV(stream, ","); @@ -1613,6 +1667,9 @@ void QGraphDoc::OnEditClear() else if (m_meta_graph->viewingProcessedShapes()) { modified = m_meta_graph->getDisplayedDataMap().removeSelected(); } + else if (m_meta_graph->viewingProcessedShapes()) { + modified = m_meta_graph->getDisplayedTraceMap().removeSelected(); + } if(modified) { modifiedFlag = true; @@ -2185,6 +2242,9 @@ void QGraphDoc::OnUpdateColumn() else if (vc & MetaGraph::VIEWDATA) { shapemap = &(m_meta_graph->getDisplayedDataMap()); } + else if (vc & MetaGraph::VIEWTRACES) { + shapemap = &(m_meta_graph->getDisplayedTraceMap()); + } if (ReplaceColumnContents(pointmap,shapemap,col)) { m_meta_graph->setDisplayedAttribute(col); @@ -2275,6 +2335,9 @@ void QGraphDoc::OnEditQuery() else if (vc & MetaGraph::VIEWDATA) { shapemap = &(m_meta_graph->getDisplayedDataMap()); } + else if (vc & MetaGraph::VIEWTRACES) { + shapemap = &(m_meta_graph->getDisplayedTraceMap()); + } if (SelectByQuery(pointmap,shapemap)) { SetRedrawFlag(VIEW_ALL, QGraphDoc::REDRAW_GRAPH, QGraphDoc::NEW_DATA ); @@ -2354,7 +2417,7 @@ bool QGraphDoc::SelectByQuery(PointMap *pointmap, ShapeMap *shapemap) void QGraphDoc::OnEditSelectToLayer() { - if ((m_meta_graph->getViewClass() & (MetaGraph::VIEWAXIAL|MetaGraph::VIEWDATA)) + if ((m_meta_graph->getViewClass() & (MetaGraph::VIEWAXIAL|MetaGraph::VIEWDATA|MetaGraph::VIEWTRACES)) && m_meta_graph->isSelected()) { CRenameObjectDlg dlg("Layer"); // note, without specifying existing layer name, this defaults to "New layer" behaviour diff --git a/depthmapX/GraphDoc.h b/depthmapX/GraphDoc.h index 1f97f93e..fa88aeda 100644 --- a/depthmapX/GraphDoc.h +++ b/depthmapX/GraphDoc.h @@ -272,6 +272,7 @@ public slots: void OnFileExport(); void OnFileExportMapGeometry(); void OnFileExportLinks(); + void OnFileExportTraces(); void OnAxialConnectionsExportAsDot(); void OnAxialConnectionsExportAsPairCSV(); void OnSegmentConnectionsExportAsPairCSV(); diff --git a/depthmapX/dialogs/ColourScaleDlg.cpp b/depthmapX/dialogs/ColourScaleDlg.cpp index 349010f8..25ad0547 100644 --- a/depthmapX/dialogs/ColourScaleDlg.cpp +++ b/depthmapX/dialogs/ColourScaleDlg.cpp @@ -278,10 +278,8 @@ void CColourScaleDlg::MyUpdateData(bool dir, bool apply_to_all) { m_color = m_displayparams.colorscale; } else if (graph->getViewClass() & MetaGraph::VIEWAXIAL) { ShapeGraph &map = graph->getDisplayedShapeGraph(); - if (map.getShapeCount() > 0) { - m_display_min = map.getDisplayMinValue(); - m_display_max = map.getDisplayMaxValue(); - } + m_display_min = map.getDisplayMinValue(); + m_display_max = map.getDisplayMaxValue(); m_displayparams = map.getDisplayParams(); m_color = m_displayparams.colorscale; bool show_lines = m_show_lines, show_fill = m_show_fill, show_centroids = m_show_centroids; @@ -291,10 +289,19 @@ void CColourScaleDlg::MyUpdateData(bool dir, bool apply_to_all) { m_show_centroids = show_centroids; } else if (graph->getViewClass() & MetaGraph::VIEWDATA) { ShapeMap &map = graph->getDisplayedDataMap(); - if (map.getShapeCount() > 0) { - m_display_min = map.getDisplayMinValue(); - m_display_max = map.getDisplayMaxValue(); - } + m_display_min = map.getDisplayMinValue(); + m_display_max = map.getDisplayMaxValue(); + m_displayparams = map.getDisplayParams(); + m_color = m_displayparams.colorscale; + bool show_lines = m_show_lines, show_fill = m_show_fill, show_centroids = m_show_centroids; + map.getPolygonDisplay(show_lines, show_fill, show_centroids); + m_show_lines = show_lines; + m_show_fill = show_fill; + m_show_centroids = show_centroids; + } else if (graph->getViewClass() & MetaGraph::VIEWTRACES) { + TraceMap &map = graph->getDisplayedTraceMap(); + m_display_min = map.getDisplayMinValue(); + m_display_max = map.getDisplayMaxValue(); m_displayparams = map.getDisplayParams(); m_color = m_displayparams.colorscale; bool show_lines = m_show_lines, show_fill = m_show_fill, show_centroids = m_show_centroids; @@ -330,6 +337,9 @@ void CColourScaleDlg::MyUpdateData(bool dir, bool apply_to_all) { } else if (graph->getViewClass() & MetaGraph::VIEWDATA) { graph->getDisplayedDataMap().setDisplayParams(m_displayparams, apply_to_all); graph->getDisplayedDataMap().setPolygonDisplay(m_show_lines, m_show_fill, m_show_centroids); + } else if (graph->getViewClass() & MetaGraph::VIEWTRACES) { + graph->getDisplayedTraceMap().setDisplayParams(m_displayparams, apply_to_all); + graph->getDisplayedTraceMap().setPolygonDisplay(m_show_lines, m_show_fill, m_show_centroids); } } m_viewDoc->SetRedrawFlag(QGraphDoc::VIEW_ALL, QGraphDoc::REDRAW_GRAPH, QGraphDoc::NEW_DEPTHMAPVIEW_SETUP); diff --git a/depthmapX/mainwindow.cpp b/depthmapX/mainwindow.cpp index 998e4f24..7398cbe7 100644 --- a/depthmapX/mainwindow.cpp +++ b/depthmapX/mainwindow.cpp @@ -376,6 +376,16 @@ void MainWindow::OnFileExportLinks() } } + +void MainWindow::OnFileExportTraces() +{ + QGraphDoc* m_p = activeMapDoc(); + if(m_p) + { + m_p->OnFileExportTraces(); + } +} + void MainWindow::OnAxialConnectionsExportAsDot() { QGraphDoc* m_p = activeMapDoc(); @@ -1361,6 +1371,21 @@ void MainWindow::OnSelchangingTree(QTreeWidgetItem* hItem, int col) } remenu = true; break; + case 3: + if (graph->getViewClass() & MetaGraph::VIEWTRACES) { + if (graph->getDisplayedTraceMapRef() == entry.m_cat) { + graph->setViewClass(MetaGraph::SHOWHIDETRACES); + } + else { + graph->setDisplayedTraceMapRef(entry.m_cat); + } + } + else { + graph->setDisplayedTraceMapRef(entry.m_cat); + graph->setViewClass(MetaGraph::SHOWTRACESTOP); + } + remenu = true; + break; case 4: // slightly different for this one break; @@ -1385,6 +1410,10 @@ void MainWindow::OnSelchangingTree(QTreeWidgetItem* hItem, int col) graph->getDataMaps()[entry.m_cat].setEditable(m_indexWidget->isItemSetEditable(hItem)); update = true; } + if (entry.m_type == 3) { + graph->getTraceMaps()[entry.m_cat].setEditable(m_indexWidget->isItemSetEditable(hItem)); + update = true; + } if (update) { // Depending on if the map is displayed you may have to redraw -- I'm just going to redraw *anyway* // (it may be worth switching it to topmost when they do click here) @@ -1401,6 +1430,10 @@ void MainWindow::OnSelchangingTree(QTreeWidgetItem* hItem, int col) update = true; graph->getDataMaps()[entry.m_cat].setLayerVisible(entry.m_subcat, m_indexWidget->isItemSetVisible(hItem)); } + else if (entry.m_type == 3) { + update = true; + graph->getTraceMaps()[entry.m_cat].setLayerVisible(entry.m_subcat, m_indexWidget->isItemSetVisible(hItem)); + } if (update) { m_treeDoc->SetRedrawFlag(QGraphDoc::VIEW_ALL, QGraphDoc::REDRAW_GRAPH, QGraphDoc::NEW_TABLE ); OnFocusGraph(m_treeDoc, QGraphDoc::CONTROLS_CHANGEATTRIBUTE); @@ -1472,6 +1505,16 @@ void MainWindow::SetGraphTreeChecks() m_backgraph = key; } break; + case 3: + if (viewclass & MetaGraph::VIEWTRACES && graph->getDisplayedTraceMapRef() == entry.m_cat) { + checkstyle = 5; + m_topgraph = key; + } + else if (viewclass & MetaGraph::VIEWBACKTRACES && graph->getDisplayedTraceMapRef() == entry.m_cat) { + checkstyle = 6; + m_backgraph = key; + } + break; } if(checkstyle == 5) @@ -1506,6 +1549,9 @@ void MainWindow::SetGraphTreeChecks() case 2: editable = graph->getDataMaps()[entry.m_cat].isEditable() ? MetaGraph::EDITABLE_ON : MetaGraph::EDITABLE_OFF; break; + case 3: + editable = graph->getTraceMaps()[entry.m_cat].isEditable() ? MetaGraph::EDITABLE_ON : MetaGraph::EDITABLE_OFF; + break; } switch (editable) { case MetaGraph::NOT_EDITABLE: @@ -1529,6 +1575,9 @@ void MainWindow::SetGraphTreeChecks() else if (entry.m_type == 2) { show = graph->getDataMaps()[entry.m_cat].isLayerVisible(entry.m_subcat); } + else if (entry.m_type == 3) { + show = graph->getTraceMaps()[entry.m_cat].isLayerVisible(entry.m_subcat); + } if (show) { m_indexWidget->setItemVisibility(key, Qt::Checked); } @@ -1565,7 +1614,7 @@ void MainWindow::ClearGraphTree() { m_attribute_locked.clear(); - for (int i = 2; i >= 0; i--) { + for (int i = 3; i >= 0; i--) { if (m_treeroots[i]) { m_treeroots[i] = NULL; } @@ -1679,6 +1728,44 @@ void MainWindow::MakeGraphTree() m_treeroots[2] = NULL; } + + if (state & MetaGraph::TRACEMAPS) { + if (!m_treeroots[3]) { + QTreeWidgetItem* hItem = m_indexWidget->addNewItem(tr("Trace Maps")); + hItem->setIcon(0, m_tree_icon[2]); + ItemTreeEntry entry(3,-1,-1); + m_treegraphmap[hItem] = entry; + m_treeroots[3] = hItem; + } + for (size_t i = 0; i < m_treeDoc->m_meta_graph->getTraceMaps().size(); i++) { + QString name = QString(m_treeDoc->m_meta_graph->getTraceMaps()[i].getName().c_str()); + QTreeWidgetItem* hItem = m_indexWidget->addNewItem(name, m_treeroots[3]); + m_indexWidget->setItemVisibility(hItem, Qt::Unchecked); + m_indexWidget->setItemEditability(hItem, Qt::Unchecked); + ItemTreeEntry entry(3,(short)i,-1); + m_treegraphmap[hItem] = entry; + + LayerManagerImpl layers = m_treeDoc->m_meta_graph->getTraceMaps()[i].getLayers(); + if(layers.getNumLayers() > 1) { + for (int j = 0; j < layers.getNumLayers(); j++) { + QString name = QString(layers.getLayerName(j).c_str()); + QTreeWidgetItem* hNewItem = m_indexWidget->addNewItem(name, hItem); + m_indexWidget->setItemVisibility(hNewItem, Qt::Unchecked); + ItemTreeEntry entry(3,(short)i,j); + m_treegraphmap.insert(std::make_pair(hNewItem,entry)); + } + } + } + } + else if (m_treeroots[3]) { + m_treeroots[3]->removeChild(m_treeroots[3]); + auto iter = m_treegraphmap.find(m_treeroots[3]); + if(iter != m_treegraphmap.end()) { + m_treegraphmap.erase(iter); + } + m_treeroots[3] = NULL; + } + SetGraphTreeChecks(); } @@ -2203,7 +2290,7 @@ void MainWindow::RedoPlotViewMenu(QGraphDoc* pDoc) in_FocusGraph = true; // this will be used to distinguish between viewing VGA and axial maps - int view_class = pDoc->m_meta_graph->getViewClass() & (MetaGraph::VIEWVGA | MetaGraph::VIEWAXIAL | MetaGraph::VIEWDATA); + int view_class = pDoc->m_meta_graph->getViewClass() & (MetaGraph::VIEWVGA | MetaGraph::VIEWAXIAL | MetaGraph::VIEWDATA | MetaGraph::VIEWTRACES); int curr_j = 0; { @@ -2248,6 +2335,19 @@ void MainWindow::RedoPlotViewMenu(QGraphDoc* pDoc) } } } + else if (view_class == MetaGraph::VIEWTRACES) { + // using attribute tables is very, very simple... + const ShapeMap& map = pDoc->m_meta_graph->getDisplayedTraceMap(); + const AttributeTable& table = map.getAttributeTable(); + m_view_map_entries.insert(std::make_pair(0, "Ref Number")); + curr_j = 0; + for (int i = 0; i < table.getNumColumns(); i++) { + m_view_map_entries.insert(std::make_pair(i+1, table.getColumnName(i))); + if (map.getDisplayedAttribute() == i) { + curr_j = i + 1; + } + } + } } } @@ -2585,6 +2685,7 @@ void MainWindow::updateMapMenu() exportAct->setEnabled(true); exportGeometryAct->setEnabled(true); exportLinksAct->setEnabled(true); + exportTracesAct->setEnabled(true); exportAxialConnectionsDotAct->setEnabled(true); exportAxialConnectionsPairAct->setEnabled(true); exportSegmentConnectionsPairAct->setEnabled(true); @@ -2594,6 +2695,7 @@ void MainWindow::updateMapMenu() exportAct->setEnabled(0); exportGeometryAct->setEnabled(false); exportLinksAct->setEnabled(0); + exportTracesAct->setEnabled(false); exportAxialConnectionsDotAct->setEnabled(0); exportAxialConnectionsPairAct->setEnabled(0); exportSegmentConnectionsPairAct->setEnabled(0); @@ -3034,6 +3136,9 @@ void MainWindow::createActions() exportLinksAct->setStatusTip(tr("Export the links of the active map")); connect(exportLinksAct, SIGNAL(triggered()), this, SLOT(OnFileExportLinks())); + exportTracesAct = new QAction(tr("&Export traces..."), this); + connect(exportTracesAct, SIGNAL(triggered()), this, SLOT(OnFileExportTraces())); + exportAxialConnectionsPairAct = new QAction(tr("&Axial Connections as CSV..."), this); exportAxialConnectionsPairAct->setStatusTip(tr("Export a list of line-line intersections")); connect(exportAxialConnectionsPairAct, SIGNAL(triggered()), this, SLOT(OnAxialConnectionsExportAsPairCSV())); @@ -3558,6 +3663,7 @@ void MainWindow::createMenus() exportSubMenu->addAction(exportAct); exportSubMenu->addAction(exportGeometryAct); exportSubMenu->addAction(exportLinksAct); + exportSubMenu->addAction(exportTracesAct); exportSubMenu->addAction(exportAxialConnectionsDotAct); exportSubMenu->addAction(exportAxialConnectionsPairAct); exportSubMenu->addAction(exportSegmentConnectionsPairAct); diff --git a/depthmapX/mainwindow.h b/depthmapX/mainwindow.h index dde4c205..456ed8b5 100644 --- a/depthmapX/mainwindow.h +++ b/depthmapX/mainwindow.h @@ -132,6 +132,7 @@ private slots: void OnFileExport(); void OnFileExportMapGeometry(); void OnFileExportLinks(); + void OnFileExportTraces(); void OnAxialConnectionsExportAsDot(); void OnAxialConnectionsExportAsPairCSV(); void OnSegmentConnectionsExportAsPairCSV(); @@ -349,6 +350,7 @@ private slots: QAction *exportAct; QAction *exportGeometryAct; QAction *exportLinksAct; + QAction *exportTracesAct; QAction *exportAxialConnectionsDotAct; QAction *exportAxialConnectionsPairAct; QAction *exportSegmentConnectionsPairAct; diff --git a/depthmapX/views/depthmapview/depthmapview.cpp b/depthmapX/views/depthmapview/depthmapview.cpp index d2ba4604..8961a6aa 100644 --- a/depthmapX/views/depthmapview/depthmapview.cpp +++ b/depthmapX/views/depthmapview/depthmapview.cpp @@ -400,7 +400,7 @@ void QDepthmapView::paintEvent(QPaintEvent *) rect = QRect(0, 0, width(), height()); m_redraw = true; - if (!m_viewport_set && state & (MetaGraph::LINEDATA | MetaGraph::SHAPEGRAPHS | MetaGraph::DATAMAPS)) { + if (!m_viewport_set && state & (MetaGraph::LINEDATA | MetaGraph::SHAPEGRAPHS | MetaGraph::DATAMAPS | MetaGraph::TRACEMAPS)) { InitViewport(rect, &m_pDoc); } if (m_redraw_all) { @@ -421,7 +421,7 @@ void QDepthmapView::paintEvent(QPaintEvent *) m_internal_redraw = false; // if redraw signalled: - if (m_redraw && (state & (MetaGraph::LINEDATA | MetaGraph::SHAPEGRAPHS | MetaGraph::DATAMAPS)) && m_viewport_set) { + if (m_redraw && (state & (MetaGraph::LINEDATA | MetaGraph::SHAPEGRAPHS | MetaGraph::DATAMAPS | MetaGraph::TRACEMAPS)) && m_viewport_set) { // note that the redraw rect is dependent on the cleared portion above // note you *must* check *state* before drawing, you cannot rely on view_class as it can be set up before the layer is ready to draw: @@ -438,6 +438,9 @@ void QDepthmapView::paintEvent(QPaintEvent *) if (state & MetaGraph::DATAMAPS && (m_pDoc.m_meta_graph->getViewClass() & (MetaGraph::VIEWBACKDATA | MetaGraph::VIEWDATA))) { m_pDoc.m_meta_graph->getDisplayedDataMap().makeViewportShapes( LogicalViewport(rect, &m_pDoc) ); } + if (state & MetaGraph::TRACEMAPS && (m_pDoc.m_meta_graph->getViewClass() & (MetaGraph::VIEWBACKTRACES | MetaGraph::VIEWTRACES))) { + m_pDoc.m_meta_graph->getDisplayedTraceMap().makeViewportShapes( LogicalViewport(rect, &m_pDoc) ); + } if (state & MetaGraph::LINEDATA) { m_pDoc.m_meta_graph->makeViewportShapes( LogicalViewport(rect, &m_pDoc) ); } @@ -453,7 +456,7 @@ void QDepthmapView::paintEvent(QPaintEvent *) } // If the meta graph (at least) contains a DXF, draw it: - if (m_continue_drawing && (state & (MetaGraph::LINEDATA | MetaGraph::SHAPEGRAPHS | MetaGraph::DATAMAPS)) && m_viewport_set) + if (m_continue_drawing && (state & (MetaGraph::LINEDATA | MetaGraph::SHAPEGRAPHS | MetaGraph::DATAMAPS | MetaGraph::TRACEMAPS)) && m_viewport_set) { if (Output(&pDC, &m_pDoc, true)) { @@ -1744,6 +1747,9 @@ void QDepthmapView::OutputEPS( std::ofstream& stream, QGraphDoc *pDoc, bool incl if (state & MetaGraph::DATAMAPS) { pDoc->m_meta_graph->getDisplayedDataMap().makeViewportShapes( logicalviewport ); } + if (state & MetaGraph::TRACEMAPS) { + pDoc->m_meta_graph->getDisplayedTraceMap().makeViewportShapes( logicalviewport ); + } if (state & MetaGraph::LINEDATA) { pDoc->m_meta_graph->makeViewportShapes( logicalviewport ); } @@ -2379,6 +2385,9 @@ void QDepthmapView::OnEditCopy() if (state & MetaGraph::DATAMAPS && (m_pDoc.m_meta_graph->getViewClass() & (MetaGraph::VIEWBACKDATA | MetaGraph::VIEWDATA))) { m_pDoc.m_meta_graph->getDisplayedDataMap().makeViewportShapes( LogicalViewport(rectin, &m_pDoc) ); } + if (state & MetaGraph::TRACEMAPS && (m_pDoc.m_meta_graph->getViewClass() & (MetaGraph::VIEWBACKTRACES | MetaGraph::VIEWTRACES))) { + m_pDoc.m_meta_graph->getDisplayedTraceMap().makeViewportShapes( LogicalViewport(rectin, &m_pDoc) ); + } if (state & MetaGraph::LINEDATA) { m_pDoc.m_meta_graph->makeViewportShapes( LogicalViewport(rectin, &m_pDoc) ); } diff --git a/depthmapX/views/glview/glview.cpp b/depthmapX/views/glview/glview.cpp index 523baeae..63e28858 100644 --- a/depthmapX/views/glview/glview.cpp +++ b/depthmapX/views/glview/glview.cpp @@ -65,6 +65,9 @@ GLView::GLView(QGraphDoc &pDoc, m_visibleDataMap.loadGLObjects(m_pDoc.m_meta_graph->getDisplayedDataMap()); } + if(m_pDoc.m_meta_graph->getViewClass() & MetaGraph::VIEWTRACES) { + m_visibleTraceMap.loadGLObjects(m_pDoc.m_meta_graph->getDisplayedTraceMap()); + } m_dragLine.setStrokeColour(m_foreground); m_selectionRect.setStrokeColour(m_background); @@ -85,6 +88,7 @@ GLView::~GLView() m_visiblePointMap.cleanup(); m_visibleShapeGraph.cleanup(); m_visibleDataMap.cleanup(); + m_visibleTraceMap.cleanup(); m_hoveredShapes.cleanup(); doneCurrent(); m_settings.writeSetting(SettingTag::depthmapViewSize, size()); @@ -112,6 +116,7 @@ void GLView::initializeGL() m_visiblePointMap.initializeGL(m_core); m_visibleShapeGraph.initializeGL(m_core); m_visibleDataMap.initializeGL(m_core); + m_visibleTraceMap.initializeGL(m_core); m_hoveredShapes.initializeGL(m_core); m_hoveredPixels.initializeGL(m_core); @@ -149,6 +154,11 @@ void GLView::paintGL() m_visibleDataMap.updateGL(m_core); } + if(m_pDoc.m_meta_graph->getViewClass() & MetaGraph::VIEWTRACES) { + m_visibleTraceMap.loadGLObjects(m_pDoc.m_meta_graph->getDisplayedTraceMap()); + m_visibleTraceMap.updateGL(m_core); + } + if(m_pDoc.m_meta_graph->getViewClass() & MetaGraph::VIEWVGA) { m_visiblePointMap.loadGLObjects(m_pDoc.m_meta_graph->getDisplayedPointMap()); m_visiblePointMap.updateGL(m_core); @@ -181,6 +191,10 @@ void GLView::paintGL() glLineWidth(1); } + if(m_pDoc.m_meta_graph->getViewClass() & MetaGraph::VIEWTRACES) { + m_visibleTraceMap.paintGL(m_mProj, m_mView, m_mModel); + } + m_visibleDrawingLines.paintGL(m_mProj, m_mView, m_mModel); if(m_pDoc.m_meta_graph->getViewClass() & MetaGraph::VIEWVGA) { diff --git a/depthmapX/views/glview/glview.h b/depthmapX/views/glview/glview.h index 1503b842..0586de2a 100644 --- a/depthmapX/views/glview/glview.h +++ b/depthmapX/views/glview/glview.h @@ -100,6 +100,7 @@ class GLView : public MapView, protected QOpenGLFunctions GLLinesUniform m_visibleDrawingLines; GLPointMap m_visiblePointMap; GLShapeMap m_visibleDataMap; + GLShapeMap m_visibleTraceMap; bool m_highlightOnHover = true; bool m_hoverHasShapes = false; diff --git a/salalib/CMakeLists.txt b/salalib/CMakeLists.txt index 0ca8d68d..30552789 100644 --- a/salalib/CMakeLists.txt +++ b/salalib/CMakeLists.txt @@ -27,7 +27,8 @@ set(salalib_SRCS tidylines.cpp mapconverter.cpp importutils.cpp - attributetableindex.cpp) + attributetableindex.cpp + tracemap.cpp) add_compile_definitions(_DEPTHMAP SALALIB_LIBRARY) diff --git a/salalib/mgraph.cpp b/salalib/mgraph.cpp index eb78f5db..1d4924cf 100644 --- a/salalib/mgraph.cpp +++ b/salalib/mgraph.cpp @@ -95,6 +95,9 @@ QtRegion MetaGraph::getBoundingBox() const if (bounds.atZero() && ((getState() & MetaGraph::DATAMAPS) == MetaGraph::DATAMAPS)) { bounds = getDisplayedDataMap().getRegion(); } + if (bounds.atZero() && ((getState() & MetaGraph::TRACEMAPS) == MetaGraph::TRACEMAPS)) { + bounds = getDisplayedTraceMap().getRegion(); + } return bounds; } @@ -109,6 +112,8 @@ bool MetaGraph::setViewClass(int command) return false; if ((command & (SHOWHIDESHAPE | SHOWSHAPETOP)) && (~m_state & DATAMAPS)) return false; + if ((command & (SHOWHIDETRACES | SHOWTRACESTOP)) && (~m_state & TRACEMAPS)) + return false; switch (command) { case SHOWHIDEVGA: if (m_view_class & (VIEWVGA | VIEWBACKVGA)) { @@ -119,9 +124,12 @@ bool MetaGraph::setViewClass(int command) else if (m_view_class & VIEWBACKDATA) { m_view_class ^= (VIEWDATA | VIEWBACKDATA); } + else if (m_view_class & VIEWBACKTRACES) { + m_view_class ^= (VIEWTRACES | VIEWBACKTRACES); + } } - else if (m_view_class & (VIEWAXIAL | VIEWDATA)) { - m_view_class &= ~(VIEWBACKAXIAL | VIEWBACKDATA); + else if (m_view_class & (VIEWAXIAL | VIEWDATA | VIEWTRACES)) { + m_view_class &= ~(VIEWBACKAXIAL | VIEWBACKDATA | VIEWBACKTRACES); m_view_class |= VIEWBACKVGA; } else { @@ -137,9 +145,12 @@ bool MetaGraph::setViewClass(int command) else if (m_view_class & VIEWBACKDATA) { m_view_class ^= (VIEWDATA | VIEWBACKDATA); } + else if (m_view_class & VIEWBACKTRACES) { + m_view_class ^= (VIEWTRACES | VIEWBACKTRACES); + } } - else if (m_view_class & (VIEWVGA | VIEWDATA)) { - m_view_class &= ~(VIEWBACKVGA | VIEWBACKDATA); + else if (m_view_class & (VIEWVGA | VIEWDATA | VIEWTRACES)) { + m_view_class &= ~(VIEWBACKVGA | VIEWBACKDATA | VIEWBACKTRACES); m_view_class |= VIEWBACKAXIAL; } else { @@ -155,15 +166,39 @@ bool MetaGraph::setViewClass(int command) else if (m_view_class & VIEWBACKAXIAL) { m_view_class ^= (VIEWAXIAL | VIEWBACKAXIAL); } + else if (m_view_class & VIEWBACKTRACES) { + m_view_class ^= (VIEWTRACES | VIEWBACKTRACES); + } } - else if (m_view_class & (VIEWVGA | VIEWAXIAL)) { - m_view_class &= ~(VIEWBACKVGA | VIEWBACKAXIAL); + else if (m_view_class & (VIEWVGA | VIEWAXIAL | VIEWTRACES)) { + m_view_class &= ~(VIEWBACKVGA | VIEWBACKAXIAL | VIEWBACKTRACES); m_view_class |= VIEWBACKDATA; } else { m_view_class |= VIEWDATA; } break; + case SHOWHIDETRACES: + if (m_view_class & (VIEWTRACES | VIEWBACKTRACES)) { + m_view_class &= ~(VIEWTRACES | VIEWBACKTRACES); + if (m_view_class & VIEWBACKVGA) { + m_view_class ^= (VIEWVGA | VIEWBACKVGA); + } + else if (m_view_class & VIEWBACKAXIAL) { + m_view_class ^= (VIEWAXIAL | VIEWBACKAXIAL); + } + else if (m_view_class & VIEWBACKDATA) { + m_view_class ^= (VIEWDATA | VIEWBACKDATA); + } + } + else if (m_view_class & (VIEWVGA | VIEWAXIAL | VIEWDATA)) { + m_view_class &= ~(VIEWBACKVGA | VIEWBACKAXIAL | VIEWBACKDATA); + m_view_class |= VIEWBACKTRACES; + } + else { + m_view_class |= VIEWTRACES; + } + break; case SHOWVGATOP: if (m_view_class & VIEWAXIAL) { m_view_class = VIEWBACKAXIAL | VIEWVGA; @@ -171,8 +206,11 @@ bool MetaGraph::setViewClass(int command) else if (m_view_class & VIEWDATA) { m_view_class = VIEWBACKDATA | VIEWVGA; } + else if (m_view_class & VIEWTRACES) { + m_view_class = VIEWBACKTRACES | VIEWVGA; + } else { - m_view_class = VIEWVGA | (m_view_class & (VIEWBACKAXIAL | VIEWBACKDATA)); + m_view_class = VIEWVGA | (m_view_class & (VIEWBACKAXIAL | VIEWBACKDATA | VIEWBACKTRACES)); } break; case SHOWAXIALTOP: @@ -182,8 +220,11 @@ bool MetaGraph::setViewClass(int command) else if (m_view_class & VIEWDATA) { m_view_class = VIEWBACKDATA | VIEWAXIAL; } + else if (m_view_class & VIEWTRACES) { + m_view_class = VIEWBACKTRACES | VIEWAXIAL; + } else { - m_view_class = VIEWAXIAL | (m_view_class & (VIEWBACKVGA | VIEWBACKDATA)); + m_view_class = VIEWAXIAL | (m_view_class & (VIEWBACKVGA | VIEWBACKDATA | VIEWBACKTRACES)); } break; case SHOWSHAPETOP: @@ -193,8 +234,25 @@ bool MetaGraph::setViewClass(int command) else if (m_view_class & VIEWAXIAL) { m_view_class = VIEWBACKAXIAL | VIEWDATA; } + else if (m_view_class & VIEWTRACES) { + m_view_class = VIEWBACKTRACES | VIEWDATA; + } + else { + m_view_class = VIEWDATA | (m_view_class & (VIEWBACKVGA | VIEWBACKAXIAL | VIEWBACKTRACES)); + } + break; + case SHOWTRACESTOP: + if (m_view_class & VIEWVGA) { + m_view_class = VIEWBACKVGA | VIEWTRACES; + } + else if (m_view_class & VIEWAXIAL) { + m_view_class = VIEWBACKAXIAL | VIEWTRACES; + } + else if (m_view_class & VIEWDATA) { + m_view_class = VIEWBACKDATA | VIEWTRACES; + } else { - m_view_class = VIEWDATA | (m_view_class & (VIEWBACKVGA | VIEWBACKAXIAL)); + m_view_class = VIEWTRACES | (m_view_class & (VIEWBACKVGA | VIEWBACKAXIAL | VIEWBACKDATA)); } break; } @@ -215,6 +273,9 @@ double MetaGraph::getLocationValue(const Point2f& point) else if (viewingProcessedShapes()) { val = getDisplayedDataMap().getLocationValue(point); } + else if (viewingProcessedTraces()) { + val = getDisplayedTraceMap().getLocationValue(point); + } return val; } @@ -384,6 +445,9 @@ bool MetaGraph::isEditableMap() else if (m_view_class & VIEWDATA) { return getDisplayedDataMap().isEditable(); } + else if (m_view_class & VIEWTRACES) { + return getDisplayedDataMap().isEditable(); + } // still to do: allow editing of drawing layers return false; } @@ -397,6 +461,9 @@ ShapeMap& MetaGraph::getEditableMap() else if (m_view_class & VIEWDATA) { map = &(getDisplayedDataMap()); } + else if (m_view_class & VIEWTRACES) { + map = &(getDisplayedDataMap()); + } else { // still to do: allow editing of drawing layers } @@ -482,6 +549,21 @@ bool MetaGraph::moveSelShape(const Line& line) map.clearSel(); } } + else if (m_view_class & VIEWTRACES) { + TraceMap& map = getDisplayedTraceMap(); + if (!map.isEditable()) { + return false; + } + if (map.getSelCount() > 1) { + return false; + } + // note, selection sets currently store rowids not uids, but moveShape sensibly works off uid: + int rowid = *map.getSelSet().begin(); + shapeMoved = map.moveShape(map.getIndex(rowid),line); + if (shapeMoved) { + map.clearSel(); + } + } return shapeMoved; } @@ -548,6 +630,9 @@ int MetaGraph::makeIsovistPath(Communicator *communicator, double fov, bool simp else if (viewclass == VIEWDATA) { map = &getDisplayedDataMap(); } + else if (viewclass == VIEWTRACES) { + map = &getDisplayedDataMap(); + } else { return 0; } @@ -749,6 +834,13 @@ void MetaGraph::removeDisplayedMap() m_state &= ~DATAMAPS; } break; + case VIEWTRACES: + removeTraceMap(ref); + if (m_traceMaps.empty()) { + setViewClass(SHOWHIDETRACES); + m_state &= ~TRACEMAPS; + } + break; } } @@ -2000,15 +2092,14 @@ void MetaGraph::runAgentEngine(Communicator *comm) if(m_agent_engine.m_record_trails) { std::string mapName = "Agent Trails"; int count = 1; - while(std::find_if(std::begin(m_dataMaps), std::end(m_dataMaps), - [&] (ShapeMap const& m) {return m.getName() == mapName; }) != m_dataMaps.end()) { + while(std::find_if(std::begin(m_traceMaps), std::end(m_traceMaps), + [&] (ShapeMap const& m) {return m.getName() == mapName; }) != m_traceMaps.end()) { mapName = "Agent Trails " + std::to_string(count); count++; } - m_dataMaps.emplace_back(mapName); - m_agent_engine.insertTrailsInMap(m_dataMaps.back()); - - m_state |= DATAMAPS; + m_traceMaps.push_back(mapName); + m_agent_engine.insertTrailsInMap(m_traceMaps.back()); + m_state |= TRACEMAPS; } if (m_agent_engine.m_gatelayer != -1) { @@ -2086,6 +2177,9 @@ int MetaGraph::getDisplayedMapRef() const case VIEWDATA: ref = getDisplayedDataMapRef(); break; + case VIEWTRACES: + ref = getDisplayedTraceMapRef(); + break; } return ref; } @@ -2102,6 +2196,8 @@ int MetaGraph::getDisplayedMapType() return getDisplayedShapeGraph().getMapType(); case VIEWDATA: return getDisplayedDataMap().getMapType(); + case VIEWTRACES: + return getDisplayedTraceMap().getMapType(); } return ShapeMap::EMPTYMAP; } @@ -2157,6 +2253,9 @@ int MetaGraph::isEditable() const case VIEWDATA: editable = getDisplayedDataMap().isEditable() ? EDITABLE_ON : EDITABLE_OFF; break; + case VIEWTRACES: + editable = getDisplayedTraceMap().isEditable() ? EDITABLE_ON : EDITABLE_OFF; + break; } return editable; } @@ -2174,6 +2273,9 @@ bool MetaGraph::canUndo() const case VIEWDATA: canundo = getDisplayedDataMap().canUndo(); break; + case VIEWTRACES: + canundo = getDisplayedTraceMap().canUndo(); + break; } return canundo; } @@ -2190,6 +2292,9 @@ void MetaGraph::undo() case VIEWDATA: getDisplayedDataMap().undo(); break; + case VIEWTRACES: + getDisplayedTraceMap().undo(); + break; } } @@ -2208,6 +2313,9 @@ int MetaGraph::addAttribute(const std::string& name) case VIEWDATA: col = getDisplayedDataMap().addAttribute(name); break; + case VIEWTRACES: + col = getDisplayedTraceMap().addAttribute(name); + break; } return col; } @@ -2224,6 +2332,9 @@ void MetaGraph::removeAttribute(int col) case VIEWDATA: getDisplayedDataMap().removeAttribute(col); break; + case VIEWTRACES: + getDisplayedTraceMap().removeAttribute(col); + break; } } @@ -2245,6 +2356,9 @@ int MetaGraph::getDisplayedAttribute() const case VIEWDATA: col = getDisplayedDataMap().getDisplayedAttribute(); break; + case VIEWTRACES: + col = getDisplayedTraceMap().getDisplayedAttribute(); + break; } return col; } @@ -2265,6 +2379,10 @@ void MetaGraph::setDisplayedAttribute(int col) getDisplayedDataMap().overrideDisplayedAttribute(-2); getDisplayedDataMap().setDisplayedAttribute(col); break; + case VIEWTRACES: + getDisplayedTraceMap().overrideDisplayedAttribute(-2); + getDisplayedTraceMap().setDisplayedAttribute(col); + break; } } @@ -2286,6 +2404,9 @@ AttributeTable& MetaGraph::getAttributeTable(int type, int layer) case VIEWDATA: tab = (layer == -1) ? &(getDisplayedDataMap().getAttributeTable()) : &(m_dataMaps[layer].getAttributeTable()); break; + case VIEWTRACES: + tab = (layer == -1) ? &(getDisplayedTraceMap().getAttributeTable()) : &(m_traceMaps[layer].getAttributeTable()); + break; } return *tab; } @@ -2306,6 +2427,9 @@ const AttributeTable& MetaGraph::getAttributeTable(int type, int layer) const case VIEWDATA: tab = layer == -1 ? &(getDisplayedDataMap().getAttributeTable()) : &(m_dataMaps[layer].getAttributeTable()); break; + case VIEWTRACES: + tab = layer == -1 ? &(getDisplayedTraceMap().getAttributeTable()) : &(m_traceMaps[layer].getAttributeTable()); + break; } return *tab; } @@ -2808,6 +2932,53 @@ bool MetaGraph::writeDataMaps( std::ofstream& stream, bool displayedmaponly ) } +bool MetaGraph::readTraceMaps(std::istream& stream) +{ + m_traceMaps.clear(); // empty existing data + // n.b. -- do not change to size_t as will cause 32-bit to 64-bit conversion problems + unsigned int displayed_map; + stream.read((char *)&displayed_map,sizeof(displayed_map)); + m_displayed_tracemap = size_t(displayed_map); + // read maps + // n.b. -- do not change to size_t as will cause 32-bit to 64-bit conversion problems + unsigned int count = 0; + stream.read((char *) &count, sizeof(count)); + + for (size_t j = 0; j < size_t(count); j++) { + m_traceMaps.emplace_back(); + m_traceMaps.back().read(stream); + } + return true; +} + +bool MetaGraph::writeTraceMaps( std::ofstream& stream, bool displayedmaponly ) +{ + if (!displayedmaponly) { + // n.b. -- do not change to size_t as will cause 32-bit to 64-bit conversion problems + unsigned int displayed_map = (unsigned int)(m_displayed_tracemap); + stream.write((char *)&displayed_map,sizeof(displayed_map)); + // write maps + // n.b. -- do not change to size_t as will cause 32-bit to 64-bit conversion problems + unsigned int count = (unsigned int) m_traceMaps.size(); + stream.write((char *) &count, sizeof(count)); + for (size_t j = 0; j < count; j++) { + m_traceMaps[j].write(stream); + } + } + else { + unsigned int dummy; + // displayed map is 0 + dummy = 0; + stream.write((char *)&dummy,sizeof(dummy)); + // count is 1 + dummy = 1; + stream.write((char *)&dummy,sizeof(dummy)); + // write map: + m_traceMaps[m_displayed_tracemap].write(stream); + } + return true; +} + bool MetaGraph::readShapeGraphs(std::istream& stream) { m_shapeGraphs.clear(); // empty existing data diff --git a/salalib/mgraph.h b/salalib/mgraph.h index 71bc3f32..44c6c392 100644 --- a/salalib/mgraph.h +++ b/salalib/mgraph.h @@ -23,6 +23,7 @@ #include "salalib/displayparams.h" #include "salalib/fileproperties.h" #include "salalib/importtypedefs.h" +#include "salalib/tracemap.h" // still call paftl: #include "salalib/connector.h" @@ -69,7 +70,7 @@ class MetaGraph : public FileProperties enum { ADD = 0x0001, REPLACE = 0x0002, CAT = 0x0010, DXF = 0x0020, NTF = 0x0040, RT1 = 0x0080, GML = 0x0100 }; enum { NONE = 0x0000, POINTMAPS = 0x0002, LINEDATA = 0x0004, - ANGULARGRAPH = 0x0010, DATAMAPS = 0x0020, AXIALLINES = 0x0040, SHAPEGRAPHS = 0x0100, + ANGULARGRAPH = 0x0010, DATAMAPS = 0x0020, AXIALLINES = 0x0040, SHAPEGRAPHS = 0x0100, TRACEMAPS = 0x0200, BUGGY = 0x8000 }; enum { NOT_EDITABLE = 0, EDITABLE_OFF = 1, EDITABLE_ON = 2 }; protected: @@ -86,6 +87,8 @@ class MetaGraph : public FileProperties std::vector> m_shapeGraphs; int m_displayed_shapegraph = -1; + std::vector m_traceMaps; + public: MetaGraph(std::string name = ""); ~MetaGraph(); @@ -235,6 +238,25 @@ class MetaGraph : public FileProperties m_displayed_datamap = map; } + + size_t m_displayed_tracemap = -1; + TraceMap& getDisplayedTraceMap() + { return m_traceMaps[m_displayed_tracemap]; } + const TraceMap& getDisplayedTraceMap() const + { return m_traceMaps[m_displayed_tracemap]; } + size_t getDisplayedTraceMapRef() const + { return m_displayed_tracemap; } + + void removeTraceMap(int i) + { if (m_displayed_tracemap >= i) m_displayed_tracemap--; m_traceMaps.erase(m_traceMaps.begin() + i); } + + void setDisplayedTraceMapRef(size_t map) + { + if (m_displayed_tracemap != -1 && m_displayed_tracemap != map) + m_traceMaps[m_displayed_tracemap].clearSel(); + m_displayed_tracemap = map; + } + template size_t getMapRef(std::vector& maps, const std::string& name) const { @@ -277,6 +299,12 @@ class MetaGraph : public FileProperties bool readDataMaps(std::istream &stream); bool writeDataMaps(std::ofstream& stream, bool displayedmaponly = false ); + std::vector& getTraceMaps() + { return m_traceMaps; } + + bool readTraceMaps(std::istream &stream); + bool writeTraceMaps(std::ofstream& stream, bool displayedmaponly = false ); + // int getDisplayedMapType(); AttributeTable& getDisplayedMapAttributes(); @@ -330,9 +358,10 @@ class MetaGraph : public FileProperties protected: int m_view_class; public: - enum { SHOWHIDEVGA = 0x0100, SHOWVGATOP = 0x0200, SHOWHIDEAXIAL = 0x0400, SHOWAXIALTOP = 0x0800, SHOWHIDESHAPE = 0x1000, SHOWSHAPETOP = 0x2000 }; + enum { SHOWHIDEVGA = 0x0100, SHOWVGATOP = 0x0200, SHOWHIDEAXIAL = 0x0400, SHOWAXIALTOP = 0x0800, SHOWHIDESHAPE = 0x1000, SHOWSHAPETOP = 0x2000, + SHOWHIDETRACES = 0x4000, SHOWTRACESTOP = 0x8000 }; enum { VIEWNONE = 0x00, VIEWVGA = 0x01, VIEWBACKVGA = 0x02, VIEWAXIAL = 0x04, VIEWBACKAXIAL = 0x08, - VIEWDATA = 0x20, VIEWBACKDATA = 0x40, VIEWFRONT = 0x25 }; + VIEWDATA = 0x20, VIEWBACKDATA = 0x40, VIEWTRACES = 0x80, VIEWBACKTRACES = 0x100, VIEWFRONT = 0xA5 }; // int getViewClass() { return m_view_class; } @@ -340,13 +369,15 @@ class MetaGraph : public FileProperties bool viewingNone() { return (m_view_class == VIEWNONE); } bool viewingProcessed() - { return ((m_view_class & (VIEWAXIAL | VIEWDATA)) || (m_view_class & VIEWVGA && getDisplayedPointMap().isProcessed())); } + { return ((m_view_class & (VIEWAXIAL | VIEWDATA | VIEWTRACES)) || (m_view_class & VIEWVGA && getDisplayedPointMap().isProcessed())); } bool viewingShapes() - { return (m_view_class & (VIEWAXIAL | VIEWDATA)) != 0; } + { return (m_view_class & (VIEWAXIAL | VIEWDATA | VIEWTRACES)) != 0; } bool viewingProcessedLines() { return ((m_view_class & VIEWAXIAL) == VIEWAXIAL); } bool viewingProcessedShapes() { return ((m_view_class & VIEWDATA) == VIEWDATA); } + bool viewingProcessedTraces() + { return ((m_view_class & VIEWTRACES) == VIEWTRACES); } bool viewingProcessedPoints() { return ((m_view_class & VIEWVGA) && getDisplayedPointMap().isProcessed()); } bool viewingUnprocessedPoints() @@ -363,8 +394,10 @@ class MetaGraph : public FileProperties return getDisplayedPointMap().isSelected(); else if (m_view_class & VIEWAXIAL) return getDisplayedShapeGraph().hasSelectedElements(); - else if (m_view_class & VIEWDATA) - return getDisplayedDataMap().hasSelectedElements(); + else if (m_view_class & VIEWDATA) + return getDisplayedDataMap().hasSelectedElements(); + else if (m_view_class & VIEWTRACES) + return getDisplayedTraceMap().hasSelectedElements(); else return false; } @@ -373,6 +406,8 @@ class MetaGraph : public FileProperties return getDisplayedShapeGraph().setCurSel(r, add); else if (m_view_class & VIEWDATA) return getDisplayedDataMap().setCurSel( r, add ); + else if (m_view_class & VIEWTRACES) + return getDisplayedTraceMap().setCurSel( r, add ); else if (m_view_class & VIEWVGA) return getDisplayedPointMap().setCurSel( r, add ); else if (m_state & POINTMAPS && !getDisplayedPointMap().isProcessed()) // this is a default select application @@ -391,6 +426,8 @@ class MetaGraph : public FileProperties return getDisplayedShapeGraph().clearSel(); else if (m_view_class & VIEWDATA) return getDisplayedDataMap().clearSel(); + else if (m_view_class & VIEWTRACES) + return getDisplayedTraceMap().clearSel(); else return false; } @@ -400,8 +437,10 @@ class MetaGraph : public FileProperties return getDisplayedPointMap().getSelCount(); else if (m_view_class & VIEWAXIAL) return (int) getDisplayedShapeGraph().getSelCount(); - else if (m_view_class & VIEWDATA) + else if (m_view_class & VIEWDATA) return (int) getDisplayedDataMap().getSelCount(); + else if (m_view_class & VIEWTRACES) + return (int) getDisplayedTraceMap().getSelCount(); else return 0; } @@ -409,10 +448,12 @@ class MetaGraph : public FileProperties { if (m_view_class & VIEWVGA) return (float)getDisplayedPointMap().getDisplayedSelectedAvg(); - else if (m_view_class & VIEWAXIAL) + else if (m_view_class & VIEWAXIAL) return (float)getDisplayedShapeGraph().getDisplayedSelectedAvg(); else if (m_view_class & VIEWDATA) return (float)getDisplayedDataMap().getDisplayedSelectedAvg(); + else if (m_view_class & VIEWTRACES) + return (float)getDisplayedTraceMap().getDisplayedSelectedAvg(); else return -1.0f; } @@ -422,8 +463,10 @@ class MetaGraph : public FileProperties return getDisplayedPointMap().getSelBounds(); else if (m_view_class & VIEWAXIAL) return getDisplayedShapeGraph().getSelBounds(); - else if (m_view_class & VIEWDATA) + else if (m_view_class & VIEWDATA) return getDisplayedDataMap().getSelBounds(); + else if (m_view_class & VIEWTRACES) + return getDisplayedTraceMap().getSelBounds(); else return QtRegion(); } @@ -433,6 +476,8 @@ class MetaGraph : public FileProperties getDisplayedPointMap().setCurSel(selset,add); else if (m_view_class & VIEWAXIAL) getDisplayedShapeGraph().setCurSel(selset,add); + else if (m_view_class & VIEWTRACES) + getDisplayedTraceMap().setCurSel(selset,add); else // if (m_view_class & VIEWDATA) getDisplayedDataMap().setCurSel(selset,add); } std::set& getSelSet() @@ -440,6 +485,8 @@ class MetaGraph : public FileProperties return getDisplayedPointMap().getSelSet(); else if (m_view_class & VIEWAXIAL) return getDisplayedShapeGraph().getSelSet(); + else if (m_view_class & VIEWTRACES) + return getDisplayedTraceMap().getSelSet(); else // if (m_view_class & VIEWDATA) return getDisplayedDataMap().getSelSet(); } const std::set& getSelSet() const @@ -447,6 +494,8 @@ class MetaGraph : public FileProperties return getDisplayedPointMap().getSelSet(); else if (m_view_class & VIEWAXIAL) return getDisplayedShapeGraph().getSelSet(); + else if (m_view_class & VIEWTRACES) + return getDisplayedTraceMap().getSelSet(); else // if (m_view_class & VIEWDATA) return getDisplayedDataMap().getSelSet(); } // diff --git a/salalib/nagent.cpp b/salalib/nagent.cpp new file mode 100644 index 00000000..d5bca48d --- /dev/null +++ b/salalib/nagent.cpp @@ -0,0 +1,1374 @@ +// sala - a component of the depthmapX - spatial network analysis platform +// Copyright (C) 2011-2012, Tasos Varoudis + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + + +// a simple agent demonstration for Salad (now within depthmapX) + +#include "salalib/pointdata.h" +#include "salalib/nagent.h" +#include "salalib/ngraph.h" + +#include "genlib/comm.h" +#include "genlib/stringutils.h" +#include "genlib/containerutils.h" +#include "genlib/paftl.h" + +int thisrun = 0; + +/////////////////////////////////////////////////////////////////////////////////////////////// + +static int rankselect(int popsize) +{ + int num = int( prandom() * popsize * (popsize + 1) * 0.5 ); + for (int i = 0; i < popsize; i++) { + if (num < (popsize - i)) { + return i; + } + num -= (popsize - i); + } + return 0; // <- this shouldn't happen +} + +// note: this is tested and right: higher fitness, lower rank (so population[0] is best) +int progcompare(const void *a, const void *b ) +{ + double test = (((AgentProgram *)a)->m_fitness - ((AgentProgram *)b)->m_fitness); + if (test < 0.0) { + return 1; + } + else if (test > 0.0) { + return -1; + } + return 0; +} + +// + + +/////////////////////////////////////////////////////////////////////////////////////////////// + +// run one agent engine only + +AgentEngine::AgentEngine() +{ + m_timesteps = 5000; + m_gatelayer = -1; + m_record_trails = false; +} + +void AgentEngine::run(Communicator *comm, PointMap *pointmap) +{ + for (auto& agentSet: agentSets) { + if(agentSet.m_sel_type == AgentProgram::SEL_LOS_OCC) { + pointmap->requireIsovistAnalysis(); + } + } + time_t atime = 0; + if (comm) { + qtimer( atime, 0 ); + comm->CommPostMessage( Communicator::NUM_RECORDS, m_timesteps ); + } + + AttributeTable& table = pointmap->getAttributeTable(); + int displaycol = table.getOrInsertColumn(g_col_total_counts); + + int output_mode = Agent::OUTPUT_COUNTS; + if (m_gatelayer != -1) { + output_mode |= Agent::OUTPUT_GATE_COUNTS; + } + + int trail_num = -1; + if (m_record_trails) { + if (m_trail_count < 1) { + m_trail_count = 1; + } + for (auto& agentSet: agentSets) { + agentSet.m_trails = std::vector>(m_trail_count); + } + trail_num = 0; + } else { + for (auto& agentSet: agentSets) { + // remove any agent trails that are left from a previous run + agentSet.m_trails.clear(); + } + } + + // remove any agents that are left from a previous run + for (auto& agentSet: agentSets) { + agentSet.agents.clear(); + } + + for (int i = 0; i < m_timesteps; i++) { + + size_t j; + for (auto& agentSet: agentSets) { + int q = invcumpoisson(prandomr(),agentSet.m_release_rate); + int length = agentSet.agents.size(); + int k; + for (k = 0; k < q; k++) { + agentSet.agents.push_back(Agent(&(agentSet),pointmap,output_mode)); + } + for (k = 0; k < q; k++) { + agentSet.init(length+k,trail_num); + if (trail_num != -1) { + trail_num++; + // after trail count, stop recording: + if (trail_num == m_trail_count) { + trail_num = -1; + } + } + } + } + + for (auto& agentSet: agentSets) { + agentSet.move(i); + } + + if (comm) { + if (qtimer( atime, 500 )) { + if (comm->IsCancelled()) { + throw Communicator::CancelledException(); + } + comm->CommPostMessage( Communicator::CURRENT_RECORD, i ); + } + } + } + + pointmap->overrideDisplayedAttribute(-2); + pointmap->setDisplayedAttribute(displaycol); +} + +void AgentEngine::insertTrailsInMap(ShapeMap& trailsMap) { + for (auto &agentSet : agentSets) { + // there is currently only one AgentSet. If at any point there are more then + // this could be amended to put the AgentSet id as a property of the trail + for (auto &trail : agentSet.m_trails) { + std::vector trailGeometry(trail.begin(), trail.end()); + trailsMap.makePolyShape(trailGeometry, true, false); + } + } +} + +void AgentEngine::insertTrailsInMap(TraceMap& trailsMap) { + for (auto &agentSet : agentSets) { + // there is currently only one AgentSet. If at any point there are more then + // this could be amended to put the AgentSet id as a property of the trail + for (auto &trail : agentSet.m_trails) { + trailsMap.makeTrace(trail); + } + } + trailsMap.overrideDisplayedAttribute(-2); + trailsMap.setDisplayedAttribute(-1); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +AgentSet::AgentSet() +{ + m_release_rate = 0.1; + m_lifetime = 1000; +} + +void AgentSet::init(int agent, int trail_num) +{ + if (m_release_locations.size()) { + int which = pafrand() % m_release_locations.size(); + agents[agent].onInit( m_release_locations[which], trail_num ); + } + else { + const PointMap& map = agents[agent].getPointMap(); + PixelRef pix; + do { + pix = map.pickPixel(prandom(m_release_locations_seed)); + } while (!map.getPoint(pix).filled()); + agents[agent].onInit(pix, trail_num); + } +} + +void AgentSet::move(int currentTimeStep) +{ + // go through backwards so remove does not affect later agents + for (auto rev_iter = agents.rbegin(); rev_iter != agents.rend(); ++rev_iter) { + rev_iter->onMove(); + rev_iter->onUpdateTrail(currentTimeStep); + if (rev_iter->getFrame() >= m_lifetime) { + agents.erase( std::next(rev_iter).base() ); + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +AgentProgram *ProgramPopulation::makeChild() +{ + int a = rankselect(POPSIZE); + int b = rankselect(POPSIZE); + while (a == b) + b = rankselect(POPSIZE); + m_population[POPSIZE - 1] = crossover(m_population[a],m_population[b]); + m_population[POPSIZE - 1].mutate(); + + return &(m_population[POPSIZE - 1]); +} + +// note: this is correct -- do not use &m_population! +void ProgramPopulation::sort() +{ + qsort(m_population,POPSIZE,sizeof(AgentProgram),progcompare); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +AgentProgram::AgentProgram() +{ + m_sel_type = SEL_LOS; + m_steps = 3; + m_vbin = 7; + m_destination_directed = false; + m_los_sqrd = false; +} + +/* +AgentProgram::AgentProgram() +{ + // random program for evolution: + // m_sel_type = SEL_LENGTH + (pafrand() % 4); + m_sel_type = SEL_OPTIC_FLOW2; + + // non-evolutionary standard agent program + // note m_steps defaults to 0 for use with evolutionary (reevaulate goal every step) + m_steps = 0; + // also note evolutionary does actually use bins when not following other rules: + m_vbin = (pafrand() % 7) + 1; + + // optic flow 2 + //m_vahead = (pafrand() % m_vbin); + //m_ahead_threshold = prandom() * 19.0 + 1.0; + //m_feeler_threshold = prandom() * 4.0 + 1.0; + //m_feeler_probability = prandom(); + + // rule order relies on putting rules into slots: + for (int i = 0; i < 4; i++) { + m_rule_order[i] = -1; + } + for (int j = 0; j < 4; j++) { + int choice = pafrand() % (4 - j); + for (int k = 0; k < choice + 1; k++) { + if (m_rule_order[k] != -1) { + choice++; + } + } + m_rule_order[choice] = j; + } + for (i = 0; i < 4; i++) { + m_rule_threshold[i] = prandom() * 100.0; + m_rule_probability[i] = prandom(); + } + + m_fitness = 0.0; + + m_destination_guided = false; + m_los_sqrd = false; +} +*/ + +void AgentProgram::mutate() +{ + // don't mutate program type +/* + if (pafrand() % 20 == 0) { + m_vbin = (pafrand() % 7) + 1; + } + if (pafrand() % 20 == 0) { + m_vahead = (pafrand() % m_vbin); + } + if (pafrand() % 20 == 0) { + m_ahead_threshold = prandom() * 19.0 + 1.0; + } + if (pafrand() % 20 == 0) { + m_feeler_threshold = prandom() * 4.0 + 1.0; + } + if (pafrand() % 20 == 0) { + m_feeler_probability = prandom(); + } +*/ + + // do mutate rule order occassionally: + if (pafrand() % 20 == 0) { + // rule order relies on putting rules into slots: + for (int i = 0; i < 4; i++) { + m_rule_order[i] = -1; + } + for (int j = 0; j < 4; j++) { + int choice = pafrand() % (4 - j); + for (int k = 0; k < choice + 1; k++) { + if (m_rule_order[k] != -1) { + choice++; + } + } + m_rule_order[choice] = j; + } + } + // mutate the rule threshold / probabilities + for (int i = 0; i < 4; i++) { + if (pafrand() % 20 == 0) { // 5% mutation rate + m_rule_threshold[i] = float(prandom() * 100.0); + } + if (pafrand() % 20 == 0) { // 5% mutation rate + m_rule_probability[i] = float(prandom()); + } + } + +} + +AgentProgram crossover(const AgentProgram& prog_a, const AgentProgram& prog_b) +{ + AgentProgram child; + // either one sel type or the other: + /* + if (pafrand() % 2) { + child.m_sel_type = prog_a.m_sel_type; + } + else { + child.m_sel_type = prog_b.m_sel_type; + } + */ + /* + // either one bin radius or the other + if (pafrand() % 2) + child.m_vbin = prog_a.m_vbin; + else + child.m_vbin = prog_b.m_vbin; + if (pafrand() % 2) + child.m_vahead = prog_a.m_vahead; + else + child.m_vahead = prog_b.m_vahead; + if (pafrand() % 2) + child.m_ahead_threshold = prog_a.m_ahead_threshold; + else + child.m_ahead_threshold = prog_b.m_ahead_threshold; + if (pafrand() % 2) + child.m_feeler_threshold = prog_a.m_feeler_threshold; + else + child.m_feeler_threshold = prog_b.m_feeler_threshold; + if (pafrand() % 2) + child.m_feeler_probability = prog_a.m_feeler_probability; + else + child.m_feeler_probability = prog_b.m_feeler_probability; +*/ + + // either one rule priority order or the other (don't try to mix!) + if (pafrand() % 2) { + for (int i = 0; i < 4; i++) { + child.m_rule_order[i] = prog_a.m_rule_order[i]; + } + } + else { + for (int i = 0; i < 4; i++) { + child.m_rule_order[i] = prog_b.m_rule_order[i]; + } + } + // for each rule, either one rule threshold / probability or the other: + for (int i = 0; i < 4; i++) { + if (pafrand() % 2) { + child.m_rule_threshold[i] = prog_a.m_rule_threshold[i]; + } + else { + child.m_rule_threshold[i] = prog_b.m_rule_threshold[i]; + } + if (pafrand() % 2) { + child.m_rule_probability[i] = prog_a.m_rule_probability[i]; + } + else { + child.m_rule_probability[i] = prog_b.m_rule_probability[i]; + } + } + + return child; +} + +// TODO: Expose this functionality to the UIs +void AgentProgram::save(const std::string& filename) +{ + // standard ascii: + std::ofstream file(filename.c_str()); + + file << "Destination selection: "; + switch (m_sel_type) { + case SEL_STANDARD: + file << "Standard" << std::endl; + break; + case SEL_LENGTH: + file << "Gibsonian Length" << std::endl; + break; + case SEL_OPTIC_FLOW: + file << "Gibsonian Optic Flow" << std::endl; + break; + case SEL_COMPARATIVE_LENGTH: + file << "Gibsonian Comparative Length" << std::endl; + break; + case SEL_COMPARATIVE_OPTIC_FLOW: + file << "Gibsonian Comparative Optic Flow" << std::endl; + break; + default: + file << "Unknown" << std::endl; + } + + file << "Steps: " << m_steps << std::endl; + file << "Bins: " << ((m_vbin == -1) ? 32 : m_vbin * 2 + 1) << std::endl; + /* + file << "Ahead bins: " << m_vahead * 2 + 1 << std::endl; + file << "Ahead threshold: " << m_ahead_threshold << std::endl; + file << "Feeler threshold: " << m_feeler_threshold << std::endl; + file << "Feeler probability: " << m_feeler_probability << std::endl; +*/ + file << "Rule order: " << m_rule_order[0] << " " + << m_rule_order[1] << " " + << m_rule_order[2] << " " + << m_rule_order[3] << std::endl; + + for (int i = 0; i < 4; i++) { + file << "Rule " << i << " (Bin -" << 1 + (i * 2) << "/+" << 1 + (i * 2) << ")" << std::endl; + file << "Threshold: " << m_rule_threshold[i] << std::endl; + file << "Turn Probability: " << m_rule_probability[i] << std::endl; + } + + file << "Fitness: " << m_fitness << std::endl; +} + +bool AgentProgram::open(const std::string& filename) +{ + // standard ascii: + std::ifstream file(filename.c_str()); + + std::string line; + file >> line; + if (!line.empty()) { + dXstring::toLower(line); + if (line.substr(0,22) != "destination selection:") { + return false; + } + else { + std::string method = line.substr(22); + dXstring::ltrim(method); + if (!method.empty()) { + if (method == "standard") { + m_sel_type = SEL_STANDARD; + } + else if (method == "gibsonian length") { + m_sel_type = SEL_LENGTH; + } + else if (method == "gibsonian optic flow") { + m_sel_type = SEL_OPTIC_FLOW; + } + else if (method == "gibsonian comparative length") { + m_sel_type = SEL_COMPARATIVE_LENGTH; + } + else if (method == "gibsonian comparative optic flow") { + m_sel_type = SEL_COMPARATIVE_OPTIC_FLOW; + } + file >> line; + } + else { + return false; + } + } + } + else { + return false; + } + + bool foundsteps = false; + bool foundbins = false; + + if (!line.empty()) { + dXstring::toLower(line); + if (line.substr(0,6) == "steps:") { + std::string steps = line.substr(6); + dXstring::ltrim(steps); + m_steps = stoi(steps); + file >> line; + foundsteps = true; + } + } + else { + return false; + } + + if (!line.empty()) { + dXstring::toLower(line); + if (line.substr(0,5) == "bins:") { + std::string bins = line.substr(6); + dXstring::ltrim(bins); + int binx = stoi(bins); + if (binx == 32) { + m_vbin = -1; + } + else { + m_vbin = (atoi(bins.c_str()) - 1) / 2; + } + file >> line; + foundbins = true; + } + } + + if (m_sel_type == SEL_STANDARD) { + if (foundbins && foundsteps) { + return true; + } + else { + return false; + } + } + + if (!line.empty()) { + dXstring::toLower(line); + if (line.substr(0,11) == "rule order:") { + std::string ruleorder = line.substr(11); + dXstring::ltrim(ruleorder); + auto orders = dXstring::split(ruleorder, ' '); + if (orders.size() != 4) { + return false; + } + for (int i = 0; i < 4; i++) { + m_rule_order[i] = stoi(orders[i]); + } + file >> line; + } + else { + return false; + } + } + else { + return false; + } + for (int i = 0; i < 4; i++) { + if (!line.empty()) { + dXstring::toLower(line); + if (line.substr(0,4) == "rule") { + file >> line; + } + dXstring::toLower(line); + if (line.substr(0,10) == "threshold:") { + auto threshold = line.substr(10); + dXstring::ltrim(threshold); + m_rule_threshold[i] = stof(threshold); + file >> line; + } + else { + return false; + } + dXstring::toLower(line); + if (line.substr(0,17) == "turn probability:") { + auto prob = line.substr(17); + dXstring::ltrim(prob); + m_rule_probability[i] = stof(prob); + file >> line; + } + else { + return false; + } + } + else { + return false; + } + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +Agent::Agent(AgentProgram *program, PointMap *pointmap, int output_mode) +{ + m_program = program; + m_pointmap = pointmap; + m_output_mode = output_mode; + m_trail_num = -1; +} + +void Agent::onInit(PixelRef node, int trail_num) +{ + m_node = node; + m_loc = m_pointmap->depixelate(m_node); + if (m_output_mode & OUTPUT_GATE_COUNTS) { + // see note about gates in Through vision analysis + m_gate = (m_pointmap->getPoint(node).filled()) + ? (int)m_pointmap->getAttributeTable().getRow(AttributeKey(m_node)).getValue(g_col_gate) + : -1; + } + else { + m_gate = -1; + } + m_gate_encountered = false; + m_step = 0; + m_stuck = false; + m_stopped = false; + m_frame = 0; + m_target_lock = false; + m_vector = Point2f(1,0); + + m_at_target = false; + m_at_destination = false; + + m_trail_num = trail_num; + + m_vector = onLook(true); + + m_vector.normalise(); + + m_target_pix = NoPixel; +} + +void Agent::onMove() +{ + m_at_target = false; + m_frame++; + if (m_program->m_destination_directed && dist(m_loc,m_destination) < 10.0) { + // reached final destination + onDestination(); + } + else if ((m_program->m_sel_type & AgentProgram::SEL_TARGETTED) && dist(m_loc,m_target) < m_pointmap->getSpacing()) { + // reached target (intermediate destination) + m_step = 0; + onTarget(); + m_vector = onLook(false); + } + else if (prandomr() < (1.0 / m_program->m_steps) && !m_target_lock) { // note, on average, will change 1 in steps + m_step = 0; + m_vector = onLook(false); + /* + if (m_program->m_destination_directed) { + Point2f vec2 = m_destination - m_loc; + vec2.normalise(); + if (dot(vec2,m_vector) < 0.0) { + m_vector = onLook(false); + } + } + */ + } + if (m_stuck) { + // oops... + return; + } + // now step... + PixelRef lastnode = m_node; + onStep(); + if (m_node != lastnode && m_output_mode != OUTPUT_NOTHING) { + if (m_pointmap->getPoint(m_node).filled()) { + AttributeRow& row = m_pointmap->getAttributeTable().getRow(AttributeKey(m_node)); + if (m_output_mode & OUTPUT_COUNTS) { + row.incrValue(g_col_total_counts); + } + if (m_output_mode & OUTPUT_GATE_COUNTS) { + int obj = (int)row.getValue(g_col_gate); + if (m_gate != obj) { + m_gate = obj; + if (m_gate != -1) { + row.incrValue(g_col_gate_counts); + // actually crossed into a new gate: + m_gate_encountered = true; + } + } + } + } + } + // done. happy hamster. +} +void Agent::onUpdateTrail(double currentTime) { + if (!m_stopped && m_trail_num != -1) { + m_program->m_trails[m_trail_num].push_back(Event2f(m_loc, currentTime)); + } +} +void Agent::onDestination() +{ + m_at_destination = true; +} +void Agent::onTarget() +{ + m_occ_memory.a().clear(); + m_at_target = true; +} + +//////////////////////////////////////////////////////////////////// + +void Agent::onStep() +{ + m_stopped = false; + m_step++; + // + Point2f nextloc = m_loc + (m_pointmap->getSpacing() * m_vector); + // note: false returns unconstrained pixel: goodStep must check it is in bounds using m_pointmap->includes + PixelRef nextnode = m_pointmap->pixelate(nextloc,false); + if (nextnode != m_node) { + // quick check location is okay... + if (goodStep(nextnode)) { + m_node = nextnode; + m_loc = nextloc; + } + else { + // try other nearby nodes... + if (!diagonalStep()) { + m_stopped = true; + } + } + } + else { + m_loc = nextloc; + } +} +bool Agent::diagonalStep() +{ + Point2f vector1 = m_vector; + vector1.rotate(M_PI / 4.0); + Point2f nextloc1 = m_loc + (m_pointmap->getSpacing() * vector1); + // note: "false" does not constrain to bounds: must be checked using m_pointmap->includes before getPoint is used + PixelRef nextnode1 = m_pointmap->pixelate(nextloc1,false); + + Point2f vector2 = m_vector; + vector2.rotate(-M_PI / 4.0); + Point2f nextloc2 = m_loc + (m_pointmap->getSpacing() * vector2); + // note: "false" does not constrain to bounds: must be checked using m_pointmap->includes before getPoint is used + int nextnode2 = m_pointmap->pixelate(nextloc2,false); + + bool good = false; + if (pafrand() % 2 == 0) { + if (goodStep(nextnode1)) { + m_node = nextnode1; + m_loc = nextloc1; + good = true; + } + else if (goodStep(nextnode2)) { + m_node = nextnode2; + m_loc = nextloc2; + good = true; + } + } + else { + if (goodStep(nextnode2)) { + m_node = nextnode2; + m_loc = nextloc2; + good = true; + } + else if (goodStep(nextnode1)) { + m_node = nextnode1; + m_loc = nextloc1; + good = true; + } + } + return good; +} +bool Agent::goodStep(PixelRef node) +{ + if (!m_pointmap->includes(node)) { + return false; + } + // n.b., you have to know how the nodes are labelled for this connectValue trick + PixelRef dir; + dir.x = node.x - m_node.x; + dir.y = node.y - m_node.y; + // now translate dir to correct CONNECT value + if (m_pointmap->getPoint(m_node).getGridConnections() & connectValue(dir)) { + return true; + } + + return false; +} + +////////////////////////////////////////////////////////////////////// + +// The various look algorithms + +Point2f Agent::onLook(bool wholeisovist) +{ + Point2f dir; + if (m_program->m_sel_type & AgentProgram::SEL_GIBSONIAN) { + dir = onGibsonianLook(wholeisovist); + } + else if ((m_program->m_sel_type & AgentProgram::SEL_OCCLUSION) == AgentProgram::SEL_OCCLUSION) { + dir = onOcclusionLook(wholeisovist, m_program->m_sel_type); + } + else { + switch (m_program->m_sel_type) { + case AgentProgram::SEL_STANDARD: + dir = onStandardLook(wholeisovist); + break; + case AgentProgram::SEL_LOS: case AgentProgram::SEL_LOS_OCC: + if (m_program->m_destination_directed) { + dir = onDirectedLoSLook(wholeisovist, m_program->m_sel_type); + } + else { + dir = onLoSLook(wholeisovist, m_program->m_sel_type); + } + break; + case AgentProgram::SEL_OPTIC_FLOW2: + dir = onGibsonianLook2(wholeisovist); + break; + } + } + if ((m_program->m_sel_type & AgentProgram::SEL_GIBSONIAN) && !m_stuck) { + // remember what the view looked like here, facing our new direction: + calcLoS(binfromvec(dir),false); + } + if ((m_program->m_sel_type & AgentProgram::SEL_GIBSONIAN2) && !m_stuck) { + calcLoS2(binfromvec(dir),false); + } + + return dir; +} + +Point2f Agent::onStandardLook(bool wholeisovist) +{ + int tarpixelate = -1; + int vbin = m_program->m_vbin; + if (wholeisovist || vbin == -1) { + vbin = 16; + } + int directionbin = 32 + binfromvec(m_vector) - vbin; + int choices = 0; + // reset for getting list, check in range: + vbin = vbin * 2 + 1; + if (vbin > 32) { + vbin = 32; + } + for (int i = 0; i < vbin; i++) { + choices += m_pointmap->getPoint(m_node).getNode().bincount( (directionbin + i) % 32 ); + } + if (choices == 0) { + if (!wholeisovist) { + return onStandardLook(true); + } + else { + m_stuck = true; + m_target = m_loc; + m_target_pix = m_node; + return Point2f(0,0); + } + } + else { + int chosen = pafrand() % choices; + Node& node = m_pointmap->getPoint(m_node).getNode(); + for (; chosen >= node.bincount( directionbin % 32 ); directionbin++) { + chosen -= node.bincount( directionbin % 32 ); + } + Bin& bin = node.bin(directionbin % 32); + bin.first(); + tarpixelate = bin.cursor(); + for (; chosen > 0; chosen--) { + bin.next(); + tarpixelate = bin.cursor(); + } + } + + m_target_pix = tarpixelate; + m_target = m_pointmap->depixelate(tarpixelate); + + return (m_target - m_loc).normalise(); +} + +// TODO: Expose this functionality to the UIs +Point2f Agent::onWeightedLook(bool wholeisovist) +{ + if (wholeisovist) { + // use standard targetted look instead: + return onStandardLook(true); + } + int tarpixelate = -1; + int vbin = m_program->m_vbin; + if (vbin == -1) { + vbin = 16; + } + int aheadbin = binfromvec(m_vector); + int directionbin = 32 + binfromvec(m_vector) - vbin; + prefvec weightmap; + double weight = 0.0; + // reset for getting list, check in range: + vbin = vbin * 2 + 1; + if (vbin > 32) { + vbin = 32; + } + for (int i = 0; i < vbin; i++) { + Bin& bin = m_pointmap->getPoint(m_node).getNode().bin((directionbin + i) % 32); + bin.first(); + + // Quick mod - TV +#if defined(_WIN32) + int node = bin.is_tail() ? -1 : bin.cursor(); +#else + int node = bin.is_tail() ? -1 : bin.cursor().x; +#endif + while (node != -1) { + weight += ((directionbin + i) % 32 == aheadbin) ? 5.0 : 1.0; + weightmap.push_back(wpair(weight,node)); + bin.next(); + // Quick mod - TV +#if defined(_WIN32) + node = bin.is_tail() ? -1 : bin.cursor(); +#else + node = bin.is_tail() ? -1 : bin.cursor().x; +#endif + } + } + if (weightmap.size() == 0) { + return onWeightedLook(true); + } + else { + double chosen = prandomr() * weight; + for (size_t i = 0; i < weightmap.size(); i++) { + if (chosen < weightmap[i].weight) { + tarpixelate = weightmap[i].node; + break; + } + } + } + + m_target_pix = tarpixelate; + m_target = m_pointmap->depixelate(tarpixelate); + + return (m_target - m_loc).normalise(); +} + +Point2f Agent::onOcclusionLook(bool wholeisovist, int looktype) +{ + if (AgentProgram::SEL_OCC_MEMORY) { + m_occ_memory.flip(); + m_occ_memory.a().clear(); + } + + if (wholeisovist) { + // use standard targetted look instead: + return onStandardLook(true); + } + PixelRef tarpixelate = NoPixel; + int vbin = m_program->m_vbin; + if (vbin == -1) { + vbin = 16; + } + int directionbin = 32 + binfromvec(m_vector) - vbin; + // reset for getting list, check in range: + vbin = vbin * 2 + 1; + if (vbin > 32) { + vbin = 32; + } + if (looktype == AgentProgram::SEL_OCC_ALL) { + int choices = 0; + Node& node = m_pointmap->getPoint(m_node).getNode(); + for (int i = 0; i < vbin; i++) { + if (node.m_occlusion_bins[(directionbin+i)%32].size()) { + choices += node.m_occlusion_bins[(directionbin+i)%32].size(); + } + } + if (choices == 0) { + if (!wholeisovist) { + return onStandardLook(false); + } + else { + m_stuck = true; + m_target_pix = m_node; + m_target = m_loc; + return Point2f(0,0); + } + } + else { + size_t chosen = pafrand() % choices; + for (; chosen >= node.m_occlusion_bins[ directionbin % 32 ].size(); directionbin++) { + chosen -= node.m_occlusion_bins[ directionbin % 32 ].size(); + } + tarpixelate = node.m_occlusion_bins[directionbin % 32].at(chosen); + } + } + else { + int subset = 1; + if (looktype == AgentProgram::SEL_OCC_BIN45) { + subset = 3; + } + else if (looktype == AgentProgram::SEL_OCC_BIN60) { + subset = 5; + } + prefvec weightmap; + double weight = 0.0; + Node& node = m_pointmap->getPoint(m_node).getNode(); + for (int i = 0; i < vbin; i += subset) { + PixelRef nigpix; + double fardist = -1.0; + for (int k = 0; k < subset; k++) { + for (size_t j = 0; j < node.m_occlusion_bins[(directionbin+i+k)%32].size(); j++) { + PixelRef pix = node.m_occlusion_bins[(directionbin+i+k)%32].at(j); + if (dist(pix,m_node) > fardist) { + fardist = dist(pix,m_node); + nigpix = pix; + } + } + } + if (fardist != -1.0) { + bool cont = true; + if (looktype == AgentProgram::SEL_OCC_MEMORY) { + depthmapX::addIfNotExists(m_occ_memory.a(), nigpix); + // the turn chance (pafrand() % 2) may have to be modified later... + if (!m_at_target && std::find(m_occ_memory.b().begin(), m_occ_memory.b().end() ,nigpix) != m_occ_memory.b().end()) { + cont = false; + } + } + if (cont) { + switch (looktype) { + case AgentProgram::SEL_OCC_WEIGHT_DIST: + weight += fardist; + break; + case AgentProgram::SEL_OCC_WEIGHT_ANG: + weight += (double(vbin-abs(i-vbin))); + break; + case AgentProgram::SEL_OCC_WEIGHT_DIST_ANG: + weight += fardist * (double(vbin-abs(i-vbin))); + break; + default: + weight += 1.0; + break; + } + weightmap.push_back(wpair(weight,nigpix)); + } + } + } + if (weightmap.size() == 0) { + if (!wholeisovist) { + return onStandardLook(false); + } + else { + m_stuck = true; + m_target = m_loc; + return Point2f(0,0); + } + } + else { + double chosen = prandomr() * weight; + for (size_t i = 0; i < weightmap.size(); i++) { + if (chosen < weightmap[i].weight) { + tarpixelate = weightmap[i].node; + break; + } + } + } + } + + m_target_pix = tarpixelate; + m_target = m_pointmap->depixelate(tarpixelate); + + return (m_target - m_loc).normalise(); +} + +// note: LOS look uses similar weighted choice mechanism + +Point2f Agent::onLoSLook(bool wholeisovist, int look_type) +{ + int bbin = -1; + if (m_program->m_destination_directed) { + Point2f vec2 = m_destination - m_loc; + double test = vec2.length(); + vec2.normalise(); + bbin = binfromvec(vec2); + } + int targetbin = -1; + int vbin = m_program->m_vbin; + if (wholeisovist || vbin == -1) { + vbin = 16; + } + int directionbin = 32 + binfromvec(m_vector) - vbin; + prefvec weightmap; + double weight = 0.0; + // reset for getting list, check in range: + vbin = vbin * 2 + 1; + if (vbin > 32) { + vbin = 32; + } + for (int i = 0; i < vbin; i++) { + double los = (look_type == AgentProgram::SEL_LOS) ? + m_pointmap->getPoint(m_node).getNode().bindistance( (directionbin + i) % 32 ) : + m_pointmap->getPoint(m_node).getNode().occdistance( (directionbin + i) % 32 ); + if (m_program->m_los_sqrd) { + los *= los; + } + if (m_program->m_destination_directed) { + los *= 1.0 - double(binsbetween( ((directionbin + i) % 32), bbin)) / 16.0; + } + weight += los; + weightmap.push_back(wpair(weight,(directionbin + i) % 32)); + } + if (weight == 0.0) { + if (!wholeisovist) { + return onLoSLook(true, look_type); + } + else { + // oops! + m_stuck = true; + return Point2f(0,0); + } + } + else { + double chosen = prandomr() * weight; + for (size_t i = 0; i < weightmap.size(); i++) { + if (chosen < weightmap[i].weight) { + targetbin = weightmap[i].node; + break; + } + } + } + + float angle = (float)anglefrombin2(targetbin); + + return Point2f( cosf(angle), sinf(angle) ); +} + +Point2f Agent::onDirectedLoSLook(bool wholeisovist, int look_type) +{ + int bbin = -1; + Point2f vec2 = m_destination - m_loc; + vec2.normalise(); + bbin = binfromvec(vec2); + int targetbin = -1; + int vbin = m_program->m_vbin; + if (wholeisovist || vbin == -1) { + vbin = 16; + } + int directionbin = 32 + binfromvec(vec2) - vbin; + prefvec weightmap; + double weight = 0.0; + // reset for getting list, check in range: + vbin = vbin * 2 + 1; + if (vbin > 32) { + vbin = 32; + } + for (int i = 0; i < vbin; i++) { + double los = (look_type == AgentProgram::SEL_LOS) ? + m_pointmap->getPoint(m_node).getNode().bindistance( (directionbin + i) % 32 ) : + m_pointmap->getPoint(m_node).getNode().occdistance( (directionbin + i) % 32 ); + if (m_program->m_los_sqrd) { + los *= los; + } + weight += los; + weightmap.push_back(wpair(weight,(directionbin + i) % 32)); + } + if (weight == 0.0) { + if (!wholeisovist) { + return onLoSLook(true, look_type); + } + else { + // oops! + m_stuck = true; + return Point2f(0,0); + } + } + else { + double chosen = prandomr() * weight; + for (size_t i = 0; i < weightmap.size(); i++) { + if (chosen < weightmap[i].weight) { + targetbin = weightmap[i].node; + break; + } + } + } + + float angle = (float)anglefrombin2(targetbin); + + return Point2f( cosf(angle), sinf(angle) ); +} + + +// Gibsonian agents record their last known information, +// and act according to their rules: + +Point2f Agent::onGibsonianLook(bool wholeisovist) +{ + // at start, go in any direction: + if (wholeisovist) { + return onLoSLook(true, AgentProgram::SEL_LOS); + } + // + calcLoS(binfromvec(m_vector),true); + // now, choose action according to type of agent: + int rule_choice = -1; + int dir = 0; + for (int k = 0; k < 4; k++) { + dir = onGibsonianRule(m_program->m_rule_order[k]); + if (dir != 0) { + rule_choice = m_program->m_rule_order[k]; + break; + } + } + + float angle = 0.0; + + if (rule_choice != -1) { + angle = (float)anglefrombin2((binfromvec(m_vector) + (2 * rule_choice + 1) * dir + 32) % 32); + } + + // if no rule selection made, carry on in current direction + return (rule_choice == -1) ? m_vector : Point2f( cosf(angle), sinf(angle) ); +} + +int Agent::onGibsonianRule(int rule) +{ + int option = 0; + switch (m_program->m_sel_type) { + case AgentProgram::SEL_LENGTH: + // rule_threshold from 0 to 100m + if (m_curr_los[rule + 1] > m_program->m_rule_threshold[rule]) { + option = 0x01; + } + if (m_curr_los[rule + 5] > m_program->m_rule_threshold[rule]) { + option |= 0x10; + } + break; + case AgentProgram::SEL_OPTIC_FLOW: + // rule_threshold reflects from 0x (0) to 5x (100.0) + if ((m_curr_los[rule + 1] + 1) / (m_last_los[rule + 1] + 1) > m_program->m_rule_threshold[rule] / 20.0) { + option = 0x01; + } + if ((m_curr_los[rule + 5] + 1) / (m_last_los[rule + 5] + 1) > m_program->m_rule_threshold[rule] / 20.0) { + option |= 0x10; + } + break; + case AgentProgram::SEL_COMPARATIVE_LENGTH: + // rule_threshold reflects from 0x (0) to 10x (100.0) + if ((m_curr_los[rule + 1] + 1) / (m_curr_los[0] + 1) > m_program->m_rule_threshold[rule] / 10.0) { + option = 0x01; + } + if ((m_curr_los[rule + 5] + 1) / (m_curr_los[0] + 1) > m_program->m_rule_threshold[rule] / 10.0) { + option |= 0x10; + } + break; + case AgentProgram::SEL_COMPARATIVE_OPTIC_FLOW: + // rule_threshold reflects from 0x (0) to 10x (100.0) + if ((m_curr_los[rule + 1] * m_last_los[0] + 1) / (m_last_los[rule + 1] * m_curr_los[0] + 1) > + m_program->m_rule_threshold[rule] / 10.0) { + option = 0x01; + } + if ((m_curr_los[rule + 5] * m_last_los[0] + 1) / (m_last_los[rule + 5] * m_curr_los[0] + 1) > + m_program->m_rule_threshold[rule] / 10.0) { + option |= 0x10; + } + break; + } + int dir = 0; + if (option == 0x01 && m_program->m_rule_probability[0] > prandomr()) { + dir = -1; + } + else if (option == 0x10 && m_program->m_rule_probability[0] > prandomr()) { + dir = +1; + } + else if (option == 0x11 && m_program->m_rule_probability[0] > prandomr() * prandomr()) { + // note, use random * random event as there are two ways to do this + dir = (rand() % 2) ? -1 : +1; + } + return dir; +} + +Point2f Agent::onGibsonianLook2(bool wholeisovist) +{ + // at start, go in any direction: + if (wholeisovist) { + return onLoSLook(true, AgentProgram::SEL_LOS); + } + // + calcLoS2(binfromvec(m_vector),true); + int maxbin = 0; + /* + // first action: adjust to longest line of sight + if (m_curr_los[3] > m_curr_los[0]) { + maxbin = -m_program->m_vahead; + if (m_curr_los[4] > m_curr_los[0] && (pafrand() % 2)) { + maxbin = m_program->m_vahead; + } + } + else if (m_curr_los[4] > m_curr_los[0]) { + maxbin = m_program->m_vahead; + } + */ + // second action, apply feeler rule: + char dir = 0x00; + if ((m_curr_los[1]-m_last_los[1])/m_curr_los[1] > m_program->m_feeler_threshold) { + dir |= 0x01; + } + if ((m_curr_los[2]-m_last_los[2])/m_curr_los[2] > m_program->m_feeler_threshold) { + dir |= 0x10; + } + if (dir == 0x01 && m_program->m_feeler_probability > prandomr()) { + maxbin = -m_program->m_vbin; + } + else if (dir == 0x10 && m_program->m_feeler_probability > prandomr()) { + maxbin = m_program->m_vbin; + } + else if (dir == 0x11 && m_program->m_feeler_probability > prandomr() * prandomr()) { + maxbin = (pafrand() % 2) ? m_program->m_vbin : -m_program->m_vbin; + } + // third action: detect heading for dead-end + if (maxbin == 0 && (m_curr_los[0] / m_pointmap->getSpacing() < m_program->m_ahead_threshold)) { + if (m_curr_los[1] >= m_curr_los[2]) { + maxbin = -m_program->m_vbin; + } + else { + maxbin = m_program->m_vbin; + } + } + + int bin = binfromvec(m_vector) + maxbin; + float angle = (float)anglefrombin2(bin); + + return (maxbin == 0) ? m_vector : Point2f( cosf(angle), sinf(angle) ); +} + +void Agent::calcLoS(int directionbin, bool curr) +{ + float *los; + if (curr) { + los = m_curr_los; + } + else { + los = m_last_los; + } + Node& node = m_pointmap->getPoint(m_node).getNode(); + // ahead + los[0] = node.bindistance( directionbin % 32 ); + // directions: + int count = 1; + for (int i = 1; i <= 7; i += 2) { + los[count] = node.bindistance((directionbin - i + 32) % 32); + count++; + } + for (int j = 1; j <= 7; j += 2) { + los[count] = node.bindistance((directionbin + j) % 32); + count++; + } +} + +void Agent::calcLoS2(int directionbin, bool curr) +{ + float *los; + if (curr) { + los = m_curr_los; + } + else { + los = m_last_los; + } + Node& node = m_pointmap->getPoint(m_node).getNode(); + // ahead + los[0] = node.bindistance(directionbin % 32); + // directions: + los[1] = node.bindistance( (directionbin - m_program->m_vbin + 32) % 32); + los[2] = node.bindistance( (directionbin + m_program->m_vbin) % 32); + // + los[3] = node.bindistance( (directionbin - m_program->m_vahead + 32) % 32); + los[4] = node.bindistance( (directionbin + m_program->m_vahead) % 32); +} diff --git a/salalib/nagent.h b/salalib/nagent.h new file mode 100644 index 00000000..ba71c6d2 --- /dev/null +++ b/salalib/nagent.h @@ -0,0 +1,341 @@ +// sala - a component of the depthmapX - spatial network analysis platform +// Copyright (C) 2011-2012, Tasos Varoudis + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +// + +#ifndef __NAGENT_H__ +#define __NAGENT_H__ + +#include "salalib/pixelref.h" +#include "salalib/point.h" +#include "salalib/pointdata.h" +#include "salalib/shapemap.h" +#include "salalib/tracemap.h" + +#include "genlib/pflipper.h" + +const char g_col_total_counts[] = "Gate Counts"; +const char g_col_gate_counts[] = "__Internal_Gate_Counts"; +const char g_col_gate[] = "__Internal_Gate"; + +////////////////////////////////////////////////////////////////////// + +// weighting +struct wpair +{ + double weight; + int node; + wpair(double w = 0.0, int n = -1) + { weight = w; node = n; } +}; + + +/* +// this is how pixels are referenced in depthmapX +struct Pixel +{ + // note, bit fields are stored the wrong way round: + short y : 16; + short x : 16; + Pixel() + { x = -1; y = -1; } + Pixel(int v) + { x = v >> 16; y = v & 0xffff; } + operator int () + { return (int&) *this; } +}; +*/ + +// convert an x / y difference to it's corresponding connection direction +inline char connectValue(PixelRef dir) +{ + if (dir.y > 0) { + return (Point::CONNECT_NE << (1 - dir.x)); + } + else if (dir.y < 0) { + return (Point::CONNECT_SW << (dir.x + 1)); + } + else if (dir.x == 1) { + return (char) Point::CONNECT_E; + } + else { + return (char) Point::CONNECT_W; + } +} + +/* +// some basic mathematical operators you might want +struct DPoint2 : public DPoint +{ + DPoint2() + { x = 0.0; y = 0.0; } + DPoint2(double p, double q) + { x = p; y = q; } + DPoint2(const DPoint& d) + { x = d.x; y = d.y; } + operator DPoint() { return DPoint(x,y); } + double length() { return sqrt(x * x + y * y); } + DPoint2& normalise() { double len = length(); x /= len; y /= len; return *this; } + DPoint2& scale(double s) { x *= s; y *= s; return *this; } + // normalise first before using angle / bin functions + double angle() const { return y < 0 ? 2.0 * DLL_PI - acos(x) : acos(x); } + // note the add 0.5 means angles from e.g., -1/32 to 1/32 are in bin 0 + int bin() const { return int(32.0 * (0.5 * angle() / DLL_PI) + 0.5);} + double& operator [] (int i) { return i == 0 ? x : y; } + const double operator [] (int i) const { return i == 0 ? x : y; } + friend DPoint2 operator + (const DPoint2& a, const DPoint2& b); + friend DPoint2 operator - (const DPoint2& a, const DPoint2& b); + friend double det(const DPoint2& a, const DPoint2& b); + friend double dot(const DPoint2& a, const DPoint2& b); + friend double dist(const DPoint2& a, const DPoint2& b); + friend DPoint2 operator * (double s, const DPoint2& b); + friend DPoint2 operator * (const DPoint2& a, double s); + // + DPoint2& rotate(const double theta) + { double t = x; x = x * cos(theta) - y * sin(theta); + y = y * cos(theta) + t * sin(theta); return *this; } +}; +inline DPoint2 operator + (const DPoint2& a, const DPoint2& b) +{ return DPoint2(a.x + b.x, a.y + b.y); } +inline DPoint2 operator - (const DPoint2& a, const DPoint2& b) +{ return DPoint2(a.x - b.x, a.y - b.y); } +inline double det(const DPoint2& a, const DPoint2& b) +{ return (a.x * b.y - a.y * b.x); } +inline double dot(const DPoint2& a, const DPoint2& b) +{ return (a.x * b.x + a.y * b.y); } +inline double dist(const DPoint2& a, const DPoint2& b) +{ DPoint2 c = b - a; return c.length(); } +inline DPoint2 operator * (double s, const DPoint2& b) +{ return DPoint2(s * b.x, s * b.y); } +inline DPoint2 operator * (const DPoint2& a, double s) +{ return DPoint2(s * a.x, s * a.y); } +*/ +//////////////////////////////////////////////////////////////// + +class AgentEngine; +struct AgentSet; +struct AgentProgram; +class Agent; + +class AgentEngine +{ +public: // public for now for speed + std::vector agentSets; + int m_gatelayer; + int m_timesteps; +public: + bool m_record_trails; + int m_trail_count = 50; +public: + AgentEngine(); + void run(Communicator *comm, PointMap *pointmap); + void insertTrailsInMap(ShapeMap& trailMap); + void insertTrailsInMap(TraceMap& trailMap); +}; + +struct AgentProgram +{ + // comparative is comparative with current heading + enum { SEL_LOS = 0x0001, SEL_LOS_OCC = 0x0002, + SEL_TARGETTED = 0x1000, + SEL_STANDARD = 0x1001, SEL_WEIGHTED = 0x1002, + SEL_GIBSONIAN = 0x2000, + SEL_LENGTH = 0x2001, SEL_OPTIC_FLOW = 0x2002, + SEL_COMPARATIVE_LENGTH = 0x2003, SEL_COMPARATIVE_OPTIC_FLOW = 0x2004, + SEL_GIBSONIAN2 = 0x4000, + SEL_OPTIC_FLOW2 = 0x4001, + SEL_OCCLUSION = 0x9000, + SEL_OCC_ALL = 0x9001, + SEL_OCC_BIN45 = 0x9002, SEL_OCC_BIN60 = 0x9003, + SEL_OCC_STANDARD = 0x9004, + SEL_OCC_WEIGHT_DIST = 0x9005, SEL_OCC_WEIGHT_ANG = 0x9006, SEL_OCC_WEIGHT_DIST_ANG = 0x9007, + SEL_OCC_MEMORY = 0x9008 + }; + int m_sel_type; + int m_steps; + int m_vbin; + // these three variables for evolved Gibsonian agents: + int m_rule_order[4]; + float m_rule_threshold[4]; + float m_rule_probability[4]; + // these are for optic flow 2 agents + int m_vahead; // how wide your ahead vision is + float m_ahead_threshold; // will turn if neg flow greater than this threshold (set in range 1/100 to 1) + float m_feeler_threshold; // will turn if flow greater than this threshold (set in range 1 to 5) + float m_feeler_probability; // turn with this much probability if a feeler triggers + // + // simple long range destinations: + bool m_destination_directed; + bool m_los_sqrd; + // + // if it is going to evolved, then have it remember its fitness: + double m_fitness; + // + AgentProgram(); + // + // for evolution + void mutate(); + friend AgentProgram crossover(const AgentProgram& prog_a, const AgentProgram& prog_b); + // to reload later: + void save(const std::string& filename); + bool open(const std::string& filename); + std::vector> m_trails; +}; + +struct AgentSet : public AgentProgram +{ + std::vector agents; + std::vector m_release_locations; + int m_release_locations_seed = 0; + double m_release_rate; + int m_lifetime; + AgentSet(); + void move(int currentTimeStep); + void init(int agent, int trail_num = -1); +}; + +const int POPSIZE = 500; +// redo ASSAYs -- assaysize * assays (3 * 200 = 600 evaluations total) +// then take mean fitness: due to large variation in fitnesses with +// short assays such as this +const int ASSAYS = 3; +const int ASSAYSIZE = 25000; +const int GENERATIONS = 10000; +const int TIMESTEPS = 1600; + +struct ProgramPopulation +{ +public: + AgentProgram m_population[POPSIZE]; +public: + ProgramPopulation() {;} + AgentProgram *makeChild(); + void sort(); +}; + +class Agent +{ +public: + enum { OUTPUT_NOTHING = 0x00, OUTPUT_COUNTS = 0x01, OUTPUT_GATE_COUNTS = 0x02, OUTPUT_TRAILS = 0x04 }; +protected: + AgentProgram *m_program; + PointMap *m_pointmap; + // + PixelRef m_node; + int m_step; + int m_frame; + int m_gate; + bool m_stuck; + bool m_stopped; + bool m_target_lock; + bool m_gate_encountered; + bool m_at_target; + bool m_at_destination; + int m_output_mode; + Point2f m_loc; + Point2f m_target; + Point2f m_vector; + PixelRef m_target_pix; + // a long term goal: + Point2f m_destination; + // + // for recording trails: + int m_trail_num; + // + // for occlusion memory + pflipper m_occ_memory; + // + // extra memory of last observed values for Gibsonian agents: + float m_last_los[9]; + float m_curr_los[9]; +public: + Agent() + { m_program = NULL; m_pointmap = NULL; m_output_mode = OUTPUT_NOTHING; } + Agent(AgentProgram *program, PointMap *pointmap, int output_mode = OUTPUT_NOTHING); + void onInit(PixelRef node, int trail_num = -1); + void onClose(); + Point2f onLook(bool wholeisovist); + Point2f onStandardLook(bool wholeisovist); + Point2f onWeightedLook(bool wholeisovist); + Point2f onOcclusionLook(bool wholeisovist, int looktype); + Point2f onLoSLook(bool wholeisovist, int look_type); + Point2f onDirectedLoSLook(bool wholeisovist, int look_type); + Point2f onGibsonianLook(bool wholeisovist); + Point2f onGibsonianLook2(bool wholeisovist); + int onGibsonianRule(int rule); + void calcLoS(int directionbin, bool curr); + void calcLoS2(int directionbin, bool curr); + void onMove(); + void onUpdateTrail(double currentTime); + void onTarget(); + void onDestination(); + void onStep(); + bool diagonalStep(); + bool goodStep(PixelRef node); + bool gateEncountered() + { return m_gate_encountered; } + const Point2f& getLoc() const + { return m_loc; } + // + const bool atTarget() const { return m_at_target; } + const bool atDestination() const { return m_at_destination; } + // + const Point2f& getLocation() const + { return m_loc; } + const Point2f& getVector() const + { return m_vector; } + const PixelRef getNode() const + { return m_node; } + const int getFrame() const + { return m_frame; } + const PointMap& getPointMap() const + { return *m_pointmap; } +}; + +// note the add 0.5 means angles from e.g., -1/32 to 1/32 are in bin 0 +inline int binfromvec(const Point2f& p) +{ return int(32.0 * (0.5 * p.angle() / M_PI) + 0.5); } + +// a random angle based on a bin direction +inline double anglefrombin2(int here) +{ + return (2.0 * M_PI) * ((double(here)-0.5)/32.0 + prandom()/32.0); +} + +inline int binsbetween(int bin1, int bin2) +{ + int b = abs(bin1 - bin2); + if (b > 16) { + b = 32 - b; + } + return b; +} + +///////////////////////////////////////////////////////////////////////////////////// + +// Playback for recorded traces + +struct Trace +{ + double starttime; + double endtime; + std::vector events; +}; + +///////////////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/salalib/salalib.pro b/salalib/salalib.pro new file mode 100644 index 00000000..90132bc9 --- /dev/null +++ b/salalib/salalib.pro @@ -0,0 +1,97 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2017-02-09T16:19:46 +# +#------------------------------------------------- +include(../defaults.pri) +include(vgamodules/vgamodules.pri) +include(axialmodules/axialmodules.pri) +include(segmmodules/segmmodules.pri) +include(parsers/parsers.pri) +include(agents/agents.pri) + +QT -= qt +CONFIG -= qt +CONFIG -= app_bundle +DEFINES += _DEPTHMAP +TARGET = salalib +TEMPLATE = lib +CONFIG += staticlib c++11 + +DEFINES += SALALIB_LIBRARY + +SOURCES += \ + axialmap.cpp \ + connector.cpp \ + isovist.cpp \ + mgraph.cpp \ + ngraph.cpp \ + pointdata.cpp \ + salaprogram.cpp \ + shapemap.cpp \ + spacepix.cpp \ + sparksieve2.cpp \ + entityparsing.cpp \ + linkutils.cpp \ + gridproperties.cpp \ + attributetable.cpp \ + layermanagerimpl.cpp \ + attributetableview.cpp \ + geometrygenerators.cpp \ + point.cpp \ + pafcolor.cpp \ + spacepixfile.cpp \ + alllinemap.cpp \ + axialminimiser.cpp \ + axialpolygons.cpp \ + tidylines.cpp \ + mapconverter.cpp \ + importutils.cpp \ + attributetableindex.cpp \ + tracemap.cpp + +HEADERS += \ + axialmap.h \ + connector.h \ + fileproperties.h \ + isovist.h \ + mgraph.h \ + ngraph.h \ + pointdata.h \ + salaprogram.h \ + shapemap.h \ + spacepix.h \ + sparksieve2.h \ + entityparsing.h \ + linkutils.h \ + gridproperties.h \ + isovistdef.h \ + mgraph_consts.h \ + attributetable.h \ + attributetableindex.h \ + layermanager.h \ + layermanagerimpl.h \ + attributetablehelpers.h \ + attributetableview.h \ + geometrygenerators.h \ + point.h \ + pixelref.h \ + displayparams.h \ + pafcolor.h \ + options.h \ + spacepixfile.h \ + alllinemap.h \ + axialminimiser.h \ + tolerances.h \ + axialpolygons.h \ + tidylines.h \ + mapconverter.h \ + ivga.h \ + iaxial.h \ + isegment.h \ + importutils.h \ + importtypedefs.h \ + tracemap.h + +DISTFILES += \ + salascript-tests.txt diff --git a/salalib/shapemap.h b/salalib/shapemap.h index 01847716..472574eb 100644 --- a/salalib/shapemap.h +++ b/salalib/shapemap.h @@ -219,7 +219,8 @@ class ShapeMap : public PixelBase { AXIALMAP = 0x0020, SEGMENTMAP = 0x0040, PESHMAP = 0x0080, - LINEMAP = 0x0070 + LINEMAP = 0x0070, + TRACEMAP = 0x0100 }; enum { COPY_NAME = 0x0001, diff --git a/salalib/tracemap.cpp b/salalib/tracemap.cpp new file mode 100644 index 00000000..1279c117 --- /dev/null +++ b/salalib/tracemap.cpp @@ -0,0 +1,86 @@ +// sala - a component of the depthmapX - spatial network analysis platform +// Copyright (C) 2011-2012, Petros Koutsolampros + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "tracemap.h" + +int TraceMap::makeTraceWithRef(const std::vector &trace, int trace_ref, + const std::map &extraAttributes) { + std::vector traceGeometry(trace.size()); + std::vector traceTimes(trace.size()); + int counter = 0; + for (const Event2f &event : trace) { + traceGeometry[counter] = event; + traceTimes[counter] = event.t; + counter++; + } + makePolyShapeWithRef(traceGeometry, true, trace_ref, false, extraAttributes); + m_traceTimes.insert(std::make_pair(trace_ref, traceTimes)); + return trace_ref; +} + +int TraceMap::makeTrace(const std::vector &trace, const std::map &extraAttributes) { + return makeTraceWithRef(trace, getNextShapeKey(), extraAttributes); +} + +bool TraceMap::read(std::istream &stream) { + ShapeMap::read(stream); + + int count = 0; + stream.read((char *)&count, sizeof(count)); + for (int j = 0; j < count; j++) { + int key; + stream.read((char *)&key, sizeof(key)); + m_traceTimes.insert(std::make_pair(key, dXreadwrite::readVector(stream))); + } + return true; +} + +bool TraceMap::write(std::ofstream &stream) { + ShapeMap::write(stream); + + // write trace times + int count = m_traceTimes.size(); + stream.write((char *)&count, sizeof(count)); + for (auto &traceTimes : m_traceTimes) { + int key = traceTimes.first; + stream.write((char *)&key, sizeof(key)); + dXreadwrite::writeVector(stream, traceTimes.second); + } + + return true; +} + +void TraceMap::writeTracesToXMLFile(std::ofstream &stream) { + stream << "" << std::endl; + + stream.precision(12); + for (auto &refShape : m_shapes) { + stream << "" << std::endl; + const auto × = m_traceTimes[refShape.first]; + int counter = 0; + for (Point2f &point : refShape.second.m_points) { + stream << "" << std::endl; + counter++; + } + stream << "" << std::endl; + } + + stream << "" << std::endl; +} diff --git a/salalib/tracemap.h b/salalib/tracemap.h new file mode 100644 index 00000000..7f713d9d --- /dev/null +++ b/salalib/tracemap.h @@ -0,0 +1,35 @@ +// sala - a component of the depthmapX - spatial network analysis platform +// Copyright (C) 2011-2012, Petros Koutsolampros + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "salalib/shapemap.h" + +#include "genlib/p2dpoly.h" + +class TraceMap : public ShapeMap { + private: + std::map> m_traceTimes; + + public: + TraceMap(std::string name = std::string()) : ShapeMap(name, ShapeMap::TRACEMAP) {} + int makeTraceWithRef(const std::vector &trace, int trace_ref, + const std::map &extraAttributes = std::map()); + int makeTrace(const std::vector &trace, const std::map &extraAttributes = std::map()); + bool read(std::istream& stream); + bool write(std::ofstream &stream); + void writeTracesToXMLFile(std::ofstream &stream); +};