ARKit - place object without touching the screen - c#

Using ARKit I can tap on the surface to place 3D object here. I also can move my finger and thus move the object along the surface.
How can I make an object to automatically appear and stick to the surface in front of the camera, with no need in touching the screen?
Here is the example script for placing 3D objects by finger tap:
using System;
using System.Collections.Generic;
namespace UnityEngine.XR.iOS
{
public class UnityARHitTestExample : MonoBehaviour
{
public Transform m_HitTransform;
bool HitTestWithResultType (ARPoint point, ARHitTestResultType resultTypes)
{
List<ARHitTestResult> hitResults = UnityARSessionNativeInterface.GetARSessionNativeInterface ().HitTest (point, resultTypes);
if (hitResults.Count > 0) {
foreach (var hitResult in hitResults) {
Debug.Log ("Got hit!");
m_HitTransform.position = UnityARMatrixOps.GetPosition (hitResult.worldTransform);
m_HitTransform.rotation = UnityARMatrixOps.GetRotation (hitResult.worldTransform);
Debug.Log (string.Format ("x:{0:0.######} y:{1:0.######} z:{2:0.######}", m_HitTransform.position.x, m_HitTransform.position.y, m_HitTransform.position.z));
return true;
}
}
return false;
}
// Update is called once per frame
void Update () {
if (Input.touchCount > 0 && m_HitTransform != null)
{
var touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began || touch.phase == TouchPhase.Moved)
{
var screenPosition = Camera.main.ScreenToViewportPoint(touch.position);
ARPoint point = new ARPoint {
x = screenPosition.x,
y = screenPosition.y
};
// prioritize reults types
ARHitTestResultType[] resultTypes = {
ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent,
// if you want to use infinite planes use this:
//ARHitTestResultType.ARHitTestResultTypeExistingPlane,
ARHitTestResultType.ARHitTestResultTypeHorizontalPlane,
ARHitTestResultType.ARHitTestResultTypeFeaturePoint
};
foreach (ARHitTestResultType resultType in resultTypes)
{
if (HitTestWithResultType (point, resultType))
{
return;
}
}
}
}
}
}
}

I've done something similar in Objective-C, so hopefully I can help with your unity example.
My logic was basically that placing the objects using a tap is based on the hitTest function with a point provided to it retrieved from the touch location. So I just created a CGPoint programmatically in the center of the screen and ran the hitTest function with this point. Had it hooked to a timer, and when a hit returned objects were added there.
CGPoint point = CGPointMake(self.sceneView.frame.size.width / 2, self.sceneView.frame.size.height / 2); //Get a point at the middle of the screen
NSArray <ARHitTestResult *> *hitResults = [_sceneView hitTest:point types:ARHitTestResultTypeFeaturePoint]; //Try to do a AR Hit Test on this point
if ([hitResults count] != 0) { //If have any results
//Perform desired operation
}

Related

Unity lineRenderer takes the 0,0 point as center

In my 2D unity mobile game, when I touch and pull the ball it shows up a lineRenderer from the ball through the way I pull there is no problem with that but if I touch to screen while the ball is dynamic (before it stopped) the new lineRenderer take (0,0) point as the center instead of the location of the ball.
This is how it works properly when I touch the screen while the ball is not moving
This is the problematic version line renderer starts from the point (0,0) instead of the ball
void Update()
{
if (Input.touchCount > 0 && !hasMoved)
{
touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
dragStart();
}
if (touch.phase == TouchPhase.Moved)
{
Dragging();
}
if (touch.phase == TouchPhase.Ended)
{
DragRelease();
}
}
}
void dragStart()
{
dragStartposition = transform.position;
dragStartposition.z = 0;
gostergeLine.positionCount = 1;
gostergeLine.SetPosition(0,transform.position);
}
void Dragging()
{
Vector3 draggingPos = Camera.main.ScreenToWorldPoint(touch.position);
draggingPos.z = 0;
gostergeLine.positionCount = 2;
gostergeLine.SetPosition(1, draggingPos);
}
void DragRelease()
{
gostergeLine.positionCount = 0;
Vector3 dragReleasePos = Camera.main.ScreenToWorldPoint(touch.position);
dragReleasePos.z = 0;
Vector3 forceVec = dragStartposition - dragReleasePos;
forcePower = forceVec.magnitude;
if(forcePower > 45)
{
topRb.velocity = forceVec * 45;
}
else
{
topRb.velocity = forceVec * forcePower;
}
}
Even though it does not read my touch while the ball is moving (!hasmoved); if I touch the screen before it stopped, the new linerenderer shows up in wrong direction.
Update is called every frame.
The TouchPhase.Began is only true in the very first frame of a touch -> your ball was moving then so you ignored it
Now you continue touching so eventually TouchPhase.Moved is true at some point
=> Since the ball stopped in the meantime you now call Dragging without having called dragStart before.
You could make sure that the other cases are only called at all if dragStart was actually called first.
You could e.g. use a nullable
private Vector2? dragStartposition;
and then check for it in
void Dragging()
{
if(dragStartposition == null) return;
...
}
and also reset it in
void DragRelease()
{
if(dragStartposition == null) return;
...
Vector3 forceVec = dragStartposition.Value - dragReleasePos;
...
dragStartposition = null;
}
This way if a touch started to early it doesn't trigger the other methods, only if one started after the ball has stopped.
There is still a pitfall though with multiple touches
=> I would also use exactly one touch only
if(Input.touchCount == 1 && !hasMoved)

Dynamically create a plane between 4 known points in Unity ARCore for Android

I am trying to dynamically create a plane between 4 known points. The 4 points are created by the user touching the phone screen and a cylinder is placed where they touched, very similar to HelloAR tutorial except with cylinders and not Andys. This is working, the 4 cylinders get created. In a hypothetical situation the user is going to create a square with the 4 points, let's say 2 meters by 2 meters. I then want to dynamically place a plane that fits into the 4 points (it doesn't have to fit exactly, just roughly). Using adb I have validated that the CreateBricks method is called and the plane is instantiated with the correct transform. I just don't see the plane get created.
Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using GoogleARCore;
public class SceneController : MonoBehaviour {
public Camera firstPersonCamera;
public Text anchorcount;
public GameObject pointPrefab; //The cylinder prefab
public GameObject patioPrefab; //The plane prefab
public ArrayList pointArray;
private int anchorCount;
private bool m_IsQuitting = false;
private bool brickCreated = false;
// Use this for initialization
void Start () {
QuitOnConnectionErrors();
anchorCount = 0;
pointArray = new ArrayList();
brickCreated = false;
}
// Update is called once per frame
void Update () {
_UpdateApplicationLifecycle();
if (anchorCount < 4)
ProcessTouches();
else if(!brickCreated)
CreateBricks();
}
private void CreateBricks()
{
try
{
brickCreated = true;
Debug.Log("CreateBricks");
float dist1 = Vector3.Distance(((GameObject)pointArray[0]).transform.position, ((GameObject)pointArray[1]).transform.position);
float dist2 = Vector3.Distance(((GameObject)pointArray[0]).transform.position, ((GameObject)pointArray[3]).transform.position);
Debug.Log("dist1:" + dist1.ToString());
Debug.Log("dist2:" + dist2.ToString());
GameObject prefab;
prefab = patioPrefab;
// Instantiate Andy model at the hit pose.
var pointObject = Instantiate(prefab);
prefab.transform.position = ((GameObject)pointArray[0]).transform.position;
prefab.transform.position.Scale(new Vector3(dist1, 0.5f, dist2));
Debug.Log(prefab.transform.position.x.ToString() + " " + prefab.transform.position.y.ToString() + " " + prefab.transform.position.z.ToString());
}
catch (System.Exception ex)
{
Debug.Log(ex.Message);
}
}
void ProcessTouches()
{
Touch touch;
if (Input.touchCount != 1 ||
(touch = Input.GetTouch(0)).phase != TouchPhase.Began)
{
return;
}
// Raycast against the location the player touched to search for planes.
TrackableHit hit;
TrackableHitFlags raycastFilter = TrackableHitFlags.PlaneWithinPolygon |
TrackableHitFlags.FeaturePointWithSurfaceNormal;
if (Frame.Raycast(touch.position.x, touch.position.y, raycastFilter, out hit))
{
// Use hit pose and camera pose to check if hittest is from the
// back of the plane, if it is, no need to create the anchor.
if ((hit.Trackable is DetectedPlane) &&
Vector3.Dot(firstPersonCamera.transform.position - hit.Pose.position,
hit.Pose.rotation * Vector3.up) < 0)
{
Debug.Log("Hit at back of the current DetectedPlane");
}
else
{
// Choose the Andy model for the Trackable that got hit.
GameObject prefab;
prefab = pointPrefab;
// Instantiate Andy model at the hit pose.
var pointObject = Instantiate(prefab, hit.Pose.position, hit.Pose.rotation);
// Compensate for the hitPose rotation facing away from the raycast (i.e. camera).
//andyObject.transform.Rotate(0, k_ModelRotation, 0, Space.Self);
// Create an anchor to allow ARCore to track the hitpoint as understanding of the physical
// world evolves.
var anchor = hit.Trackable.CreateAnchor(hit.Pose);
// Make Andy model a child of the anchor.
pointObject.transform.parent = anchor.transform;
pointArray.Add(pointObject);
anchorCount++;
anchorcount.text = "Points: " + anchorCount;
}
}
}
private void _UpdateApplicationLifecycle()
{
// Exit the app when the 'back' button is pressed.
if (Input.GetKey(KeyCode.Escape))
{
Application.Quit();
}
// Only allow the screen to sleep when not tracking.
if (Session.Status != SessionStatus.Tracking)
{
const int lostTrackingSleepTimeout = 15;
Screen.sleepTimeout = lostTrackingSleepTimeout;
}
else
{
Screen.sleepTimeout = SleepTimeout.NeverSleep;
}
if (m_IsQuitting)
{
return;
}
QuitOnConnectionErrors();
}
private void _ShowAndroidToastMessage(string message)
{
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject unityActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
if (unityActivity != null)
{
AndroidJavaClass toastClass = new AndroidJavaClass("android.widget.Toast");
unityActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
{
AndroidJavaObject toastObject = toastClass.CallStatic<AndroidJavaObject>("makeText", unityActivity,
message, 0);
toastObject.Call("show");
}));
}
}
void QuitOnConnectionErrors()
{
// Quit if ARCore was unable to connect and give Unity some time for the toast to appear.
if (Session.Status == SessionStatus.ErrorPermissionNotGranted)
{
_ShowAndroidToastMessage("Camera permission is needed to run this application.");
m_IsQuitting = true;
Invoke("_DoQuit", 0.5f);
}
else if (Session.Status.IsError())
{
_ShowAndroidToastMessage("ARCore encountered a problem connecting. Please start the app again.");
m_IsQuitting = true;
Invoke("_DoQuit", 0.5f);
}
}
}
The issue with the above code was resolved by setting the parent of the pointObject variable in the Instantiate object. The plane then showed up relative to the parent object.

ARkit HitCube Example, stop updating

Here is the UnityARHitTestExample.cs document. My aim is to have the object be placed once (one hit). After that I don't want the object to be moved after the first hit. Any help/suggestions will be helpful.
using System;
using System.Collections.Generic;
namespace UnityEngine.XR.iOS
{
public class UnityARHitTestExample : MonoBehaviour
{
public Transform m_HitTransform;
public float maxRayDistance = 30.0f;
public LayerMask collisionLayer = 1 << 10; //ARKitPlane layer
bool HitTestWithResultType (ARPoint point, ARHitTestResultType resultTypes)
{
List<ARHitTestResult> hitResults = UnityARSessionNativeInterface.GetARSessionNativeInterface ().HitTest (point, resultTypes);
if (hitResults.Count > 0) {
foreach (var hitResult in hitResults) {
Debug.Log ("Got hit!");
m_HitTransform.position = UnityARMatrixOps.GetPosition (hitResult.worldTransform);
m_HitTransform.rotation = UnityARMatrixOps.GetRotation (hitResult.worldTransform);
Debug.Log (string.Format ("x:{0:0.######} y:{1:0.######} z:{2:0.######}", m_HitTransform.position.x, m_HitTransform.position.y, m_HitTransform.position.z));
return false;
}
}
return false;
}
// Update is called once per frame
void Update () {
#if UNITY_EDITOR //we will only use this script on the editor side, though there is nothing that would prevent it from working on device
if (Input.GetMouseButtonDown ()) {
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
RaycastHit hit;
//we'll try to hit one of the plane collider gameobjects that were generated by the plugin
//effectively similar to calling HitTest with ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent
if (Physics.Raycast (ray, out hit, maxRayDistance, collisionLayer)) {
//we're going to get the position from the contact point
m_HitTransform.position = hit.point;
Debug.Log (string.Format ("x:{0:0.######} y:{1:0.######} z:{2:0.######}", m_HitTransform.position.x, m_HitTransform.position.y, m_HitTransform.position.z));
//and the rotation from the transform of the plane collider
m_HitTransform.rotation = hit.transform.rotation;
}
}
#else
if (Input.touchCount > 0 && m_HitTransform != null)
{
var touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began || touch.phase == TouchPhase.Moved)
{
var screenPosition = Camera.main.ScreenToViewportPoint(touch.position);
ARPoint point = new ARPoint {
x = screenPosition.x,
y = screenPosition.y
};
// prioritize reults types
ARHitTestResultType[] resultTypes = {
ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent,
// if you want to use infinite planes use this:
//ARHitTestResultType.ARHitTestResultTypeExistingPlane,
ARHitTestResultType.ARHitTestResultTypeHorizontalPlane,
ARHitTestResultType.ARHitTestResultTypeFeaturePoint
};
foreach (ARHitTestResultType resultType inresultTypes)
{
if (HitTestWithResultType (point, resultType))
{
return;
}
}
}
}
#endif
}
}
}A
create a bool to check if object is placed already.
bool placed = false;
Execute codes inside Update() only if not placed:
void Update () {
if (!placed){
//code
}
}
now set placed to true after placing object
if (HitTestWithResultType (point, resultType))
{
placed = true;
return;
}

Detect swipes without lifting the finger in Unity [duplicate]

This question already has answers here:
Detect swipe gesture direction
(5 answers)
Closed 5 years ago.
I'm implementing specific type of touch controller.
The player needs to hold their finger on screen in order to move.
Without lifting the finger, the player can swipe in different direction to change direction while still moving.
Once the finger is lifted, the player stops moving.
It's been a hard time isolating specific swipes (i.e. lines drawn on screen) ignoring any other movements that are not intended to draw a line.
For instance slight finger movements done when the player's finger is "stationary", they mess up my algorithm.
I thought about different approaches such as storing the last few touches and evaluate upon them to determine if there has been a swipe or not, but couldn't implement it properly.
Here's what I've tried so far. Most of the time it works fine, but often the player does erratic movement and goes absolutely the opposite direction to what I intended.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TouchInputHandler : AbstractInputHandler {
private const int SWIPE_MIN_DISTANCE = 35;
private Vector2 touchStartPoint;
private Vector2 touchMovePoint;
void Update () {
Touch[] touches = Input.touches;
if (touches.Length == 1) {
Touch firstTouch = touches [0];
if (firstTouch.phase == TouchPhase.Began) {
this.touchStartPoint = firstTouch.position;
fireNextDirectionChanged (currentDirection);
} else if (firstTouch.phase == TouchPhase.Moved) {
this.touchMovePoint = firstTouch.position;
if (Vector2.Distance(touchStartPoint, touchMovePoint) > SWIPE_MIN_DISTANCE) {
detectSwipeDirection ();
}
} else if (firstTouch.phase == TouchPhase.Stationary) {
touchStartPoint.x = touchMovePoint.x;
touchStartPoint.y = touchMovePoint.y;
} else if (firstTouch.phase == TouchPhase.Ended) {
fireNextDirectionChanged (Constants.Direction.NONE);
}
}
}
private void detectSwipeDirection() {
float xDiff = touchMovePoint.x - touchStartPoint.x;
float yDiff = touchMovePoint.y - touchStartPoint.y;
Constants.Direction nextDirection;
bool yGreater = Mathf.Abs(yDiff) >= Mathf.Abs(xDiff);
if (yGreater) {
// direction is up or down
nextDirection = yDiff < 0 ? Constants.Direction.DOWN : Constants.Direction.UP;
} else {
// direction is left or right
nextDirection = xDiff < 0 ? Constants.Direction.LEFT : Constants.Direction.RIGHT;
}
if (nextDirection != this.currentDirection)
{
fireNextDirectionChanged (nextDirection);
this.currentDirection = nextDirection;
}
}
}
I think you simply forgot a line setting the previous touch position to the current one in the TouchPhase.Moved block. Just add touchStartPoint = this.touchMovePoint; and it should work (only tested using mouse inputs but logic remains the same).
Also I commented the TouchPhase.Stationary block: I feel like it does what I suggested before but only when the finger is completely immobile. The final code looks like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TouchInputHandler : AbstractInputHandler
{
private const int SWIPE_MIN_DISTANCE = 35;
private Vector2 touchStartPoint;
private Vector2 touchMovePoint;
void Update()
{
Touch[] touches = Input.touches;
if(touches.Length == 1)
{
Touch firstTouch = touches[0];
if(firstTouch.phase == TouchPhase.Began)
{
this.touchStartPoint = firstTouch.position;
this.currentDirection = Constants.Direction.NONE;
fireNextDirectionChanged(currentDirection);
}
else if(firstTouch.phase == TouchPhase.Moved)
{
this.touchMovePoint = firstTouch.position;
if(Vector2.Distance(touchStartPoint, touchMovePoint) > SWIPE_MIN_DISTANCE)
{
detectSwipeDirection();
}
touchStartPoint = this.touchMovePoint; // <= NEW !
}
//else if(firstTouch.phase == TouchPhase.Stationary)
//{
// touchStartPoint.x = touchMovePoint.x;
// touchStartPoint.y = touchMovePoint.y;
//}
else if(firstTouch.phase == TouchPhase.Ended)
{
this.currentDirection = Constants.Direction.NONE;
fireNextDirectionChanged(Constants.Direction.NONE);
}
}
}
private void detectSwipeDirection()
{
float xDiff = touchMovePoint.x - touchStartPoint.x;
float yDiff = touchMovePoint.y - touchStartPoint.y;
Constants.Direction nextDirection;
bool yGreater = Mathf.Abs(yDiff) >= Mathf.Abs(xDiff);
if(yGreater)
{
// direction is up or down
nextDirection = yDiff < 0 ? Constants.Direction.DOWN : Constants.Direction.UP;
}
else
{
// direction is left or right
nextDirection = xDiff < 0 ? Constants.Direction.LEFT : Constants.Direction.RIGHT;
}
if(nextDirection != this.currentDirection)
{
fireNextDirectionChanged(nextDirection);
this.currentDirection = nextDirection;
}
}
}
Also when using a distance based on in game distance to detect swipe, I'd suggest adding a Screen.width/height ratio somewhere if you want to port your project to multiple resolutions devices (on Start() do something like SWIPE_MIN_DISTANCE *= Screen.width / BASE_SCREEN_WIDTH with private const int BASE_SCREEN_WIDTH = 1024;).
Hope this helps,

Repeating graphics withing view

In Unity2D, I have made a script to repeat the background sprite just before the camera can see the end.
Here's my code:
using UnityEngine;
using System.Collections;
[RequireComponent (typeof(SpriteRenderer))]
public class Tiling : MonoBehaviour {
public int offsetX = 2; // the offset so that we don't get any weird errors
// these are used for checking if we need to instantiate stuff
public bool hasARightBuddy = false;
public bool hasALeftBuddy = false;
public bool reverseScale = false; // used if the object is not tilable
private float spriteWidth = 0f; // the width of our element
private Camera cam;
private Transform myTransform;
private float localSc;
void Awake () {
cam = Camera.main;
myTransform = transform;
localSc = transform.localScale.x;
}
// Use this for initialization
void Start () {
SpriteRenderer sRenderer = GetComponent<SpriteRenderer>();
spriteWidth = sRenderer.sprite.bounds.size.x * transform.localScale.x;
}
// Update is called once per frame
void Update () {
// does it still need buddies? If not do nothing
if (hasALeftBuddy == false || hasARightBuddy == false) {
// calculate the cameras extend (half the width) of what the camera can see in world coordinates
float camHorizontalExtend = cam.orthographicSize * Screen.width/Screen.height;
// calculate the x position where the camera can see the edge of the sprite (element)
float edgeVisiblePositionRight = (myTransform.position.x + spriteWidth/2) - camHorizontalExtend;
float edgeVisiblePositionLeft = (myTransform.position.x - spriteWidth/2) + camHorizontalExtend;
// checking if we can see the edge of the element and then calling MakeNewBuddy if we can
if (cam.transform.position.x >= edgeVisiblePositionRight - offsetX && hasARightBuddy == false)
{
MakeNewBuddy (1);
hasARightBuddy = true;
}
else if (cam.transform.position.x <= edgeVisiblePositionLeft + offsetX && hasALeftBuddy == false)
{
MakeNewBuddy (-1);
hasALeftBuddy = true;
}
}
}
// a function that creates a buddy on the side required
void MakeNewBuddy (int rightOrLeft) {
// calculating the new position for our new buddy
Vector3 newPosition = new Vector3 (myTransform.position.x + spriteWidth * rightOrLeft, myTransform.position.y, myTransform.position.z);
// instantating our new body and storing him in a variable
Transform newBuddy = Instantiate (myTransform, newPosition, myTransform.rotation) as Transform;
newBuddy.parent = myTransform.parent;
// if not tilable let's reverse the x size on our object to get rid of missmatches
if (reverseScale == true) {
newBuddy.localScale = new Vector3 (localSc*-1 , 1, 1);
}
if (rightOrLeft == 1) { //if this function was called to make a right buddy (1)
newBuddy.GetComponent<Tiling>().hasALeftBuddy = true;
}
else { //else we just made a left buddy, so it has a right copy
newBuddy.GetComponent<Tiling>().hasARightBuddy = true;
}
}
Now, the script is attached to the background sprite and it works fine.
You'll see that there's a bool reverseScale to reverse the image.
This is because if the image is not repeatable, (the end and the start to not match on a pixel level) we can mirror it by reverting (* -1) the x scale.
The strange thing is, if I launch this with reverseScale disabled, eveything works as I said. If I enable reverseScale, it becomes a mess. Infinite loop of overlapping, badly scaled sprites, crashing the game.
What am I missing? Worst case (but still shouldn't happen), that code snippet should make an image that doesn't match, why is it breaking the program?
EDIT:
I found a solution thanks to Enfyve answer. I was flipping the scale of the whole graphic component instead oa single one for come reason. the flipX field should be used instead. Also, only one every two tiles has to be flipped, to avoid missmatches.
SpriteRenderer already contains a property to draw a sprite flipped, use flipX instead.

Categories

Resources