diff --git a/binaries/data/tools/atlas/lists.xml b/binaries/data/tools/atlas/lists.xml
index 7314203030..b85bc1926d 100644
--- a/binaries/data/tools/atlas/lists.xml
+++ b/binaries/data/tools/atlas/lists.xml
@@ -1,28 +1,26 @@
-
-
- - head
- - helmet
- - head_extra
- - l_hand
- - shield
- - l_forearm
- - l_shoulder
- - r_hand
- - r_forearm
- - r_shoulder
- - chest
- - back
- - shoulders
- - l_leg
- - r_leg
- - l_hip
- - r_hip
- - tree
- - props_main
- - props_fancy
+ - head
+ - helmet
+ - head_extra
+ - l_hand
+ - shield
+ - l_forearm
+ - l_shoulder
+ - r_hand
+ - r_forearm
+ - r_shoulder
+ - chest
+ - back
+ - shoulders
+ - l_leg
+ - r_leg
+ - l_hip
+ - r_hip
+ - tree
+ - props_main
+ - props_fancy
@@ -54,5 +52,5 @@
-
-
\ No newline at end of file
+
+
diff --git a/binaries/data/tools/atlas/scripts/section/terrain.js b/binaries/data/tools/atlas/scripts/section/terrain.js
new file mode 100644
index 0000000000..b50afa321a
--- /dev/null
+++ b/binaries/data/tools/atlas/scripts/section/terrain.js
@@ -0,0 +1,142 @@
+var brushShapes = {
+ 'circle': {
+ width: function (size) { return size },
+ height: function (size) { return size },
+ data: function (size) {
+ var data = [];
+ // All calculations are done in units of half-tiles, since that
+ // is the required precision
+ var mid_x = size-1;
+ var mid_y = size-1;
+ var scale = 1 / (Math.sqrt(2) - 1);
+ for (var y = 0; y < size; ++y)
+ {
+ for (var x = 0; x < size; ++x)
+ {
+ var dist_sq = // scaled to 0 in centre, 1 on edge
+ ((2*x - mid_x)*(2*x - mid_x) +
+ (2*y - mid_y)*(2*y - mid_y)) / (size*size);
+ if (dist_sq <= 1)
+ data.push((Math.sqrt(2 - dist_sq) - 1) * scale);
+ else
+ data.push(0);
+ }
+ }
+ return data;
+ }
+ },
+
+ 'square': {
+ width: function (size) { return size },
+ height: function (size) { return size },
+ data: function (size) {
+ var data = [];
+ for (var i = 0; i < size*size; ++i)
+ data.push(1);
+ return data;
+ }
+ }
+};
+
+var brush = {
+ shape: brushShapes['circle'],
+ size: 4,
+ strength: 1.0,
+ active: false,
+ send: function () {
+ Atlas.Message.Brush(
+ this.shape.width(this.size),
+ this.shape.height(this.size),
+ this.shape.data(this.size)
+ );
+ // TODO: rather than this hack to make things interact correctly with C++ tools,
+ // implement the tools in JS and do something better
+ Atlas.SetBrushStrength(this.strength);
+ }
+};
+
+function init(window)
+{
+ window.sizer = new wxBoxSizer(wxOrientation.VERTICAL);
+
+ var tools = [
+ /* text label; internal tool name; button */
+ [ 'Modify', 'AlterElevation', undefined ],
+ [ 'Flatten', 'FlattenElevation', undefined ],
+ [ 'Paint', 'PaintTerrain', undefined ]
+ ];
+ var selectedTool = null; // null if none selected, else an element of 'tools'
+
+ var toolSizer = new wxStaticBoxSizer(new wxStaticBox(window, -1, 'Elevation tools'), wxOrientation.HORIZONTAL);
+ window.sizer.add(toolSizer, 0, wxStretch.EXPAND);
+ for each (var tool in tools)
+ {
+ var button = new wxButton(window, -1, tool[0]);
+ toolSizer.add(button, 1);
+ tool[2] = button;
+
+ // Explicitly set the background to the default colour, so that the button
+ // is always owner-drawn (by the wxButton code), rather than initially using the
+ // native (standard colour) button appearance then changing inconsistently later.
+ button.backgroundColour = wxSystemSettings.getColour(wxSystemSettings.COLOUR_BTNFACE);
+
+ (function(tool) { // (local scope)
+ button.onClicked = function () {
+ if (selectedTool == tool)
+ {
+ // Clicking on one tool twice should disable it
+ selectedTool = null;
+ this.backgroundColour = wxSystemSettings.getColour(wxSystemSettings.COLOUR_BTNFACE);
+ Atlas.SetCurrentTool('');
+ }
+ else
+ {
+ if (selectedTool)
+ selectedTool[2].backgroundColour = wxSystemSettings.getColour(wxSystemSettings.COLOUR_BTNFACE);
+ selectedTool = tool;
+ this.backgroundColour = new wxColour(0xEE, 0xCC, 0x55);
+ Atlas.SetCurrentTool(tool[1]);
+ brush.send();
+ }
+ };
+ })(tool);
+ }
+
+ var brushSizer = new wxStaticBoxSizer(new wxStaticBox(window, -1, 'Brush'), wxOrientation.VERTICAL);
+ window.sizer.add(brushSizer);
+
+ var shapes = [
+ [ 'Circle', brushShapes['circle'] ],
+ [ 'Square', brushShapes['square'] ]
+ ];
+ var shapeNames = [];
+ for each (var s in shapes) shapeNames.push(s[0]);
+ var shapeBox = new wxRadioBox(window, -1, 'Shape', wxDefaultPosition, wxDefaultSize, shapeNames);
+ brushSizer.add(shapeBox);
+ shapeBox.onRadioBox = function(evt)
+ {
+ brush.shape = shapes[evt.integer][1];
+ brush.send();
+ };
+
+ var brushSettingsSizer = new wxFlexGridSizer(2);
+
+ brushSizer.add(brushSettingsSizer);
+ brushSettingsSizer.add(new wxStaticText(window, -1, 'Size'), 0, wxAlignment.RIGHT);
+ var sizeSpinner = new wxSpinCtrl(window, -1, 4, wxDefaultPosition, wxDefaultSize, wxSpinCtrl.ARROW_KEYS, 1, 100);
+ brushSettingsSizer.add(sizeSpinner);
+ sizeSpinner.onSpinCtrl = function(evt)
+ {
+ brush.size = evt.position;
+ brush.send();
+ };
+
+ brushSettingsSizer.add(new wxStaticText(window, -1, 'Strength'), wxAlignment.RIGHT);
+ var strengthSpinner = new wxSpinCtrl(window, -1, 10, wxDefaultPosition, wxDefaultSize, wxSpinCtrl.ARROW_KEYS, 1, 100);
+ brushSettingsSizer.add(strengthSpinner);
+ strengthSpinner.onSpinCtrl = function(evt)
+ {
+ brush.strength = evt.position / 10;
+ brush.send();
+ };
+}
diff --git a/source/tools/atlas/AtlasScript/ScriptInterface.cpp b/source/tools/atlas/AtlasScript/ScriptInterface.cpp
index 92220d1f85..978963a3f6 100644
--- a/source/tools/atlas/AtlasScript/ScriptInterface.cpp
+++ b/source/tools/atlas/AtlasScript/ScriptInterface.cpp
@@ -26,61 +26,130 @@
#include
#include
+#define FAIL(msg) do { JS_ReportError(cx, msg); return false; } while (false)
+
const int RUNTIME_SIZE = 1024*1024; // TODO: how much memory is needed?
const int STACK_CHUNK_SIZE = 8192;
////////////////////////////////////////////////////////////////
-template bool ScriptInterface::FromJSVal(JSContext* cx, jsval WXUNUSED(v), T& WXUNUSED(out))
+namespace
{
- JS_ReportError(cx, "Unrecognised argument type");
- // TODO: SetPendingException turns the error into a JS-catchable exception,
- // but the error report doesn't say anything useful like the line number,
- // so I'm just using ReportError instead for now (and failures are uncatchable
- // and will terminate the whole script)
- //JS_SetPendingException(cx, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, "Unrecognised argument type")));
- return false;
+ // Use templated structs instead of functions, so that we can use partial specialisation:
+
+ template struct FromJSVal
+ {
+ static bool Convert(JSContext* cx, jsval WXUNUSED(v), T& WXUNUSED(out))
+ {
+ JS_ReportError(cx, "Unrecognised argument type");
+ // TODO: SetPendingException turns the error into a JS-catchable exception,
+ // but the error report doesn't say anything useful like the line number,
+ // so I'm just using ReportError instead for now (and failures are uncatchable
+ // and will terminate the whole script)
+ //JS_SetPendingException(cx, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, "Unrecognised argument type")));
+ return false;
+ }
+ };
+
+ template<> struct FromJSVal
+ {
+ static bool Convert(JSContext* cx, jsval v, bool& out)
+ {
+ JSBool ret;
+ if (! JS_ValueToBoolean(cx, v, &ret)) return false;
+ out = (ret ? true : false);
+ return true;
+ }
+ };
+
+ template<> struct FromJSVal
+ {
+ static bool Convert(JSContext* cx, jsval v, float& out)
+ {
+ jsdouble ret;
+ if (! JS_ValueToNumber(cx, v, &ret)) return false;
+ out = ret;
+ return true;
+ }
+ };
+
+ template<> struct FromJSVal
+ {
+ static bool Convert(JSContext* cx, jsval v, int& out)
+ {
+ int32 ret;
+ if (! JS_ValueToInt32(cx, v, &ret)) return false;
+ out = ret;
+ return true;
+ }
+ };
+
+ template<> struct FromJSVal
+ {
+ static bool Convert(JSContext* cx, jsval v, std::wstring& out)
+ {
+ JSString* ret = JS_ValueToString(cx, v); // never returns NULL
+ jschar* ch = JS_GetStringChars(ret);
+ out = std::wstring(ch, ch+JS_GetStringLength(ret));
+ return true;
+ }
+ };
+
+ template<> struct FromJSVal
+ {
+ static bool Convert(JSContext* cx, jsval v, std::string& out)
+ {
+ JSString* ret = JS_ValueToString(cx, v); // never returns NULL
+ char* ch = JS_GetStringBytes(ret);
+ out = std::string(ch);
+ return true;
+ }
+ };
+
+ template<> struct FromJSVal
+ {
+ static bool Convert(JSContext* cx, jsval v, wxString& out)
+ {
+ JSString* ret = JS_ValueToString(cx, v); // never returns NULL
+ jschar* ch = JS_GetStringChars(ret);
+ out = wxString((const char*)ch, wxMBConvUTF16(), JS_GetStringLength(ret)*2);
+ return true;
+ }
+ };
+
+ template struct FromJSVal >
+ {
+ static bool Convert(JSContext* cx, jsval v, std::vector& out)
+ {
+ JSObject* obj;
+ if (! JS_ValueToObject(cx, v, &obj) || obj == NULL || !JS_IsArrayObject(cx, obj))
+ FAIL("Argument must be an array");
+ jsuint length;
+ if (! JS_GetArrayLength(cx, obj, &length))
+ FAIL("Failed to get array length");
+ for (jsint i = 0; i < length; ++i)
+ {
+ jsval el;
+ if (! JS_GetElement(cx, obj, i, &el))
+ FAIL("Failed to read array element");
+ T el2;
+ if (! FromJSVal::Convert(cx, el, el2))
+ return false;
+ out.push_back(el2);
+ }
+ return true;
+ }
+ };
}
-template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, bool& out)
+template bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, T& out)
{
- JSBool ret;
- if (! JS_ValueToBoolean(cx, v, &ret)) return false;
- out = (ret ? true : false);
- return true;
+ return ::FromJSVal::Convert(cx, v, out);
}
-template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, float& out)
-{
- jsdouble ret;
- if (! JS_ValueToNumber(cx, v, &ret)) return false;
- out = ret;
- return true;
-}
-
-template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, int& out)
-{
- int32 ret;
- if (! JS_ValueToInt32(cx, v, &ret)) return false;
- out = ret;
- return true;
-}
-
-template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, std::wstring& out)
-{
- JSString* ret = JS_ValueToString(cx, v); // never returns NULL
- jschar* ch = JS_GetStringChars(ret);
- out = std::wstring(ch, ch+JS_GetStringLength(ret));
- return true;
-}
-
-template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, std::string& out)
-{
- JSString* ret = JS_ValueToString(cx, v); // never returns NULL
- char* ch = JS_GetStringBytes(ret);
- out = std::string(ch);
- return true;
-}
+// Explicit instantiation of functions that would otherwise be unused in this file
+// but are required for linking with other files
+template bool ScriptInterface::FromJSVal(JSContext*, jsval, wxString&);
template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const float& val)
@@ -148,6 +217,14 @@ namespace
wxPrintf(_T("wxJS %s: %s\n--------\n"), isWarning ? _T("warning") : _T("error"), logMessage.c_str());
}
+ // Functions in the Atlas.* namespace:
+
+ JSBool ForceGC(JSContext* cx, JSObject* WXUNUSED(obj), uintN WXUNUSED(argc), jsval* WXUNUSED(argv), jsval* WXUNUSED(rval))
+ {
+ JS_GC(cx);
+ return JS_TRUE;
+ }
+
JSBool LoadScript(JSContext* cx, JSObject* WXUNUSED(obj), uintN WXUNUSED(argc), jsval* argv, jsval* rval)
{
if (! ( JSVAL_IS_STRING(argv[0]) && JSVAL_IS_STRING(argv[1]) ))
@@ -160,12 +237,8 @@ namespace
JS_GetStringChars(code), (uintN)JS_GetStringLength(code),
JS_GetStringBytes(name), rval);
}
-
- JSBool ForceGC(JSContext* cx, JSObject* WXUNUSED(obj), uintN WXUNUSED(argc), jsval* WXUNUSED(argv), jsval* WXUNUSED(rval))
- {
- JS_GC(cx);
- return JS_TRUE;
- }
+
+ // Functions in the global namespace:
JSBool print(JSContext* cx, JSObject* WXUNUSED(obj), uintN argc, jsval* argv, jsval* WXUNUSED(rval))
{
@@ -212,8 +285,8 @@ ScriptInterface_impl::ScriptInterface_impl()
JS_DefineFunction(m_cx, m_glob, "print", ::print, 0, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
m_atlas = JS_DefineObject(m_cx, m_glob, "Atlas", NULL, NULL, JSPROP_READONLY|JSPROP_PERMANENT);
- JS_DefineFunction(m_cx, m_atlas, "LoadScript", ::LoadScript, 2, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_atlas, "ForceGC", ::ForceGC, 0, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
+ JS_DefineFunction(m_cx, m_atlas, "LoadScript", ::LoadScript, 2, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
RegisterMessages(m_atlas);
}
diff --git a/source/tools/atlas/AtlasScript/ScriptInterface.h b/source/tools/atlas/AtlasScript/ScriptInterface.h
index bd0d4b3d84..817a9d2017 100644
--- a/source/tools/atlas/AtlasScript/ScriptInterface.h
+++ b/source/tools/atlas/AtlasScript/ScriptInterface.h
@@ -29,7 +29,7 @@ public:
// Defined elsewhere:
// template
- // void RegisterFunction(const wxString& functionName);
+ // void RegisterFunction(const char* functionName);
// (NOTE: The return type must be defined as a ToJSVal
specialisation
// in ScriptInterface.cpp, else you'll end up with linker errors.)
diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp
index a2a8be3140..28618b9841 100644
--- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp
+++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp
@@ -23,6 +23,7 @@
#include "AtlasScript/ScriptInterface.h"
#include "Tools/Common/Tools.h"
+#include "Tools/Common/Brushes.h"
static HighResTimer g_Timer;
@@ -262,6 +263,21 @@ END_EVENT_TABLE()
static AtlasWindowCommandProc g_CommandProc;
AtlasWindowCommandProc& ScenarioEditor::GetCommandProc() { return g_CommandProc; }
+namespace
+{
+ // Wrapper function because SetCurrentTool takes an optional argument, which JS doesn't like
+ void SetCurrentTool_script(wxString name)
+ {
+ SetCurrentTool(name);
+ }
+
+ // TODO: see comment in terrain.js, and remove this when/if it's no longer necessary
+ void SetBrushStrength(float strength)
+ {
+ g_Brush_Elevation.SetStrength(strength);
+ }
+}
+
ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterface)
: wxFrame(parent, wxID_ANY, _T(""), wxDefaultPosition, wxSize(1024, 768))
, m_FileHistory(_T("Scenario Editor")), m_ScriptInterface(scriptInterface)
@@ -281,6 +297,8 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterfac
//////////////////////////////////////////////////////////////////////////
// Script interface functions
GetScriptInterface().RegisterFunction("GetDataDirectory");
+ GetScriptInterface().RegisterFunction("SetCurrentTool");
+ GetScriptInterface().RegisterFunction("SetBrushStrength");
{
const wxString relativePath (_T("tools/atlas/scripts/main.js"));
diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp
index afcd5c0730..0da4673045 100644
--- a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp
+++ b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp
@@ -287,7 +287,7 @@ void SectionLayout::Build(ScenarioEditor& scenarioEditor)
m_PageMappings.insert(std::make_pair(name, (int)m_SidebarBook->GetPageCount()-1));
ADD_SIDEBAR_SCRIPT(_T("map"), _T("map.png"), _("Map"));
- //ADD_SIDEBAR_SCRIPT(_T("terrain"), _T("terrain.png"), _("Terrain"));
+ ADD_SIDEBAR_SCRIPT(_T("terrain"), _T("terrain.png"), _("Terrain"));
ADD_SIDEBAR(TerrainSidebar, _T("terrain.png"), _("Terrain"));
ADD_SIDEBAR(ObjectSidebar, _T("object.png"), _("Object"));
ADD_SIDEBAR(EnvironmentSidebar, _T("environment.png"), _("Environment"));
diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp
index a10a3db5bf..31e063f632 100644
--- a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp
+++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp
@@ -92,6 +92,11 @@ float Brush::GetStrength() const
return m_Strength;
}
+void Brush::SetStrength(float strength)
+{
+ m_Strength = strength;
+}
+
//////////////////////////////////////////////////////////////////////////
class BrushShapeCtrl : public wxRadioBox
diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.h
index fca96af576..19a1e784c2 100644
--- a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.h
+++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.h
@@ -21,6 +21,7 @@ public:
std::vector GetData() const;
float GetStrength() const;
+ void SetStrength(float strength);
void CreateUI(wxWindow* parent, wxSizer* sizer);
diff --git a/source/tools/atlas/GameInterface/Brushes.cpp b/source/tools/atlas/GameInterface/Brushes.cpp
index a4faf2a495..6ee94c517d 100644
--- a/source/tools/atlas/GameInterface/Brushes.cpp
+++ b/source/tools/atlas/GameInterface/Brushes.cpp
@@ -40,9 +40,10 @@ public:
float avg = (
m_Brush->Get(i-i0, j-j0) + m_Brush->Get(i-i0+1, j-j0) +
m_Brush->Get(i-i0, j-j0+1) + m_Brush->Get(i-i0+1, j-j0+1)
- ) / 4.f;
+ ) / 4.f;
RenderTile(CColor(0, 1, 0, avg*0.8f), false);
- RenderTileOutline(CColor(1, 1, 1, 0.4f), 1, true);
+ if (avg > 0.1f)
+ RenderTileOutline(CColor(1, 1, 1, std::min(0.4f, avg-0.1f)), 1, true);
}
const AtlasMessage::Brush* m_Brush;
@@ -64,6 +65,8 @@ void Brush::SetData(int w, int h, const std::vector& data)
m_H = h;
m_Data = data;
+
+ debug_assert(data.size() == w*h);
}
void Brush::GetCentre(int& x, int& y) const