Netcode Bootstrap
This commit is contained in:
@@ -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
|
||||
+334
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -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
|
||||
+10
@@ -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>
|
||||
+17
@@ -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
|
||||
+168
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -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
|
||||
+9
@@ -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>
|
||||
+17
@@ -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
|
||||
+17
@@ -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 ▼" 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 ▼" 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 ▼" 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>
|
||||
+17
@@ -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
|
||||
+14
@@ -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>
|
||||
+17
@@ -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
|
||||
+6
@@ -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>
|
||||
+17
@@ -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
|
||||
+43
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -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
|
||||
+20
@@ -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>
|
||||
+17
@@ -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
|
||||
+907
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -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
|
||||
+41
@@ -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);
|
||||
}
|
||||
}
|
||||
+9
@@ -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
|
||||
Reference in New Issue
Block a user