modelio-rs 0.3.0

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

private func mdl_texture_info(_ texture: MDLTexture) -> [String: Any] {
    var info: [String: Any] = [
        "name": texture.name,
        "dimensions": [texture.dimensions.x, texture.dimensions.y],
        "row_stride": texture.rowStride,
        "channel_count": texture.channelCount,
        "mip_level_count": texture.mipLevelCount,
        "channel_encoding": texture.channelEncoding.rawValue,
        "is_cube": texture.isCube,
        "has_alpha_values": texture.hasAlphaValues,
    ]
    if let urlTexture = texture as? MDLURLTexture {
        info["url"] = urlTexture.url.absoluteString
    }
    return info
}

@_cdecl("mdl_url_texture_new")
public func mdl_url_texture_new(
    _ path: UnsafePointer<CChar>?,
    _ name: UnsafePointer<CChar>?,
    _ outTexture: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let path, let outTexture else {
            throw ModelIOBridgeError.invalidArgument("missing texture path or output pointer")
        }
        let url = URL(fileURLWithPath: String(cString: path))
        let textureName = name.map { String(cString: $0) }
        outTexture.pointee = mdl_retain(MDLURLTexture(url: url, name: textureName))
    }
}

@_cdecl("mdl_checkerboard_texture_new")
public func mdl_checkerboard_texture_new(
    _ divisions: Float,
    _ name: UnsafePointer<CChar>?,
    _ width: Int32,
    _ height: Int32,
    _ channelCount: UInt64,
    _ channelEncodingRaw: Int32,
    _ color1R: Float,
    _ color1G: Float,
    _ color1B: Float,
    _ color1A: Float,
    _ color2R: Float,
    _ color2G: Float,
    _ color2B: Float,
    _ color2A: Float,
    _ outTexture: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let outTexture else {
            throw ModelIOBridgeError.invalidArgument("missing output texture pointer")
        }
        let checkerboard = MDLCheckerboardTexture(
            divisions: divisions,
            name: name.map { String(cString: $0) },
            dimensions: vector_int2(width, height),
            channelCount: Int32(channelCount),
            channelEncoding: try mdl_texture_channel_encoding(channelEncodingRaw),
            color1: mdl_color(color1R, color1G, color1B, color1A),
            color2: mdl_color(color2R, color2G, color2B, color2A)
        )
        outTexture.pointee = mdl_retain(checkerboard)
    }
}

@_cdecl("mdl_color_swatch_texture_new_temperature_gradient")
public func mdl_color_swatch_texture_new_temperature_gradient(
    _ colorTemperature1: Float,
    _ colorTemperature2: Float,
    _ name: UnsafePointer<CChar>?,
    _ width: Int32,
    _ height: Int32,
    _ outTexture: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let outTexture else {
            throw ModelIOBridgeError.invalidArgument("missing output texture pointer")
        }
        outTexture.pointee = mdl_retain(
            MDLColorSwatchTexture(
                colorTemperatureGradientFrom: colorTemperature1,
                toColorTemperature: colorTemperature2,
                name: name.map { String(cString: $0) },
                textureDimensions: vector_int2(width, height)
            )
        )
    }
}

@_cdecl("mdl_color_swatch_texture_new_color_gradient")
public func mdl_color_swatch_texture_new_color_gradient(
    _ color1R: Float,
    _ color1G: Float,
    _ color1B: Float,
    _ color1A: Float,
    _ color2R: Float,
    _ color2G: Float,
    _ color2B: Float,
    _ color2A: Float,
    _ name: UnsafePointer<CChar>?,
    _ width: Int32,
    _ height: Int32,
    _ outTexture: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let outTexture else {
            throw ModelIOBridgeError.invalidArgument("missing output texture pointer")
        }
        outTexture.pointee = mdl_retain(
            MDLColorSwatchTexture(
                colorGradientFrom: mdl_color(color1R, color1G, color1B, color1A),
                to: mdl_color(color2R, color2G, color2B, color2A),
                name: name.map { String(cString: $0) },
                textureDimensions: vector_int2(width, height)
            )
        )
    }
}

@_cdecl("mdl_noise_texture_new_vector")
public func mdl_noise_texture_new_vector(
    _ smoothness: Float,
    _ name: UnsafePointer<CChar>?,
    _ width: Int32,
    _ height: Int32,
    _ channelEncodingRaw: Int32,
    _ outTexture: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let outTexture else {
            throw ModelIOBridgeError.invalidArgument("missing output texture pointer")
        }
        outTexture.pointee = mdl_retain(
            MDLNoiseTexture(
                vectorNoiseWithSmoothness: smoothness,
                name: name.map { String(cString: $0) },
                textureDimensions: vector_int2(width, height),
                channelEncoding: try mdl_texture_channel_encoding(channelEncodingRaw)
            )
        )
    }
}

@_cdecl("mdl_noise_texture_new_scalar")
public func mdl_noise_texture_new_scalar(
    _ smoothness: Float,
    _ name: UnsafePointer<CChar>?,
    _ width: Int32,
    _ height: Int32,
    _ channelCount: UInt64,
    _ channelEncodingRaw: Int32,
    _ grayscale: Int32,
    _ outTexture: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let outTexture else {
            throw ModelIOBridgeError.invalidArgument("missing output texture pointer")
        }
        outTexture.pointee = mdl_retain(
            MDLNoiseTexture(
                scalarNoiseWithSmoothness: smoothness,
                name: name.map { String(cString: $0) },
                textureDimensions: vector_int2(width, height),
                channelCount: Int32(channelCount),
                channelEncoding: try mdl_texture_channel_encoding(channelEncodingRaw),
                grayscale: grayscale != 0
            )
        )
    }
}

@_cdecl("mdl_noise_texture_new_cellular")
public func mdl_noise_texture_new_cellular(
    _ frequency: Float,
    _ name: UnsafePointer<CChar>?,
    _ width: Int32,
    _ height: Int32,
    _ channelEncodingRaw: Int32,
    _ outTexture: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let outTexture else {
            throw ModelIOBridgeError.invalidArgument("missing output texture pointer")
        }
        outTexture.pointee = mdl_retain(
            MDLNoiseTexture(
                cellularNoiseWithFrequency: frequency,
                name: name.map { String(cString: $0) },
                textureDimensions: vector_int2(width, height),
                channelEncoding: try mdl_texture_channel_encoding(channelEncodingRaw)
            )
        )
    }
}

@_cdecl("mdl_normal_map_texture_new")
public func mdl_normal_map_texture_new(
    _ sourceTextureHandle: UnsafeMutableRawPointer?,
    _ name: UnsafePointer<CChar>?,
    _ smoothness: Float,
    _ contrast: Float,
    _ outTexture: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let sourceTexture = mdl_borrow_object(sourceTextureHandle) as? MDLTexture,
              let outTexture
        else {
            throw ModelIOBridgeError.invalidArgument("missing source texture or output pointer")
        }
        outTexture.pointee = mdl_retain(
            MDLNormalMapTexture(
                byGeneratingNormalMapWith: sourceTexture,
                name: name.map { String(cString: $0) },
                smoothness: smoothness,
                contrast: contrast
            )
        )
    }
}

@_cdecl("mdl_sky_cube_texture_new")
public func mdl_sky_cube_texture_new(
    _ name: UnsafePointer<CChar>?,
    _ channelEncodingRaw: Int32,
    _ width: Int32,
    _ height: Int32,
    _ turbidity: Float,
    _ sunElevation: Float,
    _ upperAtmosphereScattering: Float,
    _ groundAlbedo: Float,
    _ outTexture: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let outTexture else {
            throw ModelIOBridgeError.invalidArgument("missing output texture pointer")
        }
        outTexture.pointee = mdl_retain(
            MDLSkyCubeTexture(
                name: name.map { String(cString: $0) },
                channelEncoding: try mdl_texture_channel_encoding(channelEncodingRaw),
                textureDimensions: vector_int2(width, height),
                turbidity: turbidity,
                sunElevation: sunElevation,
                upperAtmosphereScattering: upperAtmosphereScattering,
                groundAlbedo: groundAlbedo
            )
        )
    }
}

@_cdecl("mdl_sky_cube_texture_new_with_azimuth")
public func mdl_sky_cube_texture_new_with_azimuth(
    _ name: UnsafePointer<CChar>?,
    _ channelEncodingRaw: Int32,
    _ width: Int32,
    _ height: Int32,
    _ turbidity: Float,
    _ sunElevation: Float,
    _ sunAzimuth: Float,
    _ upperAtmosphereScattering: Float,
    _ groundAlbedo: Float,
    _ outTexture: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let outTexture else {
            throw ModelIOBridgeError.invalidArgument("missing output texture pointer")
        }
        outTexture.pointee = mdl_retain(
            MDLSkyCubeTexture(
                name: name.map { String(cString: $0) },
                channelEncoding: try mdl_texture_channel_encoding(channelEncodingRaw),
                textureDimensions: vector_int2(width, height),
                turbidity: turbidity,
                sunElevation: sunElevation,
                sunAzimuth: sunAzimuth,
                upperAtmosphereScattering: upperAtmosphereScattering,
                groundAlbedo: groundAlbedo
            )
        )
    }
}

@_cdecl("mdl_sky_cube_texture_update")
public func mdl_sky_cube_texture_update(_ handle: UnsafeMutableRawPointer?) {
    guard let texture = mdl_borrow_object(handle) as? MDLSkyCubeTexture else { return }
    texture.update()
}

@_cdecl("mdl_texture_info_json")
public func mdl_texture_info_json(_ handle: UnsafeMutableRawPointer?) -> UnsafeMutablePointer<CChar>? {
    guard let texture = mdl_borrow_object(handle) as? MDLTexture else { return nil }
    return mdl_string(mdl_json_string(from: mdl_texture_info(texture)) ?? "{}")
}

@_cdecl("mdl_texture_write_to_url")
public func mdl_texture_write_to_url(
    _ handle: UnsafeMutableRawPointer?,
    _ path: UnsafePointer<CChar>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    mdl_run(outError) {
        guard let texture = mdl_borrow_object(handle) as? MDLTexture,
              let path
        else {
            throw ModelIOBridgeError.invalidArgument("missing texture or output path")
        }
        let url = URL(fileURLWithPath: String(cString: path))
        guard texture.write(to: url) else {
            throw ModelIOBridgeError.framework(NSError(domain: "modelio", code: -1, userInfo: [NSLocalizedDescriptionKey: "texture write failed"]))
        }
    }
}

@_cdecl("mdl_texture_texel_data_length")
public func mdl_texture_texel_data_length(
    _ handle: UnsafeMutableRawPointer?,
    _ topLeftOrigin: Int32
) -> UInt64 {
    guard let texture = mdl_borrow_object(handle) as? MDLTexture else { return 0 }
    let data = topLeftOrigin != 0 ? texture.texelDataWithTopLeftOrigin() : texture.texelDataWithBottomLeftOrigin()
    return UInt64(data?.count ?? 0)
}

@_cdecl("mdl_texture_copy_texel_data")
public func mdl_texture_copy_texel_data(
    _ handle: UnsafeMutableRawPointer?,
    _ topLeftOrigin: Int32,
    _ outBytes: UnsafeMutablePointer<UInt8>?,
    _ capacity: UInt64
) -> UInt64 {
    guard let texture = mdl_borrow_object(handle) as? MDLTexture,
          let outBytes,
          let data = topLeftOrigin != 0 ? texture.texelDataWithTopLeftOrigin() : texture.texelDataWithBottomLeftOrigin()
    else {
        return 0
    }
    let byteCount = min(Int(capacity), data.count)
    data.copyBytes(to: outBytes, count: byteCount)
    return UInt64(byteCount)
}