fix(camera-ai): eliminate generated camera trajectory jolts
This commit is contained in:
parent
1fd93454eb
commit
9a7e0a3fd0
@ -1,5 +1,37 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.4.8 - 2026-08-09
|
||||||
|
|
||||||
|
- Replaced the unstable frame-local aim controller with a causal, feed-forward
|
||||||
|
angular-velocity path. Static targets now settle without a left/right limit
|
||||||
|
cycle, legal 45/80-degree-per-second pans keep their entry velocity, and
|
||||||
|
abrupt changes are bounded to 90 deg/s, 360 deg/s^2, and 7,200 deg/s^3.
|
||||||
|
- Added C2 position resampling, speed-adaptive C2 distance transitions, and a
|
||||||
|
final distance/step audit after every trajectory transform. The final
|
||||||
|
candidate gate now fails closed on missing data and checks translation jerk,
|
||||||
|
screen-space p99 and one-frame impulses, and the actual post-clamp step.
|
||||||
|
- Calibrated the new dynamics policy against 1,381 runtime-eligible authored
|
||||||
|
shots. The 600 m/s^3 translation maximum applies only to final generated
|
||||||
|
candidates; expressive authored wide/dolly templates remain available for
|
||||||
|
retargeting and soft ranking.
|
||||||
|
- Replaced Unity's piecewise-linear reduced position curves with velocity-aware
|
||||||
|
C1 Hermite curves. Every reduced shot is audited at source times and
|
||||||
|
midpoints for position error, local speed/acceleration/jerk regressions, and
|
||||||
|
meaningful-motion retention; unsafe reductions automatically fall back to
|
||||||
|
exact position keys for that shot only.
|
||||||
|
- Rebuilt the self-contained Windows worker as 0.1.4 with the trajectory module
|
||||||
|
and build-identity snapshot included. The frozen worker doctor and 196-test
|
||||||
|
build suite pass without requiring Python on the artist machine.
|
||||||
|
- On the 12,269-frame Arisa regression input, all 56 shots passed visibility,
|
||||||
|
facing, aim, distance, step, screen-dynamics, and cut-transition checks.
|
||||||
|
Worst translation jerk improved from 641.61 to 247.81 m/s^3 and worst
|
||||||
|
angular jerk from 30,294.84 to 7,146.35 deg/s^3; uncached generation improved
|
||||||
|
from about 124 seconds to 107 seconds.
|
||||||
|
- Unity compiled with zero Camera AI errors and the package EditMode suite
|
||||||
|
passed 121/121. The Balanced importer retained simplified C1 curves for 41
|
||||||
|
shots and safely selected exact position keys for the 15 shots whose reduced
|
||||||
|
curves exceeded the local motion or 5 mm error contract.
|
||||||
|
|
||||||
## 0.4.7 - 2026-08-09
|
## 0.4.7 - 2026-08-09
|
||||||
|
|
||||||
- Fixed the incomplete 6D-to-quaternion conversion that replaced every
|
- Fixed the incomplete 6D-to-quaternion conversion that replaced every
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -155,6 +155,293 @@ namespace Streamingle.Editor
|
|||||||
Is.LessThan(exact.DutchIndices.Length));
|
Is.LessThan(exact.DutchIndices.Length));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(AICameraTimelinePreviewImporter.CurveSimplificationPreset.Balanced)]
|
||||||
|
[TestCase(AICameraTimelinePreviewImporter.CurveSimplificationPreset.Editable)]
|
||||||
|
public void ReducedPositionCurvesUseAuditedC1HermiteWithoutOvershoot(
|
||||||
|
AICameraTimelinePreviewImporter.CurveSimplificationPreset preset)
|
||||||
|
{
|
||||||
|
var samples = CreateQuadraticMotionSamples(241);
|
||||||
|
var simplified = Simplify(samples, preset);
|
||||||
|
|
||||||
|
var curves = AICameraTimelinePreviewImporter.BuildPositionCurves(
|
||||||
|
simplified,
|
||||||
|
preset);
|
||||||
|
|
||||||
|
Assert.That(curves.UsedExactFallback, Is.False, curves.FallbackReason);
|
||||||
|
Assert.That(simplified.PositionUsedExactFallback, Is.False);
|
||||||
|
Assert.That(
|
||||||
|
curves.XCurve.length,
|
||||||
|
Is.LessThan(samples.Times.Length));
|
||||||
|
AssertCurveIsC1(curves.XCurve);
|
||||||
|
AssertCurveIsC1(curves.YCurve);
|
||||||
|
AssertCurveIsC1(curves.ZCurve);
|
||||||
|
AssertCurveDoesNotOvershootSelectedSegments(
|
||||||
|
curves.XCurve,
|
||||||
|
simplified.Times,
|
||||||
|
simplified.PositionIndices);
|
||||||
|
AssertCurveDoesNotOvershootSelectedSegments(
|
||||||
|
curves.YCurve,
|
||||||
|
simplified.Times,
|
||||||
|
simplified.PositionIndices);
|
||||||
|
AssertCurveDoesNotOvershootSelectedSegments(
|
||||||
|
curves.ZCurve,
|
||||||
|
simplified.Times,
|
||||||
|
simplified.PositionIndices);
|
||||||
|
|
||||||
|
var audit = AICameraTimelinePreviewImporter.AuditPositionCurves(
|
||||||
|
simplified.Times,
|
||||||
|
simplified.Positions,
|
||||||
|
curves.XCurve,
|
||||||
|
curves.YCurve,
|
||||||
|
curves.ZCurve,
|
||||||
|
AICameraTimelinePreviewImporter
|
||||||
|
.GetCurveSimplificationSettings(preset)
|
||||||
|
.PositionErrorMeters);
|
||||||
|
Assert.That(audit.Passed, Is.True, audit.FailureReason);
|
||||||
|
|
||||||
|
var rawMaximumJerk = MeasureMaximumJerk(
|
||||||
|
simplified.Times,
|
||||||
|
simplified.Positions);
|
||||||
|
var evaluatedMaximumJerk = MeasureMaximumJerk(
|
||||||
|
simplified.Times,
|
||||||
|
EvaluatePositionCurves(simplified.Times, curves));
|
||||||
|
var allowedMaximumJerk = Mathf.Max(
|
||||||
|
250f,
|
||||||
|
Mathf.Max(rawMaximumJerk * 3f, rawMaximumJerk + 150f));
|
||||||
|
Assert.That(
|
||||||
|
evaluatedMaximumJerk,
|
||||||
|
Is.LessThanOrEqualTo(allowedMaximumJerk));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ReducedPositionCurveAuditFallsBackToExactPositionOnly()
|
||||||
|
{
|
||||||
|
const int count = 61;
|
||||||
|
var times = new double[count];
|
||||||
|
var positions = new Vector3[count];
|
||||||
|
for (var index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
var time = index / 60.0;
|
||||||
|
times[index] = time;
|
||||||
|
positions[index] = new Vector3(
|
||||||
|
Mathf.Sin((float)time * Mathf.PI * 2f),
|
||||||
|
1.6f,
|
||||||
|
-4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
var simplified = CreatePositionOnlySimplifiedCurves(
|
||||||
|
times,
|
||||||
|
positions,
|
||||||
|
new[] { 0, count - 1 });
|
||||||
|
var curves = AICameraTimelinePreviewImporter.BuildPositionCurves(
|
||||||
|
simplified,
|
||||||
|
AICameraTimelinePreviewImporter.CurveSimplificationPreset.Editable);
|
||||||
|
|
||||||
|
Assert.That(curves.UsedExactFallback, Is.True);
|
||||||
|
Assert.That(curves.FallbackReason, Does.Contain("position error"));
|
||||||
|
Assert.That(simplified.PositionUsedExactFallback, Is.True);
|
||||||
|
Assert.That(
|
||||||
|
simplified.PositionFallbackReason,
|
||||||
|
Is.EqualTo(curves.FallbackReason));
|
||||||
|
Assert.That(curves.XCurve.length, Is.EqualTo(count));
|
||||||
|
Assert.That(curves.YCurve.length, Is.EqualTo(count));
|
||||||
|
Assert.That(curves.ZCurve.length, Is.EqualTo(count));
|
||||||
|
for (var index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
Assert.That(
|
||||||
|
curves.XCurve.Evaluate((float)times[index]),
|
||||||
|
Is.EqualTo(positions[index].x).Within(0.000001f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MotionAuditRejectsPathologicalMidpointVelocityRegression()
|
||||||
|
{
|
||||||
|
const int count = 61;
|
||||||
|
var times = new double[count];
|
||||||
|
var positions = new Vector3[count];
|
||||||
|
for (var index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
var time = index / 60.0;
|
||||||
|
times[index] = time;
|
||||||
|
positions[index] = new Vector3((float)time, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathologicalX = new AnimationCurve(
|
||||||
|
new Keyframe(0f, 0f, 1f, 80f),
|
||||||
|
new Keyframe(1f, 1f, -80f, 1f));
|
||||||
|
var constant = AnimationCurve.Linear(0f, 0f, 1f, 0f);
|
||||||
|
|
||||||
|
var audit = AICameraTimelinePreviewImporter.AuditPositionCurves(
|
||||||
|
times,
|
||||||
|
positions,
|
||||||
|
pathologicalX,
|
||||||
|
constant,
|
||||||
|
constant,
|
||||||
|
100f);
|
||||||
|
|
||||||
|
Assert.That(audit.Passed, Is.False);
|
||||||
|
Assert.That(audit.FailureReason, Does.Contain("regressed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void AuditGridUsesExactSourceTimesAndDropsFloatingTailDuplicates()
|
||||||
|
{
|
||||||
|
var frame = 1.0 / 60.0;
|
||||||
|
var times = new[]
|
||||||
|
{
|
||||||
|
0.0,
|
||||||
|
frame,
|
||||||
|
frame * 2.0,
|
||||||
|
frame * 3.0,
|
||||||
|
frame * 3.0 + 0.00000000005
|
||||||
|
};
|
||||||
|
|
||||||
|
var auditTimes = AICameraTimelinePreviewImporter
|
||||||
|
.BuildPositionCurveAuditTimes(times);
|
||||||
|
|
||||||
|
Assert.That(auditTimes.Count, Is.EqualTo(7));
|
||||||
|
Assert.That(auditTimes[0], Is.EqualTo(times[0]));
|
||||||
|
Assert.That(auditTimes[2], Is.EqualTo(times[1]));
|
||||||
|
Assert.That(auditTimes[4], Is.EqualTo(times[2]));
|
||||||
|
Assert.That(auditTimes[6], Is.EqualTo(times[3]));
|
||||||
|
for (var index = 1; index < auditTimes.Count; index++)
|
||||||
|
{
|
||||||
|
Assert.That(
|
||||||
|
auditTimes[index] - auditTimes[index - 1],
|
||||||
|
Is.GreaterThan(frame * 0.49),
|
||||||
|
$"Audit time {index} introduced a floating-point tail.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void AlignedMotionAuditFindsLocalJerkHiddenByEarlierGlobalMaximum()
|
||||||
|
{
|
||||||
|
const int count = 40;
|
||||||
|
var times = new double[count];
|
||||||
|
var reference = new Vector3[count];
|
||||||
|
var candidate = new Vector3[count];
|
||||||
|
for (var index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
times[index] = index / 60.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
reference[3] = new Vector3(0.1f, 0f, 0f);
|
||||||
|
Array.Copy(reference, candidate, count);
|
||||||
|
candidate[30] = new Vector3(0.001f, 0f, 0f);
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
MeasureMaximumJerk(times, candidate),
|
||||||
|
Is.EqualTo(MeasureMaximumJerk(times, reference))
|
||||||
|
.Within(0.01f),
|
||||||
|
"The earlier source jolt must mask the new jolt in a global-max audit.");
|
||||||
|
Assert.That(
|
||||||
|
AICameraTimelinePreviewImporter.TryDescribeAlignedMotionRegression(
|
||||||
|
times,
|
||||||
|
reference,
|
||||||
|
candidate,
|
||||||
|
out var failureReason),
|
||||||
|
Is.True);
|
||||||
|
Assert.That(failureReason, Does.Contain("windowed jerk"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MotionRetentionAuditRejectsErasedSubtleLowFrequencyMove()
|
||||||
|
{
|
||||||
|
const int count = 241;
|
||||||
|
var times = new double[count];
|
||||||
|
var positions = new Vector3[count];
|
||||||
|
for (var index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
var time = index / 60.0;
|
||||||
|
times[index] = time;
|
||||||
|
positions[index] = new Vector3(
|
||||||
|
0.006f * Mathf.Sin((float)(time * Math.PI * 0.5)),
|
||||||
|
0f,
|
||||||
|
0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
var erased = AnimationCurve.Linear(
|
||||||
|
0f,
|
||||||
|
0f,
|
||||||
|
(float)times[count - 1],
|
||||||
|
0f);
|
||||||
|
var audit = AICameraTimelinePreviewImporter.AuditPositionCurves(
|
||||||
|
times,
|
||||||
|
positions,
|
||||||
|
erased,
|
||||||
|
erased,
|
||||||
|
erased,
|
||||||
|
0.02f);
|
||||||
|
|
||||||
|
Assert.That(audit.Passed, Is.False);
|
||||||
|
Assert.That(
|
||||||
|
audit.FailureReason,
|
||||||
|
Does.Contain("meaningful motion retention"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MotionRetentionAuditIgnoresSubDeadbandPositionNoise()
|
||||||
|
{
|
||||||
|
const int count = 121;
|
||||||
|
var times = new double[count];
|
||||||
|
var positions = new Vector3[count];
|
||||||
|
for (var index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
times[index] = index / 60.0;
|
||||||
|
positions[index] = new Vector3(
|
||||||
|
index % 2 == 0 ? 0.0001f : -0.0001f,
|
||||||
|
0f,
|
||||||
|
0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
var denoised = AnimationCurve.Linear(
|
||||||
|
0f,
|
||||||
|
0f,
|
||||||
|
(float)times[count - 1],
|
||||||
|
0f);
|
||||||
|
var audit = AICameraTimelinePreviewImporter.AuditPositionCurves(
|
||||||
|
times,
|
||||||
|
positions,
|
||||||
|
denoised,
|
||||||
|
denoised,
|
||||||
|
denoised,
|
||||||
|
0.02f);
|
||||||
|
|
||||||
|
Assert.That(audit.Passed, Is.True, audit.FailureReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ExactPositionCurvesRetainEveryLinearSampleWithoutFallback()
|
||||||
|
{
|
||||||
|
var samples = CreateSamples(91);
|
||||||
|
var simplified = Simplify(
|
||||||
|
samples,
|
||||||
|
AICameraTimelinePreviewImporter.CurveSimplificationPreset.Exact);
|
||||||
|
|
||||||
|
var curves = AICameraTimelinePreviewImporter.BuildPositionCurves(
|
||||||
|
simplified,
|
||||||
|
AICameraTimelinePreviewImporter.CurveSimplificationPreset.Exact);
|
||||||
|
|
||||||
|
Assert.That(curves.UsedExactFallback, Is.False);
|
||||||
|
Assert.That(curves.XCurve.length, Is.EqualTo(samples.Times.Length));
|
||||||
|
Assert.That(curves.YCurve.length, Is.EqualTo(samples.Times.Length));
|
||||||
|
Assert.That(curves.ZCurve.length, Is.EqualTo(samples.Times.Length));
|
||||||
|
for (var index = 0; index < samples.Times.Length; index++)
|
||||||
|
{
|
||||||
|
var time = (float)samples.Times[index];
|
||||||
|
Assert.That(
|
||||||
|
curves.XCurve.Evaluate(time),
|
||||||
|
Is.EqualTo(samples.Positions[index].x).Within(0.000001f));
|
||||||
|
Assert.That(
|
||||||
|
curves.YCurve.Evaluate(time),
|
||||||
|
Is.EqualTo(samples.Positions[index].y).Within(0.000001f));
|
||||||
|
Assert.That(
|
||||||
|
curves.ZCurve.Evaluate(time),
|
||||||
|
Is.EqualTo(samples.Positions[index].z).Within(0.000001f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void EditablePresetPreservesStopThroughDirectionChangeKeys()
|
public void EditablePresetPreservesStopThroughDirectionChangeKeys()
|
||||||
{
|
{
|
||||||
@ -448,6 +735,160 @@ namespace Streamingle.Editor
|
|||||||
preset);
|
preset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CameraSamples CreateQuadraticMotionSamples(int count)
|
||||||
|
{
|
||||||
|
var times = new double[count];
|
||||||
|
var positions = new Vector3[count];
|
||||||
|
var rotations = new Quaternion[count];
|
||||||
|
var fieldOfView = new float[count];
|
||||||
|
var dutch = new float[count];
|
||||||
|
for (var index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
var time = index / 60.0;
|
||||||
|
var timeFloat = (float)time;
|
||||||
|
times[index] = time;
|
||||||
|
positions[index] = new Vector3(
|
||||||
|
0.4f * timeFloat + 0.15f * timeFloat * timeFloat,
|
||||||
|
1.6f + 0.02f * timeFloat * timeFloat,
|
||||||
|
-4f + 0.1f * timeFloat);
|
||||||
|
rotations[index] = Quaternion.identity;
|
||||||
|
fieldOfView[index] = 40f;
|
||||||
|
dutch[index] = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CameraSamples(
|
||||||
|
times,
|
||||||
|
positions,
|
||||||
|
rotations,
|
||||||
|
fieldOfView,
|
||||||
|
dutch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AICameraTimelinePreviewImporter.SimplifiedCameraCurves
|
||||||
|
CreatePositionOnlySimplifiedCurves(
|
||||||
|
double[] times,
|
||||||
|
Vector3[] positions,
|
||||||
|
int[] positionIndices)
|
||||||
|
{
|
||||||
|
var rotations = Enumerable.Repeat(
|
||||||
|
Quaternion.identity,
|
||||||
|
times.Length)
|
||||||
|
.ToArray();
|
||||||
|
var fieldOfView = Enumerable.Repeat(40f, times.Length).ToArray();
|
||||||
|
var dutch = new float[times.Length];
|
||||||
|
var allIndices = Enumerable.Range(0, times.Length).ToArray();
|
||||||
|
return new AICameraTimelinePreviewImporter.SimplifiedCameraCurves(
|
||||||
|
times,
|
||||||
|
positions,
|
||||||
|
rotations,
|
||||||
|
fieldOfView,
|
||||||
|
dutch,
|
||||||
|
positionIndices,
|
||||||
|
allIndices,
|
||||||
|
allIndices,
|
||||||
|
allIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertCurveIsC1(AnimationCurve curve)
|
||||||
|
{
|
||||||
|
var keys = curve.keys;
|
||||||
|
for (var index = 0; index < keys.Length; index++)
|
||||||
|
{
|
||||||
|
Assert.That(
|
||||||
|
keys[index].inTangent,
|
||||||
|
Is.EqualTo(keys[index].outTangent).Within(0.000001f),
|
||||||
|
$"Curve key {index} has a velocity discontinuity.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertCurveDoesNotOvershootSelectedSegments(
|
||||||
|
AnimationCurve curve,
|
||||||
|
IReadOnlyList<double> times,
|
||||||
|
IReadOnlyList<int> indices)
|
||||||
|
{
|
||||||
|
const int samplesPerSegment = 16;
|
||||||
|
for (var segment = 1; segment < indices.Count; segment++)
|
||||||
|
{
|
||||||
|
var start = (float)times[indices[segment - 1]];
|
||||||
|
var end = (float)times[indices[segment]];
|
||||||
|
var startValue = curve.Evaluate(start);
|
||||||
|
var endValue = curve.Evaluate(end);
|
||||||
|
var minimum = Mathf.Min(startValue, endValue) - 0.000001f;
|
||||||
|
var maximum = Mathf.Max(startValue, endValue) + 0.000001f;
|
||||||
|
for (var sample = 0; sample <= samplesPerSegment; sample++)
|
||||||
|
{
|
||||||
|
var time = Mathf.Lerp(
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
sample / (float)samplesPerSegment);
|
||||||
|
Assert.That(
|
||||||
|
curve.Evaluate(time),
|
||||||
|
Is.InRange(minimum, maximum),
|
||||||
|
$"Curve overshot selected segment {segment - 1}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3[] EvaluatePositionCurves(
|
||||||
|
IReadOnlyList<double> times,
|
||||||
|
AICameraTimelinePreviewImporter.PositionCurveBuildResult curves)
|
||||||
|
{
|
||||||
|
var result = new Vector3[times.Count];
|
||||||
|
for (var index = 0; index < times.Count; index++)
|
||||||
|
{
|
||||||
|
var time = (float)times[index];
|
||||||
|
result[index] = new Vector3(
|
||||||
|
curves.XCurve.Evaluate(time),
|
||||||
|
curves.YCurve.Evaluate(time),
|
||||||
|
curves.ZCurve.Evaluate(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float MeasureMaximumJerk(
|
||||||
|
IReadOnlyList<double> times,
|
||||||
|
IReadOnlyList<Vector3> positions)
|
||||||
|
{
|
||||||
|
if (times.Count < 4)
|
||||||
|
{
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var velocities = new Vector3[times.Count - 1];
|
||||||
|
var velocityTimes = new double[velocities.Length];
|
||||||
|
for (var index = 0; index < velocities.Length; index++)
|
||||||
|
{
|
||||||
|
velocities[index] =
|
||||||
|
(positions[index + 1] - positions[index]) /
|
||||||
|
(float)(times[index + 1] - times[index]);
|
||||||
|
velocityTimes[index] = (times[index + 1] + times[index]) * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
var accelerations = new Vector3[velocities.Length - 1];
|
||||||
|
var accelerationTimes = new double[accelerations.Length];
|
||||||
|
for (var index = 0; index < accelerations.Length; index++)
|
||||||
|
{
|
||||||
|
accelerations[index] =
|
||||||
|
(velocities[index + 1] - velocities[index]) /
|
||||||
|
(float)(velocityTimes[index + 1] - velocityTimes[index]);
|
||||||
|
accelerationTimes[index] =
|
||||||
|
(velocityTimes[index + 1] + velocityTimes[index]) * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
var maximumJerk = 0f;
|
||||||
|
for (var index = 0; index < accelerations.Length - 1; index++)
|
||||||
|
{
|
||||||
|
var jerk =
|
||||||
|
(accelerations[index + 1] - accelerations[index]) /
|
||||||
|
(float)(accelerationTimes[index + 1] -
|
||||||
|
accelerationTimes[index]);
|
||||||
|
maximumJerk = Mathf.Max(maximumJerk, jerk.magnitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
return maximumJerk;
|
||||||
|
}
|
||||||
|
|
||||||
private static CameraSamples CreateSamples(int count)
|
private static CameraSamples CreateSamples(int count)
|
||||||
{
|
{
|
||||||
var times = new double[count];
|
var times = new double[count];
|
||||||
|
|||||||
BIN
CameraAI~/Tools~/CWCameraWorker/CWCameraWorker.exe
(Stored with Git LFS)
BIN
CameraAI~/Tools~/CWCameraWorker/CWCameraWorker.exe
(Stored with Git LFS)
Binary file not shown.
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": "cw-camera-worker-build-identity-v1",
|
"schemaVersion": "cw-camera-worker-build-identity-v1",
|
||||||
"workerVersion": "0.1.3",
|
"workerVersion": "0.1.4",
|
||||||
"createdUtc": "2026-08-08T15:42:34.493408+00:00",
|
"createdUtc": "2026-08-08T17:11:19.216870+00:00",
|
||||||
"python": "3.12.13",
|
"python": "3.12.13",
|
||||||
"sourceRootRelative": "cwai_sources/repository",
|
"sourceRootRelative": "cwai_sources/repository",
|
||||||
"sourceSha256": {
|
"sourceSha256": {
|
||||||
@ -10,20 +10,21 @@
|
|||||||
"MachineLearning/CameraDirector/camera_kinematics.py": "c7b85616ef44a0f28f11702459d67d78aad7cf26ed00c2548ecb78b06fc27025",
|
"MachineLearning/CameraDirector/camera_kinematics.py": "c7b85616ef44a0f28f11702459d67d78aad7cf26ed00c2548ecb78b06fc27025",
|
||||||
"MachineLearning/CameraDirector/camera_runtime_data.py": "29fd30a1c97d384f7bb22d024a4f88178f6245858925c302b059a7a1afdeaac1",
|
"MachineLearning/CameraDirector/camera_runtime_data.py": "29fd30a1c97d384f7bb22d024a4f88178f6245858925c302b059a7a1afdeaac1",
|
||||||
"MachineLearning/CameraDirector/cw_camera_cli.py": "943870a4cdec7e4b830b9c945d690e700902cf2a37d1e49987bba85bf0b0ea87",
|
"MachineLearning/CameraDirector/cw_camera_cli.py": "943870a4cdec7e4b830b9c945d690e700902cf2a37d1e49987bba85bf0b0ea87",
|
||||||
"MachineLearning/CameraDirector/cw_camera_runtime.py": "337157e0ddaf2b3a331c7e47c73b50f01cb8c7cdfedf2cfa620adc9d79338b54",
|
"MachineLearning/CameraDirector/cw_camera_runtime.py": "94d9f03488978cf34cadbc9495542983c15c8e5050be6d7b9b0317845ca25242",
|
||||||
"MachineLearning/CameraDirector/data_driven_cut_planner.py": "b6d48d03f8725aa8327abe81afa483c43edc368e7dee9f9e7228e21982f71ad2",
|
"MachineLearning/CameraDirector/data_driven_cut_planner.py": "b6d48d03f8725aa8327abe81afa483c43edc368e7dee9f9e7228e21982f71ad2",
|
||||||
"MachineLearning/CameraDirector/generate_hybrid.py": "68e36c9a8abc7771527a7f383ba5dc501ba437c993610103bb56f9b18e1abbc1",
|
"MachineLearning/CameraDirector/generate_hybrid.py": "fbb7bd07fb32f81efd259b4bb56e30404df59eb98fe05c9c92429a214f666659",
|
||||||
"MachineLearning/CameraDirector/hybrid_candidate_cache.py": "a0a5b6a8f612f18cb2875394f17ed89950e1e62e38c1226457d9e35848f6e380",
|
"MachineLearning/CameraDirector/hybrid_candidate_cache.py": "a0a5b6a8f612f18cb2875394f17ed89950e1e62e38c1226457d9e35848f6e380",
|
||||||
"MachineLearning/CameraDirector/hybrid_cut_reference.py": "53521d9e31d315c011892961887baf0cde6451020bf39c9d080a5072d4707fcb",
|
"MachineLearning/CameraDirector/hybrid_cut_reference.py": "53521d9e31d315c011892961887baf0cde6451020bf39c9d080a5072d4707fcb",
|
||||||
"MachineLearning/CameraDirector/hybrid_preparation_cache.py": "1b0df53124109dee2d744777979a0553999cb276cb6f538a17ddce6516e3da8b",
|
"MachineLearning/CameraDirector/hybrid_preparation_cache.py": "1b0df53124109dee2d744777979a0553999cb276cb6f538a17ddce6516e3da8b",
|
||||||
"MachineLearning/CameraDirector/hybrid_single_shot_cache.py": "f540e603b0ccffb86ee003d441c3889c245a208db8fd8b2b966fdacfeea30950",
|
"MachineLearning/CameraDirector/hybrid_single_shot_cache.py": "f540e603b0ccffb86ee003d441c3889c245a208db8fd8b2b966fdacfeea30950",
|
||||||
"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": "40e9e9a9ab824d596b14f987b2977f187695c3d5a2a2bde9da67e95d38d78836"
|
"MachineLearning/CameraDirector/train.py": "40e9e9a9ab824d596b14f987b2977f187695c3d5a2a2bde9da67e95d38d78836",
|
||||||
|
"MachineLearning/CameraDirector/trajectory_quality.py": "8ede816e1d155e9d35fd6d18b3aaa145cd4957e205d898e11c1691222ef420a2"
|
||||||
},
|
},
|
||||||
"preparationLogicIdentifier": "acba2a46d9d979b013b7154bada6b6a8a7adfd7acd1721876ce6699ed4e03e84",
|
"preparationLogicIdentifier": "acba2a46d9d979b013b7154bada6b6a8a7adfd7acd1721876ce6699ed4e03e84",
|
||||||
"candidateLogicIdentifier": "9cca18ec839f70fffad3e38ab05fbb9209c51d17081ec02dad1b2af1f93df223",
|
"candidateLogicIdentifier": "a9503f8764910d763fe0135f6b1425b0cba289dbd1dd149609d2c00e508470c1",
|
||||||
"generationCodeIdentifier": "2d225f7a926869593d8572e369e504fa6440565f84329ff797f66f3912abf7b0",
|
"generationCodeIdentifier": "05e85df5ac7eb614f68e340502cb03a9c5a073f91eba5d16d642340728d51d67",
|
||||||
"generationCodeFiles": [
|
"generationCodeFiles": [
|
||||||
"adjacent_transition.py",
|
"adjacent_transition.py",
|
||||||
"camera_kinematics.py",
|
"camera_kinematics.py",
|
||||||
@ -32,6 +33,7 @@
|
|||||||
"generate_hybrid.py",
|
"generate_hybrid.py",
|
||||||
"hybrid_cut_reference.py",
|
"hybrid_cut_reference.py",
|
||||||
"planner.py",
|
"planner.py",
|
||||||
"shot_features.py"
|
"shot_features.py",
|
||||||
|
"trajectory_quality.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.3"
|
WORKER_VERSION = "0.1.4"
|
||||||
|
|
||||||
|
|
||||||
def is_frozen_runtime() -> bool:
|
def is_frozen_runtime() -> bool:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "com.mingle.cw-ai",
|
"name": "com.mingle.cw-ai",
|
||||||
"version": "0.4.7",
|
"version": "0.4.8",
|
||||||
"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",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user