Beispiel #1
0
def export_sign_2(gltf: GLTF, name: str, sign: Sign) -> int:
    texture = sign.get_name_texture()
    # x = -0.2
    CM = 0.01
    PAPER_WIDTH, PAPER_HEIGHT = 8.5 * CM, 15.5 * CM
    # PAPER_THICK = 0.01

    # BASE_SIGN = 5 * CM
    # WIDTH_SIGN = 1.1 * CM
    #
    # y = -1.5 * PAPER_HEIGHT  # XXX not sure why this is negative
    # y = BASE_SIGN
    # x = -PAPER_WIDTH / 2

    fn = get_texture_file(texture)[0]

    material_index = make_material(gltf, doubleSided=False, baseColorFactor=[1, 1, 1, 1.0], fn=fn)
    mi = get_square()
    mesh_index = add_polygon(
        gltf,
        name + "-mesh",
        vertices=mi.vertices,
        texture=mi.textures,
        colors=mi.color,
        normals=mi.normals,
        material=material_index,
    )
    sign_node = Node(mesh=mesh_index)
    sign_node_index = add_node(gltf, sign_node)

    fn = get_texture_file("wood")[0]
    material_index_white = make_material(gltf, doubleSided=False, baseColorFactor=[0.5, 0.5, 0.5, 1], fn=fn)
    back_mesh_index = add_polygon(
        gltf,
        name + "-mesh",
        vertices=mi.vertices,
        texture=mi.textures,
        colors=mi.color,
        normals=mi.normals,
        material=material_index_white,
    )
    back_rot = SE3_from_rotation_translation(roty(np.pi), np.array([0, 0, 0]))

    back_node = Node(matrix=gm(back_rot), mesh=back_mesh_index)
    back_node_index = add_node(gltf, back_node)

    scale = np.diag([PAPER_WIDTH, PAPER_HEIGHT, PAPER_WIDTH, 1])
    rot = SE3_from_rotation_translation(
        rotz(np.pi / 2) @ rotx(np.pi / 2),
        # @ rotz(np.pi),
        np.array([0, 0, 0.8 * PAPER_HEIGHT]),
    )
    M = rot @ scale

    node = Node(matrix=gm(M), children=[sign_node_index, back_node_index])
    return add_node(gltf, node)
Beispiel #2
0
def export_DB18(gltf: GLTF, name, ob: DB18) -> int:
    _ = get_resource_path("duckiebot3/main.gltf")

    g2 = GLTF.load(_)
    # color = [0, 1, 0, 1]
    color = get_duckiebot_color_from_colorname(ob.color)
    set_duckiebot_color(g2, "gkmodel0_chassis_geom0_mat_001-material.001", color)
    set_duckiebot_color(g2, "gkmodel0_chassis_geom0_mat_001-material", color)
    index0 = embed(gltf, g2)
    radius = 0.0318
    M = SE3_from_R3(np.array([0, 0, radius]))
    node = Node(children=[index0], matrix=gm(M))
    index = add_node(gltf, node)
    return index
Beispiel #3
0
def export_sign(gltf: GLTF, name, ob: Sign):
    _ = get_resource_path("sign_generic/main.gltf")
    tname = ob.get_name_texture()
    logger.info(f"t = {type(ob)} tname = {tname}")
    fn_texture = get_texture_file(tname)[0]
    uri = os.path.join("textures/signs", os.path.basename(fn_texture))
    data = read_bytes_from_file(fn_texture)

    def transform(g: GLTF) -> GLTF:
        rf = FileResource(uri, data=data)
        g.resources.append(rf)
        g.model.images[0] = Image(name=tname, uri=uri)
        return g

    index = embed_external(gltf, _, key=tname, transform=transform)
    # M = SE3_roty(np.pi/5)
    M = SE3_rotz(-np.pi / 2)
    n2 = Node(matrix=gm(M), children=[index])
    return add_node(gltf, n2)
Beispiel #4
0
def _convert_nodes(
        rsm_version: int, nodes: List[AbstractNode],
        tex_id_by_node: List[List[int]],
        gltf_model: GLTFModel) -> Tuple[List[FileResource], List[int]]:
    root_nodes = []

    model_bbox = calculate_model_bounding_box(rsm_version, nodes)
    for node in nodes:
        if node.parent is None:
            _compute_transform_matrices(rsm_version, node,
                                        len(nodes) == 0, model_bbox)

    nodes_children: Dict[str, List[int]] = {}
    vertex_bytearray = bytearray()
    tex_vertex_bytearray = bytearray()
    byteoffset = 0
    tex_byteoffset = 0
    for node_id, node in enumerate(nodes):
        rsm_node = node.impl
        node_name = decode_string(rsm_node.name)
        if node.parent is not None:
            parent_name = decode_string(node.parent.impl.name)
            if parent_name in nodes_children:
                nodes_children[parent_name] += [node_id]
            else:
                nodes_children[parent_name] = [node_id]

        if rsm_version >= 0x203:
            node_tex_ids = tex_id_by_node[node_id]
        else:
            node_tex_ids = rsm_node.texture_ids
        vertices_by_texture = _sort_vertices_by_texture(rsm_node, node_tex_ids)

        gltf_primitives = []
        for tex_id, vertices in vertices_by_texture.items():
            # Model vertices
            bytelen = _serialize_vertices(vertices[0], vertex_bytearray)
            # Texture vertices
            tex_bytelen = _serialize_vertices(vertices[1],
                                              tex_vertex_bytearray)

            (mins, maxs) = _calculate_vertices_bounds(vertices[0])
            (tex_mins, tex_maxs) = _calculate_vertices_bounds(vertices[1])

            gltf_model.bufferViews.append(
                BufferView(
                    buffer=0,  # Vertices
                    byteOffset=byteoffset,
                    byteLength=bytelen,
                    target=BufferTarget.ARRAY_BUFFER.value))
            gltf_model.bufferViews.append(
                BufferView(
                    buffer=1,  # Texture vertices
                    byteOffset=tex_byteoffset,
                    byteLength=tex_bytelen,
                    target=BufferTarget.ARRAY_BUFFER.value))

            buffer_view_id = len(gltf_model.accessors)
            gltf_model.accessors.append(
                Accessor(bufferView=buffer_view_id,
                         byteOffset=0,
                         componentType=ComponentType.FLOAT.value,
                         count=len(vertices[0]),
                         type=AccessorType.VEC3.value,
                         min=mins,
                         max=maxs))
            gltf_model.accessors.append(
                Accessor(bufferView=buffer_view_id + 1,
                         byteOffset=0,
                         componentType=ComponentType.FLOAT.value,
                         count=len(vertices[1]),
                         type=AccessorType.VEC2.value,
                         min=tex_mins,
                         max=tex_maxs))
            gltf_primitives.append(
                Primitive(attributes=Attributes(POSITION=buffer_view_id + 0,
                                                TEXCOORD_0=buffer_view_id + 1),
                          material=tex_id))

            byteoffset += bytelen
            tex_byteoffset += tex_bytelen

        gltf_model.meshes.append(Mesh(primitives=gltf_primitives))

        # Decompose matrix to TRS
        translation, rotation, scale = decompose_matrix(
            node.gltf_transform_matrix)

        gltf_model.nodes.append(
            Node(name=node_name,
                 mesh=node_id,
                 translation=translation.to_list() if translation else None,
                 rotation=rotation.to_list() if rotation else None,
                 scale=scale.to_list() if scale else None))
        if node.parent is None:
            root_nodes.append(node_id)

    # Register vertex buffers
    vtx_file_name = 'vertices.bin'
    tex_vtx_file_name = 'tex_vertices.bin'
    gltf_resources = [
        FileResource(vtx_file_name, data=vertex_bytearray),
        FileResource(tex_vtx_file_name, data=tex_vertex_bytearray)
    ]
    gltf_model.buffers = [
        Buffer(byteLength=byteoffset, uri=vtx_file_name),
        Buffer(byteLength=tex_byteoffset, uri=tex_vtx_file_name)
    ]

    # Update nodes' children
    # Note(LinkZ): Consume children with `pop` to avoid issues with models
    # containing multiple nodes with the same name
    for gltf_node in gltf_model.nodes:
        gltf_node.children = nodes_children.pop(gltf_node.name, None)

    return gltf_resources, root_nodes
Beispiel #5
0
def embed(gltf: GLTF, g2: GLTF) -> int:
    lenn = lambda x: 0 if x is None else len(x)
    model = gltf.model
    off_accessors = lenn(model.accessors)
    off_animations = lenn(model.animations)

    off_buffers = lenn(model.buffers)
    off_bufferViews = lenn(model.bufferViews)
    off_cameras = lenn(model.cameras)
    off_images = lenn(model.images)
    off_materials = lenn(model.materials)
    off_meshes = lenn(model.meshes)
    off_nodes = lenn(model.nodes)
    off_samplers = lenn(model.samplers)
    off_scenes = lenn(model.scenes)
    off_skins = lenn(model.skins)
    off_textures = len(model.textures)
    model2 = g2.model

    def add_if_not_none(a, b):
        if a is None:
            return None
        else:
            return a + b

    # b: Buffer
    if model2.buffers:
        for b in model2.buffers:
            model.buffers.append(b)
    if model2.bufferViews:
        for bv in model2.bufferViews:
            buffer = add_if_not_none(bv.buffer, off_buffers)
            bv2 = replace(bv, buffer=buffer)
            model.bufferViews.append(bv2)
    if model2.samplers:
        for s in model2.samplers:
            model.samplers.append(s)
    if model2.cameras:
        for c in model2.cameras:
            model.cameras.append(c)
    if model2.images:
        for i in model2.images:
            i2 = replace(i, bufferView=add_if_not_none(i.bufferView, off_bufferViews))
            model.images.append(i2)

    def convert_texture_info(t: Optional[TextureInfo]) -> Optional[TextureInfo]:
        if t is None:
            return None
        index = add_if_not_none(t.index, off_textures)
        return replace(t, index=index)

    def convert_metallic_r(t: Optional[PBRMetallicRoughness]) -> Optional[PBRMetallicRoughness]:
        if t is None:
            return None
        baseColorTexture = convert_texture_info(t.baseColorTexture)

        metallicRoughnessTexture = convert_texture_info(t.metallicRoughnessTexture)

        return replace(
            t, baseColorTexture=baseColorTexture, metallicRoughnessTexture=metallicRoughnessTexture
        )

    if model2.materials:
        m: Material
        for m in model2.materials:
            pbrMetallicRoughness = convert_metallic_r(m.pbrMetallicRoughness)
            normalTexture = convert_texture_info(m.normalTexture)
            occlusionTexture = convert_texture_info(m.occlusionTexture)
            emissiveTexture = convert_texture_info(m.emissiveTexture)

            m2 = replace(
                m,
                normalTexture=normalTexture,
                occlusionTexture=occlusionTexture,
                emissiveTexture=emissiveTexture,
                pbrMetallicRoughness=pbrMetallicRoughness,
            )
            model.materials.append(m2)

    def convert_node(n1: Node) -> Node:
        camera2 = add_if_not_none(n1.camera, off_cameras)
        mesh2 = add_if_not_none(n1.mesh, off_meshes)
        if n1.children is None:
            children2 = None
        else:
            children2 = [_ + off_nodes for _ in n1.children]
        return replace(n1, camera=camera2, mesh=mesh2, children=children2)

    if model2.nodes:
        n_: Node
        for n_ in model2.nodes:
            n2 = convert_node(n_)
            model.nodes.append(n2)

    def replace_attributes(a: Attributes) -> Attributes:
        # class Attributes:
        #     """
        #     Helper type for describing the attributes of a primitive. Each property corresponds to mesh
        #     attribute semantic and
        #     each value is the index of the accessor containing the attribute's data.
        #     """
        return Attributes(
            POSITION=add_if_not_none(a.POSITION, off_accessors),
            NORMAL=add_if_not_none(a.NORMAL, off_accessors),
            TANGENT=add_if_not_none(a.TANGENT, off_accessors),
            TEXCOORD_0=add_if_not_none(a.TEXCOORD_0, off_accessors),
            TEXCOORD_1=add_if_not_none(a.TEXCOORD_1, off_accessors),
            COLOR_0=add_if_not_none(a.COLOR_0, off_accessors),
            JOINTS_0=add_if_not_none(a.JOINTS_0, off_accessors),
            WEIGHTS_0=add_if_not_none(a.WEIGHTS_0, off_accessors),
        )

    def replace_primitives(p: Primitive) -> Primitive:
        # TODO: targets
        attributes = replace_attributes(p.attributes)
        indices = add_if_not_none(p.indices, off_accessors)
        material = add_if_not_none(p.material, off_materials)
        return replace(p, attributes=attributes, indices=indices, material=material)

    if model2.meshes:
        mesh: Mesh
        for mesh in model2.meshes:
            primitives = [replace_primitives(_) for _ in mesh.primitives]
            mesh = replace(mesh, primitives=primitives)
            model.meshes.append(mesh)

    if model2.textures:
        tex: Texture
        for tex in model2.textures:
            sampler = add_if_not_none(tex.sampler, off_samplers)
            source = add_if_not_none(tex.source, off_images)
            tex2 = replace(tex, sampler=sampler, source=source)
            model.textures.append(tex2)

    if model2.accessors:
        a: Accessor
        for a in model2.accessors:
            bufferView = add_if_not_none(a.bufferView, off_bufferViews)
            a2 = replace(a, bufferView=bufferView)
            model.accessors.append(a2)

    assert len(model2.scenes) == 1, model2
    # scene: Scene
    # for scene in model2.scenes:
    #     nodes = [add_if_not_none(_, off_nodes) for _ in scene.nodes]
    #     scene2 = replace(scene, nodes=nodes)
    #     model.scenes.append(scene2)
    scene0 = model2.scenes[0]
    scene_nodes = [add_if_not_none(_, off_nodes) for _ in scene0.nodes]

    node = Node(children=scene_nodes)

    node_index = add_node(gltf, node)
    # logger.debug(f"the main scene node for imported is {node_index}")
    # model.scenes[0].nodes.extend(nodes)

    gltf.resources.extend(g2.resources)
    check_loops(gltf)
    return node_index
Beispiel #6
0
def export_gltf(dm: DuckietownMap, outdir: str, background: bool = True):
    gltf = GLTF()
    # setattr(gltf, 'md52PV', {})

    scene_index = add_scene(gltf, Scene(nodes=[]))

    map_nodes = []
    it: IterateByTestResult

    tiles = list(iterate_by_class(dm, Tile))
    if not tiles:
        raise ZValueError("no tiles?")
    for it in tiles:
        tile = cast(Tile, it.object)
        name = it.fqn[-1]
        fn = tile.fn
        fn_normal = tile.fn_normal
        fn_emissive = tile.fn_emissive
        fn_metallic_roughness = tile.fn_metallic_roughness
        fn_occlusion = tile.fn_occlusion
        material_index = make_material(
            gltf,
            doubleSided=False,
            baseColorFactor=[1, 1, 1, 1.0],
            fn=fn,
            fn_normals=fn_normal,
            fn_emissive=fn_emissive,
            fn_metallic_roughness=fn_metallic_roughness,
            fn_occlusion=fn_occlusion,
        )
        mi = get_square()
        mesh_index = add_polygon(
            gltf,
            name + "-mesh",
            vertices=mi.vertices,
            texture=mi.textures,
            colors=mi.color,
            normals=mi.normals,
            material=material_index,
        )
        node1_index = add_node(gltf, Node(mesh=mesh_index))

        i, j = ij_from_tilename(name)
        c = (i + j) % 2
        color = [1, 0, 0, 1.0] if c else [0, 1, 0, 1.0]
        add_back = False
        if add_back:

            material_back = make_material(gltf, doubleSided=False, baseColorFactor=color)
            back_mesh_index = add_polygon(
                gltf,
                name + "-mesh",
                vertices=mi.vertices,
                texture=mi.textures,
                colors=mi.color,
                normals=mi.normals,
                material=material_back,
            )

            flip = np.diag([1.0, 1.0, -1.0, 1.0])
            flip[2, 3] = -0.01
            back_index = add_node(gltf, Node(mesh=back_mesh_index, matrix=gm(flip)))
        else:
            back_index = None

        tile_transform = it.transform_sequence
        tile_matrix2d = tile_transform.asmatrix2d().m
        s = dm.tile_size
        scale = np.diag([s, s, s, 1])
        tile_matrix = SE3_from_SE2(tile_matrix2d)
        tile_matrix = tile_matrix @ scale @ SE3_rotz(-np.pi / 2)

        tile_matrix_float = list(tile_matrix.T.flatten())
        if back_index is not None:
            children = [node1_index, back_index]
        else:
            children = [node1_index]
        tile_node = Node(name=name, matrix=tile_matrix_float, children=children)
        tile_node_index = add_node(gltf, tile_node)

        map_nodes.append(tile_node_index)

    if background:
        bg_index = add_background(gltf)
        add_node_to_scene(gltf, scene_index, bg_index)

    exports = {
        "Sign": export_sign,
        # XXX: the tree model is crewed up
        "Tree": export_tree,
        # "Tree": None,
        "Duckie": export_duckie,
        "DB18": export_DB18,
        # "Bus": export_bus,
        # "Truck": export_truck,
        # "House": export_house,
        "TileMap": None,
        "TrafficLight": export_trafficlight,
        "LaneSegment": None,
        "PlacedObject": None,
        "DuckietownMap": None,
        "Tile": None,
    }

    for it in iterate_by_class(dm, PlacedObject):
        ob = it.object

        K = type(ob).__name__
        if isinstance(ob, Sign):
            K = "Sign"

        if K not in exports:
            logger.warn(f"cannot convert {type(ob).__name__}")
            continue

        f = exports[K]
        if f is None:
            continue
        thing_index = f(gltf, it.fqn[-1], ob)

        tile_transform = it.transform_sequence
        tile_matrix2d = tile_transform.asmatrix2d().m
        tile_matrix = SE3_from_SE2(tile_matrix2d) @ SE3_rotz(np.pi / 2)
        sign_node_index = add_node(gltf, Node(children=[thing_index]))
        tile_matrix_float = list(tile_matrix.T.flatten())
        tile_node = Node(name=it.fqn[-1], matrix=tile_matrix_float, children=[sign_node_index])
        tile_node_index = add_node(gltf, tile_node)
        map_nodes.append(tile_node_index)

    mapnode = Node(name="tiles", children=map_nodes)
    map_index = add_node(gltf, mapnode)
    add_node_to_scene(gltf, scene_index, map_index)

    # add_node_to_scene(model, scene_index, node1_index)
    yfov = np.deg2rad(60)
    camera = Camera(
        name="perpcamera",
        type="perspective",
        perspective=PerspectiveCameraInfo(aspectRatio=4 / 3, yfov=yfov, znear=0.01, zfar=1000),
    )
    gltf.model.cameras.append(camera)

    t = np.array([2, 2, 0.15])
    matrix = look_at(pos=t, target=np.array([0, 2, 0]))
    cam_index = add_node(gltf, Node(name="cameranode", camera=0, matrix=list(matrix.T.flatten())))
    add_node_to_scene(gltf, scene_index, cam_index)

    cleanup_model(gltf)

    fn = os.path.join(outdir, "main.gltf")
    make_sure_dir_exists(fn)
    logger.info(f"writing to {fn}")
    gltf.export(fn)
    if True:
        data = read_ustring_from_utf8_file(fn)

        j = json.loads(data)
        data2 = json.dumps(j, indent=2)
        write_ustring_to_utf8_file(data2, fn)

    fnb = os.path.join(outdir, "main.glb")
    logger.info(f"writing to {fnb}")
    gltf.export(fnb)
    verify_trimesh = False
    if verify_trimesh:
        res = trimesh.load(fn)
        # camera_pose, _ = res.graph['cameranode']
        # logger.info(res=res)
        import pyrender

        scene = pyrender.Scene.from_trimesh_scene(res)
Beispiel #7
0
def export_gltf(icon, filename, metadata=None):
    basename = PurePath(filename).stem

    vertex_info_format = ("3f" * icon.animation_shapes) + "3f 2f 3f"
    float_size = struct.calcsize("f")
    animation_speed = 0.1

    animation_present = icon.animation_shapes > 1

    model_data = bytearray()

    mins = {}
    maxs = {}

    for i, vertex in enumerate(icon.vertices):
        for j, position in enumerate(vertex.positions):
            if j == 0:
                values_basis = [
                    position.x / 4096, -position.y / 4096, -position.z / 4096
                ]
                values = values_basis
            else:
                # Subtract basis position to compensate for shape keys being relative to basis
                values = [
                    position.x / 4096 - values_basis[0],
                    -position.y / 4096 - values_basis[1],
                    -position.z / 4096 - values_basis[2]
                ]

            if j not in mins:
                mins[j] = values.copy()
            else:
                if values[0] < mins[j][0]: mins[j][0] = values[0]
                if values[1] < mins[j][1]: mins[j][1] = values[1]
                if values[2] < mins[j][2]: mins[j][2] = values[2]

            if j not in maxs:
                maxs[j] = values.copy()
            else:
                if values[0] > maxs[j][0]: maxs[j][0] = values[0]
                if values[1] > maxs[j][1]: maxs[j][1] = values[1]
                if values[2] > maxs[j][2]: maxs[j][2] = values[2]

            model_data.extend(struct.pack("3f", *values))

        model_data.extend(
            struct.pack("3f 2f 3f", vertex.normal.x / 4096,
                        -vertex.normal.y / 4096, -vertex.normal.z / 4096,
                        1.0 - (vertex.tex_coord.u / 4096),
                        1.0 - (vertex.tex_coord.v / 4096),
                        vertex.color.r / 255, vertex.color.g / 255,
                        vertex.color.b / 255))

    # Generate animation data if multiple animation shapes are present

    if animation_present:
        animation_offset = len(model_data)

        for i in range(icon.frame_count + 1):
            model_data.extend(struct.pack("f", i * animation_speed))

        for i, frame in enumerate(icon.frames + [icon.frames[0]]):
            segment = [struct.pack("f", 0.0)] * (icon.animation_shapes - 1)

            if frame.shape_id != 0:
                segment[frame.shape_id - 1] = struct.pack("f", 1.0)

            for item in segment:
                model_data.extend(item)

        animation_length = len(model_data) - animation_offset

    # Generate texture

    if isinstance(icon.texture, Ps2ico.CompressedTexture):
        image_data = convert_compressed_texture_data(icon.texture.size,
                                                     icon.texture.data)
    elif isinstance(icon.texture, Ps2ico.UncompressedTexture):
        image_data = convert_uncompressed_texture_data(icon.texture.data)

    with BytesIO() as png:
        PILImage.frombytes("RGB", (128, 128), image_data).save(png, "png")
        texture_data = png.getvalue()

    # Basic glTF info

    model = GLTFModel()

    model.asset = Asset(version="2.0", generator=f"ico2gltf v{VERSION}")

    model.scenes = [Scene(nodes=[0])]

    model.scene = 0

    model.nodes = [Node(mesh=0)]

    # If present, embed metadata

    if metadata:
        # Normalize title: turn japanese full-width characters into normal ones and insert the line break
        title = unicodedata.normalize("NFKC", metadata.title).rstrip("\x00")
        title = title[:metadata.offset_2nd_line //
                      2] + "\n" + title[metadata.offset_2nd_line // 2:]

        model.extras = {
            "title":
            title,
            "background_opacity":
            metadata.bg_opacity / 0x80,
            "background_bottom_left_color": [
                metadata.bg_color_lowerleft.r / 0x80,
                metadata.bg_color_lowerleft.g / 0x80,
                metadata.bg_color_lowerleft.b / 0x80,
                metadata.bg_color_lowerleft.a / 0x80
            ],
            "background_bottom_right_color": [
                metadata.bg_color_lowerright.r / 0x80,
                metadata.bg_color_lowerright.g / 0x80,
                metadata.bg_color_lowerright.b / 0x80,
                metadata.bg_color_lowerright.a / 0x80
            ],
            "background_top_left_color": [
                metadata.bg_color_upperleft.r / 0x80,
                metadata.bg_color_upperleft.g / 0x80,
                metadata.bg_color_upperleft.b / 0x80,
                metadata.bg_color_upperleft.a / 0x80
            ],
            "background_top_right_color": [
                metadata.bg_color_upperright.r / 0x80,
                metadata.bg_color_upperright.g / 0x80,
                metadata.bg_color_upperright.b / 0x80,
                metadata.bg_color_lowerright.a / 0x80
            ],
            "ambient_color": [
                metadata.light_ambient_color.r, metadata.light_ambient_color.g,
                metadata.light_ambient_color.b
            ],
            "light1_direction": [
                metadata.light1_direction.x, metadata.light1_direction.y,
                metadata.light1_direction.z
            ],
            "light1_color": [
                metadata.light1_color.r, metadata.light1_color.g,
                metadata.light1_color.b, metadata.light1_color.a
            ],
            "light2_direction": [
                metadata.light2_direction.x, metadata.light2_direction.y,
                metadata.light2_direction.z
            ],
            "light2_color": [
                metadata.light2_color.r, metadata.light2_color.g,
                metadata.light2_color.b, metadata.light2_color.a
            ],
            "light3_direction": [
                metadata.light3_direction.x, metadata.light3_direction.y,
                metadata.light3_direction.z
            ],
            "light3_color": [
                metadata.light3_color.r, metadata.light3_color.g,
                metadata.light3_color.b, metadata.light3_color.a
            ],
        }

    # Meshes

    primitive = Primitive(attributes=Attributes(
        POSITION=0,
        NORMAL=icon.animation_shapes,
        TEXCOORD_0=icon.animation_shapes + 1,
        COLOR_0=icon.animation_shapes + 2),
                          material=0)

    if animation_present:
        primitive.targets = [{
            "POSITION": i + 1
        } for i in range(icon.animation_shapes - 1)]

    model.meshes = [Mesh(name="Icon", primitives=[primitive])]

    # Buffers

    model.buffers = [
        Buffer(uri=f"{basename}.bin", byteLength=len(model_data)),
        Buffer(uri=f"{basename}.png", byteLength=len(texture_data))
    ]

    # Materials

    model.images = [Image(bufferView=1, mimeType="image/png")]

    model.textures = [Texture(source=0)]

    model.materials = [
        Material(name="Material",
                 pbrMetallicRoughness=PBRMetallicRoughness(
                     baseColorTexture=TextureInfo(index=0),
                     roughnessFactor=1,
                     metallicFactor=0))
    ]

    # Animations

    if animation_present:
        model.animations = [
            Animation(name="Default",
                      samplers=[
                          AnimationSampler(
                              input=icon.animation_shapes + 3,
                              output=icon.animation_shapes + 4,
                              interpolation=Interpolation.LINEAR.value)
                      ],
                      channels=[
                          Channel(sampler=0,
                                  target=Target(node=0, path="weights"))
                      ]),
        ]

    # Buffer Views

    model.bufferViews = [
        BufferView(name="Data",
                   buffer=0,
                   byteStride=struct.calcsize(vertex_info_format),
                   byteLength=len(model_data)),
        BufferView(name="Texture", buffer=1, byteLength=len(texture_data)),
    ]

    if animation_present:
        model.bufferViews.append(
            BufferView(name="Animation",
                       buffer=0,
                       byteOffset=animation_offset,
                       byteLength=animation_length), )

    # Accessors

    model.accessors = [
        Accessor(name=f"Position {i}",
                 bufferView=0,
                 byteOffset=i * 3 * float_size,
                 min=mins[i],
                 max=maxs[i],
                 count=len(icon.vertices),
                 componentType=ComponentType.FLOAT.value,
                 type=AccessorType.VEC3.value)
        for i in range(icon.animation_shapes)
    ]

    model.accessors.extend([
        Accessor(name="Normal",
                 bufferView=0,
                 byteOffset=((icon.animation_shapes - 1) * 3 * float_size) +
                 3 * float_size,
                 count=len(icon.vertices),
                 componentType=ComponentType.FLOAT.value,
                 type=AccessorType.VEC3.value),
        Accessor(name="UV",
                 bufferView=0,
                 byteOffset=((icon.animation_shapes - 1) * 3 * float_size) +
                 6 * float_size,
                 count=len(icon.vertices),
                 componentType=ComponentType.FLOAT.value,
                 type=AccessorType.VEC2.value),
        Accessor(name="Color",
                 bufferView=0,
                 byteOffset=((icon.animation_shapes - 1) * 3 * float_size) +
                 8 * float_size,
                 count=len(icon.vertices),
                 componentType=ComponentType.FLOAT.value,
                 type=AccessorType.VEC3.value),
    ])

    if animation_present:
        model.accessors.extend([
            Accessor(name="Animation Time",
                     bufferView=2,
                     byteOffset=0,
                     min=[0.0],
                     max=[(icon.frame_count) * animation_speed],
                     count=(icon.frame_count + 1),
                     componentType=ComponentType.FLOAT.value,
                     type=AccessorType.SCALAR.value),
            Accessor(name="Animation Data",
                     bufferView=2,
                     byteOffset=(icon.frame_count + 1) * float_size,
                     min=[0.0],
                     max=[1.0],
                     count=(icon.frame_count + 1) *
                     (icon.animation_shapes - 1),
                     componentType=ComponentType.FLOAT.value,
                     type=AccessorType.SCALAR.value)
        ])

    resources = [
        FileResource(f"{basename}.bin", data=model_data),
        FileResource(f"{basename}.png", data=texture_data)
    ]

    gltf = GLTF(model=model, resources=resources)
    gltf.export(filename)
def add_background(gltf: GLTF) -> int:
    model = gltf.model
    resources = gltf.resources
    root = "pannello %02d.pdf.jpg"
    found = []
    for i in range(1, 27):
        basename = root % i
        try:
            fn = get_resource_path(basename)
        except KeyError:
            logger.warn(f"not found {basename!r}")
        else:
            found.append(fn)

    found = found[:9]
    n = len(found)
    if n == 0:
        raise ValueError(root)
    from .export import make_material
    from .export import get_square
    from .export import add_polygon
    from .export import add_node
    from .export import gm

    dist = 30
    fov_y = np.deg2rad(45)
    nodes_panels = []
    for i, fn in enumerate(found):
        # if i > 5:
        #     break
        material_index = make_material(gltf,
                                       doubleSided=True,
                                       baseColorFactor=[0.5, 0.5, 0.5, 1.0],
                                       fn_emissive=fn
                                       # fn=fn, fn_normals=None
                                       )
        print(fn, material_index)

        mi = get_square()

        mesh_index = add_polygon(
            gltf,
            f"bg-{i}",
            vertices=mi.vertices,
            texture=mi.textures,
            colors=mi.color,
            normals=mi.normals,
            material=material_index,
        )
        v = (np.pi * 2) / n
        theta = v * i

        a = 2 * np.tan((2 * np.pi / n) / 2) * dist
        matrix = (SE3_rotz(theta) @ SE3_trans(np.array(
            (dist, 0, 0))) @ SE3_roty(np.pi / 2) @ SE3_rotz(np.pi / 2)
                  @ np.diag(np.array(
                      (a, a, 1, 1))) @ SE3_trans(np.array([0, 0.35, 0])))

        node1_index = add_node(
            gltf, Node(name=f"panel-{i}", mesh=mesh_index, matrix=gm(matrix)))
        nodes_panels.append(node1_index)
    node = Node(children=nodes_panels)
    return add_node(gltf, node)