diff options
| author | 3gg <3gg@shellblade.net> | 2025-10-25 15:38:47 -0700 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2025-10-25 15:38:47 -0700 |
| commit | 643382dfd364a193686201e1c82b6fe7f351f068 (patch) | |
| tree | 5ec69a5e95735b2ce1f46e4dd81072acf0054db6 | |
| parent | b1ae27b8a928e8d7477b668c9b425bccd4edce11 (diff) | |
| -rw-r--r-- | include/gfx/scene/node.h | 15 | ||||
| -rw-r--r-- | src/asset/model.c | 161 | ||||
| -rw-r--r-- | src/scene/node.c | 47 |
3 files changed, 81 insertions, 142 deletions
diff --git a/include/gfx/scene/node.h b/include/gfx/scene/node.h index 193eb25..20b97e2 100644 --- a/include/gfx/scene/node.h +++ b/include/gfx/scene/node.h | |||
| @@ -50,21 +50,6 @@ SceneNode* gfx_make_model_node(Model*); | |||
| 50 | /// Create a new object node. | 50 | /// Create a new object node. |
| 51 | SceneNode* gfx_make_object_node(SceneObject*); | 51 | SceneNode* gfx_make_object_node(SceneObject*); |
| 52 | 52 | ||
| 53 | /// Make the node an anima node. | ||
| 54 | void gfx_construct_anima_node(SceneNode*, Anima*); | ||
| 55 | |||
| 56 | /// Make the node a camera node. | ||
| 57 | void gfx_construct_camera_node(SceneNode*, SceneCamera*); | ||
| 58 | |||
| 59 | /// Make the node a light node. | ||
| 60 | void gfx_construct_light_node(SceneNode*, Light*); | ||
| 61 | |||
| 62 | /// Make the node a model node. | ||
| 63 | void gfx_construct_model_node(SceneNode*, Model*); | ||
| 64 | |||
| 65 | /// Make the node an object node. | ||
| 66 | void gfx_construct_object_node(SceneNode*, SceneObject*); | ||
| 67 | |||
| 68 | /// Recursively destroy the scene node and its children. | 53 | /// Recursively destroy the scene node and its children. |
| 69 | /// | 54 | /// |
| 70 | /// The scene node and its children are removed from the scene graph. | 55 | /// The scene node and its children are removed from the scene graph. |
diff --git a/src/asset/model.c b/src/asset/model.c index 1e05a85..ccda27d 100644 --- a/src/asset/model.c +++ b/src/asset/model.c | |||
| @@ -1489,6 +1489,66 @@ static void load_animations( | |||
| 1489 | } | 1489 | } |
| 1490 | } | 1490 | } |
| 1491 | 1491 | ||
| 1492 | /// Remove joint nodes from the Gfx Scene. | ||
| 1493 | /// | ||
| 1494 | /// Joint nodes are not needed because joints are packed into the Anima. | ||
| 1495 | static void remove_joint_nodes( | ||
| 1496 | const cgltf_data* data, SceneNode** scene_nodes) { | ||
| 1497 | assert(data); | ||
| 1498 | assert(scene_nodes); | ||
| 1499 | |||
| 1500 | // This works assuming the joint nodes are contiguous. Contiguity is checked | ||
| 1501 | // when loading skins. See load_skins(). | ||
| 1502 | size_t min_joint_index = (size_t)-1; | ||
| 1503 | size_t max_joint_index = 0; | ||
| 1504 | |||
| 1505 | // First get the minimum and maximum indices of all joint nodes. | ||
| 1506 | for (cgltf_size s = 0; s < data->skins_count; ++s) { | ||
| 1507 | const cgltf_skin* skin = &data->skins[s]; | ||
| 1508 | |||
| 1509 | for (cgltf_size j = 0; j < skin->joints_count; ++j) { | ||
| 1510 | // Joint is an index/pointer into the nodes array. | ||
| 1511 | const cgltf_size joint_index = skin->joints[j] - data->nodes; | ||
| 1512 | assert(joint_index < data->nodes_count); | ||
| 1513 | |||
| 1514 | if (joint_index < min_joint_index) { | ||
| 1515 | min_joint_index = joint_index; | ||
| 1516 | } | ||
| 1517 | if (joint_index > max_joint_index) { | ||
| 1518 | max_joint_index = joint_index; | ||
| 1519 | } | ||
| 1520 | } | ||
| 1521 | } | ||
| 1522 | |||
| 1523 | assert(min_joint_index < data->nodes_count); | ||
| 1524 | assert(max_joint_index < data->nodes_count); | ||
| 1525 | |||
| 1526 | // Now walk over the joint nodes. If a joint's parent is itself not a joint | ||
| 1527 | // node, then that joint is a root of a joint hierarchy (skins in glTF may | ||
| 1528 | // have multiple roots). In such case, delete the root joint recursively. | ||
| 1529 | for (cgltf_size s = 0; s < data->skins_count; ++s) { | ||
| 1530 | const cgltf_skin* skin = &data->skins[s]; | ||
| 1531 | |||
| 1532 | for (cgltf_size j = 0; j < skin->joints_count; ++j) { | ||
| 1533 | // Joint is an index/pointer into the nodes array. | ||
| 1534 | const cgltf_size joint_index = skin->joints[j] - data->nodes; | ||
| 1535 | assert(joint_index < data->nodes_count); | ||
| 1536 | |||
| 1537 | const cgltf_node* joint = &data->nodes[joint_index]; | ||
| 1538 | |||
| 1539 | // Parent node index. | ||
| 1540 | const cgltf_size parent_index = joint->parent - data->nodes; | ||
| 1541 | assert(parent_index < data->nodes_count); | ||
| 1542 | |||
| 1543 | // If the parent is not a joint node, recursively delete this joint node. | ||
| 1544 | if ((parent_index < min_joint_index) || | ||
| 1545 | (parent_index > max_joint_index)) { | ||
| 1546 | gfx_destroy_node(&scene_nodes[joint_index]); | ||
| 1547 | } | ||
| 1548 | } | ||
| 1549 | } | ||
| 1550 | } | ||
| 1551 | |||
| 1492 | /// Load all nodes from the glTF scene. | 1552 | /// Load all nodes from the glTF scene. |
| 1493 | /// | 1553 | /// |
| 1494 | /// This function ignores the many scenes and default scene of the glTF spec | 1554 | /// This function ignores the many scenes and default scene of the glTF spec |
| @@ -1497,7 +1557,7 @@ static void load_nodes( | |||
| 1497 | const cgltf_data* data, SceneNode* root_node, SceneObject** objects, | 1557 | const cgltf_data* data, SceneNode* root_node, SceneObject** objects, |
| 1498 | SceneCamera** cameras, const Anima* anima, SceneNode** nodes) { | 1558 | SceneCamera** cameras, const Anima* anima, SceneNode** nodes) { |
| 1499 | // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a | 1559 | // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a |
| 1500 | // disjount union of strict trees: | 1560 | // disjoint union of strict trees: |
| 1501 | // | 1561 | // |
| 1502 | // "For Version 2.0 conformance, the glTF node hierarchy is not a directed | 1562 | // "For Version 2.0 conformance, the glTF node hierarchy is not a directed |
| 1503 | // acyclic graph (DAG) or scene graph, but a disjoint union of strict trees. | 1563 | // acyclic graph (DAG) or scene graph, but a disjoint union of strict trees. |
| @@ -1515,20 +1575,22 @@ static void load_nodes( | |||
| 1515 | 1575 | ||
| 1516 | cgltf_size next_camera = 0; | 1576 | cgltf_size next_camera = 0; |
| 1517 | 1577 | ||
| 1578 | // First pass: create the nodes. | ||
| 1518 | for (cgltf_size n = 0; n < data->nodes_count; ++n) { | 1579 | for (cgltf_size n = 0; n < data->nodes_count; ++n) { |
| 1519 | const cgltf_node* node = &data->nodes[n]; | 1580 | const cgltf_node* node = &data->nodes[n]; |
| 1520 | 1581 | ||
| 1521 | // Add SceneObject, SceneCamera or Lights. | 1582 | // Add SceneObject, SceneCamera or Lights. |
| 1522 | // TODO: Handle lights once they are implemented in the gfx library. | 1583 | // TODO: Handle lights once they are implemented in the gfx library. |
| 1584 | assert(!nodes[n]); | ||
| 1523 | if (node->mesh) { | 1585 | if (node->mesh) { |
| 1524 | const cgltf_size mesh_index = node->mesh - data->meshes; | 1586 | const cgltf_size mesh_index = node->mesh - data->meshes; |
| 1525 | assert(mesh_index < data->meshes_count); | 1587 | assert(mesh_index < data->meshes_count); |
| 1526 | SceneObject* object = objects[mesh_index]; | 1588 | SceneObject* object = objects[mesh_index]; |
| 1527 | gfx_construct_object_node(nodes[n], object); | 1589 | |
| 1590 | nodes[n] = gfx_make_object_node(object); | ||
| 1528 | 1591 | ||
| 1529 | if (node->skin) { | 1592 | if (node->skin) { |
| 1530 | assert(anima); | 1593 | assert(anima); |
| 1531 | |||
| 1532 | const cgltf_size skin_index = node->skin - data->skins; | 1594 | const cgltf_size skin_index = node->skin - data->skins; |
| 1533 | assert(skin_index < data->skins_count); | 1595 | assert(skin_index < data->skins_count); |
| 1534 | const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index); | 1596 | const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index); |
| @@ -1559,11 +1621,12 @@ static void load_nodes( | |||
| 1559 | } | 1621 | } |
| 1560 | 1622 | ||
| 1561 | gfx_set_camera_camera(cameras[next_camera], &camera); | 1623 | gfx_set_camera_camera(cameras[next_camera], &camera); |
| 1562 | gfx_construct_camera_node(nodes[n], cameras[next_camera]); | 1624 | nodes[n] = gfx_make_camera_node(cameras[next_camera]); |
| 1563 | ++next_camera; | 1625 | ++next_camera; |
| 1564 | } else { | 1626 | } else { |
| 1565 | // TODO: implementation for missing node types. | 1627 | // TODO: implementation for missing node types. |
| 1566 | // These nodes currently default to logical nodes. | 1628 | // These nodes currently default to logical nodes. |
| 1629 | nodes[n] = gfx_make_node(); | ||
| 1567 | } | 1630 | } |
| 1568 | assert(nodes[n]); | 1631 | assert(nodes[n]); |
| 1569 | 1632 | ||
| @@ -1589,6 +1652,11 @@ static void load_nodes( | |||
| 1589 | } | 1652 | } |
| 1590 | } | 1653 | } |
| 1591 | gfx_set_node_transform(nodes[n], &transform); | 1654 | gfx_set_node_transform(nodes[n], &transform); |
| 1655 | } | ||
| 1656 | |||
| 1657 | // Second pass: wire up the node hierarchy. | ||
| 1658 | for (cgltf_size n = 0; n < data->nodes_count; ++n) { | ||
| 1659 | const cgltf_node* node = &data->nodes[n]; | ||
| 1592 | 1660 | ||
| 1593 | // If this is a top-level node in the glTF scene, set its parent to the | 1661 | // If this is a top-level node in the glTF scene, set its parent to the |
| 1594 | // given root node. | 1662 | // given root node. |
| @@ -1601,66 +1669,12 @@ static void load_nodes( | |||
| 1601 | assert(parent); | 1669 | assert(parent); |
| 1602 | gfx_set_node_parent(nodes[n], parent); | 1670 | gfx_set_node_parent(nodes[n], parent); |
| 1603 | } | 1671 | } |
| 1604 | } // SceneNode. | ||
| 1605 | } | ||
| 1606 | |||
| 1607 | /// Remove joint nodes from the Gfx Scene. | ||
| 1608 | /// | ||
| 1609 | /// Joint nodes are not needed because joints are packed into the Anima. | ||
| 1610 | static void remove_joint_nodes( | ||
| 1611 | const cgltf_data* data, SceneNode** scene_nodes) { | ||
| 1612 | assert(data); | ||
| 1613 | assert(scene_nodes); | ||
| 1614 | |||
| 1615 | // This works assuming the joint nodes are contiguous. Contiguity is checked | ||
| 1616 | // when loading skins. See load_skins(). | ||
| 1617 | size_t min_joint_index = (size_t)-1; | ||
| 1618 | size_t max_joint_index = 0; | ||
| 1619 | |||
| 1620 | // First get the minimum and maximum indices of all joint nodes. | ||
| 1621 | for (cgltf_size s = 0; s < data->skins_count; ++s) { | ||
| 1622 | const cgltf_skin* skin = &data->skins[s]; | ||
| 1623 | |||
| 1624 | for (cgltf_size j = 0; j < skin->joints_count; ++j) { | ||
| 1625 | // Joint is an index/pointer into the nodes array. | ||
| 1626 | const cgltf_size joint_index = skin->joints[j] - data->nodes; | ||
| 1627 | assert(joint_index < data->nodes_count); | ||
| 1628 | |||
| 1629 | if (joint_index < min_joint_index) { | ||
| 1630 | min_joint_index = joint_index; | ||
| 1631 | } | ||
| 1632 | if (joint_index > max_joint_index) { | ||
| 1633 | max_joint_index = joint_index; | ||
| 1634 | } | ||
| 1635 | } | ||
| 1636 | } | 1672 | } |
| 1637 | 1673 | ||
| 1638 | assert(min_joint_index < data->nodes_count); | 1674 | // Clean up scene nodes that correspond to joints in the glTF. These are not |
| 1639 | assert(max_joint_index < data->nodes_count); | 1675 | // needed anymore. |
| 1640 | 1676 | if (data->skins_count > 0) { | |
| 1641 | // Now walk over the joint nodes. If a joint's parent is itself not a joint | 1677 | remove_joint_nodes(data, nodes); |
| 1642 | // node, then that joint is a root of a joint hierarchy (skins in glTF may | ||
| 1643 | // have multiple roots). In such case, delete the root joint recursively. | ||
| 1644 | for (cgltf_size s = 0; s < data->skins_count; ++s) { | ||
| 1645 | const cgltf_skin* skin = &data->skins[s]; | ||
| 1646 | |||
| 1647 | for (cgltf_size j = 0; j < skin->joints_count; ++j) { | ||
| 1648 | // Joint is an index/pointer into the nodes array. | ||
| 1649 | const cgltf_size joint_index = skin->joints[j] - data->nodes; | ||
| 1650 | assert(joint_index < data->nodes_count); | ||
| 1651 | |||
| 1652 | const cgltf_node* joint = &data->nodes[joint_index]; | ||
| 1653 | |||
| 1654 | // Parent node index. | ||
| 1655 | const cgltf_size parent_index = joint->parent - data->nodes; | ||
| 1656 | assert(parent_index < data->nodes_count); | ||
| 1657 | |||
| 1658 | // If the parent is not a joint node, recursively delete this joint node. | ||
| 1659 | if ((parent_index < min_joint_index) || | ||
| 1660 | (parent_index > max_joint_index)) { | ||
| 1661 | gfx_destroy_node(&scene_nodes[joint_index]); | ||
| 1662 | } | ||
| 1663 | } | ||
| 1664 | } | 1678 | } |
| 1665 | } | 1679 | } |
| 1666 | 1680 | ||
| @@ -1768,19 +1782,9 @@ static Model* load_scene( | |||
| 1768 | goto cleanup; | 1782 | goto cleanup; |
| 1769 | } | 1783 | } |
| 1770 | 1784 | ||
| 1771 | // Skins refer to nodes, and nodes may refer to skins. To break this circular | ||
| 1772 | // dependency, glTF defines skins in terms of node indices. We could do the | ||
| 1773 | // same if Gfx allowed allocating nodes contiguously in memory. For now, | ||
| 1774 | // create the nodes up front and use the indices of the array to map to the | ||
| 1775 | // node_idx. | ||
| 1776 | for (cgltf_size i = 0; i < data->nodes_count; ++i) { | ||
| 1777 | scene_nodes[i] = gfx_make_node(); | ||
| 1778 | } | ||
| 1779 | |||
| 1780 | // Create the scene's root node. | 1785 | // Create the scene's root node. |
| 1781 | // This is an anima node if the scene has skins; otherwise it is a logical | 1786 | // This is an anima node if the scene has skins; otherwise it is a logical |
| 1782 | // node. | 1787 | // node. |
| 1783 | root_node = gfx_make_node(); | ||
| 1784 | if (data->skins_count > 0) { | 1788 | if (data->skins_count > 0) { |
| 1785 | anima_desc = calloc(1, sizeof(AnimaDesc)); | 1789 | anima_desc = calloc(1, sizeof(AnimaDesc)); |
| 1786 | if (!anima_desc) { | 1790 | if (!anima_desc) { |
| @@ -1797,19 +1801,16 @@ static Model* load_scene( | |||
| 1797 | compute_joint_bounding_boxes( | 1801 | compute_joint_bounding_boxes( |
| 1798 | data, anima_desc->num_joints, anima_desc->joints); | 1802 | data, anima_desc->num_joints, anima_desc->joints); |
| 1799 | 1803 | ||
| 1800 | anima = gfx_make_anima(anima_desc); | 1804 | anima = gfx_make_anima(anima_desc); |
| 1801 | gfx_construct_anima_node(root_node, anima); | 1805 | root_node = gfx_make_anima_node(anima); |
| 1806 | } else { | ||
| 1807 | root_node = gfx_make_node(); | ||
| 1802 | } | 1808 | } |
| 1809 | assert(root_node); | ||
| 1803 | 1810 | ||
| 1804 | // The root node becomes the root of all scene nodes. | 1811 | // The root node becomes the root of all scene nodes. |
| 1805 | load_nodes(data, root_node, scene_objects, scene_cameras, anima, scene_nodes); | 1812 | load_nodes(data, root_node, scene_objects, scene_cameras, anima, scene_nodes); |
| 1806 | 1813 | ||
| 1807 | // Clean up scene nodes that correspond to joints in the glTF. These are | ||
| 1808 | // not needed anymore. | ||
| 1809 | if (data->skins_count > 0) { | ||
| 1810 | remove_joint_nodes(data, scene_nodes); | ||
| 1811 | } | ||
| 1812 | |||
| 1813 | model = gfx_make_model(root_node); | 1814 | model = gfx_make_model(root_node); |
| 1814 | 1815 | ||
| 1815 | success = true; | 1816 | success = true; |
diff --git a/src/scene/node.c b/src/scene/node.c index c5c8d94..7cc315c 100644 --- a/src/scene/node.c +++ b/src/scene/node.c | |||
| @@ -110,53 +110,6 @@ static void free_node_resource(SceneNode* node) { | |||
| 110 | FAIL("unhandled node type"); | 110 | FAIL("unhandled node type"); |
| 111 | } | 111 | } |
| 112 | 112 | ||
| 113 | void gfx_construct_anima_node(SceneNode* node, Anima* anima) { | ||
| 114 | assert(node); | ||
| 115 | assert(anima); | ||
| 116 | free_node_resource(node); | ||
| 117 | node->type = AnimaNode; | ||
| 118 | node->anima = mem_get_anima_index(anima); | ||
| 119 | anima->parent = mem_get_node_index(node); | ||
| 120 | } | ||
| 121 | |||
| 122 | void gfx_construct_camera_node(SceneNode* node, SceneCamera* camera) { | ||
| 123 | assert(node); | ||
| 124 | assert(camera); | ||
| 125 | free_node_resource(node); | ||
| 126 | node->type = CameraNode; | ||
| 127 | node->camera = mem_get_camera_index(camera); | ||
| 128 | camera->parent = mem_get_node_index(node); | ||
| 129 | } | ||
| 130 | |||
| 131 | // TODO: Add a common helper function between each gfx_make_xyz_node() and | ||
| 132 | // gfx_construct_xyz_node() pair. | ||
| 133 | void gfx_construct_light_node(SceneNode* node, Light* light) { | ||
| 134 | assert(node); | ||
| 135 | assert(light); | ||
| 136 | free_node_resource(node); | ||
| 137 | node->type = LightNode; | ||
| 138 | node->light = mem_get_light_index(light); | ||
| 139 | light->parent = mem_get_node_index(node); | ||
| 140 | } | ||
| 141 | |||
| 142 | void gfx_construct_model_node(SceneNode* node, Model* model) { | ||
| 143 | assert(node); | ||
| 144 | assert(model); | ||
| 145 | free_node_resource(node); | ||
| 146 | node->type = ModelNode; | ||
| 147 | node->model = mem_get_model_index(model); | ||
| 148 | model->parent = mem_get_node_index(node); | ||
| 149 | } | ||
| 150 | |||
| 151 | void gfx_construct_object_node(SceneNode* node, SceneObject* object) { | ||
| 152 | assert(node); | ||
| 153 | assert(object); | ||
| 154 | free_node_resource(node); | ||
| 155 | node->type = ObjectNode; | ||
| 156 | node->object = mem_get_object_index(object); | ||
| 157 | object->parent = mem_get_node_index(node); | ||
| 158 | } | ||
| 159 | |||
| 160 | static void destroy_node_rec(SceneNode* node) { | 113 | static void destroy_node_rec(SceneNode* node) { |
| 161 | assert(node); | 114 | assert(node); |
| 162 | 115 | ||
