From 204b04f2d4668d5238f4542a3a6b669b2453e910 Mon Sep 17 00:00:00 2001 From: elexis Date: Mon, 5 Feb 2018 16:02:00 +0000 Subject: [PATCH] Allow random map scripts to load heightmap image files, fixes #5018. Move the atlas heightmap import code to MapIO.cpp and reuse it. Implements what 33e3e6c2ab and 69b7f39bf1 wanted to be. This was SVN commit r21113. --- source/graphics/MapGenerator.cpp | 27 ++++++- source/graphics/MapGenerator.h | 1 + source/graphics/MapIO.cpp | 74 ++++++++++++++++++ source/graphics/MapIO.h | 6 ++ .../GameInterface/Handlers/MapHandlers.cpp | 76 ++++--------------- 5 files changed, 120 insertions(+), 64 deletions(-) create mode 100644 source/graphics/MapIO.cpp diff --git a/source/graphics/MapGenerator.cpp b/source/graphics/MapGenerator.cpp index 8ffe7010c7..f068cd261b 100644 --- a/source/graphics/MapGenerator.cpp +++ b/source/graphics/MapGenerator.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2018 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -19,11 +19,14 @@ #include "MapGenerator.h" +#include "graphics/MapIO.h" #include "graphics/Terrain.h" +#include "lib/status.h" #include "lib/timer.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "ps/scripting/JSInterface_VFS.h" +#include "scriptinterface/ScriptConversions.h" // TODO: what's a good default? perhaps based on map size #define RMS_RUNTIME_SIZE 96 * 1024 * 1024 @@ -101,6 +104,7 @@ bool CMapGeneratorWorker::Run() // Functions for RMS JSI_VFS::RegisterScriptFunctions_Maps(*m_ScriptInterface); m_ScriptInterface->RegisterFunction("LoadLibrary"); + m_ScriptInterface->RegisterFunction("LoadHeightmapImage"); m_ScriptInterface->RegisterFunction("ExportMap"); m_ScriptInterface->RegisterFunction("SetProgress"); m_ScriptInterface->RegisterFunction("GetTemplate"); @@ -263,6 +267,27 @@ bool CMapGeneratorWorker::LoadScripts(const std::wstring& libraryName) return true; } +JS::Value CMapGeneratorWorker::LoadHeightmap(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& vfsPath) +{ + OsPath realPath; + if (g_VFS->GetRealPath(vfsPath, realPath) != INFO::OK) + return JS::UndefinedValue(); + + std::vector heightmap; + if (LoadHeightmapImage(realPath, heightmap) != INFO::OK) + { + LOGERROR("Could not load heightmap file '%s'", utf8_from_wstring(vfsPath).c_str()); + return JS::UndefinedValue(); + } + + CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); + JSContext* cx = self->m_ScriptInterface->GetContext(); + JSAutoRequest rq(cx); + JS::RootedValue returnValue(cx); + ToJSVal_vector(cx, &returnValue, heightmap); + return returnValue; +} + ////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////// diff --git a/source/graphics/MapGenerator.h b/source/graphics/MapGenerator.h index 9480410cda..74fe281d2f 100644 --- a/source/graphics/MapGenerator.h +++ b/source/graphics/MapGenerator.h @@ -123,6 +123,7 @@ private: // callbacks for script functions static bool LoadLibrary(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name); static void ExportMap(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data); + static JS::Value LoadHeightmap(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& src); static void SetProgress(ScriptInterface::CxPrivate* pCxPrivate, int progress); static CParamNode GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); static bool TemplateExists(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); diff --git a/source/graphics/MapIO.cpp b/source/graphics/MapIO.cpp new file mode 100644 index 0000000000..e8fbc76795 --- /dev/null +++ b/source/graphics/MapIO.cpp @@ -0,0 +1,74 @@ +/* Copyright (C) 2018 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. 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 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. 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 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "MapIO.h" + +#include "graphics/Patch.h" +#include "lib/file/file.h" +#include "lib/os_path.h" +#include "lib/status.h" +#include "lib/tex/tex.h" +#include "maths/MathUtil.h" + +Status LoadHeightmapImage(const OsPath& filepath, std::vector& heightmap) +{ + File file; + RETURN_STATUS_IF_ERR(file.Open(OsString(filepath), O_RDONLY)); + + size_t fileSize = lseek(file.Descriptor(), 0, SEEK_END); + lseek(file.Descriptor(), 0, SEEK_SET); + + shared_ptr fileData = shared_ptr(new u8[fileSize]); + + Status readvalue = read(file.Descriptor(), fileData.get(), fileSize); + file.Close(); + RETURN_STATUS_IF_ERR(readvalue); + + // Decode to a raw pixel format + Tex tex; + RETURN_STATUS_IF_ERR(tex.decode(fileData, fileSize)); + + // Convert to uncompressed BGRA with no mipmaps + RETURN_STATUS_IF_ERR(tex.transform_to((tex.m_Flags | TEX_BGR | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS))); + + // Pick smallest side of texture; truncate if not divisible by PATCH_SIZE + ssize_t tileSize = std::min(tex.m_Width, tex.m_Height); + tileSize -= tileSize % PATCH_SIZE; + + u8* mapdata = tex.get_data(); + ssize_t bytesPP = tex.m_Bpp / 8; + ssize_t mapLineSkip = tex.m_Width * bytesPP; + + // Copy image data into the heightmap + heightmap.resize(SQR(tileSize + 1)); + for (ssize_t y = 0; y < tileSize + 1; ++y) + for (ssize_t x = 0; x < tileSize + 1; ++x) + { + // Repeat the last pixel of the image for the last vertex of the heightmap + int offset = std::min(y, tileSize - 1) * mapLineSkip + std::min(x, tileSize - 1) * bytesPP; + + // Pick color channel with highest value + u16 value = std::max({mapdata[offset], mapdata[offset + bytesPP], mapdata[offset + bytesPP * 2]}); + value = mapdata[offset]; + + heightmap[(tileSize - y) * (tileSize + 1) + x] = clamp(value * 256, 0, 65535); + } + + return INFO::OK; +} diff --git a/source/graphics/MapIO.h b/source/graphics/MapIO.h index befb44c037..869fe44d41 100644 --- a/source/graphics/MapIO.h +++ b/source/graphics/MapIO.h @@ -18,6 +18,12 @@ #ifndef INCLUDED_MAPIO #define INCLUDED_MAPIO +#include "lib/os_path.h" +#include "lib/status.h" + +// Opens the given texture file and stores it in a one-dimensional u16 vector. +Status LoadHeightmapImage(const OsPath& filepath, std::vector& heightmap); + class CMapIO { public: diff --git a/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp index 6a4f1adbe2..1613ccf07e 100644 --- a/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp +++ b/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp @@ -23,14 +23,15 @@ #include "graphics/GameView.h" #include "graphics/LOSTexture.h" +#include "graphics/MapIO.h" #include "graphics/MapWriter.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "lib/bits.h" -#include "lib/file/file.h" -#include "lib/tex/tex.h" +#include "lib/file/vfs/vfs_path.h" +#include "lib/status.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" @@ -167,80 +168,29 @@ MESSAGEHANDLER(LoadMap) MESSAGEHANDLER(ImportHeightmap) { - CStrW src = *msg->filename; - - size_t fileSize; - shared_ptr fileData; - - // read in image file - File file; - if (file.Open(src, O_RDONLY) < 0) - { - LOGERROR("Failed to load heightmap."); - return; - } - - fileSize = lseek(file.Descriptor(), 0, SEEK_END); - lseek(file.Descriptor(), 0, SEEK_SET); - - fileData = shared_ptr(new u8[fileSize]); - - if (read(file.Descriptor(), fileData.get(), fileSize) < 0) - { - LOGERROR("Failed to read heightmap image."); - file.Close(); - return; - } - - file.Close(); - - // decode to a raw pixel format - Tex tex; - if (tex.decode(fileData, fileSize) < 0) + std::vector heightmap_source; + if (LoadHeightmapImage(*msg->filename, heightmap_source) != INFO::OK) { LOGERROR("Failed to decode heightmap."); return; } - // Convert to uncompressed BGRA with no mipmaps - if (tex.transform_to((tex.m_Flags | TEX_BGR | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)) < 0) - { - LOGERROR("Failed to transform heightmap."); - return; - } - - // pick smallest side of texture; truncate if not divisible by PATCH_SIZE - ssize_t terrainSize = std::min(tex.m_Width, tex.m_Height); - terrainSize -= terrainSize % PATCH_SIZE; - // resize terrain to heightmap size + // Notice that the number of tiles/pixels per side of the heightmap image is + // one less than the number of vertices per side of the heightmap. CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); - terrain->Resize(terrainSize / PATCH_SIZE); - ENSURE(terrainSize + 1 == g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide()); + terrain->Resize((sqrt(heightmap_source.size()) - 1) / PATCH_SIZE); // copy heightmap data into map u16* heightmap = g_Game->GetWorld()->GetTerrain()->GetHeightMap(); - - u8* mapdata = tex.get_data(); - ssize_t bytesPP = tex.m_Bpp / 8; - ssize_t mapLineSkip = tex.m_Width * bytesPP; - - for (ssize_t y = 0; y < terrainSize + 1; ++y) - { - for (ssize_t x = 0; x < terrainSize + 1; ++x) - { - // repeat the last pixel of the image for the last vertex of the heightmap - int offset = std::min(y, terrainSize - 1) * mapLineSkip + std::min(x, terrainSize - 1) * bytesPP; - - // pick color channel with highest value - u16 value = std::max(mapdata[offset+bytesPP*2], std::max(mapdata[offset], mapdata[offset+bytesPP])); - heightmap[(terrainSize - y) * (terrainSize + 1) + x] = clamp(value * 256, 0, 65535); - } - } + ENSURE(heightmap_source.size() == (std::size_t) SQR(g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide())); + std::copy(heightmap_source.begin(), heightmap_source.end(), heightmap); // update simulation CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); - if (cmpTerrain) cmpTerrain->ReloadTerrain(); + if (cmpTerrain) + cmpTerrain->ReloadTerrain(); + g_Game->GetView()->GetLOSTexture().MakeDirty(); }