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
|
||||
- Formats.md
|
||||
- CHANGELOG.md
|
||||
- VoxEdit:
|
||||
- voxedit/Index.md
|
||||
- voxedit/LUAScript.md
|
||||
- Client/Server:
|
||||
- server/Index.md
|
||||
- server/Setup.md
|
||||
|
@ -27,5 +30,4 @@ nav:
|
|||
- VoxConvert: voxconvert/Index.md
|
||||
- Thumbnailer: thumbnailer/Index.md
|
||||
- MapView: mapview/Index.md
|
||||
- VoxEdit: voxedit/Index.md
|
||||
- Game Design: GameDesign.md
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "LUAGenerator.h"
|
||||
#include "commonlua/LUAFunctions.h"
|
||||
#include "core/StringUtil.h"
|
||||
#include "lauxlib.h"
|
||||
#include "lua.h"
|
||||
#include "voxel/MaterialColor.h"
|
||||
|
@ -203,7 +204,144 @@ bool LUAGenerator::init() {
|
|||
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;
|
||||
prepareState(lua);
|
||||
|
||||
|
@ -216,7 +354,6 @@ bool LUAGenerator::exec(const core::String& luaScript, voxel::RawVolumeWrapper*
|
|||
// get main(volume, region) method
|
||||
lua_getglobal(lua, "main");
|
||||
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());
|
||||
return false;
|
||||
}
|
||||
|
@ -254,8 +391,14 @@ bool LUAGenerator::exec(const core::String& luaScript, voxel::RawVolumeWrapper*
|
|||
return false;
|
||||
}
|
||||
#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");
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "core/IComponent.h"
|
||||
#include "core/String.h"
|
||||
#include "core/collection/DynamicArray.h"
|
||||
|
||||
namespace voxel {
|
||||
class Region;
|
||||
|
@ -15,12 +16,34 @@ class Voxel;
|
|||
|
||||
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 {
|
||||
public:
|
||||
bool init() 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
|
||||
*/
|
||||
|
||||
#include "core/collection/DynamicArray.h"
|
||||
#include "core/tests/AbstractTest.h"
|
||||
#include "voxel/MaterialColor.h"
|
||||
#include "voxel/RawVolume.h"
|
||||
|
@ -61,4 +62,50 @@ TEST_F(LUAGeneratorTest, testExecute) {
|
|||
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 "core/collection/DynamicArray.h"
|
||||
#include "voxel/RawVolume.h"
|
||||
#include "voxelutil/VolumeMerger.h"
|
||||
#include "voxelutil/VolumeCropper.h"
|
||||
|
@ -881,11 +882,17 @@ void SceneManager::construct() {
|
|||
Log::error("Failed to load %s", args[0].c_str());
|
||||
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();
|
||||
voxel::RawVolumeWrapper wrapper(volume(layerId));
|
||||
// TODO: limit region to selection
|
||||
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());
|
||||
} else {
|
||||
Log::info("Executed script %s", args[0].c_str());
|
||||
|
|
Loading…
Reference in New Issue