Luxor


Hari ini kita akan melihat paket grafik untuk bahasa Julia yang disebut Luxor . Ini adalah salah satu alat yang mengubah proses pembuatan gambar vektor menjadi pemecahan masalah logis dengan badai emosi yang menyertainya.


Perhatian Di bawah potongan 8,5 MB gambar ringan dan gif yang menggambarkan telur psychedelic dan objek empat dimensi, tampilan yang dapat menyebabkan sedikit mengaburkan pikiran!


Instalasi


https://julialang.org - unduh distribusi Julia dari situs resmi. Kemudian, memulai penerjemah, kami mengarahkan perintah ke konsolnya:


using Pkg Pkg.add("Colors") Pkg.add("ColorSchemes") Pkg.add("Luxor") 

yang akan menginstal paket untuk pekerjaan tingkat lanjut dengan warna dan Luxor sendiri.


Kemungkinan masalah


Masalah utama dari kedua pemrograman modern pada umumnya dan open source pada khususnya adalah bahwa beberapa proyek dibangun di atas yang lain, mewarisi semua kesalahan, dan bahkan menghasilkan yang baru karena ketidakcocokan. Seperti banyak paket lain, Luxor menggunakan paket julia lain untuk pekerjaannya, yang, pada gilirannya, adalah cangkang dari solusi yang ada.


Jadi, ImageMagick.jl tidak ingin mengunduh dan menyimpan file. Solusinya ditemukan pada halaman asli - ternyata dia tidak suka alfabet Cyrillic dengan cara.
Masalah nomor dua muncul dengan paket grafis tingkat rendah Kairo pada Windows 7. Saya akan menyembunyikan solusinya di sini:


Menari dengan rebana
  1. Kami mengetikkan penerjemah ]add Gtk - paket untuk bekerja dengan gui akan mulai diinstal dan kemungkinan besar akan jatuh selama konstruksi
  2. Selanjutnya, unduh gtk + -bundle_3.6.4-20130513_win64
  3. Selama instalasi, semuanya diperlukan dalam folder dengan paket Julia, tetapi gtk tidak selesai selama pelaksanaan item, jadi kami mengunduh versi yang sudah selesai untuk mesin kami - kami membuang konten arsip yang diunduh ke direktori C: \ Users \ User.julia \ package \ WinRPM \ Y9QdZ \ deps \ usr \ x86_64-w64-mingw32 \ sys-root \ mingw (Jalur Anda mungkin beragam)
  4. Jalankan Julia dan mengemudi di ]build Gtk dan setelah membangun using Gtk , dan, demi kesetiaan, membangun kembali Luxor: ]build Luxor
  5. Kami memulai ulang julia, dan kami dapat dengan aman menggunakan semua yang kami butuhkan: using Luxor

Jika ada masalah lain, kami mencoba menemukan kasus kami sendiri.


Jika Anda ingin mencoba animasi


Paket Luxor membuat animasi menggunakan ffmpeg , asalkan ada di komputer Anda. ffmpeg adalah pustaka sumber terbuka lintas-platform untuk memproses file video dan audio, hal yang sangat berguna (ada perjalanan yang baik di hub ). Pasang itu:


  • Unduh ffmpeg di luar kantor. Dalam kasus saya, ini adalah unduhan untuk windows
  • Buka paket dan tulis path ke ffmpeg.exe di variabel Path .

Lebih lanjut tentang penyumbatan di Path


Properti Komputer / Sistem / Parameter sistem lanjutan / Variabel lingkungan / Path (Buat jika tidak) dan tambahkan path ke ffmpeg.exe Anda di sana
Contoh C: \ Program Files \ ffmpeg-4.1.3-win64-static \ bin
jika Path sudah memiliki nilai, maka pisahkan dengan titik koma.
Sekarang jika Anda menggerakkan ffmpeg dengan parameter yang diperlukan ke konsol perintah ( cmd ), itu akan mulai dan berfungsi, dan Julia hanya akan berkomunikasi dengannya.


Halo dunia


Mari kita mulai dengan perangkap kecil - ketika membangun gambar, file grafik dibuat dan disimpan di direktori kerja. Artinya, ketika bekerja di REPL, folder julia root akan tersumbat dengan gambar, dan jika Anda menggambar di Jupyter, maka gambar-gambar terakumulasi di sebelah notebook proyek, oleh karena itu, akan menjadi kebiasaan yang baik untuk mengatur direktori kerja di tempat yang terpisah sebelum mulai bekerja:


 using Luxor cd("C:\\Users\\User\\Desktop\\mycop") 

Buat gambar pertama


 Drawing(220, 220, "hw.png") origin() background("white") sethue("black") text("Hello world") circle(Point(0, 0), 100, :stroke) finish() preview() 


Drawing() membuat gambar, secara default dalam format PNG, nama file default adalah 'luxor-drawing.png', ukuran default adalah 800x800, untuk semua format kecuali png Anda dapat menentukan ukuran non-integer, dan Anda juga dapat menggunakan ukuran kertas ("A0 "," A1 "," A2 "," A3 "," A4 "...)
finish() - selesai menggambar dan menutup file. Anda dapat membukanya di aplikasi tampilan eksternal menggunakan preview() , yang ketika bekerja di Jupyter (IJulia) akan menampilkan file PNG atau SVG di notepad. Saat bekerja di Juno, itu akan menampilkan file PNG atau SVG di panel Grafik. Di Repl, alat untuk bekerja dengan gambar yang Anda tetapkan untuk format ini di OS Anda disebut.
Hal yang sama dapat ditulis dalam bentuk singkat menggunakan makro


 @png begin text("Hello world") circle(Point(0, 0), 100, :stroke) end 

Untuk format vektor EPS, SVG, PDF, semuanya bekerja dengan cara yang sama.


Telur Euclidean



Ini adalah cara yang agak menarik untuk menggambar telur, dan jika Anda menghubungkan titik-titik kunci dan memotong sepanjang garis yang diterima, tangram yang sangat baik akan keluar



Mari kita mulai dengan lingkaran:


 @png begin radius=80 setdash("dot") sethue("gray30") A, B = [Point(x, 0) for x in [-radius, radius]] line(A, B, :stroke) circle(O, radius, :stroke) end 200 200 "egg0" #     


Semuanya sangat sederhana: setdash("dot") - menggambar dengan titik, sethue("gray30") - warna garis: semakin kecil, semakin gelap, semakin dekat ke 100 lebih putih. Kelas titik didefinisikan tanpa kita, dan pusat koordinat (0,0) dapat ditentukan oleh huruf O Tambahkan dua lingkaran dan tandatangani poin:


 @png begin radius=80 setdash("dot") sethue("gray30") A, B = [Point(x, 0) for x in [-radius, radius]] line(A, B, :stroke) circle(O, radius, :stroke) label("A", :NW, A) label("O", :N, O) label("B", :NE, B) circle.([A, O, B], 2, :fill) circle.([A, B], 2radius, :stroke) end 600 400 "egg2" 


Untuk mencari titik persimpangan, ada fungsi yang disebut intersectionlinecircle() yang menemukan titik atau titik di mana garis memotong lingkaran. Dengan demikian, kita dapat menemukan dua titik di mana salah satu lingkaran memotong garis vertikal imajiner yang ditarik melalui O. Karena simetri, kita hanya dapat memproses lingkaran A.


 @png begin radius=80 setdash("dot") sethue("gray30") A, B = [Point(x, 0) for x in [-radius, radius]] line(A, B, :stroke) circle(O, radius, :stroke) label("A", :NW, A) label("O", :N, O) label("B", :NE, B) circle.([A, O, B], 2, :fill) circle.([A, B], 2radius, :stroke) # >>>> nints, C, D = intersectionlinecircle(Point(0, -2radius), Point(0, 2radius), A, 2radius) if nints == 2 circle.([C, D], 2, :fill) label.(["D", "C"], :N, [D, C]) end end 600 400 "egg3" 


Untuk menentukan pusat lingkaran atas kita menemukan persimpangan OD


Kode
 @png begin radius=80 setdash("dot") sethue("gray30") A, B = [Point(x, 0) for x in [-radius, radius]] line(A, B, :stroke) circle(O, radius, :stroke) label("A", :NW, A) label("O", :N, O) label("B", :NE, B) circle.([A, O, B], 2, :fill) circle.([A, B], 2radius, :stroke) # >>>> nints, C1, C2 = intersectionlinecircle(O, D, O, radius) if nints == 2 circle(C1, 3, :fill) label("C1", :N, C1) end end 600 400 "egg4" 


Jari-jari lingkaran bawahan ditentukan oleh batasan untuk dua lingkaran besar:


Kode
 @png begin radius=80 setdash("dot") sethue("gray30") A, B = [Point(x, 0) for x in [-radius, radius]] line(A, B, :stroke) circle(O, radius, :stroke) label("A", :NW, A) label("O", :N, O) label("B", :NE, B) circle.([A, O, B], 2, :fill) circle.([A, B], 2radius, :stroke) # >>>> nints, C1, C2 = intersectionlinecircle(O, D, O, radius) if nints == 2 circle(C1, 3, :fill) label("C1", :N, C1) end # >>>> nints, I3, I4 = intersectionlinecircle(A, C1, A, 2radius) nints, I1, I2 = intersectionlinecircle(B, C1, B, 2radius) circle.([I1, I2, I3, I4], 2, :fill) # >>>> if distance(C1, I1) < distance(C1, I2) ip1 = I1 else ip1 = I2 end if distance(C1, I3) < distance(C1, I4) ip2 = I3 else ip2 = I4 end label("ip1", :N, ip1) label("ip2", :N, ip2) circle(C1, distance(C1, ip1), :stroke) end 600 400 "egg5" 


Telur sudah siap! Tetap merakitnya dari empat busur yang ditentukan oleh fungsi arc2r() dan mengisi area:


Kode
 @png begin radius=80 setdash("dot") sethue("gray30") A, B = [Point(x, 0) for x in [-radius, radius]] line(A, B, :stroke) circle(O, radius, :stroke) label("A", :NW, A) label("O", :N, O) label("B", :NE, B) circle.([A, O, B], 2, :fill) circle.([A, B], 2radius, :stroke) # >>>> nints, C1, C2 = intersectionlinecircle(O, D, O, radius) if nints == 2 circle(C1, 3, :fill) label("C1", :N, C1) end # >>>> nints, I3, I4 = intersectionlinecircle(A, C1, A, 2radius) nints, I1, I2 = intersectionlinecircle(B, C1, B, 2radius) circle.([I1, I2, I3, I4], 2, :fill) # >>>> if distance(C1, I1) < distance(C1, I2) ip1 = I1 else ip1 = I2 end if distance(C1, I3) < distance(C1, I4) ip2 = I3 else ip2 = I4 end label("ip1", :N, ip1) label("ip2", :N, ip2) circle(C1, distance(C1, ip1), :stroke) # >>>> setline(5) setdash("solid") arc2r(B, A, ip1, :path) # centered at B, from A to ip1 arc2r(C1, ip1, ip2, :path) arc2r(A, ip2, B, :path) arc2r(O, B, A, :path) strokepreserve() setopacity(0.8) sethue("ivory") fillpath() end 600 400 "egg6" 


Dan sekarang, untuk menikmati dengan baik, kami akan membawa pencapaian kami ke


fungsi
 function egg(radius, action=:none) A, B = [Point(x, 0) for x in [-radius, radius]] nints, C, D = intersectionlinecircle(Point(0, -2radius), Point(0, 2radius), A, 2radius) flag, C1 = intersectionlinecircle(C, D, O, radius) nints, I3, I4 = intersectionlinecircle(A, C1, A, 2radius) nints, I1, I2 = intersectionlinecircle(B, C1, B, 2radius) if distance(C1, I1) < distance(C1, I2) ip1 = I1 else ip1 = I2 end if distance(C1, I3) < distance(C1, I4) ip2 = I3 else ip2 = I4 end newpath() arc2r(B, A, ip1, :path) arc2r(C1, ip1, ip2, :path) arc2r(A, ip2, B, :path) arc2r(O, B, A, :path) closepath() do_action(action) end 

Kami menggunakan warna acak, lapisan lukisan dan berbagai kondisi awal:


 @png begin setopacity(0.7) for θ in range(0, step=π/6, length=12) @layer begin rotate(θ) translate(100, 50) # translate(0, -150) #rulers() egg(50, :path) setline(10) randomhue() fillpreserve() randomhue() strokepath() end end end 400 400 "eggs2" 



Selain goresan dan isi, Anda dapat menggunakan garis sebagai area kliping (memotong gambar lain dalam bentuk telur) atau sebagai dasar untuk berbagai desainer. Fungsi egg () membuat garis besar dan memungkinkan Anda untuk menerapkan suatu tindakan padanya. Dimungkinkan juga untuk mengubah kreasi kami menjadi poligon (array poin). Kode berikut mengubah garis besar telur menjadi poligon, dan kemudian memindahkan satu sama lain titik poligon setengah ke centroid.


 @png begin egg(160, :path) pgon = first(pathtopoly()) pc = polycentroid(pgon) circle(pc, 5, :fill) for pt in 1:2:length(pgon) pgon[pt] = between(pc, pgon[pt], 0.5) end poly(pgon, :stroke) end 350 500 "polyegg" 


Tampilan titik internal yang tidak rata di sini muncul sebagai hasil dari pengaturan koneksi jalur standar. Eksperimen dengan setlinejoin("round") untuk melihat apakah ini mengubah geometri. Nah, sekarang mari kita coba offsetpoly() membuat kontur poligon di luar atau di dalam poligon yang ada ..


 @png begin egg(80, :path) pgon = first(pathtopoly()) pc = polycentroid(pgon) for pt in 1:2:length(pgon) pgon[pt] = between(pc, pgon[pt], 0.9) end for i in 30:-3:-8 randomhue() op = offsetpoly(pgon, i) poly(op, :stroke, close=true) end end 350 500 "polyeggs" 


Perubahan kecil dalam keteraturan titik yang dibuat dengan mengubah jalur ke poligon, dan jumlah sampel yang berbeda dibuat, terus-menerus diperkuat dalam kontur berturut-turut.


Animasi


Pertama, mari kita mendefinisikan fungsi yang mengimplementasikan latar belakang dan rendering telur, tergantung pada nomor bingkai:


Kode
 using Colors demo = Movie(400, 400, "test") function backdrop(scene, framenumber) background("black") end function frame(scene, framenumber) setopacity(0.7) θ = framenumber * π/6 @layer begin rotate(θ) translate(100, 50) egg(50, :path) setline(10) randomhue() fillpreserve() randomhue() strokepath() end end 

Animasi diimplementasikan oleh serangkaian perintah sederhana:


 animate(demo, [ Scene(demo, backdrop, 0:12), Scene(demo, frame, 0:12, easingfunction=easeinoutcubic, optarg="made with Julia") ], framerate=10, tempdirectory="C:\\Users\\User\\Desktop\\mycop", creategif=true) 

Apa yang sebenarnya menyebabkan ffmpeg kita


 run(`ffmpeg -f image2 -i $(tempdirectory)/%10d.png -vf palettegen -y $(seq.stitle)-palette.png`) run(`ffmpeg -framerate 30 -f image2 -i $(tempdirectory)/%10d.png -i $(seq.stitle)-palette.png -lavfi paletteuse -y /tmp/$(seq.stitle).gif`) 

Yaitu, serangkaian gambar dibuat, dan kemudian GIF disusun dari bingkai ini:



Pentahore


Dia adalah lima - inti - simpleks empat dimensi yang benar. Untuk menggambar dan memanipulasi objek 4 dimensi pada gambar dua dimensi, pertama-tama kita tentukan


kelas titik 4 dimensi
 struct Point4D <: AbstractArray{Float64, 1} x::Float64 y::Float64 z::Float64 w::Float64 end Point4D(a::Array{Float64, 1}) = Point4D(a...) Base.size(pt::Point4D) = (4, ) Base.getindex(pt::Point4D, i) = [pt.x, pt.y, pt.z, pt.w][i] struct Point3D <: AbstractArray{Float64, 1} x::Float64 y::Float64 z::Float64 end Base.size(pt::Point3D) = (3, ) 

Alih-alih mendefinisikan banyak operasi secara manual, kita dapat mendefinisikan struktur kita sebagai subtipe dari AbstractArray ( Selengkapnya tentang kelas sebagai antarmuka )


Tugas utama yang harus kita pecahkan adalah bagaimana mengkonversi titik 4D ke titik 2D. Mari kita mulai dengan tugas yang lebih sederhana: bagaimana mengkonversi titik 3D ke titik 2D, mis. Bagaimana kita bisa menggambar sosok 3D di permukaan yang datar? Pertimbangkan kubus sederhana. Permukaan depan dan belakang dapat memiliki koordinat X dan Y yang sama dan hanya bervariasi dalam nilai Z mereka.


Kode
 @png begin fontface("Menlo") fontsize(8) setblend(blend( boxtopcenter(BoundingBox()), boxmiddlecenter(BoundingBox()), "skyblue", "white")) box(boxtopleft(BoundingBox()), boxmiddleright(BoundingBox()), :fill) setblend(blend( boxmiddlecenter(BoundingBox()), boxbottomcenter(BoundingBox()), "grey95", "grey45" )) box(boxmiddleleft(BoundingBox()), boxbottomright(BoundingBox()), :fill) sethue("black") setline(2) bx1 = box(O, 250, 250, vertices=true) poly(bx1, :stroke, close=true) label.(["-1 1 1", "-1 -1 1", "1 -1 1", "1 1 1"], slope.(O, bx1), bx1) setline(1) bx2 = box(O, 150, 150, vertices=true) poly(bx2, :stroke, close=true) label.(["-1 1 0", "-1 -1 0", "1 -1 0", "1 1 0"], slope.(O, bx2), bx2, offset=-45) map((x, y) -> line(x, y, :stroke), bx1, bx2) end 400 400 "cube.png" 


Oleh karena itu, idenya adalah memproyeksikan kubus dari 3D ke 2D, menyimpan dua nilai pertama dan mengalikan atau mengubahnya dengan nilai ketiga. Periksa


bagaimana cara kerjanya
 const K = 4.0 function convert(Point, pt3::Point3D) k = 1/(K - pt3.z) return Point(pt3.x * k, pt3.y * k) end @png begin cube = Point3D[ Point3D(-1, -1, 1), Point3D(-1, 1, 1), Point3D( 1, -1, 1), Point3D( 1, 1, 1), Point3D(-1, -1, -1), Point3D(-1, 1, -1), Point3D( 1, -1, -1), Point3D( 1, 1, -1), ] circle.(convert.(Point, cube) * 300, 5, :fill) end 220 220 "points" 


Dengan menggunakan prinsip yang sama, mari kita buat metode untuk mengkonversi titik 4D dan fungsi yang mengambil daftar titik empat dimensi dan memetakannya dua kali ke daftar titik dua dimensi yang cocok untuk menggambar.


 function convert(Point3D, pt4::Point4D) k = 1/(K - pt4.w) return Point3D(pt4.x * k, pt4.y * k, pt4.z * k) end function flatten(shape4) return map(pt3 -> convert(Point, pt3), map(pt4 -> convert(Point3D, pt4), shape4)) end 

Selanjutnya, atur simpul dan wajah dan periksa cara kerjanya dalam warna


Aduh!
 const n = -1/√5 const pentachoron = [Point4D(vertex...) for vertex in [ [ 1.0, 1.0, 1.0, n], [ 1.0, -1.0, -1.0, n], [-1.0, 1.0, -1.0, n], [-1.0, -1.0, 1.0, n], [ 0.0, 0.0, 0.0, n + √5]]]; const pentachoronfaces = [ [1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5]]; @png begin setopacity(0.2) pentachoron2D = flatten(pentachoron) for (n, face) in enumerate(pentachoronfaces) randomhue() poly(1500 * pentachoron2D[face], :fillpreserve, close=true) sethue("black") strokepath() end end 300 250 "5ceil" 


Setiap pengembang game yang menghargai diri sendiri harus mengetahui Dasar-dasar Matematika dari Grafik Mesin . Jika Anda tidak pernah mencoba untuk mengompres, memutar, memantulkan teko di OpenGL - jangan khawatir, semuanya cukup sederhana. Untuk mencerminkan titik relatif ke garis, atau memutar pesawat di sekitar sumbu tertentu, Anda perlu mengalikan koordinat dengan matriks khusus. Sebenarnya lebih jauh, kita akan menentukan matriks transformasi yang kita butuhkan:


ambil lebih banyak
 function XY(θ) [cos(θ) -sin(θ) 0 0; sin(θ) cos(θ) 0 0; 0 0 1 0; 0 0 0 1] end function XW(θ) [cos(θ) 0 0 -sin(θ); 0 1 0 0; 0 0 1 0; sin(θ) 0 0 cos(θ)] end function XZ(θ) [cos(θ) 0 -sin(θ) 0; 0 1 0 0; sin(θ) 0 cos(θ) 0; 0 0 0 1] end function YZ(θ) [1 0 0 0; 0 cos(θ) -sin(θ) 0; 0 sin(θ) cos(θ) 0; 0 0 0 1] end function YW(θ) [1 0 0 0; 0 cos(θ) 0 -sin(θ); 0 0 1 0; 0 sin(θ) 0 cos(θ)] end function ZW(θ) [1 0 0 0; 0 1 0 0; 0 0 cos(θ) -sin(θ); 0 0 sin(θ) cos(θ)]; end function rotate4(A, matrixfunction) return map(A) do pt4 Point4D(matrixfunction * pt4) end end 

Biasanya Anda memutar titik pada bidang relatif terhadap objek satu dimensi. Poin 3D berada di sekitar garis 2D (sering kali ini adalah salah satu sumbu XYZ). Dengan demikian, adalah logis bahwa titik 4D berputar relatif terhadap bidang 3D. Kami telah mengidentifikasi matriks yang melakukan rotasi empat dimensi tentang bidang yang ditentukan oleh dua sumbu X, Y, Z, dan W. Pesawat XY biasanya merupakan bidang permukaan gambar. Jika Anda menganggap bidang XY sebagai layar komputer, maka bidang XZ sejajar dengan meja atau lantai Anda, dan bidang YZ adalah dinding di samping meja Anda di kanan atau kiri. Tapi bagaimana dengan XW, YW dan ZW? Ini adalah misteri tokoh empat dimensi: kita tidak bisa melihat pesawat ini, kita hanya bisa membayangkan keberadaannya dengan mengamati bagaimana bentuk bergerak melalui mereka dan di sekitar mereka.


Sekarang kita mengatur fungsi untuk frame dan menjahit animasi:


spoiler
 using ColorSchemes function frame(scene, framenumber, scalefactor=1000) background("white") # antiquewhite setlinejoin("bevel") setline(1.0) sethue("black") eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop) pentachoron′ = rotate4(pentachoron, XZ(eased_n * 2π)) pentachoron2D = flatten(pentachoron′) setopacity(0.2) for (n, face) in enumerate(pentachoronfaces) sethue(get(ColorSchemes.diverging_rainbow_bgymr_45_85_c67_n256, n/length(pentachoronfaces))) poly(scalefactor * pentachoron2D[face], :fillpreserve, close=true) sethue("black") strokepath() end end function makemovie(w, h, fname; scalefactor=1000) movie1 = Movie(w, h, "4D movie") animate(movie1, Scene(movie1, (s, f) -> frame(s, f, scalefactor), 1:300, easingfunction=easeinoutsine), #framerate=10, tempdirectory="C:\\Users\\User\\Desktop\\mycop", creategif=true, pathname="C:\\Users\\User\\Desktop\\mycop\\$(fname)") end makemovie(320, 320, "pentachoron-xz.gif", scalefactor=2000) 


Nah, dan sudut lainnya:


Kode
 function frame(scene, framenumber, scalefactor=1000) background("antiquewhite") setlinejoin("bevel") setline(1.0) setopacity(0.2) eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop) pentachoron2D = flatten( rotate4( pentachoron, XZ(eased_n * 2π) * YW(eased_n * 2π))) for (n, face) in enumerate(pentachoronfaces) sethue(get(ColorSchemes.diverging_rainbow_bgymr_45_85_c67_n256, n/length(pentachoronfaces))) poly(scalefactor * pentachoron2D[face], :fillpreserve, close=true) sethue("black") strokepath() end end makemovie(500, 500, "pentachoron-xz-yw.gif", scalefactor=2000) 


Keinginan untuk mewujudkan objek empat dimensi yang lebih populer, Tesseract, dibuat sepenuhnya


Atasan dan Wajah
 const tesseract = [Point4D(vertex...) for vertex in [ [-1, -1, -1, 1], [ 1, -1, -1, 1], [ 1, 1, -1, 1], [-1, 1, -1, 1], [-1, -1, 1, 1], [ 1, -1, 1, 1], [ 1, 1, 1, 1], [-1, 1, 1, 1], [-1, -1, -1, -1], [ 1, -1, -1, -1], [ 1, 1, -1, -1], [-1, 1, -1, -1], [-1, -1, 1, -1], [ 1, -1, 1, -1], [ 1, 1, 1, -1], [-1, 1, 1, -1]]] const tesseractfaces = [ [1, 2, 3, 4], [1, 2, 10, 9], [1, 4, 8, 5], [1, 5, 6, 2], [1, 9, 12, 4], [2, 3, 11, 10], [2, 3, 7, 6], [3, 4, 8, 7], [5, 6, 14, 13], [5, 6, 7, 8], [5, 8, 16, 13], [6, 7, 15, 14], [7, 8, 16, 15], [9, 10, 11, 12], [9, 10, 14, 13], [9, 13, 16, 12], [10, 11, 15, 14], [13, 14, 15, 16]]; 

Buat animasi
 function frame(scene, framenumber, scalefactor=1000) background("black") setlinejoin("bevel") setline(10.0) setopacity(0.7) eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop) tesseract2D = flatten( rotate4( tesseract, XZ(eased_n * 2π) * YW(eased_n * 2π))) for (n, face) in enumerate(tesseractfaces) sethue([Luxor.lighter_blue, Luxor.lighter_green, Luxor.lighter_purple, Luxor.lighter_red][mod1(n, 4)]...) poly(scalefactor * tesseract2D[face], :fillpreserve, close=true) sethue([Luxor.darker_blue, Luxor.darker_green, Luxor.darker_purple, Luxor.darker_red][mod1(n, 4)]...) strokepath() end end makemovie(500, 500, "tesseract-xz-yw.gif", scalefactor=1000) 


Pekerjaan rumah: mengotomatiskan pembuatan array koordinat dan nomor vertex ( permutasi dengan pengulangan dan tanpa pengulangan, masing-masing ). Juga, kami tidak menggunakan semua matriks terjemahan; Setiap perspektif baru memunculkan "Wow!" Baru, tetapi saya memutuskan untuk tidak membebani halaman. Nah, Anda bisa bereksperimen dengan banyak wajah dan dimensi.


Referensi


  • Luxor - halaman github
  • Luxor docs - panduan dengan contoh-contoh
  • Kairo - perpustakaan grafis tingkat rendah; digunakan oleh Luxor sebagai lingkungan
  • Blog perpustakaan penulis - ada banyak kesejukan dan contoh yang lebih maju, termasuk angka empat dimensi.

kottke

Source: https://habr.com/ru/post/id459842/


All Articles