public Object readNode(int nodeIndex) throws IOException {
        Object obj = fetchFromCache("nodes", nodeIndex, Object.class);
        if (obj != null) {
            if (obj instanceof JointWrapper) {
                // the node can be a previously loaded bone, let's return it
                return obj;
            } else {
                // If a spatial is referenced several times, it may be attached to different parents,
                // and it's not possible in JME, so we have to clone it.
                return ((Spatial) obj).clone();
            }
        }
        Spatial spatial;
        JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject();
        JsonArray children = nodeData.getAsJsonArray("children");
        Integer meshIndex = getAsInteger(nodeData, "mesh");
        if (meshIndex != null) {
            assertNotNull(meshes, "Can't find any mesh data, yet a node references a mesh");

            // there is a mesh in this node, however gltf
            // can split meshes in primitives (some kind of sub meshes),
            // We don't have this in JME, so we have to make one Mesh and one Geometry for each primitive.
            Geometry[] primitives = readMeshPrimitives(meshIndex);

            Node node = new Node();
            for (Geometry primitive : primitives) {
                node.attachChild(primitive);
            }

            node.setName(readMeshName(meshIndex));
            
            spatial = new Node();
            ((Node)spatial).attachChild(node);

        } else {
            // no mesh, we have a node. Can be a camera node or a regular node.
            Integer camIndex = getAsInteger(nodeData, "camera");
            if (camIndex != null) {
                Camera cam = fetchFromCache("cameras", camIndex, Camera.class);
                CameraNode node = new CameraNode(null, cam);
                node.setControlDir(CameraControl.ControlDirection.SpatialToCamera);
                spatial = node;
            } else {
                Node node = new Node();
                spatial = node;
            }
        }

        Integer skinIndex = getAsInteger(nodeData, "skin");
        if (skinIndex != null) {
            SkinData skinData = fetchFromCache("skins", skinIndex, SkinData.class);
            if (skinData != null) {
                List<Spatial> spatials = skinnedSpatials.get(skinData);
                spatials.add(spatial);
                skinData.used = true;
            }
        }

        spatial.setLocalTransform(readTransforms(nodeData));

        if (spatial.getName() == null) {
            spatial.setName(getAsString(nodeData.getAsJsonObject(), "name"));
        }

        spatial = customContentManager.readExtensionAndExtras("node", nodeData, spatial);

        addToCache("nodes", nodeIndex, spatial, nodes.size());
        return spatial;
    }
GLTF 2.0 loader
Created by Nehon on 07/08/2017.
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java