VOXELGENERATOR: support custom arguments for scripts
parent
ea8937337c
commit
a2aa83ad2a
|
@ -0,0 +1,75 @@
|
||||||
|
# Scripting api
|
||||||
|
|
||||||
|
There is a console command in voxedit to execute lua scripts for generating voxels. This command expects the lua script filename and the additional arguments for the `main()` method.
|
||||||
|
|
||||||
|
There are two functions in each script. One is called `arguments` and one `main`. `arguments` returns a list of parameters for the `main` function. The default parameters for `main` are `volume`, `region` and `color`.
|
||||||
|
|
||||||
|
# Example without parameters
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function main(volume, region, color)
|
||||||
|
local mins = region:mins()
|
||||||
|
local maxs = region:maxs()
|
||||||
|
for x = mins.x, maxs.x do
|
||||||
|
for y = mins.y, maxs.y do
|
||||||
|
for z = mins.z, maxs.z do
|
||||||
|
volume:setVoxel(x, y, z, color)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Execute this via console `xs scriptfile`
|
||||||
|
|
||||||
|
# Example with one parameter
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function arguments()
|
||||||
|
return {
|
||||||
|
{ name = 'n', desc = 'height level delta', type = 'int' }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function main(volume, region, color, n)
|
||||||
|
[...]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Execute this via console `xs scriptfile 1` where `1` will be the value of `n`.
|
||||||
|
|
||||||
|
# Color
|
||||||
|
|
||||||
|
`palette` has several methods to work with colors. E.g. to find a closest possible match for the given palette index.
|
||||||
|
|
||||||
|
The functions are:
|
||||||
|
|
||||||
|
* `color(paletteIndex)`: pushes the vec4 of the color behind the palette index (`0-255`) as float values between `0.0` and `1.0`.
|
||||||
|
|
||||||
|
* `match(r, g, b)`: returns the closest possible palette color match for the given RGB (`0-255`) color (). The returned palette index is in the range `0-255`. This value can then be used for the `setVoxel` method.
|
||||||
|
|
||||||
|
# Region
|
||||||
|
|
||||||
|
* `mins()`:
|
||||||
|
|
||||||
|
* `maxs()`:
|
||||||
|
|
||||||
|
* `x()`:
|
||||||
|
|
||||||
|
* `y()`:
|
||||||
|
|
||||||
|
* `z()`:
|
||||||
|
|
||||||
|
* `width()`:
|
||||||
|
|
||||||
|
* `height()`:
|
||||||
|
|
||||||
|
* `depth()`:
|
||||||
|
|
||||||
|
# Volume
|
||||||
|
|
||||||
|
* `voxel(x, y, z)`: returns the palette index of the voxel at the given position in the volume
|
||||||
|
|
||||||
|
* `region()`: return the region of the volume
|
||||||
|
|
||||||
|
* `setVoxel(x, y, z, color)`: set the given color to the given coordinates in the volume
|
|
@ -20,6 +20,9 @@ nav:
|
||||||
- Configuration.md
|
- Configuration.md
|
||||||
- Formats.md
|
- Formats.md
|
||||||
- CHANGELOG.md
|
- CHANGELOG.md
|
||||||
|
- VoxEdit:
|
||||||
|
- voxedit/Index.md
|
||||||
|
- voxedit/LUAScript.md
|
||||||
- Client/Server:
|
- Client/Server:
|
||||||
- server/Index.md
|
- server/Index.md
|
||||||
- server/Setup.md
|
- server/Setup.md
|
||||||
|
@ -27,5 +30,4 @@ nav:
|
||||||
- VoxConvert: voxconvert/Index.md
|
- VoxConvert: voxconvert/Index.md
|
||||||
- Thumbnailer: thumbnailer/Index.md
|
- Thumbnailer: thumbnailer/Index.md
|
||||||
- MapView: mapview/Index.md
|
- MapView: mapview/Index.md
|
||||||
- VoxEdit: voxedit/Index.md
|
|
||||||
- Game Design: GameDesign.md
|
- Game Design: GameDesign.md
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "LUAGenerator.h"
|
#include "LUAGenerator.h"
|
||||||
#include "commonlua/LUAFunctions.h"
|
#include "commonlua/LUAFunctions.h"
|
||||||
|
#include "core/StringUtil.h"
|
||||||
#include "lauxlib.h"
|
#include "lauxlib.h"
|
||||||
#include "lua.h"
|
#include "lua.h"
|
||||||
#include "voxel/MaterialColor.h"
|
#include "voxel/MaterialColor.h"
|
||||||
|
@ -203,7 +204,144 @@ bool LUAGenerator::init() {
|
||||||
void LUAGenerator::shutdown() {
|
void LUAGenerator::shutdown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LUAGenerator::exec(const core::String& luaScript, voxel::RawVolumeWrapper* volume, const voxel::Region& region, const voxel::Voxel& voxel) {
|
bool LUAGenerator::argumentInfo(const core::String& luaScript, core::DynamicArray<LUAParameterDescription>& params) {
|
||||||
|
lua::LUA lua;
|
||||||
|
|
||||||
|
// load and run once to initialize the global variables
|
||||||
|
if (luaL_dostring(lua, luaScript.c_str())) {
|
||||||
|
Log::error("%s", lua_tostring(lua, -1));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int preTop = lua_gettop(lua);
|
||||||
|
|
||||||
|
// get help method
|
||||||
|
lua_getglobal(lua, "arguments");
|
||||||
|
if (!lua_isfunction(lua, -1)) {
|
||||||
|
// this is no error - just no parameters are needed...
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int error = lua_pcall(lua, 0, LUA_MULTRET, 0);
|
||||||
|
if (error != LUA_OK) {
|
||||||
|
Log::error("LUA generate arguments script: %s", lua_isstring(lua, -1) ? lua_tostring(lua, -1) : "Unknown Error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int top = lua_gettop(lua);
|
||||||
|
if (top <= preTop) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lua_istable(lua, -1)) {
|
||||||
|
Log::error("Expected to get a table return value");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int args = lua_rawlen(lua, -1);
|
||||||
|
|
||||||
|
for (int i = 0; i < args; ++i) {
|
||||||
|
lua_pushinteger(lua, i + 1); // lua starts at 1
|
||||||
|
lua_gettable(lua, -2);
|
||||||
|
if (!lua_istable(lua, -1)) {
|
||||||
|
Log::error("Expected to return tables of { name = 'name', desc = 'description', type = 'int' } at %i", i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
core::String name = "";
|
||||||
|
core::String description = "";
|
||||||
|
LUAParameterType type = LUAParameterType::Max;
|
||||||
|
lua_pushnil(lua); // push nil, so lua_next removes it from stack and puts (k, v) on stack
|
||||||
|
while (lua_next(lua, -2) != 0) { // -2, because we have table at -1
|
||||||
|
if (!lua_isstring(lua, -1) || !lua_isstring(lua, -2)) {
|
||||||
|
Log::error("Expected to find string as parameter key and value");
|
||||||
|
// only store stuff with string key and value
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const char *key = lua_tostring(lua, -2);
|
||||||
|
const char *value = lua_tostring(lua, -1);
|
||||||
|
if (!SDL_strcmp(key, "name")) {
|
||||||
|
name = value;
|
||||||
|
} else if (!SDL_strncmp(key, "desc", 4)) {
|
||||||
|
description = value;
|
||||||
|
} else if (!SDL_strcmp(key, "type")) {
|
||||||
|
if (!SDL_strcmp(value, "int")) {
|
||||||
|
type = LUAParameterType::Integer;
|
||||||
|
} else if (!SDL_strcmp(value, "float")) {
|
||||||
|
type = LUAParameterType::Float;
|
||||||
|
} else if (!SDL_strncmp(value, "str", 3)) {
|
||||||
|
type = LUAParameterType::String;
|
||||||
|
} else if (!SDL_strncmp(value, "bool", 4)) {
|
||||||
|
type = LUAParameterType::Boolean;
|
||||||
|
} else {
|
||||||
|
Log::error("Invalid type found: %s", value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log::warn("Invalid key found: %s", key);
|
||||||
|
}
|
||||||
|
lua_pop(lua, 1); // remove value, keep key for lua_next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.empty()) {
|
||||||
|
Log::error("No name = 'myname' key given");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == LUAParameterType::Max) {
|
||||||
|
Log::error("No type = 'int', 'float', 'string', 'bool' key given for '%s'", name.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
params.emplace_back(name, description, type);
|
||||||
|
lua_pop(lua, 1); // remove table
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool luaVoxel_pushargs(lua_State* s, const core::DynamicArray<core::String>& args, const core::DynamicArray<LUAParameterDescription>& argsInfo) {
|
||||||
|
core_assert(args.size() == argsInfo.size());
|
||||||
|
for (size_t i = 0u; i < argsInfo.size(); ++i) {
|
||||||
|
const LUAParameterDescription &d = argsInfo[i];
|
||||||
|
const core::String &arg = args[i];
|
||||||
|
switch (d.type) {
|
||||||
|
case LUAParameterType::String:
|
||||||
|
lua_pushstring(s, arg.c_str());
|
||||||
|
break;
|
||||||
|
case LUAParameterType::Boolean: {
|
||||||
|
const bool val = arg == "1" || arg == "true";
|
||||||
|
lua_pushboolean(s, val ? 1 : 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LUAParameterType::Integer:
|
||||||
|
lua_pushinteger(s, core::string::toInt(arg));
|
||||||
|
break;
|
||||||
|
case LUAParameterType::Float:
|
||||||
|
lua_pushnumber(s, core::string::toFloat(arg));
|
||||||
|
break;
|
||||||
|
case LUAParameterType::Max:
|
||||||
|
Log::error("Invalid argument type");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LUAGenerator::exec(const core::String& luaScript, voxel::RawVolumeWrapper* volume, const voxel::Region& region, const voxel::Voxel& voxel, const core::DynamicArray<core::String>& args) {
|
||||||
|
core::DynamicArray<LUAParameterDescription> argsInfo;
|
||||||
|
if (!argumentInfo(luaScript, argsInfo)) {
|
||||||
|
Log::error("Failed to get argument details");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.size() != argsInfo.size()) {
|
||||||
|
Log::error("Invalid arguments given. Got %i, expected %i", (int)args.size(), (int)argsInfo.size());
|
||||||
|
for (const auto& e : argsInfo) {
|
||||||
|
Log::info(" %s => %s", e.name.c_str(), e.description.c_str());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
lua::LUA lua;
|
lua::LUA lua;
|
||||||
prepareState(lua);
|
prepareState(lua);
|
||||||
|
|
||||||
|
@ -216,7 +354,6 @@ bool LUAGenerator::exec(const core::String& luaScript, voxel::RawVolumeWrapper*
|
||||||
// get main(volume, region) method
|
// get main(volume, region) method
|
||||||
lua_getglobal(lua, "main");
|
lua_getglobal(lua, "main");
|
||||||
if (!lua_isfunction(lua, -1)) {
|
if (!lua_isfunction(lua, -1)) {
|
||||||
Log::error("%s", lua.stackDump().c_str());
|
|
||||||
Log::error("LUA generator: no main(volume, region) function found in '%s'", luaScript.c_str());
|
Log::error("LUA generator: no main(volume, region) function found in '%s'", luaScript.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -254,8 +391,14 @@ bool LUAGenerator::exec(const core::String& luaScript, voxel::RawVolumeWrapper*
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
const int error = lua_pcall(lua, 3, 0, 0);
|
|
||||||
if (error) {
|
if (!luaVoxel_pushargs(lua, args, argsInfo)) {
|
||||||
|
Log::error("Failed to push arguments");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int error = lua_pcall(lua, 3 + argsInfo.size(), 0, 0);
|
||||||
|
if (error != LUA_OK) {
|
||||||
Log::error("LUA generate script: %s", lua_isstring(lua, -1) ? lua_tostring(lua, -1) : "Unknown Error");
|
Log::error("LUA generate script: %s", lua_isstring(lua, -1) ? lua_tostring(lua, -1) : "Unknown Error");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "core/IComponent.h"
|
#include "core/IComponent.h"
|
||||||
#include "core/String.h"
|
#include "core/String.h"
|
||||||
|
#include "core/collection/DynamicArray.h"
|
||||||
|
|
||||||
namespace voxel {
|
namespace voxel {
|
||||||
class Region;
|
class Region;
|
||||||
|
@ -15,12 +16,34 @@ class Voxel;
|
||||||
|
|
||||||
namespace voxelgenerator {
|
namespace voxelgenerator {
|
||||||
|
|
||||||
|
enum class LUAParameterType {
|
||||||
|
String,
|
||||||
|
Integer,
|
||||||
|
Float,
|
||||||
|
Boolean,
|
||||||
|
|
||||||
|
Max
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LUAParameterDescription {
|
||||||
|
core::String name;
|
||||||
|
core::String description;
|
||||||
|
LUAParameterType type;
|
||||||
|
|
||||||
|
LUAParameterDescription(const core::String &_name, const core::String &_description, LUAParameterType _type)
|
||||||
|
: name(_name), description(_description), type(_type) {
|
||||||
|
}
|
||||||
|
LUAParameterDescription() : type(LUAParameterType::Max) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class LUAGenerator : public core::IComponent {
|
class LUAGenerator : public core::IComponent {
|
||||||
public:
|
public:
|
||||||
bool init() override;
|
bool init() override;
|
||||||
void shutdown() override;
|
void shutdown() override;
|
||||||
|
|
||||||
bool exec(const core::String& luaScript, voxel::RawVolumeWrapper* volume, const voxel::Region& region, const voxel::Voxel& voxel);
|
bool argumentInfo(const core::String& luaScript, core::DynamicArray<LUAParameterDescription>& params);
|
||||||
|
bool exec(const core::String& luaScript, voxel::RawVolumeWrapper* volume, const voxel::Region& region, const voxel::Voxel& voxel, const core::DynamicArray<core::String>& args = core::DynamicArray<core::String>());
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* @file
|
* @file
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "core/collection/DynamicArray.h"
|
||||||
#include "core/tests/AbstractTest.h"
|
#include "core/tests/AbstractTest.h"
|
||||||
#include "voxel/MaterialColor.h"
|
#include "voxel/MaterialColor.h"
|
||||||
#include "voxel/RawVolume.h"
|
#include "voxel/RawVolume.h"
|
||||||
|
@ -61,4 +62,50 @@ TEST_F(LUAGeneratorTest, testExecute) {
|
||||||
g.shutdown();
|
g.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(LUAGeneratorTest, testArguments) {
|
||||||
|
const core::String script = R"(
|
||||||
|
--[[
|
||||||
|
@return A parameter description
|
||||||
|
--]]
|
||||||
|
function arguments()
|
||||||
|
return {
|
||||||
|
{ name = 'name', desc = 'desc', type = 'int' },
|
||||||
|
{ name = 'name2', desc = 'desc2', type = 'float' }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function main(volume, region, color, name, name2)
|
||||||
|
if (name == 'param1') then
|
||||||
|
error('Expected to get the value param1')
|
||||||
|
end
|
||||||
|
if (name2 == 'param2') then
|
||||||
|
error('Expected to get the value param2')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)";
|
||||||
|
|
||||||
|
ASSERT_TRUE(voxel::initDefaultMaterialColors());
|
||||||
|
|
||||||
|
voxel::Region region(0, 0, 0, 7, 7, 7);
|
||||||
|
voxel::RawVolume volume(region);
|
||||||
|
voxel::RawVolumeWrapper wrapper(&volume);
|
||||||
|
|
||||||
|
LUAGenerator g;
|
||||||
|
ASSERT_TRUE(g.init());
|
||||||
|
core::DynamicArray<LUAParameterDescription> params;
|
||||||
|
EXPECT_TRUE(g.argumentInfo(script, params));
|
||||||
|
ASSERT_EQ(2u, params.size());
|
||||||
|
EXPECT_STREQ("name", params[0].name.c_str());
|
||||||
|
EXPECT_STREQ("desc", params[0].description.c_str());
|
||||||
|
EXPECT_EQ(LUAParameterType::Integer, params[0].type);
|
||||||
|
EXPECT_STREQ("name2", params[1].name.c_str());
|
||||||
|
EXPECT_STREQ("desc2", params[1].description.c_str());
|
||||||
|
EXPECT_EQ(LUAParameterType::Float, params[1].type);
|
||||||
|
core::DynamicArray<core::String> args;
|
||||||
|
args.push_back("param1");
|
||||||
|
args.push_back("param2");
|
||||||
|
EXPECT_TRUE(g.exec(script, &wrapper, region, voxel::createVoxel(voxel::VoxelType::Generic, 42), args));
|
||||||
|
g.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
function arguments()
|
||||||
|
return {
|
||||||
|
{ name = 'n', desc = 'height level delta', type = 'int' }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function main(volume, region, color, n)
|
||||||
|
local mins = region:mins()
|
||||||
|
local maxs = region:maxs()
|
||||||
|
local subtract = 0
|
||||||
|
for y = mins.y, maxs.y do
|
||||||
|
for x = mins.x + subtract, maxs.x - subtract do
|
||||||
|
for z = mins.z + subtract, maxs.z - subtract do
|
||||||
|
volume:setVoxel(x, y, z, color)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if y % n == 0 then
|
||||||
|
subtract = subtract + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "SceneManager.h"
|
#include "SceneManager.h"
|
||||||
|
|
||||||
|
#include "core/collection/DynamicArray.h"
|
||||||
#include "voxel/RawVolume.h"
|
#include "voxel/RawVolume.h"
|
||||||
#include "voxelutil/VolumeMerger.h"
|
#include "voxelutil/VolumeMerger.h"
|
||||||
#include "voxelutil/VolumeCropper.h"
|
#include "voxelutil/VolumeCropper.h"
|
||||||
|
@ -881,11 +882,17 @@ void SceneManager::construct() {
|
||||||
Log::error("Failed to load %s", args[0].c_str());
|
Log::error("Failed to load %s", args[0].c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core::DynamicArray<core::String> luaArgs;
|
||||||
|
for (size_t i = 1; i < args.size(); ++i) {
|
||||||
|
luaArgs.push_back(args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
const int layerId = _layerMgr.activeLayer();
|
const int layerId = _layerMgr.activeLayer();
|
||||||
voxel::RawVolumeWrapper wrapper(volume(layerId));
|
voxel::RawVolumeWrapper wrapper(volume(layerId));
|
||||||
// TODO: limit region to selection
|
// TODO: limit region to selection
|
||||||
const voxel::Region& region = wrapper.region();
|
const voxel::Region& region = wrapper.region();
|
||||||
if (!_luaGenerator.exec(luaScript, &wrapper, region, _modifier.cursorVoxel())) {
|
if (!_luaGenerator.exec(luaScript, &wrapper, region, _modifier.cursorVoxel(), luaArgs)) {
|
||||||
Log::error("Failed to execute %s", args[0].c_str());
|
Log::error("Failed to execute %s", args[0].c_str());
|
||||||
} else {
|
} else {
|
||||||
Log::info("Executed script %s", args[0].c_str());
|
Log::info("Executed script %s", args[0].c_str());
|
||||||
|
|
Loading…
Reference in New Issue