import QtQuick import QtQuick.Shapes import "../theme" as Theme Item { id: root required property vector2d start required property vector2d end property double segmentCount: 8 property double segmentLen: 50 property alias color: path.strokeColor readonly property Component p: PathLine { property vector2d pos property vector2d prevPos: pos property vector2d acc x: pos.x y: pos.y } readonly property double gravity: 5000 readonly property int constraintRunCount: 40 Shape { id: shape anchors.fill: parent preferredRendererType: Shape.CurveRenderer ShapePath { id: path capStyle: ShapePath.RoundCap strokeColor: Theme.Colors.ropeColor1 strokeWidth: 10 fillColor: Theme.Colors.ropeColor2 } } Component.onCompleted: () => { // Create the points const xInc = (end.x - start.x) / segmentCount; const yInc = (end.y - start.y) / segmentCount; let i = 0; while (i < segmentCount) { path.pathElements.push(p.createObject(root, { pos: start.plus(Qt.vector2d(xInc * i, yInc * i)), acc: Qt.vector2d(0, root.gravity) })); i++; } // Set up the first and last points path.pathElements[0].acc.y = 0; path.pathElements[segmentCount - 1].acc.y = 0; path.startX = path.pathElements[0].x; path.startY = path.pathElements[0].y; } FrameAnimation { running: true onTriggered: () => { const dt = frameTime; for (const p of path.pathElements.slice(1, root.segmentCount - 2)) { // https://en.wikipedia.org/wiki/Verlet_integration const newPos = p.pos.times(2.0).minus(p.prevPos).plus(p.acc.times(dt * dt)); p.prevPos = p.pos; p.pos = newPos; } for (let i = 0; i < root.segmentCount - 1; i++) { const cur = path.pathElements[i]; const next = path.pathElements[i + 1]; // Keep fixed distance between segments for (let j = 0; j < root.constraintRunCount; j++) { const toNext = next.pos.minus(cur.pos); const distToNext = toNext.length(); const error = root.segmentLen - distToNext; const pull = toNext.times(1.0 / distToNext).times(error).times(0.1); if (i != 0) { cur.pos = cur.pos.minus(pull.times(0.5)); next.pos = next.pos.plus(pull.times(0.5)); } else { next.pos = next.pos.plus(pull); } } } path.pathElements[0].pos = root.start; path.pathElements[root.segmentCount - 1].pos = root.end; path.startX = path.pathElements[0].x; path.startY = path.pathElements[0].y; } } }