Netcode Bootstrap

This commit is contained in:
Luis Gonzalez
2026-05-31 14:27:52 -07:00
parent 99d8d2d2a9
commit 7fa77ce821
1813 changed files with 2921554 additions and 84 deletions
@@ -0,0 +1,657 @@
using System;
using Rukhanka.Toolbox;
using Rukhanka.WaybackMachine;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.UIElements;
///////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Editor
{
[BurstCompile]
public partial class TimelineContent: VisualElement, IDisposable
{
public NativeReference<TimelinePortalData> timelinePortal;
public NativeReference<WaybackMachineData> recordedData;
public bool isRecording;
public float animHeaderHeight;
public float eventsHeaderHeight;
public float statesHeaderHeight;
public int eventBarsCount;
public int animBarsCount;
public int stateBarsCount;
readonly Color lineColor = new Color(1, 1, 1, 0.1f);
struct AnimationRect
{
public Rect rect;
public bool visible;
public int eventId;
public int rowIndex;
public int2 weightHistoryPointRange;
public int2 animTimeHistoryPointRange;
}
struct AnimatorStateRect
{
public Rect rect;
public bool visible;
public int eventId;
public int rowIndex;
public Color32 color;
}
struct AnimatorTransitionRect
{
public Rect rect;
public bool visible;
public int eventId;
public int rowIndex;
public Color32 colorA, colorB;
public float2 yAB;
}
struct EventShape
{
public float2 pos;
public bool visible;
public int eventId;
public int rowIndex;
}
struct EventLine
{
public float2 posMin;
public float2 posMax;
public bool visible;
}
NativeList<AnimationRect> animationRects;
NativeList<AnimatorStateRect> animatorStateShapes;
NativeList<AnimatorTransitionRect> animatorTransitionShapes;
NativeList<EventShape> animationEventShapes;
NativeList<EventShape> animatorEventShapes;
NativeList<EventLine> animatorEventLines;
NativeList<float2> timelinePoints;
// Used for text width measurements
public Label textMeasurer;
public WaybackMachineSettings settings;
///////////////////////////////////////////////////////////////////////////////////////////
public TimelineContent()
{
style.flexGrow = 1;
style.overflow = new StyleEnum<Overflow>(Overflow.Hidden);
generateVisualContent += DrawContent;
animationRects = new (0xff, Allocator.Persistent);
timelinePoints = new (0xffff, Allocator.Persistent);
animationEventShapes = new (0xff, Allocator.Persistent);
animatorEventShapes = new (0xff, Allocator.Persistent);
animatorEventLines = new (0xff, Allocator.Persistent);
animatorStateShapes = new (0xff, Allocator.Persistent);
animatorTransitionShapes = new (0xff, Allocator.Persistent);
}
///////////////////////////////////////////////////////////////////////////////////////////
public void Dispose()
{
animationRects.Dispose();
timelinePoints.Dispose();
animationEventShapes.Dispose();
animatorEventShapes.Dispose();
animatorEventLines.Dispose();
animatorStateShapes.Dispose();
animatorTransitionShapes.Dispose();
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawVLines(Painter2D p)
{
p.lineWidth = 1f;
p.lineCap = LineCap.Butt;
p.BeginPath();
p.strokeColor = lineColor;
foreach (var tl in timelinePortal.Value.tickLines)
{
if (!tl.majorTick)
continue;
var p0 = tl.lineFrom;
var p1 = tl.lineTo;
p0.y = contentRect.y + contentRect.height * p0.y;
p1.y = contentRect.yMax * p1.y;
p.MoveTo(p0);
p.LineTo(p1);
}
p.Stroke();
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawKnobLine(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
var knobPosX = timelinePortal.Value.GetKnobPosX();
var knobTooltipWidth = 0f;
var knobTooltipHeight = 0f;
if (timelinePortal.Value.knobDragging)
{
var knobFrame = timelinePortal.Value.knobFrame;
var knobTime = timelinePortal.Value.GetTimeForFrame(knobFrame);
var knobPosStr = knobFrame.ToString();
if (settings.rulerMode == WaybackMachineSettings.RulerMode.Seconds)
knobPosStr = knobTime.ToString("0.00");
var tooltipWidth = new Vector2(knobPosStr.Length * 6, 14.0f);
knobTooltipWidth = tooltipWidth.x * 1.3f;
knobTooltipHeight = tooltipWidth.y * 1.1f;
var hpt0 = new Vector2(knobPosX - knobTooltipWidth * 0.5f, contentRect.yMin + 1);
var hpt1 = new Vector2(hpt0.x + knobTooltipWidth, contentRect.yMin + 1);
var hpt2 = new Vector2(hpt1.x, contentRect.yMin + knobTooltipHeight);
var hpt3 = new Vector2(hpt0.x, hpt2.y);
p.lineJoin = LineJoin.Round;
p.fillColor = Color.black;
p.BeginPath();
p.MoveTo(hpt0);
p.LineTo(hpt1);
p.LineTo(hpt2);
p.LineTo(hpt3);
p.ClosePath();
p.Fill();
var textPt = new Vector2(knobPosX - tooltipWidth.x * 0.5f, contentRect.yMin + knobTooltipHeight - tooltipWidth.y);
ctx.DrawText(knobPosStr, textPt, 11, Color.white);
}
p.fillColor = Color.white;
p.lineWidth = 1;
p.lineJoin = LineJoin.Miter;
var knobYOffset = math.select(0, knobTooltipHeight, timelinePortal.Value.knobDragging);
var pt0 = new Vector2((int)knobPosX + 0.5f, contentRect.yMin + knobYOffset);
var pt1 = new Vector2(pt0.x, contentRect.yMax);
p.strokeColor = Color.white;
p.lineWidth = 1;
p.BeginPath();
p.MoveTo(pt0);
p.LineTo(pt1);
p.Stroke();
}
///////////////////////////////////////////////////////////////////////////////////////////
public unsafe void ComputeShapes()
{
animationRects.Clear();
animationEventShapes.Clear();
animatorEventShapes.Clear();
animatorEventLines.Clear();
animatorStateShapes.Clear();
animatorTransitionShapes.Clear();
timelinePoints.Clear();
if (!recordedData.IsCreated || recordedData.Value.animHistory.Length == 0)
return;
fixed (int* animBarsCountPtr = &animBarsCount, stateBarsCountPtr = &stateBarsCount, eventsBatCountPtr = &eventBarsCount)
{
var computeRectsJob = new BuildRectanglesJob()
{
timelinePortal = timelinePortal,
wbData = recordedData,
contentRect = contentRect,
timelinePoints = timelinePoints,
animationShapes = animationRects,
animationEventShapes = animationEventShapes,
animatorEventShapes = animatorEventShapes,
animationEventLines = animatorEventLines,
animatorStateShapes = animatorStateShapes,
animatorTransitionShapes = animatorTransitionShapes,
outEventBarsCount = eventsBatCountPtr,
outAnimBarCount = animBarsCountPtr,
outStatesBarCount = stateBarsCountPtr,
animHeaderHeight = animHeaderHeight,
eventsHeaderHeight = eventsHeaderHeight,
statesHeaderHeight = statesHeaderHeight,
settings = settings
};
computeRectsJob.Run();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawHistoryLine(int2 pointRange, Painter2D p, float lineWidth, Color color)
{
p.strokeColor = color;
p.lineWidth = lineWidth;
p.lineJoin = LineJoin.Bevel;
p.BeginPath();
for (var k = pointRange.x; k < pointRange.y; ++k)
{
if (k == 0)
p.MoveTo(timelinePoints[k]);
else
p.LineTo(timelinePoints[k]);
}
p.Stroke();
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawAnimatorStateShapes(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
for (var i = 0; i < animatorStateShapes.Length; ++i)
{
var rd = animatorStateShapes.ElementAt(i);
// Quick reject of invisible rects
if (!rd.visible)
continue;
var pt0 = new Vector2(rd.rect.xMin, rd.rect.yMin);
var pt1 = new Vector2(rd.rect.xMax, rd.rect.yMin);
var pt2 = new Vector2(rd.rect.xMax, rd.rect.yMax);
var pt3 = new Vector2(rd.rect.xMin, rd.rect.yMax);
p.strokeColor = Color.white;
p.lineWidth = 1;
p.BeginPath();
p.LineTo(pt0);
p.LineTo(pt1);
p.LineTo(pt2);
p.LineTo(pt3);
p.ClosePath();
p.fillColor = rd.color;
p.Fill();
// Draw text
var ah = recordedData.Value.controllerStateHistory[rd.eventId];
var name = ah.GetName();
DrawTextInRect(name, rd.rect, ctx);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawAnimatorTransitionShapes(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
for (var i = 0; i < animatorTransitionShapes.Length; ++i)
{
var rd = animatorTransitionShapes[i];
// Quick reject of invisible rects
if (!rd.visible)
continue;
var pt0 = new Vector2(rd.rect.xMin, rd.rect.yMin);
var pt1 = new Vector2(rd.rect.xMax, rd.rect.yMin);
var pt2 = new Vector2(rd.rect.xMax, rd.rect.yMin + rd.yAB.y);
var pt3 = new Vector2(rd.rect.xMin, rd.rect.yMin + rd.yAB.x);
p.strokeColor = Color.white;
p.lineWidth = 1;
// src side
p.BeginPath();
p.MoveTo(pt0);
p.LineTo(pt1);
p.LineTo(pt2);
p.LineTo(pt3);
p.ClosePath();
p.fillColor = rd.colorA;
p.Fill();
// dst side
pt0.y = rd.rect.yMax;
pt1.y = rd.rect.yMax;
p.BeginPath();
p.MoveTo(pt0);
p.LineTo(pt1);
p.LineTo(pt2);
p.LineTo(pt3);
p.ClosePath();
p.fillColor = rd.colorB;
p.Fill();
}
// Divider lines
p.lineCap = LineCap.Butt;
p.strokeColor = Color.black;
p.lineWidth = WaybackMachineWindow.STATE_BAR_VERTICAL_SPACE;
p.BeginPath();
for (var i = 0; i < animatorTransitionShapes.Length; ++i)
{
var rd = animatorTransitionShapes[i];
var pt0 = new Vector2(rd.rect.xMin, rd.rect.yMin);
var pt1 = new Vector2(rd.rect.xMin, rd.rect.yMin + rd.yAB.x);
var pt2 = new Vector2(rd.rect.xMax, rd.rect.yMin + rd.yAB.y);
var pt3 = new Vector2(rd.rect.xMax, rd.rect.yMax);
p.MoveTo(pt0);
p.LineTo(pt1);
p.LineTo(pt2);
p.LineTo(pt3);
}
p.Stroke();
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawAnimationShapes(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
for (var i = 0; i < animationRects.Length; ++i)
{
ref var rd = ref animationRects.ElementAt(i);
// Quick reject of invisible rects
if (!rd.visible)
continue;
var pt0 = new Vector2(rd.rect.xMin, rd.rect.yMin);
var pt1 = new Vector2(rd.rect.xMax, rd.rect.yMin);
var pt2 = new Vector2(rd.rect.xMax, rd.rect.yMax);
var pt3 = new Vector2(rd.rect.xMin, rd.rect.yMax);
p.strokeColor = Color.white;
p.lineWidth = 1;
p.BeginPath();
p.MoveTo(pt0 + new Vector2(1, 0));
p.LineTo(pt1 + new Vector2(-1, 0));
p.LineTo(pt2 + new Vector2(-1, 0));
p.LineTo(pt3 + new Vector2(1, 0));
p.ClosePath();
p.fillColor = WaybackMachineWindow.ANIMATION_BAR_COLOR;
p.Fill();
// Draw weights
if (settings.animationWeightGraphs)
DrawHistoryLine(rd.weightHistoryPointRange, p, 2, WaybackMachineWindow.ANIMATION_HISTORY_WEIGHT_LINE_COLOR);
// Draw animation times
if (settings.animationTimeGraphs)
DrawHistoryLine(rd.animTimeHistoryPointRange, p, 2, WaybackMachineWindow.ANIMATION_HISTORY_TIME_LINE_COLOR);
// Draw text
var ah = recordedData.Value.animHistory[rd.eventId];
#if RUKHANKA_DEBUG_INFO
var animName = ah.animationName.ToString();
#else
var animName = ah.animationHash.ToString();
#endif
DrawTextInRect(animName, rd.rect, ctx);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawTextInRect(string text, Rect r, MeshGenerationContext ctx)
{
var textSize = textMeasurer.MeasureTextSize(text, 0, MeasureMode.Undefined, 0, MeasureMode.Undefined);
// Trim text size if out of bounds
var f = math.min(1, r.width * 0.5f / textSize.x);
if (f < 1)
{
var offset = (int)(text.Length * f);
text = offset > 0 ? text.Substring(0, offset) : "";
textSize.x *= f;
}
if (textSize.x > 5)
{
var tpt = r.center - textSize * 0.5f;
ctx.DrawText(text, tpt, 16, Color.white);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawRecordingRect(Painter2D p)
{
if (!isRecording)
return;
var lineWidth = 4;
var halfLineWidth = lineWidth / 2;
p.strokeColor = Color.red;
p.lineWidth = lineWidth;
p.lineJoin = LineJoin.Bevel;
p.BeginPath();
p.MoveTo(new Vector2(contentRect.xMin + halfLineWidth, contentRect.yMin + halfLineWidth));
p.LineTo(new Vector2(contentRect.xMax - halfLineWidth, contentRect.yMin + halfLineWidth));
p.LineTo(new Vector2(contentRect.xMax - halfLineWidth, contentRect.yMax - halfLineWidth));
p.LineTo(new Vector2(contentRect.xMin + halfLineWidth, contentRect.yMax - halfLineWidth));
p.ClosePath();
p.Stroke();
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawAnimationEvents(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
p.strokeColor = Color.white;
p.fillColor = WaybackMachineWindow.ANIMATION_EVENT_COLOR;
p.lineWidth = 1;
for (var i = 0; i < animationEventShapes.Length; ++i)
{
ref var es = ref animationEventShapes.ElementAt(i);
if (!es.visible)
continue;
p.BeginPath();
p.MoveTo(new Vector2(es.pos.x - WaybackMachineWindow.EVENT_SHAPE_RADIUS, es.pos.y));
p.LineTo(new Vector2(es.pos.x, es.pos.y - WaybackMachineWindow.EVENT_SHAPE_RADIUS));
p.LineTo(new Vector2(es.pos.x + WaybackMachineWindow.EVENT_SHAPE_RADIUS, es.pos.y));
p.LineTo(new Vector2(es.pos.x, es.pos.y + WaybackMachineWindow.EVENT_SHAPE_RADIUS));
p.ClosePath();
p.Fill();
p.Stroke();
// Draw text labels
if (settings.eventLabels)
{
var ehd = recordedData.Value.animationEventHistory[es.eventId];
var evtName = ehd.GetName();
ctx.DrawText(evtName, new Vector2(es.pos.x + 10, es.pos.y - 9), 14, WaybackMachineWindow.EVENT_TEXT_COLOR);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawAnimatorEventLines(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
p.strokeColor = Color.white;
p.lineWidth = 1;
for (var i = 0; i < animatorEventLines.Length; ++i)
{
var el = animatorEventLines.ElementAt(i);
if (!el.visible)
continue;
p.BeginPath();
var p0 = el.posMin;
p0.y -= WaybackMachineWindow.EVENT_SHAPE_RADIUS;
p.MoveTo(p0);
p.LineTo(el.posMin);
p.LineTo(el.posMax);
var p3 = el.posMax;
p0.y -= WaybackMachineWindow.EVENT_SHAPE_RADIUS;
p.LineTo(p3);
p.Stroke();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawAnimatorEventShapes(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
p.lineWidth = 1;
for (var i = 0; i < animatorEventShapes.Length; ++i)
{
ref var es = ref animatorEventShapes.ElementAt(i);
if (!es.visible)
continue;
p.BeginPath();
p.fillColor = WaybackMachineWindow.ANIMATOR_EVENT_COLOR;
p.strokeColor = WaybackMachineWindow.EVENT_OUTLINE_COLOR;
p.MoveTo(new Vector2(es.pos.x - WaybackMachineWindow.EVENT_SHAPE_RADIUS, es.pos.y - WaybackMachineWindow.EVENT_SHAPE_RADIUS));
p.LineTo(new Vector2(es.pos.x + WaybackMachineWindow.EVENT_SHAPE_RADIUS, es.pos.y - WaybackMachineWindow.EVENT_SHAPE_RADIUS));
p.LineTo(new Vector2(es.pos.x + WaybackMachineWindow.EVENT_SHAPE_RADIUS, es.pos.y + WaybackMachineWindow.EVENT_SHAPE_RADIUS));
p.LineTo(new Vector2(es.pos.x - WaybackMachineWindow.EVENT_SHAPE_RADIUS, es.pos.y + WaybackMachineWindow.EVENT_SHAPE_RADIUS));
p.ClosePath();
p.Fill();
p.Stroke();
// Enter/Exit triangle
p.BeginPath();
p.fillColor = WaybackMachineWindow.ANIMATOR_EVENT_ENTER_EXIT_MARKER_COLOR;
p.strokeColor = WaybackMachineWindow.EVENT_OUTLINE_COLOR;
var ehd = recordedData.Value.animatorEventHistory[es.eventId];
var offset = 0f;
switch (ehd.eventType)
{
case AnimatorControllerEventComponent.EventType.StateEnter: offset = -2.5f; break;
case AnimatorControllerEventComponent.EventType.StateExit: offset = 1.5f; break;
}
var pt0 = new Vector2(es.pos.x + WaybackMachineWindow.EVENT_SHAPE_RADIUS * offset, es.pos.y - WaybackMachineWindow.EVENT_SHAPE_RADIUS);
p.MoveTo(pt0);
p.LineTo(pt0 + new Vector2(WaybackMachineWindow.EVENT_SHAPE_RADIUS, WaybackMachineWindow.EVENT_SHAPE_RADIUS));
p.LineTo(pt0 + new Vector2(0, WaybackMachineWindow.EVENT_SHAPE_RADIUS * 2));
p.ClosePath();
p.Fill();
p.Stroke();
// Draw text labels
if (settings.eventLabels)
{
var evtName = ehd.name.ToString();
ctx.DrawText(evtName, new Vector2(es.pos.x + WaybackMachineWindow.EVENT_SHAPE_RADIUS * 3, es.pos.y - 9), 14, WaybackMachineWindow.EVENT_TEXT_COLOR);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
static void GetAnimationEventShapeIndicesForPosBurst(ref NativeList<int2> outIndices, in NativeList<EventShape> hd, float posX)
{
outIndices.Clear();
for (int i = 0; i < hd.Length; ++i)
{
var hv = hd[i];
if (posX >= hv.pos.x - WaybackMachineWindow.EVENT_SHAPE_RADIUS && posX <= hv.pos.x + WaybackMachineWindow.EVENT_SHAPE_RADIUS)
outIndices.Add(new int2(hv.eventId, hv.rowIndex));
}
}
///////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
static void GetStateShapeIndicesForPosBurst(ref NativeList<int3> outIndices, in NativeList<AnimatorStateRect> hd, float posX)
{
outIndices.Clear();
for (int i = 0; i < hd.Length; ++i)
{
var hv = hd[i];
if (hv.rect.xMin <= posX && hv.rect.xMax >= posX)
{
outIndices.Add(new int3(hv.eventId, hv.rowIndex, hv.color.ToInt()));
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
static void GetTransitionShapeIndicesForPosBurst(ref NativeList<int4> outIndices, in NativeList<AnimatorTransitionRect> hd, float posX)
{
outIndices.Clear();
for (int i = 0; i < hd.Length; ++i)
{
var hv = hd[i];
if (hv.rect.xMin <= posX && hv.rect.xMax >= posX)
{
outIndices.Add(new int4(hv.eventId, hv.rowIndex, hv.colorA.ToInt(), hv.colorB.ToInt()));
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
static void GetAnimationShapeIndicesForPosBurst(ref NativeList<int2> outIndices, in NativeList<AnimationRect> hd, float posX)
{
outIndices.Clear();
for (int i = 0; i < hd.Length; ++i)
{
var hv = hd[i];
if (hv.rect.xMin <= posX && hv.rect.xMax >= posX)
{
outIndices.Add(new int2(hv.eventId, hv.rowIndex));
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
public void GetAnimationShapeIndicesForPos(ref NativeList<int2> outIndices, float posX)
{
GetAnimationShapeIndicesForPosBurst(ref outIndices, animationRects, posX);
}
///////////////////////////////////////////////////////////////////////////////////////////
public void GetTransitionShapeIndicesForPos(ref NativeList<int4> outIndices, float posX)
{
GetTransitionShapeIndicesForPosBurst(ref outIndices, animatorTransitionShapes, posX);
}
///////////////////////////////////////////////////////////////////////////////////////////
public void GetStateShapeIndicesForPos(ref NativeList<int3> outIndices, float posX)
{
GetStateShapeIndicesForPosBurst(ref outIndices, animatorStateShapes, posX);
}
///////////////////////////////////////////////////////////////////////////////////////////
public void GetAnimationEventShapeIndicesForPos(ref NativeList<int2> outIndices, float posX)
{
GetAnimationEventShapeIndicesForPosBurst(ref outIndices, animationEventShapes, posX);
}
///////////////////////////////////////////////////////////////////////////////////////////
public void GetAnimatorEventShapeIndicesForPos(ref NativeList<int2> outIndices, float posX)
{
GetAnimationEventShapeIndicesForPosBurst(ref outIndices, animatorEventShapes, posX);
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawContent(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
DrawVLines(p);
DrawAnimationShapes(ctx);
DrawAnimatorStateShapes(ctx);
DrawAnimatorTransitionShapes(ctx);
DrawAnimationEvents(ctx);
DrawAnimatorEventShapes(ctx);
DrawAnimatorEventLines(ctx);
DrawRecordingRect(p);
DrawKnobLine(ctx);
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 01fd825e276c9664fa4365922a9d466c
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/TimelineContent.cs
uploadId: 897522
@@ -0,0 +1,334 @@
using System;
using Rukhanka.WaybackMachine;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
///////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Editor
{
public partial class TimelineContent
{
[BurstCompile]
unsafe struct BuildRectanglesJob: IJob
{
[ReadOnly]
[NativeDisableContainerSafetyRestriction]
public NativeReference<WaybackMachineData> wbData;
[ReadOnly]
[NativeDisableContainerSafetyRestriction]
public NativeReference<TimelinePortalData> timelinePortal;
public Rect contentRect;
public NativeList<AnimationRect> animationShapes;
public NativeList<AnimatorStateRect> animatorStateShapes;
public NativeList<AnimatorTransitionRect> animatorTransitionShapes;
public NativeList<EventShape> animatorEventShapes;
public NativeList<EventShape> animationEventShapes;
public NativeList<EventLine> animationEventLines;
public NativeList<float2> timelinePoints;
public float animHeaderHeight;
public float eventsHeaderHeight;
public float statesHeaderHeight;
public WaybackMachineSettings settings;
[NativeDisableUnsafePtrRestriction]
public int* outEventBarsCount;
[NativeDisableUnsafePtrRestriction]
public int* outAnimBarCount;
[NativeDisableUnsafePtrRestriction]
public int* outStatesBarCount;
float baseY;
///////////////////////////////////////////////////////////////////////////////////////////
public void Execute()
{
baseY = eventsHeaderHeight;
// Animation event shapes
var l2 = wbData.Value.animationEventHistory.Length * settings.eventsVisible;
for (var k = 0; k < l2; ++k)
{
var eh = wbData.Value.animationEventHistory[k];
ComputeEventShape(k, eh.frameIndex, 0, animationEventShapes);
}
// Animator event shapes
var l3 = wbData.Value.animatorEventHistory.Length * settings.eventsVisible;
var baseRowIndex = *outEventBarsCount;
for (var k = 0; k < l3; ++k)
{
var eh = wbData.Value.animatorEventHistory[k];
if (eh.eventType != AnimatorControllerEventComponent.EventType.StateUpdate)
{
ComputeEventShape(k, eh.frameRange.x, baseRowIndex, animatorEventShapes);
}
else
{
//ComputeEventLine(k, eh);
}
}
// Animator states shapes
baseY += *outEventBarsCount * WaybackMachineWindow.EVENT_ROW_HEIGHT + statesHeaderHeight;
var l4 = wbData.Value.controllerStateHistory.Length * settings.statesVisible;
Span<int> layerCounters = stackalloc int[0xff];
for (var k = 0; k < l4; ++k)
{
ComputeControllerStateShape(k, layerCounters);
}
// Animator transition shapes
var l5 = wbData.Value.controllerTransitionHistory.Length * settings.statesVisible;
for (var k = 0; k < l5; ++k)
{
ComputeControllerTransitionShape(k);
}
// Animation shapes
baseY += *outStatesBarCount * WaybackMachineWindow.STATE_ROW_HEIGHT + animHeaderHeight;
var l = wbData.Value.animHistory.Length * settings.animationsVisible;
for (var i = 0; i < l; ++i)
{
ComputeAnimationShape(i);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
int GetEventRow(float2 eventPos, int baseRowIndex, NativeList<EventShape> eventShapes)
{
var rv = baseRowIndex - 1;
var minI = math.max(0, eventShapes.Length - 32);
var isOverlapped = false;
var esr2 = WaybackMachineWindow.EVENT_SHAPE_RADIUS * WaybackMachineWindow.EVENT_SHAPE_RADIUS;
do
{
isOverlapped = false;
eventPos.y += ++rv * WaybackMachineWindow.EVENT_ROW_HEIGHT;
for (var i = eventShapes.Length - 1; i >= minI; --i)
{
var es = eventShapes[i];
var dv = es.pos - eventPos;
var d = math.lengthsq(dv);
if (d < esr2)
{
isOverlapped = true;
break;
}
}
}
while (isOverlapped);
return rv;
}
///////////////////////////////////////////////////////////////////////////////////////////
void ComputeEventShape(int idx, int frameIndex, int baseRowIndex, NativeList<EventShape> outEventShapes)
{
var x0 = timelinePortal.Value.GetPosXForFrame(frameIndex);
var eventShape = new EventShape();
eventShape.pos = new float2(x0, baseY + WaybackMachineWindow.EVENT_ROW_HEIGHT * 0.5f);
var eventRow = GetEventRow(eventShape.pos, baseRowIndex, outEventShapes);
eventShape.pos.y += eventRow * WaybackMachineWindow.EVENT_ROW_HEIGHT;
var r = new Rect(eventShape.pos.x - WaybackMachineWindow.EVENT_SHAPE_RADIUS * 2, eventShape.pos.y, WaybackMachineWindow.EVENT_SHAPE_RADIUS * 4 , 1);
eventShape.visible = contentRect.Overlaps(r);
eventShape.eventId = idx;
eventShape.rowIndex = eventRow;
*outEventBarsCount = math.max(eventRow + 1, *outEventBarsCount);
outEventShapes.Add(eventShape);
}
///////////////////////////////////////////////////////////////////////////////////////////
int GetFreeLaneIndex(int curIdx, float x0)
{
var collision = false;
var laneIndex = 0;
do
{
collision = false;
for (var i = 0; i < curIdx; ++i)
{
var s = animationShapes[i];
var pt = new Vector2(x0, baseY + (0.5f + laneIndex) * WaybackMachineWindow.ANIMATION_BAR_HEIGHT);
if (s.rect.Contains(pt))
{
collision = true;
laneIndex += 1;
break;
}
}
} while (collision);
return laneIndex;
}
///////////////////////////////////////////////////////////////////////////////////////////
void AddPoint(HistoryValue hv, float dy, float y0, float2 xBounds)
{
float2 pt = default;
pt.x = timelinePortal.Value.GetPosXForFrame(hv.frameIndex);
pt.x = math.clamp(pt.x, xBounds.x, xBounds.y);
pt.y = (1 - hv.value) * (dy - 2) + y0 + 2;
timelinePoints.Add(pt);
}
///////////////////////////////////////////////////////////////////////////////////////////
void ComputeHistoryLine(ref int2 pointRange, Rect r, int2 frameSpan, UnsafeList<HistoryValue> hvs)
{
pointRange.x = timelinePoints.Length;
float2 xBounds = new float2(r.xMin + 2, r.xMax - 2);
for (var i = 0; i < hvs.Length; ++i)
{
var hv = hvs[i];
if (hv.value > 1)
hv.value = math.frac(hv.value);
AddPoint(hv, r.height, r.y, xBounds);
}
pointRange.y = timelinePoints.Length;
}
///////////////////////////////////////////////////////////////////////////////////////////
int2 CutTransitionArea(int2 frameSpan, int layerIndex)
{
var rv = frameSpan;
for (var i = 0; i < wbData.Value.controllerTransitionHistory.Length; ++i)
{
ref var td = ref wbData.Value.controllerTransitionHistory.ElementAt(i);
if (td.layerIndex != layerIndex)
continue;
if (td.frameSpan.y >= frameSpan.x && td.frameSpan.x <= frameSpan.y)
{
// Is this is dst state for transition
if (rv.x >= td.frameSpan.x)
{
rv.x = math.max(td.frameSpan.y, rv.x);
}
// Is this is src state for transition
if (rv.y <= td.frameSpan.y)
{
rv.y = math.min(td.frameSpan.x, rv.y);
}
}
}
return rv;
}
///////////////////////////////////////////////////////////////////////////////////////////
void ComputeControllerTransitionShape(int idx)
{
var rd = wbData.Value.controllerTransitionHistory[idx];
var x0 = timelinePortal.Value.GetPosXForFrame(rd.frameSpan.x);
var x1 = timelinePortal.Value.GetPosXForFrame(rd.frameSpan.y);
var laneIndex = rd.layerIndex;
var y0 = baseY + laneIndex * WaybackMachineWindow.STATE_ROW_HEIGHT;
var y1 = y0 + WaybackMachineWindow.STATE_BAR_HEIGHT;
var dx = x1 - x0;
var dy = y1 - y0;
var r = new Rect(x0, y0, dx, dy);
var dr = new AnimatorTransitionRect();
dr.rect = r;
dr.yAB = rd.weightRange * dy;
dr.visible = contentRect.Overlaps(r);
dr.eventId = idx;
dr.rowIndex = laneIndex;
dr.colorA = animatorStateShapes[rd.dstStateDataIndex].color;
dr.colorB = animatorStateShapes[rd.srcStateDataIndex].color;
*outStatesBarCount = math.max(*outStatesBarCount, laneIndex + 1);
animatorTransitionShapes.Add(dr);
}
///////////////////////////////////////////////////////////////////////////////////////////
void ComputeControllerStateShape(int idx, Span<int> layerCounters)
{
var rd = wbData.Value.controllerStateHistory[idx];
var ts = CutTransitionArea(rd.frameSpan, rd.layerIndex);
var x0 = timelinePortal.Value.GetPosXForFrame(ts.x);
var x1 = timelinePortal.Value.GetPosXForFrame(ts.y);
var laneIndex = rd.layerIndex;
var y0 = baseY + laneIndex * (WaybackMachineWindow.STATE_BAR_HEIGHT + WaybackMachineWindow.STATE_BAR_HORIZONTAL_SPACE);
var y1 = y0 + WaybackMachineWindow.STATE_BAR_HEIGHT;
var dx = x1 - x0;
var dy = y1 - y0;
var r = new Rect(x0 - 1, y0, dx + 2, dy);
var dr = new AnimatorStateRect();
dr.rect = r;
dr.visible = contentRect.Overlaps(r);
dr.eventId = idx;
dr.rowIndex = laneIndex;
var counter = layerCounters[rd.layerIndex]++;
dr.color = (counter & 1) == 0 ? WaybackMachineWindow.STATE_BAR_COLOR1 : WaybackMachineWindow.STATE_BAR_COLOR2;
*outStatesBarCount = math.max(*outStatesBarCount, laneIndex + 1);
animatorStateShapes.Add(dr);
}
///////////////////////////////////////////////////////////////////////////////////////////
void ComputeAnimationShape(int idx)
{
var rd = wbData.Value.animHistory[idx];
var x0 = timelinePortal.Value.GetPosXForFrame(rd.frameSpan.x);
var x1 = timelinePortal.Value.GetPosXForFrame(rd.frameSpan.y);
var laneIndex = GetFreeLaneIndex(idx, x0 + 0.001f);
var y0 = baseY + laneIndex * WaybackMachineWindow.ANIMATION_ROW_HEIGHT;
var y1 = y0 + WaybackMachineWindow.ANIMATION_BAR_HEIGHT;
var dx = x1 - x0;
var dy = y1 - y0;
*outAnimBarCount = math.max(*outAnimBarCount, laneIndex + 1);
var r = new Rect(x0, y0, dx, dy);
// Rectangle
var dr = new AnimationRect();
dr.rect = r;
dr.visible = contentRect.Overlaps(r);
dr.eventId = idx;
dr.rowIndex = laneIndex;
// Weight line
ComputeHistoryLine(ref dr.weightHistoryPointRange, r, rd.frameSpan, rd.historyWeights);
// Animation time line
ComputeHistoryLine(ref dr.animTimeHistoryPointRange, r, rd.frameSpan, rd.historyAnimTime);
animationShapes.Add(dr);
}
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 42d7bcb2254d22b41bd7664ef0a89592
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/TimelineContent_Jobs.cs
uploadId: 897522
@@ -0,0 +1,168 @@
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.UIElements;
///////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Editor
{
public class TimelineHeader: VisualElement
{
public NativeReference<TimelinePortalData> timelinePortal;
readonly float knobWidth = 10;
readonly Color knobColor = Color.white;
public WaybackMachineSettings settings;
///////////////////////////////////////////////////////////////////////////////////////////
public TimelineHeader()
{
style.flexGrow = 1;
style.overflow = new StyleEnum<Overflow>(Overflow.Hidden);
generateVisualContent += DrawContent;
RegisterCallback<MouseDownEvent>(OnMouseDown);
RegisterCallback<MouseUpEvent>(OnMouseUp);
RegisterCallback<MouseMoveEvent>(OnMouseMove);
}
///////////////////////////////////////////////////////////////////////////////////////////
unsafe void OnMouseDown(MouseDownEvent evt)
{
MouseCaptureController.CaptureMouse(this);
timelinePortal.GetUnsafePtr()->SetKnobFromPos(evt.localMousePosition.x);
timelinePortal.GetUnsafePtr()->knobDragging = true;
}
///////////////////////////////////////////////////////////////////////////////////////////
unsafe void OnMouseMove(MouseMoveEvent evt)
{
if (!MouseCaptureController.IsMouseCaptured())
return;
timelinePortal.GetUnsafePtr()->SetKnobFromPos(evt.localMousePosition.x);
}
///////////////////////////////////////////////////////////////////////////////////////////
unsafe void OnMouseUp(MouseUpEvent evt)
{
MouseCaptureController.ReleaseMouse(this);
timelinePortal.GetUnsafePtr()->knobDragging = false;
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawTimeline(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
p.lineWidth = 1f;
p.lineCap = LineCap.Butt;
p.strokeColor = Color.white;
p.BeginPath();
var textColor = new Color(1, 1, 1, 0.7f);
foreach (var tl in timelinePortal.Value.tickLines)
{
var p0 = tl.lineFrom;
var p1 = tl.lineTo;
p0.y = contentRect.y + contentRect.height * p0.y;
p1.y = contentRect.yMax * p1.y;
p.MoveTo(p0);
p.LineTo(p1);
if (tl.majorTick)
{
var str = settings.rulerMode == WaybackMachineSettings.RulerMode.Frames
? tl.frameIndex.ToString()
: timelinePortal.Value.GetTimeForFrame(tl.frameIndex).ToString("0.000");
ctx.DrawText(str, tl.lineFrom + new float2(4, 2), 10, textColor);
}
}
p.Stroke();
}
///////////////////////////////////////////////////////////////////////////////////////////
void AddPointToBBox(ref Rect r, Vector2 pt)
{
r.xMin = math.min(r.xMin, pt.x);
r.xMax = math.max(r.xMax, pt.x);
r.yMin = math.min(r.yMin, pt.y);
r.yMax = math.max(r.xMax, pt.y);
}
///////////////////////////////////////////////////////////////////////////////////////////
void RestrictPoint(Rect r, ref Vector2 pt)
{
pt.x = math.max(r.xMin, pt.x);
pt.x = math.min(r.xMax, pt.x);
pt.y = math.max(r.yMin, pt.y);
pt.y = math.min(r.yMax, pt.y);
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawKnob(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
p.strokeColor = knobColor;
p.fillColor = knobColor;
p.lineJoin = LineJoin.Miter;
p.lineCap = LineCap.Butt;
var knobPosX = timelinePortal.Value.GetKnobPosX();
Vector2[] pts = new Vector2[5];
pts[0] = new Vector2(knobPosX - knobWidth / 2, contentRect.yMin);
pts[1] = new Vector2(knobPosX + knobWidth / 2, contentRect.yMin);
pts[2] = new Vector2(pts[1].x, contentRect.y + contentRect.height * 0.7f);
pts[3] = new Vector2(knobPosX, contentRect.yMax);
pts[4] = new Vector2(pts[0].x, pts[2].y);
p.BeginPath();
p.MoveTo(pts[0]);
for (var i = 1; i < pts.Length; ++i)
{
p.LineTo(pts[i]);
}
p.Fill();
p.Stroke();
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawKnobCaption(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
var frameHalfWidth = 20.0f;
var frameHeight = 10.0f;
var frameYOffset = -2.0f;
var knobPosX = timelinePortal.Value.GetKnobPosX();
p.lineJoin = LineJoin.Round;
p.lineWidth = 2;
p.BeginPath();
p.MoveTo(new Vector2(knobPosX - frameHalfWidth, contentRect.yMin - frameHeight + frameYOffset));
p.MoveTo(new Vector2(knobPosX + frameHalfWidth, contentRect.yMin - frameHeight + frameYOffset));
p.MoveTo(new Vector2(knobPosX + frameHalfWidth, contentRect.yMin - frameYOffset));
p.MoveTo(new Vector2(knobPosX - frameHalfWidth, contentRect.yMin - frameYOffset));
p.ClosePath();
p.Stroke();
}
///////////////////////////////////////////////////////////////////////////////////////////
void DrawContent(MeshGenerationContext ctx)
{
DrawTimeline(ctx);
DrawKnob(ctx);
DrawKnobCaption(ctx);
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: da95e9db11785ac46b1b2c5211d8544a
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/TimelineHeader.cs
uploadId: 897522
@@ -0,0 +1,132 @@
using System;
using Rukhanka.WaybackMachine;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
///////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Editor
{
public struct TimelinePortalData: IDisposable
{
public float frameSizeInSec;
public float2 frameRange, visibleRange;
public float contentWidth;
public int knobFrame;
public bool knobDragging;
public struct VLineDef
{
public bool majorTick;
public float2 lineFrom, lineTo;
public int frameIndex;
}
public NativeList<VLineDef> tickLines;
///////////////////////////////////////////////////////////////////////////////////////////
public void Construct()
{
tickLines = new (0xff, Allocator.Persistent);
}
///////////////////////////////////////////////////////////////////////////////////////////
public void SetKnobFromPos(float xPos)
{
var zeroFramePos = GetPosXForFrame(0);
var zeroRelativePos = xPos - zeroFramePos;
var fIndex = math.round(zeroRelativePos / OneFrameWidth());
knobFrame = (int)math.clamp(fIndex, frameRange.x, frameRange.y);
}
///////////////////////////////////////////////////////////////////////////////////////////
public float GetFrameForTime(float t) => t / frameSizeInSec;
///////////////////////////////////////////////////////////////////////////////////////////
public float GetTimeForFrame(float frame) => frame * frameSizeInSec;
///////////////////////////////////////////////////////////////////////////////////////////
public float GetFrameForPosX(float x) => GetFrameForTime(GetTimeForPosX(x));
///////////////////////////////////////////////////////////////////////////////////////////
public float GetPosXForFrame(int frameIndex)
{
var oneFrameWidth = OneFrameWidth();
var startOffset = visibleRange.x * oneFrameWidth;
var rv = frameIndex * oneFrameWidth - startOffset;
return rv;
}
///////////////////////////////////////////////////////////////////////////////////////////
public float GetKnobPosX() => GetPosXForFrame(knobFrame);
///////////////////////////////////////////////////////////////////////////////////////////
public float GetTimeForPosX(float xPos)
{
var oneFrameWidth = OneFrameWidth();
var startOffset = visibleRange.x * oneFrameWidth;
var rv = (xPos + startOffset) / oneFrameWidth * frameSizeInSec;
return rv;
}
///////////////////////////////////////////////////////////////////////////////////////////
public float OneFrameWidth()
{
var visibleFramesCount = visibleRange.y - visibleRange.x;
var oneFrameWidth = contentWidth / visibleFramesCount;
return oneFrameWidth;
}
///////////////////////////////////////////////////////////////////////////////////////////
public void ComputeTicks()
{
tickLines.Clear();
var oneFrameWidth = OneFrameWidth();
var d = WaybackMachineWindow.TIMELINE_TICKS_MIN_SKIP_SPACE / oneFrameWidth;
var skipStep = math.ceilpow2(math.max(1, (int)math.ceil(d)));
var prevStep = skipStep >> 1;
var lerpFactor = (d - prevStep) / (skipStep - prevStep) ;
var startIndex = (int)(math.floor(visibleRange.x / skipStep) * skipStep);
var endIndex = (int)math.ceil(visibleRange.y);
for (int i = startIndex; i < endIndex; i += skipStep)
{
var marked = math.select(1, 0, (i / skipStep & 1) == 0 || skipStep == 1);
var x0 = GetPosXForFrame(i);
var y0 = lerpFactor * marked;
// 0.5 offset for 1 point width lines
var lineP0 = new Vector2((int)x0 + 0.5f, y0);
var lineP1 = new Vector2(lineP0.x, 1);
var ld = new VLineDef
{
lineFrom = lineP0,
lineTo = lineP1,
majorTick = marked == 0,
frameIndex = i
};
tickLines.Add(ld);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
public void Dispose()
{
tickLines.Dispose();
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 493e4844e73f9ee45aef458639076878
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/TimelinePortalData.cs
uploadId: 897522
@@ -0,0 +1,10 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<ui:VisualElement name="background" style="flex-grow: 1; flex-direction: column; margin-top: 2px; margin-right: 5px; margin-bottom: 0; margin-left: 5px;">
<ui:Label text="Name" name="name" style="flex-grow: 0; -unity-text-align: middle-left; overflow: hidden; -unity-font-style: bold;" />
<ui:VisualElement name="animinfo" style="flex-grow: 1; flex-direction: row; align-items: flex-start;">
<ui:Label text="Weight" name="weight" style="flex-grow: 0; font-size: 12px; -unity-text-align: middle-left; overflow: hidden; flex-shrink: 0; width: 50%;" />
<ui:Label text="Time" name="time" style="flex-grow: 0; font-size: 12px; -unity-text-align: middle-left; overflow: hidden; flex-shrink: 0; width: 50%;" />
</ui:VisualElement>
<ui:VisualElement name="divider" style="flex-grow: 0; flex-shrink: 0; height: 4px; background-color: rgb(41, 41, 41);" />
</ui:VisualElement>
</ui:UXML>
@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: 41515e63c8fb4ad45ace42e817e8e9b2
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/WaybackMachineAnimInfoPane.uxml
uploadId: 897522
@@ -0,0 +1,168 @@
using System;
using System.IO;
using Rukhanka.WaybackMachine;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEditor;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Editor
{
public static class WaybackMachineDataExtensions
{
public static unsafe void SerializeToFile(this WaybackMachineData wmd, string path)
{
using var f = File.Open(path, FileMode.Create);
using var bw = new BinaryWriter(f);
bw.Write(WaybackMachineWindow.BINARY_MAGIC);
bw.Write(WaybackMachineWindow.BINARY_VERSION);
bw.Write((int)wmd.fpsMode);
bw.Write(wmd.lastRecordedFrame);
bw.Write(wmd.animHistory.Length);
foreach (var ah in wmd.animHistory)
{
bw.Write(ah.frameSpan.x);
bw.Write(ah.frameSpan.y);
bw.Write(ah.animationHash.Value.x);
bw.Write(ah.animationHash.Value.y);
bw.Write(ah.animationHash.Value.z);
bw.Write(ah.animationHash.Value.w);
bw.Write(ah.avatarMaskHash.Value.x);
bw.Write(ah.avatarMaskHash.Value.y);
bw.Write(ah.avatarMaskHash.Value.z);
bw.Write(ah.avatarMaskHash.Value.w);
bw.Write((int)ah.blendMode);
bw.Write(ah.layerWeight);
bw.Write(ah.layerIndex);
bw.Write(ah.motionId);
bw.Write(ah.historyWeights.Length);
var weightsHistorySpan = new ReadOnlySpan<byte>(ah.historyWeights.Ptr, ah.historyWeights.Length * UnsafeUtility.SizeOf<HistoryValue>());
bw.Write(weightsHistorySpan);
bw.Write(ah.historyAnimTime.Length);
var timesHistorySpan = new ReadOnlySpan<byte>(ah.historyAnimTime.Ptr, ah.historyAnimTime.Length * UnsafeUtility.SizeOf<HistoryValue>());
bw.Write(timesHistorySpan);
bw.Write(ah.animationName.Length);
var nameSpan = new ReadOnlySpan<byte>(ah.animationName.GetUnsafePtr(), ah.animationName.Length);
bw.Write(nameSpan);
}
bw.Write(wmd.controllerStateHistory.Length);
var stateHistoryDataSpan = new ReadOnlySpan<byte>(wmd.controllerStateHistory.GetUnsafePtr(),
wmd.controllerStateHistory.Length * UnsafeUtility.SizeOf<AnimatorControllerStateHistoryData>());
bw.Write(stateHistoryDataSpan);
bw.Write(wmd.controllerTransitionHistory.Length);
var transitionHistoryDataSpan = new ReadOnlySpan<byte>(wmd.controllerTransitionHistory.GetUnsafePtr(),
wmd.controllerTransitionHistory.Length * UnsafeUtility.SizeOf<AnimatorControllerTransitionHistoryData>());
bw.Write(transitionHistoryDataSpan);
bw.Write(wmd.animationEventHistory.Length);
var animationEventHistoryDataSpan = new ReadOnlySpan<byte>(wmd.animationEventHistory.GetUnsafePtr(),
wmd.animationEventHistory.Length * UnsafeUtility.SizeOf<AnimationEventHistoryData>());
bw.Write(animationEventHistoryDataSpan);
bw.Write(wmd.animatorEventHistory.Length);
var animatorEventHistoryDataSpan = new ReadOnlySpan<byte>(wmd.animatorEventHistory.GetUnsafePtr(),
wmd.animatorEventHistory.Length * UnsafeUtility.SizeOf<AnimatorEventHistoryData>());
bw.Write(animatorEventHistoryDataSpan);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static unsafe void SerializeFromFile(this ref WaybackMachineData wmd, string path)
{
using var f = File.Open(path, FileMode.Open);
using var br = new BinaryReader(f);
var binaryMagic = br.ReadUInt32();
if (binaryMagic != WaybackMachineWindow.BINARY_MAGIC)
{
EditorUtility.DisplayDialog("Rukhanka Wayback Machine", "Recorded animation data file is corrupted.", "Close");
return;
}
var binaryVersion = br.ReadInt32();
if (binaryVersion != WaybackMachineWindow.BINARY_VERSION)
{
EditorUtility.DisplayDialog("Rukhanka Wayback Machine", "Recorded animation data was made with previous version of wayback machine. Cannot load.", "Close");
return;
}
wmd.Clear();
wmd.fpsMode = (WaybackMachineData.FPSMode)br.ReadInt32();
wmd.lastRecordedFrame = br.ReadInt32();
var animHistoryLen = br.ReadInt32();
for (var i = 0; i < animHistoryLen; ++i)
{
var ah = new AnimationHistoryData();
ah.frameSpan.x = br.ReadInt32();
ah.frameSpan.y = br.ReadInt32();
ah.animationHash.Value.x = br.ReadUInt32();
ah.animationHash.Value.y = br.ReadUInt32();
ah.animationHash.Value.z = br.ReadUInt32();
ah.animationHash.Value.w = br.ReadUInt32();
ah.avatarMaskHash.Value.x = br.ReadUInt32();
ah.avatarMaskHash.Value.y = br.ReadUInt32();
ah.avatarMaskHash.Value.z = br.ReadUInt32();
ah.avatarMaskHash.Value.w = br.ReadUInt32();
ah.blendMode = (AnimationBlendingMode)br.ReadInt32();
ah.layerWeight = br.ReadSingle();
ah.layerIndex = br.ReadInt32();
ah.motionId = br.ReadUInt32();
var weightHistoryLen = br.ReadInt32();
ah.historyWeights = new (weightHistoryLen, Allocator.Persistent);
ah.historyWeights.Resize(weightHistoryLen);
var weightsHistorySpan = new Span<byte>(ah.historyWeights.Ptr, ah.historyWeights.Length * UnsafeUtility.SizeOf<HistoryValue>());
br.Read(weightsHistorySpan);
var timesHistoryLen = br.ReadInt32();
ah.historyAnimTime = new (timesHistoryLen, Allocator.Persistent);
ah.historyAnimTime.Resize(timesHistoryLen, NativeArrayOptions.ClearMemory);
var timesHistorySpan = new Span<byte>(ah.historyAnimTime.Ptr, ah.historyAnimTime.Length * UnsafeUtility.SizeOf<HistoryValue>());
br.Read(timesHistorySpan);
var nameLen = br.ReadInt32();
ah.animationName.TryResize(nameLen);
var nameSpan = new Span<byte>(ah.animationName.GetUnsafePtr(), ah.animationName.Length);
br.Read(nameSpan);
wmd.animHistory.Add(ah);
}
var stateHistoryLen = br.ReadInt32();
wmd.controllerStateHistory.Resize(stateHistoryLen, NativeArrayOptions.ClearMemory);
var stateHistoryDataSpan = new Span<byte>(wmd.controllerStateHistory.GetUnsafePtr(),
wmd.controllerStateHistory.Length * UnsafeUtility.SizeOf<AnimatorControllerStateHistoryData>());
br.Read(stateHistoryDataSpan);
var transitionHistoryLen = br.ReadInt32();
wmd.controllerTransitionHistory.Resize(transitionHistoryLen, NativeArrayOptions.ClearMemory);
var transitionHistoryDataSpan = new Span<byte>(wmd.controllerTransitionHistory.GetUnsafePtr(),
wmd.controllerTransitionHistory.Length * UnsafeUtility.SizeOf<AnimatorControllerTransitionHistoryData>());
br.Read(transitionHistoryDataSpan);
var animationEventHistoryLen = br.ReadInt32();
wmd.animationEventHistory.Length = animationEventHistoryLen;
var animationEventHistoryDataSpan = new Span<byte>(wmd.animationEventHistory.GetUnsafePtr(),
wmd.animationEventHistory.Length * UnsafeUtility.SizeOf<AnimationEventHistoryData>());
br.Read(animationEventHistoryDataSpan);
var animatorEventHistoryLen = br.ReadInt32();
wmd.animatorEventHistory.Length = animatorEventHistoryLen;
var animatorEventHistoryDataSpan = new Span<byte>(wmd.animatorEventHistory.GetUnsafePtr(),
wmd.animatorEventHistory.Length * UnsafeUtility.SizeOf<AnimatorEventHistoryData>());
br.Read(animatorEventHistoryDataSpan);
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: bcf4cf6fc0f882744a5a15cd6341fd16
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/WaybackMachineDataExtensions.cs
uploadId: 897522
@@ -0,0 +1,9 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<ui:VisualElement name="background" style="flex-grow: 1; visibility: visible; flex-shrink: 0;">
<ui:VisualElement name="eventinfo" style="flex-grow: 1; margin-left: 5px; margin-top: 5px; margin-right: 5px; margin-bottom: 5px; height: 12px; flex-direction: row; align-items: center; overflow: hidden; justify-content: space-evenly;">
<ui:Label text="Name" name="name" style="padding-left: 10px; flex-grow: 0; font-size: 12px; -unity-text-align: middle-left; overflow: hidden; width: 50%; -unity-font-style: bold; -unity-text-generator: standard;"/>
<ui:Label name="stringv" text="Value" style="padding-left: 10px; flex-grow: 0; font-size: 12px; -unity-text-align: middle-left; flex-shrink: 0; width: 50%;"/>
</ui:VisualElement>
<ui:VisualElement name="divider-low" style="flex-grow: 0; flex-shrink: 0; height: 4px; background-color: rgb(41, 41, 41);"/>
</ui:VisualElement>
</ui:UXML>
@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: 81d64e961a6c95b4caa6115d4ea8dc1e
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/WaybackMachineEventInfoPane.uxml
uploadId: 897522
@@ -0,0 +1,17 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<ui:VisualElement name="timeline" style="flex-shrink: 0; height: 21px; border-left-color: rgb(0, 0, 0); border-right-color: rgb(0, 0, 0); border-top-color: rgb(0, 0, 0); border-bottom-color: rgb(0, 152, 170); border-bottom-width: 1px;"/>
<ui:VisualElement name="background" style="flex-grow: 1;">
<ui:Label text="Events &#9660;" name="eventsheader" style="-unity-text-align: middle-center; height: 30px; font-size: 16px; background-color: rgb(41, 41, 41); flex-direction: row-reverse; padding-right: 0; padding-left: 0;">
<ui:Button name="settings" focusable="false" style="margin-top: 3px; margin-right: 3px; margin-bottom: 3px; margin-left: 3px; padding-right: 3px; padding-left: 3px;"/>
</ui:Label>
<ui:VisualElement name="eventsbody" style="background-color: rgba(197, 83, 83, 0); flex-shrink: 0;"/>
<ui:Label text="State Machine States &#9660;" name="smheader" style="-unity-text-align: middle-center; height: 30px; font-size: 16px; background-color: rgb(41, 41, 41); flex-direction: row-reverse; padding-right: 0; padding-left: 0;">
<ui:Button name="settings" style="display: none;"/>
</ui:Label>
<ui:VisualElement name="smbody" style="flex-grow: 0; flex-shrink: 0; background-color: rgba(172, 255, 214, 0);"/>
<ui:Label text="Animations &#9660;" name="animheader" style="-unity-text-align: middle-center; height: 30px; font-size: 16px; background-color: rgb(41, 41, 41); flex-direction: row-reverse; padding-right: 0; padding-left: 0;">
<ui:Button name="settings" focusable="false" style="margin-top: 3px; margin-right: 3px; margin-bottom: 3px; margin-left: 3px; padding-right: 3px; padding-left: 3px;"/>
</ui:Label>
<ui:VisualElement name="animbody" style="flex-grow: 0; flex-shrink: 0; background-color: rgba(172, 255, 214, 0);"/>
</ui:VisualElement>
</ui:UXML>
@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: 0625fcf33fc6f894abb1ceb8e0eed94b
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/WaybackMachineLeftPane.uxml
uploadId: 897522
@@ -0,0 +1,14 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<uie:Toolbar name="toolbar" style="height: 21px;">
<uie:ToolbarButton text="Preview" name="preview-btn" focusable="false" />
<uie:ToolbarButton name="record-btn" focusable="false" style="flex-shrink: 0; width: 25px;" />
<uie:ToolbarBreadcrumbs name="entity-path" />
<uie:ToolbarSpacer style="flex-grow: 0; width: 50px;" />
<uie:ToolbarButton name="import-btn" focusable="false" style="padding-left: 2px; padding-right: 2px;" />
<uie:ToolbarButton name="save-btn" focusable="false" style="padding-left: 2px; padding-right: 2px;" />
<ui:Label text="Memory size " double-click-selects-word="false" triple-click-selects-line="false" name="memorystat" style="-unity-text-align: middle-left; padding-left: 10px; flex-grow: 1;" />
<uie:ToolbarSpacer style="flex-grow: 1;" />
<ui:Label double-click-selects-word="false" triple-click-selects-line="false" name="nodebuginfowarning" style="-unity-text-align: middle-right; padding-left: 10px; flex-grow: 1;" />
<uie:ToolbarButton name="settings-btn" focusable="false" style="flex-shrink: 0; width: 25px;" />
</uie:Toolbar>
</ui:UXML>
@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: b8f9259b827427d4bababecc0ca4b088
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/WaybackMachineMainWindow.uxml
uploadId: 897522
@@ -0,0 +1,6 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<ui:VisualElement name="timeline" style="flex-shrink: 0; height: 21px; border-left-color: rgb(0, 0, 0); border-right-color: rgb(0, 0, 0); border-top-color: rgb(0, 0, 0); border-bottom-color: rgb(0, 152, 170); border-bottom-width: 1px;" />
<ui:Label text="Label" name="text-measurer" style="visibility: visible; display: none;" />
<ui:VisualElement name="content" style="flex-grow: 1; flex-direction: column-reverse; background-color: rgb(41, 41, 41);" />
<ui:MinMaxSlider value="0,1000" low-limit="0" high-limit="1000" name="slider" />
</ui:UXML>
@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: 8e219e54165d54b47bebd4ae11ea57ac
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/WaybackMachineRightPane.uxml
uploadId: 897522
@@ -0,0 +1,43 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Editor
{
public struct WaybackMachineSettings
{
public enum RulerMode
{
Seconds,
Frames
}
public RulerMode rulerMode;
public int statesVisible;
public int animationsVisible;
public int eventsVisible;
public bool IsEventsVisible() => eventsVisible != 0;
public bool IsAnimationsVisible() => animationsVisible != 0;
public bool IsStatesVisible() => statesVisible != 0;
public bool eventLabels;
public bool animationWeightGraphs;
public bool animationTimeGraphs;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static WaybackMachineSettings MakeDefault()
{
var rv = new WaybackMachineSettings()
{
statesVisible = 1,
animationsVisible = 1,
eventsVisible = 1,
rulerMode = RulerMode.Frames,
animationTimeGraphs = false,
eventLabels = true,
animationWeightGraphs = false,
};
return rv;
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 5bf4ff83b9936bf4b9a53a5bb1f40e5a
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/WaybackMachineSettings.cs
uploadId: 897522
@@ -0,0 +1,20 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<ui:VisualElement name="background" style="flex-grow: 1; flex-direction: column; margin-top: 2px; padding-left: 5px;">
<ui:VisualElement name="srcstateinfo" style="flex-grow: 1; flex-direction: row; align-items: center;">
<ui:VisualElement name="color" style="width: 14px; height: 14px; background-color: rgb(0, 255, 0); flex-shrink: 0;" />
<ui:Label text="Name" name="name" style="padding-left: 10px; flex-grow: 1; font-size: 12px; -unity-text-align: middle-left; overflow: hidden; -unity-font-style: bold;" />
<ui:Label text="Id" name="id" style="padding-left: 10px; flex-grow: 0; font-size: 12px; -unity-text-align: middle-left; width: 30px;" />
</ui:VisualElement>
<ui:VisualElement name="dststateinfo" style="flex-grow: 1; flex-direction: row; align-items: center;">
<ui:VisualElement name="color" style="width: 14px; height: 14px; background-color: rgb(0, 255, 0); flex-shrink: 0;" />
<ui:Label text="Name" name="name" style="padding-left: 10px; flex-grow: 1; font-size: 12px; -unity-text-align: middle-left; overflow: hidden; -unity-font-style: bold;" />
<ui:Label text="Id" name="id" style="padding-left: 10px; flex-grow: 0; font-size: 12px; -unity-text-align: middle-left; width: 30px;" />
</ui:VisualElement>
<ui:VisualElement name="transitioninfo" style="flex-grow: 1; flex-direction: row;">
<ui:VisualElement name="color" style="width: 14px; height: 14px; background-color: rgba(0, 255, 0, 0); flex-shrink: 0;" />
<ui:Label text="Name" name="name" style="padding-left: 10px; flex-grow: 1; font-size: 12px; -unity-text-align: middle-left; overflow: hidden; -unity-font-style: bold;" />
<ui:Label text="Id" name="id" style="padding-left: 10px; flex-grow: 0; font-size: 12px; -unity-text-align: middle-left; width: 30px;" />
</ui:VisualElement>
<ui:VisualElement name="divider" style="flex-grow: 0; flex-shrink: 0; height: 4px; background-color: rgb(41, 41, 41);" />
</ui:VisualElement>
</ui:UXML>
@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: ac25a97ac8035ef4aa39a33fc05a4031
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/WaybackMachineStateInfoPane.uxml
uploadId: 897522
@@ -0,0 +1,907 @@
using System;
using System.Collections.Generic;
using Rukhanka.Toolbox;
using Rukhanka.WaybackMachine;
using Unity.Assertions;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Editor
{
public partial class WaybackMachineWindow: EditorWindow
{
[SerializeField]
private VisualTreeAsset windowAsset = default;
[SerializeField]
private VisualTreeAsset leftPaneAsset = default;
[SerializeField]
private VisualTreeAsset rightPaneAsset = default;
[SerializeField]
private VisualTreeAsset stateInfoAsset = default;
[SerializeField]
private VisualTreeAsset eventInfoAsset = default;
[SerializeField]
private VisualTreeAsset animInfoAsset = default;
TwoPaneSplitView splitView;
MinMaxSlider minMaxSlider;
TimelineHeader timelineHeader;
TimelineContent timelineContent;
NativeReference<TimelinePortalData> timelinePortal;
VisualElement recordBtn;
ToolbarBreadcrumbs entityPath;
ToolbarButton entityPathWorld, entityPathEntity;
ToolbarButton globalSettingsButton;
ToolbarButton previewBtn;
Label memoryStat;
ToolbarButton saveBtn;
VisualElement leftPane, rightPane;
Label statesHeader;
VisualElement statesBarBodyVE;
Button stateSettingsButton;
Label animationsHeader;
VisualElement animBarBodyVE;
Button animationSettingsButton;
Label eventsHeader;
VisualElement eventsBarBodyVE;
Button eventSettingsButton;
List<VisualElement> stateInfoWidgets = new ();
List<VisualElement> eventInfoWidgets = new ();
List<VisualElement> animInfoWidgets = new ();
int selectedWorldIndex = -1;
Entity selectedEntity = Entity.Null;
Action<PlayModeStateChange> playModeChangeFn;
NativeReference<WaybackMachineData> recordedData;
EntityQuery recordSingletonEq, playbackSingletonEq, animationDatabaseSingletonEq;
int prevKnobFrame = 1000000;
WaybackMachineSettings settings;
public const string iconPath = "Packages/com.rukhanka.animation/Rukhanka.Editor/Editor Default Resources/Icons/RukhankaWaybackMachine@16.png";
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[MenuItem("Window/Rukhanka Animation/Animation Wayback Machine")]
public static void ShowExample()
{
var wnd = GetWindow<WaybackMachineWindow>();
var icon = AssetDatabase.LoadAssetAtPath(iconPath, typeof(Texture)) as Texture;
wnd.titleContent = new GUIContent("Rukhanka Wayback Machine", icon);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SetupIconImage(Button button, Background img)
{
#if !UNITY_2023_2_OR_NEWER
var icon = button.Q<Image>();
if(icon == null)
button.Add(icon = new Image() {name = "LegacyIcon" });
icon.image = img.texture;
#else
button.iconImage = img;
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public unsafe void CreateGUI()
{
playModeChangeFn = (playModeStateChange) =>
{
PlayModeStateChanged(this);
};
EditorApplication.playModeStateChanged += playModeChangeFn;
settings = WaybackMachineSettings.MakeDefault();
recordedData = new (Allocator.Persistent);
recordedData.GetUnsafePtr()->Construct();
timelinePortal = new (Allocator.Persistent);
timelinePortal.GetUnsafePtr()->Construct();
// Each editor window contains a root VisualElement object
var root = rootVisualElement;
var doc = windowAsset.Instantiate();
root.Add(doc);
splitView = new TwoPaneSplitView(0, 250, TwoPaneSplitViewOrientation.Horizontal);
splitView.RegisterCallback<GeometryChangedEvent>(OnGeometryChange);
root.Add(splitView);
leftPane = leftPaneAsset.Instantiate();
rightPane = rightPaneAsset.Instantiate();
splitView.Add(leftPane);
splitView.Add(rightPane);
var tlh = rightPane.Q<VisualElement>("timeline");
timelineHeader = new TimelineHeader();
timelineHeader.timelinePortal = timelinePortal;
tlh.Add(timelineHeader);
var gearIconBackground = new Background() { texture = (Texture2D)EditorGUIUtility.IconContent("_Popup").image };
animBarBodyVE = leftPane.Q<VisualElement>("animbody");
animationsHeader = leftPane.Q<Label>("animheader");
animationSettingsButton = animationsHeader.Q<Button>("settings");
SetupIconImage(animationSettingsButton, gearIconBackground);
animationSettingsButton.clickable.clicked += SelectAnimationOptions;
animationsHeader.RegisterCallback<MouseDownEvent>(_ => ToggleVisibility(ref settings.animationsVisible, animationsHeader, animBarBodyVE));
statesBarBodyVE = leftPane.Q<VisualElement>("smbody");
statesHeader = leftPane.Q<Label>("smheader");
stateSettingsButton = statesHeader.Q<Button>("settings");
SetupIconImage(stateSettingsButton, gearIconBackground);
statesHeader.RegisterCallback<MouseDownEvent>(_ => ToggleVisibility(ref settings.statesVisible, statesHeader, statesBarBodyVE));
eventsBarBodyVE = leftPane.Q<VisualElement>("eventsbody");
eventsHeader = leftPane.Q<Label>("eventsheader");
eventSettingsButton = eventsHeader.Q<Button>("settings");
SetupIconImage(eventSettingsButton, gearIconBackground);
eventSettingsButton.clickable.clicked += SelectEventOptions;
eventsHeader.RegisterCallback<MouseDownEvent>(_ => ToggleVisibility(ref settings.eventsVisible, eventsHeader, eventsBarBodyVE));
var tlc = rightPane.Q<VisualElement>("content");
timelineContent = new TimelineContent();
timelineContent.timelinePortal = timelinePortal;
timelineContent.recordedData = recordedData;
timelineContent.textMeasurer = rightPane.Q<Label>("text-measurer");
timelineContent.RegisterCallback<WheelEvent>(MouseScrollOnTimeline);
tlc.Add(timelineContent);
minMaxSlider = rightPane.Q<MinMaxSlider>("slider");
minMaxSlider.minValue = minMaxSlider.lowLimit;
minMaxSlider.maxValue = minMaxSlider.highLimit;
recordBtn = doc.Q<ToolbarButton>("record-btn");
recordBtn.generateVisualContent += DrawRecordBtn;
recordBtn.RegisterCallback<ClickEvent>(ToggleRecording);
entityPath = doc.Q<ToolbarBreadcrumbs>("entity-path");
entityPath.PushItem(DEFAULT_WORLD_TEXT);
entityPathWorld = entityPath[0] as ToolbarButton;
entityPathWorld.RegisterCallback<MouseDownEvent>(SelectWorldMenu, TrickleDown.TrickleDown);
entityPath.PushItem(DEFAULT_ENTITY_TEXT);
entityPathEntity = entityPath[1] as ToolbarButton;
entityPathEntity.RegisterCallback<MouseDownEvent>(SelectEntityMenu, TrickleDown.TrickleDown);
globalSettingsButton = doc.Q<ToolbarButton>("settings-btn");
SetupIconImage(globalSettingsButton, gearIconBackground);
globalSettingsButton.RegisterCallback<MouseDownEvent>(SelectGlobalOptions, TrickleDown.TrickleDown);
previewBtn = doc.Q<ToolbarButton>("preview-btn");
previewBtn.RegisterCallback<ClickEvent>(TogglePreview, TrickleDown.TrickleDown);
saveBtn = doc.Q<ToolbarButton>("save-btn");
var saveIcon = new Background() { texture = (Texture2D)EditorGUIUtility.IconContent("SaveAs").image };
SetupIconImage(saveBtn, saveIcon);
saveBtn.clickable.clicked += SaveRecordedData;
var importBtn = doc.Q<ToolbarButton>("import-btn");
var importIcon = new Background() { texture = (Texture2D)EditorGUIUtility.IconContent("Import").image };
SetupIconImage(importBtn, importIcon);
importBtn.clickable.clicked += LoadRecordedData;
memoryStat = doc.Q<Label>("memorystat");
#if !RUKHANKA_DEBUG_INFO
var noDebugInfoWarningLabel = doc.Q<Label>("nodebuginfowarning");
noDebugInfoWarningLabel.text = "Enable 'Debug and Validation Mode' for object names";
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void PlayModeStateChanged(WaybackMachineWindow w)
{
if (w != null)
w.ResetEntityPath();
w.DisposeEntityQueries();
w.StopRecord();
w.StopPreview();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe void DisposeEntityQueries()
{
if (animationDatabaseSingletonEq.__impl != null)
animationDatabaseSingletonEq.Dispose();
if (playbackSingletonEq.__impl != null)
playbackSingletonEq.Dispose();
if (recordSingletonEq.__impl != null)
recordSingletonEq.Dispose();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ResetEntityPath()
{
selectedWorldIndex = -1;
selectedEntity = Entity.Null;
entityPathEntity.text = DEFAULT_ENTITY_TEXT;
entityPathWorld.text = DEFAULT_WORLD_TEXT;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
World GetSelectedWorld()
{
if (selectedWorldIndex < 0 || selectedWorldIndex >= World.All.Count)
return null;
return World.All[selectedWorldIndex];
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
string GetEntityName(EntityManager em, Entity e)
{
var eName = $"{em.GetName(e)} {e}";
return eName;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SelectEntityMenu(MouseDownEvent evt)
{
var world = GetSelectedWorld();
if (world == null)
return;
var eq = new EntityQueryBuilder(Allocator.Temp)
.WithAll<RigDefinitionComponent>()
.Build(world.EntityManager);
if (eq.IsEmpty)
return;
var entities = eq.ToEntityArray(Allocator.Temp);
var m = new GenericDropdownMenu();
for (var i = 0; i < entities.Length; ++i)
{
var e = entities[i];
var eName = GetEntityName(world.EntityManager, e);
m.AddItem(eName, false, _ => SelectEntity(e), null);
}
DropDown(m, entityPathEntity);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SelectAnimationOptions()
{
var m = new GenericDropdownMenu();
m.AddItem("Time Graphs", settings.animationTimeGraphs, _ => settings.animationTimeGraphs = !settings.animationTimeGraphs, null);
m.AddItem("Weight Graphs", settings.animationWeightGraphs, _ => settings.animationWeightGraphs = !settings.animationWeightGraphs, null);
DropDown(m, animationSettingsButton);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SelectEventOptions()
{
var m = new GenericDropdownMenu();
m.AddItem("Event Labels", settings.eventLabels, _ => settings.eventLabels = !settings.eventLabels, null);
DropDown(m, eventSettingsButton);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SelectGlobalOptions(MouseDownEvent evt)
{
var m = new GenericDropdownMenu();
m.AddItem("Seconds", settings.rulerMode == WaybackMachineSettings.RulerMode.Seconds, _ => settings.rulerMode = WaybackMachineSettings.RulerMode.Seconds, null);
m.AddItem("Frames", settings.rulerMode == WaybackMachineSettings.RulerMode.Frames, _ => settings.rulerMode = WaybackMachineSettings.RulerMode.Frames, null);
m.AddSeparator("Sample Rate");
m.AddItem("120 FPS", recordedData.Value.fpsMode == WaybackMachineData.FPSMode.FPS120, _ => ChangeFrameDuration(WaybackMachineData.FPSMode.FPS120), null);
m.AddItem("60 FPS", recordedData.Value.fpsMode == WaybackMachineData.FPSMode.FPS60, _ => ChangeFrameDuration(WaybackMachineData.FPSMode.FPS60), null);
m.AddItem("30 FPS", recordedData.Value.fpsMode == WaybackMachineData.FPSMode.FPS30, _ => ChangeFrameDuration(WaybackMachineData.FPSMode.FPS30), null);
DropDown(m, globalSettingsButton);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SelectWorldMenu(MouseDownEvent evt)
{
var m = new GenericDropdownMenu();
for (var i = 0; i < World.All.Count; ++i)
{
var world = World.All[i];
var worldIndex = i;
m.AddItem(world.Name, false, _ => SelectWorld(worldIndex), null);
}
DropDown(m, entityPathWorld);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SelectEntity(Entity e)
{
var world = GetSelectedWorld();
if (world == null)
return;
var eName = GetEntityName(world.EntityManager, e);
entityPathEntity.text = eName;
selectedEntity = e;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SelectWorld(int worldIndex)
{
var prevWorldIndex = selectedWorldIndex;
var world = World.All[worldIndex];
selectedWorldIndex = worldIndex;
entityPathWorld.text = world.Name;
// Create queries
recordSingletonEq = new EntityQueryBuilder(Allocator.Temp)
.WithAll<RecordComponent>()
.Build(world.EntityManager);
playbackSingletonEq = new EntityQueryBuilder(Allocator.Temp)
.WithAllRW<PlaybackComponent>()
.Build(world.EntityManager);
animationDatabaseSingletonEq = new EntityQueryBuilder(Allocator.Temp)
.WithAll<BlobDatabaseSingleton>()
.Build(world.EntityManager);
if (prevWorldIndex != worldIndex)
{
// Reset entity
selectedEntity = Entity.Null;
entityPathEntity.text = DEFAULT_ENTITY_TEXT;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void OnGeometryChange(GeometryChangedEvent evt)
{
Update();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void MouseScrollOnTimeline(WheelEvent evt)
{
// Make scroll amount constant in screen space
var minT = timelinePortal.Value.GetFrameForPosX(0);
var maxT = timelinePortal.Value.GetFrameForPosX(evt.delta.y * TIMELINE_MOUSE_SCROLL_SPEED);
var dT = maxT - minT;
var f = (evt.mousePosition.x - timelineContent.worldBound.xMin) / timelineContent.worldBound.width;
minMaxSlider.minValue -= dT * f;
minMaxSlider.maxValue += dT * (1 - f);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe void ChangeFrameDuration(WaybackMachineData.FPSMode newMode)
{
if (recordedData.Value.fpsMode == newMode)
return;
// FPS cannot be changed for existing recordings, so warn user about recorded data reset
if (recordedData.Value.lastRecordedFrame > 0 && !EditorUtility.DisplayDialog("Wayback Machine", "Sample rate cannot be changed for existing recording. Confirm to clear recorded data.", "OK", "Cancel"))
return;
recordedData.GetUnsafePtr()->Clear();
recordedData.GetUnsafePtr()->fpsMode = newMode;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe void Update()
{
if (recordedData.IsCreated && recordedData.Value.lastRecordedFrame > 0)
{
var numFrames = recordedData.Value.lastRecordedFrame;
if (numFrames > minMaxSlider.highLimit)
{
var dh = numFrames - minMaxSlider.highLimit;
minMaxSlider.highLimit = numFrames;
var v = minMaxSlider.value;
v.x += dh;
minMaxSlider.value = v;
}
if (IsRecording() && numFrames > minMaxSlider.value.y)
{
var v = minMaxSlider.value;
v.y = numFrames;
minMaxSlider.value = v;
}
}
if (minMaxSlider.value.y - minMaxSlider.value.x < 4)
{
var v = minMaxSlider.value;
v.y = v.x + 4;
minMaxSlider.value = v;
}
timelinePortal.GetUnsafePtr()->frameSizeInSec = recordedData.Value.GetFrameDuration();
timelinePortal.GetUnsafePtr()->visibleRange = minMaxSlider.value;
timelinePortal.GetUnsafePtr()->frameRange = new Vector2(minMaxSlider.lowLimit, minMaxSlider.highLimit);
timelinePortal.GetUnsafePtr()->contentWidth = timelineHeader.contentRect.width;
timelinePortal.GetUnsafePtr()->ComputeTicks();
timelineContent.animHeaderHeight = animationsHeader.localBound.height;
timelineContent.eventsHeaderHeight = eventsHeader.localBound.height;
timelineContent.statesHeaderHeight = statesHeader.localBound.height;
timelineContent.eventBarsCount = 0;
timelineContent.animBarsCount = 0;
timelineContent.stateBarsCount = 0;
timelineHeader.settings = settings;
timelineContent.settings = settings;
timelineContent.ComputeShapes();
UpdateInfoWidgetCounts();
timelineHeader.MarkDirtyRepaint();
timelineContent.MarkDirtyRepaint();
UpdatePreview();
UpdateKnobTimeInfoPanes();
UpdateMemoryStat();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DrawRecordBtn(MeshGenerationContext ctx)
{
var p = ctx.painter2D;
var r = math.min(recordBtn.contentRect.width, recordBtn.contentRect.height) / 2 * 0.8f;
var c = recordBtn.contentRect.center;
p.strokeColor = Color.white;
p.BeginPath();
p.Arc(c, r, Angle.Degrees(0), Angle.Degrees(360.0f));
p.Stroke();
if (IsRecording())
{
p.fillColor = Color.red;
p.BeginPath();
p.Arc(c, r * 0.6f, Angle.Degrees(0), Angle.Degrees(360.0f));
p.Fill();
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ToggleVisibility(ref int visStatus, Label header, VisualElement body)
{
visStatus = visStatus ^ 1;
var s = header.text;
var arrowChar = visStatus != 0 ? '▼' : '◀';
s = $"{s.Substring(0, s.Length - 1)}{arrowChar}";
header.text = s;
body.style.display = new StyleEnum<DisplayStyle>(visStatus != 0 ? DisplayStyle.Flex : DisplayStyle.None);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ToggleRecording(ClickEvent evt)
{
if (IsRecording())
StopRecord();
else
StartRecord();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TogglePreview(ClickEvent evt)
{
if (IsPreview())
StopPreview();
else
StartPreview();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void StartRecord()
{
var world = GetSelectedWorld();
if (world == null || selectedEntity == Entity.Null)
return;
timelineContent.isRecording = true;
entityPath.SetEnabled(false);
minMaxSlider.value = new Vector2(0, 1000);
var isRecording = IsRecording();
Assert.IsFalse(isRecording);
// Adjust recording system rate manager
var sg = world.GetExistingSystemManaged<WaybackMachineRecordSystemGroup>();
if (sg != null)
sg.RateManager.Timestep = recordedData.Value.GetFrameDuration();
recordedData.Value.Clear();
var rc = new RecordComponent { wbData = recordedData };
world.EntityManager.AddComponentData(selectedEntity, rc);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void StopRecord()
{
timelineContent.isRecording = false;
entityPath.SetEnabled(true);
var isRecording = IsRecording();
if (!isRecording)
return;
var world = GetSelectedWorld();
world.EntityManager.RemoveComponent<RecordComponent>(selectedEntity);
recordBtn.MarkDirtyRepaint();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void UpdateInfoWidgetCounts()
{
if (settings.IsEventsVisible())
UpdateInfoWidgetCount(eventInfoWidgets, timelineContent.eventBarsCount, EVENT_ROW_HEIGHT, eventInfoAsset, eventsBarBodyVE);
if (settings.IsStatesVisible())
UpdateInfoWidgetCount(stateInfoWidgets, timelineContent.stateBarsCount, STATE_ROW_HEIGHT, stateInfoAsset, statesBarBodyVE);
if (settings.IsAnimationsVisible())
UpdateInfoWidgetCount(animInfoWidgets, timelineContent.animBarsCount, ANIMATION_ROW_HEIGHT, animInfoAsset, animBarBodyVE);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void UpdateInfoWidgetCount(List<VisualElement> elementsArr, int newCount, float widgetHeight, VisualTreeAsset template, VisualElement parent)
{
if (elementsArr.Count == newCount)
return;
// Add if to few
var l = elementsArr.Count;
for (var i = l; i < newCount; ++i)
{
var siw = template.Instantiate();
var bkg = siw.Q<VisualElement>("background");
bkg.style.height = new StyleLength(widgetHeight);
bkg.visible = false;
parent.Add(bkg);
elementsArr.Add(bkg);
}
// Remove if too many
for (var i = newCount; i < l; ++i)
{
var siw = elementsArr[i];
parent.Remove(siw);
}
var countToRemove = l - newCount;
if (countToRemove > 0)
elementsArr.RemoveRange(newCount, countToRemove);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void UpdateKnobTimeInfoPanes()
{
var knobPos = timelinePortal.Value.GetKnobPosX();
var knobFrame = timelinePortal.Value.knobFrame;
if (prevKnobFrame == knobFrame)
return;
foreach (var eiw in eventInfoWidgets) eiw.visible = false;
foreach (var siw in stateInfoWidgets)
{
foreach (var c in siw.Children()) c.style.visibility = StyleKeyword.Null;
siw.visible = false;
}
foreach (var aiw in animInfoWidgets) aiw.visible = false;
UpdateKnobTimeAnimationEventInfos(knobPos);
UpdateKnobTimeAnimatorEventInfos(knobPos);
UpdateKnobTimeStateInfos(knobPos);
UpdateKnobTimeStateTransitionInfos(knobPos);
UpdateKnobTimeAnimationInfos(knobPos, knobFrame);
prevKnobFrame = knobFrame;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void UpdateKnobTimeAnimationInfos(float knobPos, int knobFrame)
{
NativeList<int2> tmpIndicesList = new (0xff, Allocator.Temp);
timelineContent.GetAnimationShapeIndicesForPos(ref tmpIndicesList, knobPos);
for (var i = 0; i < tmpIndicesList.Length; ++i)
{
var indexRow = tmpIndicesList[i];
var h = recordedData.Value.animHistory[indexRow.x];
var w = animInfoWidgets[indexRow.y];
w.Q<Label>("name").text = h.GetName();
var relativeKnobFrame = knobFrame - h.frameSpan.x;
var weight = h.historyWeights[relativeKnobFrame].value;
var weightColor = ColorTools.ToWebColor(ANIMATION_HISTORY_WEIGHT_LINE_COLOR);
w.Q<Label>("weight").text = $"<color={weightColor}>Weight</color>: {weight:0.##}";
var time = h.historyAnimTime[relativeKnobFrame].value;
var timeColor = ColorTools.ToWebColor(ANIMATION_HISTORY_TIME_LINE_COLOR);
w.Q<Label>("time").text = $"<color={timeColor}>Time</color>: {time:0.##}";
w.visible = true;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void UpdateKnobTimeStateInfos(float knobPos)
{
NativeList<int3> tmpIndicesList = new (0xff, Allocator.Temp);
timelineContent.GetStateShapeIndicesForPos(ref tmpIndicesList, knobPos);
for (var i = 0; i < tmpIndicesList.Length; ++i)
{
var indexRowColor = tmpIndicesList[i];
var h = recordedData.Value.controllerStateHistory[indexRowColor.x];
var w = stateInfoWidgets[indexRowColor.y];
var srcStateInfo = w.Q<VisualElement>("srcstateinfo");
var dstStateInfo = w.Q<VisualElement>("dststateinfo");
var transitionInfo = w.Q<VisualElement>("transitioninfo");
dstStateInfo.visible = false;
transitionInfo.visible = false;
var color = ColorTools.FromInt(indexRowColor.z);
srcStateInfo.Q<VisualElement>("color").style.backgroundColor = new StyleColor(color);
srcStateInfo.Q<Label>("name").text = h.GetName();
srcStateInfo.Q<Label>("id").text = h.stateId.ToString();
w.visible = true;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void UpdateKnobTimeStateTransitionInfos(float knobPos)
{
NativeList<int4> tmpIndicesList = new (0xff, Allocator.Temp);
timelineContent.GetTransitionShapeIndicesForPos(ref tmpIndicesList, knobPos);
for (var i = 0; i < tmpIndicesList.Length; ++i)
{
var indexRowColor = tmpIndicesList[i];
var h = recordedData.Value.controllerTransitionHistory[indexRowColor.x];
var w = stateInfoWidgets[indexRowColor.y];
var srcStateInfo = w.Q<VisualElement>("srcstateinfo");
var dstStateInfo = w.Q<VisualElement>("dststateinfo");
var transitionInfo = w.Q<VisualElement>("transitioninfo");
var srcColor = ColorTools.FromInt(indexRowColor.z);
var srcState = recordedData.Value.controllerStateHistory[h.srcStateDataIndex];
srcStateInfo.visible = true;
srcStateInfo.Q<VisualElement>("color").style.backgroundColor = new StyleColor(srcColor);
srcStateInfo.Q<Label>("name").text = srcState.GetName();
srcStateInfo.Q<Label>("id").text = h.srcStateId.ToString();
var dstColor = ColorTools.FromInt(indexRowColor.w);
var dstState = recordedData.Value.controllerStateHistory[h.dstStateDataIndex];
dstStateInfo.visible = true;
dstStateInfo.Q<VisualElement>("color").style.backgroundColor = new StyleColor(dstColor);
dstStateInfo.Q<Label>("name").text = dstState.GetName();
dstStateInfo.Q<Label>("id").text = h.dstStateId.ToString();
transitionInfo.visible = true;
transitionInfo.Q<Label>("name").text = h.GetName();
transitionInfo.Q<Label>("id").text = h.transitionId.ToString();
w.visible = true;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void UpdateKnobTimeAnimationEventInfos(float knobPos)
{
NativeList<int2> tmpIndicesList = new (0xff, Allocator.Temp);
timelineContent.GetAnimationEventShapeIndicesForPos(ref tmpIndicesList, knobPos);
for (var i = 0; i < tmpIndicesList.Length; ++i)
{
var indexAndRow = tmpIndicesList[i];
var h = recordedData.Value.animationEventHistory[indexAndRow.x];
var w = eventInfoWidgets[indexAndRow.y];
w.Q<Label>("name").text = h.GetName();
w.Q<Label>("stringv").text = $"<color=green>S</color>: '{h.GetStringParam()}' <color=green>F</color>: {h.floatParam} <color=green>I</color>: {h.intParam}";
w.visible = true;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void UpdateKnobTimeAnimatorEventInfos(float knobPos)
{
NativeList<int2> tmpIndicesList = new (0xff, Allocator.Temp);
timelineContent.GetAnimatorEventShapeIndicesForPos(ref tmpIndicesList, knobPos);
for (var i = 0; i < tmpIndicesList.Length; ++i)
{
var indexAndRow = tmpIndicesList[i];
var h = recordedData.Value.animatorEventHistory[indexAndRow.x];
var w = eventInfoWidgets[indexAndRow.y];
var trinagleSymbol = "▶";
var prefix = h.eventType == AnimatorControllerEventComponent.EventType.StateEnter ? trinagleSymbol : "";
var suffix = h.eventType == AnimatorControllerEventComponent.EventType.StateExit ? trinagleSymbol : "";
w.Q<Label>("name").text = $"{prefix}{h.name}{suffix}";
w.Q<Label>("stringv").text = $"<color=green>LayerId</color>: {h.layerId} <color=green>StateID</color>: {h.stateId}";
w.visible = true;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void UpdateMemoryStat()
{
var mem = recordedData.Value.GetDataSize();
memoryStat.text = $"Memory size {CommonTools.FormatMemory(mem)}";
saveBtn.SetEnabled(mem > 0);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void UpdatePreview()
{
var world = GetSelectedWorld();
if (world == null || selectedEntity == Entity.Null)
return;
if (!IsPreview())
return;
if (!animationDatabaseSingletonEq.TryGetSingleton<BlobDatabaseSingleton>(out var animDBSingleton))
return;
var pc = playbackSingletonEq.GetSingletonRW<PlaybackComponent>();
pc.ValueRW.playbackData.Clear();
var knobFrame = timelinePortal.Value.knobFrame;
for (var i = 0; i < recordedData.Value.animHistory.Length; ++i)
{
var ah = recordedData.Value.animHistory[i];
if (knobFrame >= ah.frameSpan.x && knobFrame <= ah.frameSpan.y)
{
var animationWeight = AnimationHistoryData.GetHistoryValueForFrame(ah.historyWeights, knobFrame);
var animationTime = AnimationHistoryData.GetHistoryValueForFrame(ah.historyAnimTime, knobFrame);
var atp = new AnimationToProcessComponent()
{
animation = BlobDatabaseSingleton.GetBlobAsset(ah.animationHash, animDBSingleton.animations),
avatarMask = BlobDatabaseSingleton.GetBlobAsset(ah.avatarMaskHash, animDBSingleton.avatarMasks),
blendMode = ah.blendMode,
layerIndex = ah.layerIndex,
layerWeight = ah.layerWeight,
motionId = ah.motionId,
time = animationTime,
weight = animationWeight
};
pc.ValueRW.playbackData.Add(atp);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void StartPreview()
{
StopRecord();
var world = GetSelectedWorld();
if (world == null || selectedEntity == Entity.Null)
return;
var isPreview = IsPreview();
Assert.IsFalse(isPreview);
entityPath.SetEnabled(false);
recordBtn.SetEnabled(false);
previewBtn.style.color = new StyleColor(Color.cyan);
var pc = new PlaybackComponent() { playbackData = new (0xff, Allocator.Persistent) };
world.EntityManager.AddComponentData(selectedEntity, pc);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void StopPreview()
{
previewBtn.style.color = StyleKeyword.Null;
recordBtn.SetEnabled(true);
entityPath.SetEnabled(true);
var isPreview = IsPreview();
if (!isPreview)
return;
var world = GetSelectedWorld();
world.EntityManager.RemoveComponent<PlaybackComponent>(selectedEntity);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe bool IsRecording() => recordSingletonEq.__impl != null && recordSingletonEq.HasSingleton<RecordComponent>();
unsafe bool IsPreview() => playbackSingletonEq.__impl != null && playbackSingletonEq.HasSingleton<PlaybackComponent>();
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SaveRecordedData()
{
var path = EditorUtility.SaveFilePanel("Rukhanka Wayback Machine", "", "WaybackMachineRecord", "rwbm");
recordedData.Value.SerializeToFile(path);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe void LoadRecordedData()
{
var path = EditorUtility.OpenFilePanel("Rukhanka Wayback Machine", "", "rwbm");
recordedData.GetUnsafePtr()->SerializeFromFile(path);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DropDown(GenericDropdownMenu m, VisualElement targetElement)
{
#if UNITY_6000_3_OR_NEWER
m.DropDown(entityPathEntity.worldBound, entityPathEntity, DropdownMenuSizeMode.Content);
#else
m.DropDown(targetElement.worldBound, targetElement, false);
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void OnDestroy()
{
StopRecord();
StopPreview();
if (recordedData.IsCreated)
recordedData.Value.Dispose();
recordedData.Dispose();
timelinePortal.Dispose();
timelineContent.Dispose();
DisposeEntityQueries();
EditorApplication.playModeStateChanged -= playModeChangeFn;
}
}
}
@@ -0,0 +1,25 @@
fileFormatVersion: 2
guid: 73ee215c330b6424fbc77eda27bdff25
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- m_ViewDataDictionary: {instanceID: 0}
- windowAsset: {fileID: 9197481963319205126, guid: b8f9259b827427d4bababecc0ca4b088, type: 3}
- leftPaneAsset: {fileID: 9197481963319205126, guid: 0625fcf33fc6f894abb1ceb8e0eed94b, type: 3}
- rightPaneAsset: {fileID: 9197481963319205126, guid: 8e219e54165d54b47bebd4ae11ea57ac, type: 3}
- stateInfoAsset: {fileID: 9197481963319205126, guid: ac25a97ac8035ef4aa39a33fc05a4031, type: 3}
- eventInfoAsset: {fileID: 9197481963319205126, guid: 81d64e961a6c95b4caa6115d4ea8dc1e, type: 3}
- animInfoAsset: {fileID: 9197481963319205126, guid: 41515e63c8fb4ad45ace42e817e8e9b2, type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/WaybackMachineWindow.cs
uploadId: 897522
@@ -0,0 +1,41 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
using UnityEngine;
namespace Rukhanka.Editor
{
public partial class WaybackMachineWindow
{
public static readonly int BINARY_VERSION = 1;
public static readonly uint BINARY_MAGIC = 0x4D425752;
public static readonly int TIMELINE_TICKS_MIN_SKIP_SPACE = 40;
public static readonly int TIMELINE_MOUSE_SCROLL_SPEED = 10;
public static readonly string DEFAULT_WORLD_TEXT = "<Pick World>";
public static readonly string DEFAULT_ENTITY_TEXT = "<Pick Entity>";
public static readonly float EVENT_SHAPE_RADIUS = 5;
public static readonly float EVENT_ROW_HEIGHT = 26;
public static readonly Color EVENT_TEXT_COLOR = new Color(1, 1, 1, 0.3f);
public static readonly Color ANIMATOR_EVENT_COLOR = new Color(0.0f, 0.0f, 0.5450981f, 1f);
public static readonly Color ANIMATOR_EVENT_ENTER_EXIT_MARKER_COLOR = new Color(1, 1, 1, 1);
public static readonly Color EVENT_OUTLINE_COLOR = new Color(1, 1, 1, 1);
public static readonly Color ANIMATION_EVENT_COLOR = new Color(0.0f, 0.3921569f, 0.0f, 1f);
public static readonly Color ANIMATION_BAR_COLOR = new Color(0.0f, 0.5019608f, 0.5019608f, 1f);
public static readonly Color ANIMATION_HISTORY_WEIGHT_LINE_COLOR = new Color(1f, 0.92156863f, 0.015686275f, 1f);
public static readonly Color ANIMATION_HISTORY_TIME_LINE_COLOR = new Color(1, 0.5f, 0, 1);
public static readonly float ANIMATION_BAR_HEIGHT = 60;
public static readonly float ANIMATION_BAR_HORIZONTAL_SPACE = 4;
public static readonly float ANIMATION_ROW_HEIGHT = ANIMATION_BAR_HEIGHT + ANIMATION_BAR_HORIZONTAL_SPACE;
public static readonly float STATE_BAR_HEIGHT = 60;
public static readonly float STATE_BAR_HORIZONTAL_SPACE = 4;
public static readonly float STATE_ROW_HEIGHT = STATE_BAR_HEIGHT + STATE_BAR_HORIZONTAL_SPACE;
public static readonly float STATE_BAR_VERTICAL_SPACE = 1;
public static readonly Color STATE_BAR_COLOR1 = new Color(0.8f, 0.4470588f, 0.0f, 1f);
public static readonly Color STATE_BAR_COLOR2 = new Color(0.2f, 0.4470588f, 0.8f, 1f);
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: ab4eae80fc466b742b013ba741d4dc7a
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Editor/WaybackMachine/WaybackMachineWindow_Consts.cs
uploadId: 897522