diff --git a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationMarker.java b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationMarker.java index 1f398e4..d52a3e7 100644 --- a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationMarker.java +++ b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationMarker.java @@ -1,6 +1,5 @@ package uk.co.appoly.arcorelocation; -import com.google.ar.sceneform.AnchorNode; import com.google.ar.sceneform.Node; import uk.co.appoly.arcorelocation.rendering.LocationNode; @@ -22,15 +21,9 @@ public class LocationMarker { // Node to render public Node node; - public LocationNodeRender getRenderEvent() { - return renderEvent; - } - - public void setRenderEvent(LocationNodeRender renderEvent) { - this.renderEvent = renderEvent; - } - + // Called on each frame if not null private LocationNodeRender renderEvent; + private float height = 0F; public LocationMarker(double longitude, double latitude, Node node) { this.longitude = longitude; @@ -38,4 +31,36 @@ public LocationMarker(double longitude, double latitude, Node node) { this.node = node; } + /** + * Height based on camera height + */ + public float getHeight() { + return height; + } + + /** + * Height based on camera height + * + * @param height + */ + public void setHeight(float height) { + this.height = height; + } + + /** + * Called on each frame + * + * @return + */ + public LocationNodeRender getRenderEvent() { + return renderEvent; + } + + /** + * Called on each frame. + */ + public void setRenderEvent(LocationNodeRender renderEvent) { + this.renderEvent = renderEvent; + } + } diff --git a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationScene.java b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationScene.java index a46bf83..48aa122 100644 --- a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationScene.java +++ b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationScene.java @@ -9,13 +9,7 @@ import com.google.ar.core.Frame; import com.google.ar.core.Pose; import com.google.ar.core.Session; -import com.google.ar.core.TrackingState; -import com.google.ar.sceneform.AnchorNode; import com.google.ar.sceneform.ArSceneView; -import com.google.ar.sceneform.Scene; -import com.google.ar.sceneform.SceneView; -import com.google.ar.sceneform.math.Quaternion; -import com.google.ar.sceneform.math.Vector3; import java.util.ArrayList; @@ -30,34 +24,29 @@ public class LocationScene { + public ArSceneView mArSceneView; + public DeviceLocation deviceLocation; + public DeviceOrientation deviceOrientation; + public Context mContext; + public Activity mActivity; + public ArrayList mLocationMarkers = new ArrayList<>(); // Anchors are currently re-drawn on an interval. There are likely better // ways of doing this, however it's sufficient for now. - private final static int ANCHOR_REFRESH_INTERVAL = 1000 * 5; // 5 seconds - public static Context mContext; - public static Activity mActivity; - public static ArSceneView mArSceneView; - - public ArrayList mLocationMarkers = new ArrayList<>(); - - public static DeviceLocation deviceLocation; - public static DeviceOrientation deviceOrientation; - + private int anchorRefreshInterval = 1000 * 5; // 5 seconds // Limit of where to draw markers within AR scene. // They will auto scale, but this helps prevents rendering issues - public static int distanceLimit = 20; - + private int distanceLimit = 20; + private boolean offsetOverlapping = false; // Bearing adjustment. Can be set to calibrate with true north private int bearingAdjustment = 0; - private String TAG = "LocationScene"; private boolean anchorsNeedRefresh = true; private Handler mHandler = new Handler(); - Runnable anchorRefreshTask = new Runnable() { @Override public void run() { anchorsNeedRefresh = true; - mHandler.postDelayed(anchorRefreshTask, ANCHOR_REFRESH_INTERVAL); + mHandler.postDelayed(anchorRefreshTask, anchorRefreshInterval); } }; private Session mSession; @@ -71,16 +60,71 @@ public LocationScene(Context mContext, Activity mActivity, ArSceneView mArSceneV startCalculationTask(); - deviceLocation = new DeviceLocation(); - deviceOrientation = new DeviceOrientation(); + deviceLocation = new DeviceLocation(this); + deviceOrientation = new DeviceOrientation(this); deviceOrientation.resume(); } + public int getAnchorRefreshInterval() { + return anchorRefreshInterval; + } + + /** + * Set the interval at which anchors should be automatically re-calculated. + * + * @param anchorRefreshInterval + */ + public void setAnchorRefreshInterval(int anchorRefreshInterval) { + this.anchorRefreshInterval = anchorRefreshInterval; + stopCalculationTask(); + startCalculationTask(); + } + + /** + * The distance cap for distant markers. + * ARCore doesn't like markers that are 2000km away :/ + * + * @return + */ + public int getDistanceLimit() { + return distanceLimit; + } + + /** + * The distance cap for distant markers. + * ARCore doesn't like markers that are 2000km away :/ + * Default 20 + */ + public void setDistanceLimit(int distanceLimit) { + this.distanceLimit = distanceLimit; + } + + public boolean shouldOffsetOverlapping() { + return offsetOverlapping; + } + + /** + * Attempts to raise markers vertically when they overlap. + * Needs work! + * + * @param offsetOverlapping + */ + public void setOffsetOverlapping(boolean offsetOverlapping) { + this.offsetOverlapping = offsetOverlapping; + } + public void processFrame(Frame frame) { refreshAnchorsIfRequired(frame); } - public void refreshAnchorsIfRequired(Frame frame) { + /** + * Force anchors to be re-calculated + */ + public void refreshAnchors() { + anchorsNeedRefresh = true; + } + + private void refreshAnchorsIfRequired(Frame frame) { if (anchorsNeedRefresh) { anchorsNeedRefresh = false; @@ -147,13 +191,14 @@ public void refreshAnchorsIfRequired(Frame frame) { frame.getCamera().getPose() .compose(Pose.makeTranslation(xRotated, y + (float) heightAdjustment, zRotated))); - mLocationMarkers.get(i).anchorNode = new LocationNode(newAnchor, mLocationMarkers.get(i)); + mLocationMarkers.get(i).anchorNode = new LocationNode(newAnchor, mLocationMarkers.get(i), this); mLocationMarkers.get(i).anchorNode.setParent(mArSceneView.getScene()); mLocationMarkers.get(i).anchorNode.addChild(mLocationMarkers.get(i).node); - if(mLocationMarkers.get(i).getRenderEvent() != null) { + if (mLocationMarkers.get(i).getRenderEvent() != null) { mLocationMarkers.get(i).anchorNode.setRenderEvent(mLocationMarkers.get(i).getRenderEvent()); } + mLocationMarkers.get(i).anchorNode.setHeight(mLocationMarkers.get(i).getHeight()); } catch (Exception e) { e.printStackTrace(); @@ -163,20 +208,36 @@ public void refreshAnchorsIfRequired(Frame frame) { } } - + /** + * Adjustment for compass bearing. + * + * @return + */ public int getBearingAdjustment() { return bearingAdjustment; } + /** + * Adjustment for compass bearing. + * You may use this for a custom method of improving precision. + * + * @param i + */ public void setBearingAdjustment(int i) { bearingAdjustment = i; anchorsNeedRefresh = true; } + /** + * Resume sensor services. Important! + */ public void resume() { deviceOrientation.resume(); } + /** + * Pause sensor services. Important! + */ public void pause() { deviceOrientation.pause(); } diff --git a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/rendering/LocationNode.java b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/rendering/LocationNode.java index cc05cac..7c5e4d2 100644 --- a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/rendering/LocationNode.java +++ b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/rendering/LocationNode.java @@ -1,5 +1,7 @@ package uk.co.appoly.arcorelocation.rendering; +import android.util.Log; + import com.google.ar.core.Anchor; import com.google.ar.sceneform.AnchorNode; import com.google.ar.sceneform.FrameTime; @@ -13,14 +15,28 @@ public class LocationNode extends AnchorNode { - LocationMarker locationMarker; + private String TAG = "LocationNode"; + + private LocationMarker locationMarker; private LocationNodeRender renderEvent; private int distance; private float scaleModifier = 1F; + private float height = 0F; + private boolean scaleAtDistance = true; + private LocationScene locationScene; - public LocationNode(Anchor anchor, LocationMarker locationMarker) { + public LocationNode(Anchor anchor, LocationMarker locationMarker, LocationScene locationScene) { super(anchor); this.locationMarker = locationMarker; + this.locationScene = locationScene; + } + + public float getHeight() { + return height; + } + + public void setHeight(float height) { + this.height = height; } public float getScaleModifier() { @@ -47,6 +63,14 @@ public void setDistance(int distance) { this.distance = distance; } + public boolean shouldScaleAtDistance() { + return scaleAtDistance; + } + + public void setScaleAtDistance(boolean scaleAtDistance) { + this.scaleAtDistance = scaleAtDistance; + } + @Override public void onUpdate(FrameTime frameTime) { @@ -64,9 +88,9 @@ public void onUpdate(FrameTime frameTime) { int markerDistance = (int) Math.ceil( LocationUtils.distance( locationMarker.latitude, - LocationScene.deviceLocation.currentBestLocation.getLatitude(), + locationScene.deviceLocation.currentBestLocation.getLatitude(), locationMarker.longitude, - LocationScene.deviceLocation.currentBestLocation.getLongitude(), + locationScene.deviceLocation.currentBestLocation.getLongitude(), 0, 0) ); @@ -76,11 +100,14 @@ public void onUpdate(FrameTime frameTime) { // Limit the distance of the Anchor within the scene. // Prevents uk.co.appoly.arcorelocation.rendering issues. int renderDistance = markerDistance; - if (renderDistance > LocationScene.distanceLimit) - renderDistance = LocationScene.distanceLimit; + if (renderDistance > locationScene.getDistanceLimit()) + renderDistance = locationScene.getDistanceLimit(); + + float scale = 1F; // Make sure marker stays the same size on screen, no matter the distance - float scale = 0.5F * (float) renderDistance; + if (shouldScaleAtDistance()) + scale = 0.5F * (float) renderDistance; // Distant markers a little smaller if (markerDistance > 3000) @@ -89,15 +116,21 @@ public void onUpdate(FrameTime frameTime) { scale *= scaleModifier; Vector3 cameraPosition = getScene().getCamera().getWorldPosition(); - Vector3 cardPosition = n.getWorldPosition(); - n.setWorldPosition(new Vector3(n.getWorldPosition().x, 0F, n.getWorldPosition().z)); - Vector3 direction = Vector3.subtract(cameraPosition, cardPosition); + Vector3 nodePosition = n.getWorldPosition(); + n.setWorldPosition(new Vector3(n.getWorldPosition().x, getHeight(), n.getWorldPosition().z)); + Vector3 direction = Vector3.subtract(cameraPosition, nodePosition); Quaternion lookRotation = Quaternion.lookRotation(direction, Vector3.up()); n.setWorldRotation(lookRotation); //locationMarker.node.setWorldScale(new Vector3(scale, scale, scale)); n.setWorldScale(new Vector3(scale, scale, scale)); + if (locationScene.shouldOffsetOverlapping()) { + if (locationScene.mArSceneView.getScene().overlapTestAll(n).size() > 0) { + setHeight(getHeight() + 1.2F); + } + } + if (renderEvent != null) { renderEvent.render(this); } diff --git a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceLocation.java b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceLocation.java index 10354e8..a4f0ea3 100644 --- a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceLocation.java +++ b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceLocation.java @@ -19,12 +19,14 @@ public class DeviceLocation implements LocationListener { private static final int TWO_MINUTES = 1000 * 60 * 2; public Location currentBestLocation; private LocationManager locationManager; + private LocationScene locationScene; - public DeviceLocation() { + public DeviceLocation(LocationScene locationScene) { + this.locationScene = locationScene; try { // Getting LocationManager object - locationManager = (LocationManager) LocationScene.mContext.getSystemService(Context.LOCATION_SERVICE); + locationManager = (LocationManager) locationScene.mContext.getSystemService(Context.LOCATION_SERVICE); // Creating an empty criteria object @@ -40,7 +42,7 @@ public DeviceLocation() { } catch (SecurityException e) { - Toast.makeText(LocationScene.mContext, "Enable location permissions from settings", Toast.LENGTH_SHORT).show(); + Toast.makeText(locationScene.mContext, "Enable location permissions from settings", Toast.LENGTH_SHORT).show(); } } diff --git a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceOrientation.java b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceOrientation.java index c26c0c3..ee2ccef 100644 --- a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceOrientation.java +++ b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceOrientation.java @@ -5,6 +5,7 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.location.Location; import uk.co.appoly.arcorelocation.LocationScene; @@ -27,12 +28,14 @@ public class DeviceOrientation implements SensorEventListener { private float azimuth; public float pitch; public float roll; + private LocationScene locationScene; // North public float currentDegree = 0f; - public DeviceOrientation() { - mSensorManager = (SensorManager) LocationScene.mContext.getSystemService(Context.SENSOR_SERVICE); + public DeviceOrientation(LocationScene locationScene) { + this.locationScene = locationScene; + mSensorManager = (SensorManager) locationScene.mContext.getSystemService(Context.SENSOR_SERVICE); } @Override diff --git a/examples/sceneform/app/build.gradle b/examples/sceneform/app/build.gradle index a04eebe..6cc127a 100644 --- a/examples/sceneform/app/build.gradle +++ b/examples/sceneform/app/build.gradle @@ -53,10 +53,10 @@ android { dependencies { implementation "com.google.ar.sceneform:core:1.0.0" implementation "com.google.ar.sceneform.ux:sceneform-ux:1.0.0" - implementation "com.android.support:appcompat-v7:27.1.0" - implementation "com.android.support:design:27.1.0" + implementation "com.android.support:appcompat-v7:27.1.1" + implementation "com.android.support:design:27.1.1" //implementation 'uk.co.appoly.arcorelocation:arcore-location-debug@aar' - implementation 'com.github.appoly:ARCore-Location:1.0.2' + implementation 'com.github.appoly:ARCore-Location:1.0.3' } repositories { diff --git a/examples/sceneform/app/libs/arcore-location-debug.aar b/examples/sceneform/app/libs/arcore-location-debug.aar index 12194c2..ae14e0c 100644 Binary files a/examples/sceneform/app/libs/arcore-location-debug.aar and b/examples/sceneform/app/libs/arcore-location-debug.aar differ diff --git a/examples/sceneform/app/src/main/java/uk/co/appoly/sceneform_example/LocationActivity.java b/examples/sceneform/app/src/main/java/uk/co/appoly/sceneform_example/LocationActivity.java index fc8f606..4adc60e 100644 --- a/examples/sceneform/app/src/main/java/uk/co/appoly/sceneform_example/LocationActivity.java +++ b/examples/sceneform/app/src/main/java/uk/co/appoly/sceneform_example/LocationActivity.java @@ -20,8 +20,6 @@ import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; -import android.view.GestureDetector; -import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.TextView; @@ -34,7 +32,6 @@ import com.google.ar.core.exceptions.CameraNotAvailableException; import com.google.ar.core.exceptions.UnavailableException; import com.google.ar.sceneform.ArSceneView; -import com.google.ar.sceneform.HitTestResult; import com.google.ar.sceneform.Node; import com.google.ar.sceneform.rendering.ModelRenderable; import com.google.ar.sceneform.rendering.ViewRenderable; @@ -59,6 +56,7 @@ public class LocationActivity extends AppCompatActivity { private ArSceneView arSceneView; + // Renderables for this example private ModelRenderable andyRenderable; private ViewRenderable exampleLayoutRenderable; @@ -120,15 +118,20 @@ protected void onCreate(Bundle savedInstanceState) { frameTime -> { if (locationScene == null) { - + // If our locationScene object hasn't been setup yet, this is a good time to do it + // We know that here, the AR components have been initiated. locationScene = new LocationScene(this, this, arSceneView); + // Now lets create our location markers. + // First, a layout LocationMarker layoutLocationMarker = new LocationMarker( -4.849509, 42.814603, getExampleView() ); + // An example "onRender" event, called every frame + // Updates the layout with the markers distance layoutLocationMarker.setRenderEvent(new LocationNodeRender() { @Override public void render(LocationNode node) { @@ -137,9 +140,10 @@ public void render(LocationNode node) { distanceTextView.setText(node.getDistance() + "M"); } }); - + // Adding the marker locationScene.mLocationMarkers.add(layoutLocationMarker); + // Adding a simple location marker of a 3D model locationScene.mLocationMarkers.add( new LocationMarker( -0.119677, @@ -170,10 +174,15 @@ public void render(LocationNode node) { }); - // Lastly request CAMERA permission which is required by ARCore. + // Lastly request CAMERA & fine location permission which is required by ARCore-Location. ARLocationPermissionHelper.requestPermission(this); } + /** + * Example node of a layout + * + * @return + */ private Node getExampleView() { Node base = new Node(); base.setRenderable(exampleLayoutRenderable); @@ -190,6 +199,11 @@ private Node getExampleView() { return base; } + /*** + * Example Node of a 3D model + * + * @return + */ private Node getAndy() { Node base = new Node(); base.setRenderable(andyRenderable); @@ -202,6 +216,9 @@ private Node getAndy() { return base; } + /** + * Make sure we call locationScene.resume(); + */ @Override protected void onResume() { super.onResume(); @@ -239,6 +256,9 @@ protected void onResume() { } } + /** + * Make sure we call locationScene.pause(); + */ @Override public void onPause() { super.onPause();