sourcedemos/rosetta/Stone.xtl
1⍝# Stone: the Rosetta stone's geometry and faces (docs/rosetta.md), a
2⍝# library beside the demo ("st:" u_se< "Stone"). The stone is three
3⍝# carousels sharing one place: the whole stone rolling about the x
4⍝# axis through the idioms (roll it up or down for the previous or next
5⍝# idiom), and its two halves, closed boxes, each turning about the y
6⍝# axis through the languages, one for the top half of the facing
7⍝# idiom, one for the bottom. Each is a square prism, so the geometry
8⍝# never changes: the facing side shows the current item and the sides
9⍝# at right angles show its neighbors, found by index; the lid of the
10⍝# top half and the floor of the bottom name the idioms before and
11⍝# after. The halves turn independently, which is the impossible part:
12⍝# no solid turns like this, and none needs to.
13⍝# Points are Geometry3D's (3 rows), pictures Svg's.
14ᵍ⁼u̲se< "Geometry3D"
15ᵛ⁼u̲se< "Svg"
16
17⍝# The stone's size: width, height (the halves are each half of it) and
18⍝# depth, in scene units; the viewer's distance; the picture's size in
19⍝# pixels and its scale.
20ˡwidth ← 2.4
21ˡheight ← 2.0
22ˡdepth ← 2.0
23ˡeye ← 7.0
24ˡsize ← 420.0
25zoom ← 112.0
26
27⍝# The corners of a box centered on the origin: w h d in a 3-vector,
28⍝# 3 x 8 (near face then far face, each from the top left anticlockwise,
29⍝# as g:c_ube orders them).
30ˡb̲ox ← { whd → (3 8 r̲eshape 8 r̲eplicate 0.5 × whd) × ᵍc̲ube 2 }
31
32⍝# The view: the stone turned a little about x and y so three sides
33⍝# show; every solid below is given in the view's frame.
34ˡv̲iew ← { points → ((ᵍr̲otX 22.0) ᵍt̲urn ᵍr̲otY -28.0) ᵍt̲urn points }
35
36⍝# Points in the view's frame projected and placed in the picture, y
37⍝# up: a 3 x n matrix of points to a 2 x n matrix of pixels.
38ˡs̲creen ← { points →
39 p ← ˡeye ᵍp̲roject points
40 xs ← (ˡsize ÷ 2) + zoom × 1 s̲elect p
41 ys ← (ˡsize ÷ 2) − zoom × 2 s̲elect p
42 (2 c̲at t̲ally xs) r̲eshape xs c̲at ys
43}
44
45⍝# How the prisms turn: an axis advanced by a degrees (90 per item) is
46⍝# drawn turned by only the part of a quarter turn under way, `a m_od
47⍝# 90`, and its sides show the items counted from the one the turn
48⍝# started at, f̲loor a ÷ 90 steps from the first. So a turn ends
49⍝# with the same picture it would have begun from, the sides relabel
50⍝# only at that instant, and the halves always stand upright where
51⍝# they can turn.
52ˡb̲ase ← { a → f̲loor a ÷ 90.0 }
53ˡp̲art ← { a → a − 90.0 × f̲loat ˡb̲ase a }
54
55⍝# The six sides of a language half advanced by a degrees: the closed
56⍝# box of one half (1 the top half, -1 the bottom), its front,
57⍝# right, back and left sides, then its lid and its floor, as a
58⍝# 6 x 3 x 4 array, rolled with the whole stone by d degrees about x.
59⍝# The next language comes round from the right, so the half turns by
60⍝# -a about y. ˢᵗringOffsets says which language each side shows,
61⍝# counted from st:b_ase: 0, 1, 2, -1. The lid and the floor are
62⍝# ordered so that text reads upright as they roll round to the front:
63⍝# the next idiom comes up from below, so the stone rolls by -d about
64⍝# x; ˢᵗcapOffsets says which idiom each shows, counted from
65⍝# st:b_ase: -1 on the lid, 1 on the floor (so the top half's lid and
66⍝# the bottom half's floor name the stone's neighbors; the two caps
67⍝# between the halves are hidden until a half turns). A half is a
68⍝# closed box so that nothing shows through it while it turns.
69⍝# half (a d) ˢᵗr̲ing @.
70⍝# >> "st:" u_se< "Stone"
71⍝# >> s_hape 1 st:r_ing 0.0 0.0
72⍝# 6 3 4
73⍝# >> s_hape -1 st:r_ing 45.0 30.0
74⍝# 6 3 4
75ˡr̲ing ← { half ad →
76 box ← ˡb̲ox ˡwidth c̲at (ˡheight ÷ 2) c̲at ˡdepth
77 lifted ← box + 3 8 r̲eshape 8 r̲eplicate 0.0 c̲at (half × ˡheight ÷ 4) c̲at 0.0
78 sides ← 6 4 r̲eshape 1 2 3 4 2 6 7 3 6 5 8 7 5 1 4 8 5 6 2 1 4 3 7 8
79 turned ← (ᵍr̲otY n̲eg ˡp̲art 1 s̲elect ad) ᵍt̲urn lifted
80 sides ᵍs̲olid ˡv̲iew (ᵍr̲otX n̲eg ˡp̲art 2 s̲elect ad) ᵍt̲urn turned
81}
82ˡringOffsets ← 0 1 2 -1
83ˡcapOffsets ← -1 1
84
85⍝# A face as a panel in the picture: its corners (top left, top right,
86⍝# bottom right, bottom left; 3 x 4) turned by the view, filled, with
87⍝# the given lines drawn on it in face coordinates (0 to 1 across and
88⍝# down), each line a boxed text of markup (runs from v:s_pan, or text
89⍝# through v:e_scape), the first line small (a title), the rest in the
90⍝# code size. A face turned away from the viewer gets its fill only:
91⍝# nearer faces cover it, but text laid on a face seen nearly edge-on
92⍝# could poke out past it. (fill c̲at lines) ˢᵗp̲anel face.
93ˡp̲anel ← { style f →
94 s ← ˡs̲creen f
95 fill ← d̲isclose 1 s̲elect style
96 lines ← 1 d̲rop style
97 shape ← ((ᵛf̲ill fill) c̲at "#30363d" ᵛs̲troke 1.2) ᵛp̲olygon f̲loor 0.5 + s
98 n̲ot ˡf̲acing s ? shape
99 on ← ˡa̲ffine 1 2 4 s̲elect₂ s
100 texts ← ʰj̲oin '{ k → k ˡl̲ine d̲isclose k s̲elect lines } m̲ap r̲ange t̲ally lines
101 shape c̲at on ᵛg̲roup texts
102}
103
104⍝# Picture coordinates to a thousandth of a pixel: the last digits of a
105⍝# projected coordinate differ between machines (their sine and cosine
106⍝# differ in the last place), and the pictures are pinned as text.
107⍝# >> "st:" u_se< "Stone"
108⍝# >> st:r_ounded 1.23456789 100.0004
109⍝# 1.235 100.0
110ˡr̲ounded ← { xs → (f̲loat f̲loor 0.5 + 1000.0 × xs) ÷ 1000.0 }
111
112⍝# The transform attribute mapping the unit square onto a face from its
113⍝# top left, top right and bottom left corners (2 x 3, picture
114⍝# coordinates), as v:m_atrix does, the six numbers rounded after the
115⍝# differences are taken (rounding before them leaves float noise).
116⍝# >> "st:" u_se< "Stone"
117⍝# >> st:a_ffine 2 3 r_eshape 10.0 30.0 10.0 20.0 20.0 40.0
118⍝# transform="matrix(20.0 0.0 0.0 20.0 10.0 20.0)"
119ˡa̲ffine ← { c →
120 tl ← 1 s̲elect₂ c
121 tr ← (2 s̲elect₂ c) − tl
122 bl ← (3 s̲elect₂ c) − tl
123 "transform" ᵛa̲ttr "matrix(" c̲at (f̲ormat ˡr̲ounded tr) c̲at " " c̲at (f̲ormat ˡr̲ounded bl) c̲at " " c̲at (f̲ormat ˡr̲ounded tl) c̲at ")"
124}
125
126⍝# Whether a face's corners (2 x 4 in the picture, y down) go round
127⍝# clockwise on the screen, which is how a face toward the viewer
128⍝# reads (its signed area is positive); a face turned away goes round
129⍝# the other way.
130⍝# >> "st:" u_se< "Stone"
131⍝# >> st:f_acing 2 4 r_eshape 0 1 1 0 0 0 1 1
132⍝# 1
133⍝# >> st:f_acing 2 4 r_eshape 0 0 1 1 0 1 1 0
134⍝# 0
135ˡf̲acing ← { s →
136 xs ← 1 s̲elect s
137 ys ← 2 s̲elect s
138 0 < '+ r̲/ (xs × 1 o̲- ys) − ys × 1 o̲- xs
139}
140
141⍝# One line of markup on a face: line k from the top; the first line
142⍝# is the title, small and gray, the others code, bold and black in a
143⍝# monospace face a tenth of the face high (the faces are small on a
144⍝# phone, so the code is the boldest thing on them).
145ˡl̲ine ← { k t →
146 size ← "k = 1" i̲f< "\"0.06\"; \"0.1\""
147 y ← "k = 1" i̲f< "0.13; 0.14 + 0.16 * f_loat k"
148 color ← "k = 1" i̲f< "\"#4b5563\"; \"#000000\""
149 weight ← "k = 1" i̲f< "\"500\"; \"700\""
150 ((ᵛa̲t 0.06 c̲at y) c̲at ("font-size" ᵛa̲ttr size) c̲at ("font-weight" ᵛa̲ttr weight) c̲at (ᵛf̲ill color) c̲at "font-family" ᵛa̲ttr "ui-monospace, monospace") ᵛm̲arkup t
151}
152
153⍝# The faces of a solid (F x 3 x 4, in the view's frame) in the order
154⍝# to paint them: far to near, so nearer faces cover farther ones.
155ˡp̲ainting ← { solid → ᵍo̲rder solid }
156
157⍝ Boxed texts joined into one (none is the empty text).
158ʰj̲oin ← { b →
159 0 = t̲ally b ? ""
160 d̲isclose '{ x y → e̲nclose (d̲isclose x) c̲at d̲isclose y } r̲/ b
161}