// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef QSSGSCENEDESCRIPTION_P_H #define QSSGSCENEDESCRIPTION_P_H #include #include #include #include #include #include #include #include #include // QtQuick3D #include // Materials #include #include #include #include #include // cameras #include #include #include // Lights #include #include #include // Texture #include #include #include // #include #include #include #include QT_BEGIN_NAMESPACE // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // namespace QSSGSceneDesc { struct Node; struct Animation; template using rm_cvref_t = std::remove_cv_t>; struct Q_QUICK3DASSETUTILS_EXPORT Scene { using ResourceNodes = QVarLengthArray; using MeshStorage = QVector; using Animations = QVector; // Root node, usually an empty 'transform' node. Node *root = nullptr; QString id; // Don't make any assumption about the content of this id... ResourceNodes resources; MeshStorage meshStorage; Animations animations; QString sourceDir; mutable quint16 nodeId = 0; void reset(); void cleanup(); }; struct Q_QUICK3DASSETUTILS_EXPORT PropertyCall { virtual ~PropertyCall() = default; virtual bool set(QQuick3DObject &, const char *, const void *) = 0; virtual bool set(QQuick3DObject &, const char *, const QVariant &) = 0; virtual bool get(const QQuick3DObject &, const void *[]) const = 0; }; Q_QUICK3DASSETUTILS_EXPORT void destructValue(QVariant &value); struct Flag { QMetaEnum me; int value; }; struct Property { ~Property(); enum class Type { Static, Dynamic }; QVariant value; QByteArray name; QSSGSceneDesc::PropertyCall *call = nullptr; Type type = Type::Static; }; inline Property::~Property() { delete call; destructValue(value); } Q_QUICK3DASSETUTILS_EXPORT void destructNode(QSSGSceneDesc::Node &node); struct NodeList { NodeList(void * const *data, qsizetype n) { const auto size = sizeof(Node *) * n; head = reinterpret_cast(malloc(size)); memcpy(head, data, size); count = n; } ~NodeList() { if (head) free(head); } Node **head = nullptr; qsizetype count = -1; }; struct Q_QUICK3DASSETUTILS_EXPORT Node { // Node type enum class Type : quint8 { Transform, Camera, Model, Texture, Material, Light, Mesh, Skin, Skeleton, Joint, MorphTarget }; using type = QQuick3DNode; // Runtime type type mapping between this type and the QtQuick3D type using RuntimeType = QSSGRenderGraphObject::Type; explicit Node(QByteArray name, Node::Type type, Node::RuntimeType rt) : name(name) , runtimeType(rt) , nodeType(type) {} explicit Node(Node::Type type, Node::RuntimeType rt) : Node(nullptr, type, rt) {} virtual ~Node(); void cleanupChildren(); QByteArray name; Scene *scene = nullptr; QObject *obj = nullptr; using ChildList = QList; using PropertyList = QList; ChildList children; PropertyList properties; quint16 id = 0; RuntimeType runtimeType; Type nodeType; }; template static constexpr bool is_node_v = std::is_base_of_v; // Set up type mapping from a QQuick3D type to a SceneDesc type template struct TypeMap {}; #define QSSG_DECLARE_NODE(NODE) \ static_assert(is_node_v, #NODE " - does not inherit from Node!"); \ template <> struct TypeMap { using type = QSSGSceneDesc::NODE; }; template using as_scene_type_t = typename T::type; template using as_node_type_t = typename TypeMap::type; QSSG_DECLARE_NODE(Node) struct Q_QUICK3DASSETUTILS_EXPORT Texture : Node { using type = QQuick3DTexture; explicit Texture(Node::RuntimeType rt, const QByteArray &name = {}); }; QSSG_DECLARE_NODE(Texture) struct Q_QUICK3DASSETUTILS_EXPORT TextureData : Node { using type = QQuick3DTextureData; enum class Flags : quint8 { Compressed = 0x1 }; explicit TextureData(const QByteArray &textureData, QSize size, const QByteArray &format, quint8 flags = 0, QByteArray name = {}); QByteArray data; QSize sz; QByteArray fmt; quint8 flgs; }; QSSG_DECLARE_NODE(TextureData) struct Q_QUICK3DASSETUTILS_EXPORT Material : Node { using type = QQuick3DMaterial; explicit Material(Node::RuntimeType rt); }; QSSG_DECLARE_NODE(Material) // The mesh is a special node, as it's not really a node type but // a handle to a mesh that will be turned into a source URL... struct Q_QUICK3DASSETUTILS_EXPORT Mesh : Node { explicit Mesh(QByteArray name, qsizetype index); qsizetype idx; // idx to the mesh data in the mesh data storage (see Scene). }; struct Q_QUICK3DASSETUTILS_EXPORT Model : Node { using type = QQuick3DModel; Model(); }; QSSG_DECLARE_NODE(Model) struct Q_QUICK3DASSETUTILS_EXPORT Camera : Node { using type = QQuick3DCamera; explicit Camera(RuntimeType rt); }; QSSG_DECLARE_NODE(Camera) struct Q_QUICK3DASSETUTILS_EXPORT Light : Node { using type = QQuick3DAbstractLight; explicit Light(RuntimeType rt); }; QSSG_DECLARE_NODE(Light) struct Q_QUICK3DASSETUTILS_EXPORT Skin : Node { using type = QQuick3DSkin; Skin(); }; QSSG_DECLARE_NODE(Skin) struct Q_QUICK3DASSETUTILS_EXPORT Skeleton : Node { using type = QQuick3DSkeleton; Skeleton(); // Skeleton is a virtual node, which is added for the start of the joint heirarchy. // parent - joint 1 -> parent - skeleton - joint 1 // - joint 2 - joint 2 // - model 1 - model 1 // - camera 1 - camera 1 size_t maxIndex = 0; }; QSSG_DECLARE_NODE(Skeleton) struct Q_QUICK3DASSETUTILS_EXPORT Joint : Node { using type = QQuick3DJoint; Joint(); }; QSSG_DECLARE_NODE(Joint) struct Q_QUICK3DASSETUTILS_EXPORT MorphTarget : Node { using type = QQuick3DMorphTarget; MorphTarget(); }; QSSG_DECLARE_NODE(MorphTarget) // We keep our own list data structure, since Qt does not have a variant list where all the // elements have the same type, and using a list of QVariant is very inefficient struct ListView { ~ListView() { if (data) free(data); } QMetaType mt; void *data = nullptr; qsizetype count = -1; }; Q_QUICK3DASSETUTILS_EXPORT QMetaType listViewMetaType(); struct Animation { struct KeyPosition { enum class KeyType : quint16 { Frame = 0x100, Time = 0x200 }; enum class ValueType : quint8 { Number, Vec2, Vec3, Vec4, Quaternion }; ValueType getValueType() const { return ValueType(0xf & flag); } KeyType getKeyType() const { return KeyType(0xf00 & flag); } QVariant getValue() const { switch (getValueType()) { case ValueType::Number: return value.x(); case ValueType::Vec2: return value.toVector2D(); case ValueType::Vec3: return value.toVector3D(); case ValueType::Vec4: return value; case ValueType::Quaternion: return QQuaternion(value); } return value; } QMetaType::Type getValueQMetaType() const { switch (getValueType()) { case ValueType::Number: return QMetaType::Float; case ValueType::Vec2: return QMetaType::QVector2D; case ValueType::Vec3: return QMetaType::QVector3D; case ValueType::Vec4: return QMetaType::QVector4D; case ValueType::Quaternion: return QMetaType::QQuaternion; } return QMetaType::QVector4D; } QVector4D value; float time = 0.0f; quint16 flag = 0; }; using Keys = QList; struct Channel { enum class TargetType : quint8 { Property }; // This is a bit simplistic, but is all we support so let's keep simple. enum class TargetProperty : quint8 { Unknown, Position, Rotation, Scale, Weight // for MorphMesh }; Node *target = nullptr; Keys keys; TargetType targetType = TargetType::Property; TargetProperty targetProperty = TargetProperty::Unknown; }; using Channels = QList; Channels channels; // It stores the length of this Animation, every keys in every channels in // an animation will have the same KeyType and it will be a type of // the length float length = 0.0f; float framesPerSecond = 0.0f; // for translation back to frames QByteArray name; }; // Add a child node to parent node. Q_QUICK3DASSETUTILS_EXPORT void addNode(Node &parent, Node &node); // Add node to the scene, if a node is already set the new node will // become a child of the root node. Q_QUICK3DASSETUTILS_EXPORT void addNode(Scene &scene, Node &node); template struct ListParam { enum { value = 0 }; }; template struct ListParam> { using type = T; enum { value = 1 }; }; template using listParam_t = typename ListParam>::type; template struct FuncType { enum { value = 0 }; }; template struct FuncType { enum { value = sizeof...(A) == 3 }; using Ret = R; using Arg0 = std::tuple_element_t<0, std::tuple>; using Arg1 = std::tuple_element_t<1, std::tuple>; using Arg2 = std::tuple_element_t<2, std::tuple>; using Arg2Base = rm_cvref_t; }; template struct FuncType { enum { value = sizeof... (A) == 1 }; using Ret = R; using Class = C; // For now we only care about single argument functions using Arg0 = std::tuple_element_t<0, std::tuple>; using Arg0Base = rm_cvref_t; }; template struct FuncType (C::*)()> { enum { value = 2 }; using Ret = QQmlListProperty; using Class = C; using Arg0 = void; using Arg0Base = Arg0; }; template struct PropertyProxySetter : PropertyCall { using Setter = Ret (*)(QQuick3DObject &, const char *, Arg); constexpr explicit PropertyProxySetter(Setter fn) : call(fn) {} Setter call = nullptr; bool get(const QQuick3DObject &, const void *[]) const override { return false; } bool set(QQuick3DObject &that, const char *name, const void *value) override { if constexpr (std::is_pointer_v::Arg2>) call(that, name, reinterpret_cast::Arg2>(const_cast(value))); else call(that, name, *reinterpret_cast::Arg2Base *>(const_cast(value))); return true; } bool set(QQuick3DObject &that, const char *name, const QVariant &var) override { call(that, name, qvariant_cast::Arg2Base>(var)); return true; } }; template struct PropertySetter : PropertyCall { using Setter = Ret (Class::*)(Arg); constexpr explicit PropertySetter(Setter fn) : call(fn) {} Setter call = nullptr; bool get(const QQuick3DObject &, const void *[]) const override { return false; } bool set(QQuick3DObject &that, const char *, const void *value) override { if constexpr (std::is_pointer_v::Arg0>) (qobject_cast(&that)->*call)(reinterpret_cast::Arg0>(const_cast(value))); else { (qobject_cast(&that)->*call)(*reinterpret_cast::Arg0Base *>(const_cast(value))); } return true; } bool set(QQuick3DObject &that, const char *, const QVariant &var) override { (qobject_cast(&that)->*call)(qvariant_cast::Arg0Base>(var)); return true; } }; template struct PropertyListSetter : PropertyCall { using Setter = Ret (Class::*)(Arg); using ListT = typename FuncType::Arg0Base; using It = listParam_t; constexpr explicit PropertyListSetter(Setter fn) : call(fn) {} Setter call = nullptr; bool get(const QQuick3DObject &, const void *[]) const override { return false; } bool set(QQuick3DObject &that, const char *, const void *value) override { if (const auto listView = reinterpret_cast(value)) { if (listView->count > 0) { const auto begin = reinterpret_cast(listView->data); const auto end = reinterpret_cast(listView->data) + listView->count; (qobject_cast(&that)->*call)(ListT{begin, end}); } else { (qobject_cast(&that)->*call)(ListT{}); } return true; } return false; } bool set(QQuick3DObject &that, const char *, const QVariant &var) override { if (const auto listView = qvariant_cast(var)) { if (listView->count > 0) { const auto begin = reinterpret_cast(listView->data); const auto end = reinterpret_cast(listView->data) + listView->count; (qobject_cast(&that)->*call)(ListT{begin, end}); } else { (qobject_cast(&that)->*call)(ListT{}); } } return false; } }; template typename List> struct PropertyList : PropertyCall { using ListType = List; using ListFunc = ListType (Class::*)(); constexpr explicit PropertyList(ListFunc fn) : listfn(fn) {} ListFunc listfn = nullptr; static_assert(std::is_same_v>, "Expected QQmlListProperty!"); bool get(const QQuick3DObject &, const void *[]) const override { return false; } void doSet(QQuick3DObject &that, const QSSGSceneDesc::NodeList &nodeList) { ListType list = (qobject_cast(&that)->*listfn)(); auto head = reinterpret_cast **>(nodeList.head); for (int i = 0, end = nodeList.count; i != end; ++i) list.append(&list, qobject_cast((*(head + i))->obj)); } bool set(QQuick3DObject &that, const char *, const void *value) override { if (value) { const auto &nodeList = *reinterpret_cast(value); doSet(that, nodeList); return true; } return false; } bool set(QQuick3DObject &that, const char *, const QVariant &var) override { const auto *nodeList = qvariant_cast(var); if (nodeList) { doSet(that, *nodeList); return true; } return false; } }; template using if_node = typename std::enable_if_t, bool>; template using if_compatible_t = typename std::enable_if_t::Arg0Base, rm_cvref_t>, bool>; template using if_compatible_node_list_t = typename std::enable_if_t::Ret, QQmlListProperty>>, bool>; template using if_compatible_proxy_t = typename std::enable_if_t::Arg2Base, rm_cvref_t>, bool>; // Sets a property on a node, the property is a name map to struct containing a pointer to the value and a function // to set the value on an runtime object (QtQuick3DObject). The type is verified at compile-time, so we can assume // the value is of the right type when setting it at run-time. template = false> static void setProperty(QSSGSceneDesc::Node &node, const char *name, Setter setter, T &&value) { Q_ASSERT(node.scene); auto prop = new Property; prop->name = name; prop->call = new PropertySetter(setter); prop->value = QVariant::fromValue(std::forward(value)); node.properties.push_back(prop); } template> = false> static void setProperty(QSSGSceneDesc::Node &node, const char *name, Setter setter, QFlags value) { Q_ASSERT(node.scene); auto prop = new Property; prop->name = name; prop->call = new PropertySetter(setter); prop->value = QVariant::fromValue(Flag{ QMetaEnum::fromType>(), value.toInt() }); node.properties.push_back(prop); } template> = false> static void setProperty(QSSGSceneDesc::Node &node, const char *name, Setter setter, QList value) { Q_ASSERT(node.scene); static_assert(!std::is_pointer_v, "Type cannot be a pointer!"); static_assert(std::is_trivially_destructible_v && std::is_trivially_copy_constructible_v, "List parameter type needs to be trivially constructable and trivially destructible!"); const auto count = value.size(); void *data = nullptr; if (count) { const auto asize = count * sizeof(T); data = malloc(asize); // is freed in ~ListView memcpy(data, value.constData(), asize); } auto prop = new Property; prop->name = name; prop->call = new PropertyListSetter(setter); prop->value = QVariant::fromValue(new ListView{ QMetaType::fromType>(), data, count }); node.properties.push_back(prop); } Q_QUICK3DASSETUTILS_EXPORT QSSGSceneDesc::Property *setProperty(QSSGSceneDesc::Node &node, const char *name, QVariant &&value); // Calling this will omit any type checking, so make sure the type is handled correctly // when it gets used later! template static void setProperty(QSSGSceneDesc::Node &node, const char *name, Setter setter, QVariant &&value) { Q_ASSERT(node.scene); Property *prop = new Property; prop->name = name; prop->call = new PropertySetter(setter); prop->value = value; node.properties.push_back(prop); } template = true> static void setProperty(QSSGSceneDesc::Node &node, const char *name, Setter setter, Value &&value, QSSGSceneDesc::Property::Type type = QSSGSceneDesc::Property::Type::Static) { Q_ASSERT(node.scene); static_assert(std::is_trivially_destructible_v>, "Value needs to be trivially destructible!"); Property *prop = new Property; prop->name = name; prop->call = new PropertyProxySetter(setter); prop->value = QVariant::fromValue(value); prop->type = type; node.properties.push_back(prop); } template = false> static void setProperty(QSSGSceneDesc::Node &node, const char *name, Setter setter, ViewValue view) { Q_ASSERT(node.scene); static_assert(std::is_same_v::Arg0Base>, "Type cannot be mapped to slot argument"); Property *prop = new Property; prop->name = name; prop->call = new PropertySetter(setter); prop->value = QVariant::fromValue(view); node.properties.push_back(prop); } template *> = true> static void setProperty(QSSGSceneDesc::Node &node, const char *name, Setter setter, Value *value) { Q_ASSERT(node.scene); Property *prop = new Property; prop->name = name; prop->call = new PropertySetter(setter); // Always 'Node', the Node class itself contains more fine grain type information. prop->value = QVariant::fromValue(static_cast(value)); node.properties.push_back(prop); } // Overloaded function for setting a type to a property that's a QQmlListProperty. template = true> static void setProperty(QSSGSceneDesc::Node &node, const char *name, Setter setter, const QVarLengthArray &list) { Q_ASSERT(node.scene); if (!list.isEmpty()) { NodeList *l = new NodeList(reinterpret_cast(list.constData()), list.count()); Property *prop = new Property; prop->name = name; prop->call = new PropertyList(setter); prop->value = QVariant::fromValue(l); node.properties.push_back(prop); } } } // QSSGSceneDesc QT_END_NAMESPACE Q_DECLARE_METATYPE(QSSGSceneDesc::Node) Q_DECLARE_METATYPE(QSSGSceneDesc::Texture) Q_DECLARE_METATYPE(QSSGSceneDesc::Material) Q_DECLARE_METATYPE(QSSGSceneDesc::Mesh) Q_DECLARE_METATYPE(QSSGSceneDesc::Model) Q_DECLARE_METATYPE(QSSGSceneDesc::Camera) Q_DECLARE_METATYPE(QSSGSceneDesc::Light) Q_DECLARE_METATYPE(QSSGSceneDesc::Skin) Q_DECLARE_METATYPE(QSSGSceneDesc::Skeleton) Q_DECLARE_METATYPE(QSSGSceneDesc::Joint) Q_DECLARE_METATYPE(QSSGSceneDesc::MorphTarget) Q_DECLARE_METATYPE(QSSGSceneDesc::NodeList) Q_DECLARE_METATYPE(QSSGSceneDesc::Animation) Q_DECLARE_METATYPE(QSSGSceneDesc::ListView) Q_DECLARE_METATYPE(QSSGSceneDesc::Flag) #endif // QSSGSCENEDESCRIPTION_P_H