拓冰建站拓冰建站
首页 / 资讯中心 / 正文

TiXL 时间线与动画曲线重构技术指南:插值正确性、稀疏采样缓存与向后兼容序列化

TiXL 时间线与动画曲线重构技术指南插值正确性、稀疏采样缓存与向后兼容序列化【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3本篇技术指南围绕 TiXL实时动态图形创作软件的时间线/动画曲线重构规划展开系统梳理其核心目标修复平滑/样条切线插值的正确性、降低时间线渲染与交互的开销、在保证向后兼容的前提下提升序列化质量并为速度曲线编辑器、dope sheet 与曲线编辑器的共享渲染等未来特性预留架构。读者将掌握 TiXL 动画曲线数据模型键模型、插值语义、切线与张力的设计脉络理解基于 Revision 的稀疏采样缓存渲染方案以及旧项目文件如何被透明迁移到新格式的实现细节。该规划文档位于仓库 .agentic/Plans/archive/Plan_TimelineRefactoring.md其中大部分设计决策已在当前源码Core/Animation 与 Core/DataTypes/Curve.cs中落地本文将以“规划意图 → 源码实现”对照的方式展开。一、重构目标四个层面的一次系统性升级规划文档开篇即明确了时间线重构的四个目标它们构成了后续所有设计的评价基准插值正确性Correctness重点修复 smooth/spline 切线行为尤其是平滑切线权重导致本应保持直线straight的区段被“弄弯”的问题。运行时/编辑器开销Performance削减时间线渲染与交互过程中的过度分配excessive allocation降低 GC 压力。序列化质量Serialization当前 JSON 序列化冗长且不感知精度not precision-aware需要精简体积并保证往返无损。架构前瞻Future-proofing为未来的曲线功能预留空间例如速度曲线编辑器speed-curve editor、dope sheet 与曲线编辑器之间的共享渲染。这四个目标并非独立推进而是环环相扣键模型的重设计同时服务正确性与序列化曲线 revision 机制同时服务缓存失效与增量备份渲染缓存重构则直接对接性能目标并让两个视图共享同一份采样数据。二、现状问题键模型的三个结构性缺陷规划文档指出了当前键key数据载荷的实际形态并给出了一个典型 JSON 示例{ Time: 50.443, Value: -0.30000001192092896, InType: Spline, OutType: Spline, InEditMode: Smooth, OutEditMode: Smooth, InTangentAngle: 6.283185307179586, OutTangentAngle: 3.141592653589793 }围绕该载荷文档归纳出三个模型层面的问题概念混叠插值行为InType/OutType与 UI 编辑模式InEditMode/OutEditMode被塞进两个概念里导致模型与编辑器表现耦合难以扩展新的插值语义。精度过度切线角度用完整double存储并不必要6.283185307179586这类值徒增序列化体积与比较噪声。浮点尾巴Value序列化时经常带出 float→double 的尾数噪音如-0.30000001192092896既难看又难以在文本层面做差异比较。这三个问题分别指向模型分层、存储精度策略、序列化输出策略也正是后面三节方案要逐一解决的对象。三、提议的新键模型行为与编辑模式分离规划文档给出了概念级的新键模型struct Key { double U; double Value; [Flags] enum Flags { BreakTangents, SaveDoublePrecision, } enum Interpolation { Linear, AutoSmooth, Smooth, Horizontal, ConstantOut, } float AngleIn; // Candidate: replace with normalized derivative/slope representation. float AngleOut; float TensionIn 1; float TensionOut 1; Interpolation InType; Interpolation OutType; int UniqueId; // Runtime-stable id, not serialized. }设计要点规划文档原文float切线角AngleIn/AngleOut候选改用归一化的导数/斜率表示且存储与计算均可用float精度后续“Decisions After Clarification”一节最终拍板。Tension张力TensionIn/TensionOut默认值为 1用于控制切线影响范围。UniqueId运行时稳定 ID每个新键在运行时通过原子静态计数器获得全局唯一、稳定的 ID用于健壮的选中与交互处理不参与序列化。Flags标志位BreakTangents打断切线与SaveDoublePrecision保留 double 精度作为独立标志存在。默认值省略为减小文件体积尽量省略默认值例如 linear 模式、tension1。规划文档还强调了几条约束向后兼容的反序列化是强制要求理想的切线表示应避免在修改线性键的U时重新计算切线格式需保持对 After Effects 式键/速度工作流的可扩展性。源码落地VDefinition 的实现对照当前仓库中的实际键类型是 Core/Animation/VDefinition.cs可以看到规划的大部分决策已实现运行时唯一 IDpublic int UniqueId { get; } Interlocked.Increment(ref _nextId);VDefinition.cs_nextId为静态计数器完全符合“session 内唯一、不序列化、重启重置可接受”的决策。ParentCurve 回引用internal Curve? ParentCurve;VDefinition.cs当键被加入曲线表时由Curve设置移除时清空属性 setter 通过ParentCurve?.NotifyChanged()自动失效所属曲线的缓存——这直接呼应了文档开放问题中关于“是否让 VDefinition 变为外部只读、所有变更走Curve.UpdateKey()”的讨论当前实现选择了保留直接属性赋值 ParentCurve 通知的折中路线。float 张力TensionIn/TensionOut已是float类型默认 1.0VDefinition.cs。插值枚举KeyInterpolation枚举包含Constant / Linear / Smooth / Cubic / Horizontal / Tangent六种VDefinition.cs比规划的Interpolation枚举更细额外拆分出Cubic与Tangent说明实现阶段在计划之上进一步细化。省略默认值的写入Write方法只在非默认时写出InTangentAngle、OutTangentAngle、Weighted、BrokenTangents、TensionIn、TensionOutVDefinition.cs正是规划中“omit default values”的落地。值得注意的是U的 setter 会执行Math.Round(value, Curve.TimePrecision)VDefinition.cs其中TimePrecision 4定义于 Core/DataTypes/Curve.cs从实现层面把键时间统一归约到 4 位小数避免了HasVAt/MoveKey等查询因浮点误差而失配。四、曲线 Revision缓存失效与增量备份的基石规划文档对Curves的要求只有一条但极其关键曲线应包含一个 revision 整数用于缓存以及可能的增量备份序列化每次修改曲线或其任意键时递增曲线 revision或同步到 symbol revision如果最终采用该策略。当前实现中这个 revision 对应 Core/DataTypes/Curve.cs 的public int ChangeCount { get; private set; }。所有写路径AddOrUpdateV、MoveKey、RemoveKeyframeAt、PreCurveMapping/PostCurveMappingsetter、NotifyChanged都会递增它。更值得关注的是配套实现的批量编辑机制它直接回应了规划文档“Open Design Questions”中的性能痛点“批量移动 200 个键耗时约 100msMoveKey每次移动后都调用UpdateTangents”BeginBatchEdit()递增_batchEditDepth抑制逐键的切线重算与在某些实现中的额外 ChangeCount 递增EndBatchEdit()在最外层结束时仅调用一次SplineInterpolator.UpdateTangents(_state.Table)并递增一次ChangeCountCurve.cs。仓库测试 Core.Tests/CurveChangeCountTests.cs 从测试侧锁定了这条契约AddOrUpdateV、UpdateCurveValuesfloat/int 两种重载、RemoveKeyframe、MoveKey、设置 Pre/PostCurveMapping 各自恰好递增一次ChangeCount——这正是规划中 Milestone 1“修复重复 change-count 递增”与 Milestone 2“revision 确定性递增”的验收标准。Curve还提供ScaleKeyTimes(double factor)Curve.cs以BeginBatchEdit/EndBatchEdit包裹整体重定时间整个缩放过程只重算一次切线。五、向后兼容序列化格式版本与 Legacy 迁移规划文档对序列化的演进给出了明确路线在序列化动画数据中加入显式的曲线格式版本元数据若版本缺失则按 legacy 载荷读取并在加载时透明转换为新模型增加专门的迁移/读取辅助方法例如ReadKeyframeFromJsonFormat对新格式写入启用默认值省略为新旧两种载荷变体补充往返round-trip测试。当前源码的读取逻辑位于 Core/Animation/VDefinition.cs 的Read方法实现了一套双轨策略新格式优先如果 JSON 中存在InInterpolation/OutInterpolation字段直接按统一的新插值枚举解析Legacy 回退否则读取InType/OutTypeInEditMode/OutEditMode通过ConvertLegacyVDefinition.cs映射到新枚举——映射表为Constant → ConstantLinear → LinearSpline则依据编辑模式映射到Smooth/Cubic/Horizontal/Tangent/Linear。曲线整体的读写发生在 Core/Animation/CurveState.cs写入时输出PreCurve、PostCurve即OutsideCurveBehavior与Keys数组读取时对每个键的Time执行Math.Round(time, Curve.TimePrecision)后再入表。仓库测试 Core.Tests/CurveSerializationTests.cs 提供了两条关键回归用例可直接验证规划中的 Milestone 3 退出标准ReadLegacyCurve_WithoutFormatVersion解析无格式版本的旧 JSON含InType:SplineInEditMode:Smooth等混合字段验证旧项目加载行为不变ReadLegacyCurve_ThenReSave_ProducesNewFormat读取 legacy 载荷后重新保存验证输出被转换为统一的新格式InInterpolation/OutInterpolation实现“新保存往返无损、旧载荷读取时透明迁移”。规划中“显式 curve-format version 字段”在 VDefinition 层级由“字段存在性 枚举化解析”替代实现需要说明的是这一迁移策略是VDefinition 级的逐键判断而非文档中设想的曲线级独立版本号但最终效果一致缺失新字段即视为 legacy 并按旧语义转换。六、曲线渲染重构pre/body/post 三段式 稀疏可见区缓存规划文档对曲线渲染的批判集中在三点应避免逐帧分配、应面向 immediate-mode UI 绘制优化、应支持自适应稀疏缓存。整体思路是把每条曲线看作三个可视区段pre首键之前、body键之间、post末键之后每段用单次DrawList.AddPolyline()绘制每个可见区段只计算一次采样值并缓存使用Vec2U, Value采样的稀疏缓存缓存与“仅键帧”数据源解耦使程序化动画procedural animation也能共享同一缓存。规划中的潜在绘制流程如下原文保留Draw - 每个 AnimationParameter - 每个向量分量 - GetOrUpdateValueCacheForVisibleRange - Linear单段省略直线上的点 - Constant/discrete必要时允许同一 U 处两个点 - 非线性 - dope sheet 预览用固定步数如 20 步或 - 按缩放的自适应步数如目标间距约 5px - 对带尖角的程序化曲线如 frac(u)在极值/符号变化处可选加密 - 遍历缓存值构建绘制点流 - 跳过可见区起点之前 - 加最后一个点并停在可见区终点之后 - 更新 min/max 范围供下一帧值归一化 - 像素 delta 过小如 5px时跳过 - 按 pre/body/post 样式变化绘制 polyline旁注还包括AnimationParameter应保留上一帧的 min/max 与阻尼显示范围绘制点前先钳制可见垂直范围而非依赖昂贵的 clip-rect键帧处缓存值保持精确缩放后的懒性缓存细化lazy cache refinement可选、初期可不做缓存失效必须包含 curve revision缓存策略开放倾向固定大小池 LRU 式复用计划中的特性是让同一动画参数同时出现在 dope sheet 与时间同步的曲线编辑器中共享同一套缓存段与绘制算法。源码落地CurveSampleCache 的关键参数当前实现是 Core/Animation/CurveSampleCache.cs其常量与规划文档高度吻合且给出了具体数值TargetPixelSpacing 5fCurveSampleCache.cs非线性段的目标像素间距即规划中的“~5 px target spacing”RangeMarginFactor 1.5CurveSampleCache.cs缓存覆盖范围为请求范围的 1.5 倍小幅平移时不重建ZoomChangeThreshold 1.3CurveSampleCache.cs缩放比例超过 1.3 倍才触发重建避免微小缩放抖动引发重建风暴。失效判定IsValidCurveSampleCache.cs严格遵循规划“缓存正确性必须依赖CurveRevision”同时以可见范围是否被覆盖而非全局失效作为扩展/更新决策并以 zoom 比例作为第二重触发条件。重建RebuildCurveSampleCache.cs把每条曲线切成三段分别采样pre 区先于首键Constant映射只取两点Cycle/Oscillate等非恒定映射按缩放密度采样SamplePreRegionbody 区逐段采样——Constant段在bU处生成两个同 U 不同值的点形成竖直台阶CurveSampleCache.cs线性段仅两端点非线性段按“屏幕宽度 ÷ 5px”计算步数stepCount segmentScreenWidth / TargetPixelSpacing见SampleSegmentpost 区同 pre 区对称处理。查询侧提供了GetPointsInRange(double startU, double endU)CurveSampleCache.cs对按时间排序的点列表做两次二分查找切出可见区间的ReadOnlySpanVector2零分配地交给上层绘制。点列表预分配容量 256new ListVector2(256)配合CollectionsMarshal.AsSpan与float存储Vector2完全符合“避免逐帧分配、面向 immediate-mode 绘制”的目标。规划中“初始缓存预算约 1000–5000 条曲线向量范围、上限约 16 MB”目前仍是设计目标源码中CurveSampleCache是每曲线单实例尚未出现 LRU 池文档亦将其标注为“缓存策略开放”。配套测试 Core.Tests/CurveSampleCacheTests.cs 与 Core.Tests/CurveSampleCacheDiagnosticTests.cs 覆盖了缓存正确性与诊断输出。求值链路常量/线性/Hermite/Bezier 的决策树缓存采样最终调用 Core/DataTypes/Curve.cs 的GetSampledValue(u)其内部先处理 pre/post 映射通过CurveUtils.CreateOutsideCurveMapper创建的ConstantCurveMapper/CycleCurveMapper/CycleWithOffsetCurveMapper/OscillateCurveMapper见 Core/Animation/CurveUtils.cs再按段决策求值器OutInterpolation Constant→ConstInterpolator两侧均Linear→LinearInterpolatorBezierInterpolator.SegmentNeedsBezier(a, b)→BezierInterpolator仅当某端为Tangent模式且Weighted且张力非 1 时才走 Bezier 求根路径见 Core/Animation/BezierInterpolator.cs其余 →SplineInterpolator的立方 Hermite 插值Core/Animation/SplineInterpolator.cs。其中两处细节直接对应规划的“正确性”目标SplineInterpolator.UpdateTangents对首/中/尾键分别用CalcStartTangent/CalcInTangent/CalcOutTangent/CalcEndTangent计算切线并对Smooth模式加入防过冲钳制TANGENT_CLAMP_RATIO 1.5避免平滑切线向相邻键过冲SplineInterpolator.csSlopFromAngle把tan(angle)绝对值小于1e-10的结果硬性归零SplineInterpolator.cs消除tan(π) ≈ -1.22e-16这类浮点噪声在平坦曲线上造成的可见抖动——这正是文档所说“smooth tangent weighting 使本应保持直线的区段变形”的直接修复。七、Gain/Bias 高级曲线交互面向动效设计的节奏控制规划文档提出一种面向 motion design 的新交互思路在归一化[0..1]时间上使用 Gain/Bias 重映射交互——垂直拖动调整时序分布的集中度/对比度concentration/contrast水平拖动把时间偏置temporal bias移向起点或终点尽可能复用已有的 Gain/Bias 方法指向Core/MathUtils.cs。可作用的操纵范围manipulation scopes有三种两个键之间、选中键的边界内、时间线工作区内。两个线性键的示例从两个线性键之间按住修饰键开始拖动向下拖向拖动起始时鼠标 x 定义的“中心”集中——调整切线使插值更“干脆”更陡/垂直向上拖向两端集中ease in/out——调整切线使插值更平滑更水平左右拖把时序重心移向起点或终点。直觉示例对[0,0] → [1,1]把焦点从U0.5移向U0.75应让曲线平滑地穿过[0.75, 0.5]附近规划明确说明数学精度不是优先项艺术控制才是。多键选区的示例选中多个参数上的多个键例如U10到U50修饰键向上拖把键向中心集中、在边界处散开最外侧键应保持原始U插值类型通常保持不变linear 保持 linear对非线性曲线尽可能保留感知上的平滑度。源码基础MathUtils 的 Gain/Bias 实现规划中引用的Core/MathUtils.cs实际位于 Core/Utils/MathUtils.cs其中已具备可直接复用的实现GetBias(float b, float x)MathUtils.cs经典 Bias 重映射GetSchlickBias(float g, float x)MathUtils.csSchlick 的 Gain 近似ApplyGainAndBias(this float value, float gain, float bias)MathUtils.cs把 gain 与 bias 分别钳制到[0,1]后组合应用——当gain 0.5时先 Bias 后 SchlickBias否则相反。这正是规划文档“Reuse existing Gain/Bias methods where possible”的现成答案时间重映射交互可以直接把规范化后的时间u ∈ [0,1]送入ApplyGainAndBias以 gain 控制集中度、bias 控制时间重心再映射回键区间作为新的采样时间。规划同时强调该交互是有上下文的双键交互可更新切线/插值行为多键交互则须保持 linear/constant 意图、只调整非线性段的切线——避免 Gain/Bias 破坏用户已建立的键类型语义。八、插值语义护栏Guardrails规划文档为后续所有实现划定了不可逾越的语义边界主模型(time, value)值空间插值仍是核心创作模型时间缓动/重定时可选temporal easing/retiming 是可选层不得取代值空间插值两种段求值模式Mode A直接值空间插值hold/linear/splineMode B可选的重定时层u → s后再做值空间采样等端点行为必须显式化值空间样条在端点值相等时允许产生运动/过冲overshoot仅做时序重映射 线性插值的路径在端点值相等时不得产生运动AE 兼容目标保持“表示层兼容”representational compatibility而非严格的 1:1 UI 克隆。这套护栏解释了为什么渲染缓存与求值器要区分“采样点密度”与“语义”两个维度值空间采样始终以键的(time, value)为准任何 Gain/Bias 重定时都发生在采样时间轴u → s上从而保证“端点值相等即无运动”这一可预期行为不被破坏。九、已确认的决策与仍开放的设计问题澄清后的关键决策规划文档原文要点决策项结论UniqueId序列化仅运行时使用、不序列化身份只需进程会话内唯一重启后计数器重置可接受插值行为与编辑模式分离应尽早开展且独立于渲染重构推进切线角精度存储与计算均可用float默认值省略可立即启用范围限定在曲线/动画序列化曲线格式版本引入显式版本字段缺失时按 legacy 载荷读取并转换缓存正确性必须依赖CurveRevision映射模式变更也必须递增曲线 revision缓存失效策略由可见范围驱动缓存扩展/更新而非全局失效初始缓存预算约 1000–5000 条曲线向量范围上限约 16 MBGain/Bias 上下文双键交互可更新切线/插值多键交互保持 linear/constant 意图仅调整非线性切线开放设计问题原文保留批量键移动性能MoveKey逐键调用UpdateTangents导致 200 键约 100ms 延迟——修复方案是提供批量移动 API延迟切线重算到最后一次性完成源码中BeginBatchEdit/EndBatchEdit已实现该机制UniqueId用int还是long以避免超长会话溢出IRevisionVersioningAPI 的最终形态FlagChanged、BeginNewFrame、WasChangedInLastFrame、Revision曲线是否序列化其最后一次修改时的 symbol revision还是保持纯运行时元数据legacy 迁移辅助函数的命名/位置如ReadKeyframeFromJsonFormat应放在专门的序列化器类中AE 兼容切线的存储选择显式(time, value)控制柄还是语义化的 speed/influence 在求值/编辑时派生若后续加入 unit cubic temporal bezier应强制怎样的单调性与反演约束是否让VDefinition对外只读、所有变更统一走Curve.UpdateKey()以在不引入 ParentCurve 回引用或在其基础上保证 ChangeCount 递增、切线重算与缓存失效的一致性——这会触及CurvePoint、CurveEditing、ChangeKeyframesCommand、Animator等的大规模重构。十、增量实施计划九个里程碑规划文档给出了从“正确性与安全基线”到“AE 兼容硬化”的九步路线图每步都带明确的退出标准适合作为团队排期与验收的依据Milestone 1正确性与安全基线修复克隆/复制路径中键元数据丢失Weighted、BrokenTangents修复曲线更新辅助函数中重复的 change-count 递增为克隆/复制保真度与 change-count 行为补充回归测试。退出标准键复制/克隆无已知元数据丢失旧项目加载与动画行为与之前完全一致。对应测试见 Core.Tests/CurveChangeCountTests.cs。Milestone 2键身份、Revision 与模型分离用原子静态计数器为键分配运行时UniqueId会话级、不序列化为每条曲线增加 revision 字段并在每次键/时间/值/切线/映射变更时递增尽早把插值行为与编辑模式分离过渡期保持选择与编辑代码兼容旧的引用路径。退出标准编辑与拖动期间键 ID 稳定所有编辑操作的 revision 确定性递增插值/编辑模式分离无回归。Milestone 3序列化演进向后兼容在动画序列化数据中加入显式格式版本元数据实现 legacy 回退版本缺失时按旧载荷读取并转换增加专门迁移辅助如ReadKeyframeFromJsonFormat新格式写入启用默认值省略为新旧载荷变体补往返测试。退出标准旧项目加载不变新保存往返无损legacy 载荷读取时透明迁移。实现与测试见 VDefinition.cs 与 Core.Tests/CurveSerializationTests.cs。Milestone 4值空间求值基线保持值空间插值为主模型hold/linear/spline-Hermite重构插值键查找与求值流程以减少分配与临时对象重审切线重算触发条件纯 linear/constant 路径避免不必要重算补充 constant/linear/spline 与边界行为的插值测试为“等端点 非零切线”段增加显式测试用例。退出标准参考场景采样值正确求值热路径无新增逐帧分配等端点行为有意图且被测试覆盖。Milestone 5Dope Sheet 渲染清理暂不引入缓存移除热渲染路径中的 LINQ 与ToArray()/ToList()引入可复用的 polyline 点缓冲修复 min/max 阻尼写回以正确归一化显示。退出标准scrub/pan 时 GC 压力降低视觉输出与基线等价。Milestone 6稀疏可见区缓存Phase 1缓存条目以 revision zoom 桶必要时含映射模式为键可见范围作为缓存扩展/更新决策而非全局失效触发器从缓存采样构建 pre/body/post polyline 段加入有界缓存与 LRU 式淘汰预算约 16 MB。退出标准编辑期间缓存失效正确pan/zoom 时缓存范围扩展正确密集动画场景帧时间改善。Phase 1 的核心采样与范围判定已在 CurveSampleCache.cs 落地。Milestone 7dope sheet 与曲线编辑器共享缓存Phase 2统一采样/缓存管线两个视图消费同一份值缓存视图样式各自独立但共享采样生成补充视图间一致性测试。退出标准两视图曲线形状一致双视图同时激活时交互性能稳定。Milestone 8时间重定时层可选在不取代值空间插值的前提下对选中范围实现可选的时间重映射交互先用 Schlick gain/bias 作为重定时控件保留边界键并接入 undo/redo双键模式允许切线/插值塑形多键模式保持 linear/constant 意图、只调非线性切线行为。退出标准双键与多键场景下具备可用的艺术化节奏控制值形状编辑与可选重定时控制相互独立常见编辑工作流无破坏性行为。Milestone 9AE 兼容切线表示与硬化决定并实现 AE 兼容切线的存储模型控制柄式或 speed/influence 式提供转换/派生路径使值图与速度图成为同一段数据的一致视图在大时间线上做压力/性能验证文档化迁移与兼容行为定稿 schema/版本策略。退出标准生产规模文件上编辑/播放行为稳定AE 兼容语义与表示限制有文档迁移与回退路径清晰。十一、结语从规划到源码的闭环纵观 .agentic/Plans/archive/Plan_TimelineRefactoring.md 与当前仓库源码TiXL 的时间线重构呈现出一条清晰的“规划 → 落地”闭环键模型完成了插值行为与编辑模式的分离VDefinition.KeyInterpolation六态 新枚举化读写运行时稳定 ID 与 ParentCurve 通知机制让选中、缓存失效与切线重算在无全局锁的前提下保持一致ChangeCount 批量编辑接口同时解决了缓存失效与批量移动性能两个难题CurveSampleCache以 5px 目标间距、1.5 倍范围余量与 1.3 倍缩放阈值实现了可见区驱动的稀疏采样并预留了 LRU 池与共享渲染的演进空间。对于希望深入参与的开发者Core.Tests/CurveSerializationTests.cs、Core.Tests/CurveSampleCacheTests.cs 与 Core.Tests/CurveChangeCountTests.cs 是验证上述行为的最佳入口而 Core/Animation 目录下的SplineInterpolator、BezierInterpolator、LinearInterpolator、ConstInterpolator与各*CurveMapper则完整呈现了值空间插值链条的最终形态。【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门