Compare commits

..

No commits in common. "main" and "v0.1.18" have entirely different histories.

53 changed files with 127 additions and 2179 deletions

View File

@ -1,26 +1,5 @@
# Changelog # Changelog
## 0.4.14 - 2026-08-17
- Resolved the configured CinemachineBrain from camera tracks on descendant
PlayableDirectors, including Cinemachine 3 `OutputCamera` setups, so
orchestration Timelines can import AI results without duplicating camera
tracks on the parent Timeline.
- Made AI preview cloning inactive from construction and disconnected cloned
authored Cinemachine tracks and virtual cameras before playback. This
prevents the authored Motion Timeline from competing with the generated
Shot tracks while preserving the original scene hierarchy. This is an
Editor-only hotfix; the bundled Windows worker remains 0.1.9.
## 0.4.13 - 2026-08-16
- Enforced radial distance bounds on the actual float32 camera coordinates,
preventing oblique wide-shot plateaus from drifting a few micrometres past
their authored safety annulus during publication.
- Made a final distance postcondition failure reject only that camera
candidate while preserving fail-closed handling for real generator
invariants. Rebuilt the self-contained Windows worker as 0.1.9.
## 0.4.12 - 2026-08-16 ## 0.4.12 - 2026-08-16
- Made packaged catalog freshness checks portable across Windows Git - Made packaged catalog freshness checks portable across Windows Git

View File

@ -17,7 +17,7 @@ must remain together.
In Unity Package Manager, choose **Add package from git URL** and enter: In Unity Package Manager, choose **Add package from git URL** and enter:
```text ```text
https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.20 https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.18
``` ```
The initial package download is large because the frozen Windows worker is The initial package download is large because the frozen Windows worker is
@ -26,7 +26,7 @@ it, remove the package lock entry, and add the package again.
## Reference data ## Reference data
Version 0.1.20 includes a compact, read-only `RuntimeData~` bundle with the Version 0.1.18 includes a compact, read-only `RuntimeData~` bundle with the
263 prepared reference songs, cut policy, and ranker model. An artist 263 prepared reference songs, cut policy, and ranker model. An artist
workstation does not need a separate `CW-AI` checkout or Python installation. workstation does not need a separate `CW-AI` checkout or Python installation.
If a newer access-controlled library is available, it remains an optional If a newer access-controlled library is available, it remains an optional

View File

@ -440,7 +440,7 @@ namespace Streamingle.Editor
originalDirector.gameObject.activeSelf; originalDirector.gameObject.activeSelf;
try try
{ {
previewDirectorObject = InstantiateInactivePreviewCloneForEditor( previewDirectorObject = UnityEngine.Object.Instantiate(
originalDirector.gameObject, originalDirector.gameObject,
originalDirector.transform.parent); originalDirector.transform.parent);
Undo.RegisterCreatedObjectUndo( Undo.RegisterCreatedObjectUndo(
@ -456,7 +456,6 @@ namespace Streamingle.Editor
originalTimeline, originalTimeline,
previewDirector, previewDirector,
copiedTimeline); copiedTimeline);
SuppressAuthoredCinemachineOutputsForEditor(previewDirector);
MuteCopiedCameraTracks(previewDirector, copiedTimeline); MuteCopiedCameraTracks(previewDirector, copiedTimeline);
MuteCopiedRecorderTracks(copiedTimeline); MuteCopiedRecorderTracks(copiedTimeline);
@ -596,7 +595,6 @@ namespace Streamingle.Editor
$"Position Dense C2 Fallbacks: {positionDenseFallbackCount:N0}", $"Position Dense C2 Fallbacks: {positionDenseFallbackCount:N0}",
$"Preview Director: {GetHierarchyPath(previewDirector.transform)}", $"Preview Director: {GetHierarchyPath(previewDirector.transform)}",
$"Preview Camera Root: {GetHierarchyPath(previewCameraObject.transform)}", $"Preview Camera Root: {GetHierarchyPath(previewCameraObject.transform)}",
GetPreviewOutputStatusForEditor(brain),
$"Timeline Asset: {copiedTimelinePath}", $"Timeline Asset: {copiedTimelinePath}",
$"Animation Assets: {generatedClipPaths.Count:N0} " + $"Animation Assets: {generatedClipPaths.Count:N0} " +
$"under {shotAnimationFolder}", $"under {shotAnimationFolder}",
@ -1451,8 +1449,6 @@ namespace Streamingle.Editor
TimelineAsset finalTimeline = null; TimelineAsset finalTimeline = null;
GameObject cameraPrefab = null; GameObject cameraPrefab = null;
GameObject finalCameraRoot = null; GameObject finalCameraRoot = null;
AuthoredCinemachineStateSnapshot destinationCinemachineStateBefore =
null;
try try
{ {
var sourceTimelinePath = AssetDatabase.GetAssetPath(sourceTimeline); var sourceTimelinePath = AssetDatabase.GetAssetPath(sourceTimeline);
@ -1515,11 +1511,6 @@ namespace Streamingle.Editor
destinationTimelineBefore, destinationTimelineBefore,
finalTimeline, finalTimeline,
finalCameraRoot); finalCameraRoot);
destinationCinemachineStateBefore =
CaptureAuthoredCinemachineState(destinationDirector);
SuppressAuthoredCinemachineOutputsForEditor(
destinationDirector,
false);
destinationDirector.time = destinationTimeBefore; destinationDirector.time = destinationTimeBefore;
destinationDirector.RebuildGraph(); destinationDirector.RebuildGraph();
destinationDirector.time = destinationTimeBefore; destinationDirector.time = destinationTimeBefore;
@ -1554,8 +1545,6 @@ namespace Streamingle.Editor
} }
catch catch
{ {
RestoreAuthoredCinemachineState(
destinationCinemachineStateBefore);
RestoreDestinationDirectorAfterFinalizationFailure( RestoreDestinationDirectorAfterFinalizationFailure(
destinationStateBefore, destinationStateBefore,
finalTimeline); finalTimeline);
@ -1954,7 +1943,6 @@ namespace Streamingle.Editor
destinationDirector.SetReferenceValue(exposedName, camera); destinationDirector.SetReferenceValue(exposedName, camera);
EditorUtility.SetDirty(shot); EditorUtility.SetDirty(shot);
} }
} }
internal static void ClearTimelineBindingsForFinalization( internal static void ClearTimelineBindingsForFinalization(
@ -5587,46 +5575,6 @@ namespace Streamingle.Editor
return clonedComponents[index]; return clonedComponents[index];
} }
internal static GameObject InstantiateInactivePreviewCloneForEditor(
GameObject sourceObject,
Transform destinationParent)
{
if (sourceObject == null)
{
throw new ArgumentNullException(nameof(sourceObject));
}
var sourceScene = sourceObject.scene;
if (!sourceScene.IsValid() || !sourceScene.isLoaded)
{
throw new InvalidOperationException(
"The source Timeline object must belong to a loaded scene.");
}
var stagingObject = new GameObject(
"__CWAI_InactivePreviewCloneStaging");
stagingObject.SetActive(false);
try
{
if (stagingObject.scene != sourceScene)
{
SceneManager.MoveGameObjectToScene(stagingObject, sourceScene);
}
var clone = UnityEngine.Object.Instantiate(
sourceObject,
stagingObject.transform,
true);
clone.SetActive(false);
clone.transform.SetParent(destinationParent, true);
return clone;
}
finally
{
UnityEngine.Object.DestroyImmediate(stagingObject);
}
}
private static void EnsureNoExistingPreview( private static void EnsureNoExistingPreview(
Scene scene, Scene scene,
string previewDirectorName, string previewDirectorName,
@ -5697,49 +5645,30 @@ namespace Streamingle.Editor
} }
var scene = director.gameObject.scene; var scene = director.gameObject.scene;
var directlyBoundBrains = GetBoundCinemachineBrains( var boundBrains = timeline.GetOutputTracks()
director, .OfType<CinemachineTrack>()
timeline); .Select(track => director.GetGenericBinding(track) as CinemachineBrain)
if (directlyBoundBrains.Length > 1) .Where(brain => brain != null)
.Distinct()
.ToArray();
if (boundBrains.Length > 1)
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
"The Timeline has Cinemachine Tracks bound to multiple " + "The Timeline has Cinemachine Tracks bound to multiple " +
"CinemachineBrain components. Bind every camera track to one Brain."); "CinemachineBrain components. Bind every camera track to one Brain.");
} }
if (directlyBoundBrains.Length == 1) if (boundBrains.Length == 1)
{ {
return ValidateExplicitCinemachineBrain( var boundBrain = boundBrains[0];
directlyBoundBrains[0], if (!IsUsableCinemachineBrain(boundBrain, scene))
scene);
}
var descendantBoundBrains = director.gameObject
.GetComponentsInChildren<PlayableDirector>(true)
.Where(descendant =>
descendant != null &&
descendant != director &&
descendant.gameObject.scene == scene &&
descendant.playableAsset is TimelineAsset)
.SelectMany(descendant => GetBoundCinemachineBrains(
descendant,
(TimelineAsset)descendant.playableAsset))
.Distinct()
.ToArray();
if (descendantBoundBrains.Length > 1)
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
"Descendant PlayableDirectors under the selected Timeline object " + "The CinemachineBrain bound to the Timeline must be enabled, " +
"have Cinemachine Tracks bound to multiple CinemachineBrain " + "attached to an enabled Camera, and belong to the Timeline scene.");
"components. Bind them to one Brain or bind the selected Timeline " +
"directly.");
} }
if (descendantBoundBrains.Length == 1) return boundBrain;
{
return ValidateExplicitCinemachineBrain(
descendantBoundBrains[0],
scene);
} }
var usableBrains = sceneBrains var usableBrains = sceneBrains
@ -5749,7 +5678,7 @@ namespace Streamingle.Editor
var mainCameraBrains = usableBrains var mainCameraBrains = usableBrains
.Where(brain => .Where(brain =>
{ {
var camera = brain.OutputCamera; var camera = brain.GetComponent<Camera>();
return camera != null && camera.CompareTag("MainCamera"); return camera != null && camera.CompareTag("MainCamera");
}) })
.ToArray(); .ToArray();
@ -5785,81 +5714,6 @@ namespace Streamingle.Editor
"one Camera as MainCamera or bind a Cinemachine Track explicitly."); "one Camera as MainCamera or bind a Cinemachine Track explicitly.");
} }
private static CinemachineBrain[] GetBoundCinemachineBrains(
PlayableDirector director,
TimelineAsset timeline)
{
return timeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.Select(track =>
director.GetGenericBinding(track) as CinemachineBrain)
.Where(brain => brain != null)
.Distinct()
.ToArray();
}
private static CinemachineBrain ValidateExplicitCinemachineBrain(
CinemachineBrain brain,
Scene scene)
{
if (brain.gameObject.scene != scene)
{
throw new InvalidOperationException(
"The CinemachineBrain explicitly bound to a Timeline must belong " +
"to the same scene as the selected Timeline director.");
}
var camera = brain.OutputCamera;
if (camera == null)
{
throw new InvalidOperationException(
"The CinemachineBrain explicitly bound to a Timeline must have an " +
"output Camera component on its controlled GameObject.");
}
if (camera.gameObject.scene != scene)
{
throw new InvalidOperationException(
"The CinemachineBrain output Camera explicitly bound to a Timeline " +
"must belong to the same scene as the selected Timeline director.");
}
if (!brain.isActiveAndEnabled || !camera.isActiveAndEnabled)
{
Debug.LogWarning(
"The CinemachineBrain explicitly bound to a Timeline or its " +
"Camera is disabled. Camera AI will use the configured Brain " +
"without enabling either component automatically.",
brain);
}
return brain;
}
internal static string GetPreviewOutputStatusForEditor(
CinemachineBrain brain)
{
if (brain == null)
{
throw new ArgumentNullException(nameof(brain));
}
var camera = brain.OutputCamera;
var cameraName = camera != null
? camera.gameObject.name
: brain.gameObject.name;
if (camera != null &&
brain.isActiveAndEnabled &&
camera.isActiveAndEnabled)
{
return $"Preview Output: configured '{cameraName}' CinemachineBrain " +
"and Camera are active.";
}
return $"Preview Output: configured '{cameraName}' CinemachineBrain or " +
"Camera is disabled; enable it to see AI camera playback.";
}
private static bool IsUsableCinemachineBrain( private static bool IsUsableCinemachineBrain(
CinemachineBrain brain, CinemachineBrain brain,
Scene scene) Scene scene)
@ -5871,193 +5725,8 @@ namespace Streamingle.Editor
return false; return false;
} }
var camera = brain.OutputCamera; var camera = brain.GetComponent<Camera>();
return camera != null && return camera != null && camera.isActiveAndEnabled;
camera.gameObject.scene == scene &&
camera.isActiveAndEnabled;
}
internal static void SuppressAuthoredCinemachineOutputsForEditor(
PlayableDirector selectedDirector,
bool recordUndo = true)
{
if (selectedDirector == null)
{
throw new ArgumentNullException(nameof(selectedDirector));
}
var directorObject = selectedDirector.gameObject;
var descendantDirectors = directorObject
.GetComponentsInChildren<PlayableDirector>(true)
.Where(descendant =>
descendant != null &&
descendant != selectedDirector &&
descendant.playableAsset is TimelineAsset)
.ToArray();
foreach (var descendantDirector in descendantDirectors)
{
var descendantTimeline =
(TimelineAsset)descendantDirector.playableAsset;
var cinemachineTracks = descendantTimeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.ToArray();
if (cinemachineTracks.Length == 0)
{
continue;
}
if (recordUndo)
{
Undo.RecordObject(
descendantDirector,
"Disconnect Authored Cameras From AI Preview");
}
foreach (var cinemachineTrack in cinemachineTracks)
{
descendantDirector.ClearGenericBinding(cinemachineTrack);
}
RebuildDirectorGraphPreservingPlayback(descendantDirector);
EditorUtility.SetDirty(descendantDirector);
}
foreach (var authoredCamera in directorObject
.GetComponentsInChildren<CinemachineVirtualCameraBase>(true))
{
if (authoredCamera == null || !authoredCamera.enabled)
{
continue;
}
if (recordUndo)
{
Undo.RecordObject(
authoredCamera,
"Disable Authored Cameras In AI Output");
}
authoredCamera.enabled = false;
EditorUtility.SetDirty(authoredCamera);
}
}
internal static AuthoredCinemachineStateSnapshot
CaptureAuthoredCinemachineState(PlayableDirector selectedDirector)
{
if (selectedDirector == null)
{
throw new ArgumentNullException(nameof(selectedDirector));
}
var directorObject = selectedDirector.gameObject;
var directorStates = directorObject
.GetComponentsInChildren<PlayableDirector>(true)
.Where(descendant =>
descendant != null &&
descendant != selectedDirector &&
descendant.playableAsset is TimelineAsset)
.Select(descendant =>
{
var timeline = (TimelineAsset)descendant.playableAsset;
var tracks = timeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.ToArray();
return new AuthoredCinemachineDirectorState(
descendant,
tracks,
tracks.Select(descendant.GetGenericBinding).ToArray());
})
.Where(state => state.Tracks.Length > 0)
.ToArray();
var cameraStates = directorObject
.GetComponentsInChildren<CinemachineVirtualCameraBase>(true)
.Where(camera => camera != null)
.Select(camera => new AuthoredVirtualCameraState(
camera,
camera.enabled))
.ToArray();
return new AuthoredCinemachineStateSnapshot(
directorStates,
cameraStates);
}
internal static void RestoreAuthoredCinemachineState(
AuthoredCinemachineStateSnapshot snapshot)
{
if (snapshot == null)
{
return;
}
foreach (var cameraState in snapshot.CameraStates)
{
if (cameraState.Camera == null)
{
continue;
}
cameraState.Camera.enabled = cameraState.Enabled;
EditorUtility.SetDirty(cameraState.Camera);
}
foreach (var directorState in snapshot.DirectorStates)
{
var director = directorState.Director;
if (director == null)
{
continue;
}
for (var index = 0; index < directorState.Tracks.Length; index++)
{
var track = directorState.Tracks[index];
if (track == null)
{
continue;
}
var binding = directorState.Bindings[index];
if (binding != null)
{
director.SetGenericBinding(track, binding);
}
else
{
director.ClearGenericBinding(track);
}
}
RebuildDirectorGraphPreservingPlayback(director);
EditorUtility.SetDirty(director);
}
}
private static void RebuildDirectorGraphPreservingPlayback(
PlayableDirector director)
{
if (!director.playableGraph.IsValid())
{
return;
}
var time = director.time;
var wasPlaying = director.state == PlayState.Playing;
director.Stop();
director.RebuildGraph();
director.time = time;
if (wasPlaying)
{
director.Play();
director.time = time;
}
else
{
director.Evaluate();
director.time = time;
}
} }
private static void CopyTrackBindings( private static void CopyTrackBindings(
@ -7275,51 +6944,6 @@ namespace Streamingle.Editor
public GameObject CameraRoot { get; } public GameObject CameraRoot { get; }
} }
internal sealed class AuthoredCinemachineStateSnapshot
{
public AuthoredCinemachineStateSnapshot(
AuthoredCinemachineDirectorState[] directorStates,
AuthoredVirtualCameraState[] cameraStates)
{
DirectorStates = directorStates;
CameraStates = cameraStates;
}
public AuthoredCinemachineDirectorState[] DirectorStates { get; }
public AuthoredVirtualCameraState[] CameraStates { get; }
}
internal sealed class AuthoredCinemachineDirectorState
{
public AuthoredCinemachineDirectorState(
PlayableDirector director,
CinemachineTrack[] tracks,
UnityEngine.Object[] bindings)
{
Director = director;
Tracks = tracks;
Bindings = bindings;
}
public PlayableDirector Director { get; }
public CinemachineTrack[] Tracks { get; }
public UnityEngine.Object[] Bindings { get; }
}
internal sealed class AuthoredVirtualCameraState
{
public AuthoredVirtualCameraState(
CinemachineVirtualCameraBase camera,
bool enabled)
{
Camera = camera;
Enabled = enabled;
}
public CinemachineVirtualCameraBase Camera { get; }
public bool Enabled { get; }
}
private sealed class DirectorStateSnapshot private sealed class DirectorStateSnapshot
{ {
public DirectorStateSnapshot( public DirectorStateSnapshot(

View File

@ -16,7 +16,7 @@ Cinemachine Track이나 기존 카메라 애니메이션은 필요하지 않으
Unity Package Manager의 `Add package from git URL`에는 다음 주소를 사용할 수 Unity Package Manager의 `Add package from git URL`에는 다음 주소를 사용할 수
있습니다. 있습니다.
`https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.20` `https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.18`
배포 패키지에는 Windows x64용 `CWCameraWorker` 폴더 전체가 포함됩니다. 배포 패키지에는 Windows x64용 `CWCameraWorker` 폴더 전체가 포함됩니다.
Python은 따로 설치하지 않아도 되지만, Git 패키지의 대용량 바이너리를 받으려면 Python은 따로 설치하지 않아도 되지만, Git 패키지의 대용량 바이너리를 받으려면

View File

@ -6,26 +6,13 @@ using System.Text;
using NUnit.Framework; using NUnit.Framework;
using Unity.Cinemachine; using Unity.Cinemachine;
using UnityEditor; using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine; using UnityEngine;
using UnityEngine.Playables; using UnityEngine.Playables;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using UnityEngine.Timeline; using UnityEngine.Timeline;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
namespace Streamingle.Editor.Tests namespace Streamingle.Editor.Tests
{ {
public sealed class PreviewCloneActivationProbe : MonoBehaviour
{
internal static int EnableCount { get; set; }
private void OnEnable()
{
EnableCount++;
}
}
public sealed class AICameraTimelinePreviewImporterTests public sealed class AICameraTimelinePreviewImporterTests
{ {
[Test] [Test]
@ -47,10 +34,6 @@ namespace Streamingle.Editor.Tests
timeline, timeline,
new[] { brain }), new[] { brain }),
Is.SameAs(brain)); Is.SameAs(brain));
Assert.That(
AICameraTimelinePreviewImporter
.GetPreviewOutputStatusForEditor(brain),
Does.Contain("are active"));
} }
finally finally
{ {
@ -128,843 +111,6 @@ namespace Streamingle.Editor.Tests
} }
} }
[Test]
public void BrainResolutionUsesDisabledUniqueDescendantBinding()
{
var directorObject = new GameObject("Director");
var childDirectorObject = new GameObject("Camera Director");
var cameraObject = new GameObject("Configured Camera");
childDirectorObject.transform.SetParent(directorObject.transform, false);
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
TimelineAsset childTimeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
director.playableAsset = timeline;
var childDirector =
childDirectorObject.AddComponent<PlayableDirector>();
cameraObject.AddComponent<Camera>();
var brain = cameraObject.AddComponent<CinemachineBrain>();
childTimeline = CreateTimelineBoundToBrain(
childDirector,
brain,
"Child Camera Track");
childDirectorObject.SetActive(false);
brain.enabled = false;
LogAssert.Expect(
LogType.Warning,
"The CinemachineBrain explicitly bound to a Timeline or its " +
"Camera is disabled. Camera AI will use the configured Brain " +
"without enabling either component automatically.");
Assert.That(
AICameraTimelinePreviewImporter.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { brain }),
Is.SameAs(brain));
Assert.That(brain.enabled, Is.False);
Assert.That(
AICameraTimelinePreviewImporter
.GetPreviewOutputStatusForEditor(brain),
Does.Contain("enable it to see AI camera playback"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(cameraObject);
Object.DestroyImmediate(childTimeline);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionDeduplicatesSameBrainAcrossDescendants()
{
var directorObject = new GameObject("Director");
var firstChildObject = new GameObject("First Camera Director");
var secondChildObject = new GameObject("Second Camera Director");
var cameraObject = new GameObject("Configured Camera");
firstChildObject.transform.SetParent(directorObject.transform, false);
secondChildObject.transform.SetParent(directorObject.transform, false);
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
TimelineAsset firstChildTimeline = null;
TimelineAsset secondChildTimeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
director.playableAsset = timeline;
var firstChildDirector =
firstChildObject.AddComponent<PlayableDirector>();
var secondChildDirector =
secondChildObject.AddComponent<PlayableDirector>();
cameraObject.AddComponent<Camera>();
var brain = cameraObject.AddComponent<CinemachineBrain>();
firstChildTimeline = CreateTimelineBoundToBrain(
firstChildDirector,
brain,
"First Child Camera Track");
secondChildTimeline = CreateTimelineBoundToBrain(
secondChildDirector,
brain,
"Second Child Camera Track");
Assert.That(
AICameraTimelinePreviewImporter.ResolveCinemachineBrainForEditor(
director,
timeline,
Array.Empty<CinemachineBrain>()),
Is.SameAs(brain));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(cameraObject);
Object.DestroyImmediate(firstChildTimeline);
Object.DestroyImmediate(secondChildTimeline);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionRejectsAmbiguousDescendantBindings()
{
var directorObject = new GameObject("Director");
var firstChildObject = new GameObject("First Camera Director");
var secondChildObject = new GameObject("Second Camera Director");
var firstCameraObject = new GameObject("First Configured Camera");
var secondCameraObject = new GameObject("Second Configured Camera");
firstChildObject.transform.SetParent(directorObject.transform, false);
secondChildObject.transform.SetParent(directorObject.transform, false);
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
TimelineAsset firstChildTimeline = null;
TimelineAsset secondChildTimeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
director.playableAsset = timeline;
var firstChildDirector =
firstChildObject.AddComponent<PlayableDirector>();
var secondChildDirector =
secondChildObject.AddComponent<PlayableDirector>();
firstCameraObject.AddComponent<Camera>();
var firstBrain =
firstCameraObject.AddComponent<CinemachineBrain>();
secondCameraObject.AddComponent<Camera>();
var secondBrain =
secondCameraObject.AddComponent<CinemachineBrain>();
firstChildTimeline = CreateTimelineBoundToBrain(
firstChildDirector,
firstBrain,
"First Child Camera Track");
secondChildTimeline = CreateTimelineBoundToBrain(
secondChildDirector,
secondBrain,
"Second Child Camera Track");
Assert.That(
() => AICameraTimelinePreviewImporter
.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { firstBrain, secondBrain }),
Throws.TypeOf<InvalidOperationException>()
.With.Message.Contains("Descendant PlayableDirectors"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(firstCameraObject);
Object.DestroyImmediate(secondCameraObject);
Object.DestroyImmediate(firstChildTimeline);
Object.DestroyImmediate(secondChildTimeline);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionPrefersDirectBindingOverDescendants()
{
var directorObject = new GameObject("Director");
var childDirectorObject = new GameObject("Camera Director");
var directCameraObject = new GameObject("Direct Camera");
var childCameraObject = new GameObject("Child Camera");
childDirectorObject.transform.SetParent(directorObject.transform, false);
TimelineAsset timeline = null;
TimelineAsset childTimeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
var childDirector =
childDirectorObject.AddComponent<PlayableDirector>();
directCameraObject.AddComponent<Camera>();
var directBrain =
directCameraObject.AddComponent<CinemachineBrain>();
childCameraObject.AddComponent<Camera>();
var childBrain =
childCameraObject.AddComponent<CinemachineBrain>();
timeline = CreateTimelineBoundToBrain(
director,
directBrain,
"Direct Camera Track");
childTimeline = CreateTimelineBoundToBrain(
childDirector,
childBrain,
"Child Camera Track");
Assert.That(
AICameraTimelinePreviewImporter.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { childBrain, directBrain }),
Is.SameAs(directBrain));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(directCameraObject);
Object.DestroyImmediate(childCameraObject);
Object.DestroyImmediate(childTimeline);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionRejectsExplicitBrainFromAnotherScene()
{
var directorObject = new GameObject("Director");
var cameraObject = new GameObject("Foreign Camera");
var foreignScene = EditorSceneManager.NewPreviewScene();
SceneManager.MoveGameObjectToScene(cameraObject, foreignScene);
TimelineAsset timeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
cameraObject.AddComponent<Camera>();
var brain = cameraObject.AddComponent<CinemachineBrain>();
timeline = CreateTimelineBoundToBrain(
director,
brain,
"Foreign Camera Track");
Assert.That(
() => AICameraTimelinePreviewImporter
.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { brain }),
Throws.TypeOf<InvalidOperationException>()
.With.Message.Contains("same scene"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(cameraObject);
Object.DestroyImmediate(timeline);
EditorSceneManager.ClosePreviewScene(foreignScene);
}
}
[Test]
public void BrainResolutionRejectsExplicitBrainWithoutCamera()
{
var directorObject = new GameObject("Director");
var cameraObject = new GameObject("Camera-less Brain");
TimelineAsset timeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
var camera = cameraObject.AddComponent<Camera>();
var brain = cameraObject.AddComponent<CinemachineBrain>();
Object.DestroyImmediate(camera);
timeline = CreateTimelineBoundToBrain(
director,
brain,
"Invalid Camera Track");
Assert.That(
() => AICameraTimelinePreviewImporter
.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { brain }),
Throws.TypeOf<InvalidOperationException>()
.With.Message.Contains("Camera component"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(cameraObject);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionAcceptsSeparateControlledOutputCamera()
{
var directorObject = new GameObject("Director");
var brainObject = new GameObject("Configured Brain");
var outputCameraObject = new GameObject("Controlled Output Camera");
TimelineAsset timeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
var brain = brainObject.AddComponent<CinemachineBrain>();
var outputCamera = outputCameraObject.AddComponent<Camera>();
brain.ControlledObject = outputCameraObject;
timeline = CreateTimelineBoundToBrain(
director,
brain,
"Controlled Output Camera Track");
Assert.That(brain.OutputCamera, Is.SameAs(outputCamera));
Assert.That(
AICameraTimelinePreviewImporter.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { brain }),
Is.SameAs(brain));
Assert.That(
AICameraTimelinePreviewImporter
.GetPreviewOutputStatusForEditor(brain),
Does.Contain(outputCameraObject.name));
Assert.That(
AICameraTimelinePreviewImporter
.GetPreviewOutputStatusForEditor(brain),
Does.Contain("are active"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(brainObject);
Object.DestroyImmediate(outputCameraObject);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionUsesControlledOutputMainCameraForFallback()
{
var directorObject = new GameObject("Director");
var mainBrainObject = new GameObject("Main Output Brain");
var mainCameraObject = new GameObject("Main Output Camera");
var secondaryCameraObject = new GameObject("Secondary Camera");
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
try
{
var director = directorObject.AddComponent<PlayableDirector>();
director.playableAsset = timeline;
var mainBrain = mainBrainObject.AddComponent<CinemachineBrain>();
mainCameraObject.tag = "MainCamera";
mainCameraObject.AddComponent<Camera>();
mainBrain.ControlledObject = mainCameraObject;
secondaryCameraObject.AddComponent<Camera>();
var secondaryBrain =
secondaryCameraObject.AddComponent<CinemachineBrain>();
Assert.That(
AICameraTimelinePreviewImporter.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { secondaryBrain, mainBrain }),
Is.SameAs(mainBrain));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(mainBrainObject);
Object.DestroyImmediate(mainCameraObject);
Object.DestroyImmediate(secondaryCameraObject);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionRejectsControlledOutputCameraFromAnotherScene()
{
var directorObject = new GameObject("Director");
var brainObject = new GameObject("Configured Brain");
var outputCameraObject = new GameObject("Foreign Output Camera");
var foreignScene = EditorSceneManager.NewPreviewScene();
SceneManager.MoveGameObjectToScene(outputCameraObject, foreignScene);
TimelineAsset timeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
var brain = brainObject.AddComponent<CinemachineBrain>();
outputCameraObject.AddComponent<Camera>();
brain.ControlledObject = outputCameraObject;
timeline = CreateTimelineBoundToBrain(
director,
brain,
"Foreign Controlled Output Track");
Assert.That(
() => AICameraTimelinePreviewImporter
.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { brain }),
Throws.TypeOf<InvalidOperationException>()
.With.Message.Contains("output Camera")
.And.Message.Contains("same scene"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(brainObject);
Object.DestroyImmediate(outputCameraObject);
Object.DestroyImmediate(timeline);
EditorSceneManager.ClosePreviewScene(foreignScene);
}
}
[Test]
public void InactiveStagingCloneNeverActivatesAndPreservesSourcePlayback()
{
var parentObject = new GameObject("Preview Parent");
var sourceObject = new GameObject("Source Director");
var motionObject = new GameObject("Motion Director");
var authoredCameraObject = new GameObject("Authored Virtual Camera");
sourceObject.transform.SetParent(parentObject.transform, false);
motionObject.transform.SetParent(sourceObject.transform, false);
authoredCameraObject.transform.SetParent(
motionObject.transform,
false);
parentObject.transform.position = new Vector3(4f, -2f, 7f);
sourceObject.transform.localPosition = new Vector3(1f, 2f, 3f);
var sourceTimeline = ScriptableObject.CreateInstance<TimelineAsset>();
var motionTimeline = ScriptableObject.CreateInstance<TimelineAsset>();
GameObject cloneObject = null;
PlayableDirector motionDirector = null;
try
{
var sourceDirector =
sourceObject.AddComponent<PlayableDirector>();
sourceDirector.playOnAwake = false;
sourceDirector.playableAsset = sourceTimeline;
motionDirector = motionObject.AddComponent<PlayableDirector>();
motionDirector.playOnAwake = true;
motionDirector.timeUpdateMode = DirectorUpdateMode.Manual;
motionDirector.playableAsset = motionTimeline;
var authoredCamera = authoredCameraObject
.AddComponent<CinemachineCamera>();
sourceObject.AddComponent<PreviewCloneActivationProbe>();
motionDirector.Play();
motionDirector.time = 0.4d;
motionDirector.Evaluate();
var sourceWorldPosition = sourceObject.transform.position;
var sourcePlaybackState = motionDirector.state;
PreviewCloneActivationProbe.EnableCount = 0;
cloneObject = AICameraTimelinePreviewImporter
.InstantiateInactivePreviewCloneForEditor(
sourceObject,
parentObject.transform);
var cloneMotionDirector = cloneObject.transform
.Find(motionObject.name)
.GetComponent<PlayableDirector>();
var cloneAuthoredCamera = cloneObject.transform
.Find($"{motionObject.name}/{authoredCameraObject.name}")
.GetComponent<CinemachineCamera>();
Assert.That(cloneObject.transform.parent, Is.SameAs(parentObject.transform));
Assert.That(cloneObject.transform.position, Is.EqualTo(sourceWorldPosition));
Assert.That(cloneObject.activeSelf, Is.False);
Assert.That(cloneObject.activeInHierarchy, Is.False);
Assert.That(PreviewCloneActivationProbe.EnableCount, Is.Zero);
Assert.That(cloneMotionDirector.playOnAwake, Is.True);
Assert.That(cloneMotionDirector.playableGraph.IsValid(), Is.False);
Assert.That(cloneAuthoredCamera.enabled, Is.True);
Assert.That(cloneAuthoredCamera.isActiveAndEnabled, Is.False);
Assert.That(sourceObject.activeSelf, Is.True);
Assert.That(sourceObject.activeInHierarchy, Is.True);
Assert.That(motionDirector.state, Is.EqualTo(sourcePlaybackState));
Assert.That(motionDirector.time, Is.EqualTo(0.4d).Within(0.000001d));
Assert.That(authoredCamera.enabled, Is.True);
Assert.That(authoredCamera.isActiveAndEnabled, Is.True);
}
finally
{
PreviewCloneActivationProbe.EnableCount = 0;
if (motionDirector != null)
{
motionDirector.Stop();
}
Object.DestroyImmediate(cloneObject);
Object.DestroyImmediate(parentObject);
Object.DestroyImmediate(motionTimeline);
Object.DestroyImmediate(sourceTimeline);
}
}
[Test]
public void PreviewCloneSuppressesOnlyAuthoredCinemachineOutputs()
{
var sourceObject = new GameObject("Source Director");
var motionObject = new GameObject("Motion Director");
var authoredVirtualCameraObject =
new GameObject("Authored Virtual Camera");
var cameraObject = new GameObject("Authored Camera");
motionObject.transform.SetParent(sourceObject.transform, false);
authoredVirtualCameraObject.transform.SetParent(
motionObject.transform,
false);
var sourceTimeline = ScriptableObject.CreateInstance<TimelineAsset>();
TimelineAsset motionTimeline = null;
GameObject previewObject = null;
try
{
var sourceDirector =
sourceObject.AddComponent<PlayableDirector>();
sourceDirector.playOnAwake = false;
sourceDirector.playableAsset = sourceTimeline;
var motionDirector =
motionObject.AddComponent<PlayableDirector>();
motionDirector.playOnAwake = false;
var motionAnimator = motionObject.AddComponent<Animator>();
var authoredVirtualCamera = authoredVirtualCameraObject
.AddComponent<CinemachineCamera>();
cameraObject.AddComponent<Camera>();
var authoredBrain =
cameraObject.AddComponent<CinemachineBrain>();
motionTimeline = CreateTimelineBoundToBrain(
motionDirector,
authoredBrain,
"Authored Camera Track");
var motionTrack = motionTimeline.CreateTrack<AnimationTrack>(
null,
"Motion Track");
motionDirector.SetGenericBinding(motionTrack, motionAnimator);
previewObject = Object.Instantiate(sourceObject);
var previewMotionObject =
previewObject.transform.Find(motionObject.name).gameObject;
var previewMotionDirector =
previewMotionObject.GetComponent<PlayableDirector>();
var previewMotionAnimator =
previewMotionObject.GetComponent<Animator>();
var previewAuthoredVirtualCamera = previewMotionObject.transform
.Find(authoredVirtualCameraObject.name)
.GetComponent<CinemachineCamera>();
var authoredCameraTrack = motionTimeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.Single();
var motionTimelineWasDirty = EditorUtility.IsDirty(motionTimeline);
var suppressionState = AICameraTimelinePreviewImporter
.CaptureAuthoredCinemachineState(
previewObject.GetComponent<PlayableDirector>());
Assert.That(previewObject.activeSelf, Is.True);
Assert.That(
previewMotionDirector.GetGenericBinding(authoredCameraTrack),
Is.SameAs(authoredBrain));
Assert.That(
previewMotionDirector.GetGenericBinding(motionTrack),
Is.SameAs(previewMotionAnimator));
Assert.That(previewAuthoredVirtualCamera.enabled, Is.True);
Assert.That(authoredVirtualCamera.enabled, Is.True);
AICameraTimelinePreviewImporter
.SuppressAuthoredCinemachineOutputsForEditor(
previewObject.GetComponent<PlayableDirector>());
Assert.That(
previewMotionDirector.GetGenericBinding(authoredCameraTrack),
Is.Null);
Assert.That(
previewMotionDirector.GetGenericBinding(motionTrack),
Is.SameAs(previewMotionAnimator));
Assert.That(previewAuthoredVirtualCamera.enabled, Is.False);
Assert.That(authoredVirtualCamera.enabled, Is.True);
Assert.That(
previewMotionDirector.playableAsset,
Is.SameAs(motionTimeline));
Assert.That(
motionTimeline.GetOutputTracks(),
Does.Contain(authoredCameraTrack));
Assert.That(
motionTimeline.GetOutputTracks(),
Does.Contain(motionTrack));
Assert.That(
motionDirector.GetGenericBinding(authoredCameraTrack),
Is.SameAs(authoredBrain));
Assert.That(
motionDirector.GetGenericBinding(motionTrack),
Is.SameAs(motionAnimator));
Assert.That(
motionDirector.playableAsset,
Is.SameAs(motionTimeline));
Assert.That(
EditorUtility.IsDirty(motionTimeline),
Is.EqualTo(motionTimelineWasDirty));
AICameraTimelinePreviewImporter.RestoreAuthoredCinemachineState(
suppressionState);
Assert.That(
previewMotionDirector.GetGenericBinding(authoredCameraTrack),
Is.SameAs(authoredBrain));
Assert.That(
previewMotionDirector.GetGenericBinding(motionTrack),
Is.SameAs(previewMotionAnimator));
Assert.That(previewAuthoredVirtualCamera.enabled, Is.True);
Assert.That(authoredVirtualCamera.enabled, Is.True);
}
finally
{
Object.DestroyImmediate(previewObject);
Object.DestroyImmediate(sourceObject);
Object.DestroyImmediate(cameraObject);
Object.DestroyImmediate(motionTimeline);
Object.DestroyImmediate(sourceTimeline);
}
}
[Test]
public void PreviewCloneKeepsParentGeneratedCinemachineBinding()
{
var sourceObject = new GameObject("Source Director");
var motionObject = new GameObject("Motion Director");
var generatedCameraObject = new GameObject("Generated Camera");
var authoredCameraObject = new GameObject("Authored Camera");
motionObject.transform.SetParent(sourceObject.transform, false);
TimelineAsset sourceTimeline = null;
TimelineAsset motionTimeline = null;
GameObject previewObject = null;
try
{
var sourceDirector =
sourceObject.AddComponent<PlayableDirector>();
sourceDirector.playOnAwake = false;
var motionDirector =
motionObject.AddComponent<PlayableDirector>();
motionDirector.playOnAwake = false;
generatedCameraObject.AddComponent<Camera>();
var generatedBrain =
generatedCameraObject.AddComponent<CinemachineBrain>();
authoredCameraObject.AddComponent<Camera>();
var authoredBrain =
authoredCameraObject.AddComponent<CinemachineBrain>();
sourceTimeline = CreateTimelineBoundToBrain(
sourceDirector,
generatedBrain,
AICameraTimelinePreviewImporter.GeneratedCinemachineTrackName);
motionTimeline = CreateTimelineBoundToBrain(
motionDirector,
authoredBrain,
"Authored Camera Track");
var generatedTrack = sourceTimeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.Single();
var authoredTrack = motionTimeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.Single();
previewObject = Object.Instantiate(sourceObject);
var previewDirector =
previewObject.GetComponent<PlayableDirector>();
var previewMotionDirector = previewObject.transform
.Find(motionObject.name)
.GetComponent<PlayableDirector>();
AICameraTimelinePreviewImporter
.SuppressAuthoredCinemachineOutputsForEditor(
previewDirector);
Assert.That(
previewDirector.GetGenericBinding(generatedTrack),
Is.SameAs(generatedBrain));
Assert.That(
previewMotionDirector.GetGenericBinding(authoredTrack),
Is.Null);
Assert.That(
sourceDirector.GetGenericBinding(generatedTrack),
Is.SameAs(generatedBrain));
Assert.That(
motionDirector.GetGenericBinding(authoredTrack),
Is.SameAs(authoredBrain));
}
finally
{
Object.DestroyImmediate(previewObject);
Object.DestroyImmediate(sourceObject);
Object.DestroyImmediate(generatedCameraObject);
Object.DestroyImmediate(authoredCameraObject);
Object.DestroyImmediate(motionTimeline);
Object.DestroyImmediate(sourceTimeline);
}
}
[Test]
public void FinalSuppressionDoesNotCreatePartialUndoEntry()
{
var destinationObject = new GameObject("Destination Director");
var motionObject = new GameObject("Motion Director");
var authoredVirtualCameraObject =
new GameObject("Authored Virtual Camera");
var outputCameraObject = new GameObject("Output Camera");
var undoSentinelObject = new GameObject("Undo Sentinel");
motionObject.transform.SetParent(destinationObject.transform, false);
authoredVirtualCameraObject.transform.SetParent(
motionObject.transform,
false);
var destinationTimeline =
ScriptableObject.CreateInstance<TimelineAsset>();
TimelineAsset motionTimeline = null;
try
{
var destinationDirector =
destinationObject.AddComponent<PlayableDirector>();
destinationDirector.playableAsset = destinationTimeline;
var motionDirector =
motionObject.AddComponent<PlayableDirector>();
var authoredVirtualCamera = authoredVirtualCameraObject
.AddComponent<CinemachineCamera>();
outputCameraObject.AddComponent<Camera>();
var brain = outputCameraObject.AddComponent<CinemachineBrain>();
motionTimeline = CreateTimelineBoundToBrain(
motionDirector,
brain,
"Authored Camera Track");
var cameraTrack = motionTimeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.Single();
Undo.ClearAll();
Undo.IncrementCurrentGroup();
var sentinelGroup = Undo.GetCurrentGroup();
Undo.RegisterCompleteObjectUndo(
undoSentinelObject.transform,
"Final Suppression Undo Sentinel");
undoSentinelObject.transform.position = Vector3.one;
Undo.CollapseUndoOperations(sentinelGroup);
Undo.FlushUndoRecordObjects();
Undo.IncrementCurrentGroup();
AICameraTimelinePreviewImporter
.SuppressAuthoredCinemachineOutputsForEditor(
destinationDirector,
false);
Undo.FlushUndoRecordObjects();
Assert.That(
motionDirector.GetGenericBinding(cameraTrack),
Is.Null);
Assert.That(authoredVirtualCamera.enabled, Is.False);
Undo.PerformUndo();
Assert.That(undoSentinelObject.transform.position, Is.EqualTo(Vector3.zero));
Assert.That(
motionDirector.GetGenericBinding(cameraTrack),
Is.Null);
Assert.That(authoredVirtualCamera.enabled, Is.False);
}
finally
{
Undo.ClearAll();
Object.DestroyImmediate(destinationObject);
Object.DestroyImmediate(outputCameraObject);
Object.DestroyImmediate(undoSentinelObject);
Object.DestroyImmediate(motionTimeline);
Object.DestroyImmediate(destinationTimeline);
}
}
[Test]
public void SuppressionRebuildsPlayingDirectorAndReleasesBrainOverride()
{
var sourceObject = new GameObject("Source Director");
var motionObject = new GameObject("Motion Director");
var authoredCameraObject = new GameObject("Authored Virtual Camera");
var outputCameraObject = new GameObject("Output Camera");
motionObject.transform.SetParent(sourceObject.transform, false);
authoredCameraObject.transform.SetParent(
motionObject.transform,
false);
var sourceTimeline = ScriptableObject.CreateInstance<TimelineAsset>();
var motionTimeline = ScriptableObject.CreateInstance<TimelineAsset>();
PlayableDirector motionDirector = null;
try
{
var sourceDirector =
sourceObject.AddComponent<PlayableDirector>();
sourceDirector.playOnAwake = false;
sourceDirector.playableAsset = sourceTimeline;
motionDirector = motionObject.AddComponent<PlayableDirector>();
motionDirector.playOnAwake = false;
motionDirector.timeUpdateMode = DirectorUpdateMode.Manual;
motionDirector.playableAsset = motionTimeline;
var authoredCamera =
authoredCameraObject.AddComponent<CinemachineCamera>();
outputCameraObject.AddComponent<Camera>();
var brain = outputCameraObject.AddComponent<CinemachineBrain>();
brain.UpdateMethod = CinemachineBrain.UpdateMethods.ManualUpdate;
var track = motionTimeline.CreateTrack<CinemachineTrack>(
null,
"Authored Camera Track");
var clip = track.CreateDefaultClip();
clip.start = 0d;
clip.duration = 1d;
var shot = clip.asset as CinemachineShot;
Assert.That(shot, Is.Not.Null);
var exposedName = new PropertyName("AuthoredCameraForSuppressionTest");
shot.VirtualCamera.exposedName = exposedName;
shot.VirtualCamera.defaultValue = authoredCamera;
motionDirector.SetReferenceValue(exposedName, authoredCamera);
motionDirector.SetGenericBinding(track, brain);
motionDirector.Play();
motionDirector.time = 0.25d;
motionDirector.Evaluate();
brain.ManualUpdate(100, 1f / 60f);
Assert.That(motionDirector.playableGraph.IsValid(), Is.True);
Assert.That(motionDirector.state, Is.EqualTo(PlayState.Playing));
Assert.That(brain.ActiveVirtualCamera, Is.SameAs(authoredCamera));
var suppressionState = AICameraTimelinePreviewImporter
.CaptureAuthoredCinemachineState(sourceDirector);
AICameraTimelinePreviewImporter
.SuppressAuthoredCinemachineOutputsForEditor(sourceDirector);
brain.ManualUpdate(101, 1f / 60f);
Assert.That(motionDirector.state, Is.EqualTo(PlayState.Playing));
Assert.That(motionDirector.time, Is.EqualTo(0.25d).Within(0.000001d));
Assert.That(motionDirector.GetGenericBinding(track), Is.Null);
Assert.That(authoredCamera.enabled, Is.False);
Assert.That(brain.ActiveVirtualCamera, Is.Not.SameAs(authoredCamera));
AICameraTimelinePreviewImporter.RestoreAuthoredCinemachineState(
suppressionState);
brain.ManualUpdate(102, 1f / 60f);
Assert.That(motionDirector.state, Is.EqualTo(PlayState.Playing));
Assert.That(motionDirector.time, Is.EqualTo(0.25d).Within(0.000001d));
Assert.That(
motionDirector.GetGenericBinding(track),
Is.SameAs(brain));
Assert.That(authoredCamera.enabled, Is.True);
Assert.That(brain.ActiveVirtualCamera, Is.SameAs(authoredCamera));
}
finally
{
if (motionDirector != null)
{
motionDirector.Stop();
}
Object.DestroyImmediate(sourceObject);
Object.DestroyImmediate(outputCameraObject);
Object.DestroyImmediate(motionTimeline);
Object.DestroyImmediate(sourceTimeline);
}
}
[TestCase(false)] [TestCase(false)]
[TestCase(true)] [TestCase(true)]
public void GeneratedDirectorySupportsLegacyAndDeclaredHashes( public void GeneratedDirectorySupportsLegacyAndDeclaredHashes(
@ -1632,18 +778,6 @@ namespace Streamingle.Editor.Tests
} }
} }
private static TimelineAsset CreateTimelineBoundToBrain(
PlayableDirector director,
CinemachineBrain brain,
string trackName)
{
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
var track = timeline.CreateTrack<CinemachineTrack>(null, trackName);
director.playableAsset = timeline;
director.SetGenericBinding(track, brain);
return timeline;
}
private static string CreateGeneratedDirectory(bool includeHashes) private static string CreateGeneratedDirectory(bool includeHashes)
{ {
var directory = Path.Combine( var directory = Path.Combine(

Binary file not shown.

View File

@ -1,7 +1,7 @@
{ {
"schemaVersion": "cw-camera-worker-build-identity-v1", "schemaVersion": "cw-camera-worker-build-identity-v1",
"workerVersion": "0.1.9", "workerVersion": "0.1.8",
"createdUtc": "2026-08-15T16:31:20.228140+00:00", "createdUtc": "2026-08-15T15:56:01.810396+00:00",
"python": "3.12.13", "python": "3.12.13",
"sourceRootRelative": "cwai_sources/repository", "sourceRootRelative": "cwai_sources/repository",
"sourceSha256": { "sourceSha256": {
@ -10,9 +10,9 @@
"MachineLearning/CameraDirector/camera_kinematics.py": "e94eefcb56c4ebdccbc57ae8d3650109b2c912e3642892ac13add9409db21a7f", "MachineLearning/CameraDirector/camera_kinematics.py": "e94eefcb56c4ebdccbc57ae8d3650109b2c912e3642892ac13add9409db21a7f",
"MachineLearning/CameraDirector/camera_runtime_data.py": "8ef2e84a9d79b24c3f3232b1f18da49741988765044dffc6075f6e6dd5ff001d", "MachineLearning/CameraDirector/camera_runtime_data.py": "8ef2e84a9d79b24c3f3232b1f18da49741988765044dffc6075f6e6dd5ff001d",
"MachineLearning/CameraDirector/cw_camera_cli.py": "b523fdf6820b3b203dcdef80877a6ad9576fba41d7285c790c300e1813789012", "MachineLearning/CameraDirector/cw_camera_cli.py": "b523fdf6820b3b203dcdef80877a6ad9576fba41d7285c790c300e1813789012",
"MachineLearning/CameraDirector/cw_camera_runtime.py": "83968ed0b952a6ecf4673d31070deea677fff712c0dcae21878d527d0e67b7d4", "MachineLearning/CameraDirector/cw_camera_runtime.py": "5cc252ff45b2a80dffbeea14ad37013ba4a428c4f6fe31a617957eec6c823429",
"MachineLearning/CameraDirector/data_driven_cut_planner.py": "36cf86a80577278d2e966cb0ebf7c3110b5b1dda5abd1d8a6b0a76e31d10e32b", "MachineLearning/CameraDirector/data_driven_cut_planner.py": "36cf86a80577278d2e966cb0ebf7c3110b5b1dda5abd1d8a6b0a76e31d10e32b",
"MachineLearning/CameraDirector/generate_hybrid.py": "af94ea04f1a05f9248086790293d88e7cb2b9d0123d6e126ab275c87b9022760", "MachineLearning/CameraDirector/generate_hybrid.py": "bd37744b7b6573bf14be4f0ec5202f1465e11bff71ccabec210533e135b546c3",
"MachineLearning/CameraDirector/hybrid_candidate_cache.py": "a0a5b6a8f612f18cb2875394f17ed89950e1e62e38c1226457d9e35848f6e380", "MachineLearning/CameraDirector/hybrid_candidate_cache.py": "a0a5b6a8f612f18cb2875394f17ed89950e1e62e38c1226457d9e35848f6e380",
"MachineLearning/CameraDirector/hybrid_cut_reference.py": "306e650dc193fd0d9c42fd52d3f14eef112d1a0da2a27c13b88256d42d8542aa", "MachineLearning/CameraDirector/hybrid_cut_reference.py": "306e650dc193fd0d9c42fd52d3f14eef112d1a0da2a27c13b88256d42d8542aa",
"MachineLearning/CameraDirector/hybrid_preparation_cache.py": "e4d062a48cf39e01ea97951d103a37817c860e94f440f49f080c622b4969fb5a", "MachineLearning/CameraDirector/hybrid_preparation_cache.py": "e4d062a48cf39e01ea97951d103a37817c860e94f440f49f080c622b4969fb5a",
@ -20,11 +20,11 @@
"MachineLearning/CameraDirector/planner.py": "8575788c0ff1e984f8238354dcd6de2dab9f62afeb71fa4b77ebfc7c66400372", "MachineLearning/CameraDirector/planner.py": "8575788c0ff1e984f8238354dcd6de2dab9f62afeb71fa4b77ebfc7c66400372",
"MachineLearning/CameraDirector/shot_features.py": "fd912b00320ea53ca682c010de6ed1102edcd021ceb4822a5785033a500786fe", "MachineLearning/CameraDirector/shot_features.py": "fd912b00320ea53ca682c010de6ed1102edcd021ceb4822a5785033a500786fe",
"MachineLearning/CameraDirector/train.py": "070d1c4159c56a2107ae0d383ab0b716b12c13186d45383e94b83d3ba6305d03", "MachineLearning/CameraDirector/train.py": "070d1c4159c56a2107ae0d383ab0b716b12c13186d45383e94b83d3ba6305d03",
"MachineLearning/CameraDirector/trajectory_quality.py": "3d2c0a1b6eff8d629dff4211aec38b46c8fd450220f72a31e91f015cd3cc8565" "MachineLearning/CameraDirector/trajectory_quality.py": "20cd2ddc1b2349f8c33f6d1460580f37964a521eaa86ceee5b8ae457512ba49d"
}, },
"preparationLogicIdentifier": "1cb808e207d54854267a1020a658812c79e9bad5a5296f78140228b3a15eeb3b", "preparationLogicIdentifier": "1cb808e207d54854267a1020a658812c79e9bad5a5296f78140228b3a15eeb3b",
"candidateLogicIdentifier": "b3479b263b24f1419ee1221527878df73b6cf82addb5683753f6e26da632f640", "candidateLogicIdentifier": "e768283e8496360ff703b5135f85c51520a888787612b15456def131d5ee1d89",
"generationCodeIdentifier": "9d4c7b2d8192f5185ef6ee96378774ffd1365cdc9df0e4b7c7a934b199d4c66b", "generationCodeIdentifier": "80e9eeca5aed9e17a9cfb61b235f954ed4eb24308a1a1ca962e424dbb54bea88",
"generationCodeFiles": [ "generationCodeFiles": [
"adjacent_transition.py", "adjacent_transition.py",
"camera_kinematics.py", "camera_kinematics.py",

View File

@ -19,7 +19,7 @@ from typing import Any, Iterable
BUILD_IDENTITY_SCHEMA_VERSION = "cw-camera-worker-build-identity-v1" BUILD_IDENTITY_SCHEMA_VERSION = "cw-camera-worker-build-identity-v1"
BUILD_IDENTITY_FILE = "cw_camera_worker_build_identity.json" BUILD_IDENTITY_FILE = "cw_camera_worker_build_identity.json"
WORKER_VERSION = "0.1.9" WORKER_VERSION = "0.1.8"
def is_frozen_runtime() -> bool: def is_frozen_runtime() -> bool:

View File

@ -167,8 +167,7 @@ CANDIDATE_CACHE_STAGE_VERSIONS = {
"adjacent-transition-v3-no-forced-side-alternation" "adjacent-transition-v3-no-forced-side-alternation"
), ),
"trajectoryRetargeting": ( "trajectoryRetargeting": (
"retarget-v29-float32-bound-speed-adaptive-final-radial-post-kinematic-" "retarget-v28-speed-adaptive-final-radial-post-kinematic-audit-"
"audit-"
"residual-dynamics-semantic-exact-tracking-causal-entry-minimum-jerk" "residual-dynamics-semantic-exact-tracking-causal-entry-minimum-jerk"
), ),
"compositionSafety": ( "compositionSafety": (
@ -182,8 +181,7 @@ CANDIDATE_CACHE_STAGE_VERSIONS = {
"sequenceContinuation": SEED_VARIATION_SEQUENCE_CONTEXT_POLICY, "sequenceContinuation": SEED_VARIATION_SEQUENCE_CONTEXT_POLICY,
} }
TRAJECTORY_RETARGETING_VERSION = ( TRAJECTORY_RETARGETING_VERSION = (
"c2-position-float32-bound-speed-adaptive-final-radial-post-kinematic-" "c2-position-speed-adaptive-final-radial-post-kinematic-audit-v15"
"audit-v16"
) )
TEMPLATE_MOTION_SMOOTHING_VERSION = ( TEMPLATE_MOTION_SMOOTHING_VERSION = (
"savgol61-nearest-edge-step-guard-v1" "savgol61-nearest-edge-step-guard-v1"
@ -418,10 +416,6 @@ class ShotControl:
body_follow_strength: float = DEFAULT_BODY_FOLLOW_STRENGTH body_follow_strength: float = DEFAULT_BODY_FOLLOW_STRENGTH
class CandidateRetargetSafetyRejection(RuntimeError):
"""A candidate-local publishability failure that permits trying another rank."""
@dataclass(frozen=True) @dataclass(frozen=True)
class RealizedShotState: class RealizedShotState:
template: ShotTemplate template: ShotTemplate
@ -6874,9 +6868,7 @@ def retarget_template_to_shot(
<= smooth_maximum_distance + 1e-6 <= smooth_maximum_distance + 1e-6
) )
if not final_distance_bounds_passed: if not final_distance_bounds_passed:
raise CandidateRetargetSafetyRejection( raise RuntimeError("Final camera distance audit violated its bounds.")
"Final camera distance audit violated its bounds."
)
radial_distance_clamp_metrics = { radial_distance_clamp_metrics = {
**final_radial_distance_clamp_metrics, **final_radial_distance_clamp_metrics,
"initialPass": initial_radial_distance_clamp_metrics, "initialPass": initial_radial_distance_clamp_metrics,
@ -8452,8 +8444,7 @@ def evaluate_safe_candidate_pool(
safety_distance_scale: float = 1.0, safety_distance_scale: float = 1.0,
*, *,
cache_eligible: bool, cache_eligible: bool,
) -> dict | None: ) -> dict:
try:
candidate_result = retarget_template_to_shot( candidate_result = retarget_template_to_shot(
candidate_template, candidate_template,
previous, previous,
@ -8472,8 +8463,6 @@ def evaluate_safe_candidate_pool(
safety_distance_scale=safety_distance_scale, safety_distance_scale=safety_distance_scale,
front_facing_reference=front_facing_reference, front_facing_reference=front_facing_reference,
) )
except CandidateRetargetSafetyRejection:
return None
candidate_result["candidateRank"] = candidate_rank candidate_result["candidateRank"] = candidate_rank
candidate_result["selectionScore"] = candidate_selection_score( candidate_result["selectionScore"] = candidate_selection_score(
candidate_result, candidate_result,
@ -8580,11 +8569,6 @@ def evaluate_safe_candidate_pool(
) )
if not seed_variation_needs_more_candidates(safe_results): if not seed_variation_needs_more_candidates(safe_results):
break break
if not candidate_results:
raise CandidateRetargetSafetyRejection(
"All bounded retarget candidates failed their final safety audit."
)
else: else:
safe_results = selectable_safe_candidate_results( safe_results = selectable_safe_candidate_results(
candidate_results, candidate_results,

View File

@ -16,7 +16,7 @@ import numpy as np
TRAJECTORY_QUALITY_VERSION = "trajectory-quality-v2" TRAJECTORY_QUALITY_VERSION = "trajectory-quality-v2"
C2_RESAMPLING_POLICY_VERSION = "clamped-cubic-c2-endpoint-speed-guard-v1" C2_RESAMPLING_POLICY_VERSION = "clamped-cubic-c2-endpoint-speed-guard-v1"
RADIAL_CLAMP_POLICY_VERSION = "quintic-bound-identity-c2-speed-adaptive-v3" RADIAL_CLAMP_POLICY_VERSION = "quintic-bound-identity-c2-speed-adaptive-v2"
DYNAMICS_POLICY_VERSION = ( DYNAMICS_POLICY_VERSION = (
"camera-motion-stabilized-screen-residual-cost-hard-gate-v3" "camera-motion-stabilized-screen-residual-cost-hard-gate-v3"
) )
@ -698,59 +698,6 @@ def smooth_radial_distance_clamp(
): ):
raise RuntimeError("smooth radial clamp failed to enforce its bounds") raise RuntimeError("smooth radial clamp failed to enforce its bounds")
# The generated camera is published as float32. An oblique vector that is
# exactly on a radial plateau in float64 can acquire a norm a few ULPs past
# that plateau after its components are rounded independently. Audit and
# repair the actual published representation, not only the temporary
# float64 curve. The inward margin is microscopic (tens of micrometres at
# the largest supported radii) while remaining comfortably larger than the
# worst three-component float32 norm error.
published_result = result.astype(np.float32)
published_radii = np.linalg.norm(
published_result.astype(np.float64),
axis=1,
)
bound_span = upper_bound - lower_bound
publication_margin = min(
bound_span * 0.25,
max(
1e-6,
16.0
* float(np.finfo(np.float32).eps)
* max(1.0, abs(lower_bound), abs(upper_bound)),
),
)
lower_publication_violation = (
(published_radii < lower_bound) if lower_bound > 0.0 else np.zeros(
len(published_radii),
dtype=bool,
)
)
upper_publication_violation = published_radii > upper_bound
if np.any(lower_publication_violation):
published_result[lower_publication_violation] = (
directions[lower_publication_violation]
* (lower_bound + publication_margin)
).astype(np.float32)
if np.any(upper_publication_violation):
published_result[upper_publication_violation] = (
directions[upper_publication_violation]
* (upper_bound - publication_margin)
).astype(np.float32)
publication_projection = (
lower_publication_violation | upper_publication_violation
)
published_radii = np.linalg.norm(
published_result.astype(np.float64),
axis=1,
)
if np.any(published_radii < lower_bound - tolerance) or np.any(
published_radii > upper_bound + tolerance
):
raise RuntimeError(
"smooth radial clamp failed to enforce float32 publication bounds"
)
metadata: dict[str, object] = { metadata: dict[str, object] = {
"version": TRAJECTORY_QUALITY_VERSION, "version": TRAJECTORY_QUALITY_VERSION,
"policyVersion": RADIAL_CLAMP_POLICY_VERSION, "policyVersion": RADIAL_CLAMP_POLICY_VERSION,
@ -777,17 +724,13 @@ def smooth_radial_distance_clamp(
"directionFallbackFrameCount": direction_fallback_count, "directionFallbackFrameCount": direction_fallback_count,
"minimumInputDistanceMeters": float(np.min(radii)), "minimumInputDistanceMeters": float(np.min(radii)),
"maximumInputDistanceMeters": float(np.max(radii)), "maximumInputDistanceMeters": float(np.max(radii)),
"minimumOutputDistanceMeters": float(np.min(published_radii)), "minimumOutputDistanceMeters": float(np.min(output_radii)),
"maximumOutputDistanceMeters": float(np.max(published_radii)), "maximumOutputDistanceMeters": float(np.max(output_radii)),
"float32BoundaryProjectionFrameCount": int(
np.count_nonzero(publication_projection)
),
"float32BoundaryProjectionMarginMeters": publication_margin,
"maximumRadialAdjustmentMeters": float( "maximumRadialAdjustmentMeters": float(
np.max(np.abs(published_radii - radii)) np.max(np.abs(mapped_radii - radii))
), ),
} }
return published_result, metadata return result.astype(np.float32), metadata
def _persistent_direction_reversals( def _persistent_direction_reversals(

View File

@ -1,4 +1,4 @@
../../Scripts/wheel.exe,sha256=33fhzLGTaJQ0McB6oI5dG_iQAgUgrifXlD5WqsCV_T4,108448 ../../Scripts/wheel.exe,sha256=9jTRWXNV9efL3HxLn6B_Q7zW31wwIW3r808tmvMnVhQ,108448
wheel-0.45.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 wheel-0.45.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
wheel-0.45.1.dist-info/LICENSE.txt,sha256=MMI2GGeRCPPo6h0qZYx8pBe9_IkcmO8aifpP8MmChlQ,1107 wheel-0.45.1.dist-info/LICENSE.txt,sha256=MMI2GGeRCPPo6h0qZYx8pBe9_IkcmO8aifpP8MmChlQ,1107
wheel-0.45.1.dist-info/METADATA,sha256=mKz84H7m7jsxJyzeIcTVORiTb0NPMV39KvOIYhGgmjA,2313 wheel-0.45.1.dist-info/METADATA,sha256=mKz84H7m7jsxJyzeIcTVORiTb0NPMV39KvOIYhGgmjA,2313

View File

@ -1,7 +1,7 @@
{ {
"schemaVersion": "cw-camera-worker-distribution-build-v1", "schemaVersion": "cw-camera-worker-distribution-build-v1",
"createdUtc": "2026-08-15T16:32:26.2657601Z", "createdUtc": "2026-08-15T15:57:09.6006631Z",
"workerVersion": "0.1.9", "workerVersion": "0.1.8",
"protocolVersion": "1", "protocolVersion": "1",
"buildIdentitySchemaVersion": "cw-camera-worker-build-identity-v1", "buildIdentitySchemaVersion": "cw-camera-worker-build-identity-v1",
"buildEnvironment": { "buildEnvironment": {
@ -65,9 +65,9 @@
], ],
"payload": { "payload": {
"fileCount": 646, "fileCount": 646,
"bytes": 267781759, "bytes": 267778040,
"executableSha256": "7cdbaaba556b54215c77f2c64b4005df55e94e8a7feaeff06def5cd6e9449f21", "executableSha256": "559fb67e5dee12cedb0daf1a1f900460fbcf3ff51ece0e826da6ff353506d2ac",
"buildIdentitySha256": "de655829a299d6ec264217ae6c4a2ab2ec89821e4cffed8499c23b79caece1da", "buildIdentitySha256": "fe1e6e3fd8b22a4874bcf8dc5a93528f6e6aa359b42a6438bc0aba932f18a3bc",
"sanityRange": { "sanityRange": {
"minimumFileCount": 500, "minimumFileCount": 500,
"maximumFileCount": 1000, "maximumFileCount": 1000,

View File

@ -1,6 +1,6 @@
{ {
"name": "com.mingle.cw-ai", "name": "com.mingle.cw-ai",
"version": "0.4.14", "version": "0.4.12",
"displayName": "Mingle Camera Work AI", "displayName": "Mingle Camera Work AI",
"description": "Self-contained high-quality Unity Timeline camera generation with an embedded prepared reference library, per-shot editable clips, and A/B review tools.", "description": "Self-contained high-quality Unity Timeline camera generation with an embedded prepared reference library, per-shot editable clips, and A/B review tools.",
"unity": "6000.0", "unity": "6000.0",

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: f5f43322334040eda82a02238038c637
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

View File

@ -1,89 +0,0 @@
fileFormatVersion: 2
guid: 07da6f60b8c84789bc7ee4602f68d356
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 256
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings: []
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

View File

@ -1,89 +0,0 @@
fileFormatVersion: 2
guid: 8f25ac2c42604ab79638fbe063e38a1e
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 256
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings: []
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 KiB

View File

@ -1,89 +0,0 @@
fileFormatVersion: 2
guid: 0c84e46fbbf5438d8099a052a0afc864
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 256
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings: []
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

View File

@ -1,89 +0,0 @@
fileFormatVersion: 2
guid: 07856e0ad4ad45cea23acd5143fd834a
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 256
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings: []
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

View File

@ -1,89 +0,0 @@
fileFormatVersion: 2
guid: 9f06836401f24edab7eb10cfa8cc366c
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 256
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings: []
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

View File

@ -1,89 +0,0 @@
fileFormatVersion: 2
guid: cb343fa7887e4c33841ba7c34114ba1b
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 256
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings: []
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

View File

@ -1,89 +0,0 @@
fileFormatVersion: 2
guid: 6e8f1bdf638d4772bde06d7d0eeb8af3
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 256
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings: []
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,69 +0,0 @@
using System;
using System.Linq;
using Streamingle.Utils.LerpConstraints;
using UnityEditor;
using UnityEngine;
namespace Streamingle.Utils.LerpConstraintsEditor
{
[InitializeOnLoad]
internal static class LerpConstraintIconInstaller
{
private const string IconDirectory = "Packages/com.streamingle.utilities/Editor/Constraints/Icons";
private static readonly IconDefinition[] IconDefinitions =
{
new IconDefinition(typeof(LerpAimConstraint), "LerpAimConstraintIcon"),
new IconDefinition(typeof(LerpLookAtConstraint), "LerpLookAtConstraintIcon"),
new IconDefinition(typeof(LerpParentConstraint), "LerpParentConstraintIcon"),
new IconDefinition(typeof(LerpPositionConstraint), "LerpPositionConstraintIcon"),
new IconDefinition(typeof(LerpRotationConstraint), "LerpRotationConstraintIcon"),
new IconDefinition(typeof(LerpScaleConstraint), "LerpScaleConstraintIcon")
};
static LerpConstraintIconInstaller()
{
EditorApplication.delayCall += ApplyIcons;
}
private static void ApplyIcons()
{
string[] scriptGuids = AssetDatabase.FindAssets(
"t:MonoScript",
new[] { "Packages/com.streamingle.utilities/Runtime/Constraints" });
foreach (IconDefinition definition in IconDefinitions)
{
Texture2D icon = AssetDatabase.LoadAssetAtPath<Texture2D>(
$"{IconDirectory}/{definition.IconName}.png");
if (icon == null)
{
continue;
}
MonoScript script = scriptGuids
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<MonoScript>)
.FirstOrDefault(candidate => candidate != null && candidate.GetClass() == definition.ConstraintType);
if (script != null)
{
EditorGUIUtility.SetIconForObject(script, icon);
}
}
}
private readonly struct IconDefinition
{
public IconDefinition(Type constraintType, string iconName)
{
ConstraintType = constraintType;
IconName = iconName;
}
public Type ConstraintType { get; }
public string IconName { get; }
}
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 97633b0c6f0f4ca0a862c50e6de6f70b

View File

@ -1,13 +1,13 @@
using Streamingle.Utils.LerpConstraints; using Streamingle.Utils.RoughConstraints;
using UnityEditor; using UnityEditor;
using UnityEditorInternal; using UnityEditorInternal;
using UnityEngine; using UnityEngine;
namespace Streamingle.Utils.LerpConstraintsEditor namespace Streamingle.Utils.RoughConstraintsEditor
{ {
[CustomEditor(typeof(LerpConstraintBase), true)] [CustomEditor(typeof(RoughConstraintBase), true)]
[CanEditMultipleObjects] [CanEditMultipleObjects]
public sealed class LerpConstraintEditor : UnityEditor.Editor public sealed class RoughConstraintEditor : UnityEditor.Editor
{ {
private SerializedProperty constraintActive; private SerializedProperty constraintActive;
private SerializedProperty locked; private SerializedProperty locked;
@ -18,7 +18,7 @@ namespace Streamingle.Utils.LerpConstraintsEditor
private SerializedProperty useUnscaledTime; private SerializedProperty useUnscaledTime;
private SerializedProperty snapOnEnable; private SerializedProperty snapOnEnable;
private ReorderableList sourceList; private ReorderableList sourceList;
private bool lerpFollowFoldout = true; private bool roughFollowFoldout = true;
private bool settingsFoldout = true; private bool settingsFoldout = true;
private void OnEnable() private void OnEnable()
@ -62,7 +62,7 @@ namespace Streamingle.Utils.LerpConstraintsEditor
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
if (GUILayout.Button("Activate", EditorStyles.miniButton, GUILayout.Width(72f))) if (GUILayout.Button("Activate", EditorStyles.miniButton, GUILayout.Width(72f)))
{ {
ApplyAction("Activate Lerp Constraint", ActivateConstraint); ApplyAction("Activate Rough Constraint", ActivateConstraint);
} }
if (GUILayout.Button("Zero", EditorStyles.miniButton, GUILayout.Width(56f))) if (GUILayout.Button("Zero", EditorStyles.miniButton, GUILayout.Width(56f)))
@ -84,8 +84,8 @@ namespace Streamingle.Utils.LerpConstraintsEditor
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
foreach (var selectedTarget in targets) foreach (var selectedTarget in targets)
{ {
var constraint = (LerpConstraintBase)selectedTarget; var constraint = (RoughConstraintBase)selectedTarget;
Undo.RecordObject(constraint, "Change Lerp Constraint Lock"); Undo.RecordObject(constraint, "Change Rough Constraint Lock");
constraint.SetLocked(newValue); constraint.SetLocked(newValue);
EditorUtility.SetDirty(constraint); EditorUtility.SetDirty(constraint);
} }
@ -102,8 +102,8 @@ namespace Streamingle.Utils.LerpConstraintsEditor
private void DrawTypeSettings() private void DrawTypeSettings()
{ {
lerpFollowFoldout = EditorGUILayout.Foldout(lerpFollowFoldout, "Lerp Follow", true); roughFollowFoldout = EditorGUILayout.Foldout(roughFollowFoldout, "Rough Follow", true);
if (lerpFollowFoldout) if (roughFollowFoldout)
{ {
using (new EditorGUI.IndentLevelScope()) using (new EditorGUI.IndentLevelScope())
{ {
@ -128,27 +128,27 @@ namespace Streamingle.Utils.LerpConstraintsEditor
private void DrawTypeProperties() private void DrawTypeProperties()
{ {
if (target is LerpPositionConstraint) if (target is RoughPositionConstraint)
{ {
DrawProperties("constrainedAxis", "positionOffset", "offsetInSourceSpace"); DrawProperties("constrainedAxis", "positionOffset", "offsetInSourceSpace");
} }
else if (target is LerpRotationConstraint) else if (target is RoughRotationConstraint)
{ {
DrawProperties("constrainedAxis", "rotationOffset"); DrawProperties("constrainedAxis", "rotationOffset");
} }
else if (target is LerpScaleConstraint) else if (target is RoughScaleConstraint)
{ {
DrawProperties("constrainedAxis", "scaleOffset"); DrawProperties("constrainedAxis", "scaleOffset");
} }
else if (target is LerpParentConstraint) else if (target is RoughParentConstraint)
{ {
DrawProperties("constrainedPositionAxis", "positionOffset", "positionOffsetInSourceSpace", "constrainedRotationAxis", "rotationOffset"); DrawProperties("constrainedPositionAxis", "positionOffset", "positionOffsetInSourceSpace", "constrainedRotationAxis", "rotationOffset");
} }
else if (target is LerpAimConstraint) else if (target is RoughAimConstraint)
{ {
DrawProperties("aimAxis", "upAxis", "worldUpVector", "rotationOffset"); DrawProperties("aimAxis", "upAxis", "worldUpVector", "rotationOffset");
} }
else if (target is LerpLookAtConstraint) else if (target is RoughLookAtConstraint)
{ {
DrawProperties("forwardAxis", "worldUpVector", "targetPositionOffset", "rotationOffset"); DrawProperties("forwardAxis", "worldUpVector", "targetPositionOffset", "rotationOffset");
} }
@ -166,22 +166,22 @@ namespace Streamingle.Utils.LerpConstraintsEditor
} }
} }
private void ApplyAction(string undoName, System.Action<LerpConstraintBase> action) private void ApplyAction(string undoName, System.Action<RoughConstraintBase> action)
{ {
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
foreach (var selectedTarget in targets) foreach (var selectedTarget in targets)
{ {
var constraint = (LerpConstraintBase)selectedTarget; var constraint = (RoughConstraintBase)selectedTarget;
Undo.RecordObject(constraint.transform, undoName); Undo.RecordObject(constraint.transform, undoName);
action(constraint); action(constraint);
EditorUtility.SetDirty(constraint.transform); EditorUtility.SetDirty(constraint.transform);
} }
} }
private void ActivateConstraint(LerpConstraintBase constraint) private void ActivateConstraint(RoughConstraintBase constraint)
{ {
Undo.RecordObject(constraint, "Activate Lerp Constraint"); Undo.RecordObject(constraint, "Activate Rough Constraint");
constraint.ActivateConstraint(); constraint.ActivateConstraint();
EditorUtility.SetDirty(constraint); EditorUtility.SetDirty(constraint);
} }
@ -189,7 +189,7 @@ namespace Streamingle.Utils.LerpConstraintsEditor
private void ZeroOffsets() private void ZeroOffsets()
{ {
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
Undo.RecordObjects(targets, "Zero Lerp Constraint Offsets"); Undo.RecordObjects(targets, "Zero Rough Constraint Offsets");
SetVector3IfExists("positionOffset", Vector3.zero); SetVector3IfExists("positionOffset", Vector3.zero);
SetVector3IfExists("rotationOffset", Vector3.zero); SetVector3IfExists("rotationOffset", Vector3.zero);
@ -261,13 +261,13 @@ namespace Streamingle.Utils.LerpConstraintsEditor
private string GetSettingsTitle() private string GetSettingsTitle()
{ {
if (target is LerpLookAtConstraint) if (target is RoughLookAtConstraint)
{ {
return "Look At"; return "Look At";
} }
var name = target.GetType().Name; var name = target.GetType().Name;
name = name.Replace("Lerp", string.Empty).Replace("Constraint", string.Empty); name = name.Replace("Rough", string.Empty).Replace("Constraint", string.Empty);
return ObjectNames.NicifyVariableName(name); return ObjectNames.NicifyVariableName(name);
} }
} }

View File

@ -1,8 +1,8 @@
using Streamingle.Utils.LerpConstraints; using Streamingle.Utils.RoughConstraints;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
namespace Streamingle.Utils.LerpConstraintsEditor namespace Streamingle.Utils.RoughConstraintsEditor
{ {
[CustomPropertyDrawer(typeof(Vector3Bool))] [CustomPropertyDrawer(typeof(Vector3Bool))]
public sealed class Vector3BoolDrawer : UnityEditor.PropertyDrawer public sealed class Vector3BoolDrawer : UnityEditor.PropertyDrawer

View File

@ -37,7 +37,7 @@ Install **Git and Git LFS before opening Unity**, then use **Add package from
git URL** with: git URL** with:
```text ```text
https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.20 https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.18
``` ```
The Camera AI package includes the complete Windows x64 The Camera AI package includes the complete Windows x64

View File

@ -1,12 +1,10 @@
using UnityEngine; using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
namespace Streamingle.Utils.LerpConstraints namespace Streamingle.Utils.RoughConstraints
{ {
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughAimConstraint")]
[ExecuteAlways] [ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Aim Constraint")] [AddComponentMenu("Streamingle Utilities/Constraints/Rough Aim Constraint")]
public class LerpAimConstraint : LerpConstraintBase public class RoughAimConstraint : RoughConstraintBase
{ {
[Header("Aim")] [Header("Aim")]
public Vector3 aimAxis = Vector3.forward; public Vector3 aimAxis = Vector3.forward;

View File

@ -1,13 +1,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
#if UNITY_EDITOR #if UNITY_EDITOR
using UnityEditor; using UnityEditor;
#endif #endif
namespace Streamingle.Utils.LerpConstraints namespace Streamingle.Utils.RoughConstraints
{ {
public enum LerpConstraintUpdateMode public enum RoughConstraintUpdateMode
{ {
Update, Update,
LateUpdate, LateUpdate,
@ -15,9 +14,8 @@ namespace Streamingle.Utils.LerpConstraints
Manual Manual
} }
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughConstraintBase")]
[ExecuteAlways] [ExecuteAlways]
public abstract class LerpConstraintBase : MonoBehaviour public abstract class RoughConstraintBase : MonoBehaviour
{ {
[Header("Constraint")] [Header("Constraint")]
public bool constraintActive = true; public bool constraintActive = true;
@ -26,13 +24,13 @@ namespace Streamingle.Utils.LerpConstraints
[Range(0f, 1f)] [Range(0f, 1f)]
public float weight = 1f; public float weight = 1f;
public List<LerpConstraintSource> sources = new List<LerpConstraintSource>(); public List<RoughConstraintSource> sources = new List<RoughConstraintSource>();
[Header("Lerp Follow")] [Header("Rough Follow")]
[Min(0f)] [Min(0f)]
public float damping = 12f; public float damping = 12f;
public LerpConstraintUpdateMode updateMode = LerpConstraintUpdateMode.LateUpdate; public RoughConstraintUpdateMode updateMode = RoughConstraintUpdateMode.LateUpdate;
public bool useUnscaledTime; public bool useUnscaledTime;
public bool snapOnEnable = true; public bool snapOnEnable = true;
@ -49,7 +47,7 @@ namespace Streamingle.Utils.LerpConstraints
return 1f / 60f; return 1f / 60f;
} }
if (updateMode == LerpConstraintUpdateMode.FixedUpdate) if (updateMode == RoughConstraintUpdateMode.FixedUpdate)
{ {
return useUnscaledTime ? Time.fixedUnscaledDeltaTime : Time.fixedDeltaTime; return useUnscaledTime ? Time.fixedUnscaledDeltaTime : Time.fixedDeltaTime;
} }
@ -96,7 +94,7 @@ namespace Streamingle.Utils.LerpConstraints
{ {
if (!Application.isPlaying) if (!Application.isPlaying)
{ {
if (updateMode != LerpConstraintUpdateMode.Manual) if (updateMode != RoughConstraintUpdateMode.Manual)
{ {
Evaluate(); Evaluate();
} }
@ -104,7 +102,7 @@ namespace Streamingle.Utils.LerpConstraints
return; return;
} }
if (updateMode == LerpConstraintUpdateMode.Update) if (updateMode == RoughConstraintUpdateMode.Update)
{ {
Evaluate(); Evaluate();
} }
@ -117,7 +115,7 @@ namespace Streamingle.Utils.LerpConstraints
return; return;
} }
if (updateMode == LerpConstraintUpdateMode.LateUpdate) if (updateMode == RoughConstraintUpdateMode.LateUpdate)
{ {
Evaluate(); Evaluate();
} }
@ -130,7 +128,7 @@ namespace Streamingle.Utils.LerpConstraints
return; return;
} }
if (updateMode == LerpConstraintUpdateMode.FixedUpdate) if (updateMode == RoughConstraintUpdateMode.FixedUpdate)
{ {
Evaluate(); Evaluate();
} }
@ -167,7 +165,7 @@ namespace Streamingle.Utils.LerpConstraints
return; return;
} }
if (Application.isPlaying || updateMode == LerpConstraintUpdateMode.Manual) if (Application.isPlaying || updateMode == RoughConstraintUpdateMode.Manual)
{ {
return; return;
} }

View File

@ -1,10 +1,10 @@
using System; using System;
using UnityEngine; using UnityEngine;
namespace Streamingle.Utils.LerpConstraints namespace Streamingle.Utils.RoughConstraints
{ {
[Serializable] [Serializable]
public struct LerpConstraintSource public struct RoughConstraintSource
{ {
public Transform sourceTransform; public Transform sourceTransform;

View File

@ -1,12 +1,10 @@
using UnityEngine; using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
namespace Streamingle.Utils.LerpConstraints namespace Streamingle.Utils.RoughConstraints
{ {
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughLookAtConstraint")]
[ExecuteAlways] [ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Look At Constraint")] [AddComponentMenu("Streamingle Utilities/Constraints/Rough Look At Constraint")]
public class LerpLookAtConstraint : LerpConstraintBase public class RoughLookAtConstraint : RoughConstraintBase
{ {
[Header("Look At")] [Header("Look At")]
public Vector3 forwardAxis = Vector3.forward; public Vector3 forwardAxis = Vector3.forward;

View File

@ -1,12 +1,10 @@
using UnityEngine; using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
namespace Streamingle.Utils.LerpConstraints namespace Streamingle.Utils.RoughConstraints
{ {
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughParentConstraint")]
[ExecuteAlways] [ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Parent Constraint")] [AddComponentMenu("Streamingle Utilities/Constraints/Rough Parent Constraint")]
public class LerpParentConstraint : LerpConstraintBase public class RoughParentConstraint : RoughConstraintBase
{ {
[Header("Position")] [Header("Position")]
public Vector3Bool constrainedPositionAxis = Vector3Bool.All; public Vector3Bool constrainedPositionAxis = Vector3Bool.All;

View File

@ -1,12 +1,10 @@
using UnityEngine; using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
namespace Streamingle.Utils.LerpConstraints namespace Streamingle.Utils.RoughConstraints
{ {
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughPositionConstraint")]
[ExecuteAlways] [ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Position Constraint")] [AddComponentMenu("Streamingle Utilities/Constraints/Rough Position Constraint")]
public class LerpPositionConstraint : LerpConstraintBase public class RoughPositionConstraint : RoughConstraintBase
{ {
[Header("Position")] [Header("Position")]
public Vector3Bool constrainedAxis = Vector3Bool.All; public Vector3Bool constrainedAxis = Vector3Bool.All;

View File

@ -1,12 +1,10 @@
using UnityEngine; using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
namespace Streamingle.Utils.LerpConstraints namespace Streamingle.Utils.RoughConstraints
{ {
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughRotationConstraint")]
[ExecuteAlways] [ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Rotation Constraint")] [AddComponentMenu("Streamingle Utilities/Constraints/Rough Rotation Constraint")]
public class LerpRotationConstraint : LerpConstraintBase public class RoughRotationConstraint : RoughConstraintBase
{ {
[Header("Rotation")] [Header("Rotation")]
public Vector3Bool constrainedAxis = Vector3Bool.All; public Vector3Bool constrainedAxis = Vector3Bool.All;

View File

@ -1,12 +1,10 @@
using UnityEngine; using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
namespace Streamingle.Utils.LerpConstraints namespace Streamingle.Utils.RoughConstraints
{ {
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughScaleConstraint")]
[ExecuteAlways] [ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Scale Constraint")] [AddComponentMenu("Streamingle Utilities/Constraints/Rough Scale Constraint")]
public class LerpScaleConstraint : LerpConstraintBase public class RoughScaleConstraint : RoughConstraintBase
{ {
[Header("Scale")] [Header("Scale")]
public Vector3Bool constrainedAxis = Vector3Bool.All; public Vector3Bool constrainedAxis = Vector3Bool.All;

View File

@ -1,6 +1,6 @@
using System; using System;
namespace Streamingle.Utils.LerpConstraints namespace Streamingle.Utils.RoughConstraints
{ {
[Serializable] [Serializable]
public struct Vector3Bool public struct Vector3Bool

View File

@ -1,7 +1,7 @@
{ {
"name": "com.streamingle.utilities", "name": "com.streamingle.utilities",
"displayName": "Streamingle Utilities", "displayName": "Streamingle Utilities",
"version": "0.1.22", "version": "0.1.7",
"unity": "6000.0", "unity": "6000.0",
"description": "Reusable Streamingle runtime components and Unity editor utilities.", "description": "Reusable Streamingle runtime components and Unity editor utilities.",
"keywords": [ "keywords": [