Алгоритм решения точек равномерно распределенной / равномерно зазорной спирали?
Во-первых, просто для наглядного представления о том, чего я добиваюсь, вот самое близкое (но не совсем то, что мне нужно) изображение, которое я нашел:
Вот вся ссылка на сайт: http://www.mathematische-basteleien.de/spiral.htm
НО, это точно не решает проблему, которую я преследую. Я хотел бы сохранить массив точек очень специфического спирального алгоритма.
- Точки распределены равномерно
- 360-градусные циклы имеют равномерный зазор
Если я не ошибаюсь, первые два пункта будут:
- точка [ 0 ] = новая точка (0,0);
- точка [ 1 ] = новая точка (1,0);
Но куда идти отсюда?
Единственные аргументы, которые я хотел бы привести:
- количество точек, которые я хочу разрешить (длина массива).
- расстояние между точками (зазор в пикселях).
- расстояние между циклами.
Для меня почти звучит, что я должен вычислить "спиральную окружность" (если есть такой термин), чтобы построить равномерно распределенные точки вдоль спирали.
Как вы думаете, можно ли надежно использовать радиус 2*PI*?
Если это было сделано раньше, пожалуйста, покажите пример кода!
3 ответа
Веселая маленькая проблема:)
Если вы посмотрите на диаграмму поближе, последовательность четко сформулирована:
Есть, вероятно, много решений для рисования, может быть, более элегантно, но вот мое:
Вы знаете, что гипотенуза является квадратным корнем из текущего числа сегментов +1, а противоположная сторона треугольника всегда равна 1.
Также вы знаете, что синус (Math.sin) угла равен противоположной стороне, деленной на гипотенузу. из старого мнонического SOH(синус, противоположность, гипотенуза),-CAH-TOA.
Math.sin(angle) = opp/hyp
Вы знаете значение синуса для угла, вы знаете две стороны, но вы еще не знаете угол, но вы можете использовать для этого функцию дуги синуса (Math.asin)
angle = Math.asin(opp/hyp)
Теперь вы знаете угол для каждого сегмента и замечаете, что он увеличивается с каждой линией.
Теперь, когда у вас есть угол и радиус (гипотенуза), вы можете использовать формулу от полярной к декартовой для преобразования пары угол-радиус в пару x,y.
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;
}
Это могло бы быть написано в меньшем количестве строк, но я хотел разделить это на две функции: одна, которая имеет дело только с вычислением чисел, и другая, которая имеет дело с рисованием линий.
Вот несколько скриншотов:
Для забавы я добавил версию этого, используя 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>
Джордж ответил отлично! Я долго искал решение.
Вот тот же код, скорректированный для 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
**/
}
}
интересная спираль.
Вот решение, созданное на C++/VCL (извините, я не пишу код в AS3) без гониометрии:
double i,x,y,dx,dy,ii,scale=20.0;
int xx,yy,rr=3;
bmp->Canvas->Pen->Color=clAqua;
bmp->Canvas->Brush->Color=clBlue;
xx=xs2; yy=ys2;
bmp->Canvas->Ellipse(xx-rr,yy-rr,xx+rr,yy+rr);
for (i=1,x=1.0,y=0.0;;i++)
{
// get screen coordinates
xx=xs2+(scale*x);
yy=ys2-(scale*y);
// render actual segment of spiral
if (i==1) bmp->Canvas->MoveTo(xx,yy);
else bmp->Canvas->LineTo(xx,yy);
bmp->Canvas->Ellipse(xx-rr,yy-rr,xx+rr,yy+rr);
// update position
ii=1.0/sqrt(i);
dx=-y*ii;
dy=+x*ii;
x+=dx;
y+=dy;
// stop if out of image area
if ((xx>=xs)||(xx<=0)) break;
if ((yy>=ys)||(xx<=0)) break;
}
гдеbmp
выходное растровое изображение иxs,ys
это его разрешение иxs2,ys2
его половина разрешения (в центре). Вы можете игнорировать материал VCL, это не так важно, что вам действительно нужно, это простоMoveTo/LineTo
.
Идея состоит в том, чтобы просто взять вектор, указывающий на последнюю известную точку.(x,y)
спирали поверните ее на 90 градусов (путем замены координат и отрицания одной) и нормализуйте ее до размера единицы(dx,dy)
и просто добавьте его к последней известной точке, чтобы получить следующую. как мы знаем, размер вектораsqrt(i)
мы можем использовать это напрямую.
Этот метод должен быть более быстрым, поскольку он не использует никаких гониометрических показателей, однако он итеративный, поэтому ошибки округления могут быть проблемой для спиралей со многими точками.
Вот предварительный просмотр: