557 lines
26 KiB
Plaintext
557 lines
26 KiB
Plaintext
#pragma once
|
|
|
|
#include <osgEarthImGui/ImGuiPanel>
|
|
#include <osgEarthProcedural/Export>
|
|
#include <osgEarthProcedural/VegetationLayer>
|
|
#include <osgEarthProcedural/BiomeLayer>
|
|
#include <osgEarthProcedural/VegetationFeatureGenerator>
|
|
#include <osgEarth/FeatureNode>
|
|
|
|
namespace osgEarth
|
|
{
|
|
namespace Procedural
|
|
{
|
|
using namespace osgEarth;
|
|
|
|
struct VegetationLayerGUI : public osgEarth::ImGuiPanel
|
|
{
|
|
bool _first;
|
|
bool _forceGenerate;
|
|
bool _manualBiomes;
|
|
bool _showBiomeUnderMouse;
|
|
bool _showPlacements;
|
|
bool _simulateTreePlacement = false;
|
|
std::string _simulateTreePlacementOutput;
|
|
std::unordered_map<const Biome*, bool> _isManualBiomeActive;
|
|
osg::observer_ptr<BiomeLayer> _biolayer;
|
|
osg::observer_ptr<VegetationLayer> _veglayer;
|
|
osg::observer_ptr<MapNode> _mapNode;
|
|
Future<const Biome*> _biomeUnderMouse;
|
|
TileKey _biomeUnderMouseKey;
|
|
osg::ref_ptr<FeatureNode> _placementNode;
|
|
TileKey _showPlacementsLastTileKey;
|
|
osg::ref_ptr<osg::Node> _boxgeom;
|
|
GeoPoint _pointUnderMouse;
|
|
|
|
VegetationLayerGUI() : ImGuiPanel("Vegetation"),
|
|
_first(true),
|
|
_forceGenerate(false),
|
|
_manualBiomes(false),
|
|
_showBiomeUnderMouse(false),
|
|
_showPlacements(false)
|
|
{
|
|
auto geom = new osg::Geometry();
|
|
geom->setUseVertexBufferObjects(true);
|
|
geom->setUseDisplayList(false);
|
|
const osg::Vec3 verts_data[8] = { {-0.5, -0.5, 0.0}, {0.5, -0.5, 0.0}, {0.5, 0.5, 0.0}, {-0.5, 0.5, 0.0},
|
|
{-0.5, -0.5, 1.0}, {0.5, -0.5, 1.0}, {0.5, 0.5, 1.0}, {-0.5, 0.5, 1.0} };
|
|
geom->setVertexArray(new osg::Vec3Array(8, verts_data));
|
|
const GLushort indices[24] = { 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 };
|
|
geom->addPrimitiveSet(new osg::DrawElementsUShort(GL_LINES, 24, indices));
|
|
const osg::Vec4 color(1.0f, 1.0f, 0.5f, 1.0);
|
|
geom->setColorArray(new osg::Vec4Array(1, &color));
|
|
geom->setColorBinding(osg::Geometry::BIND_OVERALL);
|
|
|
|
_boxgeom = geom;
|
|
}
|
|
|
|
void load(const Config& conf) override
|
|
{
|
|
}
|
|
|
|
void save(Config& conf) override
|
|
{
|
|
}
|
|
|
|
void draw(osg::RenderInfo& ri) override
|
|
{
|
|
if (!findNodeOrHide<MapNode>(_mapNode, ri))
|
|
return;
|
|
if (!findLayerOrHide(_biolayer, ri))
|
|
return;
|
|
findLayer(_veglayer, ri);
|
|
|
|
if (_first)
|
|
{
|
|
_first = false;
|
|
|
|
EventRouter::get(view(ri))
|
|
.onMove([&](osg::View* v, float x, float y) { onMove(v, x, y); });
|
|
|
|
// activate tweakable uniforms
|
|
stateset(ri)->setDataVariance(osg::Object::DYNAMIC);
|
|
stateset(ri)->removeDefine("OE_TWEAKABLE");
|
|
stateset(ri)->setDefine("OE_TWEAKABLE", 0x7);
|
|
}
|
|
|
|
ImGui::Begin("Vegetation", visible());
|
|
{
|
|
if (_veglayer.valid() && _veglayer->isOpen())
|
|
{
|
|
if (ImGuiLTable::Begin("VegetationDetails"))
|
|
{
|
|
float near_scale = _veglayer->getNearLODScale();
|
|
if (ImGuiLTable::SliderFloat("Near LOD scale", &near_scale, 0.0f, 4.0f, "%.2f", ImGuiSliderFlags_Logarithmic))
|
|
_veglayer->setNearLODScale(near_scale);
|
|
|
|
float far_scale = _veglayer->getFarLODScale();
|
|
if (ImGuiLTable::SliderFloat("Far LOD scale", &far_scale, 0.0f, 4.0f, "%.2f", ImGuiSliderFlags_Logarithmic))
|
|
_veglayer->setFarLODScale(far_scale);
|
|
|
|
float padding = _veglayer->getLODTransitionPadding();
|
|
if (ImGuiLTable::SliderFloat("LOD padding", &padding, 0.0f, 1.0f))
|
|
_veglayer->setLODTransitionPadding(padding);
|
|
|
|
float bb_low = _veglayer->getImpostorLowAngle().as(Units::DEGREES);
|
|
if (ImGuiLTable::SliderFloat("Low impostor angle", &bb_low, 0.0f, 90.0f))
|
|
_veglayer->setImpostorLowAngle(Angle(bb_low, Units::DEGREES));
|
|
|
|
float bb_high = _veglayer->getImpostorHighAngle().as(Units::DEGREES);
|
|
if (ImGuiLTable::SliderFloat("High impostor angle", &bb_high, 0.0f, 90.0f))
|
|
_veglayer->setImpostorHighAngle(Angle(bb_high, Units::DEGREES));
|
|
|
|
bool normalMaps = _veglayer->getUseImpostorNormalMaps();
|
|
if (ImGuiLTable::Checkbox("Use impostor normal maps", &normalMaps))
|
|
_veglayer->setUseImpostorNormalMaps(normalMaps);
|
|
|
|
bool pbrMaps = _veglayer->getUseImpostorPBRMaps();
|
|
if (ImGuiLTable::Checkbox("Use impostor PBR maps", &pbrMaps))
|
|
_veglayer->setUseImpostorPBRMaps(pbrMaps);
|
|
|
|
if (_veglayer->isAlphaToCoverageAvailable())
|
|
{
|
|
bool a2c = _veglayer->getUseAlphaToCoverage();
|
|
if (ImGuiLTable::Checkbox("Alpha to Coverage", &a2c))
|
|
_veglayer->setUseAlphaToCoverage(a2c);
|
|
}
|
|
|
|
unsigned maxTex = _veglayer->getMaxTextureSize();
|
|
const char* maxTexItems[] = { "16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192", "16384" };
|
|
unsigned maxTexIndex = 0;
|
|
for (maxTexIndex = 0; maxTex > (unsigned)::atoi(maxTexItems[maxTexIndex]) && maxTexIndex < IM_ARRAYSIZE(maxTexItems) - 1; ++maxTexIndex);
|
|
if (ImGuiLTable::BeginCombo("Max tex size", maxTexItems[maxTexIndex])) {
|
|
for (int n = 0; n < IM_ARRAYSIZE(maxTexItems); n++) {
|
|
const bool is_selected = (maxTexIndex == n);
|
|
if (ImGui::Selectable(maxTexItems[n], is_selected)) {
|
|
maxTexIndex = n;
|
|
_veglayer->setMaxTextureSize(atoi(maxTexItems[maxTexIndex]));
|
|
}
|
|
if (is_selected)
|
|
ImGui::SetItemDefaultFocus();
|
|
}
|
|
ImGuiLTable::EndCombo();
|
|
}
|
|
|
|
ImGui::Separator();
|
|
ImGuiLTable::Section("Layers:");
|
|
int vi = 0;
|
|
for (auto& iter : _veglayer->options().groups())
|
|
{
|
|
auto& groupName = iter.first;
|
|
auto& groupOptions = iter.second;
|
|
|
|
ImGui::PushID(vi++);
|
|
|
|
if (vi > 1)
|
|
ImGui::Separator();
|
|
|
|
ImGuiLTable::Checkbox(
|
|
groupName.c_str(),
|
|
&groupOptions.enabled().mutable_value());
|
|
|
|
ImGuiLTable::SliderInt(
|
|
" Instances per sqkm",
|
|
&groupOptions.instancesPerSqKm().mutable_value(),
|
|
1024, 1024000,
|
|
"%d",
|
|
ImGuiSliderFlags_Logarithmic);
|
|
|
|
ImGuiLTable::SliderFloat(
|
|
" Overlap %",
|
|
&groupOptions.overlap().mutable_value(),
|
|
0.0f, 1.0f,
|
|
"%.2f", 0);
|
|
|
|
ImGuiLTable::SliderFloat(
|
|
" Alpha cutoff",
|
|
&groupOptions.alphaCutoff().mutable_value(),
|
|
0.0f, 1.0f,
|
|
"%.2f", 0);
|
|
|
|
ImGui::PopID();
|
|
}
|
|
|
|
ImGuiLTable::End();
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::Button("Regenerate"))
|
|
{
|
|
_veglayer->dirty();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Vegetation layer is closed");
|
|
}
|
|
|
|
ImGui::Separator();
|
|
ImGuiLTable::Section("Biomes:");
|
|
|
|
if (ImGuiLTable::Begin("VegBlend"))
|
|
{
|
|
float blendPerc = _biolayer->getBlendPercentage();
|
|
if (ImGuiLTable::SliderFloat("Biome feather %",
|
|
&blendPerc,
|
|
0.0f, 1.0f,
|
|
"%.2f", ImGuiSliderFlags_Logarithmic))
|
|
{
|
|
_biolayer->setBlendPercentage(blendPerc);
|
|
}
|
|
ImGuiLTable::End();
|
|
}
|
|
|
|
if (ImGui::Button("Refresh biomes"))
|
|
{
|
|
_biolayer->dirty();
|
|
auto cp = _biolayer->getCachePolicy();
|
|
cp.minTime() = DateTime().asTimeStamp();
|
|
_biolayer->setCachePolicy(cp);
|
|
std::vector<const Layer*> layers = { _biolayer.get() };
|
|
_mapNode->getTerrainEngine()->invalidateRegion(layers, GeoExtent());
|
|
}
|
|
|
|
ImGui::Checkbox("Show biome under mouse", &_showBiomeUnderMouse);
|
|
if (_showBiomeUnderMouse)
|
|
{
|
|
if (_biomeUnderMouse.working())
|
|
{
|
|
ImGui::Text("searching...");
|
|
ImGui::Text("");
|
|
}
|
|
else if (_biomeUnderMouse.available())
|
|
{
|
|
const Biome* biome = _biomeUnderMouse.value();
|
|
if (biome)
|
|
{
|
|
ImGui::Text("%s", biome->name()->c_str());
|
|
ImGui::Text("id=%s parentid=%s index=%d key=%s",
|
|
biome->id().c_str(),
|
|
biome->parentId()->c_str(),
|
|
biome->index(),
|
|
_biomeUnderMouseKey.str().c_str());
|
|
}
|
|
else
|
|
{
|
|
ImGui::Text("No biome");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGui::Text("No biome");
|
|
}
|
|
}
|
|
|
|
bool showTileBBox = _veglayer->getShowTileBoundingBoxes();
|
|
if (ImGui::Checkbox("Show tile bounding boxes", &showTileBBox))
|
|
{
|
|
_veglayer->setShowTileBoundingBoxes(showTileBBox);
|
|
}
|
|
|
|
static std::map<const Biome*, unsigned> biomeCounts;
|
|
ImGui::Checkbox("Show asset bounding boxes", &_showPlacements);
|
|
if (_showPlacements)
|
|
{
|
|
const char* group_items[] = { "trees", "bushes", "undergrowth" };
|
|
static unsigned group_item = 0;
|
|
ImGui::SameLine();
|
|
if (ImGuiLTable::BeginCombo("Group", group_items[group_item])) {
|
|
for (int n = 0; n < IM_ARRAYSIZE(group_items); n++) {
|
|
const bool is_selected = (group_item == n);
|
|
if (ImGui::Selectable(group_items[n], is_selected))
|
|
group_item = n;
|
|
if (is_selected)
|
|
ImGui::SetItemDefaultFocus();
|
|
}
|
|
ImGuiLTable::EndCombo();
|
|
}
|
|
const char* group_name = group_items[group_item];
|
|
|
|
biomeCounts.clear();
|
|
|
|
auto& p = _pointUnderMouse;
|
|
if (p.isValid())
|
|
{
|
|
auto lod = _veglayer->options().group(group_name).lod().get();
|
|
TileKey key = _mapNode->getMap()->getProfile()->createTileKey(p, lod);
|
|
if (key != _showPlacementsLastTileKey)
|
|
{
|
|
_showPlacementsLastTileKey = key;
|
|
|
|
std::vector<VegetationLayer::Placement> placements;
|
|
|
|
_veglayer->getAssetPlacements(
|
|
key,
|
|
group_name,
|
|
true,
|
|
placements,
|
|
nullptr);
|
|
|
|
FeatureList features;
|
|
|
|
for (auto& p : placements)
|
|
{
|
|
// record the asset's position and properties as a point feature:
|
|
Point* point = new Point();
|
|
point->push_back(p.mapPoint());
|
|
|
|
osg::ref_ptr<Feature> feature = new Feature(point, key.getExtent().getSRS());
|
|
feature->set("elevation", p.mapPoint().z());
|
|
|
|
float width = 1.0f, height = 1.0f;
|
|
auto& bbox = p.asset()->boundingBox();
|
|
if (bbox.valid())
|
|
{
|
|
width = std::max(
|
|
p.asset()->boundingBox().xMax() - p.asset()->boundingBox().xMin(),
|
|
p.asset()->boundingBox().yMax() - p.asset()->boundingBox().yMin());
|
|
|
|
height =
|
|
p.asset()->boundingBox().zMax() - p.asset()->boundingBox().zMin();
|
|
}
|
|
|
|
feature->set("width", width * std::max(p.scale().x(), p.scale().y()));
|
|
feature->set("height", height * p.scale().z());
|
|
feature->set("rotation", p.rotation() * 180.0f / M_PI);
|
|
feature->set("name", p.asset()->assetDef()->name());
|
|
|
|
features.push_back(feature.get());
|
|
|
|
if (biomeCounts.find(p.biome) == biomeCounts.end())
|
|
biomeCounts[p.biome] = 0;
|
|
else
|
|
biomeCounts[p.biome]++;
|
|
}
|
|
|
|
Style s;
|
|
s.getOrCreate<ModelSymbol>()->setModel(_boxgeom.get());
|
|
s.getOrCreate<ModelSymbol>()->scaleX() = NumericExpression("feature.properties.width");
|
|
s.getOrCreate<ModelSymbol>()->scaleY() = NumericExpression("feature.properties.width");
|
|
s.getOrCreate<ModelSymbol>()->scaleZ() = NumericExpression("feature.properties.height");
|
|
|
|
if (_placementNode.valid())
|
|
_placementNode->getParent(0)->removeChild(_placementNode.get());
|
|
|
|
_placementNode = new FeatureNode(features, s);
|
|
_mapNode->addChild(_placementNode);
|
|
}
|
|
}
|
|
|
|
for (auto iter : biomeCounts)
|
|
{
|
|
std::string biome_name = iter.first ? iter.first->name().get() : "null";
|
|
ImGui::Text("(%ld) %s", iter.second, biome_name.c_str());
|
|
}
|
|
}
|
|
else if (_placementNode.valid())
|
|
{
|
|
if (_placementNode.valid())
|
|
_placementNode->getParent(0)->removeChild(_placementNode.get());
|
|
_placementNode = nullptr;
|
|
_showPlacementsLastTileKey = {};
|
|
}
|
|
|
|
ImGui::Checkbox("Simulate tree placement under mouse", &_simulateTreePlacement);
|
|
if (_simulateTreePlacement)
|
|
{
|
|
ImGui::TextWrapped(_simulateTreePlacementOutput.c_str());
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::CollapsingHeader("All Biomes"))
|
|
{
|
|
auto layer = _biolayer;
|
|
auto biocat = layer->getBiomeCatalog();
|
|
auto& bioman = layer->getBiomeManager();
|
|
|
|
#if 0 // doesn't work ... revisit later
|
|
if (ImGui::Checkbox("Select biomes manually", &_manualBiomes))
|
|
{
|
|
layer->setAutoBiomeManagement(!_manualBiomes);
|
|
|
|
if (_manualBiomes)
|
|
{
|
|
_isManualBiomeActive.clear();
|
|
for (auto biome : biocat->getBiomes())
|
|
_isManualBiomeActive[biome] = false;
|
|
}
|
|
else
|
|
{
|
|
stateset(ri)->setDefine("OE_BIOME_INDEX", "-1", 0x7);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ImGui::Separator();
|
|
|
|
if (_manualBiomes)
|
|
{
|
|
for (auto biome : biocat->getBiomes())
|
|
{
|
|
ImGui::PushID(biome);
|
|
char buf[255];
|
|
sprintf(buf, "[%s] %s", biome->id().c_str(), biome->name()->c_str());
|
|
if (ImGui::Checkbox(buf, &_isManualBiomeActive[biome]))
|
|
{
|
|
if (_isManualBiomeActive[biome])
|
|
{
|
|
bioman.ref(biome);
|
|
stateset(ri)->setDefine("OE_BIOME_INDEX", std::to_string(biome->index()), 0x7);
|
|
}
|
|
else
|
|
{
|
|
bioman.unref(biome);
|
|
}
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto biomes = biocat->getBiomes();
|
|
std::sort(biomes.begin(), biomes.end(), [](const Biome* lhs, const Biome* rhs) {
|
|
return lhs->id() < rhs->id(); });
|
|
|
|
for (auto biome : biomes)
|
|
{
|
|
char buf[255];
|
|
sprintf(buf, "[%s] %s", biome->id().c_str(), biome->name()->c_str());
|
|
ImGui::Text(buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ImGui::CollapsingHeader("Active Biomes", ImGuiTreeNodeFlags_DefaultOpen))
|
|
{
|
|
auto biocat = _biolayer->getBiomeCatalog();
|
|
auto& bioman = _biolayer->getBiomeManager();
|
|
auto biomes = bioman.getActiveBiomes();
|
|
for (auto biome : biomes)
|
|
{
|
|
char buf[255];
|
|
sprintf(buf, "[%s] %s", biome->id().c_str(), biome->name()->c_str());
|
|
if (ImGui::TreeNode(buf))
|
|
{
|
|
for (auto& iter : _veglayer->options().groups())
|
|
{
|
|
auto& groupName = iter.first;
|
|
|
|
auto assets = biome->getModelAssets(groupName);
|
|
if (!assets.empty() && ImGui::TreeNode(groupName.c_str()))
|
|
{
|
|
for (auto& asset_ref : assets)
|
|
{
|
|
drawModelAsset(asset_ref->asset());
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ImGui::CollapsingHeader("Resident Assets", ImGuiTreeNodeFlags_DefaultOpen))
|
|
{
|
|
auto biocat = _biolayer->getBiomeCatalog();
|
|
auto& bioman = _biolayer->getBiomeManager();
|
|
|
|
auto assets = bioman.getResidentAssetsIfNotLocked();
|
|
for (auto& asset : assets)
|
|
{
|
|
drawModelAsset(asset);
|
|
}
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void drawModelAsset(const ModelAsset* asset)
|
|
{
|
|
std::string name = asset->name();
|
|
if (asset->traits().empty() == false)
|
|
name += " (" + AssetTraits::toString(asset->traits()) + ")";
|
|
|
|
if (ImGui::TreeNode(name.c_str()))
|
|
{
|
|
if (asset->modelURI().isSet())
|
|
ImGui::Text("Model: %s", asset->modelURI()->base().c_str());
|
|
if (asset->sideBillboardURI().isSet())
|
|
ImGui::Text("Side BB: %s", asset->sideBillboardURI()->base().c_str());
|
|
if (asset->topBillboardURI().isSet())
|
|
ImGui::Text("Top BB: %s", asset->topBillboardURI()->base().c_str());
|
|
if (asset->traits().empty() == false)
|
|
ImGui::Text("Traits: %s", AssetTraits::toString(asset->traits()).c_str());
|
|
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
|
|
void onMove(osg::View* view, float x, float y)
|
|
{
|
|
if (_showBiomeUnderMouse || _showPlacements || _simulateTreePlacement)
|
|
{
|
|
_pointUnderMouse = _mapNode->getGeoPointUnderMouse(view, x, y);
|
|
}
|
|
|
|
if (_showBiomeUnderMouse)
|
|
{
|
|
_biomeUnderMouse.reset();
|
|
|
|
TerrainTile* tile = _mapNode->getTerrain()->getTerrainTileUnderMouse(view, x, y);
|
|
if (tile)
|
|
{
|
|
auto& p = _pointUnderMouse;
|
|
TileKey key = _mapNode->getMap()->getProfile()->createTileKey(p.x(), p.y(), tile->getKey().getLOD());
|
|
key = _biolayer->getBestAvailableTileKey(key, false);
|
|
|
|
if (key.valid())
|
|
{
|
|
_biomeUnderMouseKey = key;
|
|
|
|
_biomeUnderMouse = jobs::dispatch([&, key, p](Cancelable& c)
|
|
{
|
|
const Biome* result = nullptr;
|
|
osg::ref_ptr<ProgressCallback> prog = new ProgressCallback(&c);
|
|
auto g = _biolayer->createImage(key, prog.get());
|
|
if (g.valid())
|
|
{
|
|
GeoImage::pixel_type pixel;
|
|
g.getReader().setBilinear(false);
|
|
g.read(pixel, p);
|
|
int biome_index = (int)pixel.r();
|
|
result = _biolayer->getBiomeCatalog()->getBiomeByIndex(biome_index);
|
|
}
|
|
return result;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_simulateTreePlacement)
|
|
{
|
|
auto& p = _pointUnderMouse;
|
|
_simulateTreePlacementOutput = _veglayer->simulateAssetPlacement(p, "trees");
|
|
}
|
|
else
|
|
{
|
|
_simulateTreePlacementOutput = {};
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|