Skip to content

Commit

Permalink
Merge pull request #21 from Klebert-Engineering/feature/picking
Browse files Browse the repository at this point in the history
Feature Picking
  • Loading branch information
josephbirkner authored Aug 31, 2023
2 parents 7e8c6d7 + 46c00e9 commit 8171987
Show file tree
Hide file tree
Showing 13 changed files with 375 additions and 63 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ project(erdblick)

include(FetchContent)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD 17)

# Treat warnings as errors, with some exceptions for Cesium.
set (ERDBLICK_CXX_FLAGS
Expand Down
1 change: 0 additions & 1 deletion libs/core/include/erdblick/buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class SharedUint8Array
explicit SharedUint8Array(std::string const& data);
[[nodiscard]] uint32_t getSize() const;
__UINT64_TYPE__ getPointer();
std::shared_ptr<std::vector<uint8_t>> getArray();

void writeToArray(const char* start, const char* end);
void writeToArray(std::string const& content);
Expand Down
41 changes: 27 additions & 14 deletions libs/core/include/erdblick/testdataprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,36 @@ class TestDataProvider
fieldNames_);
result->setPrefix({{"areaId", "TheBestArea"}});

// Create a feature with line geometry
auto feature1 = result->newFeature("Way", {{"wayId", 42}});
// Create a function to generate a random coordinate between two given points
auto randomCoordinateBetween = [&](const auto& point1, const auto& point2) {
auto x = point1.x + (point2.x - point1.x) * (rand() / static_cast<double>(RAND_MAX));
auto y = point1.y + (point2.y - point1.y) * (rand() / static_cast<double>(RAND_MAX));
auto z = 100. / static_cast<double>(level);
return mapget::Point{x, y, z};
};

// Use high-level geometry API
auto ne = tileId.ne();
auto sw = tileId.sw();
ne.z = sw.z = 100./static_cast<double>(level);
feature1->addLine({ne, sw});
// Seed the random number generator for consistency
srand(time(nullptr));

// Add a fixed attribute
feature1->attributes()->addField("main_ingredient", "Pepper");
// Create 10 random lines inside the bounding box defined by ne and sw
for (int i = 0; i < 10; i++) {
// Create a feature with line geometry
auto feature = result->newFeature("Way", {{"wayId", 42 + i}});

// Add an attribute layer
auto attrLayer = feature1->attributeLayers()->newLayer("cheese");
auto attr = attrLayer->newAttribute("mozzarella");
attr->setDirection(mapget::Attribute::Direction::Positive);
attr->addField("smell", "neutral");
// Generate random start and end points for the line
auto start = randomCoordinateBetween(tileId.ne(), tileId.sw());
auto end = randomCoordinateBetween(tileId.ne(), tileId.sw());
feature->addLine({start, end});

// Add a fixed attribute
feature->attributes()->addField("main_ingredient", "Pepper");

// Add an attribute layer
auto attrLayer = feature->attributeLayers()->newLayer("cheese");
auto attr = attrLayer->newAttribute("mozzarella");
attr->setDirection(mapget::Attribute::Direction::Positive);
attr->addField("smell", "neutral");
}

return result;
}
Expand Down
26 changes: 26 additions & 0 deletions libs/core/src/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "style.h"
#include "testdataprovider.h"

#include "mapget/log.h"

using namespace erdblick;
namespace em = emscripten;

Expand All @@ -30,6 +32,19 @@ EMSCRIPTEN_BINDINGS(FeatureLayerRendererBind)
////////// FeatureLayerStyle
em::class_<FeatureLayerStyle>("FeatureLayerStyle").constructor<SharedUint8Array&>();

////////// Feature
using FeaturePtr = mapget::model_ptr<mapget::Feature>;
em::class_<FeaturePtr>("Feature")
.function(
"id",
std::function<std::string(FeaturePtr&)>(
[](FeaturePtr& self) { return self->id()->toString(); }))
.function(
"geojson",
std::function<std::string(FeaturePtr&)>(
[](FeaturePtr& self) {
return self->toGeoJson().dump(4); }));

////////// TileFeatureLayer
em::class_<mapget::TileFeatureLayer>("TileFeatureLayer")
.smart_ptr<std::shared_ptr<mapget::TileFeatureLayer>>(
Expand All @@ -48,6 +63,17 @@ EMSCRIPTEN_BINDINGS(FeatureLayerRendererBind)
result.set("y", self.tileId().center().y);
result.set("z", self.tileId().z());
return result;
}))
.function(
"at",
std::function<
mapget::model_ptr<mapget::Feature>(mapget::TileFeatureLayer const&, int i)>(
[](mapget::TileFeatureLayer const& self, int i)
{
if (i < 0 || i >= self.numRoots()) {
mapget::log().error("TileFeatureLayer::at(): Index {} is oob.", i);
}
return self.at(i);
}));

////////// FeatureLayerRenderer
Expand Down
12 changes: 4 additions & 8 deletions libs/core/src/buffer.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "buffer.h"

#include <cstring>

namespace erdblick
{

Expand Down Expand Up @@ -38,16 +40,10 @@ std::string SharedUint8Array::toString() const
return {array_.begin(), array_.end()};
}

std::shared_ptr<std::vector<uint8_t>> SharedUint8Array::getArray()
{
return std::make_shared<std::vector<uint8_t>>(array_);
}

void SharedUint8Array::writeToArray(const std::vector<std::byte>& content)
{
array_.reserve(content.size());
for (auto& byte : content)
array_.emplace_back((uint8_t)byte);
array_.resize(content.size());
std::memcpy(array_.data(), content.data(), content.size());
}

}
68 changes: 64 additions & 4 deletions libs/core/src/renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include "CesiumGltf/Model.h"
#include "CesiumGltfWriter/GltfWriter.h"
#include "CesiumGltf/ExtensionExtMeshFeatures.h"
#include "CesiumGltf/ExtensionModelExtStructuralMetadata.h"
#include "CesiumGltf/ExtensionMeshPrimitiveExtStructuralMetadata.h"

#include "renderer.h"

Expand Down Expand Up @@ -139,6 +141,8 @@ struct RuleGeometry
meshFeatureExtFeatureIds.attribute = 0;
meshFeatureExtFeatureIds.featureCount = numDistinctFeatureIDs();
meshFeatureExtFeatureIds.nullFeatureId = -1;
meshFeatureExtFeatureIds.label = "mapgetFeatureIndex";
meshFeatureExtFeatureIds.propertyTable = 0;
primitive.extensions[CesiumGltf::ExtensionExtMeshFeatures::ExtensionName] = meshFeatureExt;

auto& posAccessor = model.accessors.emplace_back();
Expand Down Expand Up @@ -236,10 +240,18 @@ mapget::Point FeatureLayerRenderer::render( // NOLINT (render can be made stati
const std::shared_ptr<TileFeatureLayer>& layer,
SharedUint8Array& result)
{
uint32_t bufferSize = 0;
uint32_t globalBufferSize = 0;
auto tileOrigin = wgsToEuclidean<glm::dvec3>(layer->tileId().center());
std::map<std::pair<uint64_t, GeomType>, std::unique_ptr<RuleGeometry>> geomForRule;

// We store feature ID's in an extra dummy property buffer.
// This attributes features with their index by their index,
// so the information stored is fairly useless.
// But cesium demands us to declare at least
// one per-feature property for picking to work. So this is the way.
std::vector<uint32_t> featureIdBuffer;
featureIdBuffer.reserve(layer->numRoots());

// The Feature ID corresponds to the index of the feature
// within the TileFeatureLayer.
uint32_t featureId = 0;
Expand All @@ -253,23 +265,71 @@ mapget::Point FeatureLayerRenderer::render( // NOLINT (render can be made stati
std::unique_ptr<RuleGeometry>{});
if (wasInserted) {
it->second =
std::make_unique<RuleGeometry>(geomType, tileOrigin, rule, bufferSize);
std::make_unique<RuleGeometry>(geomType, tileOrigin, rule, globalBufferSize);
}
it->second->addFeature(feature, featureId);
}
}
}
featureIdBuffer.emplace_back(featureId);
++featureId;
}
globalBufferSize += featureIdBuffer.size() * sizeof(uint32_t);

// Convert to GLTF
std::vector<std::byte> buffer;
buffer.resize(bufferSize);
buffer.resize(globalBufferSize);
int64_t bufferOffset = 0;
CesiumGltf::Model model;
model.buffers.emplace_back(); // Add single implicit buffer
model.asset.version = "2.0";
model.extensionsUsed.emplace_back(CesiumGltf::ExtensionExtMeshFeatures::ExtensionName);
model.extensionsUsed.emplace_back(CesiumGltf::ExtensionModelExtStructuralMetadata::ExtensionName);

// Prepare feature metadata table description - see
// https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_structural_metadata
CesiumGltf::ExtensionModelExtStructuralMetadata metadataModelExt;
CesiumGltf::ExtensionExtStructuralMetadataSchema metadataSchema;
CesiumGltf::ExtensionExtStructuralMetadataClass metadataClass;
CesiumGltf::ExtensionExtStructuralMetadataClassProperty metadataFeatureIdProperty;
CesiumGltf::ExtensionExtStructuralMetadataPropertyTable metadataPropertyTable;
CesiumGltf::ExtensionExtStructuralMetadataPropertyTableProperty metadataFeatureIdTableColumn;
metadataFeatureIdProperty.type = "UINT32";
metadataFeatureIdProperty.description = "Mapget feature index within the TileFeatureLayer.";
metadataClass.name = "mapgetFeatureMetadataClass";
metadataClass.properties["mapgetFeatureId"] = metadataFeatureIdProperty;
metadataSchema.id = "mapgetFeatureMetadataSchema";
metadataSchema.classes[*metadataClass.name] = metadataClass;
metadataFeatureIdTableColumn.values = 0; // Values are stored in first accessor
metadataPropertyTable.name = "mapgetFeaturePropertyTable";
metadataPropertyTable.classProperty = *metadataClass.name;
metadataPropertyTable.count = (int64_t)featureIdBuffer.size();
metadataPropertyTable.properties["mapgetFeatureId"] = metadataFeatureIdTableColumn;
metadataModelExt.schema = metadataSchema;
metadataModelExt.propertyTables.emplace_back(std::move(metadataPropertyTable));
model.extensions[CesiumGltf::ExtensionModelExtStructuralMetadata::ExtensionName] = metadataModelExt;

// Create Accessor and BufferView for Feature ID property values
auto& featIdAccessor = model.accessors.emplace_back();
featIdAccessor.bufferView = 0;
featIdAccessor.byteOffset = 0;
featIdAccessor.componentType = CesiumGltf::Accessor::ComponentType::UNSIGNED_INT;
featIdAccessor.count = static_cast<int>(featureIdBuffer.size());
featIdAccessor.type = CesiumGltf::Accessor::Type::SCALAR;
featIdAccessor.min = {featureIdBuffer.empty() ? .0 : (double)featureIdBuffer.front()};
featIdAccessor.max = {featureIdBuffer.empty() ? .0 : (double)featureIdBuffer.back()};

auto& featIdBufferView = model.bufferViews.emplace_back();
featIdBufferView.buffer = 0; // All buffer views must refer to the implicit buffer 0
featIdBufferView.byteOffset = bufferOffset;
featIdBufferView.byteLength = static_cast<int>(featureIdBuffer.size() * sizeof(uint32_t));
featIdBufferView.target = CesiumGltf::BufferView::Target::ARRAY_BUFFER;

std::memcpy(
buffer.data() + bufferOffset,
featureIdBuffer.data(),
static_cast<size_t>(featIdBufferView.byteLength));
bufferOffset += featIdBufferView.byteLength;

auto& scene = model.scenes.emplace_back();
for (auto&& [_, ruleGeom] : geomForRule)
Expand Down Expand Up @@ -302,8 +362,8 @@ void FeatureLayerRenderer::makeTileset( // NOLINT (could be made static)

Cesium3DTiles::Tileset tileset;
tileset.asset.version = "1.1";

tileset.geometricError = geometricError;
tileset.extensionsUsed.emplace_back(CesiumGltf::ExtensionExtMeshFeatures::ExtensionName);

glm::dquat noRotation =
glm::angleAxis(CesiumUtility::Math::degreesToRadians(0.0), glm::dvec3(1.0, 0.0, 0.0));
Expand Down
103 changes: 92 additions & 11 deletions static/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,101 @@ body {
-o-user-select:none;
}

.mapviewer-label {
position: fixed;
width: max-content;
}

#mapviewer-canvas-container canvas {
width: 100% !important;
height: 100% !important;
#cesiumContainer .cesium-widget-credits {
display:none !important;
}

#info {
/* Shared styles for both panels */
.panel {
color: white;
font-size: large;
background-color: transparent;
background-color: rgba(91, 91, 91, 0.7);
font-family: monospace;
position: relative;
position: absolute;
top: 10px;
border-radius: 10px;
overflow: hidden;
height: 22px;
padding: 1em;
width: calc(50% - 65px);
}

.panel.expanded {
max-height: calc(100vh - 60px);
height: auto;
}

@media (max-width: 768px) {
.panel {
width: calc(100% - 70px);
position: relative;
left: auto !important;
right: auto !important;
top: auto;
margin: 10px;
}

.panel.expanded {
max-height: calc(100vh - 120px);
}
}

/* Base style for the toggle-indicator */
.toggle-indicator::before {
content: '';
border-style: solid;
display: inline-block;
margin-right: 5px; /* A bit of margin for spacing */
vertical-align: middle; /* Align with the text */
}

.panel .toggle-indicator {
width: 18px;
display: inline-block;
}

/* Triangle pointing right for the collapsed state */
.panel .toggle-indicator::before {
border-width: 5px 5px 5px 8px;
border-color: transparent transparent transparent white;
}

/* Triangle pointing down for the expanded state */
.panel.expanded .toggle-indicator::before {
border-width: 8px 5px 0 5px;
border-color: white transparent transparent transparent;
}

/* Specific styles for the info panel */

#info {
left: 10px;
}

#info > #log {
display: none;
}

#info.expanded > #log {
display: block;
}

/* Specific styles for the selection panel */

#selectionPanel {
right: 30px;
}

#selectionPanel.expanded {
overflow-y: scroll;
scrollbar-width: thin;
overflow-x: hidden;
}

#selectionPanel > pre {
display: none;
}

#selectionPanel.expanded > pre {
display: block;
}
Loading

0 comments on commit 8171987

Please sign in to comment.