Added the iOS 11 ARKitAudio sample.
|
@ -0,0 +1,31 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27205.2004
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ARKitAudio", "ARKitAudio\ARKitAudio.csproj", "{BC14A9A0-F771-4C15-BCD7-7974A2A71260}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|iPhone = Debug|iPhone
|
||||
Debug|iPhoneSimulator = Debug|iPhoneSimulator
|
||||
Release|iPhone = Release|iPhone
|
||||
Release|iPhoneSimulator = Release|iPhoneSimulator
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BC14A9A0-F771-4C15-BCD7-7974A2A71260}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||
{BC14A9A0-F771-4C15-BCD7-7974A2A71260}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||
{BC14A9A0-F771-4C15-BCD7-7974A2A71260}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||
{BC14A9A0-F771-4C15-BCD7-7974A2A71260}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||
{BC14A9A0-F771-4C15-BCD7-7974A2A71260}.Release|iPhone.ActiveCfg = Release|iPhone
|
||||
{BC14A9A0-F771-4C15-BCD7-7974A2A71260}.Release|iPhone.Build.0 = Release|iPhone
|
||||
{BC14A9A0-F771-4C15-BCD7-7974A2A71260}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||
{BC14A9A0-F771-4C15-BCD7-7974A2A71260}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {04A35DA2-FEED-451C-B35B-3CDFD32AE9FB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,144 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform>
|
||||
<ProjectTypeGuids>{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<ProjectGuid>{BC14A9A0-F771-4C15-BCD7-7974A2A71260}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>ARKitAudio</RootNamespace>
|
||||
<IPhoneResourcePrefix>Resources</IPhoneResourcePrefix>
|
||||
<AssemblyName>ARKitAudio</AssemblyName>
|
||||
<UseMSBuildEngine>True</UseMSBuildEngine>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\iPhoneSimulator\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
<MtouchArch>x86_64</MtouchArch>
|
||||
<MtouchLink>None</MtouchLink>
|
||||
<MtouchDebug>true</MtouchDebug>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\iPhoneSimulator\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<MtouchLink>None</MtouchLink>
|
||||
<MtouchArch>x86_64</MtouchArch>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\iPhone\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
<MtouchArch>ARM64</MtouchArch>
|
||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||
<MtouchDebug>true</MtouchDebug>
|
||||
<CodesignKey>iPhone Developer</CodesignKey>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\iPhone\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||
<MtouchArch>ARM64</MtouchArch>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
<CodesignKey>iPhone Developer</CodesignKey>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Xamarin.iOS" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<InterfaceDefinition Include="Resources\LaunchScreen.storyboard" />
|
||||
<SceneKitAsset Include="art.scnassets\sharedImages\environment_blur.exr" />
|
||||
<SceneKitAsset Include="art.scnassets\Scene.scn" />
|
||||
<None Include="Info.plist" />
|
||||
<None Include="Entitlements.plist" />
|
||||
<SceneKitAsset Include="art.scnassets\candle\candle.scn" />
|
||||
<SceneKitAsset Include="art.scnassets\candle\textures\candle_AO.png" />
|
||||
<SceneKitAsset Include="art.scnassets\candle\textures\candle_DIFFUSE.png" />
|
||||
<SceneKitAsset Include="art.scnassets\candle\textures\candle_METALLIC.png" />
|
||||
<SceneKitAsset Include="art.scnassets\candle\textures\candle_NORMAL.png" />
|
||||
<SceneKitAsset Include="art.scnassets\candle\textures\candle_ROUGHNESS.png" />
|
||||
<SceneKitAsset Include="art.scnassets\candle\textures\candle_SHADOW.png" />
|
||||
<SceneKitAsset Include="art.scnassets\candle\textures\flame_PARTICLE.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="PreviewNode.cs" />
|
||||
<Compile Include="ViewController.cs" />
|
||||
<Compile Include="ViewController.designer.cs">
|
||||
<DependentUpon>ViewController.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Extensions\ARSCNViewExtensions.cs" />
|
||||
<Compile Include="Extensions\Utilities.cs" />
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="AppDelegate.cs" />
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\Contents.json">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\icon-app-60@2x.png">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\Icon-app-60@3x.png">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\icon-app-76.png">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\icon-app-76@2x.png">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\icon-spotlight-29.png">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\icon-spotlight-29@2x.png">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\icon-spotlight-40.png">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\icon-spotlight-40@2x.png">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\icon-spotlight-40@3x.png">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\Icon-app-83.5%402x.png">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
<ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\app-store-logo.png">
|
||||
<Visible>false</Visible>
|
||||
</ImageAsset>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<InterfaceDefinition Include="Resources\Main.storyboard" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
<SceneKitAsset Include="art.scnassets\sharedImages\environment.jpg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<SceneKitAsset Include="art.scnassets\ping.aif" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="art.scnassets\candle\" />
|
||||
<Folder Include="art.scnassets\candle\textures\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,52 @@
|
|||
|
||||
namespace ARKitAudio
|
||||
{
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
// The UIApplicationDelegate for the application. This class is responsible for launching the
|
||||
// User Interface of the application, as well as listening (and optionally responding) to
|
||||
// application events from iOS.
|
||||
[Register ("AppDelegate")]
|
||||
public partial class AppDelegate : UIApplicationDelegate
|
||||
{
|
||||
// class-level declarations
|
||||
|
||||
public override UIWindow Window {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
|
||||
{
|
||||
// Override point for customization after application launch.
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// This method is invoked when the application is about to move from active to inactive state.
|
||||
//
|
||||
// OpenGL applications should use this method to pause.
|
||||
//
|
||||
public override void OnResignActivation (UIApplication application)
|
||||
{
|
||||
}
|
||||
|
||||
// This method should be used to release shared resources and it should store the application state.
|
||||
// If your application supports background exection this method is called instead of WillTerminate
|
||||
// when the user quits.
|
||||
public override void DidEnterBackground (UIApplication application)
|
||||
{
|
||||
}
|
||||
|
||||
// This method is called as part of the transiton from background to active state.
|
||||
public override void WillEnterForeground (UIApplication application)
|
||||
{
|
||||
}
|
||||
|
||||
// This method is called when the application is about to terminate. Save data, if needed.
|
||||
public override void WillTerminate (UIApplication application)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,228 @@
|
|||
|
||||
namespace ARKitAudio
|
||||
{
|
||||
using ARKit;
|
||||
using CoreGraphics;
|
||||
using OpenTK;
|
||||
using SceneKit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Convenience extensions on ARSCNView for hit testing
|
||||
/// </summary>
|
||||
public static class ARSCNViewExtensions
|
||||
{
|
||||
public static HitTestRay? HitTestRayFromScreenPosition(this ARSCNView view, CGPoint point)
|
||||
{
|
||||
HitTestRay? result = null;
|
||||
using (var frame = view.Session.CurrentFrame)
|
||||
{
|
||||
if (frame != null)
|
||||
{
|
||||
var cameraPosition = frame.Camera.Transform.GetTranslation();
|
||||
|
||||
// Note: z: 1.0 will unproject() the screen position to the far clipping plane.
|
||||
var positionVector = new SCNVector3((float)point.X, (float)point.Y, 1f);
|
||||
|
||||
var screenPositionOnFarClippingPlane = view.UnprojectPoint(positionVector);
|
||||
var rayDirection = SCNVector3.Normalize(screenPositionOnFarClippingPlane - cameraPosition);
|
||||
|
||||
result = new HitTestRay { Origin = cameraPosition, Direction = rayDirection };
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static SCNVector3? HitTestWithInfiniteHorizontalPlane(this ARSCNView view, CGPoint point, SCNVector3 pointOnPlane)
|
||||
{
|
||||
SCNVector3? result = null;
|
||||
|
||||
var ray = view.HitTestRayFromScreenPosition(point);
|
||||
if (ray.HasValue)
|
||||
{
|
||||
// Do not intersect with planes above the camera or if the ray is almost parallel to the plane.
|
||||
if (ray.Value.Direction.Y <= -0.03f)
|
||||
{
|
||||
// Return the intersection of a ray from the camera through the screen position with a horizontal plane
|
||||
// at height (Y axis).
|
||||
result = Utilities.RayIntersectionWithHorizontalPlane(ray.Value.Origin, ray.Value.Direction, pointOnPlane.Y);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static IList<FeatureHitTestResult> HitTestWithFeatures(this ARSCNView view,
|
||||
CGPoint point,
|
||||
float coneOpeningAngleInDegrees,
|
||||
float minDistance = 0,
|
||||
float maxDistance = float.MaxValue,
|
||||
int maxResults = 1)
|
||||
{
|
||||
|
||||
var results = new List<FeatureHitTestResult>();
|
||||
|
||||
ARPointCloud features = null;
|
||||
using(var frame = view.Session.CurrentFrame)
|
||||
{
|
||||
features = frame?.RawFeaturePoints;
|
||||
}
|
||||
|
||||
if (features != null)
|
||||
{
|
||||
var ray = view.HitTestRayFromScreenPosition(point);
|
||||
if (ray.HasValue)
|
||||
{
|
||||
var maxAngleInDegrees = Math.Min(coneOpeningAngleInDegrees, 360f) / 2f;
|
||||
var maxAngle = (maxAngleInDegrees / 180f) * Math.PI;
|
||||
|
||||
var points = features.Points;
|
||||
for (nuint j = 0; j < features.Count; j++)
|
||||
{
|
||||
var feature = points[j];
|
||||
|
||||
var featurePosition = new SCNVector3((Vector3)feature);
|
||||
var originToFeature = featurePosition - ray.Value.Origin;
|
||||
|
||||
var crossProduct = SCNVector3.Cross(originToFeature, ray.Value.Direction);
|
||||
var featureDistanceFromResult = crossProduct.Length;
|
||||
|
||||
var hitTestResult = ray.Value.Origin + (ray.Value.Direction * SCNVector3.Dot(ray.Value.Direction, originToFeature));
|
||||
var hitTestResultDistance = (hitTestResult - ray.Value.Origin).Length;
|
||||
|
||||
if (hitTestResultDistance < minDistance || hitTestResultDistance > maxDistance)
|
||||
{
|
||||
// Skip this feature - it is too close or too far away.
|
||||
continue;
|
||||
}
|
||||
|
||||
var originToFeatureNormalized = SCNVector3.Normalize(originToFeature);
|
||||
var angleBetweenRayAndFeature = Math.Acos(SCNVector3.Dot(ray.Value.Direction, originToFeatureNormalized));
|
||||
|
||||
if (angleBetweenRayAndFeature > maxAngle)
|
||||
{
|
||||
// Skip this feature - is outside of the hit test cone.
|
||||
continue;
|
||||
}
|
||||
|
||||
// All tests passed: Add the hit against this feature to the results.
|
||||
results.Add(new FeatureHitTestResult
|
||||
{
|
||||
Position = hitTestResult,
|
||||
DistanceToRayOrigin = hitTestResultDistance,
|
||||
FeatureHit = featurePosition,
|
||||
FeatureDistanceToHitResult = featureDistanceFromResult
|
||||
});
|
||||
}
|
||||
|
||||
// Sort the results by feature distance to the ray.
|
||||
results = results.OrderBy(result => result.DistanceToRayOrigin).ToList();
|
||||
|
||||
// Cap the list to maxResults.
|
||||
var cappedResults = new List<FeatureHitTestResult>();
|
||||
var i = 0;
|
||||
|
||||
while (i < maxResults && i < results.Count)
|
||||
{
|
||||
cappedResults.Add(results[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
results = cappedResults;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static IList<FeatureHitTestResult> HitTestWithFeatures(this ARSCNView view, CGPoint point)
|
||||
{
|
||||
var results = new List<FeatureHitTestResult>();
|
||||
|
||||
var ray = view.HitTestRayFromScreenPosition(point);
|
||||
if (ray.HasValue)
|
||||
{
|
||||
var result = view.HitTestFromOrigin(ray.Value.Origin, ray.Value.Direction);
|
||||
if (result.HasValue)
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static FeatureHitTestResult? HitTestFromOrigin(this ARSCNView view, SCNVector3 origin, SCNVector3 direction)
|
||||
{
|
||||
FeatureHitTestResult? result = null;
|
||||
|
||||
ARPointCloud features = null;
|
||||
using (var frame = view.Session.CurrentFrame)
|
||||
{
|
||||
features = frame?.RawFeaturePoints;
|
||||
}
|
||||
|
||||
if (features != null)
|
||||
{
|
||||
var points = features.Points;
|
||||
|
||||
// Determine the point from the whole point cloud which is closest to the hit test ray.
|
||||
var closestFeaturePoint = origin;
|
||||
var minDistance = float.MaxValue; // Float.greatestFiniteMagnitude
|
||||
|
||||
for (nuint i = 0; i < features.Count; i++)
|
||||
{
|
||||
var feature = points[i];
|
||||
|
||||
var featurePosition = new SCNVector3((Vector3)feature);
|
||||
var originVector = origin - featurePosition;
|
||||
|
||||
var crossProduct = SCNVector3.Cross(originVector, direction);
|
||||
var featureDistanceFromResult = crossProduct.Length;
|
||||
|
||||
if (featureDistanceFromResult < minDistance)
|
||||
{
|
||||
closestFeaturePoint = featurePosition;
|
||||
minDistance = featureDistanceFromResult;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the point along the ray that is closest to the selected feature.
|
||||
var originToFeature = closestFeaturePoint - origin;
|
||||
var hitTestResult = origin + (direction * SCNVector3.Dot(direction, originToFeature));
|
||||
var hitTestResultDistance = (hitTestResult - origin).Length;
|
||||
|
||||
result = new FeatureHitTestResult
|
||||
{
|
||||
Position = hitTestResult,
|
||||
DistanceToRayOrigin = hitTestResultDistance,
|
||||
FeatureHit = closestFeaturePoint,
|
||||
FeatureDistanceToHitResult = minDistance
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public struct HitTestRay
|
||||
{
|
||||
public SCNVector3 Origin { get; set; }
|
||||
|
||||
public SCNVector3 Direction { get; set; }
|
||||
}
|
||||
|
||||
public struct FeatureHitTestResult
|
||||
{
|
||||
public SCNVector3 Position { get; set; }
|
||||
|
||||
public float DistanceToRayOrigin { get; set; }
|
||||
|
||||
public SCNVector3 FeatureHit { get; set; }
|
||||
|
||||
public float FeatureDistanceToHitResult { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
|
||||
namespace ARKitAudio
|
||||
{
|
||||
using ARKit;
|
||||
using CoreGraphics;
|
||||
using SceneKit;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Utility functions and type extensions used throughout the projects.
|
||||
/// </summary>
|
||||
public static class Utilities
|
||||
{
|
||||
public static SCNVector3? GetAverage(this IList<SCNVector3> vectors)
|
||||
{
|
||||
SCNVector3? result = null;
|
||||
|
||||
if (vectors != null && vectors.Count > 0)
|
||||
{
|
||||
var sum = vectors.Aggregate(SCNVector3.Zero, (vector1, vector2) => vector1 + vector2);
|
||||
result = sum / vectors.Count;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void KeepLast<T>(this List<T> list, int elementsToKeep)
|
||||
{
|
||||
if (list.Count > elementsToKeep)
|
||||
{
|
||||
list.RemoveRange(0, list.Count - elementsToKeep);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Treats matrix as a (right-hand column-major convention) transform matrix
|
||||
/// and factors out the translation component of the transform.
|
||||
/// </summary>
|
||||
public static OpenTK.Vector3 GetTranslation(this OpenTK.NMatrix4 matrix)
|
||||
{
|
||||
var translation = matrix.Column3;
|
||||
return new OpenTK.Vector3(translation.X, translation.Y, translation.Z);
|
||||
}
|
||||
|
||||
#region Math
|
||||
|
||||
public static SCNVector3? RayIntersectionWithHorizontalPlane(SCNVector3 rayOrigin, SCNVector3 direction, float planeY)
|
||||
{
|
||||
direction = SCNVector3.Normalize(direction);
|
||||
|
||||
// Special case handling: Check if the ray is horizontal as well.
|
||||
if (direction.Y == 0)
|
||||
{
|
||||
if (rayOrigin.Y == planeY)
|
||||
{
|
||||
// The ray is horizontal and on the plane, thus all points on the ray intersect with the plane.
|
||||
// Therefore we simply return the ray origin.
|
||||
return rayOrigin;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The ray is parallel to the plane and never intersects.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// The distance from the ray's origin to the intersection point on the plane is:
|
||||
// (pointOnPlane - rayOrigin) dot planeNormal
|
||||
// --------------------------------------------
|
||||
// direction dot planeNormal
|
||||
|
||||
// Since we know that horizontal planes have normal (0, 1, 0), we can simplify this to:
|
||||
var dist = (planeY - rayOrigin.Y) / direction.Y;
|
||||
|
||||
// Do not return intersections behind the ray's origin.
|
||||
if (dist < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return the intersection point.
|
||||
return rayOrigin + (direction * dist);
|
||||
}
|
||||
|
||||
public static (SCNVector3?, ARPlaneAnchor, bool) WorldPositionFromScreenPosition(CGPoint position,
|
||||
ARSCNView sceneView,
|
||||
SCNVector3? objectPos,
|
||||
bool infinitePlane = false)
|
||||
{
|
||||
// -------------------------------------------------------------------------------
|
||||
// 1. Always do a hit test against existing plane anchors first.
|
||||
// (If any such anchors exist & only within their extents.)
|
||||
|
||||
var result = sceneView.HitTest(position, ARHitTestResultType.ExistingPlaneUsingExtent)?.FirstOrDefault();
|
||||
if (result != null)
|
||||
{
|
||||
var planeHitTestPosition = result.WorldTransform.GetTranslation();
|
||||
var planeAnchor = result.Anchor;
|
||||
|
||||
// Return immediately - this is the best possible outcome.
|
||||
return (planeHitTestPosition, planeAnchor as ARPlaneAnchor, true);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// 2. Collect more information about the environment by hit testing against
|
||||
// the feature point cloud, but do not return the result yet.
|
||||
|
||||
SCNVector3? featureHitTestPosition = null;
|
||||
var highQualityFeatureHitTestResult = false;
|
||||
|
||||
var highQualityfeatureHitTestResults = sceneView.HitTestWithFeatures(position, 18f, 0.2f, 2f);
|
||||
if (highQualityfeatureHitTestResults.Any())
|
||||
{
|
||||
featureHitTestPosition = highQualityfeatureHitTestResults[0].Position;
|
||||
highQualityFeatureHitTestResult = true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// 3. If desired or necessary (no good feature hit test result): Hit test
|
||||
// against an infinite, horizontal plane (ignoring the real world).
|
||||
|
||||
if (infinitePlane || !highQualityFeatureHitTestResult)
|
||||
{
|
||||
if (objectPos.HasValue)
|
||||
{
|
||||
var pointOnInfinitePlane = sceneView.HitTestWithInfiniteHorizontalPlane(position, objectPos.Value);
|
||||
if (pointOnInfinitePlane.HasValue)
|
||||
{
|
||||
return (pointOnInfinitePlane, null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// 4. If available, return the result of the hit test against high quality
|
||||
// features if the hit tests against infinite planes were skipped or no
|
||||
// infinite plane was hit.
|
||||
|
||||
if (highQualityFeatureHitTestResult)
|
||||
{
|
||||
return (featureHitTestPosition, null, false);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// 5. As a last resort, perform a second, unfiltered hit test against features.
|
||||
// If there are no features in the scene, the result returned here will be nil.
|
||||
|
||||
var unfilteredFeatureHitTestResults = sceneView.HitTestWithFeatures(position);
|
||||
if (unfilteredFeatureHitTestResults.Any())
|
||||
{
|
||||
var first = unfilteredFeatureHitTestResults[0];
|
||||
return (first.Position, null, false);
|
||||
}
|
||||
|
||||
return (null, null, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>ARKitAudio</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.xamarin.ARKitAudio</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This application will use the camera for Augmented Reality.</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>11.0</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIMainStoryboardFile~ipad</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
<string>arkit</string>
|
||||
</array>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Resources/Images.xcassets/AppIcons.appiconset</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>ARKitAudio</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
namespace ARKitAudio
|
||||
{
|
||||
using UIKit;
|
||||
|
||||
public class Application
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, "AppDelegate");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
|
||||
namespace ARKitAudio
|
||||
{
|
||||
using ARKit;
|
||||
using Foundation;
|
||||
using SceneKit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// SceneKit node wrapper that estimates an object's final placement
|
||||
/// </summary>
|
||||
public class PreviewNode : SCNNode
|
||||
{
|
||||
// Use average of recent positions to avoid jitter.
|
||||
private readonly List<SCNVector3> recentPreviewNodePositions = new List<SCNVector3>();
|
||||
|
||||
// Saved positions that help smooth the movement of the preview
|
||||
private SCNVector3 lastPositionOnPlane;
|
||||
|
||||
// Saved positions that help smooth the movement of the preview
|
||||
private SCNVector3 lastPosition;
|
||||
|
||||
public PreviewNode(IntPtr handle) : base(handle) { }
|
||||
|
||||
public PreviewNode(NSCoder coder)
|
||||
{
|
||||
throw new NotImplementedException("init(coder:) has not been implemented");
|
||||
}
|
||||
|
||||
public PreviewNode(SCNNode node) : base()
|
||||
{
|
||||
this.Opacity = 0.5f;
|
||||
this.AddChildNode(node);
|
||||
}
|
||||
|
||||
// Appearance
|
||||
|
||||
public void Update(SCNVector3 position, ARPlaneAnchor planeAnchor, ARCamera camera)
|
||||
{
|
||||
this.lastPosition = position;
|
||||
|
||||
if (planeAnchor != null)
|
||||
{
|
||||
this.lastPositionOnPlane = position;
|
||||
}
|
||||
|
||||
this.UpdateTransform(position, camera);
|
||||
}
|
||||
|
||||
private void UpdateTransform(SCNVector3 position, ARCamera camera)
|
||||
{
|
||||
// Add to the list of recent positions.
|
||||
this.recentPreviewNodePositions.Add(position);
|
||||
|
||||
// Remove anything older than the last 8 positions.
|
||||
this.recentPreviewNodePositions.KeepLast(8);
|
||||
|
||||
// Move to average of recent positions to avoid jitter.
|
||||
var average = this.recentPreviewNodePositions.GetAverage();
|
||||
if (average.HasValue)
|
||||
{
|
||||
this.Position = average.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("ARKitAudio")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("ARKitAudio")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2018")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("bc14a9a0-f771-4c15-bcd7-7974a2a71260")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,216 @@
|
|||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "icon-spotlight-40.png",
|
||||
"size": "20x20",
|
||||
"scale": "2x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"size": "20x20",
|
||||
"scale": "3x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"size": "29x29",
|
||||
"scale": "2x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"size": "29x29",
|
||||
"scale": "3x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"filename": "icon-spotlight-40@2x.png",
|
||||
"size": "40x40",
|
||||
"scale": "2x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"filename": "icon-spotlight-40@3x.png",
|
||||
"size": "40x40",
|
||||
"scale": "3x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"filename": "icon-app-60@2x.png",
|
||||
"size": "60x60",
|
||||
"scale": "2x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-app-60@3x.png",
|
||||
"size": "60x60",
|
||||
"scale": "3x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"size": "20x20",
|
||||
"scale": "1x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"filename": "icon-spotlight-40.png",
|
||||
"size": "20x20",
|
||||
"scale": "2x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"filename": "icon-spotlight-29.png",
|
||||
"size": "29x29",
|
||||
"scale": "1x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"filename": "icon-spotlight-29@2x.png",
|
||||
"size": "29x29",
|
||||
"scale": "2x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"filename": "icon-spotlight-40.png",
|
||||
"size": "40x40",
|
||||
"scale": "1x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"filename": "icon-spotlight-40@2x.png",
|
||||
"size": "40x40",
|
||||
"scale": "2x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-app-83.5@2x.png",
|
||||
"size": "83.5x83.5",
|
||||
"scale": "2x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"filename": "icon-app-76.png",
|
||||
"size": "76x76",
|
||||
"scale": "1x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"filename": "icon-app-76@2x.png",
|
||||
"size": "76x76",
|
||||
"scale": "2x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"filename": "app-store-logo.png",
|
||||
"size": "1024x1024",
|
||||
"scale": "1x",
|
||||
"idiom": "ios-marketing"
|
||||
},
|
||||
{
|
||||
"role": "notificationCenter",
|
||||
"size": "24x24",
|
||||
"subtype": "38mm",
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"role": "notificationCenter",
|
||||
"size": "27.5x27.5",
|
||||
"subtype": "42mm",
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"role": "companionSettings",
|
||||
"size": "29x29",
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"role": "companionSettings",
|
||||
"size": "29x29",
|
||||
"scale": "3x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"role": "appLauncher",
|
||||
"size": "40x40",
|
||||
"subtype": "38mm",
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"role": "longLook",
|
||||
"size": "44x44",
|
||||
"subtype": "42mm",
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"role": "quickLook",
|
||||
"size": "86x86",
|
||||
"subtype": "38mm",
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"role": "quickLook",
|
||||
"size": "98x98",
|
||||
"subtype": "42mm",
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"size": "16x16",
|
||||
"scale": "1x",
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"size": "16x16",
|
||||
"scale": "2x",
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"size": "32x32",
|
||||
"scale": "1x",
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"size": "32x32",
|
||||
"scale": "2x",
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"size": "128x128",
|
||||
"scale": "1x",
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"size": "128x128",
|
||||
"scale": "2x",
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"size": "256x256",
|
||||
"scale": "1x",
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"size": "256x256",
|
||||
"scale": "2x",
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"size": "512x512",
|
||||
"scale": "1x",
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"size": "512x512",
|
||||
"scale": "2x",
|
||||
"idiom": "mac"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "xcode"
|
||||
}
|
||||
}
|
Двоичные данные
ios11/ARKitAudio/ARKitAudio/Resources/Images.xcassets/AppIcons.appiconset/Icon-app-60@3x.png
Normal file
После Ширина: | Высота: | Размер: 5.0 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/Resources/Images.xcassets/AppIcons.appiconset/Icon-app-83.5@2x.png
Normal file
После Ширина: | Высота: | Размер: 8.6 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/Resources/Images.xcassets/AppIcons.appiconset/app-store-logo.png
Normal file
После Ширина: | Высота: | Размер: 22 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/Resources/Images.xcassets/AppIcons.appiconset/icon-app-60@2x.png
Normal file
После Ширина: | Высота: | Размер: 3.3 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/Resources/Images.xcassets/AppIcons.appiconset/icon-app-76.png
Normal file
После Ширина: | Высота: | Размер: 2.0 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/Resources/Images.xcassets/AppIcons.appiconset/icon-app-76@2x.png
Normal file
После Ширина: | Высота: | Размер: 4.3 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/Resources/Images.xcassets/AppIcons.appiconset/icon-spotlight-29.png
Normal file
После Ширина: | Высота: | Размер: 900 B |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/Resources/Images.xcassets/AppIcons.appiconset/icon-spotlight-29@2x.png
Normal file
После Ширина: | Высота: | Размер: 1.8 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/Resources/Images.xcassets/AppIcons.appiconset/icon-spotlight-40.png
Normal file
После Ширина: | Высота: | Размер: 1.1 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/Resources/Images.xcassets/AppIcons.appiconset/icon-spotlight-40@2x.png
Normal file
После Ширина: | Высота: | Размер: 2.2 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/Resources/Images.xcassets/AppIcons.appiconset/icon-spotlight-40@3x.png
Normal file
После Ширина: | Высота: | Размер: 3.3 KiB |
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13189.4" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13165.3"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BV1-FR-VrT">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Game View Controller-->
|
||||
<scene sceneID="tXr-a1-R10">
|
||||
<objects>
|
||||
<viewController id="BV1-FR-VrT" customClass="ViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="220"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="221"/>
|
||||
</layoutGuides>
|
||||
<view contentMode="scaleToFill" id="450" key="view">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<subviews>
|
||||
<arscnView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" id="451" translatesAutoresizingMaskIntoConstraints="NO" ibSceneName="Scene.scn">
|
||||
<rect key="frame" x="0.0" y="20" width="414" height="716"/>
|
||||
</arscnView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="452" translatesAutoresizingMaskIntoConstraints="NO" misplaced="YES" usesAttributedText="YES">
|
||||
<rect key="frame" x="20" y="695.5" width="374" height="20.5"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<attributedString key="attributedText">
|
||||
<fragment content="">
|
||||
<attributes>
|
||||
<font key="NSFont" name="HelveticaNeue" size="17"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
</attributedString>
|
||||
<accessibility key="accessibilityConfiguration" identifier="sessionInfoLabel"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint id="894" firstItem="451" firstAttribute="top" secondItem="220" secondAttribute="bottom"/>
|
||||
<constraint id="895" firstItem="450" firstAttribute="leading" secondItem="451" secondAttribute="leading"/>
|
||||
<constraint id="896" firstItem="451" firstAttribute="trailing" secondItem="450" secondAttribute="trailing"/>
|
||||
<constraint id="897" firstItem="221" firstAttribute="top" secondItem="451" secondAttribute="bottom"/>
|
||||
<constraint id="898" firstItem="451" firstAttribute="bottom" secondItem="452" secondAttribute="bottom" constant="20"/>
|
||||
<constraint id="899" firstItem="452" firstAttribute="leading" secondItem="450" secondAttribute="leadingMargin"/>
|
||||
<constraint id="900" firstItem="450" firstAttribute="trailingMargin" secondItem="452" secondAttribute="trailing"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="sceneView" destination="451" id="name-outlet-451"/>
|
||||
<outlet property="sessionInfoLabel" destination="452" id="name-outlet-452"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="SZV-WD-TEh" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="0.0" y="0.0"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -0,0 +1,363 @@
|
|||
|
||||
namespace ARKitAudio
|
||||
{
|
||||
using ARKit;
|
||||
using AVFoundation;
|
||||
using CoreGraphics;
|
||||
using Foundation;
|
||||
using OpenTK;
|
||||
using SceneKit;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UIKit;
|
||||
|
||||
/// <summary>
|
||||
/// Main view controller for the AR experience.
|
||||
/// </summary>
|
||||
public partial class ViewController : UIViewController, IARSCNViewDelegate
|
||||
{
|
||||
private CGPoint screenCenter = CGPoint.Empty;
|
||||
|
||||
// Shows a preview of the object to be placed and hovers over estimated planes.
|
||||
private PreviewNode previewNode;
|
||||
|
||||
// Contains the cup model that is shared by the preview and final nodes.
|
||||
private SCNNode cupNode = new SCNNode();
|
||||
|
||||
// Audio source for positional audio feedback.
|
||||
private SCNAudioSource source;
|
||||
|
||||
public ViewController(IntPtr handle) : base(handle) { }
|
||||
|
||||
#region View Life Cycle
|
||||
|
||||
public override void ViewDidLoad()
|
||||
{
|
||||
base.ViewDidLoad();
|
||||
|
||||
this.sceneView.Delegate = this;
|
||||
|
||||
// Show statistics such as FPS and timing information.
|
||||
this.sceneView.ShowsStatistics = true;
|
||||
|
||||
// Setup environment mapping.
|
||||
var environmentMap = UIImage.FromBundle("art.scnassets/sharedImages/environment_blur.exr");
|
||||
this.sceneView.Scene.LightingEnvironment.Contents = environmentMap;
|
||||
|
||||
// Complete rendering setup of ARSCNView.
|
||||
this.sceneView.AntialiasingMode = SCNAntialiasingMode.Multisampling4X;
|
||||
this.sceneView.AutomaticallyUpdatesLighting = false;
|
||||
this.sceneView.ContentScaleFactor = 1.3f;
|
||||
}
|
||||
|
||||
public override void ViewDidAppear(bool animated)
|
||||
{
|
||||
base.ViewDidAppear(animated);
|
||||
|
||||
// Preload the audio file.
|
||||
this.source = SCNAudioSource.FromFile("art.scnassets/ping.aif");
|
||||
this.source.Loops = true;
|
||||
this.source.Load();
|
||||
|
||||
if (ARConfiguration.IsSupported)
|
||||
{
|
||||
// Start the ARSession.
|
||||
var configuration = new ARWorldTrackingConfiguration { PlaneDetection = ARPlaneDetection.Horizontal };
|
||||
this.sceneView.Session.Run(configuration, default(ARSessionRunOptions));
|
||||
|
||||
this.screenCenter = new CGPoint(this.sceneView.Bounds.GetMidX(), this.sceneView.Bounds.GetMidY());
|
||||
|
||||
// Prevent the screen from being dimmed after a while as users will likely have
|
||||
// long periods of interaction without touching the screen or buttons.
|
||||
UIApplication.SharedApplication.IdleTimerDisabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ShowUnsupportedDeviceError();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ViewWillDisappear(bool animated)
|
||||
{
|
||||
base.ViewWillDisappear(animated);
|
||||
|
||||
this.cupNode.RemoveAllAudioPlayers();
|
||||
|
||||
// Pause the view's session.
|
||||
this.sceneView.Session.Pause();
|
||||
}
|
||||
|
||||
public override void ViewWillTransitionToSize(CGSize toSize, IUIViewControllerTransitionCoordinator coordinator)
|
||||
{
|
||||
base.ViewWillTransitionToSize(toSize, coordinator);
|
||||
|
||||
// The screen's center point changes on orientation switch, so recalculate `screenCenter`.
|
||||
this.screenCenter = new CGPoint(toSize.Width / 2f, toSize.Height / 2f);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal methods
|
||||
|
||||
private void ShowUnsupportedDeviceError()
|
||||
{
|
||||
// This device does not support 6DOF world tracking.
|
||||
var alertController = UIAlertController.Create("ARKit is not available on this device.",
|
||||
"This app requires world tracking, which is available only on iOS devices with the A9 processor or later.",
|
||||
UIAlertControllerStyle.Alert);
|
||||
|
||||
alertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
|
||||
this.PresentModalViewController(alertController, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check the light estimate from the current ARFrame and update the scene.
|
||||
/// </summary>
|
||||
private void UpdateLightEstimate()
|
||||
{
|
||||
using (var frame = this.sceneView.Session.CurrentFrame)
|
||||
{
|
||||
var lightEstimate = frame?.LightEstimate;
|
||||
if (lightEstimate != null)
|
||||
{
|
||||
this.sceneView.Scene.LightingEnvironment.Intensity = lightEstimate.AmbientIntensity / 40f;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.sceneView.Scene.LightingEnvironment.Intensity = 40f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetTracking()
|
||||
{
|
||||
var configuration = new ARWorldTrackingConfiguration { PlaneDetection = ARPlaneDetection.Horizontal };
|
||||
this.sceneView.Session.Run(configuration, ARSessionRunOptions.ResetTracking | ARSessionRunOptions.RemoveExistingAnchors);
|
||||
|
||||
// Reset preview state.
|
||||
this.cupNode.RemoveFromParentNode();
|
||||
this.cupNode.Dispose();
|
||||
this.cupNode = new SCNNode();
|
||||
|
||||
this.previewNode?.RemoveFromParentNode();
|
||||
this.previewNode?.Dispose();
|
||||
this.previewNode = null;
|
||||
|
||||
this.PlaySound();
|
||||
}
|
||||
|
||||
private void SetNewVirtualObjectToAnchor(SCNNode node, ARAnchor anchor, NMatrix4 cameraTransform)
|
||||
{
|
||||
var cameraWorldPosition = cameraTransform.GetTranslation();
|
||||
var cameraToPosition = anchor.Transform.GetTranslation() - cameraWorldPosition;
|
||||
|
||||
// Limit the distance of the object from the camera to a maximum of 10 meters.
|
||||
if (cameraToPosition.Length > 10f)
|
||||
{
|
||||
cameraToPosition = Vector3.Normalize(cameraToPosition);
|
||||
cameraToPosition *= 10f;
|
||||
}
|
||||
|
||||
node.Position = cameraWorldPosition + cameraToPosition;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ARSCNViewDelegate
|
||||
|
||||
/// <summary>
|
||||
/// UpdateAudioPlayback
|
||||
/// </summary>
|
||||
[Export("renderer:updateAtTime:")]
|
||||
public void Update(ISCNSceneRenderer renderer, double timeInSeconds)
|
||||
{
|
||||
if (this.cupNode.ParentNode == null && this.previewNode == null)
|
||||
{
|
||||
// If our model hasn't been placed and we lack a preview for placement then setup a preview.
|
||||
this.SetupPreviewNode();
|
||||
this.UpdatePreviewNode();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.UpdatePreviewNode();
|
||||
}
|
||||
|
||||
this.UpdateLightEstimate();
|
||||
this.CutVolumeIfPlacedObjectIsInView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PlaceARContent
|
||||
/// </summary>
|
||||
[Export("renderer:didAddNode:forAnchor:")]
|
||||
public void DidAddNode(ISCNSceneRenderer renderer, SCNNode node, ARAnchor anchor)
|
||||
{
|
||||
// Place content only for anchors found by plane detection.
|
||||
if (anchor is ARPlaneAnchor && this.previewNode != null)
|
||||
{
|
||||
// Stop showing a preview version of the object to be placed.
|
||||
this.cupNode.RemoveFromParentNode();
|
||||
|
||||
this.previewNode?.RemoveFromParentNode();
|
||||
this.previewNode?.Dispose();
|
||||
this.previewNode = null;
|
||||
|
||||
// Add the cupNode to the scene's root node using the anchor's position.
|
||||
var cameraTransform = this.sceneView.Session.CurrentFrame?.Camera?.Transform;
|
||||
if (cameraTransform.HasValue)
|
||||
{
|
||||
this.SetNewVirtualObjectToAnchor(this.cupNode, anchor, cameraTransform.Value);
|
||||
this.sceneView.Scene.RootNode.AddChildNode(this.cupNode);
|
||||
|
||||
// Disable plane detection after the model has been added.
|
||||
var configuration = new ARWorldTrackingConfiguration { PlaneDetection = ARPlaneDetection.Horizontal };
|
||||
this.sceneView.Session.Run(configuration, default(ARSessionRunOptions));
|
||||
|
||||
// Set up positional audio to play in case the object moves offscreen.
|
||||
this.PlaySound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PlaceARContent
|
||||
/// </summary>
|
||||
[Export("session:cameraDidChangeTrackingState:")]
|
||||
private void CameraDidChangeTrackingState(ARSession session, ARCamera camera)
|
||||
{
|
||||
var message = string.Empty;
|
||||
|
||||
// Inform the user of their camera tracking state.
|
||||
switch (camera.TrackingState)
|
||||
{
|
||||
case ARTrackingState.NotAvailable:
|
||||
message = "Tracking unavailable";
|
||||
break;
|
||||
|
||||
case ARTrackingState.Limited:
|
||||
switch (camera.TrackingStateReason)
|
||||
{
|
||||
case ARTrackingStateReason.ExcessiveMotion:
|
||||
message = "Tracking limited - Too much camera movement";
|
||||
break;
|
||||
case ARTrackingStateReason.InsufficientFeatures:
|
||||
message = "Tracking limited - Not enough surface detail";
|
||||
break;
|
||||
case ARTrackingStateReason.Initializing:
|
||||
message = "Initializing AR Session";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ARTrackingState.Normal:
|
||||
message = "Tracking normal";
|
||||
break;
|
||||
}
|
||||
|
||||
this.sessionInfoLabel.Text = message;
|
||||
}
|
||||
|
||||
[Export("session:didFailWithError:")]
|
||||
public void DidFail(ARSession session, NSError error)
|
||||
{
|
||||
// Present an error message to the user.
|
||||
this.sessionInfoLabel.Text = $"Session failed: {error.LocalizedDescription}";
|
||||
this.ResetTracking();
|
||||
}
|
||||
|
||||
[Export("sessionWasInterrupted:")]
|
||||
public void WasInterrupted(ARSession session)
|
||||
{
|
||||
// Inform the user that the session has been interrupted, for example, by presenting an overlay.
|
||||
this.sessionInfoLabel.Text = "Session was interrupted";
|
||||
this.ResetTracking();
|
||||
}
|
||||
|
||||
[Export("sessionInterruptionEnded:")]
|
||||
public void InterruptionEnded(ARSession session)
|
||||
{
|
||||
// Reset tracking and/or remove existing anchors if consistent tracking is required.
|
||||
this.sessionInfoLabel.Text = "Session interruption ended";
|
||||
this.ResetTracking();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Preview Node
|
||||
|
||||
/// <summary>
|
||||
/// Loads the cup model (`cupNode`) that is used for the duration of the app.
|
||||
/// Initializes a `PreviewNode` that contains the `cupNode` and adds it to the node hierarchy.
|
||||
/// </summary>
|
||||
private void SetupPreviewNode()
|
||||
{
|
||||
if (this.cupNode.FindChildNode("candle", false) == null)
|
||||
{
|
||||
// Load the cup scene from the bundle only once.
|
||||
var modelScene = SCNScene.FromFile("art.scnassets/candle/candle.scn");
|
||||
// Get a handle to the cup model.
|
||||
var cup = modelScene.RootNode.FindChildNode("candle", true);
|
||||
// Set the cup model onto `cupNode`.
|
||||
this.cupNode.AddChildNode(cup);
|
||||
}
|
||||
|
||||
// Initialize `previewNode` to display the cup model.
|
||||
this.previewNode = new PreviewNode(this.cupNode);
|
||||
// Add `previewNode` to the node hierarchy.
|
||||
this.sceneView.Scene.RootNode.AddChildNode(this.previewNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `previewNode` exists when ARKit is finding a plane. During this time, get a world position for the areas closest to the scene's point of view that ARKit believes might be a plane, and use it to update the `previewNode` position.
|
||||
/// </summary>
|
||||
private void UpdatePreviewNode()
|
||||
{
|
||||
if (this.previewNode != null)
|
||||
{
|
||||
var (worldPosition, planeAnchor, _) = Utilities.WorldPositionFromScreenPosition(this.screenCenter, this.sceneView, this.previewNode.Position);
|
||||
if (worldPosition.HasValue)
|
||||
{
|
||||
this.previewNode.Update(worldPosition.Value, planeAnchor, this.sceneView.Session.CurrentFrame?.Camera);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sound
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the `cupNode` is visible. If the `cupNode` isn't visible, a sound is played using
|
||||
/// SceneKit's positional audio functionality to locate the `cupNode`.
|
||||
/// </summary>
|
||||
private void CutVolumeIfPlacedObjectIsInView()
|
||||
{
|
||||
if (this.previewNode == null && this.sceneView.PointOfView != null)
|
||||
{
|
||||
var player = this.cupNode.AudioPlayers?.FirstOrDefault();
|
||||
var avNode = player?.AudioNode as AVAudioPlayerNode;
|
||||
if (player != null && avNode != null)
|
||||
{
|
||||
var placedObjectIsInView = this.sceneView.IsNodeInsideFrustum(this.cupNode, this.sceneView.PointOfView);
|
||||
avNode.Volume = placedObjectIsInView ? 0f : 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a sound on the cupNode using SceneKit's positional audio.
|
||||
/// </summary>
|
||||
private void PlaySound()
|
||||
{
|
||||
// ensure there is only one audio player
|
||||
this.cupNode.RemoveAllAudioPlayers();
|
||||
|
||||
if (this.cupNode.AudioPlayers == null || !this.cupNode.AudioPlayers.Any())
|
||||
{
|
||||
this.cupNode.AddAudioPlayer(new SCNAudioPlayer(this.source));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// WARNING
|
||||
//
|
||||
// This file has been generated automatically by Visual Studio from the outlets and
|
||||
// actions declared in your storyboard file.
|
||||
// Manual changes to this file will not be maintained.
|
||||
//
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
using UIKit;
|
||||
|
||||
namespace ARKitAudio
|
||||
{
|
||||
[Register ("ViewController")]
|
||||
partial class ViewController
|
||||
{
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
ARKit.ARSCNView sceneView { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UILabel sessionInfoLabel { get; set; }
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
if (sceneView != null) {
|
||||
sceneView.Dispose ();
|
||||
sceneView = null;
|
||||
}
|
||||
|
||||
if (sessionInfoLabel != null) {
|
||||
sessionInfoLabel.Dispose ();
|
||||
sessionInfoLabel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
После Ширина: | Высота: | Размер: 214 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/art.scnassets/candle/textures/candle_DIFFUSE.png
Normal file
После Ширина: | Высота: | Размер: 824 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/art.scnassets/candle/textures/candle_METALLIC.png
Normal file
После Ширина: | Высота: | Размер: 395 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/art.scnassets/candle/textures/candle_NORMAL.png
Normal file
После Ширина: | Высота: | Размер: 437 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/art.scnassets/candle/textures/candle_ROUGHNESS.png
Normal file
После Ширина: | Высота: | Размер: 361 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/art.scnassets/candle/textures/candle_SHADOW.png
Normal file
После Ширина: | Высота: | Размер: 84 KiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/art.scnassets/candle/textures/flame_PARTICLE.png
Normal file
После Ширина: | Высота: | Размер: 148 KiB |
После Ширина: | Высота: | Размер: 4.2 MiB |
Двоичные данные
ios11/ARKitAudio/ARKitAudio/art.scnassets/sharedImages/environment_blur.exr
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<SampleMetadata>
|
||||
<ID>140d20be-f5a4-4040-b585-d3877b4457a4</ID>
|
||||
<IsFullApplication>false</IsFullApplication>
|
||||
<Brief>Runs an ARKit world tracking session with content displayed in a SceneKit view. To demonstrate plane detection, the app simply places a 3D model onto the first plane that ARKit detects. If the model's position is outside the current field of view of the camera, the app uses SceneKit's positional audio feature to indicate which direction to turn the device to see the model.</Brief>
|
||||
<Level>Intermediate</Level>
|
||||
<Tags>Graphics, Media, Platform Features, Device Features, SceneKit, ARKit, iOS11</Tags>
|
||||
<LicenseRequirement>Starter</LicenseRequirement>
|
||||
<Gallery>true</Gallery>
|
||||
</SampleMetadata>
|
|
@ -0,0 +1,32 @@
|
|||
ARKitAudio
|
||||
============
|
||||
|
||||
This sample runs an ARKit world tracking session with content displayed in a SceneKit view. To demonstrate plane detection, the app simply places a 3D model onto the first plane that ARKit detects. If the model's position is outside the current field of view of the camera, the app uses SceneKit's positional audio feature to indicate which direction to turn the device to see the model.
|
||||
|
||||
![Placed object](Screenshots/screenshots_2.png)
|
||||
|
||||
Build Requirements
|
||||
-------
|
||||
|
||||
Xcode 9.0 or later; iOS 11.0 SDK or later
|
||||
|
||||
Refs
|
||||
-------
|
||||
|
||||
- [Original sample](https://developer.apple.com/library/content/samplecode/AudioInARKit/Introduction/Intro.html)
|
||||
- [Documentation](https://developer.apple.com/documentation/arkit/)
|
||||
|
||||
Target
|
||||
-------
|
||||
|
||||
This sample is runnable on iPhone/iPad devices since it requires a real camera.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Xamarin port changes are released under the MIT license.
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
Ported to Xamarin.iOS by Mykyta Bondarenko
|
После Ширина: | Высота: | Размер: 1.3 MiB |
После Ширина: | Высота: | Размер: 1.3 MiB |
После Ширина: | Высота: | Размер: 54 KiB |