mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
feat(editor): texture eyedropper, WOT/WHM preference loading
- Texture eyedropper: pickTextureAt() samples dominant texture at world position by reading chunk alpha maps. Alt+Click in paint mode or click the Eyedropper button to activate - loadADT now checks custom_zones/ and output/ for WOT/WHM open format files first, falling back to ADT binary only if not found - Fix unused variable warning in scatterPatches - Document Alt+Click in help panel
This commit is contained in:
parent
acfbf19144
commit
d2acdc7620
4 changed files with 99 additions and 16 deletions
|
|
@ -401,7 +401,18 @@ void EditorApp::processEvents() {
|
|||
giz.setMode(TransformMode::None);
|
||||
} else if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||||
// Path point capture (river/road tool)
|
||||
if (ui_.getPathCapture() != EditorUI::PathCapture::None) {
|
||||
// Alt+click eyedropper in paint mode
|
||||
if (mode_ == EditorMode::Paint && (SDL_GetModState() & KMOD_ALT)) {
|
||||
if (terrainEditor_.brush().isActive()) {
|
||||
std::string picked = texturePainter_.pickTextureAt(
|
||||
terrainEditor_.brush().getPosition());
|
||||
if (!picked.empty()) {
|
||||
texturePainter_.setActiveTexture(picked);
|
||||
showToast("Picked: " + picked.substr(picked.rfind('\\') + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ui_.getPathCapture() != EditorUI::PathCapture::None) {
|
||||
auto ext = window_->getVkContext()->getSwapchainExtent();
|
||||
rendering::Ray ray = camera_.getCamera().screenToWorldRay(
|
||||
static_cast<float>(event.button.x),
|
||||
|
|
@ -620,23 +631,37 @@ void EditorApp::refreshDirtyChunks() {
|
|||
}
|
||||
|
||||
void EditorApp::loadADT(const std::string& mapName, int tileX, int tileY) {
|
||||
std::ostringstream path;
|
||||
path << "World\\Maps\\" << mapName << "\\" << mapName
|
||||
<< "_" << tileX << "_" << tileY << ".adt";
|
||||
|
||||
LOG_INFO("Loading ADT: ", path.str());
|
||||
|
||||
auto adtData = assetManager_->readFile(path.str());
|
||||
if (adtData.empty()) {
|
||||
LOG_ERROR("ADT file not found: ", path.str());
|
||||
return;
|
||||
// Prefer open format (WOT/WHM) if available
|
||||
for (const char* dir : {"custom_zones", "output"}) {
|
||||
std::string wotBase = std::string(dir) + "/" + mapName + "/" + mapName + "_" +
|
||||
std::to_string(tileX) + "_" + std::to_string(tileY);
|
||||
if (WoweeTerrain::importOpen(wotBase, terrain_) && terrain_.isLoaded()) {
|
||||
LOG_INFO("Loaded open format terrain: ", wotBase);
|
||||
showToast("Loaded WOT/WHM: " + mapName);
|
||||
goto terrainReady;
|
||||
}
|
||||
}
|
||||
|
||||
terrain_ = pipeline::ADTLoader::load(adtData);
|
||||
if (!terrain_.isLoaded()) {
|
||||
LOG_ERROR("Failed to parse ADT: ", path.str());
|
||||
return;
|
||||
{
|
||||
std::ostringstream path;
|
||||
path << "World\\Maps\\" << mapName << "\\" << mapName
|
||||
<< "_" << tileX << "_" << tileY << ".adt";
|
||||
|
||||
LOG_INFO("Loading ADT: ", path.str());
|
||||
|
||||
auto adtData = assetManager_->readFile(path.str());
|
||||
if (adtData.empty()) {
|
||||
LOG_ERROR("ADT file not found: ", path.str());
|
||||
return;
|
||||
}
|
||||
|
||||
terrain_ = pipeline::ADTLoader::load(adtData);
|
||||
if (!terrain_.isLoaded()) {
|
||||
LOG_ERROR("Failed to parse ADT: ", path.str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
terrainReady:
|
||||
|
||||
// Override internal coords with what we know from the filename
|
||||
// (instanced maps have arbitrary internal coord values)
|
||||
|
|
|
|||
|
|
@ -477,6 +477,7 @@ void EditorUI::renderMenuBar(EditorApp& app) {
|
|||
ImGui::Text("Quick Actions:");
|
||||
ImGui::BulletText("Ctrl+N — new terrain");
|
||||
ImGui::BulletText("Ctrl+O — load map tile");
|
||||
ImGui::BulletText("Alt+Click — eyedropper (paint mode)");
|
||||
ImGui::BulletText("Middle-drag — orbit camera");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("View:");
|
||||
|
|
@ -1108,6 +1109,17 @@ void EditorUI::renderTexturePaintPanel(EditorApp& app) {
|
|||
if (ImGui::Combo("Paint Mode", &pm, paintModes, 3))
|
||||
paintMode_ = static_cast<PaintMode>(pm);
|
||||
|
||||
if (ImGui::Button("Eyedropper (Alt+Click)")) {
|
||||
auto& brush = app.getTerrainEditor().brush();
|
||||
if (brush.isActive()) {
|
||||
std::string picked = app.getTexturePainter().pickTextureAt(brush.getPosition());
|
||||
if (!picked.empty()) {
|
||||
app.getTexturePainter().setActiveTexture(picked);
|
||||
app.showToast("Picked: " + picked.substr(picked.rfind('\\') + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Directory filter
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ void TexturePainter::gradientBlend(const std::string& tex1, const std::string& t
|
|||
void TexturePainter::scatterPatches(const std::string& texturePath, int count,
|
||||
float minRadius, float maxRadius, uint32_t seed) {
|
||||
if (!terrain_ || texturePath.empty()) return;
|
||||
uint32_t texId = ensureTextureInList(texturePath);
|
||||
ensureTextureInList(texturePath);
|
||||
|
||||
float tileNW_X = (32.0f - static_cast<float>(terrain_->coord.y)) * 533.33333f;
|
||||
float tileNW_Y = (32.0f - static_cast<float>(terrain_->coord.x)) * 533.33333f;
|
||||
|
|
@ -371,5 +371,48 @@ std::vector<int> TexturePainter::erase(const glm::vec3& center, float radius,
|
|||
return modified;
|
||||
}
|
||||
|
||||
std::string TexturePainter::pickTextureAt(const glm::vec3& worldPos) const {
|
||||
if (!terrain_) return "";
|
||||
|
||||
for (int ci = 0; ci < 256; ci++) {
|
||||
const auto& chunk = terrain_->chunks[ci];
|
||||
if (!chunk.hasHeightMap() || chunk.layers.empty()) continue;
|
||||
|
||||
glm::vec2 uv = worldToChunkUV(ci, worldPos);
|
||||
if (uv.x < 0 || uv.x > 1 || uv.y < 0 || uv.y > 1) continue;
|
||||
|
||||
if (chunk.layers.size() == 1) {
|
||||
if (chunk.layers[0].textureId < terrain_->textures.size())
|
||||
return terrain_->textures[chunk.layers[0].textureId];
|
||||
continue;
|
||||
}
|
||||
|
||||
int px = std::clamp(static_cast<int>(uv.x * 64.0f), 0, 63);
|
||||
int py = std::clamp(static_cast<int>(uv.y * 64.0f), 0, 63);
|
||||
int pixelIdx = py * 64 + px;
|
||||
|
||||
uint32_t bestLayer = 0;
|
||||
uint8_t bestAlpha = 0;
|
||||
for (size_t li = 1; li < chunk.layers.size(); li++) {
|
||||
size_t alphaOffset = (li - 1) * 4096 + pixelIdx;
|
||||
if (alphaOffset < chunk.alphaMap.size()) {
|
||||
uint8_t alpha = chunk.alphaMap[alphaOffset];
|
||||
if (alpha > bestAlpha) {
|
||||
bestAlpha = alpha;
|
||||
bestLayer = static_cast<uint32_t>(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestAlpha < 128) bestLayer = 0;
|
||||
|
||||
if (bestLayer < chunk.layers.size() &&
|
||||
chunk.layers[bestLayer].textureId < terrain_->textures.size()) {
|
||||
return terrain_->textures[chunk.layers[bestLayer].textureId];
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ public:
|
|||
// Erase a texture layer at the given position
|
||||
std::vector<int> erase(const glm::vec3& center, float radius, float strength, float falloff);
|
||||
|
||||
// Pick the dominant texture at a world position (eyedropper)
|
||||
std::string pickTextureAt(const glm::vec3& worldPos) const;
|
||||
|
||||
private:
|
||||
uint32_t ensureTextureInList(const std::string& path);
|
||||
int ensureLayerOnChunk(int chunkIdx, uint32_t textureId);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue