#include "nodegraph.h" #include "animation.h" #include "noise.h" #include "RobotConfig.h" #include extern RobotConfig config; // CurveNode evaluation void CurveNode::evaluate(uint32_t tick) { if (!animation) { outputValue = 2048; return; } outputValue = animation->getMotorPosition(curveID, tick); } Node* NodeGraph::findNodeByID(uint8_t id) const { for (Node* node : nodes) { if (node->id == id) { return node; } } return nullptr; // not found } void NoiseNode::evaluate(uint32_t tick) { float t = (static_cast(tick) / 480.0f) * frequency * 10.0f; float raw = perlin1D_octave(seed, t, 4, 0.5f); outputValue = (raw + 1.0f) * 2047.5f; // → [0, 4095] } // ServoNode evaluation void ServoNode::evaluate(uint32_t tick) { outputValue = inputValue; } extern uint16_t getSineWaveValue(uint32_t tick); // extern int faceDetectX(); // extern int faceDetectY(); void VariableNode::evaluate(uint32_t currentTick) { switch (source) { case VAR_SINE: outputValue = getSineWaveValue(currentTick); break; case VAR_FACE_X: //outputValue = faceDetectX(); break; case VAR_FACE_Y: //outputValue = faceDetectY(); break; case VAR_ANALOG: outputValue = analogRead(7); // or whichever pin break; case VAR_SERVO_FEEDBACK: outputValue = config.getMotorPosition(arg0); break; } } void MathNode::evaluate(uint32_t tick) { float input = static_cast(inputValue); float result = 0; switch (op) { case OP_MULTIPLY: result = input * value; break; case OP_DIVIDE: result = value != 0 ? input / value : 0; break; case OP_ADD: result = input + value; break; case OP_SUBTRACT: result = input - value; break; } outputValue = static_cast(result); } void MapNode::evaluate(uint32_t tick) { int32_t input = inputValue; if (inMax == inMin) { outputValue = outMin; return; } int32_t result = (input - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; outputValue = static_cast(result); } // NodeGraph tick void NodeGraph::tick(uint32_t currentTick, const Animation& animation) { // Step 1: Evaluate each node and propagate outputs immediately for (Node* node : nodes) { node->evaluate(currentTick); for (const NodeConnection& conn : connections) { if (conn.fromID == node->id) { Node* to = findNodeByID(conn.toID); if (to) { to->inputValue = node->outputValue; // ✅ generic propagation } } } } } std::vector> NodeGraph::getServoOutputs() const { std::vector> outputs; for (Node* node : nodes) { if (node->type == TYPE_SERVONODE) { const ServoNode* servo = static_cast(node); outputs.emplace_back(servo->motorID, servo->outputValue); } } return outputs; } std::vector NodeGraph::getSortedNodes() { std::unordered_map> adj; std::unordered_map inDegree; std::unordered_map idMap; for (Node* node : nodes) { idMap[node->id] = node; inDegree[node->id] = 0; } for (const NodeConnection& conn : connections) { adj[conn.fromID].push_back(conn.toID); inDegree[conn.toID]++; } std::vector sorted; std::queue q; for (const auto& [id, deg] : inDegree) { if (deg == 0) q.push(id); } while (!q.empty()) { uint8_t id = q.front(); q.pop(); Node* node = idMap[id]; // ✅ Reset values here node->inputValue = 65535; node->outputValue = 65535; sorted.push_back(node); for (uint8_t neighbor : adj[id]) { if (--inDegree[neighbor] == 0) { q.push(neighbor); } } } return sorted; } // This function links nodes to their outside inputs void NodeGraph::bindAnimationContext(Animation* animation) { for (Node* node : nodes) { if (node->type == TYPE_CURVENODE) { CurveNode* curveNode = static_cast(node); curveNode->animation = animation; // ✅ link from outside } // Add other node types here as needed } } void loadNodeGraph(const uint8_t* packet, size_t length, NodeGraph& graph) { size_t offset = 0; // Read node count uint16_t nodeCount = packet[offset]; offset += 1; // Parse nodes for (uint16_t i = 0; i < nodeCount; ++i) { if (offset + 6 > length) break; // safety check uint8_t type = packet[offset++]; uint8_t id = packet[offset++]; uint16_t x = packet[offset] | (packet[offset + 1] << 8); offset += 2; uint16_t y = packet[offset] | (packet[offset + 1] << 8); offset += 2; Node* node = nullptr; switch (type) { case TYPE_CURVENODE: { // CurveNode if (offset + 1 > length) break; uint8_t curveID = packet[offset++]; auto* curve = new CurveNode(); curve->id = id; curve->type = type; curve->x = x; curve->y = y; curve->curveID = curveID; node = curve; break; } case TYPE_SERVONODE: { // ServoNode if (offset + 1 > length) break; uint8_t motorID = packet[offset++]; auto* servo = new ServoNode(); servo->id = id; servo->type = type; servo->x = x; servo->y = y; servo->motorID = motorID; node = servo; break; } case TYPE_VARIABLENODE: { // ServoNode if (offset + 1 > length) break; uint8_t source = packet[offset++]; uint8_t arg0 = packet[offset++]; auto* newNode = new VariableNode(); newNode->id = id; newNode->type = type; newNode->x = x; newNode->y = y; newNode->source = static_cast(source); newNode->arg0 = arg0; node = newNode; break; } case TYPE_MATHNODE: { if (offset + 5 > length) break; // 1 byte op + 4 bytes float uint8_t rawOp = packet[offset++]; float val; memcpy(&val, &packet[offset], sizeof(float)); offset += sizeof(float); auto* math = new MathNode(); math->id = id; math->type = type; math->x = x; math->y = y; math->op = static_cast(rawOp); math->value = val; node = math; break; } case TYPE_MAPNODE: { if (offset + 16 > length) break; // 4 x float32 = 16 bytes float inMin, inMax, outMin, outMax; memcpy(&inMin, &packet[offset], sizeof(float)); offset += 4; memcpy(&inMax, &packet[offset], sizeof(float)); offset += 4; memcpy(&outMin, &packet[offset], sizeof(float)); offset += 4; memcpy(&outMax, &packet[offset], sizeof(float)); offset += 4; auto* mapNode = new MapNode(); mapNode->id = id; mapNode->type = type; mapNode->x = x; mapNode->y = y; mapNode->inMin = inMin; mapNode->inMax = inMax; mapNode->outMin = outMin; mapNode->outMax = outMax; node = mapNode; break; } case TYPE_NOISENODE: { if (offset + 6 > length) break; // 4 bytes for float + 2 bytes for uint16_t float frequency; uint16_t seed; memcpy(&frequency, &packet[offset], sizeof(float)); offset += 4; memcpy(&seed, &packet[offset], sizeof(uint16_t)); offset += 2; auto* noiseNode = new NoiseNode(); noiseNode->id = id; noiseNode->type = type; noiseNode->x = x; noiseNode->y = y; noiseNode->frequency = frequency; noiseNode->seed = seed; node = noiseNode; } default: break; } if (node) { graph.nodes.push_back(node); } // Sort node list topologically for execution. graph.nodes = graph.getSortedNodes(); } // Parse connections graph.connections.clear(); if (offset + 2 > length) return; uint16_t connectionCount = packet[offset]; offset += 1; for (uint16_t i = 0; i < connectionCount; ++i) { if (offset + 2 > length) break; uint8_t fromID = packet[offset++]; uint8_t toID = packet[offset++]; graph.connections.push_back({ fromID, toID }); } } String printNodeGraph(const NodeGraph& graph) { String output = "📦 NodeGraph Dump\n"; output += "Nodes:\n"; for (const Node* node : graph.nodes) { output += " ID " + String(node->id); output += " | Type " + String(node->type); output += " | Pos (" + String(node->x) + ", " + String(node->y) + ")"; switch (node->type) { case TYPE_CURVENODE: { // CurveNode const CurveNode* curve = static_cast(node); output += " | CurveID " + String(curve->curveID); break; } case TYPE_SERVONODE: { // ServoNode const ServoNode* servo = static_cast(node); output += " | MotorID " + String(servo->motorID); break; } case TYPE_VARIABLENODE: { // VariableNode const VariableNode* servo = static_cast(node); output += " | source: " + String(servo->source); output += " | arg0: " + String(servo->arg0); break; } case TYPE_NOISENODE: { // NoiseNode const NoiseNode* noise = static_cast(node); output += " | Frequency " + String(noise->frequency, 4); output += " | Seed " + String(noise->seed); break; } } output += "\n"; } output += "Connections:\n"; for (const auto& conn : graph.connections) { output += " " + String(conn.fromID) + " → " + String(conn.toID) + "\n"; } return output; } std::vector NodeGraph::serialize() { std::vector data; // Node count data.push_back(static_cast(nodes.size())); // Serialize nodes for (Node* node : nodes) { data.push_back(node->type); data.push_back(node->id); uint16_t x = node->x; uint16_t y = node->y; data.insert(data.end(), reinterpret_cast(&x), reinterpret_cast(&x) + sizeof(uint16_t)); data.insert(data.end(), reinterpret_cast(&y), reinterpret_cast(&y) + sizeof(uint16_t)); switch (node->type) { case TYPE_SERVONODE: { auto* n = static_cast(node); data.push_back(n->motorID); break; } case TYPE_CURVENODE: { auto* n = static_cast(node); data.push_back(n->curveID); break; } case TYPE_VARIABLENODE: { auto* n = static_cast(node); data.push_back(static_cast(n->source)); data.push_back(static_cast(n->arg0)); break; } case TYPE_MATHNODE: { auto* n = static_cast(node); data.push_back(static_cast(n->op)); data.insert(data.end(), reinterpret_cast(&n->value), reinterpret_cast(&n->value) + sizeof(float)); break; } case TYPE_MAPNODE: { auto* n = static_cast(node); data.insert(data.end(), reinterpret_cast(&n->inMin), reinterpret_cast(&n->inMin) + sizeof(float)); data.insert(data.end(), reinterpret_cast(&n->inMax), reinterpret_cast(&n->inMax) + sizeof(float)); data.insert(data.end(), reinterpret_cast(&n->outMin), reinterpret_cast(&n->outMin) + sizeof(float)); data.insert(data.end(), reinterpret_cast(&n->outMax), reinterpret_cast(&n->outMax) + sizeof(float)); break; } case TYPE_NOISENODE: { auto* n = static_cast(node); data.insert(data.end(), reinterpret_cast(&n->frequency), reinterpret_cast(&n->frequency) + sizeof(float)); data.insert(data.end(), reinterpret_cast(&n->seed), reinterpret_cast(&n->seed) + sizeof(uint16_t)); break; } } } // Connection count data.push_back(static_cast(connections.size())); // Serialize connections for (const NodeConnection& conn : connections) { data.push_back(conn.fromID); data.push_back(conn.toID); } return data; }