Алгоритм решения точек равномерно распределенной / равномерно зазорной спирали? - PullRequest
7 голосов
/ 03 декабря 2010

Во-первых, просто для наглядного представления о том, что мне нужно, вот ближайший результат (но не совсем то, что я ищу), который я нашел:

enter image description here

Вот вся ссылка на сайт: http://www.mathematische -basteleien.de / spiral.htm

НО, это точно не решает проблему, которую я преследую. Я хотел бы сохранить массив точек очень специфического спирального алгоритма.

  • Точки распределены равномерно
  • 360-градусные циклы имеют равномерный зазор

Если я не ошибаюсь, первые два пункта будут:

  • точка [0] = новая точка (0,0);
  • точка [1] = новая точка (1,0);

Но куда идти отсюда?

Единственные аргументы, которые я хотел бы привести:

  • количество точек, которые я хочу разрешить (длина массива).
  • расстояние между точками (зазор в пикселях).
  • расстояние между циклами.

Для меня почти звучит, что мне нужно вычислить " спиральная окружность " (если есть такой термин), чтобы построить равномерно распределенные точки вдоль спирали.

Может ли 2 * PI * радиус быть надежно использован для этого расчета, как вы думаете?

Если это было сделано ранее, приведите пример кода!

Ответы [ 2 ]

18 голосов
/ 04 декабря 2010

Забавная маленькая проблема:)

Если вы посмотрите на диаграмму поближе, последовательность будет четко сформулирована:

spiral diagram

Возможно, есть много решений для рисования, может быть, более элегантно, но вот мое:

Вы знаете, что гипотенуза является квадратным корнем из текущего количества сегментов + 1 и противоположная сторона треугольника всегда равна 1.

Также вы знаете, что синус (Math.sin) угла равен противоположной стороне, деленной на гипотенузу. из старой мнонической SOH (синус, противоположность, гипотенуза), - CAH-TOA.

Math.sin(angle) = opp/hyp

Вы знаете значение синуса для угла, вы знаете две стороны, но вы еще не знаете угол, но вы можете использовать для этого функцию дуги синуса (Math.asin)

angle = Math.asin(opp/hyp)

Теперь вы знаете угол для каждого сегмента и замечаете, что он увеличивается с каждой линией.

Теперь, когда у вас есть угол и радиус (гипотенуза), вы можете использовать для полярная в декартовая формула для преобразования этой пары angle, radius х, у пара.

x = Math.cos(angle) * radius;
y = Math.sin(angle) * radius;

Поскольку вы запросили решение для ActionScript, класс Point уже предоставляет вам эту функцию с помощью метода polar () . Вы передаете ему радиус и угол, и он возвращает ваши x и y в объекте Point.

Вот небольшой фрагмент, который показывает спираль. Вы можете контролировать количество сегментов, перемещая мышь по оси Y.

var sw:Number = stage.stageWidth,sh:Number = stage.stageHeight;
this.addEventListener(Event.ENTER_FRAME,update);
function update(event:Event):void{
    drawTheodorus(144*(mouseY/sh),sw*.5,sh*.5,20);
}
//draw points
function drawTheodorus(segments:int,x:Number,y:Number,scale:Number):void{
    graphics.clear();
    var points:Array = getTheodorus(segments,scale);
    for(var i:int = 0 ; i < segments; i++){
        points[i].offset(x,y);
        graphics.lineStyle(1,0x990000,1.05-(.05+i/segments));
        graphics.moveTo(x,y);//move to centre
        graphics.lineTo(points[i].x,points[i].y);//draw hypotenuse
        graphics.lineStyle(1+(i*(i/segments)*.05),0,(.05+i/segments));
        if(i > 0) graphics.lineTo(points[i-1].x,points[i-1].y);//draw opposite
    }
}
//calculate points
function getTheodorus(segments:int = 1,scale:Number = 10):Array{
    var result = [];
    var radius:Number = 0;
    var angle:Number = 0;
    for(var i:int = 0 ; i < segments ; i++){
        radius = Math.sqrt(i+1);
        angle += Math.asin(1/radius);//sin(angle) = opposite/hypothenuse => used asin to get angle
        result[i] = Point.polar(radius*scale,angle);//same as new Point(Math.cos(angle)*radius.scale,Math.sin(angle)*radius.scale)
    }
    return result;
}

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

Вот несколько скриншотов:

spiral 1

spiral 2

spiral 3

Ради интереса я добавил эту версию, используя ProcessingJS здесь . Работает немного медленно, поэтому я бы порекомендовал Chromium / Chrome для этого.

Теперь вы можете запустить этот код прямо здесь (двигать мышь вверх и вниз):

var totalSegments = 850,hw = 320,hh = 240,segments;
var len = 10;
points = [];
function setup(){
  createCanvas(640,480);
  smooth();
  colorMode(HSB,255,100,100);
  stroke(0);
  noFill();
  //println("move cursor vertically");
}
function draw(){
  background(0);
  translate(hw,hh);
  segments = floor(totalSegments*(mouseY/height));
  points = getTheodorus(segments,len);
  for(var i = 0 ; i < segments ; i++){
    strokeWeight(1);
    stroke(255-((i/segments) * 255),100,100,260-((i/segments) * 255));
    line(0,0,points[i].x,points[i].y);
    // strokeWeight(1+(i*(i/segments)*.01));
    strokeWeight(2);
    stroke(0,0,100,(20+i/segments));
    if(i > 0) line(points[i].x,points[i].y,points[i-1].x,points[i-1].y);
  }
}
function getTheodorus(segments,len){
  var result = [];
  var radius = 0;
  var angle = 0;
  for(var i = 0 ; i < segments ; i++){
    radius = sqrt(i+1);
    angle += asin(1/radius);
    result[i] = new p5.Vector(cos(angle) * radius*len,sin(angle) * radius*len);
  }
  return result;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.4/p5.min.js"></script>
3 голосов
/ 12 июня 2011

Джордж ответил отлично! Я долго искал решение.

Вот тот же код, скорректированный для PHP, на случай, если он кому-нибудь поможет. Я использую скрипт для рисования точек (= городов) для карты с координатами X, Y. X начинается слева, Y начинается снизу слева.

<?
/**
 * Initialize variables
 **/

// MAXIMUM width & height of canvas (X: 0->400, Y: 0->400)
$width = 400;

// For loop iteration amount, adjust this manually
$segments = 10000;

// Scale for radius
$radiusScale = 2;

// Draw dot (e.g. a city in a game) for every N'th drawn point
$cityForEveryNthDot = 14; 

/**
 * Private variables
 **/
$radius = 0;
$angle = 0;
$centerPoint = $width/2;

/**
 * Container print
 **/
print("<div style=\"width: ${width}px; height: ${width}px; background: #cdcdcd; z-index: 1; position: absolute; left: 0; top: 0;\"></div>");

/**
 * Looper
 **/
for($i=0;$i<$segments;$i++) {
    // calculate radius and angle
    $radius = sqrt($i+1) * $radiusScale;
    $angle += asin(1/$radius);

    // skip this point, if city won't be created here
    if($i % $cityForEveryNthDot != 0) {
        continue;
    }   

    // calculate X & Y (from top left) for this point
    $x = cos($angle) * $radius;
    $y = sin($angle) * $radius;

    // print dot
    print("<div style=\"width: 1px; height: 1px; background: black; position: absolute; z-index: 2; left: " . round($x+$centerPoint) . "; top: " . round($y+$centerPoint) . ";\"></div>");

    // calculate rounded X & Y (from bottom left)
    $xNew = round($x+$centerPoint);
    $yNew = round($width - ($y+$centerPoint));

    // just some internal checks
    if($xNew > 1 && $yNew > 1 && $xNew < $width && $yNew < $width) {
        /**
         * do something (e.g. store to database). Use xNew and yNew
         **/
    }   
}
...