R2grap项目介绍(2):动画结构篇

介绍R2grap的动画layer-path结构设计

承接上节,本节主要介绍个人开源项目R2grap中动画Json文件的字段定义,以及相应的编解码流程实现。

AE中Shape动画描述形式

要对AE的shape动画进行描述,首先要知道AE中不同layer的表现形式,以及所包含的动画信息,在AE中实际的图层表现如下图所示:

AE-layer结构

直接看这张图可能有些复杂,上面不仅包含了layer的内部结构,也包含了transform的关键帧信息,以及当前layer与其他layer的绑定关系。会在接下来进行详细描述。

layers内部结构

首先对于单独的layer来说,它所包含数据内容从上图中可以看到,AE中每一个layer包含了若干个group,以及其自身的Transform属性。

对于Group来说,其内部所存储的是一个树状结构,即有可能嵌套包含另若干个Group,也有可能直接包含一个描述图元信息。由于最终的子group所包含的shape数据结构是相类似的,我们着重讲解下末尾子group所包含的数据形式;

group内部结构

描述图元信息的主要为:

  1. Path:以二次贝塞尔曲线描述的shape的顶点信息;
  2. Fill:用以描述该shape的颜色填充信息,rgb格式;
  3. Stroke:用以描述该Shape的边线的颜色信息,rgb格式;

其中Fill与Stroke并不是一定存在的,有可能group下仅包含Fill或Stroke,但一定存在Path信息;

对于Transform信息来说,主要用于表示该shape的静态信息与动态关键帧信息。因为transform不管是在group还是layer中,所包含的内容是相类似的,因此做下统一的描述。

Transform通用信息

描述动画的信息主要为:

  1. Anchor Pos : 图元的锚节点坐标,该值是当前layer/group所绑定的layer/group的Anchor Pos的相对值;
  2. Position:图元的相对坐标,以Anchor Pos为基准点;
  3. Scale: 图元的缩放值,缩放的中心点为Anchor Pos;
  4. Rotation: 图元的旋转值,旋转的中心点为Anchor Pos;
  5. Opacity: 图元的透明度值;

对于有关键帧的Transform属性,在Json中除记录了Transform的初始值之外,还记录了每个关键帧的数据。当关键帧类型为linear时,只是记录了时间与关键帧值的信息;当关键帧为Bezier Curve时,还需要记录每个关键帧的控制点坐标;

最终不管是Layer还是Group都存在“主从关系”这一属性,对于layer来说,当一个layer A对另一个layer B进行了链接(link),即代表layer A从属于layer B,layer A会继承B的Transform属性与关键帧信息。对于group来说,当group A 包含了 group B(A作为B的父group),则代表group B会继承A的全部transform与关键帧信息;

实际Json中每一个layer的表现形式,可以通过这个链接进行查看。

构建layer-group-path/transform的数据结构

前面简单分析了Layer内所包含的各类信息,下面我们从代码实现的角度去对其进行构建,首先定义LayersInfo类如下:

class LayersInfo{
public:
  LayersInfo(const nlohmann::json& layer);
  /*
  一些读取成员变量的读取函数,详情可在github上查看
  */

private:
  unsigned index_;  //layer索引
  std::string name_;//layer名称
  int link_; //与其他layer的链接情况,当未与其他layer链接时,为-1;
  float out_point_;//layer的结束时间
  float in_point_;//layer的开始时间
  std::vector<std::shared_ptr<ShapeGroup>> groups_;//groups指针数组
  std::shared_ptr<Transform> transform_;//transform指针
};

可以看到LayersInfo下包含了一组ShapeGroup,我们接下来继续看ShapeGroup的内部结构:

class ShapeGroup{
public:
  ShapeGroup(const nlohmann::json& json);
  /*
  一些读取成员变量的读取函数,详情可在github上查看
  */

private:
  void SetParent(const std::weak_ptr<ShapeGroup> parent){ parent_group_ = parent;}

private:
  std::weak_ptr<ShapeGroup> parent_group_;//用以指向父group的指针
  std::vector<std::shared_ptr<ShapeGroup>> child_groups_;//数组存放所包含的子group指针
  std::shared_ptr<GroupContents> contents_;//group内所包含的信息
  std::shared_ptr<Transform> transform_;//transform指针
};

以上述形式构造group的树状结构,其中transform_与LayerInfo中类似;

对于GroupContent的构造形式如下:

class GroupContents{
public:
  GroupContents(const nlohmann::json& json);
  /*
  一些读取成员变量的读取函数,详情可在github上查看
  */

private:
  std::vector<std::shared_ptr<PathInfo>> paths_;//可能包含多组Path,使用数组存放
  std::shared_ptr<FillInfo> fills_ = nullptr;//Fill信息,由于可能为空,默认置nullptr
  std::shared_ptr<StrokeInfo> stroke_ = nullptr;//Stroke信息,由于可能为空,默认置nullptr
  bool existMergePaths_ = false;//多组path直接是否存在Merge关系
};

可以看到每一个GroupContent中包含了path,由于fill和storke是可能存在的,所以对其默认置空,同时在AE中多组Path之间可以进行逻辑运算,实现多组path构建同一个图元(可以想象为内部镂空的这种图元);

class PathInfo{
public:
  PathInfo(const nlohmann::json& json);
  /*
  一些读取成员变量的读取函数,详情可在github上查看
  */
private:
  bool closed_; //是否为闭合图元,
  std::vector<glm::vec2> vertices_;
  std::vector<glm::vec2> out_pos_vecs_;
  std::vector<glm::vec2> in_pos_vecs_;

  std::vector<glm::vec2> bezier_verts_;
  std::vector<unsigned int> tri_index_list_;

  std::map<unsigned int, std::vector<glm::vec2>> linear_map_; //first: frame index, second: array of vert
  std::map<unsigned int, std::vector<unsigned int>> tri_index_map_;//first: frame index, second: array of triangle index

PathInfo的内容较多,逐一进行分析下:

  1. 在PathInfo的构造函数中会根据其控制点参数(out_pos_vecs_&in_pos_vecs_),对这一条Path进行贝塞尔插值构成曲线,将其存入到bezier_verts_中;
  2. 使用closed_标记当前path是否为闭合图元,如果是闭合图元,需要对beizier_verts_中的顶点进行三角形切分,即将顶点索引按耳切法组成符合渲染要求的顶点索引;
  3. Path也存在着有关键帧的情况,对于有关键帧的Path,这里使用了map去存放对应下的顶点数组与顶点索引数组;

在FillInfo/StorkeInfo中主要记录了填充/线条的颜色信息、透明度信息,以及关键帧信息;

最后在来说明下Transform的组成部分:

class Transform{
public:
  explicit Transform(const nlohmann::json& transform, bool IsShapeTransform = false, int link = -1);
  Transform() :
  type_(TransformType::t_NoneTrans),
  property_values_(std::map<std::string, std::variant<glm::vec3, float>>()),
  keyframe_data_(KeyframesMap())
  {}

  glm::vec3 GetShapeGrapOffset();
  static bool IsVectorProperty(std::string);
  bool IsNoKeyframe(){return keyframe_data_.size() == 0; }

  /*
  一些读取成员变量的读取函数,详情可在github上查看
  */

protected:
  void ReadProperty(const std::string& propname, const nlohmann::json& transform);
  void SetOriginalProerty(){original_property_values_ = property_values_;}
private:
  TransformType type_;//记录是LayerTransform(ShapeTransform)还是GroupTransform
  std::map<std::string, std::variant<glm::vec3, float>> property_values_;//记录Transform属性值,link运算后
  std::map<std::string, std::variant<glm::vec3, float>> original_property_values_;//记录Transform属性值,link运算前
  KeyframesMap keyframe_data_;//存放关键帧信息的map,同fill与storke类似,同样需要考虑标量与分量共存的问题
};

由于Transform的信息包含标量与分量,因此使用std::variant进行记录,property_values_和original_property_values_都是以这种形式存储的transform属性。

前文说过不管是Layer还是Group,它们的Transform属性都是与Link的对象相关的,需要在父对象的transform基础上加上子类的偏移量,才是最终图元在画幅中的绝对位置。

See also

comments powered by Disqus