modelio-rs 0.3.1

Safe Rust bindings for Apple's ModelIO framework — assets, meshes, materials, lights, cameras, voxels, textures, and animation on macOS
Documentation
import Foundation
import ModelIO
import simd

private let MDLX_OBJECT_KIND_UNKNOWN: Int32 = 0
private let MDLX_OBJECT_KIND_OBJECT: Int32 = 1
private let MDLX_OBJECT_KIND_MESH: Int32 = 2
private let MDLX_OBJECT_KIND_LIGHT: Int32 = 3
private let MDLX_OBJECT_KIND_PHYSICALLY_PLAUSIBLE_LIGHT: Int32 = 4
private let MDLX_OBJECT_KIND_CAMERA: Int32 = 5
private let MDLX_OBJECT_KIND_VOXEL_ARRAY: Int32 = 6
private let MDLX_OBJECT_KIND_SKELETON: Int32 = 7
private let MDLX_OBJECT_KIND_PACKED_JOINT_ANIMATION: Int32 = 8

private func mdl_object_kind_raw(_ object: MDLObject) -> Int32 {
    if object is MDLPhysicallyPlausibleLight {
        return MDLX_OBJECT_KIND_PHYSICALLY_PLAUSIBLE_LIGHT
    }
    if object is MDLCamera {
        return MDLX_OBJECT_KIND_CAMERA
    }
    if object is MDLVoxelArray {
        return MDLX_OBJECT_KIND_VOXEL_ARRAY
    }
    if object is MDLSkeleton {
        return MDLX_OBJECT_KIND_SKELETON
    }
    if object is MDLPackedJointAnimation {
        return MDLX_OBJECT_KIND_PACKED_JOINT_ANIMATION
    }
    if object is MDLLight {
        return MDLX_OBJECT_KIND_LIGHT
    }
    if object is MDLMesh {
        return MDLX_OBJECT_KIND_MESH
    }
    return MDLX_OBJECT_KIND_OBJECT
}

private func mdl_object_info(_ object: MDLObject) -> [String: Any] {
    [
        "kind": mdl_object_kind_raw(object),
        "name": object.name,
        "path": object.path,
        "hidden": object.hidden,
        "component_count": object.components.count,
        "child_count": object.children.count,
        "has_parent": object.parent != nil,
        "has_instance": object.instance != nil,
        "bounding_box": mdl_bounding_box(object.boundingBox(atTime: 0)),
    ]
}

private func mdl_object_at_path_impl(_ object: MDLObject, _ path: String) -> MDLObject? {
    let selector = NSSelectorFromString("objectAtPath:")
    guard object.responds(to: selector),
          let unmanaged = object.perform(selector, with: NSString(string: path)),
          let resolved = unmanaged.takeUnretainedValue() as? MDLObject
    else {
        return nil
    }
    return resolved
}

@_cdecl("mdl_object_new")
public func mdl_object_new(
    _ outObject: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let outObject else {
            throw ModelIOBridgeError.invalidArgument("missing output object pointer")
        }
        outObject.pointee = mdl_retain(MDLObject())
    }
}

@_cdecl("mdl_object_info_json")
public func mdl_object_info_json(_ handle: UnsafeMutableRawPointer?) -> UnsafeMutablePointer<CChar>? {
    guard let object = mdl_borrow_object(handle) as? MDLObject else { return nil }
    return mdl_string(mdl_json_string(from: mdl_object_info(object)) ?? "{}")
}

@_cdecl("mdl_object_kind")
public func mdl_object_kind(_ handle: UnsafeMutableRawPointer?) -> Int32 {
    guard let object = mdl_borrow_object(handle) as? MDLObject else {
        return MDLX_OBJECT_KIND_UNKNOWN
    }
    return mdl_object_kind_raw(object)
}

@_cdecl("mdl_object_name_string")
public func mdl_object_name_string(_ handle: UnsafeMutableRawPointer?) -> UnsafeMutablePointer<CChar>? {
    guard let object = mdl_borrow_object(handle) as? MDLObject else { return nil }
    return mdl_string(object.name)
}

@_cdecl("mdl_object_set_name")
public func mdl_object_set_name(_ handle: UnsafeMutableRawPointer?, _ name: UnsafePointer<CChar>?) {
    guard let object = mdl_borrow_object(handle) as? MDLObject,
          let name
    else {
        return
    }
    object.name = String(cString: name)
}

@_cdecl("mdl_object_path_string")
public func mdl_object_path_string(_ handle: UnsafeMutableRawPointer?) -> UnsafeMutablePointer<CChar>? {
    guard let object = mdl_borrow_object(handle) as? MDLObject else { return nil }
    return mdl_string(object.path)
}

@_cdecl("mdl_object_hidden")
public func mdl_object_hidden(_ handle: UnsafeMutableRawPointer?) -> Int32 {
    guard let object = mdl_borrow_object(handle) as? MDLObject else { return 0 }
    return object.hidden ? 1 : 0
}

@_cdecl("mdl_object_set_hidden")
public func mdl_object_set_hidden(_ handle: UnsafeMutableRawPointer?, _ hidden: Int32) {
    guard let object = mdl_borrow_object(handle) as? MDLObject else { return }
    object.hidden = hidden != 0
}

@_cdecl("mdl_object_add_child")
public func mdl_object_add_child(_ handle: UnsafeMutableRawPointer?, _ childHandle: UnsafeMutableRawPointer?) {
    guard let object = mdl_borrow_object(handle) as? MDLObject,
          let child = mdl_borrow_object(childHandle) as? MDLObject
    else {
        return
    }
    object.addChild(child)
}

@_cdecl("mdl_object_children_container")
public func mdl_object_children_container(_ handle: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? {
    guard let object = mdl_borrow_object(handle) as? MDLObject,
          let container = object.children as? MDLObjectContainer
    else {
        return nil
    }
    return mdl_retain(container)
}

@_cdecl("mdl_object_set_children_container")
public func mdl_object_set_children_container(_ handle: UnsafeMutableRawPointer?, _ containerHandle: UnsafeMutableRawPointer?) {
    guard let object = mdl_borrow_object(handle) as? MDLObject else { return }
    object.children = (mdl_borrow_object(containerHandle) as? MDLObjectContainer) ?? MDLObjectContainer()
}

@_cdecl("mdl_object_child_count")
public func mdl_object_child_count(_ handle: UnsafeMutableRawPointer?) -> UInt64 {
    guard let object = mdl_borrow_object(handle) as? MDLObject else { return 0 }
    return UInt64(object.children.count)
}

@_cdecl("mdl_object_child_at")
public func mdl_object_child_at(_ handle: UnsafeMutableRawPointer?, _ index: UInt64) -> UnsafeMutableRawPointer? {
    guard let object = mdl_borrow_object(handle) as? MDLObject,
          index < UInt64(object.children.count)
    else {
        return nil
    }
    return mdl_retain(object.children[Int(index)])
}

@_cdecl("mdl_object_at_path")
public func mdl_object_at_path(_ handle: UnsafeMutableRawPointer?, _ path: UnsafePointer<CChar>?) -> UnsafeMutableRawPointer? {
    guard let object = mdl_borrow_object(handle) as? MDLObject,
          let path
    else {
        return nil
    }

    let query = String(cString: path)
    var candidates: [String] = [query]

    if query.hasPrefix(object.path + "/") {
        candidates.append(String(query.dropFirst(object.path.count + 1)))
    }
    if query.hasPrefix("/") {
        candidates.append(String(query.dropFirst()))
    }
    if let lastComponent = query.split(separator: "/").last {
        candidates.append(String(lastComponent))
    }

    for candidate in candidates where !candidate.isEmpty {
        if let resolved = mdl_object_at_path_impl(object, candidate) {
            return mdl_retain(resolved)
        }
    }

    return nil
}

@_cdecl("mdl_object_bounding_box_at_time")
public func mdl_object_bounding_box_at_time(
    _ handle: UnsafeMutableRawPointer?,
    _ time: Double,
    _ outMinX: UnsafeMutablePointer<Float>?,
    _ outMinY: UnsafeMutablePointer<Float>?,
    _ outMinZ: UnsafeMutablePointer<Float>?,
    _ outMaxX: UnsafeMutablePointer<Float>?,
    _ outMaxY: UnsafeMutablePointer<Float>?,
    _ outMaxZ: UnsafeMutablePointer<Float>?
) {
    let zero = MDLAxisAlignedBoundingBox(maxBounds: SIMD3<Float>(repeating: 0), minBounds: SIMD3<Float>(repeating: 0))
    let boundingBox = (mdl_borrow_object(handle) as? MDLObject)?.boundingBox(atTime: time) ?? zero
    outMinX?.pointee = boundingBox.minBounds.x
    outMinY?.pointee = boundingBox.minBounds.y
    outMinZ?.pointee = boundingBox.minBounds.z
    outMaxX?.pointee = boundingBox.maxBounds.x
    outMaxY?.pointee = boundingBox.maxBounds.y
    outMaxZ?.pointee = boundingBox.maxBounds.z
}

@_cdecl("mdl_object_container_new")
public func mdl_object_container_new(
    _ outContainer: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let outContainer else {
            throw ModelIOBridgeError.invalidArgument("missing output object container pointer")
        }
        outContainer.pointee = mdl_retain(MDLObjectContainer())
    }
}

@_cdecl("mdl_object_container_count")
public func mdl_object_container_count(_ handle: UnsafeMutableRawPointer?) -> UInt64 {
    guard let container = mdl_borrow_object(handle) as? MDLObjectContainer else { return 0 }
    return UInt64(container.count)
}

@_cdecl("mdl_object_container_object_at")
public func mdl_object_container_object_at(_ handle: UnsafeMutableRawPointer?, _ index: UInt64) -> UnsafeMutableRawPointer? {
    guard let container = mdl_borrow_object(handle) as? MDLObjectContainer,
          index < UInt64(container.count)
    else {
        return nil
    }
    return mdl_retain(container.objects[Int(index)])
}

@_cdecl("mdl_object_container_add_object")
public func mdl_object_container_add_object(_ handle: UnsafeMutableRawPointer?, _ objectHandle: UnsafeMutableRawPointer?) {
    guard let container = mdl_borrow_object(handle) as? MDLObjectContainer,
          let object = mdl_borrow_object(objectHandle) as? MDLObject
    else {
        return
    }
    container.add(object)
}

@_cdecl("mdl_object_container_remove_object")
public func mdl_object_container_remove_object(_ handle: UnsafeMutableRawPointer?, _ objectHandle: UnsafeMutableRawPointer?) {
    guard let container = mdl_borrow_object(handle) as? MDLObjectContainer,
          let object = mdl_borrow_object(objectHandle) as? MDLObject
    else {
        return
    }
    container.remove(object)
}