Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1d1e8ae19 | ||
|
|
a24acd2901 | ||
|
|
150ef2d30c | ||
|
|
9cfdf3d947 |
@ -1,5 +1,26 @@
|
|||||||
# 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
|
||||||
|
|||||||
@ -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.18
|
https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.20
|
||||||
```
|
```
|
||||||
|
|
||||||
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.18 includes a compact, read-only `RuntimeData~` bundle with the
|
Version 0.1.20 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
|
||||||
|
|||||||
@ -440,7 +440,7 @@ namespace Streamingle.Editor
|
|||||||
originalDirector.gameObject.activeSelf;
|
originalDirector.gameObject.activeSelf;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
previewDirectorObject = UnityEngine.Object.Instantiate(
|
previewDirectorObject = InstantiateInactivePreviewCloneForEditor(
|
||||||
originalDirector.gameObject,
|
originalDirector.gameObject,
|
||||||
originalDirector.transform.parent);
|
originalDirector.transform.parent);
|
||||||
Undo.RegisterCreatedObjectUndo(
|
Undo.RegisterCreatedObjectUndo(
|
||||||
@ -456,6 +456,7 @@ namespace Streamingle.Editor
|
|||||||
originalTimeline,
|
originalTimeline,
|
||||||
previewDirector,
|
previewDirector,
|
||||||
copiedTimeline);
|
copiedTimeline);
|
||||||
|
SuppressAuthoredCinemachineOutputsForEditor(previewDirector);
|
||||||
MuteCopiedCameraTracks(previewDirector, copiedTimeline);
|
MuteCopiedCameraTracks(previewDirector, copiedTimeline);
|
||||||
MuteCopiedRecorderTracks(copiedTimeline);
|
MuteCopiedRecorderTracks(copiedTimeline);
|
||||||
|
|
||||||
@ -595,6 +596,7 @@ 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}",
|
||||||
@ -1449,6 +1451,8 @@ 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);
|
||||||
@ -1511,6 +1515,11 @@ 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;
|
||||||
@ -1545,6 +1554,8 @@ namespace Streamingle.Editor
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
RestoreAuthoredCinemachineState(
|
||||||
|
destinationCinemachineStateBefore);
|
||||||
RestoreDestinationDirectorAfterFinalizationFailure(
|
RestoreDestinationDirectorAfterFinalizationFailure(
|
||||||
destinationStateBefore,
|
destinationStateBefore,
|
||||||
finalTimeline);
|
finalTimeline);
|
||||||
@ -1943,6 +1954,7 @@ 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(
|
||||||
@ -5575,6 +5587,46 @@ 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,
|
||||||
@ -5645,30 +5697,49 @@ namespace Streamingle.Editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
var scene = director.gameObject.scene;
|
var scene = director.gameObject.scene;
|
||||||
var boundBrains = timeline.GetOutputTracks()
|
var directlyBoundBrains = GetBoundCinemachineBrains(
|
||||||
.OfType<CinemachineTrack>()
|
director,
|
||||||
.Select(track => director.GetGenericBinding(track) as CinemachineBrain)
|
timeline);
|
||||||
.Where(brain => brain != null)
|
if (directlyBoundBrains.Length > 1)
|
||||||
.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 (boundBrains.Length == 1)
|
if (directlyBoundBrains.Length == 1)
|
||||||
{
|
{
|
||||||
var boundBrain = boundBrains[0];
|
return ValidateExplicitCinemachineBrain(
|
||||||
if (!IsUsableCinemachineBrain(boundBrain, scene))
|
directlyBoundBrains[0],
|
||||||
{
|
scene);
|
||||||
throw new InvalidOperationException(
|
|
||||||
"The CinemachineBrain bound to the Timeline must be enabled, " +
|
|
||||||
"attached to an enabled Camera, and belong to the Timeline scene.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return boundBrain;
|
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(
|
||||||
|
"Descendant PlayableDirectors under the selected Timeline object " +
|
||||||
|
"have Cinemachine Tracks bound to multiple CinemachineBrain " +
|
||||||
|
"components. Bind them to one Brain or bind the selected Timeline " +
|
||||||
|
"directly.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (descendantBoundBrains.Length == 1)
|
||||||
|
{
|
||||||
|
return ValidateExplicitCinemachineBrain(
|
||||||
|
descendantBoundBrains[0],
|
||||||
|
scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
var usableBrains = sceneBrains
|
var usableBrains = sceneBrains
|
||||||
@ -5678,7 +5749,7 @@ namespace Streamingle.Editor
|
|||||||
var mainCameraBrains = usableBrains
|
var mainCameraBrains = usableBrains
|
||||||
.Where(brain =>
|
.Where(brain =>
|
||||||
{
|
{
|
||||||
var camera = brain.GetComponent<Camera>();
|
var camera = brain.OutputCamera;
|
||||||
return camera != null && camera.CompareTag("MainCamera");
|
return camera != null && camera.CompareTag("MainCamera");
|
||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
@ -5714,6 +5785,81 @@ 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)
|
||||||
@ -5725,8 +5871,193 @@ namespace Streamingle.Editor
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var camera = brain.GetComponent<Camera>();
|
var camera = brain.OutputCamera;
|
||||||
return camera != null && camera.isActiveAndEnabled;
|
return camera != null &&
|
||||||
|
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(
|
||||||
@ -6944,6 +7275,51 @@ 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(
|
||||||
|
|||||||
@ -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.18`
|
`https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.20`
|
||||||
|
|
||||||
배포 패키지에는 Windows x64용 `CWCameraWorker` 폴더 전체가 포함됩니다.
|
배포 패키지에는 Windows x64용 `CWCameraWorker` 폴더 전체가 포함됩니다.
|
||||||
Python은 따로 설치하지 않아도 되지만, Git 패키지의 대용량 바이너리를 받으려면
|
Python은 따로 설치하지 않아도 되지만, Git 패키지의 대용량 바이너리를 받으려면
|
||||||
|
|||||||
@ -6,13 +6,26 @@ 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]
|
||||||
@ -34,6 +47,10 @@ 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
|
||||||
{
|
{
|
||||||
@ -111,6 +128,843 @@ 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(
|
||||||
@ -778,6 +1632,18 @@ 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(
|
||||||
|
|||||||
BIN
CameraAI~/Tools~/CWCameraWorker/CWCameraWorker.exe
(Stored with Git LFS)
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": "cw-camera-worker-build-identity-v1",
|
"schemaVersion": "cw-camera-worker-build-identity-v1",
|
||||||
"workerVersion": "0.1.8",
|
"workerVersion": "0.1.9",
|
||||||
"createdUtc": "2026-08-15T15:56:01.810396+00:00",
|
"createdUtc": "2026-08-15T16:31:20.228140+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": "5cc252ff45b2a80dffbeea14ad37013ba4a428c4f6fe31a617957eec6c823429",
|
"MachineLearning/CameraDirector/cw_camera_runtime.py": "83968ed0b952a6ecf4673d31070deea677fff712c0dcae21878d527d0e67b7d4",
|
||||||
"MachineLearning/CameraDirector/data_driven_cut_planner.py": "36cf86a80577278d2e966cb0ebf7c3110b5b1dda5abd1d8a6b0a76e31d10e32b",
|
"MachineLearning/CameraDirector/data_driven_cut_planner.py": "36cf86a80577278d2e966cb0ebf7c3110b5b1dda5abd1d8a6b0a76e31d10e32b",
|
||||||
"MachineLearning/CameraDirector/generate_hybrid.py": "bd37744b7b6573bf14be4f0ec5202f1465e11bff71ccabec210533e135b546c3",
|
"MachineLearning/CameraDirector/generate_hybrid.py": "af94ea04f1a05f9248086790293d88e7cb2b9d0123d6e126ab275c87b9022760",
|
||||||
"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": "20cd2ddc1b2349f8c33f6d1460580f37964a521eaa86ceee5b8ae457512ba49d"
|
"MachineLearning/CameraDirector/trajectory_quality.py": "3d2c0a1b6eff8d629dff4211aec38b46c8fd450220f72a31e91f015cd3cc8565"
|
||||||
},
|
},
|
||||||
"preparationLogicIdentifier": "1cb808e207d54854267a1020a658812c79e9bad5a5296f78140228b3a15eeb3b",
|
"preparationLogicIdentifier": "1cb808e207d54854267a1020a658812c79e9bad5a5296f78140228b3a15eeb3b",
|
||||||
"candidateLogicIdentifier": "e768283e8496360ff703b5135f85c51520a888787612b15456def131d5ee1d89",
|
"candidateLogicIdentifier": "b3479b263b24f1419ee1221527878df73b6cf82addb5683753f6e26da632f640",
|
||||||
"generationCodeIdentifier": "80e9eeca5aed9e17a9cfb61b235f954ed4eb24308a1a1ca962e424dbb54bea88",
|
"generationCodeIdentifier": "9d4c7b2d8192f5185ef6ee96378774ffd1365cdc9df0e4b7c7a934b199d4c66b",
|
||||||
"generationCodeFiles": [
|
"generationCodeFiles": [
|
||||||
"adjacent_transition.py",
|
"adjacent_transition.py",
|
||||||
"camera_kinematics.py",
|
"camera_kinematics.py",
|
||||||
|
|||||||
@ -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.8"
|
WORKER_VERSION = "0.1.9"
|
||||||
|
|
||||||
|
|
||||||
def is_frozen_runtime() -> bool:
|
def is_frozen_runtime() -> bool:
|
||||||
|
|||||||
@ -167,7 +167,8 @@ CANDIDATE_CACHE_STAGE_VERSIONS = {
|
|||||||
"adjacent-transition-v3-no-forced-side-alternation"
|
"adjacent-transition-v3-no-forced-side-alternation"
|
||||||
),
|
),
|
||||||
"trajectoryRetargeting": (
|
"trajectoryRetargeting": (
|
||||||
"retarget-v28-speed-adaptive-final-radial-post-kinematic-audit-"
|
"retarget-v29-float32-bound-speed-adaptive-final-radial-post-kinematic-"
|
||||||
|
"audit-"
|
||||||
"residual-dynamics-semantic-exact-tracking-causal-entry-minimum-jerk"
|
"residual-dynamics-semantic-exact-tracking-causal-entry-minimum-jerk"
|
||||||
),
|
),
|
||||||
"compositionSafety": (
|
"compositionSafety": (
|
||||||
@ -181,7 +182,8 @@ CANDIDATE_CACHE_STAGE_VERSIONS = {
|
|||||||
"sequenceContinuation": SEED_VARIATION_SEQUENCE_CONTEXT_POLICY,
|
"sequenceContinuation": SEED_VARIATION_SEQUENCE_CONTEXT_POLICY,
|
||||||
}
|
}
|
||||||
TRAJECTORY_RETARGETING_VERSION = (
|
TRAJECTORY_RETARGETING_VERSION = (
|
||||||
"c2-position-speed-adaptive-final-radial-post-kinematic-audit-v15"
|
"c2-position-float32-bound-speed-adaptive-final-radial-post-kinematic-"
|
||||||
|
"audit-v16"
|
||||||
)
|
)
|
||||||
TEMPLATE_MOTION_SMOOTHING_VERSION = (
|
TEMPLATE_MOTION_SMOOTHING_VERSION = (
|
||||||
"savgol61-nearest-edge-step-guard-v1"
|
"savgol61-nearest-edge-step-guard-v1"
|
||||||
@ -416,6 +418,10 @@ 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
|
||||||
@ -6868,7 +6874,9 @@ 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 RuntimeError("Final camera distance audit violated its bounds.")
|
raise CandidateRetargetSafetyRejection(
|
||||||
|
"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,
|
||||||
@ -8444,7 +8452,8 @@ def evaluate_safe_candidate_pool(
|
|||||||
safety_distance_scale: float = 1.0,
|
safety_distance_scale: float = 1.0,
|
||||||
*,
|
*,
|
||||||
cache_eligible: bool,
|
cache_eligible: bool,
|
||||||
) -> dict:
|
) -> dict | None:
|
||||||
|
try:
|
||||||
candidate_result = retarget_template_to_shot(
|
candidate_result = retarget_template_to_shot(
|
||||||
candidate_template,
|
candidate_template,
|
||||||
previous,
|
previous,
|
||||||
@ -8463,6 +8472,8 @@ 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,
|
||||||
@ -8569,6 +8580,11 @@ 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,
|
||||||
|
|||||||
@ -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-v2"
|
RADIAL_CLAMP_POLICY_VERSION = "quintic-bound-identity-c2-speed-adaptive-v3"
|
||||||
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,6 +698,59 @@ 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,
|
||||||
@ -724,13 +777,17 @@ 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(output_radii)),
|
"minimumOutputDistanceMeters": float(np.min(published_radii)),
|
||||||
"maximumOutputDistanceMeters": float(np.max(output_radii)),
|
"maximumOutputDistanceMeters": float(np.max(published_radii)),
|
||||||
|
"float32BoundaryProjectionFrameCount": int(
|
||||||
|
np.count_nonzero(publication_projection)
|
||||||
|
),
|
||||||
|
"float32BoundaryProjectionMarginMeters": publication_margin,
|
||||||
"maximumRadialAdjustmentMeters": float(
|
"maximumRadialAdjustmentMeters": float(
|
||||||
np.max(np.abs(mapped_radii - radii))
|
np.max(np.abs(published_radii - radii))
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
return result.astype(np.float32), metadata
|
return published_result, metadata
|
||||||
|
|
||||||
|
|
||||||
def _persistent_direction_reversals(
|
def _persistent_direction_reversals(
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
../../Scripts/wheel.exe,sha256=9jTRWXNV9efL3HxLn6B_Q7zW31wwIW3r808tmvMnVhQ,108448
|
../../Scripts/wheel.exe,sha256=33fhzLGTaJQ0McB6oI5dG_iQAgUgrifXlD5WqsCV_T4,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
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": "cw-camera-worker-distribution-build-v1",
|
"schemaVersion": "cw-camera-worker-distribution-build-v1",
|
||||||
"createdUtc": "2026-08-15T15:57:09.6006631Z",
|
"createdUtc": "2026-08-15T16:32:26.2657601Z",
|
||||||
"workerVersion": "0.1.8",
|
"workerVersion": "0.1.9",
|
||||||
"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": 267778040,
|
"bytes": 267781759,
|
||||||
"executableSha256": "559fb67e5dee12cedb0daf1a1f900460fbcf3ff51ece0e826da6ff353506d2ac",
|
"executableSha256": "7cdbaaba556b54215c77f2c64b4005df55e94e8a7feaeff06def5cd6e9449f21",
|
||||||
"buildIdentitySha256": "fe1e6e3fd8b22a4874bcf8dc5a93528f6e6aa359b42a6438bc0aba932f18a3bc",
|
"buildIdentitySha256": "de655829a299d6ec264217ae6c4a2ab2ec89821e4cffed8499c23b79caece1da",
|
||||||
"sanityRange": {
|
"sanityRange": {
|
||||||
"minimumFileCount": 500,
|
"minimumFileCount": 500,
|
||||||
"maximumFileCount": 1000,
|
"maximumFileCount": 1000,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "com.mingle.cw-ai",
|
"name": "com.mingle.cw-ai",
|
||||||
"version": "0.4.12",
|
"version": "0.4.14",
|
||||||
"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",
|
||||||
|
|||||||
8
Editor/Constraints/Icons.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f5f43322334040eda82a02238038c637
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Editor/Constraints/Icons/LerpAimConstraintIcon.png
Normal file
|
After Width: | Height: | Size: 210 KiB |
89
Editor/Constraints/Icons/LerpAimConstraintIcon.png.meta
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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:
|
||||||
BIN
Editor/Constraints/Icons/LerpConstraintIcon.png
Normal file
|
After Width: | Height: | Size: 202 KiB |
89
Editor/Constraints/Icons/LerpConstraintIcon.png.meta
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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:
|
||||||
BIN
Editor/Constraints/Icons/LerpLookAtConstraintIcon.png
Normal file
|
After Width: | Height: | Size: 317 KiB |
89
Editor/Constraints/Icons/LerpLookAtConstraintIcon.png.meta
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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:
|
||||||
BIN
Editor/Constraints/Icons/LerpParentConstraintIcon.png
Normal file
|
After Width: | Height: | Size: 209 KiB |
89
Editor/Constraints/Icons/LerpParentConstraintIcon.png.meta
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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:
|
||||||
BIN
Editor/Constraints/Icons/LerpPositionConstraintIcon.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
89
Editor/Constraints/Icons/LerpPositionConstraintIcon.png.meta
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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:
|
||||||
BIN
Editor/Constraints/Icons/LerpRotationConstraintIcon.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
89
Editor/Constraints/Icons/LerpRotationConstraintIcon.png.meta
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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:
|
||||||
BIN
Editor/Constraints/Icons/LerpScaleConstraintIcon.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
89
Editor/Constraints/Icons/LerpScaleConstraintIcon.png.meta
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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:
|
||||||
@ -1,13 +1,13 @@
|
|||||||
using Streamingle.Utils.RoughConstraints;
|
using Streamingle.Utils.LerpConstraints;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditorInternal;
|
using UnityEditorInternal;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Streamingle.Utils.RoughConstraintsEditor
|
namespace Streamingle.Utils.LerpConstraintsEditor
|
||||||
{
|
{
|
||||||
[CustomEditor(typeof(RoughConstraintBase), true)]
|
[CustomEditor(typeof(LerpConstraintBase), true)]
|
||||||
[CanEditMultipleObjects]
|
[CanEditMultipleObjects]
|
||||||
public sealed class RoughConstraintEditor : UnityEditor.Editor
|
public sealed class LerpConstraintEditor : UnityEditor.Editor
|
||||||
{
|
{
|
||||||
private SerializedProperty constraintActive;
|
private SerializedProperty constraintActive;
|
||||||
private SerializedProperty locked;
|
private SerializedProperty locked;
|
||||||
@ -18,7 +18,7 @@ namespace Streamingle.Utils.RoughConstraintsEditor
|
|||||||
private SerializedProperty useUnscaledTime;
|
private SerializedProperty useUnscaledTime;
|
||||||
private SerializedProperty snapOnEnable;
|
private SerializedProperty snapOnEnable;
|
||||||
private ReorderableList sourceList;
|
private ReorderableList sourceList;
|
||||||
private bool roughFollowFoldout = true;
|
private bool lerpFollowFoldout = true;
|
||||||
private bool settingsFoldout = true;
|
private bool settingsFoldout = true;
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
@ -62,7 +62,7 @@ namespace Streamingle.Utils.RoughConstraintsEditor
|
|||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
if (GUILayout.Button("Activate", EditorStyles.miniButton, GUILayout.Width(72f)))
|
if (GUILayout.Button("Activate", EditorStyles.miniButton, GUILayout.Width(72f)))
|
||||||
{
|
{
|
||||||
ApplyAction("Activate Rough Constraint", ActivateConstraint);
|
ApplyAction("Activate Lerp 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.RoughConstraintsEditor
|
|||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
foreach (var selectedTarget in targets)
|
foreach (var selectedTarget in targets)
|
||||||
{
|
{
|
||||||
var constraint = (RoughConstraintBase)selectedTarget;
|
var constraint = (LerpConstraintBase)selectedTarget;
|
||||||
Undo.RecordObject(constraint, "Change Rough Constraint Lock");
|
Undo.RecordObject(constraint, "Change Lerp Constraint Lock");
|
||||||
constraint.SetLocked(newValue);
|
constraint.SetLocked(newValue);
|
||||||
EditorUtility.SetDirty(constraint);
|
EditorUtility.SetDirty(constraint);
|
||||||
}
|
}
|
||||||
@ -102,8 +102,8 @@ namespace Streamingle.Utils.RoughConstraintsEditor
|
|||||||
|
|
||||||
private void DrawTypeSettings()
|
private void DrawTypeSettings()
|
||||||
{
|
{
|
||||||
roughFollowFoldout = EditorGUILayout.Foldout(roughFollowFoldout, "Rough Follow", true);
|
lerpFollowFoldout = EditorGUILayout.Foldout(lerpFollowFoldout, "Lerp Follow", true);
|
||||||
if (roughFollowFoldout)
|
if (lerpFollowFoldout)
|
||||||
{
|
{
|
||||||
using (new EditorGUI.IndentLevelScope())
|
using (new EditorGUI.IndentLevelScope())
|
||||||
{
|
{
|
||||||
@ -128,27 +128,27 @@ namespace Streamingle.Utils.RoughConstraintsEditor
|
|||||||
|
|
||||||
private void DrawTypeProperties()
|
private void DrawTypeProperties()
|
||||||
{
|
{
|
||||||
if (target is RoughPositionConstraint)
|
if (target is LerpPositionConstraint)
|
||||||
{
|
{
|
||||||
DrawProperties("constrainedAxis", "positionOffset", "offsetInSourceSpace");
|
DrawProperties("constrainedAxis", "positionOffset", "offsetInSourceSpace");
|
||||||
}
|
}
|
||||||
else if (target is RoughRotationConstraint)
|
else if (target is LerpRotationConstraint)
|
||||||
{
|
{
|
||||||
DrawProperties("constrainedAxis", "rotationOffset");
|
DrawProperties("constrainedAxis", "rotationOffset");
|
||||||
}
|
}
|
||||||
else if (target is RoughScaleConstraint)
|
else if (target is LerpScaleConstraint)
|
||||||
{
|
{
|
||||||
DrawProperties("constrainedAxis", "scaleOffset");
|
DrawProperties("constrainedAxis", "scaleOffset");
|
||||||
}
|
}
|
||||||
else if (target is RoughParentConstraint)
|
else if (target is LerpParentConstraint)
|
||||||
{
|
{
|
||||||
DrawProperties("constrainedPositionAxis", "positionOffset", "positionOffsetInSourceSpace", "constrainedRotationAxis", "rotationOffset");
|
DrawProperties("constrainedPositionAxis", "positionOffset", "positionOffsetInSourceSpace", "constrainedRotationAxis", "rotationOffset");
|
||||||
}
|
}
|
||||||
else if (target is RoughAimConstraint)
|
else if (target is LerpAimConstraint)
|
||||||
{
|
{
|
||||||
DrawProperties("aimAxis", "upAxis", "worldUpVector", "rotationOffset");
|
DrawProperties("aimAxis", "upAxis", "worldUpVector", "rotationOffset");
|
||||||
}
|
}
|
||||||
else if (target is RoughLookAtConstraint)
|
else if (target is LerpLookAtConstraint)
|
||||||
{
|
{
|
||||||
DrawProperties("forwardAxis", "worldUpVector", "targetPositionOffset", "rotationOffset");
|
DrawProperties("forwardAxis", "worldUpVector", "targetPositionOffset", "rotationOffset");
|
||||||
}
|
}
|
||||||
@ -166,22 +166,22 @@ namespace Streamingle.Utils.RoughConstraintsEditor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyAction(string undoName, System.Action<RoughConstraintBase> action)
|
private void ApplyAction(string undoName, System.Action<LerpConstraintBase> action)
|
||||||
{
|
{
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
foreach (var selectedTarget in targets)
|
foreach (var selectedTarget in targets)
|
||||||
{
|
{
|
||||||
var constraint = (RoughConstraintBase)selectedTarget;
|
var constraint = (LerpConstraintBase)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(RoughConstraintBase constraint)
|
private void ActivateConstraint(LerpConstraintBase constraint)
|
||||||
{
|
{
|
||||||
Undo.RecordObject(constraint, "Activate Rough Constraint");
|
Undo.RecordObject(constraint, "Activate Lerp Constraint");
|
||||||
constraint.ActivateConstraint();
|
constraint.ActivateConstraint();
|
||||||
EditorUtility.SetDirty(constraint);
|
EditorUtility.SetDirty(constraint);
|
||||||
}
|
}
|
||||||
@ -189,7 +189,7 @@ namespace Streamingle.Utils.RoughConstraintsEditor
|
|||||||
private void ZeroOffsets()
|
private void ZeroOffsets()
|
||||||
{
|
{
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
Undo.RecordObjects(targets, "Zero Rough Constraint Offsets");
|
Undo.RecordObjects(targets, "Zero Lerp 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.RoughConstraintsEditor
|
|||||||
|
|
||||||
private string GetSettingsTitle()
|
private string GetSettingsTitle()
|
||||||
{
|
{
|
||||||
if (target is RoughLookAtConstraint)
|
if (target is LerpLookAtConstraint)
|
||||||
{
|
{
|
||||||
return "Look At";
|
return "Look At";
|
||||||
}
|
}
|
||||||
|
|
||||||
var name = target.GetType().Name;
|
var name = target.GetType().Name;
|
||||||
name = name.Replace("Rough", string.Empty).Replace("Constraint", string.Empty);
|
name = name.Replace("Lerp", string.Empty).Replace("Constraint", string.Empty);
|
||||||
return ObjectNames.NicifyVariableName(name);
|
return ObjectNames.NicifyVariableName(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
69
Editor/Constraints/LerpConstraintIconInstaller.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Editor/Constraints/LerpConstraintIconInstaller.cs.meta
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 97633b0c6f0f4ca0a862c50e6de6f70b
|
||||||
@ -1,8 +1,8 @@
|
|||||||
using Streamingle.Utils.RoughConstraints;
|
using Streamingle.Utils.LerpConstraints;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Streamingle.Utils.RoughConstraintsEditor
|
namespace Streamingle.Utils.LerpConstraintsEditor
|
||||||
{
|
{
|
||||||
[CustomPropertyDrawer(typeof(Vector3Bool))]
|
[CustomPropertyDrawer(typeof(Vector3Bool))]
|
||||||
public sealed class Vector3BoolDrawer : UnityEditor.PropertyDrawer
|
public sealed class Vector3BoolDrawer : UnityEditor.PropertyDrawer
|
||||||
|
|||||||
@ -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.18
|
https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.20
|
||||||
```
|
```
|
||||||
|
|
||||||
The Camera AI package includes the complete Windows x64
|
The Camera AI package includes the complete Windows x64
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Scripting.APIUpdating;
|
||||||
|
|
||||||
namespace Streamingle.Utils.RoughConstraints
|
namespace Streamingle.Utils.LerpConstraints
|
||||||
{
|
{
|
||||||
|
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughAimConstraint")]
|
||||||
[ExecuteAlways]
|
[ExecuteAlways]
|
||||||
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Aim Constraint")]
|
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Aim Constraint")]
|
||||||
public class RoughAimConstraint : RoughConstraintBase
|
public class LerpAimConstraint : LerpConstraintBase
|
||||||
{
|
{
|
||||||
[Header("Aim")]
|
[Header("Aim")]
|
||||||
public Vector3 aimAxis = Vector3.forward;
|
public Vector3 aimAxis = Vector3.forward;
|
||||||
@ -1,12 +1,13 @@
|
|||||||
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.RoughConstraints
|
namespace Streamingle.Utils.LerpConstraints
|
||||||
{
|
{
|
||||||
public enum RoughConstraintUpdateMode
|
public enum LerpConstraintUpdateMode
|
||||||
{
|
{
|
||||||
Update,
|
Update,
|
||||||
LateUpdate,
|
LateUpdate,
|
||||||
@ -14,8 +15,9 @@ namespace Streamingle.Utils.RoughConstraints
|
|||||||
Manual
|
Manual
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughConstraintBase")]
|
||||||
[ExecuteAlways]
|
[ExecuteAlways]
|
||||||
public abstract class RoughConstraintBase : MonoBehaviour
|
public abstract class LerpConstraintBase : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Header("Constraint")]
|
[Header("Constraint")]
|
||||||
public bool constraintActive = true;
|
public bool constraintActive = true;
|
||||||
@ -24,13 +26,13 @@ namespace Streamingle.Utils.RoughConstraints
|
|||||||
[Range(0f, 1f)]
|
[Range(0f, 1f)]
|
||||||
public float weight = 1f;
|
public float weight = 1f;
|
||||||
|
|
||||||
public List<RoughConstraintSource> sources = new List<RoughConstraintSource>();
|
public List<LerpConstraintSource> sources = new List<LerpConstraintSource>();
|
||||||
|
|
||||||
[Header("Rough Follow")]
|
[Header("Lerp Follow")]
|
||||||
[Min(0f)]
|
[Min(0f)]
|
||||||
public float damping = 12f;
|
public float damping = 12f;
|
||||||
|
|
||||||
public RoughConstraintUpdateMode updateMode = RoughConstraintUpdateMode.LateUpdate;
|
public LerpConstraintUpdateMode updateMode = LerpConstraintUpdateMode.LateUpdate;
|
||||||
public bool useUnscaledTime;
|
public bool useUnscaledTime;
|
||||||
public bool snapOnEnable = true;
|
public bool snapOnEnable = true;
|
||||||
|
|
||||||
@ -47,7 +49,7 @@ namespace Streamingle.Utils.RoughConstraints
|
|||||||
return 1f / 60f;
|
return 1f / 60f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateMode == RoughConstraintUpdateMode.FixedUpdate)
|
if (updateMode == LerpConstraintUpdateMode.FixedUpdate)
|
||||||
{
|
{
|
||||||
return useUnscaledTime ? Time.fixedUnscaledDeltaTime : Time.fixedDeltaTime;
|
return useUnscaledTime ? Time.fixedUnscaledDeltaTime : Time.fixedDeltaTime;
|
||||||
}
|
}
|
||||||
@ -94,7 +96,7 @@ namespace Streamingle.Utils.RoughConstraints
|
|||||||
{
|
{
|
||||||
if (!Application.isPlaying)
|
if (!Application.isPlaying)
|
||||||
{
|
{
|
||||||
if (updateMode != RoughConstraintUpdateMode.Manual)
|
if (updateMode != LerpConstraintUpdateMode.Manual)
|
||||||
{
|
{
|
||||||
Evaluate();
|
Evaluate();
|
||||||
}
|
}
|
||||||
@ -102,7 +104,7 @@ namespace Streamingle.Utils.RoughConstraints
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateMode == RoughConstraintUpdateMode.Update)
|
if (updateMode == LerpConstraintUpdateMode.Update)
|
||||||
{
|
{
|
||||||
Evaluate();
|
Evaluate();
|
||||||
}
|
}
|
||||||
@ -115,7 +117,7 @@ namespace Streamingle.Utils.RoughConstraints
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateMode == RoughConstraintUpdateMode.LateUpdate)
|
if (updateMode == LerpConstraintUpdateMode.LateUpdate)
|
||||||
{
|
{
|
||||||
Evaluate();
|
Evaluate();
|
||||||
}
|
}
|
||||||
@ -128,7 +130,7 @@ namespace Streamingle.Utils.RoughConstraints
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateMode == RoughConstraintUpdateMode.FixedUpdate)
|
if (updateMode == LerpConstraintUpdateMode.FixedUpdate)
|
||||||
{
|
{
|
||||||
Evaluate();
|
Evaluate();
|
||||||
}
|
}
|
||||||
@ -165,7 +167,7 @@ namespace Streamingle.Utils.RoughConstraints
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Application.isPlaying || updateMode == RoughConstraintUpdateMode.Manual)
|
if (Application.isPlaying || updateMode == LerpConstraintUpdateMode.Manual)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Streamingle.Utils.RoughConstraints
|
namespace Streamingle.Utils.LerpConstraints
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public struct RoughConstraintSource
|
public struct LerpConstraintSource
|
||||||
{
|
{
|
||||||
public Transform sourceTransform;
|
public Transform sourceTransform;
|
||||||
|
|
||||||
@ -1,10 +1,12 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Scripting.APIUpdating;
|
||||||
|
|
||||||
namespace Streamingle.Utils.RoughConstraints
|
namespace Streamingle.Utils.LerpConstraints
|
||||||
{
|
{
|
||||||
|
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughLookAtConstraint")]
|
||||||
[ExecuteAlways]
|
[ExecuteAlways]
|
||||||
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Look At Constraint")]
|
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Look At Constraint")]
|
||||||
public class RoughLookAtConstraint : RoughConstraintBase
|
public class LerpLookAtConstraint : LerpConstraintBase
|
||||||
{
|
{
|
||||||
[Header("Look At")]
|
[Header("Look At")]
|
||||||
public Vector3 forwardAxis = Vector3.forward;
|
public Vector3 forwardAxis = Vector3.forward;
|
||||||
@ -1,10 +1,12 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Scripting.APIUpdating;
|
||||||
|
|
||||||
namespace Streamingle.Utils.RoughConstraints
|
namespace Streamingle.Utils.LerpConstraints
|
||||||
{
|
{
|
||||||
|
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughParentConstraint")]
|
||||||
[ExecuteAlways]
|
[ExecuteAlways]
|
||||||
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Parent Constraint")]
|
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Parent Constraint")]
|
||||||
public class RoughParentConstraint : RoughConstraintBase
|
public class LerpParentConstraint : LerpConstraintBase
|
||||||
{
|
{
|
||||||
[Header("Position")]
|
[Header("Position")]
|
||||||
public Vector3Bool constrainedPositionAxis = Vector3Bool.All;
|
public Vector3Bool constrainedPositionAxis = Vector3Bool.All;
|
||||||
@ -1,10 +1,12 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Scripting.APIUpdating;
|
||||||
|
|
||||||
namespace Streamingle.Utils.RoughConstraints
|
namespace Streamingle.Utils.LerpConstraints
|
||||||
{
|
{
|
||||||
|
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughPositionConstraint")]
|
||||||
[ExecuteAlways]
|
[ExecuteAlways]
|
||||||
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Position Constraint")]
|
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Position Constraint")]
|
||||||
public class RoughPositionConstraint : RoughConstraintBase
|
public class LerpPositionConstraint : LerpConstraintBase
|
||||||
{
|
{
|
||||||
[Header("Position")]
|
[Header("Position")]
|
||||||
public Vector3Bool constrainedAxis = Vector3Bool.All;
|
public Vector3Bool constrainedAxis = Vector3Bool.All;
|
||||||
@ -1,10 +1,12 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Scripting.APIUpdating;
|
||||||
|
|
||||||
namespace Streamingle.Utils.RoughConstraints
|
namespace Streamingle.Utils.LerpConstraints
|
||||||
{
|
{
|
||||||
|
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughRotationConstraint")]
|
||||||
[ExecuteAlways]
|
[ExecuteAlways]
|
||||||
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Rotation Constraint")]
|
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Rotation Constraint")]
|
||||||
public class RoughRotationConstraint : RoughConstraintBase
|
public class LerpRotationConstraint : LerpConstraintBase
|
||||||
{
|
{
|
||||||
[Header("Rotation")]
|
[Header("Rotation")]
|
||||||
public Vector3Bool constrainedAxis = Vector3Bool.All;
|
public Vector3Bool constrainedAxis = Vector3Bool.All;
|
||||||
@ -1,10 +1,12 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Scripting.APIUpdating;
|
||||||
|
|
||||||
namespace Streamingle.Utils.RoughConstraints
|
namespace Streamingle.Utils.LerpConstraints
|
||||||
{
|
{
|
||||||
|
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughScaleConstraint")]
|
||||||
[ExecuteAlways]
|
[ExecuteAlways]
|
||||||
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Scale Constraint")]
|
[AddComponentMenu("Streamingle Utilities/Constraints/Lerp Scale Constraint")]
|
||||||
public class RoughScaleConstraint : RoughConstraintBase
|
public class LerpScaleConstraint : LerpConstraintBase
|
||||||
{
|
{
|
||||||
[Header("Scale")]
|
[Header("Scale")]
|
||||||
public Vector3Bool constrainedAxis = Vector3Bool.All;
|
public Vector3Bool constrainedAxis = Vector3Bool.All;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Streamingle.Utils.RoughConstraints
|
namespace Streamingle.Utils.LerpConstraints
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public struct Vector3Bool
|
public struct Vector3Bool
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "com.streamingle.utilities",
|
"name": "com.streamingle.utilities",
|
||||||
"displayName": "Streamingle Utilities",
|
"displayName": "Streamingle Utilities",
|
||||||
"version": "0.1.7",
|
"version": "0.1.22",
|
||||||
"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": [
|
||||||
|
|||||||