package com.reactnativear import android.app.Activity import android.content.Context import android.util.Log import com.facebook.react.bridge.* import com.facebook.react.module.annotations.ReactModule import com.facebook.react.modules.core.DeviceEventManagerModule import com.google.ar.core.* import com.google.ar.core.exceptions.* import java.util.* @ReactModule(name = ARModule.NAME) class ARModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { companion object { const val NAME = "ARModule" private const val TAG = "ARModule" } private var session: Session? = null private var isSessionRunning = false private val objects = HashMap() private data class ObjectEntry( val id: String, var position: FloatArray, var rotation: FloatArray, var scale: Float, var anchor: Anchor? = null ) override fun getName(): String { return NAME } @ReactMethod fun startSession(options: ReadableMap, promise: Promise) { val activity = currentActivity if (activity == null) { promise.reject("E_ACTIVITY_DOES_NOT_EXIST", "Activity doesn't exist") return } try { if (session == null) { // Create a new ARCore session session = Session(activity) // Configure the session val config = Config(session) config.planeFindingMode = Config.PlaneFindingMode.HORIZONTAL_AND_VERTICAL config.updateMode = Config.UpdateMode.LATEST_CAMERA_IMAGE config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR session?.configure(config) } isSessionRunning = true promise.resolve(null) // Emit session started event sendEvent("onSessionStarted", Arguments.createMap()) } catch (e: UnavailableArcoreNotInstalledException) { promise.reject("E_ARCORE_NOT_INSTALLED", "ARCore is not installed") } catch (e: UnavailableApkTooOldException) { promise.reject("E_ARCORE_TOO_OLD", "ARCore is too old") } catch (e: UnavailableSdkTooOldException) { promise.reject("E_SDK_TOO_OLD", "SDK is too old") } catch (e: UnavailableDeviceNotCompatibleException) { promise.reject("E_DEVICE_NOT_COMPATIBLE", "Device is not compatible with AR") } catch (e: Exception) { promise.reject("E_UNKNOWN_ERROR", "Unknown error: ${e.message}") } } @ReactMethod fun pauseSession(promise: Promise) { try { isSessionRunning = false session?.pause() promise.resolve(null) // Emit session paused event sendEvent("onSessionPaused", Arguments.createMap()) } catch (e: Exception) { promise.reject("E_PAUSE_ERROR", "Error pausing session: ${e.message}") } } @ReactMethod fun resetSession(promise: Promise) { try { // Remove all anchors objects.values.forEach { it.anchor?.detach() } objects.clear() // Reset the session session?.close() session = null isSessionRunning = false // Restart the session startSession(Arguments.createMap(), promise) } catch (e: Exception) { promise.reject("E_RESET_ERROR", "Error resetting session: ${e.message}") } } @ReactMethod fun isSupported(promise: Promise) { val availability = ArCoreApk.getInstance().checkAvailability(reactContext) val isSupported = (availability.isTransient || availability == ArCoreApk.Availability.SUPPORTED_INSTALLED) promise.resolve(isSupported) } @ReactMethod fun addObject(model: ReadableMap, promise: Promise) { try { val id = UUID.randomUUID().toString() // Extract position, rotation, scale from the model val position = if (model.hasKey("position")) { val pos = model.getMap("position") floatArrayOf( pos.getDouble("x").toFloat(), pos.getDouble("y").toFloat(), pos.getDouble("z").toFloat() ) } else { floatArrayOf(0f, 0f, 0f) } val rotation = if (model.hasKey("rotation")) { val rot = model.getMap("rotation") floatArrayOf( rot.getDouble("x").toFloat(), rot.getDouble("y").toFloat(), rot.getDouble("z").toFloat() ) } else { floatArrayOf(0f, 0f, 0f) } val scale = if (model.hasKey("scale")) model.getDouble("scale").toFloat() else 1.0f // Create an object entry val objectEntry = ObjectEntry(id, position, rotation, scale) objects[id] = objectEntry // Resolve with the object ID promise.resolve(id) } catch (e: Exception) { promise.reject("E_ADD_OBJECT_ERROR", "Error adding object: ${e.message}") } } @ReactMethod fun removeObject(id: String, promise: Promise) { try { val objectEntry = objects[id] if (objectEntry != null) { objectEntry.anchor?.detach() objects.remove(id) promise.resolve(null) } else { promise.reject("E_OBJECT_NOT_FOUND", "Object with ID $id not found") } } catch (e: Exception) { promise.reject("E_REMOVE_OBJECT_ERROR", "Error removing object: ${e.message}") } } @ReactMethod fun moveObject(id: String, position: ReadableMap, promise: Promise) { try { val objectEntry = objects[id] if (objectEntry != null) { objectEntry.position = floatArrayOf( position.getDouble("x").toFloat(), position.getDouble("y").toFloat(), position.getDouble("z").toFloat() ) // Update the anchor if needed updateObjectAnchor(objectEntry) promise.resolve(null) } else { promise.reject("E_OBJECT_NOT_FOUND", "Object with ID $id not found") } } catch (e: Exception) { promise.reject("E_MOVE_OBJECT_ERROR", "Error moving object: ${e.message}") } } @ReactMethod fun rotateObject(id: String, rotation: ReadableMap, promise: Promise) { try { val objectEntry = objects[id] if (objectEntry != null) { objectEntry.rotation = floatArrayOf( rotation.getDouble("x").toFloat(), rotation.getDouble("y").toFloat(), rotation.getDouble("z").toFloat() ) promise.resolve(null) } else { promise.reject("E_OBJECT_NOT_FOUND", "Object with ID $id not found") } } catch (e: Exception) { promise.reject("E_ROTATE_OBJECT_ERROR", "Error rotating object: ${e.message}") } } @ReactMethod fun scaleObject(id: String, scale: Double, promise: Promise) { try { val objectEntry = objects[id] if (objectEntry != null) { objectEntry.scale = scale.toFloat() promise.resolve(null) } else { promise.reject("E_OBJECT_NOT_FOUND", "Object with ID $id not found") } } catch (e: Exception) { promise.reject("E_SCALE_OBJECT_ERROR", "Error scaling object: ${e.message}") } } private fun updateObjectAnchor(objectEntry: ObjectEntry) { val currentSession = session ?: return // Detach old anchor if it exists objectEntry.anchor?.detach() // Create a new anchor at the object's position val pose = Pose.makeTranslation( objectEntry.position[0], objectEntry.position[1], objectEntry.position[2] ) objectEntry.anchor = currentSession.createAnchor(pose) } private fun sendEvent(eventName: String, params: WritableMap) { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(eventName, params) } }