Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable own implementation of camera manipulator #571

Merged
merged 4 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 22 additions & 22 deletions arsceneview/src/main/java/io/github/sceneview/ar/ARSceneView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -239,28 +239,28 @@ open class ARSceneView @JvmOverloads constructor(
*/
var onSessionUpdated: ((session: Session, frame: Frame) -> Unit)? = null,
) : SceneView(
context,
attrs,
defStyleAttr,
defStyleRes,
sharedEngine,
sharedModelLoader,
sharedMaterialLoader,
sharedEnvironmentLoader,
sharedScene,
sharedView,
sharedRenderer,
sharedCameraNode,
sharedMainLightNode,
sharedEnvironment,
isOpaque,
sharedCollisionSystem,
null,
viewNodeWindowManager,
onGestureListener,
onTouchEvent,
sharedActivity,
sharedLifecycle
context = context,
attrs = attrs,
defStyleAttr = defStyleAttr,
defStyleRes = defStyleRes,
sharedEngine = sharedEngine,
sharedModelLoader = sharedModelLoader,
sharedMaterialLoader = sharedMaterialLoader,
sharedEnvironmentLoader = sharedEnvironmentLoader,
sharedScene = sharedScene,
sharedView = sharedView,
sharedRenderer = sharedRenderer,
sharedCameraNode = sharedCameraNode,
sharedMainLightNode = sharedMainLightNode,
sharedEnvironment = sharedEnvironment,
isOpaque = isOpaque,
sharedCollisionSystem = sharedCollisionSystem,
cameraManipulator = null,
viewNodeWindowManager = viewNodeWindowManager,
onGestureListener = onGestureListener,
onTouchEvent = onTouchEvent,
sharedActivity = sharedActivity,
sharedLifecycle = sharedLifecycle
) {
open val arCore = ARCore(
onSessionCreated = ::onSessionCreated,
Expand Down
1 change: 1 addition & 0 deletions samples/model-viewer-compose-camera-manipulator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
55 changes: 55 additions & 0 deletions samples/model-viewer-compose-camera-manipulator/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
}

android {
namespace 'io.github.sceneview.sample.modelviewer.compose.cameramanipulator'

compileSdk 34

defaultConfig {
applicationId "io.github.sceneview.sample.modelviewer.compose.cameramanipulator"
minSdk 28
targetSdk 34
versionCode 1
versionName "1.0.0"
}

buildTypes {
release {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.14'
}
androidResources {
noCompress 'filamat', 'ktx'
}
}

dependencies {
implementation project(":samples:common")

implementation "androidx.compose.ui:ui:1.6.7"
implementation "androidx.compose.foundation:foundation:1.6.7"
implementation 'androidx.activity:activity-compose:1.9.0'
implementation 'androidx.compose.material:material:1.6.7'
implementation "androidx.compose.ui:ui-tooling-preview:1.6.7"
implementation "androidx.navigation:navigation-compose:2.7.7"
debugImplementation "androidx.compose.ui:ui-tooling:1.6.7"

// SceneView
releaseImplementation "io.github.sceneview:sceneview:2.2.1"
debugImplementation project(":sceneview")
}
Binary file not shown.
21 changes: 21 additions & 0 deletions samples/model-viewer-compose-camera-manipulator/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.SceneViewSample">
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="locked">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package io.github.sceneview.sample.modelviewer.compose.cameramanipulator

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.github.sceneview.Scene
import io.github.sceneview.collision.CollisionSystem
import io.github.sceneview.gesture.CameraGestureDetector
import io.github.sceneview.math.Position
import io.github.sceneview.math.toVector3
import io.github.sceneview.node.CameraNode
import io.github.sceneview.node.ModelNode
import io.github.sceneview.rememberCameraManipulator
import io.github.sceneview.rememberCameraNode
import io.github.sceneview.rememberCollisionSystem
import io.github.sceneview.rememberEngine
import io.github.sceneview.rememberEnvironmentLoader
import io.github.sceneview.rememberModelLoader
import io.github.sceneview.rememberNode
import io.github.sceneview.rememberOnGestureListener
import io.github.sceneview.rememberView
import io.github.sceneview.sample.SceneviewTheme
import kotlin.math.sign

class MainActivity : ComponentActivity() {

@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
SceneviewTheme {
Box(modifier = Modifier.fillMaxSize()) {
val engine = rememberEngine()
val modelLoader = rememberModelLoader(engine)
val environmentLoader = rememberEnvironmentLoader(engine)
val view = rememberView(engine)
val collisionSystem = rememberCollisionSystem(view)

val centerNode = rememberNode(engine)

val cameraNode = rememberCameraNode(engine) {
position = Position(y = -0.5f, z = 2.0f)
lookAt(centerNode)
centerNode.addChildNode(this)
}

val cameraManipulator = rememberCameraManipulator(
creator = {
AdvancedCameraManipulator(
cameraNode = cameraNode,
collisionSystem = collisionSystem,
orbitHomePosition = cameraNode.worldPosition,
targetPosition = centerNode.worldPosition
)
}
)

Scene(
modifier = Modifier.fillMaxSize(),
engine = engine,
modelLoader = modelLoader,
view = view,
cameraNode = cameraNode,
cameraManipulator = cameraManipulator,
childNodes = listOf(centerNode,
rememberNode {
ModelNode(
modelInstance = modelLoader.createModelInstance(
assetFileLocation = "models/damaged_helmet.glb"
),
scaleToUnits = 0.25f
)
}),
collisionSystem = collisionSystem,
environment = environmentLoader.createHDREnvironment(
assetFileLocation = "environments/sky_2k.hdr"
)!!,
onGestureListener = rememberOnGestureListener(
onDoubleTap = { _, node ->
node?.apply {
scale *= 2.0f
}
}
)
)
Image(
modifier = Modifier
.width(192.dp)
.align(Alignment.BottomEnd)
.navigationBarsPadding()
.padding(16.dp)
.background(
color = MaterialTheme.colorScheme.primaryContainer.copy(
alpha = 0.5f
),
shape = MaterialTheme.shapes.medium
)
.padding(8.dp),
painter = painterResource(id = R.drawable.logo),
contentDescription = "Logo"
)
TopAppBar(
title = {
Text(
text = stringResource(id = R.string.app_name)
)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.25f),
titleContentColor = MaterialTheme.colorScheme.onPrimary

)
)
}
}
}
}
}

/**
* Override default camera manipulator to achieve relative zooming based on distance from camera to
* target and separation between two fingers.
*
* Target is calculated as closest node to center of finger's position in direction of camera.
*/
class AdvancedCameraManipulator(
private val cameraNode: CameraNode,
private val collisionSystem: CollisionSystem,
orbitHomePosition: Position? = null,
targetPosition: Position? = null
): CameraGestureDetector.DefaultCameraManipulator(
orbitHomePosition = orbitHomePosition,
targetPosition = targetPosition
) {
private var scrollBeginCameraPosition = Position()
private var scrollBeginDistance: Float? = 0f
private var scrollBeginSeparation = 0f

override fun scrollBegin(x: Int, y: Int, separation: Float) {
val hitResults = collisionSystem.hitTest(x.toFloat(), y.toFloat())
scrollBeginDistance = hitResults.firstOrNull()?.node?.position?.let {
(cameraNode.position - it).toVector3().length()
}
scrollBeginCameraPosition = cameraNode.position
scrollBeginSeparation = separation
}

override fun scrollUpdate(x: Int, y: Int, prevSeparation: Float, currSeparation: Float) {
val beginDistance = scrollBeginDistance
if (beginDistance == null) {
super.scrollUpdate(x, y, prevSeparation, currSeparation)
return
}

val movedVector = (cameraNode.position - scrollBeginCameraPosition).toVector3()
val movedDirection = listOf(
movedVector.x.sign,
movedVector.y.sign,
movedVector.z.sign,
).firstOrNull { it != 0f }?.sign ?: 1f

val ratio = scrollBeginSeparation / currSeparation
val moved = movedVector.length() * movedDirection
val target = (beginDistance * ratio)
val adjust = target - (beginDistance - moved)

manipulator.scroll(x, y, adjust)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Model Viewer Compose Advanced Camera Manipulator</string>
</resources>
Loading
Loading