blob: bc0174d30ad323edf888b920527f52a251f001ad [file] [log] [blame]
#ifdef WITH_RIVE_SCRIPTING
#include "rive/math/vec2d.hpp"
#include "rive/lua/rive_lua_libs.hpp"
#include "rive/math/raw_path.hpp"
#include "rive/math/contour_measure.hpp"
#include "rive/math/path_measure.hpp"
#include "rive/factory.hpp"
#include <math.h>
#include <stdio.h>
using namespace rive;
RenderPath* ScriptedPath::renderPath(lua_State* L)
{
if (m_isRenderPathDirty)
{
m_isRenderPathDirty = false;
if (!m_renderPath)
{
ScriptingContext* context =
static_cast<ScriptingContext*>(lua_getthreaddata(L));
m_renderPath = context->factory()->makeEmptyRenderPath();
m_renderPath->fillRule(FillRule::clockwise);
}
else
{
m_renderPath->rewind();
}
m_renderPath->addRawPath(rawPath);
}
return m_renderPath.get();
}
ScriptedPathData::ScriptedPathData(const RawPath* path)
{
rawPath.addPath(*path);
}
int ScriptedPathData::totalCommands() { return (int)rawPath.verbs().size(); }
static ScriptedPath* lua_pushpath(lua_State* L)
{
return lua_newrive<ScriptedPath>(L);
}
static int path_new(lua_State* L)
{
lua_pushpath(L);
return 1;
}
static int path_moveTo(lua_State* L)
{
auto scriptedPath = lua_torive<ScriptedPath>(L, 1);
auto vec = lua_checkvec2d(L, 2);
scriptedPath->rawPath.move(*vec);
scriptedPath->markDirty();
return 0;
}
static int path_lineTo(lua_State* L)
{
auto scriptedPath = lua_torive<ScriptedPath>(L, 1);
auto vec = lua_checkvec2d(L, 2);
scriptedPath->rawPath.line(*vec);
scriptedPath->markDirty();
return 0;
}
static int path_quadTo(lua_State* L)
{
auto scriptedPath = lua_torive<ScriptedPath>(L, 1);
auto p1 = lua_checkvec2d(L, 2);
auto p2 = lua_checkvec2d(L, 3);
scriptedPath->rawPath.quad(*p1, *p2);
scriptedPath->markDirty();
return 0;
}
static int path_cubicTo(lua_State* L)
{
auto scriptedPath = lua_torive<ScriptedPath>(L, 1);
auto p1 = lua_checkvec2d(L, 2);
auto p2 = lua_checkvec2d(L, 3);
auto p3 = lua_checkvec2d(L, 4);
scriptedPath->rawPath.cubic(*p1, *p2, *p3);
scriptedPath->markDirty();
return 0;
}
static int path_close(lua_State* L)
{
auto scriptedPath = lua_torive<ScriptedPath>(L, 1);
scriptedPath->rawPath.close();
scriptedPath->markDirty();
return 0;
}
static int path_reset(lua_State* L)
{
auto scriptedPath = lua_torive<ScriptedPath>(L, 1);
scriptedPath->rawPath.reset();
scriptedPath->markDirty();
return 0;
}
static int path_add(lua_State* L)
{
auto scriptedPath = (ScriptedPathData*)lua_touserdata(L, 1);
auto scriptedPathToAdd = (ScriptedPathData*)lua_touserdata(L, 2);
const Mat2D* transform = nullptr;
int nargs = lua_gettop(L);
if (nargs == 3)
{
auto matrix = lua_torive<ScriptedMat2D>(L, 3);
transform = &matrix->value;
}
scriptedPath->rawPath.addPath(scriptedPathToAdd->rawPath, transform);
scriptedPath->markDirty();
return 0;
}
static int path_command(lua_State* L)
{
auto scriptedPath = (ScriptedPathData*)lua_touserdata(L, 1);
auto rawPath = scriptedPath->rawPath;
auto verbs = rawPath.verbs();
auto points = rawPath.points();
auto verbIndex = int(luaL_checknumber(L, 2));
auto verbName = "none";
std::vector<Vec2D> verbPoints;
if (verbIndex >= 0 && verbIndex < (int)verbs.size())
{
auto verb = verbs[verbIndex];
// Calculate the point index for this verb by summing point counts of
// previous verbs
int pointIndex = 0;
for (int i = 0; i < verbIndex; i++)
{
pointIndex += path_verb_to_point_count(verbs[i]);
}
switch (verb)
{
case PathVerb::move:
verbName = "moveTo";
if (pointIndex < (int)points.size())
{
verbPoints.push_back(points[pointIndex]);
}
break;
case PathVerb::line:
verbName = "lineTo";
if (pointIndex < (int)points.size())
{
verbPoints.push_back(points[pointIndex]);
}
break;
case PathVerb::quad:
verbName = "quadTo";
if (pointIndex + 1 < (int)points.size())
{
verbPoints.push_back(points[pointIndex]);
verbPoints.push_back(points[pointIndex + 1]);
}
break;
case PathVerb::cubic:
verbName = "cubicTo";
if (pointIndex + 2 < (int)points.size())
{
verbPoints.push_back(points[pointIndex]);
verbPoints.push_back(points[pointIndex + 1]);
verbPoints.push_back(points[pointIndex + 2]);
}
break;
case PathVerb::close:
verbName = "close";
// close has no points
break;
}
}
lua_newrive<ScriptedPathCommand>(L, verbName, verbPoints);
return 1;
}
static int path_contours(lua_State* L)
{
auto scriptedPath = (ScriptedPathData*)lua_touserdata(L, 1);
// Use the copy constructor to ensure ContourMeasure outlives the path
auto iter = make_rcp<RefCntContourMeasureIter>(scriptedPath->rawPath);
auto firstContour = iter->get()->next();
if (firstContour)
{
lua_newrive<ScriptedContourMeasure>(L, firstContour, iter);
return 1;
}
lua_pushnil(L);
return 1;
}
static int path_measure(lua_State* L)
{
auto scriptedPath = (ScriptedPathData*)lua_touserdata(L, 1);
PathMeasure pathMeasure(&scriptedPath->rawPath);
lua_newrive<ScriptedPathMeasure>(L, std::move(pathMeasure));
return 1;
}
static int path_namecall(lua_State* L)
{
int atom;
const char* str = lua_namecallatom(L, &atom);
if (str != nullptr)
{
switch (atom)
{
case (int)LuaAtoms::moveTo:
return path_moveTo(L);
case (int)LuaAtoms::lineTo:
return path_lineTo(L);
case (int)LuaAtoms::quadTo:
return path_quadTo(L);
case (int)LuaAtoms::cubicTo:
return path_cubicTo(L);
case (int)LuaAtoms::close:
return path_close(L);
case (int)LuaAtoms::reset:
return path_reset(L);
case (int)LuaAtoms::add:
return path_add(L);
case (int)LuaAtoms::contours:
return path_contours(L);
case (int)LuaAtoms::measure:
return path_measure(L);
}
}
luaL_error(L, "%s is not a valid method of %s", str, ScriptedPath::luaName);
return 0;
}
// ContourMeasure methods
static int contour_measure_length(lua_State* L)
{
auto scripted = lua_torive<ScriptedContourMeasure>(L, 1);
lua_pushnumber(L, scripted->measure()->length());
return 1;
}
static int contour_measure_isClosed(lua_State* L)
{
auto scripted = lua_torive<ScriptedContourMeasure>(L, 1);
lua_pushboolean(L, scripted->measure()->isClosed());
return 1;
}
static int contour_measure_positionAndTangent(lua_State* L)
{
auto scripted = lua_torive<ScriptedContourMeasure>(L, 1);
float distance = (float)luaL_checknumber(L, 2);
auto posTan = scripted->measure()->getPosTan(distance);
lua_pushvec2d(L, posTan.pos);
lua_pushvec2d(L, posTan.tan);
return 2;
}
static int contour_measure_warp(lua_State* L)
{
auto scripted = lua_torive<ScriptedContourMeasure>(L, 1);
auto src = lua_checkvec2d(L, 2);
Vec2D result = scripted->measure()->warp(*src);
lua_pushvec2d(L, result);
return 1;
}
static int contour_measure_extract(lua_State* L)
{
auto scripted = lua_torive<ScriptedContourMeasure>(L, 1);
float startDistance = (float)luaL_checknumber(L, 2);
float endDistance = (float)luaL_checknumber(L, 3);
auto destPath = lua_torive<ScriptedPath>(L, 4);
bool startWithMove = lua_isboolean(L, 5) ? lua_toboolean(L, 5) : true;
scripted->measure()->getSegment(startDistance,
endDistance,
&destPath->rawPath,
startWithMove);
destPath->markDirty();
return 0;
}
static int contour_measure_next(lua_State* L)
{
auto scripted = lua_torive<ScriptedContourMeasure>(L, 1);
auto iter = scripted->iter();
if (iter)
{
auto nextContour = iter->get()->next();
if (nextContour)
{
// Create new ScriptedContourMeasure with the same rcp iter
// The iter is already advanced, so we can reuse it
lua_newrive<ScriptedContourMeasure>(L, nextContour, iter);
return 1;
}
}
lua_pushnil(L);
return 1;
}
static int path_index(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
// If it's not a string/atom, treat it as a numeric index
if (!key)
{
int index = luaL_checkinteger(L, 2);
// Convert from 1-based Lua index to 0-based C++ index
// Replace the index at position 2 with the 0-based version
lua_pushinteger(L, index - 1); // Push 0-based index
lua_replace(L, 2); // Replace the value at position 2
// Now stack has: 1=path, 2=0-based index, which is what path_command
// expects
return path_command(L);
}
// String indices are handled by __namecall for methods
return 0;
}
static int path_length(lua_State* L)
{
auto scriptedPath = (ScriptedPathData*)lua_touserdata(L, 1);
auto totalCommands = scriptedPath->totalCommands();
lua_pushnumber(L, totalCommands);
return 1;
}
static int contour_measure_index(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
if (!key)
{
luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING));
return 0;
}
switch (atom)
{
case (int)LuaAtoms::length:
return contour_measure_length(L);
case (int)LuaAtoms::isClosed:
return contour_measure_isClosed(L);
case (int)LuaAtoms::next:
return contour_measure_next(L);
default:
return 0;
}
}
static int contour_measure_namecall(lua_State* L)
{
int atom;
const char* str = lua_namecallatom(L, &atom);
if (str != nullptr)
{
switch (atom)
{
case (int)LuaAtoms::positionAndTangent:
return contour_measure_positionAndTangent(L);
case (int)LuaAtoms::warp:
return contour_measure_warp(L);
case (int)LuaAtoms::extract:
return contour_measure_extract(L);
}
}
luaL_error(L,
"%s is not a valid method of %s",
str,
ScriptedContourMeasure::luaName);
return 0;
}
// PathMeasure methods
static int path_measure_length(lua_State* L)
{
auto scripted = lua_torive<ScriptedPathMeasure>(L, 1);
lua_pushnumber(L, scripted->measure()->length());
return 1;
}
static int path_measure_isClosed(lua_State* L)
{
auto scripted = lua_torive<ScriptedPathMeasure>(L, 1);
lua_pushboolean(L, scripted->measure()->isClosed());
return 1;
}
static int path_measure_positionAndTangent(lua_State* L)
{
auto scripted = lua_torive<ScriptedPathMeasure>(L, 1);
float distance = (float)luaL_checknumber(L, 2);
auto posTanDist = scripted->measure()->atDistance(distance);
lua_pushvec2d(L, posTanDist.pos);
lua_pushvec2d(L, posTanDist.tan);
return 2;
}
static int path_measure_warp(lua_State* L)
{
auto scripted = lua_torive<ScriptedPathMeasure>(L, 1);
auto src = lua_checkvec2d(L, 2);
// Use atDistance to get position and tangent, then apply warp formula
auto posTanDist = scripted->measure()->atDistance(src->x);
Vec2D result = {
posTanDist.pos.x - posTanDist.tan.y * src->y,
posTanDist.pos.y + posTanDist.tan.x * src->y,
};
lua_pushvec2d(L, result);
return 1;
}
static int path_measure_extract(lua_State* L)
{
auto scripted = lua_torive<ScriptedPathMeasure>(L, 1);
float startDistance = (float)luaL_checknumber(L, 2);
float endDistance = (float)luaL_checknumber(L, 3);
auto destPath = lua_torive<ScriptedPath>(L, 4);
bool startWithMove = lua_isboolean(L, 5) ? lua_toboolean(L, 5) : true;
scripted->measure()->getSegment(startDistance,
endDistance,
&destPath->rawPath,
startWithMove);
destPath->markDirty();
return 0;
}
static int path_measure_index(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
if (!key)
{
luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING));
return 0;
}
switch (atom)
{
case (int)LuaAtoms::length:
return path_measure_length(L);
case (int)LuaAtoms::isClosed:
return path_measure_isClosed(L);
default:
return 0;
}
}
static int path_measure_namecall(lua_State* L)
{
int atom;
const char* str = lua_namecallatom(L, &atom);
if (str != nullptr)
{
switch (atom)
{
case (int)LuaAtoms::positionAndTangent:
return path_measure_positionAndTangent(L);
case (int)LuaAtoms::warp:
return path_measure_warp(L);
case (int)LuaAtoms::extract:
return path_measure_extract(L);
}
}
luaL_error(L,
"%s is not a valid method of %s",
str,
ScriptedPathMeasure::luaName);
return 0;
}
static int property_namecall_atom(lua_State* L,
ScriptedPathCommand* pathCommand,
uint8_t tag,
int atom,
bool& error)
{
switch (atom)
{
case (int)LuaAtoms::type:
{
lua_pushstring(L, pathCommand->type().c_str());
return 1;
}
}
error = true;
return 0;
}
static int pathCommand_index(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
// If it's not a string/atom, treat it as a numeric index
if (!key)
{
int luaIndex = luaL_checkinteger(L, 2);
int index = luaIndex - 1;
auto pathCommand = (ScriptedPathCommand*)lua_touserdata(L, 1);
auto points = pathCommand->points();
auto size = (int)points.size();
if (index >= 0 && index < size)
{
auto point = points[index];
lua_pushvec2d(L, point);
return 1;
}
}
else
{
int atom;
lua_tostringatom(L, 2, &atom);
size_t namelen = 0;
const char* name = luaL_checklstring(L, 2, &namelen);
auto tag = lua_userdatatag(L, 1);
auto pathCommand = (ScriptedPathCommand*)lua_touserdata(L, 1);
bool error = false;
int stackChange =
property_namecall_atom(L, pathCommand, tag, atom, error);
if (!error)
{
return stackChange;
}
luaL_error(L, "'%s' is not a valid index of PathCommand", name);
return 0;
}
return 0;
}
static int pathCommand_length(lua_State* L)
{
auto pathCommand = (ScriptedPathCommand*)lua_touserdata(L, 1);
auto points = pathCommand->points();
lua_pushnumber(L, (int)points.size());
return 1;
}
static int pathCommand_namecall(lua_State* L)
{
int atom;
const char* str = lua_namecallatom(L, &atom);
luaL_error(L,
"%s is not a valid method of %s",
str,
ScriptedPathCommand::luaName);
return 0;
}
static const luaL_Reg pathStaticMethods[] = {
{"new", path_new},
{NULL, NULL},
};
int luaopen_rive_path(lua_State* L)
{
{
lua_register_rive<ScriptedPathData>(L);
lua_pushcfunction(L, path_index, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, path_length, nullptr);
lua_setfield(L, -2, "__len");
lua_pushcfunction(L, path_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
{
luaL_register(L, ScriptedPath::luaName, pathStaticMethods);
lua_register_rive<ScriptedPath>(L);
lua_pushcfunction(L, path_index, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, path_length, nullptr);
lua_setfield(L, -2, "__len");
lua_pushcfunction(L, path_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
{
lua_register_rive<ScriptedPathCommand>(L);
lua_pushcfunction(L, pathCommand_index, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, pathCommand_length, nullptr);
lua_setfield(L, -2, "__len");
lua_pushcfunction(L, pathCommand_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
// Register ContourMeasure
lua_register_rive<ScriptedContourMeasure>(L);
lua_pushcfunction(L, contour_measure_index, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, contour_measure_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
// Register PathMeasure
lua_register_rive<ScriptedPathMeasure>(L);
lua_pushcfunction(L, path_measure_index, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, path_measure_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
return 1;
}
#endif