diff --git a/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASEHelpers.cs b/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASEHelpers.cs
index c5de5d7d..cf4919e2 100644
--- a/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASEHelpers.cs
+++ b/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASEHelpers.cs
@@ -103,6 +103,14 @@ public enum ReverbPresets
/// True on success, false otherwise.
[DllImport(PluginDllName)] public static extern bool PHASESetSourceTransform(long inSourceId, Matrix4x4 inTransform);
+ ///
+ /// Parents a source under the listener or back under the scene root.
+ ///
+ /// The unique ID representing the source.
+ /// True to parent under the listener, false to parent under root.
+ /// True on success, false otherwise.
+ [DllImport(PluginDllName)] public static extern bool PHASESetSourceListenerAnchored(long inSourceId, bool inAnchored);
+
///
/// Set the gain of the source in the PHASE engine.
///
@@ -821,6 +829,17 @@ static public Matrix4x4 GetPhaseTransform(Transform inTransform)
return phaseTransform;
}
+ ///
+ /// Converts a Unity matrix to a PHASE transform (Left-Handed to Right-Handed).
+ ///
+ /// Unity based matrix to convert to PHASE coordinates.
+ /// A Matrix4x4 representing a transform in PHASE coordinates.
+ static public Matrix4x4 GetPhaseTransform(Matrix4x4 inTransform)
+ {
+ // RhConversionMat (S) * M * S, passed transposed because the native side reads it that way.
+ return RhConversionMat * inTransform.transpose * RhConversionMat;
+ }
+
static private Vector3 GetCombinedHierachyScale(Transform inTransform)
{
Vector3 combinedScale = inTransform.localScale;
diff --git a/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASEListener.cs b/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASEListener.cs
index 3add6fdc..20379116 100644
--- a/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASEListener.cs
+++ b/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASEListener.cs
@@ -67,6 +67,10 @@ void CreateListener()
{
Debug.LogError("Failed to create PHASE Listener");
}
+ else
+ {
+ PHASESource.ReanchorSources();
+ }
}
// Update is called once per frame.
diff --git a/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASESource.cs b/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASESource.cs
index b18a963b..497b0b1f 100644
--- a/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASESource.cs
+++ b/plug-ins/Apple.PHASE/Apple.PHASE_Unity/Assets/Runtime/PHASESource.cs
@@ -63,6 +63,9 @@ enum SourceMode
// Active sound event instance on this source.
private List _soundEventInstance = new List();
+ // When anchored, the local transform is sent to PHASE as the offset from the listener.
+ private bool _listenerAnchored = false;
+
// Source id to store.
private long _sourceId = Helpers.InvalidId;
@@ -243,7 +246,10 @@ protected virtual void ManualUpdate()
{
if (_transform != null && _sourceId != Helpers.InvalidId)
{
- Matrix4x4 phaseTransform = Helpers.GetPhaseTransform(_transform);
+ // An anchored source sends its local transform, read relative to the listener.
+ Matrix4x4 phaseTransform = _listenerAnchored
+ ? Helpers.GetPhaseTransform(Matrix4x4.TRS(_transform.localPosition, _transform.localRotation, Vector3.one))
+ : Helpers.GetPhaseTransform(_transform);
bool result = Helpers.PHASESetSourceTransform(_sourceId, phaseTransform);
if (result == false)
{
@@ -266,6 +272,19 @@ protected internal static void UpdateSources()
entry.Value.ManualUpdate();
}
}
+
+ // Re-parents anchored sources under a newly created listener.
+ protected internal static void ReanchorSources()
+ {
+ foreach (PHASESource source in _registeredSources.Values)
+ {
+ if (source._listenerAnchored)
+ {
+ source.SetListenerAnchored(true);
+ }
+ }
+ }
+
private void UpdateGain()
{
var result = Helpers.PHASESetSourceGain(_sourceId, _gain);
@@ -349,6 +368,35 @@ void SetSendParametersForSpatialMixer(string mixer_name, long instanceId)
}
}
+ ///
+ /// Parents this source under the listener, or back under the scene root.
+ /// While anchored, the source's local transform is its offset from the listener,
+ /// so parent the GameObject under the listener's GameObject.
+ ///
+ /// True to parent under the listener, false to parent under root.
+ /// True on success, false otherwise.
+ public bool SetListenerAnchored(bool anchored)
+ {
+ if (_sourceId == Helpers.InvalidId)
+ {
+ _listenerAnchored = false;
+ return false;
+ }
+
+ bool result = Helpers.PHASESetSourceListenerAnchored(_sourceId, anchored);
+ _listenerAnchored = anchored && result;
+ return result;
+ }
+
+ ///
+ /// Whether this source is currently anchored to the listener.
+ ///
+ /// True if anchored to the listener, false otherwise.
+ public bool IsListenerAnchored()
+ {
+ return _listenerAnchored;
+ }
+
///
/// Set a meta parameter of type integer associated with this source's sound event.
///
@@ -409,6 +457,7 @@ public void DestroyFromPHASE()
Helpers.PHASEDestroySource(_sourceId);
_toBeDestroyed = true;
_sourceId = Helpers.InvalidId;
+ _listenerAnchored = false;
}
// Stop is called when the object stops.
diff --git a/plug-ins/Apple.PHASE/Native/PHASEInterface.h b/plug-ins/Apple.PHASE/Native/PHASEInterface.h
index 90b1d304..6088f609 100644
--- a/plug-ins/Apple.PHASE/Native/PHASEInterface.h
+++ b/plug-ins/Apple.PHASE/Native/PHASEInterface.h
@@ -94,6 +94,12 @@ int64_t PHASECreatePointSource();
*/
bool PHASESetSourceTransform(int64_t inSourceId, Matrix4x4 inTransform);
+/*
+ Parents a source under the listener or back under the scene root.
+ Returns true on success, false otherwise.
+*/
+bool PHASESetSourceListenerAnchored(int64_t inSourceId, bool inAnchored);
+
/*
Sets the gain linear scale value of a given source, range of [0,1].
Given gain values outside of this range will be clamped.
diff --git a/plug-ins/Apple.PHASE/Native/PHASEInterface.mm b/plug-ins/Apple.PHASE/Native/PHASEInterface.mm
index eaf3d2de..3d30d368 100644
--- a/plug-ins/Apple.PHASE/Native/PHASEInterface.mm
+++ b/plug-ins/Apple.PHASE/Native/PHASEInterface.mm
@@ -163,6 +163,12 @@ bool PHASESetSourceTransform(int64_t inSourceId, Matrix4x4 inTransform)
return [engineWrapper setSourceTransformWithId:inSourceId transform:sourceTransform];
}
+bool PHASESetSourceListenerAnchored(int64_t inSourceId, bool inAnchored)
+{
+ PHASEEngineWrapper* engineWrapper = [PHASEEngineWrapper sharedInstance];
+ return [engineWrapper setSourceListenerAnchoredWithId:inSourceId anchored:inAnchored];
+}
+
bool PHASESetSourceGain(int64_t inSourceId, double inGain)
{
PHASEEngineWrapper* engineWrapper = [PHASEEngineWrapper sharedInstance];
diff --git a/plug-ins/Apple.PHASE/Native/PHASEWrapper/PHASEWrapper.h b/plug-ins/Apple.PHASE/Native/PHASEWrapper/PHASEWrapper.h
index 340dccda..344465b7 100644
--- a/plug-ins/Apple.PHASE/Native/PHASEWrapper/PHASEWrapper.h
+++ b/plug-ins/Apple.PHASE/Native/PHASEWrapper/PHASEWrapper.h
@@ -197,6 +197,14 @@ enum CalibrationMode
*/
- (BOOL)setSourceTransformWithId:(int64_t)sourceId transform:(simd_float4x4)transform;
+/*! @method setSourceListenerAnchoredWithId
+ @abstract Parents a source under the listener or back under the scene root.
+ @param sourceId source ID to re-parent
+ @param anchored true to parent under the listener, false to parent under root
+ @return true on success, false otherwise
+*/
+- (BOOL)setSourceListenerAnchoredWithId:(int64_t)sourceId anchored:(BOOL)anchored;
+
/*! @method setSourceGainWithId
@abstract Sets the gain linear scale value of the source, range of [0,1]. Given gain values outside of this range will be clamped.
@param sourceId source ID to update gain for
diff --git a/plug-ins/Apple.PHASE/Native/PHASEWrapper/PHASEWrapper.mm b/plug-ins/Apple.PHASE/Native/PHASEWrapper/PHASEWrapper.mm
index 599acbc1..7d8f2e96 100644
--- a/plug-ins/Apple.PHASE/Native/PHASEWrapper/PHASEWrapper.mm
+++ b/plug-ins/Apple.PHASE/Native/PHASEWrapper/PHASEWrapper.mm
@@ -337,6 +337,18 @@ - (BOOL)destroyListener
return NO;
}
+ // Return any anchored sources to the root before the listener goes away.
+ NSArray* children = mListener.children;
+ [mListener removeChildren];
+ for (PHASEObject* child in children)
+ {
+ NSError* errorRef = nil;
+ if (![mEngine.rootObject addChild:child error:&errorRef])
+ {
+ NSLog(@"Phase Wrapper: Failed to return PHASE Source to the root %@.", errorRef);
+ }
+ }
+
// Remove from the hierarchy
[mEngine.rootObject removeChild:mListener];
@@ -409,6 +421,47 @@ - (BOOL)setSourceTransformWithId:(int64_t)sourceId transform:(simd_float4x4)tran
return YES;
}
+- (BOOL)setSourceListenerAnchoredWithId:(int64_t)sourceId anchored:(BOOL)anchored
+{
+ PHASESource* source = [mSources objectForKey:[NSNumber numberWithLongLong:sourceId]];
+ if (source == nil)
+ {
+ NSLog(@"Phase Wrapper: Failed to find PHASE Source to anchor.");
+ return NO;
+ }
+
+ if (anchored && mListener == nil)
+ {
+ NSLog(@"Phase Wrapper: Listener does not exist.");
+ return NO;
+ }
+
+ PHASEObject* newParent = anchored ? mListener : mEngine.rootObject;
+ if (source.parent == newParent)
+ {
+ // Already there. Re-parenting an anchored source again has silenced it on device.
+ return YES;
+ }
+
+ // addChild fails if the source already has a parent.
+ [source.parent removeChild:source];
+
+ NSError* errorRef = nil;
+ const BOOL result = [newParent addChild:source error:&errorRef];
+ if (!result)
+ {
+ NSLog(@"Phase Wrapper: Failed to add PHASE Source to the %@ %@.", anchored ? @"Listener" : @"root", errorRef);
+ // Put it back under the root rather than leave it orphaned.
+ NSError* rollbackError = nil;
+ if (![mEngine.rootObject addChild:source error:&rollbackError])
+ {
+ NSLog(@"Phase Wrapper: Failed to return PHASE Source to the root %@.", rollbackError);
+ }
+ return NO;
+ }
+ return YES;
+}
+
- (BOOL)setSourceGainWithId:(int64_t)sourceId sourceGain:(double)sourceGain
{
PHASESource* source = [mSources objectForKey:[NSNumber numberWithLongLong:sourceId]];
@@ -440,7 +493,7 @@ - (void)destroySourceWithId:(int64_t)sourceId
if (source != nil)
{
// Remove from the hierarchy
- [mEngine.rootObject removeChild:source];
+ [source.parent removeChild:source];
[mSources removeObjectForKey:[NSNumber numberWithLongLong:sourceId]];