From 620def19f110a45bb235ce8fe1fc17e5981c9d7c Mon Sep 17 00:00:00 2001 From: John McCardle Date: Wed, 8 Mar 2023 09:20:57 -0500 Subject: [PATCH] Fixed animations, jank-noted some issues & workarounds with the Animation python API --- src/Animation.cpp | 21 ++++--- src/Animation.h | 1 + src/McRFPy_API.cpp | 121 ++++++++++++++++++++++++++++++++++++++- src/PythonScene.cpp | 6 +- src/scripts/TestScene.py | 49 ++++++++++++++-- 5 files changed, 182 insertions(+), 16 deletions(-) diff --git a/src/Animation.cpp b/src/Animation.cpp index 0ed59b3..0d024d7 100644 --- a/src/Animation.cpp +++ b/src/Animation.cpp @@ -14,7 +14,7 @@ template DiscreteAnimation::DiscreteAnimation(float _d, std::vector _v, std::function _cb, std::function _w, bool _l) :Animation(_d, _cb, _l), //duration(_d), target(_t), callback(_cb), loop(_l), elapsed(0.0f), index(0), nonelapsed(0.0f), values(_v), write(_w) { - timestep = _v.size() / _d; + timestep = _d / _v.size(); } /* // don't call virtual functions (like cancel()) from base class destructor @@ -47,11 +47,11 @@ void LerpAnimation::lerp() { template<> void LerpAnimation::lerp() { - std::cout << "sf::Vector2f implementation of lerp." << std::endl; + //std::cout << "sf::Vector2f implementation of lerp." << std::endl; int delta_x = endvalue.x - startvalue.x; int delta_y = endvalue.y - startvalue.y; - std::cout << "Start: " << startvalue.x << ", " << startvalue.y << "; End: " << endvalue.x << ", " << endvalue.y << std::endl; - std::cout << "Delta: " << delta_x << ", " << delta_y << std::endl; + //std::cout << "Start: " << startvalue.x << ", " << startvalue.y << "; End: " << endvalue.x << ", " << endvalue.y << std::endl; + //std::cout << "Delta: " << delta_x << ", " << delta_y << std::endl; //((sf::Vector2f*)target)->x = startvalue.x + (elapsed / duration * delta_x); //((sf::Vector2f*)target)->y = startvalue.y + (elapsed / duration * delta_y); write(sf::Vector2f(startvalue.x + (elapsed / duration * delta_x), @@ -70,24 +70,28 @@ void LerpAnimation::lerp() { template void LerpAnimation::step(float delta) { + if (complete) return; elapsed += delta; - std::cout << "LerpAnimation step function. Elapsed: " << elapsed < void DiscreteAnimation::step(float delta) { + if (complete) return; nonelapsed += delta; + //std::cout << "Nonelapsed: " << nonelapsed << " elapsed (pre-add): " << elapsed << " timestep: " << timestep << " duration: " << duration << " index: " << index << std::endl; if (nonelapsed < timestep) return; - if (index == values.size() - 1) return; + //std::cout << "values size: " << values.size() << " isDone(): " << isDone() << std::endl; + if (elapsed > duration && !complete) {callback(); complete = true; return; } elapsed += nonelapsed; // or should it be += timestep? + if (index == values.size() - 1) return; nonelapsed = 0; // or should it -= timestep? index++; //*(T*)target = values[index]; write(values[index]); - if (isDone()) cancel(); //use the exact value, not my math } template @@ -111,6 +115,7 @@ namespace animation_template_implementations { //LerpAnimation implement_vector2f; auto implement_v2f_const = LerpAnimation>(4.0, sf::Vector2(), sf::Vector2f(1,1), [](){}, [](sf::Vector2f v){}, false); + auto implement_disc_i = DiscreteAnimation(3.0, std::vector{0},[](){},[](int){},false); //LerpAnimation implement_vector2i; //LerpAnimation implment_int; //LerpAnimation implment_string; diff --git a/src/Animation.h b/src/Animation.h index 54a1dd7..36c9f6f 100644 --- a/src/Animation.h +++ b/src/Animation.h @@ -9,6 +9,7 @@ protected: float duration, elapsed; std::function callback; bool loop; + bool complete=false; public: //Animation(float, T, T*, std::function, bool); // lerp //Animation(float, std::vector, T*, std::function, bool); // discrete diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index 6de00c2..695a104 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -630,7 +630,7 @@ PyObject* McRFPy_API::_modGrid(PyObject* self, PyObject* args) { return Py_None; } -PyObject* McRFPy_API::_createAnimation(PyObject *self, PyObject *args) { +PyObject* _test_createAnimation(PyObject *self, PyObject *args) { //LerpAnimation::LerpAnimation(float _d, T _ev, T* _t, std::function _cb, std::function _w, bool _l) std::string menu_key = "demobox1"; McRFPy_API::animations.push_back( @@ -651,3 +651,122 @@ PyObject* McRFPy_API::_createAnimation(PyObject *self, PyObject *args) { Py_INCREF(Py_None); return Py_None; } + +#define CEQ(A, B) (std::string(A).compare(B) == 0) + +PyObject* McRFPy_API::_createAnimation(PyObject *self, PyObject *args) { + std::cout << "Creating animation called..." << std::endl; + float duration; + const char* parent; + const char* target_type; + PyObject* target_id_obj; + const char* field; + PyObject* callback; + PyObject* loop_obj; + PyObject* values_obj; + PyObject* evdata; // for decoding values_obj + //std::cout << PyUnicode_AsUTF8(PyObject_Repr(args)) << std::endl; + if (!PyArg_ParseTuple(args, "fssOsOOO", &duration, &parent, &target_type, &target_id_obj, &field, &callback, &loop_obj, &values_obj)) { return NULL; } + bool loop = PyObject_IsTrue(loop_obj); + int target_id = PyLong_AsLong(target_id_obj); + Py_INCREF(callback); + std::cout << "Animation fields received:" << + "\nduration: " << duration << + "\nparent: " << parent << + "\ntarget type: " << target_type << + "\ntarget id: " << PyUnicode_AsUTF8(PyObject_Repr(target_id_obj)) << + "\nfield: " << field << + "\ncallback: " << PyUnicode_AsUTF8(PyObject_Repr(callback)) << + "\nloop: " << loop << + "\nvalues: " << PyUnicode_AsUTF8(PyObject_Repr(values_obj)) << std::endl; + /* Jank alert: + * The following block is meant to raise an exception when index is missing from object animations that require one, + * but accept the target_id_obj error (and accept None as an index) for menus/grids. + * Instead, I get a "latent" exception, not properly raised, when the index is None. + * That not-really-raised exception causes other scripts to silently fail to execute + * until I go into the REPL and run any code, and get a bizarre, botched traceback. + * So, Grid/Menu can just take an index of 0 in the scripts until this is dejankified + */ + if (!CEQ(target_type, "menu") && !CEQ(target_type, "grid") && target_id == -1) { + PyErr_SetObject(PyExc_TypeError, target_id_obj); + PyErr_SetString(PyExc_TypeError, "target_id (integer, index value) is required for targets other than 'menu' or 'grid'"); + return NULL; + } + // at this point, `values` needs to be decoded based on the `field` provided + +// 3.0, # duration, seconds +// "demobox1", # parent: a UIMenu or Grid key +// "menu", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity' +// None, # target id: integer index for menu or grid objs; None for grid/menu +// "position", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite' +// lambda: self.animation_done("demobox1"), #callback: callable once animation is complete +// False, #loop: repeat indefinitely +// [100, 100] # values: iterable of frames for 'sprite', lerp target for others + +//LerpAnimation::LerpAnimation(float _d, T _ev, T _sv, std::function _cb, std::function _w, bool _l) + if (CEQ(target_type, "menu")) { + auto obj = menus[std::string(parent)]; + if (CEQ(field, "position")) { + if (PyList_Check(values_obj)) evdata = PyList_AsTuple(values_obj); else evdata = values_obj; + auto end_value = sf::Vector2f(PyFloat_AsDouble(PyTuple_GetItem(evdata, 0)), + PyFloat_AsDouble(PyTuple_GetItem(evdata, 1))); + McRFPy_API::animations.push_back(new LerpAnimation( + duration, end_value, + obj->box.getPosition(), + [=](){PyObject_Call(callback, PyTuple_New(0), NULL);}, + [=](sf::Vector2f v){obj->box.setPosition(v);}, + loop) + ); + } + else if (CEQ(field, "size")) { + if (PyList_Check(values_obj)) evdata = PyList_AsTuple(values_obj); else evdata = values_obj; + auto end_value = sf::Vector2f(PyFloat_AsDouble(PyTuple_GetItem(evdata, 0)), + PyFloat_AsDouble(PyTuple_GetItem(evdata, 1))); + McRFPy_API::animations.push_back(new LerpAnimation( + duration, end_value, + obj->box.getSize(), + [=](){PyObject_Call(callback, PyTuple_New(0), NULL);}, + [=](sf::Vector2f v){obj->box.setSize(v);}, + loop) + ); + } + // else if (CEQ(field, "bgcolor")) { ) + } + else if (CEQ(target_type, "sprite")) { + auto obj = menus[std::string(parent)]->sprites[target_id]; + if (CEQ(field, "position")) { + PyObject* evdata; + if (PyList_Check(values_obj)) evdata = PyList_AsTuple(values_obj); else evdata = values_obj; + auto end_value = sf::Vector2f(PyFloat_AsDouble(PyTuple_GetItem(evdata, 0)), + PyFloat_AsDouble(PyTuple_GetItem(evdata, 1))); + McRFPy_API::animations.push_back(new LerpAnimation(duration, end_value, + sf::Vector2f(obj.x, obj.y), + [=](){PyObject_Call(callback, PyTuple_New(0), NULL);}, + [&](sf::Vector2f v){obj.x = v.x; obj.y = v.y;}, + loop) + ); + } + else if (CEQ(field, "sprite")) { + auto obj = menus[std::string(parent)]; + PyObject* evdata; + if (PyList_Check(values_obj)) evdata = PyList_AsTuple(values_obj); else evdata = values_obj; + std::vector frames; + for (int i = 0; i < PyTuple_Size(evdata); i++) { + frames.push_back(PyLong_AsLong(PyTuple_GetItem(evdata, i))); + } +//DiscreteAnimation(float _d, std::vector _v, std::function _cb, std::function _w, bool _l) + McRFPy_API::animations.push_back(new DiscreteAnimation( + duration, + frames, + [=](){PyObject_Call(callback, PyTuple_New(0), NULL);}, + [=](int s){obj->sprites[target_id].sprite_index = s;}, + loop) + ); + std::cout << "Frame animation constructed, there are now " <getFrameTime(); auto it = McRFPy_API::animations.begin(); while (it != McRFPy_API::animations.end()) { - std::cout << "Iterating" << std::endl; + //std::cout << "Iterating" << std::endl; (*it)->step(frametime); - std::cout << "Step complete" << std::endl; + //std::cout << "Step complete" << std::endl; if ((*it)->isDone()) { - std::cout << "Cleaning up" << std::endl; + std::cout << "Cleaning up Animation" << std::endl; auto prev = it; it++; McRFPy_API::animations.erase(prev); diff --git a/src/scripts/TestScene.py b/src/scripts/TestScene.py index 34674f8..1a35ed5 100644 --- a/src/scripts/TestScene.py +++ b/src/scripts/TestScene.py @@ -19,10 +19,12 @@ class TestScene: mcrfpy.createMenu(ui_name, 20, 520, 500, 200) mcrfpy.createCaption(ui_name, "Hello There", 18, BLACK) mcrfpy.createCaption(ui_name, "", 18, BLACK) - mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKBLUE, (0, 0, 0), "clicky", "testaction") + #mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKBLUE, (0, 0, 0), "clicky", "testaction") mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKRED, (0, 0, 0), "REPL", "startrepl") mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKGREEN, (0, 0, 0), "map gen", "gridgen") - mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKBLUE, (192, 0, 0), "anim", "animtest") + mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKGREEN, (0, 0, 0), "mapL", "gridgen2") + mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKBLUE, (192, 0, 0), "a_menu", "animtest") + mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKRED, GREEN, "a_spr", "animtest2") mcrfpy.createSprite(ui_name, 0, randint(0, 3), 300, 20, 5.0) self.menus = mcrfpy.listMenus() self.menus[0].visible = True @@ -30,9 +32,11 @@ class TestScene: # Button behavior self.clicks = 0 - mcrfpy.registerPyAction("testaction", self.click) + #mcrfpy.registerPyAction("testaction", self.click) mcrfpy.registerPyAction("gridgen", self.gridgen) - mcrfpy.registerPyAction("animtest", lambda: mcrfpy.createAnimation()) + mcrfpy.registerPyAction("gridgen2", lambda: self.gridgen()) + mcrfpy.registerPyAction("animtest", lambda: self.createAnimation()) + mcrfpy.registerPyAction("animtest2", lambda: self.createAnimation2()) # create grid (title, gx, gy, gs, x, y, w, h) mcrfpy.createGrid(grid_name, 20, 20, 16, 20, 20, 800, 500) @@ -51,6 +55,43 @@ class TestScene: print("[Python] Modifying:") self.grids[0].visible = True mcrfpy.modGrid(self.grids[0]) + + def animation_done(self, s): + print(f"The `{s}` animation completed.") + #self.menus = mcrfpy.listMenus() + + # if (!PyArg_ParseTuple(args, "fsssiOOO", &duration, &parent, &target_type, &target_id, &field, &callback, &loop_obj, &values_obj)) return NULL; + def createAnimation(self): + print(self.menus) + self.menus = mcrfpy.listMenus() + self.menus[0].w = 500 + self.menus[0].h = 200 + print(self.menus) + mcrfpy.modMenu(self.menus[0]) + print(self.menus) + mcrfpy.createAnimation( + 3.0, # duration, seconds + "demobox1", # parent: a UIMenu or Grid key + "menu", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity' + 0, # target id: integer index for menu or grid objs; None for grid/menu + "size", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite' + lambda: self.animation_done("demobox1"), #callback: callable once animation is complete + False, #loop: repeat indefinitely + [150, 100] # values: iterable of frames for 'sprite', lerp target for others + ) + + def createAnimation2(self): + mcrfpy.createAnimation( + 5, + "demobox1", + "sprite", + 0, + "sprite", + lambda: self.animation_done("sprite change"), + False, + [0, 1, 2, 1, 2, 0] + ) + scene = None def start(): global scene