Sierpinski arrowhead curve: Difference between revisions
No edit summary |
|||
Line 696: | Line 696: | ||
lns = Nest[Catenate[DoStep /@ #] &, lns, 5]; |
lns = Nest[Catenate[DoStep /@ #] &, lns, 5]; |
||
Graphics[lns]</lang> |
Graphics[lns]</lang> |
||
=={{header|Nim}}== |
|||
{{trans|C++}} |
|||
Output is an SVG file. |
|||
<lang Nim>import math |
|||
const Sqrt3_2 = sqrt(3.0) / 2.0 |
|||
type Point = tuple[x, y: float] |
|||
func sierpinskiArrowheadNext(points: seq[Point]): seq[Point] = |
|||
result.setLen(3 * (points.len - 1) + 1) |
|||
var j = 0 |
|||
for i in 0..<points.high: |
|||
let (x0, y0) = points[i] |
|||
let (x1, y1) = points[i + 1] |
|||
let dx = x1 - x0 |
|||
result[j] = (x0, y0) |
|||
if y0 == y1: |
|||
let d = abs(dx * Sqrt3_2 / 2) |
|||
result[j + 1] = (x0 + dx / 4, y0 - d) |
|||
result[j + 2] = (x1 - dx / 4, y0 - d) |
|||
elif y1 < y0: |
|||
result[j + 1] = (x1, y0) |
|||
result[j + 2] = (x1 + dx / 2, (y0 + y1) / 2) |
|||
else: |
|||
result[j + 1] = (x0 - dx / 2, (y0 + y1) / 2) |
|||
result[j + 2] = (x0, y1) |
|||
inc j, 3 |
|||
result[j] = points[^1] |
|||
proc writeSierpinskiArrowhead(outfile: File; size, iterations: int) = |
|||
outfile.write "<svg xmlns='http://www.w3.org/2000/svg' width='", size, "' height='", size, "'>\n" |
|||
outfile.write "<rect width='100%' height='100%' fill='white'/>\n" |
|||
outfile.write "<path stroke-width='1' stroke='black' fill='none' d='" |
|||
const Margin = 20.0 |
|||
let side = size.toFloat - 2 * Margin |
|||
let x = Margin |
|||
let y = 0.5 * size.toFloat + 0.5 * Sqrt3_2 * side |
|||
var points = @[(x: x, y: y), (x: x + side, y: y)] |
|||
for _ in 1..iterations: |
|||
points = sierpinskiArrowheadNext(points) |
|||
for i, point in points: |
|||
outfile.write if i == 0: 'M' else: 'L', point.x, ',', point.y, '\n' |
|||
outfile.write "'/>\n</svg>\n" |
|||
let outfile = open("sierpinski_arrowhead.svg", fmWrite) |
|||
outfile.writeSierpinskiArrowhead(600, 8) |
|||
outfile.close()</lang> |
|||
{{out}} |
|||
See output of C++ program. |
|||
=={{header|Perl}}== |
=={{header|Perl}}== |
Revision as of 22:05, 28 June 2021
- Task
Produce a graphical or ASCII-art representation of a Sierpinski arrowhead curve of at least order 3.
ALGOL W
Produces an Ascii Art Sierpinski Arrowhead Curve using the algorithm from the Wikipedia page.
Note that the Wikipedia algotrithm draws even order curves with the base at the top.
<lang algolw>begin % draw sierpinski arrowhead curves using ascii art %
integer CANVAS_WIDTH; CANVAS_WIDTH := 200; begin % the ascii art canvas and related items % string(1) array canvas ( 1 :: CANVAS_WIDTH, 1 :: CANVAS_WIDTH ); integer heading, asciiX, asciiY, width, maxX, maxY, minX, minY; % draw a line using ascii art - the length is ignored and the heading determines the % % character to use % % the position is updated % procedure drawLine( real value length ) ; begin % stores the min and max coordinates % procedure updateCoordinateRange ; begin if asciiX > maxX then maxX := asciiX; if asciiY > maxY then maxY := asciiY; if asciiX < minX then minX := asciiX; if asciiY < minY then minY := asciiY end updateCoordinateRange ; if heading = 0 then begin updateCoordinateRange; canvas( asciiX, asciiY ) := "_"; asciiX := asciiX + 1 end else if heading = 60 then begin updateCoordinateRange; canvas( asciiX, asciiY ) := "/"; asciiY := asciiY - 1; asciiX := asciiX + 1 end else if heading = 120 then begin asciiX := asciiX - 1; updateCoordinateRange; canvas( asciiX, asciiY ) := "\"; asciiY := asciiY - 1 end else if heading = 180 then begin asciiX := asciiX - 1; updateCoordinateRange; canvas( asciiX, asciiY ) := "_" end else if heading = 240 then begin asciiX := asciiX - 1; asciiY := asciiY + 1; updateCoordinateRange; canvas( asciiX, asciiY ) := "/" end else if heading = 300 then begin asciiY := asciiY + 1; updateCoordinateRange; canvas( asciiX, asciiY ) := "\"; asciiX := asciiX + 1 end if_various_headings end drawLine ; % changes the heading by the specified angle ( in degrees ) - angle must be +/- 60 % procedure turn( integer value angle ) ; if angle > 0 then heading := ( heading + angle ) rem 360 else begin heading := heading + angle; if heading < 0 then heading := heading + 360 end tuen ; % initialises the ascii art canvas % procedure initArt ; begin heading := 0; asciiX := CANVAS_WIDTH div 2; asciiY := asciiX; maxX := asciiX; maxY := asciiY; minX := asciiX; minY := asciiY; for x := 1 until CANVAS_WIDTH do for y := 1 until CANVAS_WIDTH do canvas( x, y ) := " " end initArt ; % shows the used parts of the canvas % procedure drawArt ; begin for y := minY until maxY do begin write(); for x := minX until maxX do writeon( canvas( x, y ) ) end for_y ; write() end drawIArt ; % draws a sierpinski arrowhead curve of the specified order and line length % procedure sierpinskiArrowheadCurve( integer value order; real value length ) ; begin % recursively draws a segment of the sierpinski arrowhead curve % procedure curve( integer value order; real value length; integer value angle ) ; if 0 = order then drawline( length ) else begin curve( order - 1, length / 2, - angle ); turn( angle ); curve( order - 1, length / 2, angle ); turn( angle ); curve( order - 1, length / 2, - angle ) end curve ; if not odd( order ) then begin % order is even, we can just draw the curve. % curve( order, length, +60 ); end else begin % order is odd % turn( +60 ); curve( order, length, -60 ) end if_not_odd_order__ end sierpinskiArrowheadCurve ; % draw curves % i_w := 1; s_w := 0; % set output formatting % for order := 5 do begin write( "Sierpinski arrowhead curve of order ", order ); write( "=====================================" ); write(); initArt; sierpinskiArrowheadCurve( order, 1 ); drawArt end for_order end
end.</lang>
- Output:
Sierpinski arrowhead curve of order 5 ===================================== _ / \ \ / _/ \_ / \ \_ _/ _ \ / _ / \_/ \_/ \ \ / _/ \_ / _ _ \ \_/ \ / \_/ _ / \ _ / \ \_ _/ / \ \ / _ \ / _ \ / _/ \_/ \_/ \_/ \_/ \_ / \ \_ _/ _ \ / _ / \_/ \_/ \ \ _ _ / _/ / \ / \ \_ / _ \ / \ / _ \ \_/ \_/ \_ _/ \_/ \_/ _ \ / _ / \ _/ \_ / \ \ / / _ _ \ \ / _/ \_ \_/ \ / \_/ _/ \_ / \ _ / \ _ / \ \_ _/ / \ \_ _/ / \ \_ _/ _ \ / _ \ / _ \ / _ \ / _ \ / _ / \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \
AutoHotkey
Requires Gdip Library <lang AutoHotkey>order := 7 theta := 0
curve := [] curve.curveW := 1000 curve.curveH := 1000 curve.iy := 1 curve.cx := curve.curveW/2 curve.cy := curve.curveH curve.ch := curve.cx/2
arrowhead(order, curve, theta, Arr :=[]) xmin := xmax := ymin := ymax := 0 for i, point in Arr { xmin := A_Index = 1 ? point.x : xmin < point.x ? xmin : point.x xmax := point.x > xmax ? point.x : xmax ymin := A_Index = 1 ? point.y : ymin < point.y ? ymin : point.y ymax := point.y > ymax ? point.y : ymax } arrowheadX := A_ScreenWidth/2 - (xmax-xmin)/2 , arrowheadY := A_ScreenHeight/2 - (ymax-ymin)/2 for i, point in Arr points .= point.x - xmin + arrowheadX "," point.y - ymin + arrowheadY "|"
points := Trim(points, "|") gdip1() Gdip_DrawLines(G, pPen, Points) UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height) return
- ---------------------------------------------------------------
arrowhead(order, curve, theta, Arr) { length := curve.cx if (order&1 = 0) curve(order, length, theta, 60, Arr) else { theta := turn(theta, 60) theta := curve(order, length, theta, -60, Arr) } drawLine(length, theta, Arr) }
- ---------------------------------------------------------------
drawLine(length, theta, Arr) { global curve Arr[Arr.count()+1, "x"] := curve.cx-curve.curveW/2+curve.ch Arr[Arr.count(), "y"] := (curve.curveH-curve.cy)*curve.iy+2*curve.ch pi := 3.141592653589793 curve.cx := curve.cx + length * Cos(theta*pi/180) curve.cy := curve.cy + length * Sin(theta*pi/180) }
- ---------------------------------------------------------------
turn(theta, angle) { return theta := Mod(theta+angle, 360) }
- ---------------------------------------------------------------
curve(order, length, theta, angle, Arr) { if (order = 0) drawLine(length, theta, Arr) else { theta := curve(order-1, length/2, theta, -angle, Arr) theta := turn(theta, angle) theta := curve(order-1, length/2, theta, angle, Arr) theta := turn(theta, angle) theta := curve(order-1, length/2, theta, -angle, Arr) } return theta }
- ---------------------------------------------------------------
gdip1(){ global If !pToken := Gdip_Startup() { MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system ExitApp } OnExit, Exit Width := A_ScreenWidth, Height := A_ScreenHeight Gui, 1: -Caption +E0x80000 +LastFound +OwnDialogs +Owner +AlwaysOnTop Gui, 1: Show, NA hwnd1 := WinExist() hbm := CreateDIBSection(Width, Height) hdc := CreateCompatibleDC() obm := SelectObject(hdc, hbm) G := Gdip_GraphicsFromHDC(hdc) Gdip_SetSmoothingMode(G, 4) pPen := Gdip_CreatePen(0xFFFF0000, 2) }
- ---------------------------------------------------------------
gdip2(){ global Gdip_DeleteBrush(pBrush) Gdip_DeletePen(pPen) SelectObject(hdc, obm) DeleteObject(hbm) DeleteDC(hdc) Gdip_DeleteGraphics(G) }
- ---------------------------------------------------------------
Exit: gdip2() Gdip_Shutdown(pToken) ExitApp Return</lang>
C
This code is based on the Phix and Go solutions, but produces a file in SVG format. <lang c>// See https://en.wikipedia.org/wiki/Sierpi%C5%84ski_curve#Arrowhead_curve
- include <math.h>
- include <stdio.h>
- include <stdlib.h>
// Structure to keep track of current position and orientation typedef struct cursor_tag {
double x; double y; int angle;
} cursor_t;
void turn(cursor_t* cursor, int angle) {
cursor->angle = (cursor->angle + angle) % 360;
}
void draw_line(FILE* out, cursor_t* cursor, double length) {
double theta = (M_PI * cursor->angle)/180.0; cursor->x += length * cos(theta); cursor->y += length * sin(theta); fprintf(out, "L%g,%g\n", cursor->x, cursor->y);
}
void curve(FILE* out, int order, double length, cursor_t* cursor, int angle) {
if (order == 0) { draw_line(out, cursor, length); } else { curve(out, order - 1, length/2, cursor, -angle); turn(cursor, angle); curve(out, order - 1, length/2, cursor, angle); turn(cursor, angle); curve(out, order - 1, length/2, cursor, -angle); }
}
void write_sierpinski_arrowhead(FILE* out, int size, int order) {
const double margin = 20.0; const double side = size - 2.0 * margin; cursor_t cursor; cursor.angle = 0; cursor.x = margin; cursor.y = 0.5 * size + 0.25 * sqrt(3) * side; if ((order & 1) != 0) turn(&cursor, -60); fprintf(out, "<svg xmlns='http://www.w3.org/2000/svg' width='%d' height='%d'>\n", size, size); fprintf(out, "<rect width='100%%' height='100%%' fill='white'/>\n"); fprintf(out, "<path stroke-width='1' stroke='black' fill='none' d='"); fprintf(out, "M%g,%g\n", cursor.x, cursor.y); curve(out, order, side, &cursor, 60); fprintf(out, "'/>\n</svg>\n");
}
int main(int argc, char** argv) {
const char* filename = "sierpinski_arrowhead.svg"; if (argc == 2) filename = argv[1]; FILE* out = fopen(filename, "w"); if (!out) { perror(filename); return EXIT_FAILURE; } write_sierpinski_arrowhead(out, 600, 8); fclose(out); return EXIT_SUCCESS;
}</lang>
- Output:
See: sierpinski_arrowhead.svg (offsite SVG image)
C++
The output of this program is an SVG file. <lang cpp>#include <fstream>
- include <iostream>
- include <vector>
constexpr double sqrt3_2 = 0.86602540378444; // sqrt(3)/2
struct point {
double x; double y;
};
std::vector<point> sierpinski_arrowhead_next(const std::vector<point>& points) {
size_t size = points.size(); std::vector<point> output(3*(size - 1) + 1); double x0, y0, x1, y1; size_t j = 0; for (size_t i = 0; i + 1 < size; ++i, j += 3) { x0 = points[i].x; y0 = points[i].y; x1 = points[i + 1].x; y1 = points[i + 1].y; double dx = x1 - x0; output[j] = {x0, y0}; if (y0 == y1) { double d = dx * sqrt3_2/2; if (d < 0) d = -d; output[j + 1] = {x0 + dx/4, y0 - d}; output[j + 2] = {x1 - dx/4, y0 - d}; } else if (y1 < y0) { output[j + 1] = {x1, y0}; output[j + 2] = {x1 + dx/2, (y0 + y1)/2}; } else { output[j + 1] = {x0 - dx/2, (y0 + y1)/2}; output[j + 2] = {x0, y1}; } } output[j] = {x1, y1}; return output;
}
void write_sierpinski_arrowhead(std::ostream& out, int size, int iterations) {
out << "<svg xmlns='http://www.w3.org/2000/svg' width='" << size << "' height='" << size << "'>\n"; out << "<rect width='100%' height='100%' fill='white'/>\n"; out << "<path stroke-width='1' stroke='black' fill='none' d='"; const double margin = 20.0; const double side = size - 2.0 * margin; const double x = margin; const double y = 0.5 * size + 0.5 * sqrt3_2 * side; std::vector<point> points{{x, y}, {x + side, y}}; for (int i = 0; i < iterations; ++i) points = sierpinski_arrowhead_next(points); for (size_t i = 0, n = points.size(); i < n; ++i) out << (i == 0 ? "M" : "L") << points[i].x << ',' << points[i].y << '\n'; out << "'/>\n</svg>\n";
}
int main() {
std::ofstream out("sierpinski_arrowhead.svg"); if (!out) { std::cerr << "Cannot open output file\n"; return EXIT_FAILURE; } write_sierpinski_arrowhead(out, 600, 8); return EXIT_SUCCESS;
}</lang>
- Output:
See: sierpinski_arrowhead.svg (offsite SVG image)
Factor
<lang factor>USING: accessors L-system ui ;
- arrowhead ( L-system -- L-system )
L-parser-dialect >>commands [ 60 >>angle ] >>turtle-values "XF" >>axiom { { "X" "YF+XF+Y" } { "Y" "XF-YF-X" } } >>rules ;
[ <L-system> arrowhead "Arrowhead" open-window ] with-ui</lang>
When using the L-system visualizer, the following controls apply:
Button | Command |
---|---|
a | zoom in |
z | zoom out |
left arrow | turn left |
right arrow | turn right |
up arrow | pitch down |
down arrow | pitch up |
q | roll left |
w | roll right |
Button | Command |
---|---|
x | iterate L-system |
Forth
ASCII
<lang forth>( ASCII output with use of ANSI terminal control )
- draw-line ( direction -- )
case 0 of .\" _" endof ( horizontal right: _ ) 1 of .\" \e[B\\" endof ( down right: CUD \ ) 2 of .\" \e[D\e[B/\e[D" endof ( down left: CUB CUD / CUB ) 3 of .\" \e[D_\e[D" endof ( horizontal left: CUB _ CUB ) 4 of .\" \e[D\\\e[A\e[D" endof ( up left: CUB \ CUU CUB ) 5 of .\" /\e[A" endof ( up right: / CUU ) endcase ( cursor is up-right of the last point )
- turn+ 1+ 6 mod ;
- turn- 1- 6 mod ;
defer curve
- A-rule ( order direction -- ) turn+ 2dup 'B curve turn- 2dup 'A curve turn- 'B curve ;
- B-rule ( order direction -- ) turn- 2dup 'A curve turn+ 2dup 'B curve turn+ 'A curve ;
- noname ( order direction type -- )
2 pick 0 = if drop draw-line drop exit then \ draw line when order is 0 rot 1- rot rot 'A = if A-rule else B-rule then
- is curve
- arrowhead ( order -- )
page s" Sierpinski arrowhead curve of order " type dup . cr s" =====================================" type cr 0 'A curve
5 arrowhead</lang>
- Output:
Sierpinski arrowhead curve of order 5 ===================================== _ _ _ _ _ _ _ _ _ _ ok \_/ \ / \_/ \ / \_/ \ / \_/ \ / \_/ \ / \_/ _/ \_ / \ _/ \_ / \ _/ \_ / \ \_/ / \ \_/ / \ \_ _/ _ \ / _ \_ _/ \ / / \_/ \_/ \ \ / / \ \_ _/ / \ \_/ \ / \_/ _ _ _/ \_ _ _ / \_/ \ / \ / \_/ \ \_ / \ / \ _/ \ \_/ \_/ / / _ _ \ \_/ \ / \_/ _/ \_ / \ \_ _ _ _ _ _/ \ / \_/ \ / \_/ \ / / \ _/ \_ / \ \_/ / \ \_/ _ \ / _ / \_/ \_/ \ \_ _/ \ / / _ _ \ \_/ \ / \_/ _/ \_ / \ \_ _/ \ / / \ \_/
SVG file
<lang forth>( SVG ouput )
- draw-line ( direction -- ) \ line-length=10 ; sin(60)=0.87 ; cos(60)=0.5
case 0 of s" h 10" type cr endof 1 of s" l 5 8.7" type cr endof 2 of s" l -5 8.7" type cr endof 3 of s" h -10" type cr endof 4 of s" l -5 -8.7" type cr endof 5 of s" l 5 -8.7" type cr endof endcase
- turn+ 1+ 6 mod ;
- turn- 1- 6 mod ;
defer curve
- A-rule ( order direction -- ) turn+ 2dup 'B curve turn- 2dup 'A curve turn- 'B curve ;
- B-rule ( order direction -- ) turn- 2dup 'A curve turn+ 2dup 'B curve turn+ 'A curve ;
- noname ( order direction type -- )
2 pick 0 = if drop draw-line drop exit then \ draw line when order is 0 rot 1- rot rot 'A = if A-rule else B-rule then
- is curve
- raw. ( u -- ) 0 <# #s #> type ;
- svg-start
dup 1 swap lshift 10 * ( -- order image-width ) \ image-width is 2 power order s" sierpinski_arrowhead.svg" w/o create-file throw to outfile-id s" <svg xmlns='http://www.w3.org/2000/svg' width='" type dup raw. 87 * 100 / ( -- order image-height ) \ image-height; sin(60)=0.87 s" ' height='" type raw. s" '>" type cr s" <rect width='100%' height='100%' fill='white'/>" type cr s" <path stroke-width='1' stroke='black' fill='none' d='" type cr s" M 0 0" type cr
- svg-end
s" '/> </svg>" type cr outfile-id close-file throw
- arrowhead ( order -- )
outfile-id >r svg-start 0 'A curve svg-end r> to outfile-id
5 arrowhead</lang>
Go
A partial translation anyway which produces a static image of a SAC of order 6, magenta on black, which can be viewed with a utility such as EOG. <lang go>package main
import (
"github.com/fogleman/gg" "math"
)
var (
width = 770.0 height = 770.0 dc = gg.NewContext(int(width), int(height)) iy = 1.0 theta = 0
)
var cx, cy, h float64
func arrowhead(order int, length float64) {
// if order is even, we can just draw the curve if order&1 == 0 { curve(order, length, 60) } else { turn(60) curve(order, length, -60) } drawLine(length) // needed to make base symmetric
}
func drawLine(length float64) {
dc.LineTo(cx-width/2+h, (height-cy)*iy+2*h) rads := gg.Radians(float64(theta)) cx += length * math.Cos(rads) cy += length * math.Sin(rads)
}
func turn(angle int) {
theta = (theta + angle) % 360
}
func curve(order int, length float64, angle int) {
if order == 0 { drawLine(length) } else { curve(order-1, length/2, -angle) turn(angle) curve(order-1, length/2, angle) turn(angle) curve(order-1, length/2, -angle) }
}
func main() {
dc.SetRGB(0, 0, 0) // black background dc.Clear() order := 6 if order&1 == 0 { iy = -1 // apex will point upwards } cx, cy = width/2, height h = cx / 2 arrowhead(order, cx) dc.SetRGB255(255, 0, 255) // magenta curve dc.SetLineWidth(2) dc.Stroke() dc.SavePNG("sierpinski_arrowhead_curve.png")
}</lang>
Julia
<lang julia>using Lindenmayer # https://github.com/cormullion/Lindenmayer.jl
scurve = LSystem(Dict("F" => "G+F+Gt", "G"=>"F-G-F"), "G")
drawLSystem(scurve,
forward = 3, turn = 60, startingy = -350, iterations = 8, startingorientation = π/3, filename = "sierpinski_arrowhead_curve.png", showpreview = true
) </lang>
Mathematica / Wolfram Language
<lang Mathematica>ClearAll[DoStep] DoStep[Line[{x_, y_}]] := Module[{diff, perp, pts},
diff = y - x; perp = Cross[diff] Sqrt[3]/2; pts = {x, x + diff/4 + perp/2, x + 3 diff/4 + perp/2, y}; {Line[pts[[{2, 1}]]], Line[pts[[{2, 3}]]], Line[pts[[{4, 3}]]]} ]
lns = {Line[{{0.0, 0.0}, {1.0, 0.0}}]}; lns = Nest[Catenate[DoStep /@ #] &, lns, 5]; Graphics[lns]</lang>
Nim
Output is an SVG file. <lang Nim>import math
const Sqrt3_2 = sqrt(3.0) / 2.0
type Point = tuple[x, y: float]
func sierpinskiArrowheadNext(points: seq[Point]): seq[Point] =
result.setLen(3 * (points.len - 1) + 1) var j = 0 for i in 0..<points.high: let (x0, y0) = points[i] let (x1, y1) = points[i + 1] let dx = x1 - x0 result[j] = (x0, y0) if y0 == y1: let d = abs(dx * Sqrt3_2 / 2) result[j + 1] = (x0 + dx / 4, y0 - d) result[j + 2] = (x1 - dx / 4, y0 - d) elif y1 < y0: result[j + 1] = (x1, y0) result[j + 2] = (x1 + dx / 2, (y0 + y1) / 2) else: result[j + 1] = (x0 - dx / 2, (y0 + y1) / 2) result[j + 2] = (x0, y1) inc j, 3 result[j] = points[^1]
proc writeSierpinskiArrowhead(outfile: File; size, iterations: int) =
outfile.write "<svg xmlns='http://www.w3.org/2000/svg' width='", size, "' height='", size, "'>\n" outfile.write "<rect width='100%' height='100%' fill='white'/>\n" outfile.write "<path stroke-width='1' stroke='black' fill='none' d='" const Margin = 20.0 let side = size.toFloat - 2 * Margin let x = Margin let y = 0.5 * size.toFloat + 0.5 * Sqrt3_2 * side var points = @[(x: x, y: y), (x: x + side, y: y)] for _ in 1..iterations: points = sierpinskiArrowheadNext(points) for i, point in points: outfile.write if i == 0: 'M' else: 'L', point.x, ',', point.y, '\n' outfile.write "'/>\n</svg>\n"
let outfile = open("sierpinski_arrowhead.svg", fmWrite)
outfile.writeSierpinskiArrowhead(600, 8)
outfile.close()</lang>
- Output:
See output of C++ program.
Perl
<lang perl>use strict; use warnings; use SVG; use List::Util qw(max min); use constant pi => 2 * atan2(1, 0);
my %rules = (
X => 'YF+XF+Y', Y => 'XF-YF-X'
); my $S = 'Y'; $S =~ s/([XY])/$rules{$1}/eg for 1..7;
my (@X, @Y); my ($x, $y) = (0, 0); my $theta = 0; my $r = 6;
for (split //, $S) {
if (/F/) { push @X, sprintf "%.0f", $x; push @Y, sprintf "%.0f", $y; $x += $r * cos($theta); $y += $r * sin($theta); } elsif (/\+/) { $theta += pi/3; } elsif (/\-/) { $theta -= pi/3; }
}
my ($xrng, $yrng) = ( max(@X) - min(@X), max(@Y) - min(@Y)); my ($xt, $yt) = (-min(@X) + 10, -min(@Y) + 10);
my $svg = SVG->new(width=>$xrng+20, height=>$yrng+20); my $points = $svg->get_path(x=>\@X, y=>\@Y, -type=>'polyline'); $svg->rect(width=>"100%", height=>"100%", style=>{'fill'=>'black'}); $svg->polyline(%$points, style=>{'stroke'=>'orange', 'stroke-width'=>1}, transform=>"translate($xt,$yt)");
open my $fh, '>', 'sierpinski-arrowhead-curve.svg'; print $fh $svg->xmlify(-namespace=>'svg'); close $fh;</lang> See: sierpinski-arrowhead-curve.svg (offsite SVG image)
Phix
<lang Phix>-- demo\rosetta\Sierpinski_arrowhead_curve.exw -- -- Draws curves lo to hi (simultaneously), initially {6,6}, max {10,10} -- Press +/- to change hi, shift +/- to change lo. -- ("=_" are also mapped to "+-", for the non-numpad +/-) -- include pGUI.e
Ihandle dlg, canvas cdCanvas cddbuffer, cdcanvas
integer width, height,
lo = 6, hi = 6
atom cx, cy, h, theta
integer iy = +1
procedure draw_line(atom l)
cdCanvasVertex(cddbuffer, cx-width/2+h, (height-cy)*iy+2*h) cx += l*cos(theta*CD_DEG2RAD) cy += l*sin(theta*CD_DEG2RAD)
end procedure
procedure turn(integer angle)
theta = mod(theta+angle,360)
end procedure
procedure curve(integer order, atom l, integer angle)
if order=0 then draw_line(l) else curve(order-1, l/2, -angle) turn(angle) curve(order-1, l/2, angle) turn(angle) curve(order-1, l/2, -angle) end if
end procedure
procedure sierpinski_arrowhead_curve(integer order, atom l)
-- If order is even we can just draw the curve. if and_bits(order,1)=0 then curve(order, l, +60) else -- order is odd turn( +60) curve(order, l, -60) end if draw_line(l)
end procedure
function redraw_cb(Ihandle /*ih*/, integer /*posx*/, integer /*posy*/)
{width, height} = IupGetIntInt(canvas, "DRAWSIZE") cdCanvasActivate(cddbuffer) for order=lo to hi do cx = width/2 cy = height h = cx/2 theta = 0 iy = iff(and_bits(order,1)?-1:+1) cdCanvasBegin(cddbuffer, CD_OPEN_LINES) sierpinski_arrowhead_curve(order, cx) cdCanvasEnd(cddbuffer) end for cdCanvasFlush(cddbuffer) return IUP_DEFAULT
end function
function map_cb(Ihandle ih)
cdcanvas = cdCreateCanvas(CD_IUP, ih) cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas) cdCanvasSetBackground(cddbuffer, CD_WHITE) cdCanvasSetForeground(cddbuffer, CD_BLUE) return IUP_DEFAULT
end function
function key_cb(Ihandle /*ih*/, atom c)
if c=K_ESC then return IUP_CLOSE end if if find(c,"+=-_") then bool bShift = IupGetInt(NULL,"SHIFTKEY") if c='+' or c='=' then if bShift then lo = min(lo+1,hi) else hi = min(10,hi+1) end if elsif c='-' or c='_' then if bShift then lo = max(1,lo-1) else hi = max(lo,hi-1) end if end if IupSetStrAttribute(dlg, "TITLE", "Sierpinski arrowhead curve (%d..%d)",{lo,hi}) cdCanvasClear(cddbuffer) IupUpdate(canvas) end if return IUP_CONTINUE
end function
procedure main()
IupOpen() canvas = IupCanvas(NULL) IupSetAttribute(canvas, "RASTERSIZE", "770x770") IupSetCallback(canvas, "MAP_CB", Icallback("map_cb")) IupSetCallback(canvas, "ACTION", Icallback("redraw_cb"))
dlg = IupDialog(canvas) IupSetAttribute(dlg, "TITLE", "Sierpinski arrowhead curve (6..6)") IupSetCallback(dlg, "K_ANY", Icallback("key_cb"))
IupMap(dlg) IupShowXY(dlg,IUP_CENTER,IUP_CENTER) IupMainLoop() IupClose()
end procedure
main()</lang>
Processing
<lang java>final PVector t = new PVector(20, 30, 60);
void setup() {
size(450, 400); noLoop(); background(0, 0, 200); stroke(-1); sc(7, 400, -60, t);
}
PVector sc(int o, float l, final int a, final PVector s) {
if (o > 0) { sc(--o, l *= .5, -a, s).z += a; sc(o, l, a, s).z += a; sc(o, l, -a, s); } else line(s.x, s.y, s.x += cos(radians(s.z)) * l, s.y += sin(radians(s.z)) * l); return s;
}</lang>The sketch can be run online :
here.
Processing Python mode
<lang python>
t = { 'x': 20, 'y': 30, 'a': 60 }
def setup():
size(450, 400) background(0, 0, 200) stroke(-1) sc(7, 400, -60)
def sc(o, l, a, s = t, X = 'x', Y = 'y', A = 'a', HALF = .5):
if o: o -= 1 l *= HALF sc(o, l, -a)[A] += a sc(o, l, a)[A] += a sc(o, l, -a) else: x, y = s[X], s[Y] s[X] += cos(radians(s[A])) * l s[Y] += sin(radians(s[A])) * l line(x, y, s[X], s[Y])
return s
</lang>
The sketch can be run online :
here.
Prolog
<lang prolog>main:-
write_sierpinski_arrowhead('sierpinski_arrowhead.svg', 600, 8).
write_sierpinski_arrowhead(File, Size, Order):-
open(File, write, Stream), format(Stream, "<svg xmlns='http://www.w3.org/2000/svg' width='~d' height='~d'>\n", [Size, Size]), write(Stream, "<rect width='100%' height='100%' fill='white'/>\n"), write(Stream, "<path stroke-width='1' stroke='black' fill='none' d='"), Margin = 20.0, Side is Size - 2.0 * Margin, X = Margin, Y is 0.5 * Size + 0.25 * sqrt(3) * Side, Cursor = cursor(X, Y, 0), (Order mod 2 == 1 -> turn(Cursor, -60, Cursor1) ; Cursor1 = Cursor), format(Stream, "M~g,~g", [X, Y]), curve(Stream, Order, Side, Cursor1, _, 60), write(Stream, "'/>\n</svg>\n"), close(Stream).
turn(cursor(X, Y, A), Angle, cursor(X, Y, A1)):-
A1 is (A + Angle) mod 360.
draw_line(Stream, cursor(X, Y, A), Length, cursor(X1, Y1, A)):-
Theta is (pi * A)/180.0, X1 is X + Length * cos(Theta), Y1 is Y + Length * sin(Theta), format(Stream, "L~g,~g", [X1, Y1]).
curve(Stream, 0, Length, Cursor, Cursor1, _):-
!, draw_line(Stream, Cursor, Length, Cursor1).
curve(Stream, Order, Length, Cursor, Cursor1, Angle):-
Order1 is Order - 1, Angle1 is -Angle, Length2 is Length/2.0, curve(Stream, Order1, Length2, Cursor, Cursor2, Angle1), turn(Cursor2, Angle, Cursor3), curve(Stream, Order1, Length2, Cursor3, Cursor4, Angle), turn(Cursor4, Angle, Cursor5), curve(Stream, Order1, Length2, Cursor5, Cursor1, Angle1).</lang>
- Output:
See: sierpinski_arrowhead.svg (offsite SVG image)
Raku
(formerly Perl 6)
<lang perl6>use SVG;
role Lindenmayer {
has %.rules; method succ { self.comb.map( { %!rules{$^c} // $c } ).join but Lindenmayer(%!rules) }
}
my $arrow = 'X' but Lindenmayer( { X => 'YF+XF+Y', Y => 'XF-YF-X' } );
$arrow++ xx 7;
my $w = 800; my $h = ($w * 3**.5 / 2).round(1);
my $scale = 6; my @points = (400, 15); my $dir = pi/3;
for $arrow.comb {
state ($x, $y) = @points[0,1]; state $d = $dir; when 'F' { @points.append: ($x += $scale * $d.cos).round(1), ($y += $scale * $d.sin).round(1) } when '+' { $d += $dir } when '-' { $d -= $dir } default { }
}
my $out = './sierpinski-arrowhead-curve-perl6.svg'.IO;
$out.spurt: SVG.serialize(
svg => [ :width($w), :height($h), :rect[:width<100%>, :height<100%>, :fill<black>], :polyline[ :points(@points.join: ','), :fill<black>, :style<stroke:#FF4EA9> ], ],
);</lang> See: Sierpinski-arrowhead-curve-perl6.svg (offsite SVG image)
REXX
<lang rexx>/*REXX pgm computes and displays a Sierpinski Arrowhead Curve using the characters: \_/ */ parse arg order . /*obtain optional argument from the CL.*/ if order== | order=="," then order= 5 /*Not specified? Then use the default.*/ say ' Sierpinski arrowhead curve of order' order /*display the title. */ say '═════════════════════════════════════════' /* " " separator.*/ $= init() /*initialize a bunch of variables. */ if order//2 then do; call turn +60; call curve order, len, -60; end /*CURVE odd? */
else call curve order, len, +60 /*CURVE even.*/
do row=Ly to Hy; a= /*show arrowhead graph 1 row at a time.*/ do col=Lx to Hx; a= a || @.col.row /*build a row of " " col " " " */ end /*col*/; say strip(a, 'T') /*show " " " " row " " " */ end /*row*/
exit 0 /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ init: @.=" "; #=0; len=512; x=len; y=x;Hx=x;Hy=y;Lx=x;Ly=y; return '@. # Hx Hy Lx Ly x y' turn: parse arg angle; #= (#+angle)//360; if #<0 then #= #+360; return /*normalize.*/ /*──────────────────────────────────────────────────────────────────────────────────────*/ curve: procedure expose ($); parse arg order,len,angle /*$: list of exposed variables*/
if order==0 then call draw len /*Is ORDER zero? Then draw it.*/ else do; call curve order-1, len/2, -angle; call turn angle call curve order-1, len/2, +angle; call turn angle call curve order-1, len/2, -angle end return /*The CURVE function is recursive. */
/*──────────────────────────────────────────────────────────────────────────────────────*/ draw: select /*draw part of the curve using a char. */
when #== 0 then do; @.x.y= '_'; x= x + 1; end when #== 60 then do; @.x.y= '/'; x= x + 1; y= y - 1; end when #==120 then do; x= x - 1; @.x.y= '\'; y= y - 1; end when #==180 then do; x= x - 1; @.x.y= '_'; end when #==240 then do; x= x - 1; y= y + 1; @.x.y= '/'; end when #==300 then do; y= y + 1; @.x.y= '\'; x= x + 1; end end /*select*/ /*curve character is based on direction*/ Lx= min(Lx,x); Hx= max(Hx,x); Ly= min(Ly,y); Hy= max(Hy,y) /*min&max of x,y*/ return /*#: heading in degrees of the curve. */</lang>
- output when using the default input value of: 5
Sierpinski arrowhead curve of order 5 ═════════════════════════════════════════ _ / \ \ / _/ \_ / \ \_ _/ _ \ / _ / \_/ \_/ \ \ / _/ \_ / _ _ \ \_/ \ / \_/ _ / \ _ / \ \_ _/ / \ \ / _ \ / _ \ / _/ \_/ \_/ \_/ \_/ \_ / \ \_ _/ _ \ / _ / \_/ \_/ \ \ _ _ / _/ / \ / \ \_ / _ \ / \ / _ \ \_/ \_/ \_ _/ \_/ \_/ _ \ / _ / \ _/ \_ / \ \ / / _ _ \ \ / _/ \_ \_/ \ / \_/ _/ \_ / \ _ / \ _ / \ \_ _/ / \ \_ _/ / \ \_ _/ _ \ / _ \ / _ \ / _ \ / _ \ / _ / \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \
Ruby
For grammar see Hilbert Curve <lang ruby> load_libraries :grammar attr_reader :points
def setup
sketch_title 'Sierpinski Arrowhead' sierpinski = SierpinskiArrowhead.new(Vec2D.new(width * 0.15, height * 0.7)) production = sierpinski.generate 6 # 6 generations looks OK @points = sierpinski.translate_rules(production) no_loop
end
def draw
background(0) render points
end
def render(points)
no_fill stroke 200.0 stroke_weight 3 begin_shape points.each_slice(2) do |v0, v1| v0.to_vertex(renderer) v1.to_vertex(renderer) end end_shape
end
def renderer
@renderer ||= GfxRender.new(g)
end
def settings
size(800, 800)
end
- SierpinskiArrowhead class
class SierpinskiArrowhead
include Processing::Proxy attr_reader :draw_length, :pos, :theta, :axiom, :grammar DELTA = PI / 3 # 60 degrees def initialize(pos) @axiom = 'XF' # Axiom rules = { 'X' => 'YF+XF+Y', 'Y' => 'XF-YF-X' } @grammar = Grammar.new(axiom, rules) @theta = 0 @draw_length = 200 @pos = pos end
def generate(gen) @draw_length = draw_length * 0.6**gen grammar.generate gen end
def forward(pos) pos + Vec2D.from_angle(theta) * draw_length end
def translate_rules(prod) [].tap do |pts| # An array to store line vertices as Vec2D prod.scan(/./) do |ch| case ch when 'F' new_pos = forward(pos) pts << pos << new_pos @pos = new_pos when '+' @theta += DELTA when '-' @theta -= DELTA when 'X', 'Y' else puts("character #{ch} not in grammar") end end end end
end
</lang>
Rust
Output is a file in SVG format. Another variation on the same theme as the C, Go and Phix solutions. <lang rust>// [dependencies] // svg = "0.8.0"
const SQRT3_2: f64 = 0.86602540378444;
use svg::node::element::path::Data;
struct Cursor {
x: f64, y: f64, angle: i32,
}
impl Cursor {
fn new(x: f64, y: f64) -> Cursor { Cursor { x: x, y: y, angle: 0, } } fn turn(&mut self, angle: i32) { self.angle = (self.angle + angle) % 360; } fn draw_line(&mut self, data: Data, length: f64) -> Data { let theta = (self.angle as f64).to_radians(); self.x += length * theta.cos(); self.y += length * theta.sin(); data.line_to((self.x, self.y)) }
}
fn curve(mut data: Data, order: usize, length: f64, cursor: &mut Cursor, angle: i32) -> Data {
if order == 0 { return cursor.draw_line(data, length); } data = curve(data, order - 1, length / 2.0, cursor, -angle); cursor.turn(angle); data = curve(data, order - 1, length / 2.0, cursor, angle); cursor.turn(angle); curve(data, order - 1, length / 2.0, cursor, -angle)
}
fn write_sierpinski_arrowhead(file: &str, size: usize, order: usize) -> std::io::Result<()> {
use svg::node::element::Path; use svg::node::element::Rectangle;
let margin = 20.0; let side = (size as f64) - 2.0 * margin; let y = 0.5 * (size as f64) + 0.5 * SQRT3_2 * side; let x = margin; let mut cursor = Cursor::new(x, y); if (order & 1) != 0 { cursor.turn(-60); } let mut data = Data::new().move_to((x, y)); data = curve(data, order, side, &mut cursor, 60); let rect = Rectangle::new() .set("width", "100%") .set("height", "100%") .set("fill", "white"); let mut document = svg::Document::new() .set("width", size) .set("height", size) .add(rect); let path = Path::new() .set("fill", "none") .set("stroke", "black") .set("stroke-width", "1") .set("d", data); document = document.add(path); svg::save(file, &document)
}
fn main() {
write_sierpinski_arrowhead("sierpinski_arrowhead.svg", 600, 8).unwrap();
}</lang>
- Output:
See: sierpinski_arrowhead.svg (offsite SVG image)
Sidef
Uses the LSystem() class from Hilbert curve. <lang ruby>var rules = Hash(
x => 'yF+xF+y', y => 'xF-yF-x',
)
var lsys = LSystem(
width: 550, height: 500,
xoff: -20, yoff: -30,
len: 4, turn: -90, angle: 60, color: 'dark green',
)
lsys.execute('xF', 7, "sierpiński_arrowhead.png", rules)</lang> Output image: Sierpiński arrowhead
Wren
<lang ecmascript>import "graphics" for Canvas, Color, Point import "dome" for Window
class Game {
static init() { Window.title = "Sierpinski Arrowhead Curve" __width = 770 __height = 770 Window.resize(__width, __height) Canvas.resize(__width, __height) var order = 6 __iy = (order&1 == 0) ? -1: 1 // apex will point upwards __theta = 0 __cx = __width / 2 __cy = __height __h = __cx / 2 __prev = Point.new(__cx-__width/2 +__h, (__height-__cy)*__iy + 2*__h) __col = Color.white arrowhead(order, __cx) }
static update() {}
static draw(alpha) {}
static arrowhead(order, length) { // if order is even, we can just draw the curve if (order&1 == 0) { curve(order, length, 60) } else { turn(60) curve(order, length, -60) } drawLine(length) // needed to make base symmetric }
static drawLine(length) { var curr = Point.new(__cx-__width/2 +__h, (__height-__cy)*__iy + 2*__h) Canvas.line(__prev.x, __prev.y, curr.x, curr.y, __col) var rads = __theta * Num.pi / 180 __cx = __cx + length*(rads.cos) __cy = __cy + length*(rads.sin) __prev = curr }
static turn(angle) { __theta = (__theta + angle) % 360 }
static curve(order, length, angle) { if (order == 0) { drawLine(length) } else { curve(order-1, length/2, -angle) turn(angle) curve(order-1, length/2, angle) turn(angle) curve(order-1, length/2, -angle) } }
}</lang>
zkl
Uses Image Magick and the PPM class from http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#zkl <lang zkl>order:=7; sierpinskiArrowheadCurve(order) : turtle(_,order);
fcn sierpinskiArrowheadCurve(n){ // Lindenmayer system --> Data of As & Bs
var [const] A="BF+AF+B", B="AF-BF-A"; // Production rules var [const] Axiom="AF"; buf1,buf2 := Data(Void,Axiom).howza(3), Data().howza(3); // characters do(n){ buf1.pump(buf2.clear(),fcn(c){ if(c=="A") A else if(c=="B") B else c }); t:=buf1; buf1=buf2; buf2=t; // swap buffers } buf1 // n=7 --> 6,560 characters
}
fcn turtle(curve,order){ // Turtle with that can turn +-60*
const D=10.0, a60=60; dir:=order.isOdd and a60 or 0; // start direction depends on order img,color := PPM(1300,1200), 0x00ff00; // green on black x,y := 10, 10; foreach c in (curve){ // A & B are no-op during drawing switch(c){
case("F"){ // draw forward a,b := D.toRectangular(dir.toFloat().toRad()); img.line(x,y, (x+=a.round()),(y+=b.round()), color) } case("+"){ dir=(dir - a60)%360; } // turn left 60* case("-"){ dir=(dir + a60)%360; } // turn right 60*
} } img.writeJPGFile("sierpinskiArrowheadCurve.zkl.jpg");
}</lang>
- Output:
Offsite image at Sierpinski arrowhead curve order 7