From 52aa29681cd2bd6af90b234af66c096f67d85541 Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Tue, 12 Jun 2007 22:01:25 +0000 Subject: [PATCH] Atlas: Partial porting of the Terrain section to JS. Slightly nicer rendering of circular brush previews. This was SVN commit r5168. --- binaries/data/tools/atlas/lists.xml | 46 +++-- .../tools/atlas/scripts/section/terrain.js | 142 ++++++++++++++ .../atlas/AtlasScript/ScriptInterface.cpp | 175 +++++++++++++----- .../tools/atlas/AtlasScript/ScriptInterface.h | 2 +- .../AtlasUI/ScenarioEditor/ScenarioEditor.cpp | 18 ++ .../AtlasUI/ScenarioEditor/SectionLayout.cpp | 2 +- .../ScenarioEditor/Tools/Common/Brushes.cpp | 5 + .../ScenarioEditor/Tools/Common/Brushes.h | 1 + source/tools/atlas/GameInterface/Brushes.cpp | 7 +- 9 files changed, 319 insertions(+), 79 deletions(-) create mode 100644 binaries/data/tools/atlas/scripts/section/terrain.js 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