Skip to content

Commit

Permalink
Simple model loading
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Gorisse committed Feb 26, 2021
1 parent a821d98 commit bd392ab
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 56 deletions.
111 changes: 59 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
Sceneform SDK for Android - Maintained
======================================
Maintained Sceneform SDK for Android
====================================

#### This repository is a fork of [Sceneform](https://github.com/google-ar/sceneform-android-sdk)
Copyright (c) 2018 Google Inc. All rights reserved.

[ ![jCenter](https://img.shields.io/badge/jCenter-1.18.3-blue) ](https://bintray.com/thomasgorisse/maven/com.gorisse.thomas.sceneform:sceneform/1.18.3/link)
[ ![jCenter](https://img.shields.io/badge/jCenter-1.18.4-blue) ](https://bintray.com/thomasgorisse/maven/com.gorisse.thomas.sceneform:sceneform/1.18.4/link)

Sceneform is a 3D framework with a physically based renderer that's optimized
for mobile devices and that makes it easy for you to build Augmented Reality (AR)
Expand All @@ -16,34 +16,30 @@ apps without requiring OpenGL or Unity.
* Continuous compatibility with the latests versions of [ARCore SDK](https://github.com/google-ar/arcore-android-sdk) and [Filament](https://github.com/google/filament)
* Based on AndroidX
* Available has jCenter dependency
* Supports <a href="https://www.khronos.org/gltf/">glTF</a> instead of olds <code>SFA</code> and <code>SFB</code> formats
* Animations support
* Open source
* Supports <a href="https://www.khronos.org/gltf/">glTF</a> format
* Animations made easy
* Simple model loading for basic usage



## Dependencies

Sceneform is available on `jCenter()`

*app/build.gradle*
```gradle
dependencies {
implementation("com.gorisse.thomas.sceneform:sceneform:1.18.3")
implementation("com.gorisse.thomas.sceneform:sceneform:1.18.4")
}
```



## Usage
## Basic Usage (Simple model viewer)


### Update your `AndroidManifest.xml`

Modify your `AndroidManifest.xml` to indicate that your app uses (AR Optional) or requires (AR Required) ARCore and Camera access:

*AndroidManifest.xml*
```
```xml
<uses-permission android:name="android.permission.CAMERA" />

<application>
Expand All @@ -52,62 +48,78 @@ Modify your `AndroidManifest.xml` to indicate that your app uses (AR Optional) o
</application>
```

#### Optional
If your app requires ARCore (AR Required) and is not only (AR Optional)
Use this manifest to indicates that this app requires Google Play Services for AR (AR Required) and results in
the app only being visible in the Google Play Store on devices that support ARCore:
```
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.ar" android:required="true"/>

<application>
<meta-data android:name="com.google.ar.core" android:value="required" />
</application>
```
[More infos](https://developers.google.com/ar/develop/java/enable-arcore)

### Add the `FragmentContainerView` to your `layout`
### Add the `View` to your `layout`
*res/layout/main_activity.xml*
```xml
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container_view"
android:id="@+id/arFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
```


### Edit your `Activity` or `Fragment`
*src/main/java/…/MainActivity.java*
```java
if(Sceneform.isSupported(this)) {
ModelRenderable.builder()
.setSource(
this,
// Http source
Uri.parse("https://storage.googleapis.com/ar-answers-in-search-models/static/Tiger/model.glb")
// Or raw resource : model.glb
// R.raw.model
)
.setIsFilamentGltf(true)
.build()
.thenAccept { modelRenderable: ModelRenderable? ->
activity.renderable = modelRenderable
@Override
protected void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
if (Sceneform.isSupported(this)) {
getSupportFragmentManager().beginTransaction()
.add(R.id.arFragment, ArFragment.class)
.commit();
}
}
.exceptionally { throwable: Throwable? ->
Toast.makeText(this, "Unable to load renderable", Toast.LENGTH_LONG)
null
}

@Override
public void onAttachFragment(@NonNull Fragment fragment) {
super.onAttachFragment(fragment);

if (fragment.getId() == R.id.arFragment) {
// Load model.glb from assets folder or http url
((ArFragment) fragment).setOnTapPlaneGlbModel("model.glb", new ArFragment.OnTapModelListener() {
@Override
public void onModelAdded(RenderableInstance renderableInstance) {
}

@Override
public void onModelError(Throwable exception) {
}
});
}
}
```



## Go further


#### AR Required vs AR Optional

If your app requires ARCore (AR Required) and is not only (AR Optional), use this manifest to indicates that this app requires Google Play Services for AR (AR Required) and results in
the app only being visible in the Google Play Store on devices that support ARCore:
```xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.ar" android:required="true"/>

<application>
<meta-data android:name="com.google.ar.core" android:value="required" />
</application>
```
[more...](https://developers.google.com/ar/develop/java/enable-arcore)


## Animations

Until now, only `RenderableInstance` are animtable. Below `model` corresponds to a `RenderablaInstance` returned from a `node.getRenderableInstance()`


### Simple usage
### Basic usage

On a very basic 3D model like a single infinite rotating sphere, you should not have to
use ModelAnimator but probably instead just call:
Expand Down Expand Up @@ -208,13 +220,8 @@ Every PropertyValuesHolder that applies a modification on the time position of t
must use the `ModelAnimation.TIME_POSITION` instead of its own Property in order to possibly cancel
any ObjectAnimator operating time modifications on the same ModelAnimation.

[more...](/docs/animations/)


## Release notes
[more...](/docs/animations/index.md)

The SDK release notes are available on the
[releases](/releases/) page.


## License
Expand Down
2 changes: 1 addition & 1 deletion bintray.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apply plugin: 'com.jfrog.bintray'

project.ext {
mavenGroupId = 'com.gorisse.thomas.sceneform'
mavenVersion = '1.18.3'
mavenVersion = '1.18.4'
mavenDesc = 'Sceneform Core'
mavenWebsiteUrl = 'https://github.com/thomasgorisse/sceneform-android-sdk'
mavenIssueTrackerUrl = 'https://github.com/thomasgorisse/sceneform-android-sdk/issues'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ default List<String> getAnimationNames() {
return names;
}

/**
* Return true if {@link #getAnimationCount()} > 0
*/
default boolean hasAnimations() {
return getAnimationCount() > 0;
}

/**
* Sets the current position of (seeks) the animation to the specified time position in seconds.
* This time should be
Expand Down
5 changes: 2 additions & 3 deletions samples/gltf/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,5 @@ android {
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'

api project(":ux")
// implementation("com.gorisse.thomas.sceneform:sceneform:1.18.+")
}
implementation("com.gorisse.thomas.sceneform:sceneform:1.18.+")
}
75 changes: 75 additions & 0 deletions ux/src/main/java/com/google/ar/sceneform/ux/ArFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,25 @@
*/
package com.google.ar.sceneform.ux;

import android.net.Uri;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Toast;

import com.google.ar.core.Anchor;
import com.google.ar.core.Config;
import com.google.ar.core.HitResult;
import com.google.ar.core.Plane;
import com.google.ar.core.Session;
import com.google.ar.core.exceptions.UnavailableApkTooOldException;
import com.google.ar.core.exceptions.UnavailableArcoreNotInstalledException;
import com.google.ar.core.exceptions.UnavailableDeviceNotCompatibleException;
import com.google.ar.core.exceptions.UnavailableException;
import com.google.ar.core.exceptions.UnavailableSdkTooOldException;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.rendering.ModelRenderable;
import com.google.ar.sceneform.rendering.Renderable;
import com.google.ar.sceneform.rendering.RenderableInstance;

import java.util.Collections;
import java.util.Set;
Expand All @@ -36,6 +45,8 @@
public class ArFragment extends BaseArFragment {
private static final String TAG = "StandardArFragment";

private Renderable onTapRenderable;

@Override
public boolean isArRequired() {
return true;
Expand Down Expand Up @@ -74,4 +85,68 @@ protected Config getSessionConfiguration(Session session) {
protected Set<Session.Feature> getSessionFeatures() {
return Collections.emptySet();
}

/**
* Loads a monolithic binary glTF and add it to the fragment when the user tap on a detected
* plane surface.
* <p>
* Plays the animations automatically if the model has one.
* </p>
*
* @param glbSource Glb file source location can be come from the asset folder ("model.glb")
* or an http source ("http://domain.com/model.glb")
*/
public void setOnTapPlaneGlbModel(String glbSource, OnTapModelListener listener) {
ModelRenderable.builder()
.setSource(
getContext(),
Uri.parse(glbSource))
.setIsFilamentGltf(true)
.build()
.thenAccept(
modelRenderable -> {
onTapRenderable = modelRenderable;
})
.exceptionally(
throwable -> {
if (listener != null) {
listener.onModelError(throwable);
}
return null;
});

setOnTapArPlaneListener(
(HitResult hitResult, Plane plane, MotionEvent motionEvent) -> {
if (onTapRenderable == null) {
return;
}

// Create the Anchor.
Anchor anchor = hitResult.createAnchor();
AnchorNode anchorNode = new AnchorNode(anchor);
anchorNode.setParent(getArSceneView().getScene());

// Create the transformable model and add it to the anchor.
TransformableNode model = new TransformableNode(getTransformationSystem());
model.setParent(anchorNode);
model.setRenderable(onTapRenderable);
model.select();

// Animate if has animation
RenderableInstance renderableInstance = model.getRenderableInstance();
if (renderableInstance != null && renderableInstance.hasAnimations()) {
renderableInstance.animate(true);
}

if (listener != null) {
listener.onModelAdded(renderableInstance);
}
});
}

public interface OnTapModelListener {
void onModelAdded(RenderableInstance renderableInstance);

void onModelError(Throwable exception);
}
}

0 comments on commit bd392ab

Please sign in to comment.