C# Avalonia 16- Animation- PathBasedAnimation

news/2025/10/18 9:14:51/文章来源:https://www.cnblogs.com/dalgleish/p/19149194

写一个辅助类PathHelper,用于将PathGeometry转换为对应离散的Point,用于路径追踪。

using System;
using System.Collections.Generic;
using Avalonia;
using Avalonia.Media;public class PathHelper
{private readonly List<Point> points = new(); // 离散点缓存private readonly List<double> lengths = new(); // 累计长度缓存private double totalLength; // 总路径长度// geometry: 输入路径,step: 离散步长 (0~1)public PathHelper(PathGeometry geometry, double step = 0.01){BuildPathCache(geometry, step);}// 将 PathGeometry 离散化private void BuildPathCache(PathGeometry geometry, double step){double accumulated = 0; // 累计长度foreach (var figure in geometry.Figures!){Point start = figure.StartPoint;points.Add(start);lengths.Add(accumulated);foreach (var seg in figure.Segments!){switch (seg){case LineSegment line:// 线段处理accumulated += GetDistance(start, line.Point);start = line.Point;points.Add(start);lengths.Add(accumulated);break;case BezierSegment bezier:// 三次贝塞尔曲线处理AddBezierSegment(ref accumulated, ref start, bezier.Point1, bezier.Point2, bezier.Point3, step);break;case PolyLineSegment poly:// 多线段处理foreach (var p in poly.Points){accumulated += GetDistance(start, p);start = p;points.Add(p);lengths.Add(accumulated);}break;case PolyBezierSegment pbezier:// 多贝塞尔曲线处理if (pbezier.Points!.Count % 3 != 0)Console.WriteLine("警告: PolyBezierSegment 点数不是 3 的倍数,末尾点将被忽略");for (int i = 0; i + 2 < pbezier.Points.Count; i += 3){AddBezierSegment(ref accumulated, ref start,pbezier.Points[i], pbezier.Points[i + 1], pbezier.Points[i + 2], step);}break;case ArcSegment arc:// 弧线处理var arcPoints = FlattenArcSegment(start, arc, step);Point lastArcPoint = start;foreach (var p in arcPoints){if (p != lastArcPoint) // 避免重复起点
                            {accumulated += GetDistance(lastArcPoint, p);points.Add(p);lengths.Add(accumulated);lastArcPoint = p;}}start = arc.Point; // 更新起点为弧终点break;default:Console.WriteLine($"不支持的 Segment 类型: {seg.GetType().Name}");break;}}}totalLength = accumulated; // 保存总长度
    }// 贝塞尔离散化逻辑private void AddBezierSegment(ref double accumulated, ref Point start, Point p1, Point p2, Point p3, double step){Point lastPoint = start;int segments = Math.Max(1, (int)(1.0 / step)); // 离散段数for (int i = 1; i < segments; i++){double t = i / (double)segments;var p = EvaluateBezier(start, p1, p2, p3, t);accumulated += GetDistance(lastPoint, p);points.Add(p);lengths.Add(accumulated);lastPoint = p;}// 添加终点accumulated += GetDistance(lastPoint, p3);points.Add(p3);lengths.Add(accumulated);start = p3;}// 根据 progress 获取路径上对应的点 (0~1)public Point GetPoint(double progress){if (points.Count == 0) return new Point();if (progress <= 0) return points[0];if (progress >= 1) return points[^1];double targetLength = progress * totalLength;// 二分查找长度区间int left = 0, right = lengths.Count - 1;while (left < right){int mid = (left + right) / 2;if (lengths[mid] < targetLength)left = mid + 1;elseright = mid;}// 检查精确匹配情况if (left < lengths.Count && Math.Abs(lengths[left] - targetLength) < 1e-10){return points[left];}int index = Math.Max(left - 1, 0);// 避免 index+1 越界if (index + 1 >= points.Count) return points[^1];double segLength = lengths[index + 1] - lengths[index];if (segLength < 1e-10) return points[index];double t = (targetLength - lengths[index]) / segLength;return Interpolate(points[index], points[index + 1], t);}// 线性插值private static Point Interpolate(Point p1, Point p2, double t)=> new Point(p1.X + (p2.X - p1.X) * t, p1.Y + (p2.Y - p1.Y) * t);// 两点间距离private static double GetDistance(Point p1, Point p2)=> Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));// 计算三次贝塞尔曲线上点private static Point EvaluateBezier(Point p0, Point p1, Point p2, Point p3, double t){double u = 1 - t;double tt = t * t;double uu = u * u;double uuu = uu * u;double ttt = tt * t;double x = uuu * p0.X + 3 * uu * t * p1.X + 3 * u * tt * p2.X + ttt * p3.X;double y = uuu * p0.Y + 3 * uu * t * p1.Y + 3 * u * tt * p2.Y + ttt * p3.Y;return new Point(x, y);}// 将 ArcSegment 转换为离散点private static List<Point> FlattenArcSegment(Point start, ArcSegment arc, double step){var result = new List<Point> { start };double rx = Math.Abs(arc.Size.Width);double ry = Math.Abs(arc.Size.Height);double phi = arc.RotationAngle * Math.PI / 180.0;bool largeArc = arc.IsLargeArc;bool sweep = arc.SweepDirection == SweepDirection.Clockwise;Point end = arc.Point;if (rx == 0 || ry == 0){result.Add(end);return result;}double dx2 = (start.X - end.X) / 2.0;double dy2 = (start.Y - end.Y) / 2.0;double x1p = Math.Cos(phi) * dx2 + Math.Sin(phi) * dy2;double y1p = -Math.Sin(phi) * dx2 + Math.Cos(phi) * dy2;double rx_sq = rx * rx;double ry_sq = ry * ry;double x1p_sq = x1p * x1p;double y1p_sq = y1p * y1p;double lambda = x1p_sq / rx_sq + y1p_sq / ry_sq;if (lambda > 1){double scale = Math.Sqrt(lambda);rx *= scale;ry *= scale;rx_sq = rx * rx;ry_sq = ry * ry;}double sign = (largeArc == sweep) ? -1 : 1;double sq = ((rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq)) / ((rx_sq * y1p_sq) + (ry_sq * x1p_sq));sq = Math.Max(sq, 0);double coef = sign * Math.Sqrt(sq);double cxp = coef * (rx * y1p) / ry;double cyp = coef * -(ry * x1p) / rx;double cx = Math.Cos(phi) * cxp - Math.Sin(phi) * cyp + (start.X + end.X) / 2.0;double cy = Math.Sin(phi) * cxp + Math.Cos(phi) * cyp + (start.Y + end.Y) / 2.0;double vectorAngle(double ux, double uy, double vx, double vy){double dot = ux * vx + uy * vy;double det = ux * vy - uy * vx;return Math.Atan2(det, dot);}double theta1 = vectorAngle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);double deltaTheta = vectorAngle((x1p - cxp) / rx, (y1p - cyp) / ry,(-x1p - cxp) / rx, (-y1p - cyp) / ry);if (!sweep && deltaTheta > 0) deltaTheta -= 2 * Math.PI;else if (sweep && deltaTheta < 0) deltaTheta += 2 * Math.PI;int segments = Math.Max((int)(Math.Abs(deltaTheta) / (step * 2 * Math.PI)), 1);for (int i = 1; i <= segments; i++){double t = i / (double)segments;double angle = theta1 + t * deltaTheta;double x = cx + rx * Math.Cos(angle) * Math.Cos(phi) - ry * Math.Sin(angle) * Math.Sin(phi);double y = cy + rx * Math.Cos(angle) * Math.Sin(phi) + ry * Math.Sin(angle) * Math.Cos(phi);result.Add(new Point(x, y));}return result;}// 总路径长度public double TotalLength => totalLength;// 离散点数量public int PointCount => points.Count;
}

PathBasedAnimation.axaml代码

<Window xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"Height="381.6" Width="521.6"x:Class="AvaloniaUI.PathBasedAnimation"Title="PathBasedAnimation"><Window.Resources><PathGeometry x:Key="pathGeometry">M0,0 A15,10 0 0 1 120,350 A5,5 0 0 1 400,50</PathGeometry></Window.Resources><Canvas Margin="10"><Path Stroke="Red" StrokeThickness="1" Data="{StaticResource pathGeometry}"/><Image Name="image"  Width="20" Height="20"><Image.Source><DrawingImage><DrawingImage.Drawing><GeometryDrawing Brush="LightSteelBlue"><GeometryDrawing.Geometry><GeometryGroup><EllipseGeometry Center="10,10" RadiusX="9" RadiusY="4"/><EllipseGeometry Center="10,10" RadiusX="4" RadiusY="9"/></GeometryGroup></GeometryDrawing.Geometry><GeometryDrawing.Pen><Pen Thickness="1" Brush="Black"/></GeometryDrawing.Pen></GeometryDrawing></DrawingImage.Drawing></DrawingImage></Image.Source></Image></Canvas>
</Window>

PathBasedAnimation.axaml.cs代码

using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Shares.Avalonia;
using System;
using System.Drawing;namespace AvaloniaUI;public partial class PathBasedAnimation : Window
{private AnimationPlayer player = new AnimationPlayer();private PathHelper? helper;public PathBasedAnimation(){InitializeComponent();var geometry = this.Resources["pathGeometry"] as PathGeometry;helper = new PathHelper(geometry!);player.At(0).PlayLocal(p =>{var pt = helper.GetPoint(p);Canvas.SetLeft(image, pt.X - image.Width / 2);Canvas.SetTop(image, pt.Y - image.Height / 2);});player.Start();}
}

运行效果

image

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/939374.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年危险品运输公司权威推荐榜:安全高效,专业服务值得信赖!

2025年危险品运输公司权威推荐榜:安全高效,专业服务值得信赖!随着全球化工产业的快速发展,危险品运输行业的需求也在不断增加。为了确保危险品在运输过程中的安全性与效率,选择一家专业的危险品运输公司显得尤为重…

2025 年联轴器厂家最新推荐排行榜:聚焦万向、膜片、齿式等多类型产品,精选行业优质厂家

在现代工业生产中,联轴器作为连接机械传动部件的核心组件,直接影响设备运行的稳定性与效率,广泛应用于冶金、矿山、石油化工等关键领域。当前市场上联轴器品牌数量众多,产品质量参差不齐,部分企业存在技术落后、售…

2025 年换热器厂家最新推荐榜单:涵盖不锈钢钛哈氏合金等材质及列管式螺旋板等类型,为企业采购提供优质选择

引言在化工、环保、制药、食品等众多关键行业中,换热器作为核心热能交换设备,其质量稳定性、热交换效率与节能性能,直接关系到企业生产流程的连续性、能源成本控制及生产安全合规性。当前换热器市场企业数量繁杂,部…

2025 年最新推荐!反应釜制造厂家榜单重磅发布,聚焦不锈钢钛合金哈氏合金等多类型设备优质厂商

一、反应釜制造企业推荐榜推荐一:江苏旭阳化工设备有限公司 推荐指数:★★★★★ 口碑评分:9.9 分 品牌介绍:创建于 2009 年,坐落于靖江经济技术开发区城南园区,是集研发、生产和进出口贸易于一体的高科技企业。…

多模态、世界模型和主动智能丨Convo AIRTE2025

多模态是从 LLM 到 AGI 的必经之路。从 AI 视频生成到可实时交互的世界模型,从被动响应到主动感知与交互,再到下一代多模态大模型的设计与构建——由商汤科技和 RTE 开发者社区联合出品的 「多模态技术专场」 将展望…

2025年发电机组厂家推荐排行榜,柴油/燃气/船用/静音箱式/移动拖车/集装箱式/上柴/玉柴/潍柴/康明斯/沃尔沃/道依茨/帕金斯/MTU发电机组公司精选

2025年发电机组厂家推荐排行榜:柴油/燃气/船用/静音箱式/移动拖车/集装箱式/上柴/玉柴/潍柴/康明斯/沃尔沃/道依茨/帕金斯/MTU发电机组公司精选随着能源需求的不断增长和技术的快速发展,发电机组在各行各业中的应用越…

2025 防火隔断厂家最新推荐排行榜:甲级防火玻璃隔断厂家深度剖析,精选优质品牌助力采购决策

随着建筑行业对消防安全的要求日益严苛,防火隔断作为保障建筑安全的核心设施,市场需求持续攀升,但行业乱象却让采购者陷入选择困境。部分品牌缺乏核心技术,产品耐火性能不达标,火灾时无法有效阻隔火势与有毒烟气;…

clickhouse数据库 数据插入 去重和覆盖

一、存在则忽略(只插入全新用户)from clickhouse_driver import Client import pandas as pd client = Client(host=localhost, port=9000, database=default)# 0) 待写入的新数据 df_new = pd.DataFrame({ user_id:…

nacos客户端(接口调用者)如何感知被调用服务下线? (二)

🧩 一、问题背景 场景: 你有两个微服务:order-service(调用方 / Consumer)product-service(被调用方 / Provider)当 product-service 的一个实例下线(比如机器宕机或应用关闭)时, order-service 要知道它已…

2025 水泥墩源头厂家最新推荐排行榜:光伏 / 交通 / 围挡等多品类优选,实力品牌权威榜单发布

水泥墩作为市政基建、光伏电站、交通防护等领域的核心基础建材,其质量直接关系到工程安全与使用寿命。当前市场中厂家数量繁杂,部分企业为逐利偷工减料,导致产品强度不足、尺寸偏差大,难以抵御恶劣环境;部分厂家工…

2025年鸡精生产线/高速混合机/WDG农药生产线/鸡粉干燥设备/海鲜精干燥设备厂家推荐排行榜,调味料干燥设备/全自动配料/螺带混合机优质品牌!

2025年鸡精生产线/高速混合机/WDG农药生产线/鸡粉干燥设备/海鲜精干燥设备厂家推荐排行榜,调味料干燥设备/全自动配料/螺带混合机优质品牌!随着食品加工和化工行业的快速发展,相关生产设备的需求也在不断增加。鸡精…

2025 年过滤机厂家最新推荐排行榜:胶带式 / 盘式真空 / 脱水 / 带式真空 / 水平带式过滤机企业精选及选购指南

在工业生产与环保处理领域,固液分离环节的效率与质量直接关乎企业生产进度、产品品质及环保合规性,而过滤机作为该环节的核心设备,其重要性不言而喻。当前过滤机市场品牌繁杂、产品质量参差不齐,部分设备存在过滤速…

nacos客户端(接口调用者)如何感知被调用服务下线?(一)

Nacos 客户端(接口调用者)感知被调用服务下线的过程,依赖于 Nacos 的服务注册与发现机制、健康检查机制以及客户端缓存更新策略。核心逻辑是:Nacos 服务器实时维护服务实例状态,客户端通过 “主动拉取 + 被动推送…

2025年防水织带/鞋垫/编织包/针织包/飞织包包/松紧带/鞋带/织带/飞织鞋面厂家推荐排行榜,品质与创新的完美结合!

2025年防水织带/鞋垫/编织包/针织包/飞织包包/松紧带/鞋带/织带/飞织鞋面厂家推荐排行榜,品质与创新的完美结合!随着技术的不断进步和消费者需求的多样化,防水织带、鞋垫、编织包/针织包/飞织包包、松紧带、鞋带、织…

2025年压铸机械手厂家推荐排行榜,铝镁合金压铸周边自动化,压铸岛专业解决方案!

2025年压铸机械手厂家推荐排行榜,铝镁合金压铸周边自动化,压铸岛专业解决方案!随着制造业的不断发展和技术进步,压铸、压铸机械手、铝镁合金压铸周边自动化以及压铸岛等设备的需求日益增长。为了帮助筛选优质的压铸…

在MySQL中 redolog undolog binlog 写入的场景,顺序

🧩 一、三个日志的基本概念日志类型作用层面主要功能存放位置Redo Log InnoDB 引擎层 记录“数据页的物理修改” InnoDB 特有(ib_logfile)Undo Log InnoDB 引擎层 用于事务回滚 & MVCC InnoDB 表空间中Binlog …

2025年证卡打印机厂家权威推荐榜:含证件/PVC卡/IC卡/ID卡/智能卡,宝瑞迪/BOOD品牌优选!

2025年证卡打印机厂家权威推荐榜:含证件/PVC卡/IC卡/ID卡/智能卡,宝瑞迪/BOOD品牌优选!随着科技的不断进步和信息化管理的需求日益增长,证卡打印机在各行各业中的应用越来越广泛。无论是企业、学校、政府机关还是医…

2025 年压滤机厂家最新推荐榜:隔膜 / 污泥 / 真空 / 板框 / 带式压滤机优质企业精选指南

随着环保政策趋严与工业智能化升级,固液分离领域对压滤机的效率、适配性与智能化需求大幅提升。隔膜、污泥、真空等细分类型设备的性能差异显著,化工、矿山、市政等行业的需求更是千差万别。当前市场中,既有深耕多年…

灵芯派(基于Debian系统)

在灵芯派(基于Debian系统)上查看与宇树GO2机器狗连接的网口名称及状态,可以按照以下流程操作。使用 ethtool 命令是判断网口物理连接状态最直接有效的方法。 flowchart TDA[开始:查看网口连接状态] --> B[步骤一…

2025年大连网络营销推广/媒体投放/全案推广/新媒体营销/全媒体推广/代运营公司权威推荐榜

2025年大连网络营销推广/媒体投放/全案推广/新媒体营销/全媒体推广/代运营公司权威推荐榜随着互联网技术的飞速发展,网络营销已经成为企业不可或缺的一部分。在大连,众多企业在寻求网络营销推广、媒体投放、全案推广…