As3 Функция для экстраполяции точек на кривой сплайна или кривой Эрмита, подобной интерполяции кефрейма? - PullRequest
3 голосов
/ 26 августа 2011

Я надеялся, что кто-нибудь сможет мне помочь в продвинутом переформатировании данных.Я надеюсь на функцию, в которую я могу ввести значение вместе с массивом ключевых позиций, например так:

function remap(percentage:Number, keypoints:Array) { ...

Массив будет начинаться с минимума и заканчиваться максимальной точкой, с вложеннымключевые моменты на этом пути.Например, я бы ввел что-то вроде remap(0.25, [0:0,80:50,100:100] ), и функция «представила бы» график кривой сплайна из (0,0) - (100,100) с ключевой точкой (80,50), а затем вернула значение y, которое25% по этому графику.

Надеюсь, это понятно ... Есть идеи?

1 Ответ

14 голосов
/ 27 августа 2011

Уравнение для кривой Эрмита таково:

Hermite Curve Equation (через Википедию )

Где p (t) - этоточка на кривой в точке t (процент от 0,0 до 1,0)

  1. p0 - первая контрольная точка
  2. m0 - первая опорная точка
  3. p1 - вторая контрольная точкаpoint
  4. m1 - контрольная точка scond

Итак, уравнение в ActionScript будет выглядеть примерно так:

        /*
         * Computes x,y values for a given traversal of a Hermite Curve
         * @param t:Number - a normalized value (0.0 to 1.0) describing path traversal
         * @param points:Array - an array contining the 4 points describing the curve (P0,T0,P1,T1 - always in this order)
         * Anchor points are relative to they're control points
         */
        private function hermite(t:Number,points:Array):Point{
            var result:Point = new Point();
            result.x = (2 * Math.pow(t,3) - 3 * t * t + 1) * points[0].x+
                        (Math.pow(t,3) - 2 * t * t + t) * points[1].x + 
                        (- 2 * Math.pow(t,3) + 3*t*t) * points[2].x +
                        ( Math.pow(t,3) - t*t) * points[3].x;
            result.y = (2 * Math.pow(t,3) - 3 * t * t + 1) * points[0].y+
                        (Math.pow(t,3) - 2 * t * t + t) * points[1].y + 
                        (- 2 * Math.pow(t,3) + 3*t*t) * points[2].y +
                        ( Math.pow(t,3) - t*t) * points[3].y;
            return result;
        }

Вы можете увидеть базовую демонстрацию здесь Hermite Curve Basic Demo

Тем не менее, я немного обеспокоен тем, что в вашем примере упоминаются 3 точки (2 контрольных точки и одна точка привязки).

Кубические кривые (Эрмит)/ Catmull-Rom / и т.д.) имеют 2 контрольные точки и 2 опорные точки (уравнения со степенью 3 - куб.)

Если вам нужна только одна контрольная точка, вам необходимо использовать Quadratic Curve : Quadratic vs Cubic

(Изображение из документации Adobe Actionscript 3)

Кубическая кривая: Cubic

Квадратичная кривая: Quadratic

(Анимации из Википедии)

Квадратичное уравнение таково: Quadratic Equation

Что будет означать:

private function quad(t:Number,p:Array):Point{
            var result:Point = new Point();
            var oneMinusTSq:Number = (1-t) * (1-t);
            var TSq:Number = t*t;
            result.x = oneMinusTSq*p[0].x+2*(1-t)*t*p[1].x+TSq*p[2].x;
            result.y = oneMinusTSq*p[0].y+2*(1-t)*t*p[1].y+TSq*p[2].y;
            return result;
        }

И немного тестаcode:

package {
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Point;

    /**
     * @author george
     */
    public class BasicQuad extends Sprite {
        private var p0:Point = new Point(0,0);
        private var p1:Point = new Point(80,50);
        private var p2:Point = new Point(100,100);
        private var pts:Array = [p0,p1,p2];
        private var t:Number = 0;
        private var pt : Point;

        public function BasicQuad() {
            init();
        }
        private function init():void{
            stage.doubleClickEnabled = true;
            stage.addEventListener(MouseEvent.DOUBLE_CLICK, reset);
            reset();
        }
        private function reset(event : MouseEvent = null) : void {
            graphics.clear();
            graphics.lineStyle(3,0x009900,.5);
            t = 0;
            this.addEventListener(Event.ENTER_FRAME, draw);
        }
        private function draw(event : Event) : void {
            trace(t,pt);
            pt = quad(t, pts);
            if(t == 0) graphics.moveTo(pt.x,pt.y);//draw
            graphics.lineTo(pt.x,pt.y);
            t+= 0.015;
            if(t >= 1) removeEventListener(Event.ENTER_FRAME, draw);//done
        }
        private function quad(t:Number,p:Array):Point{
            var result:Point = new Point();
            var oneMinusTSq:Number = (1-t) * (1-t);
            var TSq:Number = t*t;
            result.x = oneMinusTSq*p[0].x+2*(1-t)*t*p[1].x+TSq*p[2].x;
            result.y = oneMinusTSq*p[0].y+2*(1-t)*t*p[1].y+TSq*p[2].y;
            return result;
        }
    }
}

Кроме того, неясно, что вы подразумеваете под

расширенное переформатирование данных

Фрагменты кода - это формулы, записанные в виде кода, но есть и другие способы вычислить это.

Квадратичная кривая Безье - это путь, пройденный функцией B (t), заданные точки P0, P1 и P2,

Нам нужно перейти от P0 к P1 и от P1 к P2.Поскольку вы ищете решение Flash / ActionScript, мы можем воспользоваться методом Point interpolate () .Таким образом, мы интерполируем между P0 и P1, чтобы получить, скажем, P01, затем из P1 в P2, чтобы получить P12, и интерполяция через все 3 точки будет интерполяцией между P01 и P12:

function quadLerp(t:Number,p:Array):Point {
            var p1:Point = Point.interpolate(p[1], p[0], t);
            var p2:Point = Point.interpolate(p[2], p[1], t);
            return Point.interpolate(p2, p1, t);
        }

Код выглядит немногов противоположность тому, что я написал выше, из-за того, как реализована интерполяция ActionScript: «Уровень интерполяции между двумя точками. Указывает, где будет находиться новая точка, вдоль линии между pt1 и pt2. Если f = 1, возвращается pt1;если f = 0, возвращается pt2. "

ОБНОВЛЕНИЕ

Далее запутано:

В связи с вашим вопросом: мой пример на самом делеупоминает 3 контрольные точки и 0 опорных точек

Вы пытаетесь просто получить значение y текущего x по ряду линий (несколько точек, 0 опорных точек ... прямые линии)?

Помните, мне не нужен сам график - мне просто нужна точка на нем. Просто пройдитесь по прямому графику / неровной линии (вообще никаких кривых)?

Если это так, вы можете сделать что-то вроде этого:

  1. Перебрать все точки вашего пути и найти линию для текущего значения x (это будет линия, для которой начальная позиция x равнаменьше заданного значения x, а конечная позиция x в строке больше заданной позиции x)
  2. Рассчитать соотношение между расстоянием x от начала строки до заданного x и всей линией (конец)..x-start.x)
  3. Используйте это отношение, чтобы разделить текущую строку 'height' (разницу между end.y и start.y) и сместить ее на start.y, используя преимущество аналогичные треугольники, согласно теореме Фалеса

Вот краткий набросок, иллюстрирующий идею: Thales Представьте прямоугольный треугольник, где текущая линия - гипотенуза (ABC).Теперь представьте вертикальную линию от курсора мыши, разделяющую этот треугольник на два одинаковых треугольника (OO ').Маленький треугольник имеет те же углы, что и большой, и его стороны пропорциональны.Вы используете соотношение между AO и AB, чтобы разделить AC и получить длину OO '(позиция y на строке для этого x).

Вот функция:

private function getYforX(x:Number,pts:Vector.<Point>):Number{
            var numPts:int = pts.length;
            for (var i : int = 1; i < numPts; i++) {
                if(x > pts[i-1].x && x < pts[i].x) {//find the line on which the cursor lies
                    t = (x-pts[i-1].x)/(pts[i].x-pts[i-1].x);//ratio between the x distance from the start of the line to mouseX and the whole line (end.x-start.x)
                    return pts[i-1].y + ((pts[i].y-pts[i-1].y) * t);//Thales similar triangles version, cheaper version of Point.interpolate(pts[i], pts[i-1], t).y; 
                }
            }
            return -1;
        }

И короткая демонстрация:

package {
    import flash.events.*;
    import flash.display.*;
    import flash.geom.Point;

    public class LerpPoints extends Sprite {

        private var path:Shape = new Shape();
        private var cursor:Shape = new Shape();
        private var numPts:int = 11;
        private var pts:Vector.<Point> = new Vector.<Point>(numPts,true);
        private var t:Number = 0;

        public function LerpPoints() {
            init();
        }
        private function init():void{
            cursor.graphics.lineStyle(10,0x009900);
            cursor.graphics.drawCircle(-3, -3, 3);
            cursor.graphics.lineStyle(1,0x000099);
            cursor.graphics.moveTo(0, -stage.stageHeight);
            cursor.graphics.lineTo(0, stage.stageHeight);
            reset();
            addChild(path);addChild(cursor);
            addEventListener(Event.ENTER_FRAME, update);
            stage.addEventListener(MouseEvent.MOUSE_DOWN, reset);
        }
        private function reset(event:Event = null):void{
            path.graphics.clear();
            for (var i : int = 0; i < numPts; i++) {
                pts[i] = new Point(i*55,Math.random() * 200);//generate points
                path.graphics.lineStyle(3,0);
                if(i == 0) path.graphics.moveTo(pts[0].x,pts[0].y);//draw path
                path.graphics.lineTo(pts[i].x,pts[i].y);
                if(i > 0){//right angled triangles
                    path.graphics.lineStyle(1,0x990000);
                    path.graphics.lineTo(pts[i-1].x,pts[i].y);
                    path.graphics.lineTo(pts[i-1].x,pts[i-1].y);
                    path.graphics.moveTo(pts[i].x,pts[i].y);
                }
            }
        }
        private function update(event:Event):void{
            cursor.x = mouseX;
            cursor.y = getYforX(mouseX, pts);
        }
        private function getYforX(x:Number,pts:Vector.<Point>):Number{
            var numPts:int = pts.length;
            for (var i : int = 1; i < numPts; i++) {
                if(x > pts[i-1].x && x < pts[i].x) {//find the line on which the cursor lies
                    t = (x-pts[i-1].x)/(pts[i].x-pts[i-1].x);//ratio between the x distance from the start of the line to mouseX and the whole line (end.x-start.x)
                    return pts[i-1].y + ((pts[i].y-pts[i-1].y) * t);//Thales similar triangles version, cheaper version of Point.interpolate(pts[i], pts[i-1], t).y; 
                }
            }
            return -1;
        }
    }
}

Обратите внимание, что это работает, если значения x в вашем массиве точек отсортированы по возрастанию (например, ваш путь идет только слева направо)

Грязный взлом, который приходит на ум, состоит в том, чтобы перебирать пары точек и сохранять значения Y в таблице поиска. Количество циклов будет «деталью линии»

Опять же, это меня смущает:

просто нужно оценивать массив точек (а не только 2) и в идеале сплайновая кривая их, а не просто соединение точек Итак, у вас есть несколько точек, но где появляется сплайн, так как вы упомянули 0 опорных точек?

НТН

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...