├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── sampledata │ ├── model.fbx │ └── model.sfa └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── nouman │ │ └── sceneview │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── model.sfb │ ├── java │ │ └── com │ │ │ └── nouman │ │ │ └── sceneview │ │ │ ├── MainActivity.kt │ │ │ ├── SceneViewActivity.kt │ │ │ └── nodes │ │ │ ├── DragRotationController.kt │ │ │ └── DragTransformableNode.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── activity_scene_view.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── nouman │ └── sceneview │ └── ExampleUnitTest.kt ├── build.gradle ├── first.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── second.png ├── settings.gradle └── third.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | xmlns:android 20 | 21 | ^$ 22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 | xmlns:.* 31 | 32 | ^$ 33 | 34 | 35 | BY_NAME 36 | 37 |
38 |
39 | 40 | 41 | 42 | .*:id 43 | 44 | http://schemas.android.com/apk/res/android 45 | 46 | 47 | 48 |
49 |
50 | 51 | 52 | 53 | .*:name 54 | 55 | http://schemas.android.com/apk/res/android 56 | 57 | 58 | 59 |
60 |
61 | 62 | 63 | 64 | name 65 | 66 | ^$ 67 | 68 | 69 | 70 |
71 |
72 | 73 | 74 | 75 | style 76 | 77 | ^$ 78 | 79 | 80 | 81 |
82 |
83 | 84 | 85 | 86 | .* 87 | 88 | ^$ 89 | 90 | 91 | BY_NAME 92 | 93 |
94 |
95 | 96 | 97 | 98 | .* 99 | 100 | http://schemas.android.com/apk/res/android 101 | 102 | 103 | ANDROID_ATTRIBUTE_ORDER 104 | 105 |
106 |
107 | 108 | 109 | 110 | .* 111 | 112 | .* 113 | 114 | 115 | BY_NAME 116 | 117 |
118 |
119 |
120 |
121 | 122 | 124 |
125 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SceneView 2 | 3 | Simple Demo application to understand the use of SceneView of Sceneform Arcore in android. Helping Video https://youtu.be/IhITGO0shs8 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 29 9 | buildToolsVersion "29.0.2" 10 | defaultConfig { 11 | applicationId "com.nouman.sceneview" 12 | minSdkVersion 24 13 | targetSdkVersion 29 14 | versionCode 1 15 | versionName "1.0" 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility = '1.8' 26 | targetCompatibility = '1.8' 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation fileTree(dir: 'libs', include: ['*.jar']) 32 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 33 | implementation 'androidx.appcompat:appcompat:1.1.0' 34 | implementation 'androidx.core:core-ktx:1.1.0' 35 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 36 | testImplementation 'junit:junit:4.12' 37 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 38 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 39 | 40 | implementation "com.google.ar.sceneform.ux:sceneform-ux:1.13.0" 41 | implementation 'com.google.ar.sceneform:assets:1.13.0' 42 | } 43 | apply plugin: 'com.google.ar.sceneform.plugin' 44 | 45 | sceneform.asset('sampledata/model.fbx', 46 | 'default', 47 | 'sampledata/model.sfa', 48 | 'src/main/assets/model') -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/sampledata/model.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chnouman/SceneView/75c0ef0680856e2aa97277bacb0eb78c352a7a7e/app/sampledata/model.fbx -------------------------------------------------------------------------------- /app/sampledata/model.sfa: -------------------------------------------------------------------------------- 1 | { 2 | animations: [ 3 | { 4 | path: 'sampledata/model.fbx', 5 | }, 6 | ], 7 | materials: [ 8 | { 9 | name: 'material_8___25568', 10 | parameters: [ 11 | { 12 | baseColor: [ 13 | 0.95686300000000002, 14 | 0.26274500000000001, 15 | 0.21176500000000001, 16 | 1, 17 | ], 18 | }, 19 | { 20 | baseColorMap: null, 21 | }, 22 | { 23 | normalMap: null, 24 | }, 25 | { 26 | interpolatedColor: null, 27 | }, 28 | { 29 | metallic: 0, 30 | }, 31 | { 32 | metallicMap: null, 33 | }, 34 | { 35 | roughness: 1, 36 | }, 37 | { 38 | roughnessMap: null, 39 | }, 40 | { 41 | opacity: null, 42 | }, 43 | ], 44 | source: 'build/sceneform_sdk/default_materials/fbx_material.sfm', 45 | }, 46 | { 47 | name: 'material_21___25568', 48 | parameters: [ 49 | { 50 | baseColor: [ 51 | 1, 52 | 1, 53 | 1, 54 | 1, 55 | ], 56 | }, 57 | { 58 | baseColorMap: null, 59 | }, 60 | { 61 | normalMap: null, 62 | }, 63 | { 64 | interpolatedColor: null, 65 | }, 66 | { 67 | metallic: 0, 68 | }, 69 | { 70 | metallicMap: null, 71 | }, 72 | { 73 | roughness: 1, 74 | }, 75 | { 76 | roughnessMap: null, 77 | }, 78 | { 79 | opacity: null, 80 | }, 81 | ], 82 | source: 'build/sceneform_sdk/default_materials/fbx_material.sfm', 83 | }, 84 | { 85 | name: 'material_15___25568', 86 | parameters: [ 87 | { 88 | baseColor: [ 89 | 0.81176499999999996, 90 | 0.84705900000000001, 91 | 0.86274499999999998, 92 | 1, 93 | ], 94 | }, 95 | { 96 | baseColorMap: null, 97 | }, 98 | { 99 | normalMap: null, 100 | }, 101 | { 102 | interpolatedColor: null, 103 | }, 104 | { 105 | metallic: 0, 106 | }, 107 | { 108 | metallicMap: null, 109 | }, 110 | { 111 | roughness: 1, 112 | }, 113 | { 114 | roughnessMap: null, 115 | }, 116 | { 117 | opacity: null, 118 | }, 119 | ], 120 | source: 'build/sceneform_sdk/default_materials/fbx_material.sfm', 121 | }, 122 | { 123 | name: 'material_23___25568', 124 | parameters: [ 125 | { 126 | baseColor: [ 127 | 0.101961, 128 | 0.101961, 129 | 0.101961, 130 | 1, 131 | ], 132 | }, 133 | { 134 | baseColorMap: null, 135 | }, 136 | { 137 | normalMap: null, 138 | }, 139 | { 140 | interpolatedColor: null, 141 | }, 142 | { 143 | metallic: 0, 144 | }, 145 | { 146 | metallicMap: null, 147 | }, 148 | { 149 | roughness: 1, 150 | }, 151 | { 152 | roughnessMap: null, 153 | }, 154 | { 155 | opacity: null, 156 | }, 157 | ], 158 | source: 'build/sceneform_sdk/default_materials/fbx_material.sfm', 159 | }, 160 | { 161 | name: 'material_22___25568', 162 | parameters: [ 163 | { 164 | baseColor: [ 165 | 0.61960800000000005, 166 | 0.61960800000000005, 167 | 0.61960800000000005, 168 | 1, 169 | ], 170 | }, 171 | { 172 | baseColorMap: null, 173 | }, 174 | { 175 | normalMap: null, 176 | }, 177 | { 178 | interpolatedColor: null, 179 | }, 180 | { 181 | metallic: 0, 182 | }, 183 | { 184 | metallicMap: null, 185 | }, 186 | { 187 | roughness: 1, 188 | }, 189 | { 190 | roughnessMap: null, 191 | }, 192 | { 193 | opacity: null, 194 | }, 195 | ], 196 | source: 'build/sceneform_sdk/default_materials/fbx_material.sfm', 197 | }, 198 | { 199 | name: 'material_13___25568', 200 | parameters: [ 201 | { 202 | baseColor: [ 203 | 1, 204 | 0.596078, 205 | 0, 206 | 1, 207 | ], 208 | }, 209 | { 210 | baseColorMap: null, 211 | }, 212 | { 213 | normalMap: null, 214 | }, 215 | { 216 | interpolatedColor: null, 217 | }, 218 | { 219 | metallic: 0, 220 | }, 221 | { 222 | metallicMap: null, 223 | }, 224 | { 225 | roughness: 1, 226 | }, 227 | { 228 | roughnessMap: null, 229 | }, 230 | { 231 | opacity: null, 232 | }, 233 | ], 234 | source: 'build/sceneform_sdk/default_materials/fbx_material.sfm', 235 | }, 236 | ], 237 | model: { 238 | attributes: [ 239 | 'Position', 240 | 'Orientation', 241 | 'BoneIndices', 242 | 'BoneWeights', 243 | ], 244 | collision: {}, 245 | file: 'sampledata/model.fbx', 246 | name: 'model', 247 | recenter: 'root', 248 | }, 249 | version: '0.54:2', 250 | } 251 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/nouman/sceneview/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.nouman.sceneview 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.nouman.sceneview", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/assets/model.sfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chnouman/SceneView/75c0ef0680856e2aa97277bacb0eb78c352a7a7e/app/src/main/assets/model.sfb -------------------------------------------------------------------------------- /app/src/main/java/com/nouman/sceneview/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.nouman.sceneview 2 | 3 | import android.content.Intent 4 | import androidx.appcompat.app.AppCompatActivity 5 | import android.os.Bundle 6 | import android.view.View 7 | import com.google.ar.sceneform.SceneView 8 | 9 | class MainActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_main) 14 | } 15 | 16 | 17 | fun onLocalModelClick(view: View) { 18 | startActivity( 19 | Intent( 20 | this, 21 | SceneViewActivity::class.java 22 | ).putExtra(SceneViewActivity.Statics.EXTRA_MODEL_TYPE, "local") 23 | ) 24 | 25 | } 26 | 27 | fun onRemoteModelClick(view: View) { 28 | startActivity( 29 | Intent( 30 | this, 31 | SceneViewActivity::class.java 32 | ).putExtra(SceneViewActivity.Statics.EXTRA_MODEL_TYPE, "remote") 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/nouman/sceneview/SceneViewActivity.kt: -------------------------------------------------------------------------------- 1 | package com.nouman.sceneview 2 | 3 | import android.content.DialogInterface 4 | import android.net.Uri 5 | import androidx.appcompat.app.AppCompatActivity 6 | import android.os.Bundle 7 | import android.os.Handler 8 | import android.os.Looper 9 | import android.view.MotionEvent 10 | import android.view.View 11 | import androidx.appcompat.app.AlertDialog 12 | import com.google.ar.core.exceptions.CameraNotAvailableException 13 | import com.google.ar.sceneform.HitTestResult 14 | import com.google.ar.sceneform.assets.RenderableSource 15 | import com.google.ar.sceneform.rendering.ModelRenderable 16 | import com.google.ar.sceneform.ux.FootprintSelectionVisualizer 17 | import com.google.ar.sceneform.ux.TransformationSystem 18 | import com.nouman.sceneview.SceneViewActivity.Statics.EXTRA_MODEL_TYPE 19 | import com.nouman.sceneview.nodes.DragTransformableNode 20 | import kotlinx.android.synthetic.main.activity_scene_view.* 21 | import java.lang.Exception 22 | import java.util.concurrent.CompletionException 23 | 24 | class SceneViewActivity : AppCompatActivity() { 25 | 26 | var remoteModelUrl = 27 | "https://poly.googleusercontent.com/downloads/0BnDT3T1wTE/85QOHCZOvov/Mesh_Beagle.gltf" 28 | 29 | var localModel = "model.sfb" 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | setContentView(R.layout.activity_scene_view) 33 | 34 | val remoteModelUrl = intent.getStringExtra(EXTRA_MODEL_TYPE) 35 | if (remoteModelUrl.equals("remote")) { 36 | renderRemoteObject() 37 | } else { 38 | //load local model 39 | renderLocalObject() 40 | } 41 | } 42 | 43 | private fun renderRemoteObject() { 44 | 45 | skuProgressBar.setVisibility(View.VISIBLE) 46 | ModelRenderable.builder() 47 | .setSource( 48 | this, RenderableSource.Builder().setSource( 49 | this, 50 | Uri.parse(remoteModelUrl), 51 | RenderableSource.SourceType.GLTF2 52 | ).setScale(0.01f) 53 | .setRecenterMode(RenderableSource.RecenterMode.CENTER) 54 | .build() 55 | ) 56 | .setRegistryId(remoteModelUrl) 57 | .build() 58 | .thenAccept { modelRenderable: ModelRenderable -> 59 | skuProgressBar.setVisibility(View.GONE) 60 | addNodeToScene(modelRenderable) 61 | } 62 | .exceptionally { throwable: Throwable? -> 63 | var message: String? 64 | message = if (throwable is CompletionException) { 65 | skuProgressBar.setVisibility(View.GONE) 66 | "Internet is not working" 67 | } else { 68 | skuProgressBar.setVisibility(View.GONE) 69 | "Can't load Model" 70 | } 71 | val mainHandler = Handler(Looper.getMainLooper()) 72 | val finalMessage: String = message 73 | val myRunnable = Runnable { 74 | AlertDialog.Builder(this) 75 | .setTitle("Error") 76 | .setMessage(finalMessage + "") 77 | .setPositiveButton("Retry") { dialogInterface: DialogInterface, _: Int -> 78 | renderRemoteObject() 79 | dialogInterface.dismiss() 80 | } 81 | .setNegativeButton("Cancel") { dialogInterface, _ -> dialogInterface.dismiss() } 82 | .show() 83 | } 84 | mainHandler.post(myRunnable) 85 | null 86 | } 87 | } 88 | 89 | private fun renderLocalObject() { 90 | 91 | skuProgressBar.setVisibility(View.VISIBLE) 92 | ModelRenderable.builder() 93 | .setSource(this, Uri.parse(localModel)) 94 | .setRegistryId(localModel) 95 | .build() 96 | .thenAccept { modelRenderable: ModelRenderable -> 97 | skuProgressBar.setVisibility(View.GONE) 98 | addNodeToScene(modelRenderable) 99 | } 100 | .exceptionally { throwable: Throwable? -> 101 | var message: String? 102 | message = if (throwable is CompletionException) { 103 | skuProgressBar.setVisibility(View.GONE) 104 | "Internet is not working" 105 | } else { 106 | skuProgressBar.setVisibility(View.GONE) 107 | "Can't load Model" 108 | } 109 | val mainHandler = Handler(Looper.getMainLooper()) 110 | val finalMessage: String = message 111 | val myRunnable = Runnable { 112 | AlertDialog.Builder(this) 113 | .setTitle("Error") 114 | .setMessage(finalMessage + "") 115 | .setPositiveButton("Retry") { dialogInterface: DialogInterface, _: Int -> 116 | renderLocalObject() 117 | dialogInterface.dismiss() 118 | } 119 | .setNegativeButton("Cancel") { dialogInterface, _ -> dialogInterface.dismiss() } 120 | .show() 121 | } 122 | mainHandler.post(myRunnable) 123 | null 124 | } 125 | } 126 | 127 | override fun onPause() { 128 | super.onPause() 129 | sceneView.pause() 130 | } 131 | 132 | private fun addNodeToScene(model: ModelRenderable) { 133 | if (sceneView != null) { 134 | val transformationSystem = makeTransformationSystem() 135 | var dragTransformableNode = DragTransformableNode(1f, transformationSystem) 136 | dragTransformableNode?.renderable = model 137 | sceneView.getScene().addChild(dragTransformableNode) 138 | dragTransformableNode?.select() 139 | sceneView.getScene() 140 | .addOnPeekTouchListener { hitTestResult: HitTestResult?, motionEvent: MotionEvent? -> 141 | transformationSystem.onTouch( 142 | hitTestResult, 143 | motionEvent 144 | ) 145 | } 146 | } 147 | } 148 | 149 | private fun makeTransformationSystem(): TransformationSystem { 150 | val footprintSelectionVisualizer = FootprintSelectionVisualizer() 151 | return TransformationSystem(resources.displayMetrics, footprintSelectionVisualizer) 152 | } 153 | 154 | 155 | override fun onResume() { 156 | super.onResume() 157 | try { 158 | sceneView.resume() 159 | } catch (e: CameraNotAvailableException) { 160 | e.printStackTrace() 161 | } 162 | } 163 | 164 | object Statics { 165 | var EXTRA_MODEL_TYPE = "modelType" 166 | } 167 | 168 | override fun onDestroy() { 169 | super.onDestroy() 170 | try { 171 | 172 | sceneView.destroy() 173 | } catch (e: Exception) { 174 | e.printStackTrace() 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /app/src/main/java/com/nouman/sceneview/nodes/DragRotationController.kt: -------------------------------------------------------------------------------- 1 | package com.nouman.sceneview.nodes 2 | 3 | import android.os.Handler 4 | import com.google.ar.sceneform.Node 5 | import com.google.ar.sceneform.math.Quaternion 6 | import com.google.ar.sceneform.math.Vector3 7 | import com.google.ar.sceneform.ux.BaseTransformationController 8 | import com.google.ar.sceneform.ux.DragGesture 9 | import com.google.ar.sceneform.ux.DragGestureRecognizer 10 | 11 | 12 | class DragRotationController( 13 | private val transformableNode: DragTransformableNode, 14 | gestureRecognizer: DragGestureRecognizer 15 | ) : 16 | BaseTransformationController(transformableNode, gestureRecognizer) { 17 | 18 | companion object { 19 | 20 | private const val initialLat = 26.15444376319647 21 | private const val initialLong = 18.995950736105442 22 | 23 | var lat: Double = initialLat 24 | var long: Double = initialLong 25 | } 26 | 27 | // Rate that the node rotates in degrees per degree of twisting. 28 | private var rotationRateDegrees = 0.5f 29 | 30 | public override fun canStartTransformation(gesture: DragGesture): Boolean { 31 | return transformableNode.isSelected 32 | } 33 | 34 | private fun getX(lat: Double, long: Double): Float { 35 | return (transformableNode.radius * Math.cos(Math.toRadians(lat)) * Math.sin(Math.toRadians(long))).toFloat() 36 | } 37 | 38 | private fun getY(lat: Double, long: Double): Float { 39 | return transformableNode.radius * Math.sin(Math.toRadians(lat)).toFloat() 40 | } 41 | 42 | private fun getZ(lat: Double, long: Double): Float { 43 | return (transformableNode.radius * Math.cos(Math.toRadians(lat)) * Math.cos(Math.toRadians(long))).toFloat() 44 | } 45 | 46 | override fun onActivated(node: Node?) { 47 | super.onActivated(node) 48 | Handler().postDelayed({ 49 | transformCamera(lat, long) 50 | }, 0) 51 | } 52 | 53 | public override fun onContinueTransformation(gesture: DragGesture) { 54 | 55 | val rotationAmountY = gesture.delta.y * rotationRateDegrees 56 | val rotationAmountX = gesture.delta.x * rotationRateDegrees 57 | val deltaAngleY = rotationAmountY.toDouble() 58 | val deltaAngleX = rotationAmountX.toDouble() 59 | 60 | long -= deltaAngleX 61 | lat += deltaAngleY 62 | 63 | //lat = Math.max(Math.min(lat, 90.0), 0.0) 64 | 65 | transformCamera(lat, long) 66 | } 67 | 68 | private fun transformCamera(lat: Double, long: Double) { 69 | val camera = transformableNode.scene?.camera 70 | 71 | var rot = Quaternion.eulerAngles(Vector3(0F, 0F, 0F)) 72 | val pos = Vector3(getX(lat, long), getY(lat, long), getZ(lat, long)) 73 | rot = Quaternion.multiply(rot, Quaternion(Vector3.up(), (long).toFloat())) 74 | rot = Quaternion.multiply(rot, Quaternion(Vector3.right(), (-lat).toFloat())) 75 | camera?.localRotation = rot 76 | camera?.localPosition = pos 77 | } 78 | 79 | fun resetInitialState() { 80 | transformCamera(initialLat, initialLong) 81 | } 82 | 83 | 84 | 85 | public override fun onEndTransformation(gesture: DragGesture) {} 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/nouman/sceneview/nodes/DragTransformableNode.kt: -------------------------------------------------------------------------------- 1 | package com.nouman.sceneview.nodes 2 | import com.google.ar.sceneform.ux.TransformableNode 3 | import com.google.ar.sceneform.ux.TransformationSystem 4 | 5 | class DragTransformableNode(val radius: Float, transformationSystem: TransformationSystem) : 6 | TransformableNode(transformationSystem) { 7 | val dragRotationController = DragRotationController( 8 | this, 9 | transformationSystem.dragRecognizer 10 | ) 11 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 |