Ticket #1122: atlas_eyedropper-03202012.patch

File atlas_eyedropper-03202012.patch, 20.3 KB (added by historic_bruno, 12 years ago)
  • source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp

     
    372372    wxSystemOptions::SetOption(_T("osx.openfiledialog.always-show-types"), 1);  // has global effect
    373373
    374374    // wxLog::SetTraceMask(wxTraceMessages);
     375   
     376    g_SelectedTexture = _T("grass1_spring");
     377    g_SelectedTexture.NotifyObservers();
    375378
    376379    SetOpenFilename(_T(""));
    377380
  • source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2012 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    4545    TextureNotebook* m_Textures;
    4646};
    4747
    48 
    4948enum
    5049{
    5150    ID_Passability = 1,
     
    6059    return window;
    6160}
    6261
     62// Add spaces into the displayed name so there are more wrapping opportunities
     63static wxString FormatTextureName(wxString name)
     64{
     65    if (name.Len())
     66        name[0] = wxToupper(name[0]);
     67    name.Replace(_T("_"), _T(" "));
     68
     69    return name;
     70}
     71
     72//////////////////////////////////////////////////////////////////////////
     73
     74class TexturePreviewPanel : public wxPanel
     75{
     76private:
     77    static const int imageWidth = 120;
     78    static const int imageHeight = 40;
     79
     80public:
     81    TexturePreviewPanel(wxWindow* parent)
     82        : wxPanel(parent, wxID_ANY), m_Timer(this)
     83    {
     84        m_Conn = g_SelectedTexture.RegisterObserver(0, &TexturePreviewPanel::OnTerrainChange, this);
     85        m_Sizer = new wxStaticBoxSizer(wxVERTICAL, this, _T("Texture"));
     86        SetSizer(m_Sizer);
     87
     88        // Use placeholder bitmap for now
     89        m_Sizer->Add(new wxStaticBitmap(this, wxID_ANY, wxNullBitmap), wxSizerFlags(1).Expand());
     90    }
     91
     92    void LoadPreview()
     93    {
     94        if (m_TextureName.IsEmpty())
     95        {
     96            // If we haven't got a texture yet, copy the global
     97            m_TextureName = g_SelectedTexture;
     98        }
     99
     100        Freeze();
     101
     102        m_Sizer->Clear(true);
     103
     104        AtlasMessage::qGetTerrainTexturePreview qry(m_TextureName.wc_str(), imageWidth, imageHeight);
     105        qry.Post();
     106
     107        AtlasMessage::sTerrainTexturePreview preview = qry.preview;
     108
     109        // Check for invalid/missing texture - shouldn't happen
     110        if (!wxString(qry.preview->name.c_str()).IsEmpty())
     111        {
     112            // Construct the wrapped-text label
     113            wxStaticText* label = new wxStaticText(this, wxID_ANY, FormatTextureName(*qry.preview->name), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
     114            label->Wrap(m_Sizer->GetSize().GetX());
     115
     116            unsigned char* buf = (unsigned char*)(malloc(preview.imageData.GetSize()));
     117            // imagedata.GetBuffer() gives a Shareable<unsigned char>*, which
     118            // is stored the same as a unsigned char*, so we can just copy it.
     119            memcpy(buf, preview.imageData.GetBuffer(), preview.imageData.GetSize());
     120            wxImage img(qry.preview->imageWidth, qry.preview->imageHeight, buf);
     121
     122            wxStaticBitmap* bitmap = new wxStaticBitmap(this, wxID_ANY, wxBitmap(img), wxDefaultPosition, wxSize(qry.preview->imageWidth, qry.preview->imageHeight), wxBORDER_SIMPLE);
     123            m_Sizer->Add(bitmap, wxSizerFlags(1).Align(wxALIGN_CENTRE));
     124            m_Sizer->Add(label, wxSizerFlags().Expand());
     125       
     126            // We have to force the sidebar to layout manually
     127            GetParent()->Layout();
     128
     129            if (preview.loaded && m_Timer.IsRunning())
     130            {
     131                m_Timer.Stop();
     132            }
     133            else if (!preview.loaded && !m_Timer.IsRunning())
     134            {
     135                m_Timer.Start(2000);
     136            }
     137        }
     138
     139        Layout();
     140        Thaw();
     141    }
     142
     143    void OnTerrainChange(const wxString& texture)
     144    {
     145        // Check if texture really changed, to avoid doing this too often
     146        if (texture != m_TextureName)
     147        {
     148            // Load new texture preview
     149            m_TextureName = texture;
     150            LoadPreview();
     151        }
     152    }
     153
     154    void OnTimer(wxTimerEvent& WXUNUSED(evt))
     155    {
     156        LoadPreview();
     157    }
     158
     159private:
     160    ObservableScopedConnection m_Conn;
     161    wxSizer* m_Sizer;
     162    wxTimer m_Timer;
     163    wxString m_TextureName;
     164
     165    DECLARE_EVENT_TABLE();
     166};
     167
     168BEGIN_EVENT_TABLE(TexturePreviewPanel, wxPanel)
     169    EVT_TIMER(wxID_ANY, TexturePreviewPanel::OnTimer)
     170END_EVENT_TABLE();
     171
     172//////////////////////////////////////////////////////////////////////////
     173
    63174TerrainSidebar::TerrainSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer) :
    64175    Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer)
    65176{
     
    84195        wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Texture tools"));
    85196        wxSizer* gridSizer = new wxGridSizer(3);
    86197        gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), this, _("Paint"), _T("PaintTerrain")),
    87             _("Brush with left mouse button to paint texture dominantly,\nright mouse button to paint submissively")), wxSizerFlags().Expand());
     198            _("Brush with left mouse button to paint texture dominantly,\nright mouse button to paint submissively.\nShift-left-click for eyedropper tool")), wxSizerFlags().Expand());
    88199        gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), this, _("Replace"), _T("ReplaceTerrain")),
    89200            _("Replace all of a terrain texture with a new one")), wxSizerFlags().Expand());
    90201        gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), this, _("Fill"), _T("FillTerrain")),
     
    97208        /////////////////////////////////////////////////////////////////////////
    98209        // Brush settings
    99210        wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Brush"));
     211
     212        m_TexturePreview = new TexturePreviewPanel(this);
     213        sizer->Add(m_TexturePreview, wxSizerFlags(1).Expand());
     214
    100215        g_Brush_Elevation.CreateUI(this, sizer);
    101216        m_MainSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10));
    102217    }
     
    145260        m_PassabilityChoice->Append(passClasses[i].c_str());
    146261
    147262    static_cast<TerrainBottomBar*>(m_BottomBar)->LoadTerrain();
     263    m_TexturePreview->LoadPreview();
    148264}
    149265
    150266void TerrainSidebar::OnPassabilityChoice(wxCommandEvent& evt)
     
    245361        AtlasMessage::qGetTerrainGroupPreviews qry((std::wstring)m_Name.wc_str(), imageWidth, imageHeight);
    246362        qry.Post();
    247363
    248         std::vector<AtlasMessage::sTerrainGroupPreview> previews = *qry.previews;
     364        std::vector<AtlasMessage::sTerrainTexturePreview> previews = *qry.previews;
    249365
    250366        bool allLoaded = true;
    251367
     
    254370            if (!previews[i].loaded)
    255371                allLoaded = false;
    256372
    257             // Construct the wrapped-text label
    258373            wxString name = previews[i].name.c_str();
    259374
    260             // Add spaces into the displayed name so there are more wrapping opportunities
    261             wxString labelText = name;
    262             if (labelText.Len())
    263                 labelText[0] = wxToupper(labelText[0]);
    264             labelText.Replace(_T("_"), _T(" "));
    265             wxStaticText* label = new wxStaticText(m_ScrolledPanel, wxID_ANY, labelText, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
     375            // Construct the wrapped-text label
     376            wxStaticText* label = new wxStaticText(m_ScrolledPanel, wxID_ANY, FormatTextureName(name), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
    266377            label->Wrap(imageWidth);
    267378
    268379            unsigned char* buf = (unsigned char*)(malloc(previews[i].imageData.GetSize()));
     
    303414        wxButton* button = wxDynamicCast(evt.GetEventObject(), wxButton);
    304415        wxString name = static_cast<wxStringClientData*>(button->GetClientObject())->GetData();
    305416        g_SelectedTexture = name;
     417        g_SelectedTexture.NotifyObservers();
    306418
    307419        if (m_LastTerrainSelection)
    308420            m_LastTerrainSelection->SetBackgroundColour(wxNullColour);
  • source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2012 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    1717
    1818#include "../Common/Sidebar.h"
    1919
     20class TexturePreviewPanel;
     21
    2022class TerrainSidebar : public Sidebar
    2123{
    2224public:
     
    3133    void OnResizeMap(wxCommandEvent& evt);
    3234
    3335    wxChoice* m_PassabilityChoice;
     36    TexturePreviewPanel* m_TexturePreview;
    3437
    3538    DECLARE_EVENT_TABLE();
    3639};
     40
  • source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/MiscState.cpp

     
    1 /* Copyright (C) 2009 Wildfire Games.
     1/* Copyright (C) 2012 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    1919
    2020#include "MiscState.h"
    2121
    22 wxString g_SelectedTexture = _T("grass1_spring");
     22Observable<wxString> g_SelectedTexture;
    2323
    2424Observable<std::vector<AtlasMessage::ObjectID> > g_SelectedObjects;
  • source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/MiscState.h

     
    2525    typedef unsigned int ObjectID;
    2626}
    2727
    28 extern wxString g_SelectedTexture;
     28extern Observable<wxString> g_SelectedTexture;
    2929
    3030extern Observable<std::vector<AtlasMessage::ObjectID> > g_SelectedObjects;
    3131
  • source/tools/atlas/AtlasUI/ScenarioEditor/Tools/PaintTerrain.cpp

     
    1 /* Copyright (C) 2009 Wildfire Games.
     1/* Copyright (C) 2012 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    3131
    3232    Position m_Pos;
    3333
     34    // Brush for eyedropper preview
     35    //  (it's confusing if we use the arbitrarily sized paint brush)
     36    Brush m_EyedropperBrush;
     37
     38    static const wxKeyCode EYEDROPPER_HOTKEY = WXK_SHIFT;
     39
    3440public:
    3541    PaintTerrain()
    3642    {
    3743        SetState(&Waiting);
     44
     45        m_EyedropperBrush.SetSquare(2);
    3846    }
    3947
    4048
     
    4957        POST_MESSAGE(BrushPreview, (false, Position()));
    5058    }
    5159
    52 
    5360    struct sWaiting : public State
    5461    {
     62        bool OnKey(PaintTerrain* obj, wxKeyEvent& evt, KeyEventType type)
     63        {
     64            if (type == KEY_DOWN && evt.GetKeyCode() == EYEDROPPER_HOTKEY)
     65            {
     66                SET_STATE(Eyedropper);
     67                return true;
     68            }
     69            else
     70            {
     71                return false;
     72            }
     73        }
     74
    5575        bool OnMouse(PaintTerrain* obj, wxMouseEvent& evt)
    5676        {
    5777            if (evt.LeftDown())
     
    135155        int GetPriority() { return AtlasMessage::ePaintTerrainPriority::LOW; }
    136156    }
    137157    PaintingLow;
     158
     159    struct sEyedropper : public State
     160    {
     161        void OnEnter(PaintTerrain* obj)
     162        {
     163            obj->m_EyedropperBrush.MakeActive();
     164        }
     165
     166        void OnLeave(PaintTerrain* WXUNUSED(obj))
     167        {
     168            g_Brush_Elevation.MakeActive();
     169        }
     170
     171        bool OnKey(PaintTerrain* obj, wxKeyEvent& evt, KeyEventType type)
     172        {
     173            if (type == KEY_UP && evt.GetKeyCode() == EYEDROPPER_HOTKEY)
     174            {
     175                SET_STATE(Waiting);
     176                return true;
     177            }
     178            else
     179            {
     180                return false;
     181            }
     182        }
     183
     184        bool OnMouse(PaintTerrain* WXUNUSED(obj), wxMouseEvent& evt)
     185        {
     186            if (evt.LeftDown() || evt.Dragging())
     187            {
     188                POST_MESSAGE(BrushPreview, (true, evt.GetPosition()));
     189                AtlasMessage::qGetTerrainTexture qry(evt.GetPosition());
     190                qry.Post();
     191
     192                g_SelectedTexture = wxString(qry.texture.c_str());
     193                g_SelectedTexture.NotifyObservers();
     194                return true;
     195            }
     196            else if (evt.Moving())
     197            {
     198                POST_MESSAGE(BrushPreview, (true, Position(evt.GetPosition())));
     199                return true;
     200            }
     201            else
     202            {
     203                return false;
     204            }
     205        }
     206    }
     207    Eyedropper;
    138208};
    139209
    140210IMPLEMENT_DYNAMIC_CLASS(PaintTerrain, StateDrivenTool<PaintTerrain>);
  • source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp

     
    5151    msg->groupNames = groupNames;
    5252}
    5353
    54 static bool CompareTerrain(const sTerrainGroupPreview& a, const sTerrainGroupPreview& b)
     54static bool CompareTerrain(const sTerrainTexturePreview& a, const sTerrainTexturePreview& b)
    5555{
    5656    return (wcscmp(a.name.c_str(), b.name.c_str()) < 0);
    5757}
    5858
    59 QUERYHANDLER(GetTerrainGroupPreviews)
     59static sTerrainTexturePreview GetPreview(CTerrainTextureEntry* tex, int width, int height)
    6060{
    61     std::vector<sTerrainGroupPreview> previews;
     61    sTerrainTexturePreview preview;
     62    preview.name = tex->GetTag().FromUTF8();
    6263
    63     CTerrainGroup* group = g_TexMan.FindGroup(CStrW(*msg->groupName).ToUTF8());
    64     for (std::vector<CTerrainTextureEntry*>::const_iterator it = group->GetTerrains().begin(); it != group->GetTerrains().end(); ++it)
    65     {
    66         previews.push_back(sTerrainGroupPreview());
    67         previews.back().name = (*it)->GetTag().FromUTF8();
     64    std::vector<unsigned char> buf (width*height*3);
    6865
    69         std::vector<unsigned char> buf (msg->imageWidth*msg->imageHeight*3);
    70 
    7166#if !CONFIG2_GLES
    72         // It's not good to shrink the entire texture to fit the small preview
    73         // window, since it's the fine details in the texture that are
    74         // interesting; so just go down one mipmap level, then crop a chunk
    75         // out of the middle.
     67    // It's not good to shrink the entire texture to fit the small preview
     68    // window, since it's the fine details in the texture that are
     69    // interesting; so just go down one mipmap level, then crop a chunk
     70    // out of the middle.
    7671
    77         // Read the size of the texture. (Usually loads the texture from
    78         // disk, which is slow.)
    79         (*it)->GetTexture()->Bind();
    80         int level = 1; // level 0 is the original size
    81         int w = std::max(1, (int)(*it)->GetTexture()->GetWidth() >> level);
    82         int h = std::max(1, (int)(*it)->GetTexture()->GetHeight() >> level);
     72    // Read the size of the texture. (Usually loads the texture from
     73    // disk, which is slow.)
     74    tex->GetTexture()->Bind();
     75    int level = 1; // level 0 is the original size
     76    int w = std::max(1, (int)tex->GetTexture()->GetWidth() >> level);
     77    int h = std::max(1, (int)tex->GetTexture()->GetHeight() >> level);
    8378
    84         if (w >= msg->imageWidth && h >= msg->imageHeight)
     79    if (w >= width && h >= height)
     80    {
     81        // Read the whole texture into a new buffer
     82        unsigned char* texdata = new unsigned char[w*h*3];
     83        glGetTexImage(GL_TEXTURE_2D, level, GL_RGB, GL_UNSIGNED_BYTE, texdata);
     84
     85        // Extract the middle section (as a representative preview),
     86        // and copy into buf
     87        unsigned char* texdata_ptr = texdata + (w*(h - height)/2 + (w - width)/2) * 3;
     88        unsigned char* buf_ptr = &buf[0];
     89        for (ssize_t y = 0; y < height; ++y)
    8590        {
    86             // Read the whole texture into a new buffer
    87             unsigned char* texdata = new unsigned char[w*h*3];
    88             glGetTexImage(GL_TEXTURE_2D, level, GL_RGB, GL_UNSIGNED_BYTE, texdata);
     91            memcpy(buf_ptr, texdata_ptr, width*3);
     92            buf_ptr += width*3;
     93            texdata_ptr += w*3;
     94        }
    8995
    90             // Extract the middle section (as a representative preview),
    91             // and copy into buf
    92             unsigned char* texdata_ptr = texdata + (w*(h - msg->imageHeight)/2 + (w - msg->imageWidth)/2) * 3;
    93             unsigned char* buf_ptr = &buf[0];
    94             for (ssize_t y = 0; y < msg->imageHeight; ++y)
    95             {
    96                 memcpy(buf_ptr, texdata_ptr, msg->imageWidth*3);
    97                 buf_ptr += msg->imageWidth*3;
    98                 texdata_ptr += w*3;
    99             }
    100 
    101             delete[] texdata;
    102         }
    103         else
     96        delete[] texdata;
     97    }
     98    else
    10499#endif
     100    {
     101        // Too small to preview, or glGetTexImage not supported (on GLES)
     102        // Just use a flat color instead
     103        u32 c = tex->GetBaseColor();
     104        for (ssize_t i = 0; i < width*height; ++i)
    105105        {
    106             // Too small to preview, or glGetTexImage not supported (on GLES)
    107             // Just use a flat color instead
    108             u32 c = (*it)->GetBaseColor();
    109             for (ssize_t i = 0; i < msg->imageWidth*msg->imageHeight; ++i)
    110             {
    111                 buf[i*3+0] = (c>>16) & 0xff;
    112                 buf[i*3+1] = (c>>8) & 0xff;
    113                 buf[i*3+2] = (c>>0) & 0xff;
    114             }
     106            buf[i*3+0] = (c>>16) & 0xff;
     107            buf[i*3+1] = (c>>8) & 0xff;
     108            buf[i*3+2] = (c>>0) & 0xff;
    115109        }
    116 
    117         previews.back().loaded = (*it)->GetTexture()->IsLoaded();
    118         previews.back().imageWidth = msg->imageWidth;
    119         previews.back().imageHeight = msg->imageHeight;
    120         previews.back().imageData = buf;
    121110    }
    122111
     112    preview.loaded = tex->GetTexture()->IsLoaded();
     113    preview.imageWidth = width;
     114    preview.imageHeight = height;
     115    preview.imageData = buf;
     116
     117    return preview;
     118}
     119
     120QUERYHANDLER(GetTerrainGroupPreviews)
     121{
     122    std::vector<sTerrainTexturePreview> previews;
     123
     124    CTerrainGroup* group = g_TexMan.FindGroup(CStrW(*msg->groupName).ToUTF8());
     125    for (std::vector<CTerrainTextureEntry*>::const_iterator it = group->GetTerrains().begin(); it != group->GetTerrains().end(); ++it)
     126    {
     127        previews.push_back(GetPreview(*it, msg->imageWidth, msg->imageHeight));
     128    }
     129
    123130    // Sort the list alphabetically by name
    124131    std::sort(previews.begin(), previews.end(), CompareTerrain);
    125132    msg->previews = previews;
     
    139146    }
    140147}
    141148
     149QUERYHANDLER(GetTerrainTexture)
     150{
     151    ssize_t x, y;
     152    g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
     153    g_CurrentBrush.GetCentre(x, y);
     154
     155    CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
     156    CMiniPatch* tile = terrain->GetTile(x, y);
     157    if (tile)
     158    {
     159        CTerrainTextureEntry* tex = tile->GetTextureEntry();
     160        msg->texture = tex->GetTag().FromUTF8();
     161    }
     162    else
     163    {
     164        msg->texture = std::wstring();
     165    }
     166}
     167
     168QUERYHANDLER(GetTerrainTexturePreview)
     169{
     170    CTerrainTextureEntry* tex = g_TexMan.FindTexture(CStrW(*msg->name).ToUTF8());
     171    if (tex)
     172    {
     173        msg->preview = GetPreview(tex, msg->imageWidth, msg->imageHeight);
     174    }
     175    else
     176    {
     177        sTerrainTexturePreview noPreview;
     178        noPreview.name = std::wstring();
     179        noPreview.imageHeight = 0;
     180        noPreview.imageWidth = 0;
     181        msg->preview = noPreview;
     182    }
     183}
     184
    142185//////////////////////////////////////////////////////////////////////////
    143186
    144187namespace {
  • source/tools/atlas/GameInterface/Messages.h

     
    260260      );
    261261
    262262#ifndef MESSAGES_SKIP_STRUCTS
    263 struct sTerrainGroupPreview
     263struct sTerrainTexturePreview
    264264{
    265265    Shareable<std::wstring> name;
    266266    Shareable<bool> loaded;
     
    268268    Shareable<int> imageHeight;
    269269    Shareable<std::vector<unsigned char> > imageData; // RGB*width*height
    270270};
    271 SHAREABLE_STRUCT(sTerrainGroupPreview);
     271SHAREABLE_STRUCT(sTerrainTexturePreview);
    272272#endif
    273273
    274274QUERY(GetTerrainGroupPreviews,
     
    276276      ((int, imageWidth))
    277277      ((int, imageHeight))
    278278      ,
    279       ((std::vector<sTerrainGroupPreview>, previews))
     279      ((std::vector<sTerrainTexturePreview>, previews))
    280280      );
    281281
    282282QUERY(GetTerrainPassabilityClasses,
     
    284284      ((std::vector<std::wstring>, classNames))
    285285      );
    286286
     287QUERY(GetTerrainTexturePreview,
     288        ((std::wstring, name))
     289        ((int, imageWidth))
     290        ((int, imageHeight))
     291        ,
     292        ((sTerrainTexturePreview, preview))
     293        );
     294
    287295//////////////////////////////////////////////////////////////////////////
    288296
    289297#ifndef MESSAGES_SKIP_STRUCTS
     
    482490        ((std::wstring, texture))
    483491        );
    484492
     493QUERY(GetTerrainTexture,
     494        ((Position, pos))
     495        ,
     496        ((std::wstring, texture))
     497        );
     498
    485499//////////////////////////////////////////////////////////////////////////
    486500
    487501QUERY(PickObject,
  • source/tools/atlas/GameInterface/Shareable.h

     
    118118// Shareable containers must have shareable contents - but it's easy to forget
    119119// to declare them, so make sure the errors are almost readable, like:
    120120//   "use of undefined type 'REQUIRE_TYPE_TO_BE_SHAREABLE_FAILURE<T,__formal>
    121 //      with [ T=AtlasMessage::sTerrainGroupPreview, __formal=false ]"
     121//      with [ T=AtlasMessage::sTerrainTexturePreview, __formal=false ]"
    122122//
    123123// (Implementation based on boost/static_assert)
    124124template <typename T, bool> struct REQUIRE_TYPE_TO_BE_SHAREABLE_FAILURE;