如何实现Collection1点随Collection2点加权平移且无永久形变
问题描述
笛卡尔平面上有两个点集:Collection1构成近似矩形的复杂多边形,Collection2是位于其内部的两点线段。移动Collection2的单个点时,要求Collection1中靠近移动点的点大幅平移,远离的点小幅平移,但多次移动后Collection1会产生永久形变——把Collection2的点移回原位时,Collection1无法恢复初始形态。
用户提供的相关代码如下:
fun moveSelectedBoneConstrained(x: Double, y: Double) { selectedBoneIndex?.let { val oldPoint = bones[it] val anchorPoint = if (it == 0) bones[1] else bones[0] var angle = Math.atan2((y - anchorPoint.y).toDouble(), (x - anchorPoint.x ).toDouble()) val r = getDistance(oldPoint, anchorPoint) val cosX = Math.cos(angle) val sinY = Math.sin(angle) val newX = r * cosX val newY = r * sinY val newPoint = PointBig((anchorPoint.x + newX.toFloat()), anchorPoint.y + newY.toFloat()) transmuteOutlineLinearWithDeltaWeight(oldPoint, newPoint, anchorPoint) //must change bones last bones[it] = newPoint } }
fun transmuteOutlineLinearWithDeltaWeight( oldPoint: PointBig, newPoint: PointBig,anchorPoint: PointBig){ val tempOutline = mutableListOf<PointBig>() var deltaX = newPoint.x - oldPoint.x var deltaY = newPoint.y - oldPoint.y outline.forEach { //get weight val distanceToOld = calculateDistance( it.x, it.y, oldPoint.x, oldPoint.y) val distanceToAnchor = calculateDistance( it.x, it.y, anchorPoint.x, anchorPoint.y) val sumDistance = distanceToOld + distanceToAnchor val weighting = distanceToAnchor/sumDistance //multiply by weight var newdeltaX = deltaX * weighting var newdeltaY = deltaY * weighting val newX = it.x + newdeltaX val newY = it.y + newdeltaY tempOutline.add(PointBig(newX, newY)) } outline.clear() outline.addAll(tempOutline) } fun calculateDistance(x1: Double, y1: Double, x2: Double, y2: Double): Double { val xDiff = x2 - x1 val yDiff = y2 - y1 return kotlin.math.sqrt((xDiff * xDiff) + (yDiff * yDiff)) }
问题根源
当前实现的核心问题是没有保留初始基准状态:每次形变都是直接在当前轮廓的基础上叠加偏移量,多次移动后,轮廓点的位置已经偏离初始值。即使把骨点移回原位,由于之前的偏移是叠加在修改后的轮廓上,无法反向抵消所有累积的形变,自然没法恢复初始形态。
解决方案
核心思路是保留初始轮廓和初始骨点的原始数据,每次形变都从初始状态重新计算,而不是基于当前修改后的轮廓叠加。具体实现步骤如下:
存储初始基准数据
新增两个变量保存初始的轮廓和骨点,后续绝不修改这两个集合:private val originalOutline = mutableListOf<PointBig>() private val originalBones = mutableListOf<PointBig>() // 初始化时调用,保存初始状态 fun initShape(initialOutline: List<PointBig>, initialBones: List<PointBig>) { originalOutline.clear() originalOutline.addAll(initialOutline) originalBones.clear() originalBones.addAll(initialBones) // 同步当前轮廓和骨点为初始状态 outline.clear() outline.addAll(initialOutline) bones.clear() bones.addAll(initialBones) }重写形变计算函数
不再基于当前轮廓点计算偏移,而是从初始轮廓点出发,结合当前骨点与初始骨点的差异,加权计算每个轮廓点的最终位置:fun transmuteOutlineLinearWithDeltaWeight() { val tempOutline = mutableListOf<PointBig>() // 获取当前骨点与初始骨点的位置差 val currentBone0 = bones[0] val currentBone1 = bones[1] val originalBone0 = originalBones[0] val originalBone1 = originalBones[1] originalOutline.forEach { originalPoint -> // 基于初始位置计算权重,确保权重逻辑和之前一致 val distToBone0 = calculateDistance(originalPoint.x.toDouble(), originalPoint.y.toDouble(), originalBone0.x.toDouble(), originalBone0.y.toDouble()) val distToBone1 = calculateDistance(originalPoint.x.toDouble(), originalPoint.y.toDouble(), originalBone1.x.toDouble(), originalBone1.y.toDouble()) val sumDist = distToBone0 + distToBone1 val weight0 = distToBone1 / sumDist // 靠近bone0的点,受bone0移动影响更大 val weight1 = distToBone0 / sumDist // 计算每个骨点带来的偏移量 val deltaX0 = currentBone0.x - originalBone0.x val deltaY0 = currentBone0.y - originalBone0.y val deltaX1 = currentBone1.x - originalBone1.x val deltaY1 = currentBone1.y - originalBone1.y // 加权求和得到总偏移 val totalDeltaX = deltaX0 * weight0 + deltaX1 * weight1 val totalDeltaY = deltaY0 * weight0 + deltaY1 * weight1 // 基于初始点计算最终位置 val newX = originalPoint.x + totalDeltaX val newY = originalPoint.y + totalDeltaY tempOutline.add(PointBig(newX, newY)) } outline.clear() outline.addAll(tempOutline) }修改骨点移动函数
移动骨点后,直接调用重写后的形变函数即可,无需传递旧点和新点:fun moveSelectedBoneConstrained(x: Double, y: Double) { selectedBoneIndex?.let { val anchorPoint = if (it == 0) bones[1] else bones[0] val angle = Math.atan2((y - anchorPoint.y).toDouble(), (x - anchorPoint.x).toDouble()) val r = calculateDistance(bones[it].x.toDouble(), bones[it].y.toDouble(), anchorPoint.x.toDouble(), anchorPoint.y.toDouble()) val cosX = Math.cos(angle) val sinY = Math.sin(angle) val newX = anchorPoint.x + (r * cosX).toFloat() val newY = anchorPoint.y + (r * sinY).toFloat() bones[it] = PointBig(newX, newY) // 基于初始状态重新生成当前轮廓 transmuteOutlineLinearWithDeltaWeight() } }
为什么这样能解决问题?
所有形变计算都基于不可修改的初始轮廓和骨点,每次移动骨点后,都是从初始状态重新生成当前轮廓,不存在任何累积形变。当骨点移回初始位置时,所有偏移量都会归零,轮廓会精确回到最初的形态。
内容的提问来源于stack exchange,提问作者Dan Anderson
相关产品推荐
相关产品推荐

