import Flutter import UIKit import OpenGLES.ES3 import GLKit public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture { var registrar : FlutterPluginRegistrar var textureId: Int64? var registry: FlutterTextureRegistry var width: Double = 0 var height: Double = 0 var context: EAGLContext?; var targetPixelBuffer: CVPixelBuffer?; var textureCache: CVOpenGLESTextureCache?; var texture: CVOpenGLESTexture? = nil; var frameBuffer: GLuint = 0; var pixelBufferAttrs = [ kCVPixelBufferPixelFormatTypeKey: NSNumber(value: kCVPixelFormatType_32BGRA), kCVPixelBufferOpenGLCompatibilityKey: kCFBooleanTrue, kCVPixelBufferOpenGLESCompatibilityKey: kCFBooleanTrue, kCVPixelBufferIOSurfacePropertiesKey: [:] ] as CFDictionary var resources:NSMutableDictionary = [:] var viewer:UnsafeMutableRawPointer? = nil var displayLink:CADisplayLink? = nil static var messenger : FlutterBinaryMessenger? = nil; var loadResourcePtr: UnsafeMutableRawPointer? = nil var freeResourcePtr: UnsafeMutableRawPointer? = nil var resourcesPtr : UnsafeMutableRawPointer? = nil var _rendering = true var loadResource : @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer) -> ResourceBuffer = { uri, resourcesPtr in let instance:SwiftPolyvoxFilamentPlugin = Unmanaged.fromOpaque(resourcesPtr).takeUnretainedValue() let uriString = String(cString:uri.assumingMemoryBound(to: UInt8.self)) var path:String? = nil if(uriString.hasPrefix("file://")) { path = String(uriString.dropFirst(7)) } else { let key = instance.registrar.lookupKey(forAsset:uriString) path = Bundle.main.path(forResource: key, ofType:nil) guard path != nil else { print("File not present in bundle : \(uri)") return ResourceBuffer() } } do { let data = try Data(contentsOf: URL(fileURLWithPath:path!)) let resId = instance.resources.count let nsData = data as NSData instance.resources[resId] = nsData let rawPtr = nsData.bytes return ResourceBuffer(data:rawPtr, size:UInt32(nsData.count), id:UInt32(resId)) } catch { print("Error opening file: \(error)") return ResourceBuffer() } return ResourceBuffer() } var freeResource : @convention(c) (UInt32,UnsafeMutableRawPointer) -> () = { rid, resourcesPtr in let instance:SwiftPolyvoxFilamentPlugin = Unmanaged.fromOpaque(resourcesPtr).takeUnretainedValue() instance.resources.removeObject(forKey:rid) } func createDisplayLink() { displayLink = CADisplayLink(target: self, selector: #selector(doRender)) displayLink!.add(to: .current, forMode: RunLoop.Mode.default) } @objc func doRender() { if(viewer != nil && _rendering) { render(viewer, 0) self.registry.textureFrameAvailable(self.textureId!) } } public func copyPixelBuffer() -> Unmanaged? { if(targetPixelBuffer == nil) { print("empty") return nil; } return Unmanaged.passRetained(targetPixelBuffer!); } public static func register(with registrar: FlutterPluginRegistrar) { let _messenger = registrar.messenger(); messenger = _messenger; let channel = FlutterMethodChannel(name: "app.polyvox.filament/event", binaryMessenger: _messenger) let instance = SwiftPolyvoxFilamentPlugin(textureRegistry: registrar.textures(), registrar:registrar) registrar.addMethodCallDelegate(instance, channel: channel) } init(textureRegistry: FlutterTextureRegistry, registrar:FlutterPluginRegistrar) { self.registry = textureRegistry; self.registrar = registrar } private func createPixelBuffer(width:Int, height:Int) { if(targetPixelBuffer != nil) { destroy_swap_chain(self.viewer) } if(CVPixelBufferCreate(kCFAllocatorDefault, Int(width), Int(height), kCVPixelFormatType_32BGRA, pixelBufferAttrs, &targetPixelBuffer) != kCVReturnSuccess) { print("Error allocating pixel buffer") } print("Pixel buffer created") } private func initialize(width:Int32, height:Int32) { createPixelBuffer(width:Int(width), height:Int(height)) self.textureId = self.registry.register(self) loadResourcePtr = unsafeBitCast(loadResource, to: UnsafeMutableRawPointer.self) freeResourcePtr = unsafeBitCast(freeResource, to: UnsafeMutableRawPointer.self) viewer = filament_viewer_new_ios( nil, loadResourcePtr!, freeResourcePtr!, Unmanaged.passUnretained(self).toOpaque() ) create_swap_chain( self.viewer, unsafeBitCast(targetPixelBuffer!, to: UnsafeMutableRawPointer.self), UInt32(width), UInt32(height)) update_viewport_and_camera_projection(self.viewer!, Int32(width), Int32(height), 1.0); createDisplayLink() } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { let methodName = call.method; switch methodName { case "addLight": let args = call.arguments as! Array let entity = add_light( self.viewer, args[0] as! UInt8, Float(args[1] as! Double), Float(args[2] as! Double), Float(args[3] as! Double), Float(args[4] as! Double), Float(args[5] as! Double), Float(args[6] as! Double), Float(args[7] as! Double), Float(args[8] as! Double), args[9] as! Bool) result(entity); case "setAnimation": let args = call.arguments as! Array let assetPtr = UnsafeMutableRawPointer.init(bitPattern: args[0] as! Int) let frameData = args[1] as! Array let numWeights = args[2] as! Int let numFrames = args[3] as! Int let frameLenInMs = args[4] as! Double frameData.map { Float($0)}.withUnsafeBufferPointer { set_animation(assetPtr, UnsafeMutablePointer.init(mutating:$0.baseAddress), Int32(numWeights), Int32(numFrames), Float(frameLenInMs)) } result("OK") case "initialize": let args = call.arguments as! Array initialize(width:args[0], height:args[1]) result(self.textureId); case "clearLights": clear_lights(self.viewer); result(true); case "loadSkybox": load_skybox(self.viewer!, call.arguments as! String) result("OK"); case "removeSkybox": remove_skybox(self.viewer!) result("OK"); case "loadGlb": let assetPtr = load_glb(self.viewer, call.arguments as! String) result(unsafeBitCast(assetPtr, to:Int64.self)); case "loadGltf": let args = call.arguments as! Array result(load_gltf(self.viewer, args[0] as! String, args[1] as! String)); case "removeAsset": let assetPtr = UnsafeMutableRawPointer.init(bitPattern: call.arguments as! Int) remove_asset(viewer!, assetPtr) result("OK") case "clearAssets": clear_assets(viewer!) result("OK") case "loadIbl": load_ibl(self.viewer, call.arguments as! String) result("OK"); case "removeIbl": remove_ibl(self.viewer) result("OK"); case "setCamera": let args = call.arguments as! Array let assetPtr = UnsafeMutableRawPointer.init(bitPattern: args[0] as! Int) set_camera(self.viewer, assetPtr, args[1] as! String) result("OK"); case "playAnimation": let args = call.arguments as! Array let assetPtr = UnsafeMutableRawPointer.init(bitPattern: args[0] as! Int) let animationIndex = args[1] as! Int32; let loop = args[2] as! Bool; let reverse = args[3] as! Bool; play_animation(assetPtr, animationIndex, loop, reverse) result("OK"); case "stopAnimation": let args = call.arguments as! Array let assetPtr = UnsafeMutableRawPointer.init(bitPattern: args[0] as! Int) let animationIndex = args[1] as! Int32 stop_animation(assetPtr, animationIndex) // TODO result("OK"); case "getMorphTargetNames": let args = call.arguments as! Array let assetPtr = UnsafeMutableRawPointer.init(bitPattern: args[0] as! Int) let meshName = args[1] as! String let numNames = get_morph_target_name_count(assetPtr, meshName) var names = [String]() for i in 0...numNames - 1{ let outPtr = UnsafeMutablePointer.allocate(capacity:256) get_morph_target_name(assetPtr, meshName, outPtr, i) names.append(String(cString:outPtr)) } result(names); case "getAnimationNames": let assetPtr = UnsafeMutableRawPointer.init(bitPattern: call.arguments as! Int) let numNames = get_animation_count(assetPtr) var names = [String]() for i in 0...allocate(capacity:256) get_animation_name(assetPtr, outPtr, i) names.append(String(cString:outPtr)) } result(names); case "setMorphTargetWeights": let args = call.arguments as! Array let assetPtr = UnsafeMutableRawPointer.init(bitPattern: args[0] as! Int) let weights = args[1] as! Array weights.map { Float($0) }.withUnsafeBufferPointer { apply_weights(assetPtr, UnsafeMutablePointer.init(mutating:$0.baseAddress), Int32(weights.count)) } result("OK") case "panStart": let args = call.arguments as! Array grab_begin(self.viewer, Float(args[0] as! Double), Float(args[1] as! Double), true) result("OK") case "panUpdate": let args = call.arguments as! Array grab_update(self.viewer, Float(args[0] as! Double), Float(args[1] as! Double)) result("OK") case "panEnd": grab_end(self.viewer) result("OK") case "removeLight": remove_light(self.viewer,call.arguments as! Int32) result(true); case "render": doRender() result("OK") case "resize": let args = call.arguments as! Array let width = Int(args[0]) let height = Int(args[1]) createPixelBuffer(width: width, height:height) create_swap_chain( self.viewer, unsafeBitCast(targetPixelBuffer!, to: UnsafeMutableRawPointer.self), UInt32(width), UInt32(height)) result("OK") case "rotateStart": let args = call.arguments as! Array grab_begin(self.viewer, Float(args[0] as! Double), Float(args[1] as! Double), false) result("OK") case "rotateUpdate": let args = call.arguments as! Array grab_update(self.viewer, Float(args[0] as! Double), Float(args[1] as! Double)) result("OK") case "rotateEnd": grab_end(self.viewer) result("OK") case "setBackgroundImage": let uri = call.arguments as! String set_background_image(self.viewer!, uri) render(self.viewer!, 0) self.registry.textureFrameAvailable(self.textureId!) result("OK") case "setBackgroundImagePosition": let args = call.arguments as! Array set_background_image_position(self.viewer, Float(args[0] as! Double), Float(args[1] as! Double), args[2] as! Bool) result("OK"); case "setPosition": let args = call.arguments as! Array let assetPtr = UnsafeMutableRawPointer.init(bitPattern: args[0] as! Int) let x = Float(args[1] as! Double) set_position(assetPtr, x, Float(args[2] as! Double), Float(args[3] as! Double)) result("OK") case "setRotation": let args = call.arguments as! Array let assetPtr = UnsafeMutableRawPointer.init(bitPattern: args[0] as! Int) set_rotation(assetPtr, Float(args[1] as! Double), Float(args[2] as! Double), Float(args[3] as! Double), Float(args[4] as! Double)) result("OK") case "setScale": let args = call.arguments as! Array let assetPtr = UnsafeMutableRawPointer.init(bitPattern: args[0] as! Int) set_scale(assetPtr, Float(args[1] as! Double)) result("OK"); case "setCameraPosition": let args = call.arguments as! Array set_camera_position(self.viewer, Float(args[0] as! Double), Float(args[1] as! Double), Float(args[2] as! Double)) result("OK"); case "setCameraRotation": let args = call.arguments as! Array set_camera_rotation(self.viewer, Float(args[0] as! Double), Float(args[1] as! Double), Float(args[2] as! Double),Float(args[3] as! Double)) result("OK") case "setCameraFocalLength": set_camera_focal_length(self.viewer, Float(call.arguments as! Double)) result("OK"); case "setCameraFocusDistance": // TODO // set_camera_focus_distance(self.viewer, Float(call.arguments as! Double)) // result("OK"); break case "setFrameInterval": set_frame_interval(self.viewer, Float(call.arguments as! Double)); result("OK") case "setRendering": _rendering = call.arguments as! Bool result("OK") case "setTexture": let args = call.arguments as! Array let assetPtr = UnsafeMutableRawPointer.init(bitPattern: args[0] as! Int) load_texture(assetPtr, args[1] as! String, args[2] as! Int32) result("OK"); case "transformToUnitCube": let assetPtr = UnsafeMutableRawPointer.init(bitPattern: call.arguments as! Int) transform_to_unit_cube(assetPtr) result("OK"); case "zoomBegin": scroll_begin(self.viewer) result("OK") case "zoomUpdate": let args = call.arguments as! Array scroll_update(self.viewer, Float(args[0] as! Double), Float(args[1] as! Double),Float(args[2] as! Double)) result("OK") case "zoomEnd": scroll_end(self.viewer) result("OK") default: result(FlutterMethodNotImplemented) } } }