Compare commits

..

10 Commits

Author SHA1 Message Date
Parker Britt
375a98b664 Update README.md
Some checks failed
Build & Deploy Doxygen / doxygen (push) Has been cancelled
2025-08-24 19:36:59 +01:00
parker
21323d092c docs: add context class doxygen docs 2025-08-22 23:34:40 +01:00
Parker Britt
acf459bc5a Update README.md 2025-08-21 21:38:26 +01:00
Parker Britt
e8a1427c0a Update README.md 2025-08-21 21:27:27 +01:00
parker
0e5a929408 fix: restore accidental removal of geometry constructor 2025-08-20 18:32:02 +01:00
4eb59a59a6 docs: fix typos 2025-08-20 17:49:46 +01:00
e41db66cca docs: add doxygen comments for NetworkManager.h 2025-08-20 17:48:46 +01:00
c9461b4347 docs: add doxygen comments for GeometryOperator.h 2025-08-20 17:27:27 +01:00
45c2e757e6 docs: add doxygen comments for GeometryOpDef.h 2025-08-20 16:47:33 +01:00
b8f8dfad43 docs: add doxygen comments for GeometryConnection class 2025-08-20 16:22:58 +01:00
8 changed files with 276 additions and 20 deletions

View File

@@ -3,15 +3,18 @@
</div> </div>
<h1 align="center">Enzo 3D</h1> <h1 align="center">Enzo 3D</h1>
<img align="left" src="https://parkerbritt.com/jenkins_badge?job=enzo"> <img align="left" src="https://cards.parkerbritt.com/jenkins_badge?job=enzo">
<div align="right"> <div align="right">
<img src="https://parkerbritt.com/badge?label=C%2B%2B&icon=cplusplus&color=00599C"> <img src="https://cards.parkerbritt.com/badge?label=C%2B%2B&icon=cplusplus&color=00599C">
<img src="https://parkerbritt.com/badge?label=opengl&icon=opengl&color=5586A4"> <img src="https://cards.parkerbritt.com/badge?label=opengl&icon=opengl&color=5586A4">
<img src="https://parkerbritt.com/badge?label=qt&icon=qt&color=41CD52"> <img src="https://cards.parkerbritt.com/badge?label=qt&icon=qt&color=41CD52">
</div> </div>
<div align="center"> <div align="justify">
Enzo is a 3D animation software application focused on procedural geometry editing. Enzo is a node-based procedural 3D modeling framework for Visual Effects and Animation.<br><br>
It was created as an accessible alternative to existing industry tools, which are often prohibitively expensive, closed source, and resistant to user driven innovation.
Enzo is currently designed as a proof of concept and hobby project. The goal is to build a lean functional foundation for further development, experimentation, and community contribution.
</div> </div>
<br><br> <br><br>
<img src="https://github.com/user-attachments/assets/e919e41b-f41f-41b9-8aec-082d53fed470"> <img src="https://github.com/user-attachments/assets/e919e41b-f41f-41b9-8aec-082d53fed470">
@@ -33,5 +36,5 @@ Enzo is a 3D animation software application focused on procedural geometry editi
## Docs ## Documentation
Check out the api [here!](https://parkerbritt.github.io/enzo/) Doxygen documentation is generated automatically and can be accessed [here.](https://parkerbritt.github.io/enzo/)

View File

@@ -7,30 +7,95 @@
#include <QObject> #include <QObject>
namespace enzo::nt { namespace enzo::nt {
/**
* @brief The central coordinator of the engine's node system.
*
* The network manager is the central coordinator of the engines node system.
* It manages the lifecycle of operators, including their creation, storage,
* and validation, while also tracking dependencies between them. Acting
* as a singleton, it ensures that all parts of the engine work with a single
* consistent view of the network, providing global access. Beyond just storing
* operators, it also controls cooking and traversing dependency graphs,
* ensuring that updates flow correctly through the network when nodes change.
*
* @todo remove Qobject inheritance, this is no longer needed since switching to boost signals.
*
*/
class NetworkManager class NetworkManager
: public QObject : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
// delete copy constructor /// @brief Deleted the copy constructor for singleton.
NetworkManager(const NetworkManager& obj) = delete; NetworkManager(const NetworkManager& obj) = delete;
/// @brief Returns a reference to the singleton instance.
static NetworkManager& getInstance(); static NetworkManager& getInstance();
// functions /**
* @brief Creates a new node in the network
*
* @param OpInfo Data designating the properties of the node.
*
* @returns The operator ID of the newly created node
*/
OpId addOperator(op::OpInfo opInfo); OpId addOperator(op::OpInfo opInfo);
/** @brief Returns the operator ID for the node with its display flag set.
* There can only be only be one operator displayed at a time.
* Return value is nullopt if no node is set to display
*/
std::optional<OpId> getDisplayOp(); std::optional<OpId> getDisplayOp();
/**
* @brief Returns whether the node exists in the network and is valid.
* @param opId Operator ID of the node to check the validity of.
*/
bool isValidOp(nt::OpId opId); bool isValidOp(nt::OpId opId);
/**
* @brief Returns a reference to the GeometryOperator with the given OpId
*/
GeometryOperator& getGeoOperator(nt::OpId opId); GeometryOperator& getGeoOperator(nt::OpId opId);
/**
* @brief Sets given OpId to be displayed, releasing previous display Node
*/
void setDisplayOp(OpId opId); void setDisplayOp(OpId opId);
/**
* @brief Set the selection state for the given node.
*
* @param opId The node to set the state on.
* @param selected The selection state, true selects the node, false unselects it.
* @param add By default all other nodes are unselected, this parameter
* allows adding a selected node without deslecting any others.
*/
void setSelectedNode(OpId opId, bool selected, bool add=false); void setSelectedNode(OpId opId, bool selected, bool add=false);
/**
* @brief Returns the OpIds for all selected nodes.
*/
const std::vector<enzo::nt::OpId>& getSelectedNodes(); const std::vector<enzo::nt::OpId>& getSelectedNodes();
/** @name Signals
* @{
*/
// @brief A signal emitted when the display node is changed
boost::signals2::signal<void (nt::OpId)> displayNodeChanged; boost::signals2::signal<void (nt::OpId)> displayNodeChanged;
boost::signals2::signal<void (std::vector<nt::OpId> selectedNodeIds)> selectedNodesChanged;
// @brief A signal emitted when the geometry to be displayed is changed
// This is different to #displayNodeChanged because the state of geometry
// in a node can change based on parameters or other factors.
boost::signals2::signal<void (enzo::geo::Geometry& geometry)> displayGeoChanged; boost::signals2::signal<void (enzo::geo::Geometry& geometry)> displayGeoChanged;
// @brief A signal emitted when the selection of nodes changes
boost::signals2::signal<void (std::vector<nt::OpId> selectedNodeIds)> selectedNodesChanged;
/** @} */
#ifdef UNIT_TEST #ifdef UNIT_TEST
/// @brief For use in unit tests, resets the state of the operator.
void _reset(); void _reset();
#endif #endif

View File

@@ -10,6 +10,12 @@ namespace enzo::nt
namespace enzo::op namespace enzo::op
{ {
/**
* @class Context
* @brief Provides network context for the cookOp function.
*
* The context class is an interface for the cookOp function that provides important runtime context about the network. It allows querying parameters, reading input geometry, and in the future provides values like time.
*/
class Context class Context
{ {
public: public:

View File

@@ -53,6 +53,9 @@ using attributeIterator = std::vector<std::shared_ptr<ga::Attribute>>::iterator;
class Geometry class Geometry
{ {
public: public:
Geometry();
Geometry(const Geometry& other);
/** /**
* @brief Assignment operator. Performs a deep copy of another Geometry. * @brief Assignment operator. Performs a deep copy of another Geometry.
* @param rhs The Geometry object to copy from. * @param rhs The Geometry object to copy from.

View File

@@ -6,24 +6,56 @@
namespace enzo::nt namespace enzo::nt
{ {
class GeometryOperator; class GeometryOperator;
/**
* @class GeometryConnection
* @brief Directional edges to connect nodes
*
*
* @todo Currently geometry connections are stored on nodes. It might
* make more sense to move them to a container within the network manager
* so they're all in the same place
*
* Input and output are in relation to data flow.
*
* @todo Currently geometry connections are stored on nodes. It might
* make more sense to move them to a container within the network manager
* so they're all in the same place
*/
class GeometryConnection class GeometryConnection
{ {
public: public:
// input and output are in relation to data flow /**
// the input node is the node the data flows from * @brief Constructs a connection between two nodes
// the output node is the node the data flows to *
GeometryConnection(enzo::nt::OpId inputOpId, unsigned int inputIndex, enzo::nt::OpId outputOpId, unsigned int outputIndex); * Input and output are in relation to data flow.
*
* @param inputOpId The [Operator ID](@ref OpId) in which data flows from.
* @param outputOpId The [Operator ID](@ref OpId) in which data flows to.
* @param inputOpIndex The output socket number of @p inputOpId in which data flows from.
* @param outputOpIndex The input socket number of @p outputOpId in which data flows to.
*/
GeometryConnection(enzo::nt::OpId inputOpId, unsigned int inputOpIndex, enzo::nt::OpId outputOpId, unsigned int outputOpIndex);
/// @brief Returns the [Operator ID](@ref OpId) of the connection input (where data flows from).
enzo::nt::OpId getInputOpId() const; enzo::nt::OpId getInputOpId() const;
/// @brief Returns the [Operator ID](@ref OpId) of the connection output (where data flows to).
enzo::nt::OpId getOutputOpId() const; enzo::nt::OpId getOutputOpId() const;
/// @brief Returns the socket number of #getInputOpId in which data flows from.
unsigned int getInputIndex() const; unsigned int getInputIndex() const;
/// @brief Returns the socket number of #getOutputOpId in which data flows to.
unsigned int getOutputIndex() const; unsigned int getOutputIndex() const;
/// @brief Provides an ostream representation of the connection, useful for debugging.
friend std::ostream& operator<<(std::ostream& os, const GeometryConnection& p) friend std::ostream& operator<<(std::ostream& os, const GeometryConnection& p)
{ {
return os << p.inputOperatorId_ << ":" << p.inputIndex_ << " -> " << p.outputOperatorId_ << ":" << p.outputIndex_ << "\n"; return os << p.inputOperatorId_ << ":" << p.inputIndex_ << " -> " << p.outputOperatorId_ << ":" << p.outputIndex_ << "\n";
} }
/// @brief A signal emitted when the connection is removed
boost::signals2::signal<void ()> removed; boost::signals2::signal<void ()> removed;
/// @brief Removes the connection from it's associated nodes. Does not delete the object.
void remove(); void remove();
// bool isValid(); // bool isValid();
private: private:

View File

@@ -13,17 +13,58 @@ namespace enzo::nt
{ {
class NetworkManager; class NetworkManager;
/**
* @brief Abstract class used to create new operators.
*
* The operator definition is a base class from which new geometry operators
* are inherited from. It provides and abstracted interface, to read and
* write data about itself and the context it is being computed.
*
* The class exposes utility functions for setting outputs and reading information
* about itself like the number of inputs.
*
* The most important part of this node is the virtual cookOp member function.
* This must be overridden to implement the node's logic when being cooked.
* When a node is cooked it takes the optional input geometry from the context
* class and outputs new geometry based on the purpose of that operator.
*/
class BOOST_SYMBOL_EXPORT GeometryOpDef class BOOST_SYMBOL_EXPORT GeometryOpDef
{ {
public: public:
/**
* @brief Sets up internal state
*/
GeometryOpDef(nt::NetworkManager* network, op::OpInfo opInfo); GeometryOpDef(nt::NetworkManager* network, op::OpInfo opInfo);
/**
* @brief This function is called at runtime to create the output geometry
*
* @post When overriding, this function must call setOutputGeo(n) at
* the end of a successful cook. Any outputs that are not set will output
* an emtpy geometry object.
*/
virtual void cookOp(op::Context context) = 0; virtual void cookOp(op::Context context) = 0;
/**
* @brief Returns the current output geometry.
*
* For use by the runtime Node
* Does not force a cook.
*
* @todo move to friend class Node
*/
geo::Geometry& getOutputGeo(unsigned outputIndex); geo::Geometry& getOutputGeo(unsigned outputIndex);
/**
* @brief Stops the cook and displays an error. Use inside the #cookOp function.
* @todo Add visual error to GUI
*/
void throwError(std::string error); void throwError(std::string error);
/// @brief Returns the minimum number of input connections required for the node to function. Set by op::OpInfo when registering the operator.
unsigned int getMinInputs() const; unsigned int getMinInputs() const;
/// @brief Returns the maximum number of input connections accepted by the node. Set by op::OpInfo when registering the operator.
unsigned int getMaxInputs() const; unsigned int getMaxInputs() const;
/// @brief Returns the number of available outputs the node provides. Set by op::OpInfo when registering the operator.
unsigned int getMaxOutputs() const; unsigned int getMaxOutputs() const;
private: private:
std::vector<enzo::geo::Geometry> outputGeometry_; std::vector<enzo::geo::Geometry> outputGeometry_;

View File

@@ -11,46 +11,149 @@
namespace enzo::nt { namespace enzo::nt {
std::weak_ptr<GeometryConnection> connectOperators(enzo::nt::OpId inputOpId, unsigned int inputIndex, enzo::nt::OpId outputOpId, unsigned int outputIndex); std::weak_ptr<GeometryConnection> connectOperators(enzo::nt::OpId inputOpId, unsigned int inputIndex, enzo::nt::OpId outputOpId, unsigned int outputIndex);
/**
* @class GeometryOperator
* @brief The unique runtime representation of a node
*/
class GeometryOperator class GeometryOperator
{ {
public: public:
/**
* @brief Constructs a new node
*
* @param opId the operator id assigned to this node. For most situations
* this should be set by the nt::NetworkManager
* @param opInfo The data class informing the node what its properties
* are that set it apart from other nodes. This is what makes a grid
* node different to a transform node.
*/
GeometryOperator(enzo::nt::OpId opId, op::OpInfo opInfo); GeometryOperator(enzo::nt::OpId opId, op::OpInfo opInfo);
// disable copying /// @brief Deleted copy constructor to avoid accidental copies.
GeometryOperator(const GeometryOperator&) = delete; GeometryOperator(const GeometryOperator&) = delete;
/// @brief Deleted copy assignment operator to avoid accidental copies.
GeometryOperator& operator=(const GeometryOperator&) = delete; GeometryOperator& operator=(const GeometryOperator&) = delete;
/// @brief Computes the output geometry based on the [cookOp](@ref nt::GeometryOpDef::cookOp)
/// definition in nt::GeometryOpDef. This is set by the @p opInfo constructor parameter
void cookOp(op::Context context); void cookOp(op::Context context);
/**
* @brief Returns the current output geometry.
*
* Does not trigger a cook so if the geometry may be outdated if not cooked first.
*
* @todo Add option to force cook or cook if dirty.
*/
geo::Geometry& getOutputGeo(unsigned int outputIndex) const; geo::Geometry& getOutputGeo(unsigned int outputIndex) const;
/** @brief Adds a GeometryConnection to one of the inputs. Replacing old connections if needed.
*
* Which input is decided and stored on the connection.
*
* Nodes can only have one connection so it will automatically remove existing connections
* with the same index, prioritizing the new one.
*/
void addInputConnection(std::shared_ptr<nt::GeometryConnection> connection); void addInputConnection(std::shared_ptr<nt::GeometryConnection> connection);
/** @brief Adds a GeometryConnection to one of the outputs. Replacing old connections if needed.
*
* Which output is decided and stored on the connection.
*
* Nodes can only have one connection so it will automatically remove existing connections
* with the same index, prioritizing the new one.
*/
void addOutputConnection(std::shared_ptr<nt::GeometryConnection> connection); void addOutputConnection(std::shared_ptr<nt::GeometryConnection> connection);
/** @brief Removes an input from the node's container.
*
* Does not remove the connection from any other node it's connected
* to, likely causing undefined behavior if called incorrectly.
*
* @todo remove in favor of the rewrite suggested in GeometryConnection
* todo in which connections are handled by the network manager rather than individual nodes.
*/
void removeInputConnection(unsigned int inputIndex); void removeInputConnection(unsigned int inputIndex);
/** @brief Removes an output from the node's container.
*
* Does not remove the connection from any other node it's connected
* to, likely causing undefined behavior if called incorrectly.
*
* @todo remove in favor of the rewrite suggested in GeometryConnection
* todo in which connections are handled by the network manager rather than individual nodes.
*/
void removeOutputConnection(const nt::GeometryConnection* connection); void removeOutputConnection(const nt::GeometryConnection* connection);
/**
* @brief Returns a vector containing weak pointers for all input connections.
*
* Connections returned by this function are weak pointers to indicate
* ownership belongs to the node/network and can be modified or deleted at any time.
*/
std::vector<std::weak_ptr<const GeometryConnection>> getInputConnections() const; std::vector<std::weak_ptr<const GeometryConnection>> getInputConnections() const;
/**
* @brief Returns a vector containing weak pointers for all output connections.
*
* Connections returned by this function are weak pointers to indicate
* ownership belongs to the node/network and can be modified or deleted at any time.
*/
std::vector<std::weak_ptr<const GeometryConnection>> getOutputConnections() const; std::vector<std::weak_ptr<const GeometryConnection>> getOutputConnections() const;
/**
* @brief Returns an optional connection from a specific input index.
*
* @returns Nullopt if the connection doesn't exist.
*/
std::optional<std::reference_wrapper<const GeometryConnection>> getInputConnection(size_t index) const; std::optional<std::reference_wrapper<const GeometryConnection>> getInputConnection(size_t index) const;
/// @brief Returns all parameters belonging to this node.
std::vector<std::weak_ptr<prm::Parameter>> getParameters(); std::vector<std::weak_ptr<prm::Parameter>> getParameters();
/// @brief Returns a parameter with the given name belonging to this node.
/// @returns Empty default constructed std::weak_ptr<prm::Parameter>() if no parameter of that name exists.
std::weak_ptr<prm::Parameter> getParameter(std::string parameterName); std::weak_ptr<prm::Parameter> getParameter(std::string parameterName);
/**
* @brief NOT YET IMPLEMENTED. Returns the runtime label given to this node as a unique identifier within it's scope.
* @todo implement
*/
std::string getLabel(); // TODO: implement node labels std::string getLabel(); // TODO: implement node labels
/**
* @brief Returns the name belonging to this type of node (eg. grid or transform). Not to be confused with the label.
*
* The type name decided at compile time and is shared across all nodes
* of the given type. All grids nodes share the same type name. Labels
* on the other hand are unique identifiers for a given runtime node (eg. myGrid1, groundGrid, wall).
*/
std::string getTypeName(); std::string getTypeName();
/**
* @brief Marks the outputed geometry as outdated and notifies the network
*
* @param dirtyDescendents Sets whether all descendents (nodes connected
* directly or indirectly to the output of this node) are also dirtied.
* This is usually what you want.
*/
void dirtyNode(bool dirtyDescendents=true); void dirtyNode(bool dirtyDescendents=true);
/// @brief Returns true if the node is dirty and false if the node is clean (does not need cooking).
bool isDirty(); bool isDirty();
/// @brief Returns the minimum number of input connections required
/// for the node to function. These are in order so 3 would mean the
/// first three inputs must have a connection.
unsigned int getMinInputs() const; unsigned int getMinInputs() const;
/// @brief Returns the maximum number of input connections accepted by the node.
unsigned int getMaxInputs() const; unsigned int getMaxInputs() const;
/// @brief Returns the number of available outputs the node provides.
unsigned int getMaxOutputs() const; unsigned int getMaxOutputs() const;
// signals /// @brief A signal emitted when the node is dirtied. This will usually notify the NetworkManager
boost::signals2::signal<void (nt::OpId opId, bool dirtyDescendents)> nodeDirtied; boost::signals2::signal<void (nt::OpId opId, bool dirtyDescendents)> nodeDirtied;
private: private:
void initParameters(); void initParameters();

View File

@@ -74,6 +74,9 @@ namespace enzo
} }
namespace nt namespace nt
{ {
/**
* @brief The unique ID assigned to each node in the network.
*/
using OpId = uint64_t; using OpId = uint64_t;
enum class SocketIOType { enum class SocketIOType {