如何在沿曲线排布图形对象时实现渐进式间距增减效果
问题描述
尝试实现不同空间间距之间的平滑过渡效果,动态逻辑对标运动加减速过程,但实现失败,最终出现上百个圆形完全堆叠成实心黑块的异常结果。
当前实现代码
def Place_circles(curve, circle_space, cs, draw=True, screen=None): curve_acceleration = [] if type(curve) == tuple: curve_acceleration = curve[1][0] curve_intensity = curve[1][1] curve = curve[0] Circle_list = [] idx = [0,0] for c in reversed(range(0,len(curve))): for p in reversed(range(0,len(curve[c]))): user_dist = circle_space[curve_intensity[c]] + curve_acceleration[c] * p dist = math.sqrt(math.pow(curve[c][p][0] - curve[idx[0]][idx[1]][0],2)+math.pow(curve [c][p][1] - curve[idx[0]][idx[1]][1],2)) if dist > user_dist: idx = [c,p] Circle_list.append(circles.circles(round(curve[c][p][0]), round(curve[c][p][1]), cs, draw, screen))
现有逻辑说明
沿曲线反向遍历点位,根据当前曲线的随机强度参数(取值范围0-2,对应不同基础间距,如0对应20px、2对应30px、中间值线性过渡),计算当前点位与上一个已放置圆形的欧氏距离,达到当前要求的间距阈值时就放置新圆形。
遇到的问题
- 上述逻辑最终导致大量圆形堆叠,不符合预期。
- 尝试套用经典加速度公式动态调整间距时存在适配问题:项目需要按固定节奏放置圆形(如180BPM下每0.333s放置一个),由于无法预先确定两个点位之间会生成多少个圆形,无法直接套用加速度公式实现间距平滑过渡。
- 核心诉求是找到固定放置节奏下实现间距渐进式增减的可行方案,不需要预先统计区间内的圆形生成数量。
可行实现方案
原有代码的堆叠bug定位
出现堆叠的核心原因有两点:
- 间距阈值
user_dist的计算绑定了曲线内部的索引p,反向遍历时p从大到小递减,如果curve_acceleration为正值,越往遍历后期阈值会越小,哪怕两个相邻点位仅差1px,也会满足dist > user_dist的放置条件,导致连续堆叠。 - 初始索引
idx = [0,0]的设定不符合反向遍历的逻辑,第一次计算距离时是拿曲线末端的点和曲线起点做对比,逻辑自相矛盾。
固定节奏下的渐变间距实现思路
完全不需要沿用“凑够距离就放圆”的旧逻辑,换用弧长参数化+预生成间距序列的方案,完全符合诉求,不需要额外统计区间内的圆形数量:
核心步骤
- 对曲线做弧长参数化
不管曲线的采样点分布是否均匀,先遍历所有采样点,计算每一段相邻采样点之间的欧氏距离,累加得到整个曲线从起点到每个采样点的累计弧长cum_s,以及整个曲线的总弧长total_s。这个过程只需要遍历一次曲线,不需要预先知道要放多少个圆。 - 生成符合加减速逻辑的间距序列
因为放置节奏是固定的,相邻两个圆的时间间隔dt是固定值(比如180BPM下dt=0.333s),只需要指定这段曲线的起始基础间距s_start、结束基础间距s_end,就可以直接生成第i个圆和上一个圆的间距:d_i = s_start + (s_end - s_start) * i / (圆的总数 - 1)。其中圆的总数可以通过曲线段的总时长除以dt直接算出——谱面片段的总时长是固定的,完全不需要统计曲线采样点的数量。 - 把间距序列转换成对应弧长位置
把生成的间距序列累加,得到每个圆形对应的目标弧长s_target_i,从0开始累加直到不超过总弧长。 - 匹配曲线坐标
对每个s_target_i,在之前算好的cum_s数组里做线性插值,找到对应弧长位置的坐标,直接放置圆形即可。
方案适配性说明
- 不需要预判曲线有多少采样点能放多少圆,只要拿到曲线的总弧长和片段总时长就行。
- 间距是严格按线性规律渐变的,对应匀速加减速的运动逻辑,平滑无跳变。
- 完全适配固定节奏:每个圆形对应固定的时间点,时间间隔恒定。
修正后的代码参考
def Place_circles(curve, circle_space, cs, draw=True, screen=None, segment_duration=None, bpm=180): # 解包曲线参数 if type(curve) == tuple: curve_acceleration, curve_intensity = curve[1][0], curve[1][1] curve = curve[0] # 第一步:计算曲线累计弧长 cum_s = [0.0] prev_x, prev_y = curve[0][0][0], curve[0][0][1] for c_idx, c in enumerate(curve): for p_idx, (x, y) in enumerate(c): if c_idx ==0 and p_idx ==0: continue dist = math.hypot(x - prev_x, y - prev_y) cum_s.append(cum_s[-1] + dist) prev_x, prev_y = x, y total_s = cum_s[-1] # 把曲线点按顺序拍平,方便插值 flat_points = [p for c in curve for p in c] # 第二步:计算固定节奏下的圆形总数 dt = 60 / bpm circle_count = int(segment_duration / dt) # 第三步:生成渐变间距序列 s_start = circle_space[curve_intensity[0]] s_end = circle_space[curve_intensity[-1]] distance_seq = [ s_start + (s_end - s_start) * i / max(circle_count - 1, 1) for i in range(circle_count) ] # 第四步:计算每个圆形的目标弧长 target_s = [] current_s = 0.0 for d in distance_seq: current_s += d if current_s > total_s: break target_s.append(current_s) # 第五步:弧长插值找对应坐标,放置圆形 Circle_list = [] import bisect for s in target_s: # 找到s在cum_s中的插入位置,做线性插值 idx = bisect.bisect_left(cum_s, s) if idx ==0: x, y = flat_points[0] elif idx >= len(cum_s)-1: x, y = flat_points[-1] else: s0, s1 = cum_s[idx-1], cum_s[idx] p0, p1 = flat_points[idx-1], flat_points[idx] ratio = (s - s0) / (s1 - s0) x = p0[0] + (p1[0] - p0[0]) * ratio y = p0[1] + (p1[1] - p0[1]) * ratio Circle_list.append(circles.circles(round(x), round(y), cs, draw, screen)) return Circle_list
内容的提问来源于stack exchange,提问作者Gess1t
相关产品推荐
相关产品推荐

