|
@@ -1,25 +1,31 @@
|
|
-using System.Collections.Generic;
|
|
|
|
|
|
+using System.Collections;
|
|
using System.Drawing;
|
|
using System.Drawing;
|
|
-using System.IO;
|
|
|
|
-using System.Linq;
|
|
|
|
using ChunkyImageLib;
|
|
using ChunkyImageLib;
|
|
using ChunkyImageLib.DataHolders;
|
|
using ChunkyImageLib.DataHolders;
|
|
|
|
+using Microsoft.Extensions.DependencyInjection;
|
|
using PixiEditor.AvaloniaUI.Helpers;
|
|
using PixiEditor.AvaloniaUI.Helpers;
|
|
-using PixiEditor.AvaloniaUI.Models.Handlers;
|
|
|
|
|
|
+using PixiEditor.AvaloniaUI.Helpers.Extensions;
|
|
|
|
+using PixiEditor.AvaloniaUI.Models.IO;
|
|
using PixiEditor.AvaloniaUI.Models.IO.FileEncoders;
|
|
using PixiEditor.AvaloniaUI.Models.IO.FileEncoders;
|
|
|
|
+using PixiEditor.AvaloniaUI.Models.Serialization;
|
|
|
|
+using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
|
|
|
|
+using PixiEditor.ChangeableDocument.Changeables;
|
|
using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
|
|
using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
|
|
using PixiEditor.ChangeableDocument.Changeables.Interfaces;
|
|
using PixiEditor.ChangeableDocument.Changeables.Interfaces;
|
|
|
|
+using PixiEditor.DrawingApi.Core;
|
|
using PixiEditor.DrawingApi.Core.Bridge;
|
|
using PixiEditor.DrawingApi.Core.Bridge;
|
|
-using PixiEditor.DrawingApi.Core.Numerics;
|
|
|
|
-using PixiEditor.DrawingApi.Core.Surface;
|
|
|
|
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
|
|
|
|
|
|
+using PixiEditor.DrawingApi.Core.Surfaces;
|
|
|
|
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
|
|
using PixiEditor.Extensions.CommonApi.Palettes;
|
|
using PixiEditor.Extensions.CommonApi.Palettes;
|
|
using PixiEditor.Numerics;
|
|
using PixiEditor.Numerics;
|
|
using PixiEditor.Parser;
|
|
using PixiEditor.Parser;
|
|
using PixiEditor.Parser.Collections;
|
|
using PixiEditor.Parser.Collections;
|
|
-using BlendMode = PixiEditor.Parser.BlendMode;
|
|
|
|
|
|
+using PixiEditor.Parser.Graph;
|
|
|
|
+using PixiEditor.Parser.Skia;
|
|
|
|
+using PixiEditor.Parser.Skia.Encoders;
|
|
using IKeyFrameChildrenContainer = PixiEditor.ChangeableDocument.Changeables.Interfaces.IKeyFrameChildrenContainer;
|
|
using IKeyFrameChildrenContainer = PixiEditor.ChangeableDocument.Changeables.Interfaces.IKeyFrameChildrenContainer;
|
|
using PixiDocument = PixiEditor.Parser.Document;
|
|
using PixiDocument = PixiEditor.Parser.Document;
|
|
|
|
+using ReferenceLayer = PixiEditor.Parser.ReferenceLayer;
|
|
|
|
|
|
namespace PixiEditor.AvaloniaUI.ViewModels.Document;
|
|
namespace PixiEditor.AvaloniaUI.ViewModels.Document;
|
|
|
|
|
|
@@ -27,24 +33,141 @@ internal partial class DocumentViewModel
|
|
{
|
|
{
|
|
public PixiDocument ToSerializable()
|
|
public PixiDocument ToSerializable()
|
|
{
|
|
{
|
|
- var root = new Folder();
|
|
|
|
-
|
|
|
|
|
|
+ NodeGraph graph = new();
|
|
|
|
+ ImageEncoder encoder = new QoiEncoder();
|
|
var doc = Internals.Tracker.Document;
|
|
var doc = Internals.Tracker.Document;
|
|
|
|
+
|
|
|
|
+ Dictionary<Guid, int> nodeIdMap = new();
|
|
|
|
+ Dictionary<Guid, int> keyFrameIdMap = new();
|
|
|
|
+
|
|
|
|
+ List<SerializationFactory> factories =
|
|
|
|
+ ViewModelMain.Current.Services.GetServices<SerializationFactory>().ToList(); // a bit ugly, sorry
|
|
|
|
|
|
- //AddMembers(doc.StructureRoot.Children, doc, root);
|
|
|
|
|
|
+ AddNodes(doc.NodeGraph, graph, nodeIdMap, keyFrameIdMap, new SerializationConfig(encoder), factories);
|
|
|
|
|
|
var document = new PixiDocument
|
|
var document = new PixiDocument
|
|
{
|
|
{
|
|
- Width = Width, Height = Height,
|
|
|
|
- Swatches = ToCollection(Swatches), Palette = ToCollection(Palette),
|
|
|
|
- RootFolder = root, PreviewImage = (TryRenderWholeImage(0).Value as Surface)?.DrawingSurface.Snapshot().Encode().AsSpan().ToArray(),
|
|
|
|
|
|
+ Width = Width,
|
|
|
|
+ Height = Height,
|
|
|
|
+ Swatches = ToCollection(Swatches),
|
|
|
|
+ Palette = ToCollection(Palette),
|
|
|
|
+ Graph = graph,
|
|
|
|
+ PreviewImage =
|
|
|
|
+ (TryRenderWholeImage(0).Value as Surface)?.DrawingSurface.Snapshot().Encode().AsSpan().ToArray(),
|
|
ReferenceLayer = GetReferenceLayer(doc),
|
|
ReferenceLayer = GetReferenceLayer(doc),
|
|
- AnimationData = ToAnimationData(doc.AnimationData)
|
|
|
|
|
|
+ AnimationData = ToAnimationData(doc.AnimationData, nodeIdMap, keyFrameIdMap),
|
|
|
|
+ ImageEncoderUsed = encoder.EncodedFormatName
|
|
};
|
|
};
|
|
|
|
|
|
return document;
|
|
return document;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private static void AddNodes(IReadOnlyNodeGraph graph, NodeGraph targetGraph,
|
|
|
|
+ Dictionary<Guid, int> nodeIdMap,
|
|
|
|
+ Dictionary<Guid, int> keyFrameIdMap,
|
|
|
|
+ SerializationConfig config, IReadOnlyList<SerializationFactory> allFactories)
|
|
|
|
+ {
|
|
|
|
+ targetGraph.AllNodes = new List<Node>();
|
|
|
|
+
|
|
|
|
+ int id = 0;
|
|
|
|
+ foreach (var node in graph.AllNodes)
|
|
|
|
+ {
|
|
|
|
+ nodeIdMap[node.Id] = id + 1;
|
|
|
|
+ id++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ foreach (var node in graph.AllNodes)
|
|
|
|
+ {
|
|
|
|
+ NodePropertyValue[] properties = new NodePropertyValue[node.InputProperties.Count];
|
|
|
|
+
|
|
|
|
+ for (int i = 0; i < node.InputProperties.Count(); i++)
|
|
|
|
+ {
|
|
|
|
+ properties[i] = new NodePropertyValue()
|
|
|
|
+ {
|
|
|
|
+ PropertyName = node.InputProperties[i].InternalPropertyName,
|
|
|
|
+ Value = SerializationUtil.SerializeObject(node.InputProperties[i].NonOverridenValue, config, allFactories)
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Dictionary<string, object> additionalData = new();
|
|
|
|
+ node.SerializeAdditionalData(additionalData);
|
|
|
|
+
|
|
|
|
+ KeyFrameData[] keyFrames = new KeyFrameData[node.KeyFrames.Count];
|
|
|
|
+
|
|
|
|
+ for (int i = 0; i < node.KeyFrames.Count; i++)
|
|
|
|
+ {
|
|
|
|
+ keyFrameIdMap[node.KeyFrames[i].KeyFrameGuid] = i + 1;
|
|
|
|
+ keyFrames[i] = new KeyFrameData
|
|
|
|
+ {
|
|
|
|
+ Id = i + 1,
|
|
|
|
+ Data = SerializationUtil.SerializeObject(node.KeyFrames[i].Data, config, allFactories),
|
|
|
|
+ AffectedElement = node.KeyFrames[i].AffectedElement,
|
|
|
|
+ StartFrame = node.KeyFrames[i].StartFrame,
|
|
|
|
+ Duration = node.KeyFrames[i].Duration,
|
|
|
|
+ IsVisible = node.KeyFrames[i].IsVisible
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Dictionary<string, object> converted = ConvertToSerializable(additionalData, config, allFactories);
|
|
|
|
+
|
|
|
|
+ List<PropertyConnection> connections = new();
|
|
|
|
+
|
|
|
|
+ foreach (var inputProp in node.InputProperties)
|
|
|
|
+ {
|
|
|
|
+ if (inputProp.Connection != null)
|
|
|
|
+ {
|
|
|
|
+ connections.Add(new PropertyConnection()
|
|
|
|
+ {
|
|
|
|
+ OutputNodeId = nodeIdMap[inputProp.Connection.Node.Id],
|
|
|
|
+ OutputPropertyName = inputProp.Connection.InternalPropertyName,
|
|
|
|
+ InputPropertyName = inputProp.InternalPropertyName
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Node parserNode = new Node()
|
|
|
|
+ {
|
|
|
|
+ Id = nodeIdMap[node.Id],
|
|
|
|
+ Name = node.DisplayName,
|
|
|
|
+ UniqueNodeName = node.GetNodeTypeUniqueName(),
|
|
|
|
+ Position = node.Position.ToVector2(),
|
|
|
|
+ InputPropertyValues = properties,
|
|
|
|
+ AdditionalData = converted,
|
|
|
|
+ KeyFrames = keyFrames,
|
|
|
|
+ InputConnections = connections.ToArray()
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ targetGraph.AllNodes.Add(parserNode);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static Dictionary<string, object> ConvertToSerializable(
|
|
|
|
+ Dictionary<string, object> additionalData,
|
|
|
|
+ SerializationConfig config,
|
|
|
|
+ IReadOnlyList<SerializationFactory> allFactories)
|
|
|
|
+ {
|
|
|
|
+ Dictionary<string, object> converted = new();
|
|
|
|
+ foreach (var (key, value) in additionalData)
|
|
|
|
+ {
|
|
|
|
+ if (value is IEnumerable enumerable)
|
|
|
|
+ {
|
|
|
|
+ List<object> list = new();
|
|
|
|
+ foreach (var item in enumerable)
|
|
|
|
+ {
|
|
|
|
+ list.Add(SerializationUtil.SerializeObject(item, config, allFactories));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ converted[key] = list;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ converted[key] = SerializationUtil.SerializeObject(value, config, allFactories);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return converted;
|
|
|
|
+ }
|
|
|
|
+
|
|
private static ReferenceLayer? GetReferenceLayer(IReadOnlyDocument document)
|
|
private static ReferenceLayer? GetReferenceLayer(IReadOnlyDocument document)
|
|
{
|
|
{
|
|
if (document.ReferenceLayer == null)
|
|
if (document.ReferenceLayer == null)
|
|
@@ -55,13 +178,13 @@ internal partial class DocumentViewModel
|
|
var layer = document.ReferenceLayer!;
|
|
var layer = document.ReferenceLayer!;
|
|
|
|
|
|
var surface = new Surface(new VecI(layer.ImageSize.X, layer.ImageSize.Y));
|
|
var surface = new Surface(new VecI(layer.ImageSize.X, layer.ImageSize.Y));
|
|
-
|
|
|
|
|
|
+
|
|
surface.DrawBytes(surface.Size, layer.ImageBgra8888Bytes.ToArray(), ColorType.Bgra8888, AlphaType.Premul);
|
|
surface.DrawBytes(surface.Size, layer.ImageBgra8888Bytes.ToArray(), ColorType.Bgra8888, AlphaType.Premul);
|
|
|
|
|
|
var encoder = new UniversalFileEncoder(EncodedImageFormat.Png);
|
|
var encoder = new UniversalFileEncoder(EncodedImageFormat.Png);
|
|
|
|
|
|
using var stream = new MemoryStream();
|
|
using var stream = new MemoryStream();
|
|
-
|
|
|
|
|
|
+
|
|
encoder.Save(stream, surface);
|
|
encoder.Save(stream, surface);
|
|
|
|
|
|
stream.Position = 0;
|
|
stream.Position = 0;
|
|
@@ -75,9 +198,9 @@ internal partial class DocumentViewModel
|
|
OffsetY = (float)layer.Shape.TopLeft.Y,
|
|
OffsetY = (float)layer.Shape.TopLeft.Y,
|
|
Corners = new Corners
|
|
Corners = new Corners
|
|
{
|
|
{
|
|
- TopLeft = layer.Shape.TopLeft.ToVector2(),
|
|
|
|
- TopRight = layer.Shape.TopRight.ToVector2(),
|
|
|
|
- BottomLeft = layer.Shape.BottomLeft.ToVector2(),
|
|
|
|
|
|
+ TopLeft = layer.Shape.TopLeft.ToVector2(),
|
|
|
|
+ TopRight = layer.Shape.TopRight.ToVector2(),
|
|
|
|
+ BottomLeft = layer.Shape.BottomLeft.ToVector2(),
|
|
BottomRight = layer.Shape.BottomRight.ToVector2()
|
|
BottomRight = layer.Shape.BottomRight.ToVector2()
|
|
},
|
|
},
|
|
Opacity = 1,
|
|
Opacity = 1,
|
|
@@ -85,129 +208,49 @@ internal partial class DocumentViewModel
|
|
};
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
- private static void AddMembers(IEnumerable<IReadOnlyStructureNode> members, IReadOnlyDocument document, Folder parent)
|
|
|
|
- {
|
|
|
|
- foreach (var member in members)
|
|
|
|
- {
|
|
|
|
- if (member is IReadOnlyFolderNode readOnlyFolder)
|
|
|
|
- {
|
|
|
|
- var folder = ToSerializable(readOnlyFolder);
|
|
|
|
-
|
|
|
|
- //AddMembers(readOnlyFolder.Children, document, folder);
|
|
|
|
-
|
|
|
|
- parent.Children.Add(folder);
|
|
|
|
- }
|
|
|
|
- else if (member is IReadOnlyLayerNode readOnlyLayer)
|
|
|
|
- {
|
|
|
|
- parent.Children.Add(ToSerializable(readOnlyLayer, document));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static Folder ToSerializable(IReadOnlyFolderNode folder)
|
|
|
|
- {
|
|
|
|
- return new Folder
|
|
|
|
- {
|
|
|
|
- Name = folder.MemberName,
|
|
|
|
- BlendMode = (BlendMode)(int)folder.BlendMode.Value,
|
|
|
|
- Enabled = folder.IsVisible.Value,
|
|
|
|
- Opacity = folder.Opacity.Value,
|
|
|
|
- ClipToMemberBelow = folder.ClipToPreviousMember.Value,
|
|
|
|
- Mask = GetMask(folder.Mask.Value, folder.MaskIsVisible.Value)
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static ImageLayer ToSerializable(IReadOnlyLayerNode layer, IReadOnlyDocument document)
|
|
|
|
- {
|
|
|
|
- var result = document.GetLayerRasterizedImage(layer.Id, 0);
|
|
|
|
-
|
|
|
|
- var tightBounds = document.GetChunkAlignedLayerBounds(layer.Id, 0);
|
|
|
|
- using var data = result?.Encode();
|
|
|
|
- byte[] bytes = data?.AsSpan().ToArray();
|
|
|
|
- var serializable = new ImageLayer
|
|
|
|
- {
|
|
|
|
- Width = result?.Width ?? 0, Height = result?.Height ?? 0, OffsetX = tightBounds?.X ?? 0, OffsetY = tightBounds?.Y ?? 0,
|
|
|
|
- Enabled = layer.IsVisible.Value, BlendMode = (BlendMode)(int)layer.BlendMode.Value, ImageBytes = bytes,
|
|
|
|
- ClipToMemberBelow = layer.ClipToPreviousMember.Value, Name = layer.MemberName,
|
|
|
|
- Guid = layer.Id,
|
|
|
|
- LockAlpha = layer is ITransparencyLockable { LockTransparency: true },
|
|
|
|
- Opacity = layer.Opacity.Value, Mask = GetMask(layer.Mask.Value, layer.MaskIsVisible.Value)
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- return serializable;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static Mask GetMask(IReadOnlyChunkyImage mask, bool maskVisible)
|
|
|
|
- {
|
|
|
|
- if (mask == null)
|
|
|
|
- return null;
|
|
|
|
-
|
|
|
|
- var maskBound = mask.FindChunkAlignedMostUpToDateBounds();
|
|
|
|
-
|
|
|
|
- if (maskBound == null)
|
|
|
|
- {
|
|
|
|
- return new Mask();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- var surface = DrawingBackendApi.Current.SurfaceImplementation.Create(new ImageInfo(
|
|
|
|
- maskBound.Value.Width,
|
|
|
|
- maskBound.Value.Height));
|
|
|
|
-
|
|
|
|
- mask.DrawMostUpToDateRegionOn(new RectI(0, 0, maskBound.Value.Width, maskBound.Value.Height), ChunkResolution.Full, surface, new VecI(0, 0));
|
|
|
|
-
|
|
|
|
- return new Mask
|
|
|
|
- {
|
|
|
|
- Width = maskBound.Value.Width, Height = maskBound.Value.Height,
|
|
|
|
- OffsetX = maskBound.Value.X, OffsetY = maskBound.Value.Y,
|
|
|
|
- Enabled = maskVisible, ImageBytes = surface.Snapshot().Encode().AsSpan().ToArray()
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
private ColorCollection ToCollection(IList<PaletteColor> collection) =>
|
|
private ColorCollection ToCollection(IList<PaletteColor> collection) =>
|
|
new(collection.Select(x => Color.FromArgb(255, x.R, x.G, x.B)));
|
|
new(collection.Select(x => Color.FromArgb(255, x.R, x.G, x.B)));
|
|
|
|
|
|
- private AnimationData ToAnimationData(IReadOnlyAnimationData animationData)
|
|
|
|
|
|
+ private AnimationData ToAnimationData(IReadOnlyAnimationData animationData, Dictionary<Guid, int> nodeIdMap, Dictionary<Guid, int> keyFrameIds)
|
|
{
|
|
{
|
|
var animData = new AnimationData();
|
|
var animData = new AnimationData();
|
|
animData.KeyFrameGroups = new List<KeyFrameGroup>();
|
|
animData.KeyFrameGroups = new List<KeyFrameGroup>();
|
|
- BuildKeyFrames(animationData.KeyFrames, animData);
|
|
|
|
-
|
|
|
|
|
|
+ BuildKeyFrames(animationData.KeyFrames, animData, nodeIdMap, keyFrameIds);
|
|
|
|
+
|
|
return animData;
|
|
return animData;
|
|
}
|
|
}
|
|
|
|
|
|
- private static void BuildKeyFrames(IReadOnlyList<IReadOnlyKeyFrame> root, AnimationData animationData)
|
|
|
|
|
|
+ private static void BuildKeyFrames(IReadOnlyList<IReadOnlyKeyFrame> root, AnimationData animationData,
|
|
|
|
+ Dictionary<Guid, int> nodeIdMap, Dictionary<Guid, int> keyFrameIds)
|
|
{
|
|
{
|
|
foreach (var keyFrame in root)
|
|
foreach (var keyFrame in root)
|
|
{
|
|
{
|
|
- if(keyFrame is IKeyFrameChildrenContainer container)
|
|
|
|
|
|
+ if (keyFrame is IKeyFrameChildrenContainer container)
|
|
{
|
|
{
|
|
KeyFrameGroup group = new();
|
|
KeyFrameGroup group = new();
|
|
- group.LayerGuid = keyFrame.LayerGuid;
|
|
|
|
|
|
+ group.NodeId = nodeIdMap[keyFrame.NodeId];
|
|
group.Enabled = keyFrame.IsVisible;
|
|
group.Enabled = keyFrame.IsVisible;
|
|
-
|
|
|
|
|
|
+
|
|
foreach (var child in container.Children)
|
|
foreach (var child in container.Children)
|
|
{
|
|
{
|
|
- if (child is IKeyFrameChildrenContainer groupKeyFrame)
|
|
|
|
|
|
+ if (child is IReadOnlyRasterKeyFrame rasterKeyFrame)
|
|
{
|
|
{
|
|
- BuildKeyFrames(groupKeyFrame.Children, null);
|
|
|
|
- }
|
|
|
|
- else if (child is IReadOnlyRasterKeyFrame rasterKeyFrame)
|
|
|
|
- {
|
|
|
|
- BuildRasterKeyFrame(rasterKeyFrame, group);
|
|
|
|
|
|
+ BuildRasterKeyFrame(rasterKeyFrame, group, nodeIdMap, keyFrameIds);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
animationData?.KeyFrameGroups.Add(group);
|
|
animationData?.KeyFrameGroups.Add(group);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private static void BuildRasterKeyFrame(IReadOnlyRasterKeyFrame rasterKeyFrame, KeyFrameGroup group)
|
|
|
|
|
|
+ private static void BuildRasterKeyFrame(IReadOnlyRasterKeyFrame rasterKeyFrame, KeyFrameGroup group,
|
|
|
|
+ Dictionary<Guid, int> idMap, Dictionary<Guid, int> keyFrameIds)
|
|
{
|
|
{
|
|
var bounds = rasterKeyFrame.Image.FindChunkAlignedMostUpToDateBounds();
|
|
var bounds = rasterKeyFrame.Image.FindChunkAlignedMostUpToDateBounds();
|
|
|
|
|
|
DrawingSurface surface = null;
|
|
DrawingSurface surface = null;
|
|
-
|
|
|
|
|
|
+
|
|
if (bounds != null)
|
|
if (bounds != null)
|
|
{
|
|
{
|
|
surface = DrawingBackendApi.Current.SurfaceImplementation.Create(
|
|
surface = DrawingBackendApi.Current.SurfaceImplementation.Create(
|
|
@@ -218,12 +261,10 @@ internal partial class DocumentViewModel
|
|
new VecI(0, 0));
|
|
new VecI(0, 0));
|
|
}
|
|
}
|
|
|
|
|
|
- group.Children.Add(new RasterKeyFrame()
|
|
|
|
|
|
+ group.Children.Add(new ElementKeyFrame()
|
|
{
|
|
{
|
|
- LayerGuid = rasterKeyFrame.LayerGuid,
|
|
|
|
- StartFrame = rasterKeyFrame.StartFrame,
|
|
|
|
- Duration = rasterKeyFrame.Duration,
|
|
|
|
- ImageBytes = surface?.Snapshot().Encode().AsSpan().ToArray(),
|
|
|
|
|
|
+ NodeId = idMap[rasterKeyFrame.NodeId],
|
|
|
|
+ KeyFrameId = keyFrameIds[rasterKeyFrame.Id],
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|