1
0
forked from 0ad/0ad

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.
This commit is contained in:
elexis 2018-02-05 16:02:00 +00:00
parent c95901515e
commit 204b04f2d4
5 changed files with 120 additions and 64 deletions

View File

@ -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<bool, std::wstring, CMapGeneratorWorker::LoadLibrary>("LoadLibrary");
m_ScriptInterface->RegisterFunction<JS::Value, std::wstring, CMapGeneratorWorker::LoadHeightmap>("LoadHeightmapImage");
m_ScriptInterface->RegisterFunction<void, JS::HandleValue, CMapGeneratorWorker::ExportMap>("ExportMap");
m_ScriptInterface->RegisterFunction<void, int, CMapGeneratorWorker::SetProgress>("SetProgress");
m_ScriptInterface->RegisterFunction<CParamNode, std::string, CMapGeneratorWorker::GetTemplate>("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<u16> 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<CMapGeneratorWorker*>(pCxPrivate->pCBData);
JSContext* cx = self->m_ScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue returnValue(cx);
ToJSVal_vector(cx, &returnValue, heightmap);
return returnValue;
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////

View File

@ -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);

74
source/graphics/MapIO.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<u16>& 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<u8> fileData = shared_ptr<u8>(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;
}

View File

@ -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<u16>& heightmap);
class CMapIO
{
public:

View File

@ -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<u8> 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<u8>(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<u16> 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<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (cmpTerrain) cmpTerrain->ReloadTerrain();
if (cmpTerrain)
cmpTerrain->ReloadTerrain();
g_Game->GetView()->GetLOSTexture().MakeDirty();
}