package com.reactnativear import android.content.Context import android.opengl.GLES20 import android.opengl.GLSurfaceView import android.util.Log import android.view.GestureDetector import android.view.MotionEvent import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.RCTEventEmitter import com.google.ar.core.* import com.google.ar.core.exceptions.CameraNotAvailableException import java.io.IOException import java.util.* import javax.microedition.khronos.egl.EGLConfig import javax.microedition.khronos.opengles.GL10 class ARView(context: Context) : GLSurfaceView(context), GestureDetector.OnGestureListener { private val TAG = "ARView" private var session: Session? = null private val gestureDetector = GestureDetector(context, this) private var placeOnTap = true private var objectScale = 1.0f private val reactContext = context as ReactContext private val planes = HashMap() private val objects = HashMap() init { preserveEGLContextOnPause = true setEGLContextClientVersion(2) setEGLConfigChooser(8, 8, 8, 8, 16, 0) setRenderer(object : Renderer { override fun onSurfaceCreated(gl: GL10, config: EGLConfig) { GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f) try { // Initialize AR resources // In a real implementation, this would set up OpenGL resources } catch (e: IOException) { Log.e(TAG, "Failed to initialize AR resources", e) } } override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) { GLES20.glViewport(0, 0, width, height) // Update camera projection if needed } override fun onDrawFrame(gl: GL10) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT) val session = session ?: return if (!reactContext.hasActiveReactInstance()) { return } try { session.setCameraTextureName(0) // In a real app, this would be a texture ID val frame = session.update() val camera = frame.camera // Update tracking state updateTrackingState(camera) // Update planes updatePlanes(frame) // Render AR objects // In a real implementation, this would render 3D models } catch (e: CameraNotAvailableException) { Log.e(TAG, "Camera not available during onDrawFrame", e) } catch (e: Exception) { Log.e(TAG, "Exception on the OpenGL thread", e) } } }) renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY } fun setSession(session: Session) { this.session = session } fun setPlaceOnTap(placeOnTap: Boolean) { this.placeOnTap = placeOnTap } fun setObjectScale(scale: Float) { this.objectScale = scale } override fun onTouchEvent(event: MotionEvent): Boolean { gestureDetector.onTouchEvent(event) return true } override fun onDown(e: MotionEvent): Boolean { return true } override fun onShowPress(e: MotionEvent) { // Not used } override fun onSingleTapUp(e: MotionEvent): Boolean { val currentSession = session ?: return false try { val frame = currentSession.update() val camera = frame.camera if (camera.trackingState != TrackingState.TRACKING) { return false } // Handle tap val tap = e val tapX = tap.x val tapY = tap.y // Perform hit test val hitResult = frame.hitTest(tapX, tapY).firstOrNull() ?: return false // Get the hit pose val hitPose = hitResult.hitPose val position = floatArrayOf(hitPose.tx(), hitPose.ty(), hitPose.tz()) // Create event data val eventData = Arguments.createMap().apply { putMap("position", Arguments.createMap().apply { putDouble("x", position[0].toDouble()) putDouble("y", position[1].toDouble()) putDouble("z", position[2].toDouble()) }) } // Check if we hit a plane if (hitResult.trackable is Plane) { val plane = hitResult.trackable as Plane val planeId = getPlaneId(plane) eventData.putString("planeId", planeId) } // Emit tap event sendEvent("onTapNative", eventData) // Place object if enabled if (placeOnTap) { placeObject(hitPose) } return true } catch (e: Exception) { Log.e(TAG, "Error handling tap", e) return false } } private fun placeObject(pose: Pose) { val currentSession = session ?: return try { // Create an anchor at the hit location val anchor = currentSession.createAnchor(pose) val objectId = UUID.randomUUID().toString() objects[objectId] = anchor // Create event data for object placement val eventData = Arguments.createMap().apply { putString("id", objectId) putMap("position", Arguments.createMap().apply { putDouble("x", pose.tx().toDouble()) putDouble("y", pose.ty().toDouble()) putDouble("z", pose.tz().toDouble()) }) putDouble("scale", objectScale.toDouble()) putMap("rotation", Arguments.createMap().apply { putDouble("x", 0.0) putDouble("y", 0.0) putDouble("z", 0.0) }) } // Emit object placed event sendEvent("onObjectPlacedNative", eventData) } catch (e: Exception) { Log.e(TAG, "Error placing object", e) } } override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean { // Could implement dragging objects here return false } override fun onLongPress(e: MotionEvent) { // Could implement object selection or context menu here } override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { // Not used return false } private fun updateTrackingState(camera: Camera) { val trackingState = camera.trackingState val state = when (trackingState) { TrackingState.TRACKING -> "NORMAL" TrackingState.PAUSED -> "LIMITED" TrackingState.STOPPED -> "NOT_AVAILABLE" else -> "NOT_AVAILABLE" } val reason = when { trackingState == TrackingState.PAUSED -> { when (camera.trackingFailureReason) { TrackingFailureReason.NONE -> "NONE" TrackingFailureReason.BAD_STATE -> "RELOCALIZING" TrackingFailureReason.INSUFFICIENT_LIGHT -> "INSUFFICIENT_FEATURES" TrackingFailureReason.EXCESSIVE_MOTION -> "EXCESSIVE_MOTION" TrackingFailureReason.INSUFFICIENT_FEATURES -> "INSUFFICIENT_FEATURES" else -> "NONE" } } trackingState == TrackingState.STOPPED -> "NONE" else -> "NONE" } val eventData = Arguments.createMap().apply { putString("state", state) putString("reason", reason) } sendEvent("onTrackingUpdatedNative", eventData) } private fun updatePlanes(frame: Frame) { val currentPlanes = HashMap() for (plane in frame.getUpdatedTrackables(Plane::class.java)) { val planeId = getPlaneId(plane) currentPlanes[planeId] = plane // If this is a new plane, emit an event if (!planes.containsKey(planeId)) { val pose = plane.centerPose val position = floatArrayOf(pose.tx(), pose.ty(), pose.tz()) val orientation = if (plane.type == Plane.Type.VERTICAL) "VERTICAL" else "HORIZONTAL" val eventData = Arguments.createMap().apply { putString("id", planeId) putMap("position", Arguments.createMap().apply { putDouble("x", position[0].toDouble()) putDouble("y", position[1].toDouble()) putDouble("z", position[2].toDouble()) }) putMap("extent", Arguments.createMap().apply { putDouble("width", plane.extentX.toDouble()) putDouble("height", plane.extentZ.toDouble()) }) putString("orientation", orientation) } sendEvent("onPlaneDetectedNative", eventData) } } // Update the planes map planes.clear() planes.putAll(currentPlanes) } private fun getPlaneId(plane: Plane): String { // Use the hashCode as a unique identifier for the plane return "plane_${plane.hashCode()}" } private fun sendEvent(eventName: String, params: WritableMap) { reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent( id, eventName, params ) } }