You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现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))
}
问题根源

当前实现的核心问题是没有保留初始基准状态:每次形变都是直接在当前轮廓的基础上叠加偏移量,多次移动后,轮廓点的位置已经偏离初始值。即使把骨点移回原位,由于之前的偏移是叠加在修改后的轮廓上,无法反向抵消所有累积的形变,自然没法恢复初始形态。

解决方案

核心思路是保留初始轮廓和初始骨点的原始数据,每次形变都从初始状态重新计算,而不是基于当前修改后的轮廓叠加。具体实现步骤如下:

  1. 存储初始基准数据
    新增两个变量保存初始的轮廓和骨点,后续绝不修改这两个集合:

    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)
    }
    
  2. 重写形变计算函数
    不再基于当前轮廓点计算偏移,而是从初始轮廓点出发,结合当前骨点与初始骨点的差异,加权计算每个轮廓点的最终位置:

    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)
    }
    
  3. 修改骨点移动函数
    移动骨点后,直接调用重写后的形变函数即可,无需传递旧点和新点:

    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 11:22:15