mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-18 14:23:56 -07:00
Replace New dialog box with separate tools for resizing maps and replacing terrain textures, to provide more power and to simplify the problem of initialising map settings. Fix engine to cope with dynamic map resizing. Add JSON support to AtObj, to let C++ interact with JSON more easily. This was SVN commit r9566.
383 lines
10 KiB
C++
383 lines
10 KiB
C++
/* Copyright (C) 2011 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 "MessageHandler.h"
|
|
|
|
#include "../CommandProc.h"
|
|
|
|
#include "graphics/Patch.h"
|
|
#include "graphics/TerrainTextureManager.h"
|
|
#include "graphics/TerrainTextureEntry.h"
|
|
#include "graphics/Terrain.h"
|
|
#include "ps/Game.h"
|
|
#include "ps/World.h"
|
|
#include "lib/ogl.h"
|
|
#include "lib/res/graphics/ogl_tex.h"
|
|
#include "simulation2/Simulation2.h"
|
|
#include "simulation2/components/ICmpPathfinder.h"
|
|
#include "simulation2/components/ICmpTerrain.h"
|
|
|
|
#include "../Brushes.h"
|
|
#include "../DeltaArray.h"
|
|
#include "../View.h"
|
|
|
|
namespace AtlasMessage {
|
|
|
|
QUERYHANDLER(GetTerrainGroups)
|
|
{
|
|
const CTerrainTextureManager::TerrainGroupMap &groups = g_TexMan.GetGroups();
|
|
std::vector<std::wstring> groupNames;
|
|
for (CTerrainTextureManager::TerrainGroupMap::const_iterator it = groups.begin(); it != groups.end(); ++it)
|
|
groupNames.push_back(it->first.FromUTF8());
|
|
msg->groupNames = groupNames;
|
|
}
|
|
|
|
static bool CompareTerrain(const sTerrainGroupPreview& a, const sTerrainGroupPreview& b)
|
|
{
|
|
return (wcscmp(a.name.c_str(), b.name.c_str()) < 0);
|
|
}
|
|
|
|
QUERYHANDLER(GetTerrainGroupPreviews)
|
|
{
|
|
std::vector<sTerrainGroupPreview> previews;
|
|
|
|
CTerrainGroup* group = g_TexMan.FindGroup(CStrW(*msg->groupName).ToUTF8());
|
|
for (std::vector<CTerrainTextureEntry*>::const_iterator it = group->GetTerrains().begin(); it != group->GetTerrains().end(); ++it)
|
|
{
|
|
previews.push_back(sTerrainGroupPreview());
|
|
previews.back().name = (*it)->GetTag().FromUTF8();
|
|
|
|
std::vector<unsigned char> buf (msg->imageWidth*msg->imageHeight*3);
|
|
|
|
// It's not good to shrink the entire texture to fit the small preview
|
|
// window, since it's the fine details in the texture that are
|
|
// interesting; so just go down one mipmap level, then crop a chunk
|
|
// out of the middle.
|
|
|
|
// Read the size of the texture. (Usually loads the texture from
|
|
// disk, which is slow.)
|
|
GLint w, h;
|
|
ssize_t level = 1; // level 0 is the original size
|
|
(*it)->GetTexture()->Bind();
|
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &w);
|
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &h);
|
|
|
|
if (w < msg->imageWidth || h < msg->imageHeight)
|
|
{
|
|
// Oops, too small to preview - just use a flat colour
|
|
u32 c = (*it)->GetBaseColor();
|
|
for (ssize_t i = 0; i < msg->imageWidth*msg->imageHeight; ++i)
|
|
{
|
|
buf[i*3+0] = (c>>16) & 0xff;
|
|
buf[i*3+1] = (c>>8) & 0xff;
|
|
buf[i*3+2] = (c>>0) & 0xff;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Read the whole texture into a new buffer
|
|
unsigned char* texdata = new unsigned char[w*h*3];
|
|
glGetTexImage(GL_TEXTURE_2D, level, GL_RGB, GL_UNSIGNED_BYTE, texdata);
|
|
|
|
// Extract the middle section (as a representative preview),
|
|
// and copy into buf
|
|
unsigned char* texdata_ptr = texdata + (w*(h - msg->imageHeight)/2 + (w - msg->imageWidth)/2) * 3;
|
|
unsigned char* buf_ptr = &buf[0];
|
|
for (ssize_t y = 0; y < msg->imageHeight; ++y)
|
|
{
|
|
memcpy(buf_ptr, texdata_ptr, msg->imageWidth*3);
|
|
buf_ptr += msg->imageWidth*3;
|
|
texdata_ptr += w*3;
|
|
}
|
|
|
|
delete[] texdata;
|
|
}
|
|
|
|
previews.back().loaded = (*it)->GetTexture()->IsLoaded();
|
|
previews.back().imageWidth = msg->imageWidth;
|
|
previews.back().imageHeight = msg->imageHeight;
|
|
previews.back().imageData = buf;
|
|
}
|
|
|
|
// Sort the list alphabetically by name
|
|
std::sort(previews.begin(), previews.end(), CompareTerrain);
|
|
msg->previews = previews;
|
|
}
|
|
|
|
QUERYHANDLER(GetTerrainPassabilityClasses)
|
|
{
|
|
CmpPtr<ICmpPathfinder> cmpPathfinder(*View::GetView_Game()->GetSimulation2(), SYSTEM_ENTITY);
|
|
if (!cmpPathfinder.null())
|
|
{
|
|
std::map<std::string, ICmpPathfinder::pass_class_t> classes = cmpPathfinder->GetPassabilityClasses();
|
|
|
|
std::vector<std::wstring> classNames;
|
|
for (std::map<std::string, ICmpPathfinder::pass_class_t>::iterator it = classes.begin(); it != classes.end(); ++it)
|
|
classNames.push_back(CStr(it->first).FromUTF8());
|
|
msg->classNames = classNames;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace {
|
|
|
|
struct TerrainTile
|
|
{
|
|
TerrainTile(CTerrainTextureEntry* t, ssize_t p) : tex(t), priority(p) {}
|
|
CTerrainTextureEntry* tex;
|
|
ssize_t priority;
|
|
};
|
|
|
|
class TerrainArray : public DeltaArray2D<TerrainTile>
|
|
{
|
|
public:
|
|
void Init()
|
|
{
|
|
m_Terrain = g_Game->GetWorld()->GetTerrain();
|
|
m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
|
|
}
|
|
|
|
void UpdatePriority(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priorityScale, ssize_t& priority)
|
|
{
|
|
CMiniPatch* tile = m_Terrain->GetTile(x, y);
|
|
if (!tile)
|
|
return; // tile was out-of-bounds
|
|
|
|
// If this tile matches the current texture, we just want to match its
|
|
// priority; otherwise we want to exceed its priority
|
|
if (tile->GetTextureEntry() == tex)
|
|
priority = std::max(priority, tile->GetPriority()*priorityScale);
|
|
else
|
|
priority = std::max(priority, tile->GetPriority()*priorityScale + 1);
|
|
}
|
|
|
|
CTerrainTextureEntry* GetTexEntry(ssize_t x, ssize_t y)
|
|
{
|
|
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
|
|
return NULL;
|
|
|
|
return get(x, y).tex;
|
|
}
|
|
|
|
ssize_t GetPriority(ssize_t x, ssize_t y)
|
|
{
|
|
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
|
|
return 0;
|
|
|
|
return get(x, y).priority;
|
|
}
|
|
|
|
void PaintTile(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priority)
|
|
{
|
|
// Ignore out-of-bounds tiles
|
|
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
|
|
return;
|
|
|
|
set(x,y, TerrainTile(tex, priority));
|
|
}
|
|
|
|
ssize_t GetTilesPerSide()
|
|
{
|
|
return m_VertsPerSide-1;
|
|
}
|
|
|
|
protected:
|
|
TerrainTile getOld(ssize_t x, ssize_t y)
|
|
{
|
|
CMiniPatch* mp = m_Terrain->GetTile(x, y);
|
|
ENSURE(mp);
|
|
return TerrainTile(mp->Tex, mp->Priority);
|
|
}
|
|
void setNew(ssize_t x, ssize_t y, const TerrainTile& val)
|
|
{
|
|
CMiniPatch* mp = m_Terrain->GetTile(x, y);
|
|
ENSURE(mp);
|
|
mp->Tex = val.tex;
|
|
mp->Priority = val.priority;
|
|
}
|
|
|
|
CTerrain* m_Terrain;
|
|
ssize_t m_VertsPerSide;
|
|
};
|
|
|
|
}
|
|
|
|
BEGIN_COMMAND(PaintTerrain)
|
|
{
|
|
TerrainArray m_TerrainDelta;
|
|
ssize_t m_i0, m_j0, m_i1, m_j1;
|
|
|
|
cPaintTerrain()
|
|
{
|
|
m_TerrainDelta.Init();
|
|
}
|
|
|
|
void MakeDirty()
|
|
{
|
|
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
|
|
CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
|
|
if (!cmpTerrain.null())
|
|
cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
|
|
}
|
|
|
|
void Do()
|
|
{
|
|
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
|
|
|
|
ssize_t x0, y0;
|
|
g_CurrentBrush.GetBottomLeft(x0, y0);
|
|
|
|
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8());
|
|
if (! texentry)
|
|
{
|
|
debug_warn(L"Can't find texentry"); // TODO: nicer error handling
|
|
return;
|
|
}
|
|
|
|
// Priority system: If the new tile should have a high priority,
|
|
// set it to one plus the maximum priority of all surrounding tiles
|
|
// that aren't included in the brush (so that it's definitely the highest).
|
|
// Similar for low priority.
|
|
ssize_t priorityScale = (msg->priority == ePaintTerrainPriority::HIGH ? +1 : -1);
|
|
ssize_t priority = 0;
|
|
|
|
for (ssize_t dy = -1; dy < g_CurrentBrush.m_H+1; ++dy)
|
|
{
|
|
for (ssize_t dx = -1; dx < g_CurrentBrush.m_W+1; ++dx)
|
|
{
|
|
if (!(g_CurrentBrush.Get(dx, dy) > 0.5f)) // ignore tiles that will be painted over
|
|
m_TerrainDelta.UpdatePriority(x0+dx, y0+dy, texentry, priorityScale, priority);
|
|
}
|
|
}
|
|
|
|
for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
|
|
{
|
|
for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
|
|
{
|
|
if (g_CurrentBrush.Get(dx, dy) > 0.5f) // TODO: proper solid brushes
|
|
m_TerrainDelta.PaintTile(x0+dx, y0+dy, texentry, priority*priorityScale);
|
|
}
|
|
}
|
|
|
|
m_i0 = x0;
|
|
m_j0 = y0;
|
|
m_i1 = x0 + g_CurrentBrush.m_W;
|
|
m_j1 = y0 + g_CurrentBrush.m_H;
|
|
MakeDirty();
|
|
}
|
|
|
|
void Undo()
|
|
{
|
|
m_TerrainDelta.Undo();
|
|
MakeDirty();
|
|
}
|
|
|
|
void Redo()
|
|
{
|
|
m_TerrainDelta.Redo();
|
|
MakeDirty();
|
|
}
|
|
|
|
void MergeIntoPrevious(cPaintTerrain* prev)
|
|
{
|
|
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
|
|
prev->m_i0 = std::min(prev->m_i0, m_i0);
|
|
prev->m_j0 = std::min(prev->m_j0, m_j0);
|
|
prev->m_i1 = std::max(prev->m_i1, m_i1);
|
|
prev->m_j1 = std::max(prev->m_j1, m_j1);
|
|
}
|
|
};
|
|
END_COMMAND(PaintTerrain)
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
BEGIN_COMMAND(ReplaceTerrain)
|
|
{
|
|
TerrainArray m_TerrainDelta;
|
|
ssize_t m_i0, m_j0, m_i1, m_j1;
|
|
|
|
cReplaceTerrain()
|
|
{
|
|
m_TerrainDelta.Init();
|
|
}
|
|
|
|
void MakeDirty()
|
|
{
|
|
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
|
|
CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
|
|
if (!cmpTerrain.null())
|
|
cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
|
|
}
|
|
|
|
void Do()
|
|
{
|
|
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
|
|
|
|
ssize_t x0, y0;
|
|
g_CurrentBrush.GetBottomLeft(x0, y0);
|
|
|
|
m_i0 = m_i1 = x0;
|
|
m_j0 = m_j1 = y0;
|
|
|
|
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8());
|
|
if (! texentry)
|
|
{
|
|
debug_warn(L"Can't find texentry"); // TODO: nicer error handling
|
|
return;
|
|
}
|
|
|
|
CTerrainTextureEntry* replacedTex = m_TerrainDelta.GetTexEntry(x0, y0);
|
|
|
|
ssize_t tiles = m_TerrainDelta.GetTilesPerSide();
|
|
|
|
for (ssize_t j = 0; j < tiles; ++j)
|
|
{
|
|
for (ssize_t i = 0; i < tiles; ++i)
|
|
{
|
|
if (m_TerrainDelta.GetTexEntry(i, j) == replacedTex)
|
|
{
|
|
m_i0 = std::min(m_i0, i);
|
|
m_j0 = std::min(m_j0, j);
|
|
m_i1 = std::max(m_i1, i+1);
|
|
m_j1 = std::max(m_j1, j+1);
|
|
m_TerrainDelta.PaintTile(i, j, texentry, m_TerrainDelta.GetPriority(i, j));
|
|
}
|
|
}
|
|
}
|
|
|
|
MakeDirty();
|
|
}
|
|
|
|
void Undo()
|
|
{
|
|
m_TerrainDelta.Undo();
|
|
MakeDirty();
|
|
}
|
|
|
|
void Redo()
|
|
{
|
|
m_TerrainDelta.Redo();
|
|
MakeDirty();
|
|
}
|
|
};
|
|
END_COMMAND(ReplaceTerrain)
|
|
|
|
|
|
}
|