dopairb 0.2.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +77 -9
- data/exe/dopacc +84 -0
- data/lib/dopairb/claude_code.rb +330 -0
- data/lib/dopairb/cli.rb +4 -6
- data/lib/dopairb/config.rb +6 -4
- data/lib/dopairb/demo.rb +9 -2
- data/lib/dopairb/director.rb +35 -5
- data/lib/dopairb/fx.rb +23 -0
- data/lib/dopairb/gallery/impressionism.rb +818 -0
- data/lib/dopairb/gallery/japan.rb +693 -0
- data/lib/dopairb/gallery/modern_ancient.rb +774 -0
- data/lib/dopairb/gallery/renaissance.rb +829 -0
- data/lib/dopairb/gallery/romantic.rb +703 -0
- data/lib/dopairb/gallery.rb +349 -0
- data/lib/dopairb/game.rb +84 -7
- data/lib/dopairb/input_fx.rb +63 -9
- data/lib/dopairb/irb_adapter.rb +8 -0
- data/lib/dopairb/profile.rb +106 -0
- data/lib/dopairb/reline_adapter.rb +1 -0
- data/lib/dopairb/scene.rb +5 -0
- data/lib/dopairb/scenes/bonus.rb +279 -0
- data/lib/dopairb/scenes/failure.rb +1 -0
- data/lib/dopairb/scenes/finale.rb +42 -2
- data/lib/dopairb/scenes/session.rb +21 -3
- data/lib/dopairb/scenes/success.rb +11 -4
- data/lib/dopairb/session.rb +53 -7
- data/lib/dopairb/sound.rb +57 -2
- data/lib/dopairb/term.rb +3 -1
- data/lib/dopairb/tour.rb +89 -0
- data/lib/dopairb/version.rb +1 -1
- data/lib/dopairb.rb +101 -12
- data/spec.md +3 -3
- metadata +13 -1
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dopairb
|
|
4
|
+
module Gallery
|
|
5
|
+
# Distance from (px, py) to the segment (ax, ay)-(bx, by); pass aspect-scaled x.
|
|
6
|
+
JP_SEG = lambda do |px, py, ax, ay, bx, by|
|
|
7
|
+
dx = bx - ax
|
|
8
|
+
dy = by - ay
|
|
9
|
+
t = (((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy + 1e-12)).clamp(0.0, 1.0)
|
|
10
|
+
Math.hypot(px - ax - t * dx, py - ay - t * dy)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The color a feature loop produced with `break color`, or nil when it ran out.
|
|
14
|
+
JP_HIT = ->(r) { r if r.is_a?(Array) && r.size == 3 && r[0].is_a?(Integer) }
|
|
15
|
+
|
|
16
|
+
# Triangle wave 0..1..0 with period 1.
|
|
17
|
+
JP_TRI = ->(x) { 1.0 - (x - x.floor - 0.5).abs * 2 }
|
|
18
|
+
|
|
19
|
+
# Approximate signed distance to a rotated ellipse, all in aspect-scaled units.
|
|
20
|
+
JP_RING = lambda do |x, y, cx, cy, rx, ry, ang = 0.0|
|
|
21
|
+
dx = x - cx
|
|
22
|
+
dy = y - cy
|
|
23
|
+
c = Math.cos(ang)
|
|
24
|
+
s = Math.sin(ang)
|
|
25
|
+
ex = dx * c + dy * s
|
|
26
|
+
ey = -dx * s + dy * c
|
|
27
|
+
(Math.sqrt((ex / rx)**2 + (ey / ry)**2) - 1.0) * [rx, ry].min
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Tohaku's pine groves: [x, top, ink density, lean, [[dy, dx, rx] needle blots]]
|
|
31
|
+
JP_PINES = [
|
|
32
|
+
[0.07, 0.14, 0.55, 0.03, [[0.02, 0.01, 0.04], [0.14, -0.02, 0.05], [0.27, 0.02, 0.045]]],
|
|
33
|
+
[0.13, 0.30, 0.30, -0.02, [[0.0, 0.0, 0.035], [0.12, 0.015, 0.045]]],
|
|
34
|
+
[0.29, 0.04, 1.00, 0.04, [[0.0, 0.0, 0.045], [0.10, -0.03, 0.06], [0.22, 0.025, 0.07], [0.36, -0.02, 0.06]]],
|
|
35
|
+
[0.36, 0.18, 0.80, -0.03, [[0.0, 0.01, 0.04], [0.12, 0.03, 0.06], [0.25, -0.02, 0.05]]],
|
|
36
|
+
[0.44, 0.32, 0.45, 0.02, [[0.0, 0.0, 0.04], [0.11, -0.02, 0.05]]],
|
|
37
|
+
[0.62, 0.12, 0.40, -0.02, [[0.0, 0.0, 0.04], [0.13, 0.02, 0.055], [0.27, -0.02, 0.05]]],
|
|
38
|
+
[0.68, 0.28, 0.25, 0.03, [[0.0, 0.0, 0.035], [0.12, 0.02, 0.045]]],
|
|
39
|
+
[0.86, 0.10, 0.18, 0.02, [[0.0, 0.0, 0.04], [0.14, -0.02, 0.05], [0.28, 0.02, 0.045]]],
|
|
40
|
+
[0.94, 0.26, 0.10, -0.02, [[0.0, 0.0, 0.035], [0.12, 0.01, 0.04]]]
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
# Hokusai, Thunderstorm Beneath the Summit
|
|
44
|
+
piece :thunderstorm_beneath_the_summit, "Thunderstorm Beneath the Summit", "Katsushika Hokusai", "c. 1831", aspect: 1.45 do |u, v|
|
|
45
|
+
pu = 0.62
|
|
46
|
+
pv = 0.13
|
|
47
|
+
d = u - pu
|
|
48
|
+
slope = pv + d.abs * (d < 0 ? 0.95 : 0.80) - d * d * 0.25
|
|
49
|
+
if v >= slope
|
|
50
|
+
base_line = 0.56 + 0.04 * Math.sin(u * 11) + 0.02 * Math.sin(u * 37)
|
|
51
|
+
if v > base_line
|
|
52
|
+
# the lightning bolt crackling over the dark foot
|
|
53
|
+
lx = 0.70 + (v - 0.62) * 0.55 + 0.05 * (JP_TRI.((v - 0.6) * 7) - 0.5)
|
|
54
|
+
next [240, 70, 35] if v > 0.62 && v < 0.97 && (u - lx).abs < 0.014
|
|
55
|
+
next jitter([32, 26, 24], u, v, 8, 20)
|
|
56
|
+
end
|
|
57
|
+
# snow streaks running down from the summit
|
|
58
|
+
reach = pv + 0.07 + 0.10 * vnoise(u, 0.3, 30)
|
|
59
|
+
next mix([250, 248, 240], [215, 215, 225], (v - pv) * 5) if v < reach && Math.sin(u * 95) > -0.2
|
|
60
|
+
next [250, 248, 240] if v < pv + 0.03
|
|
61
|
+
base = mix([150, 55, 30], [95, 35, 25], smooth(0.2, 0.55, v))
|
|
62
|
+
streak = Math.sin(u * 120 + v * 15) > 0.7 ? 0.8 : 1.0
|
|
63
|
+
next Color.scale(jitter(base, u, v, 10), streak)
|
|
64
|
+
end
|
|
65
|
+
sky = mix([30, 75, 160], [195, 215, 230], smooth(0.0, 0.75, v))
|
|
66
|
+
# billowing clouds low on the left and right
|
|
67
|
+
cl = vnoise(u, v, 9) + smooth(0.25, 0.65, v) * 0.6 - 0.45
|
|
68
|
+
if cl > 0.35 && (u < 0.42 || u > 0.85)
|
|
69
|
+
next mix([255, 255, 250], [205, 210, 220], (cl - 0.35) * 0.5 + (v - 0.3))
|
|
70
|
+
end
|
|
71
|
+
sky
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Hokusai, Ejiri in Suruga Province
|
|
75
|
+
piece :ejiri_in_suruga, "Ejiri in Suruga Province", "Katsushika Hokusai", "c. 1831", aspect: 1.45 do |u, v|
|
|
76
|
+
x = u * 1.45
|
|
77
|
+
# sheets of paper whirling across the sky
|
|
78
|
+
papers = [[0.08, 0.55], [0.20, 0.42], [0.30, 0.30], [0.45, 0.36], [0.52, 0.22], [0.66, 0.30], [0.74, 0.16],
|
|
79
|
+
[0.88, 0.24], [0.98, 0.12], [0.60, 0.46], [0.38, 0.18], [1.10, 0.20]]
|
|
80
|
+
hit = JP_HIT.(papers.each_with_index do |(px, py), i|
|
|
81
|
+
a = i * 1.3
|
|
82
|
+
dx = x - px
|
|
83
|
+
dy = v - py
|
|
84
|
+
rx = dx * Math.cos(a) + dy * Math.sin(a)
|
|
85
|
+
ry = -dx * Math.sin(a) + dy * Math.cos(a)
|
|
86
|
+
next unless rx.abs < 0.022 && ry.abs < 0.016
|
|
87
|
+
break rx.abs > 0.016 || ry.abs > 0.011 ? [150, 140, 120] : [252, 250, 240]
|
|
88
|
+
end)
|
|
89
|
+
next hit if hit
|
|
90
|
+
# a hat tumbling up high
|
|
91
|
+
next [175, 135, 70] if ellipse(u, v, 0.56, 0.12, 0.022, 0.018) < 1
|
|
92
|
+
# bent trees on the right
|
|
93
|
+
hit = JP_HIT.([[0.78, 0.0], [0.86, 0.03], [0.70, 0.05]].each do |tx, off|
|
|
94
|
+
cx = tx - 0.22 * (0.92 - v)**2 + off
|
|
95
|
+
break [70, 55, 40] if v > 0.18 + off * 2 && v < 0.92 && (u - cx).abs < 0.008 + (v - 0.2) * 0.008
|
|
96
|
+
if v > 0.12 && v < 0.5 && ellipse(u, v, cx - 0.05, 0.22 + off * 3, 0.07, 0.05) < 1 && vnoise(u, v, 30) > 0.5
|
|
97
|
+
break [70, 95, 60]
|
|
98
|
+
end
|
|
99
|
+
end)
|
|
100
|
+
next hit if hit
|
|
101
|
+
# travelers bending into the wind along the path
|
|
102
|
+
hit = JP_HIT.([[0.18, 0.80], [0.30, 0.82], [0.42, 0.79], [0.55, 0.83], [0.64, 0.80]].each do |fx, fy|
|
|
103
|
+
next unless (u - fx).abs < 0.03 && v > fy - 0.10 && v < fy + 0.04
|
|
104
|
+
break [180, 140, 70] if ellipse(u, v, fx + 0.012, fy - 0.075, 0.022, 0.02) < 1 # straw hat
|
|
105
|
+
lean = fx + (fy - v) * 0.3
|
|
106
|
+
break [45, 55, 85] if (u - lean).abs < 0.012 && v > fy - 0.065
|
|
107
|
+
end)
|
|
108
|
+
next hit if hit
|
|
109
|
+
ground = 0.66 + 0.02 * Math.sin(u * 6)
|
|
110
|
+
if v > ground
|
|
111
|
+
next mix([165, 145, 95], [130, 105, 70], (v - 0.85) * 5) if v > 0.84
|
|
112
|
+
next Math.sin(u * 160 + v * 30) > 0.6 ? [140, 145, 95] : [185, 180, 125]
|
|
113
|
+
end
|
|
114
|
+
# faint outline of Fuji on the left
|
|
115
|
+
fd = (u - 0.25).abs
|
|
116
|
+
fslope = 0.40 + fd * 0.55
|
|
117
|
+
if fd < 0.25 && v > fslope - 0.012
|
|
118
|
+
next v < fslope + 0.012 ? [120, 125, 130] : [238, 236, 225]
|
|
119
|
+
end
|
|
120
|
+
mix([175, 195, 205], [235, 230, 205], smooth(0.0, 0.65, v))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Hokusai, Kajikazawa in Kai Province
|
|
124
|
+
piece :kajikazawa, "Kajikazawa in Kai Province", "Katsushika Hokusai", "c. 1831", aspect: 1.45 do |u, v|
|
|
125
|
+
x = u * 1.45
|
|
126
|
+
# fishing lines from the angler's hands into the surf
|
|
127
|
+
lines = [[0.74, 0.33, 1.00, 0.62], [0.74, 0.33, 1.10, 0.56], [0.74, 0.33, 1.18, 0.66]]
|
|
128
|
+
next [20, 20, 30] if lines.any? { |ax, ay, bx, by| JP_SEG.(x, v, ax, ay, bx, by) < 0.005 }
|
|
129
|
+
# the fisherman and the boy on the rock tip
|
|
130
|
+
next [25, 30, 45] if ellipse(u, v, 0.49, 0.36, 0.022, 0.07) < 1
|
|
131
|
+
next [205, 170, 95] if ellipse(u, v, 0.49, 0.285, 0.04, 0.02) < 1
|
|
132
|
+
next [60, 75, 110] if ellipse(u, v, 0.43, 0.40, 0.014, 0.04) < 1
|
|
133
|
+
rock = [[0.0, 0.62], [0.15, 0.55], [0.30, 0.48], [0.45, 0.43], [0.56, 0.42], [0.55, 0.50], [0.45, 0.62], [0.36, 0.78], [0.30, 1.0], [0.0, 1.0]]
|
|
134
|
+
if inside?(u, v, rock)
|
|
135
|
+
next [70, 105, 150] if Math.sin((u * 1.2 - v) * 40) > 0.8
|
|
136
|
+
next mix([28, 45, 80], [10, 18, 40], (v - 0.45) * 2)
|
|
137
|
+
end
|
|
138
|
+
sea_top = 0.50
|
|
139
|
+
if v > sea_top
|
|
140
|
+
# claws of surf around the rock and along the bottom
|
|
141
|
+
foam = Math.sin(u * 40 + Math.sin(v * 25) * 3) * 0.5 + 0.5
|
|
142
|
+
near = Math.exp(-((u - (0.55 - (v - 0.5) * 0.6))**2) * 300)
|
|
143
|
+
next [245, 248, 250] if foam * near > 0.5 || (v > 0.82 && Math.sin(u * 55 + v * 40) > 0.75)
|
|
144
|
+
next mix([95, 150, 205], [40, 90, 165], (v - sea_top) * 2.5)
|
|
145
|
+
end
|
|
146
|
+
# pale Fuji above the mist
|
|
147
|
+
fd = (u - 0.70).abs
|
|
148
|
+
if v > 0.12 + fd * 0.75 && v < 0.40
|
|
149
|
+
next v < 0.17 + 0.02 * Math.sin(u * 80) ? [250, 250, 248] : [140, 165, 195]
|
|
150
|
+
end
|
|
151
|
+
next [245, 240, 225] if v > 0.33 # mist
|
|
152
|
+
mix([90, 120, 170], [235, 232, 215], smooth(0.0, 0.3, v))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Hokusai, Kirifuri Waterfall at Kurokami Mountain
|
|
156
|
+
piece :kirifuri_waterfall, "Kirifuri Waterfall at Kurokami Mountain", "Katsushika Hokusai", "c. 1832", aspect: 0.68 do |u, v|
|
|
157
|
+
# tiny travelers at the bottom left
|
|
158
|
+
hit = JP_HIT.([[0.08, 0.86], [0.14, 0.87], [0.20, 0.88]].each do |fx, fy|
|
|
159
|
+
break [200, 160, 90] if ellipse(u, v, fx, fy - 0.035, 0.022, 0.009) < 1
|
|
160
|
+
break [40, 50, 80] if ellipse(u, v, fx, fy, 0.012, 0.03) < 1
|
|
161
|
+
end)
|
|
162
|
+
next hit if hit
|
|
163
|
+
next mix([120, 115, 90], [90, 85, 70], v) if v > 0.89 && u < 0.32 # ledge
|
|
164
|
+
# the falls: one stream at the top splitting into fingers
|
|
165
|
+
cx = 0.52 + 0.03 * Math.sin(v * 5)
|
|
166
|
+
half = v < 0.12 ? 0.07 : 0.07 + (v - 0.12) * 0.25
|
|
167
|
+
if (u - cx).abs < half && v < 0.92
|
|
168
|
+
f = (u - cx) / half
|
|
169
|
+
fingers = v < 0.2 ? 1.0 : Math.cos(f * Math::PI * 2.5 + v * 2)
|
|
170
|
+
if fingers > -0.35
|
|
171
|
+
streak = Math.sin(u * 160 + Math.sin(v * 12) * 2) > 0.3
|
|
172
|
+
next streak ? [235, 245, 250] : mix([60, 120, 190], [30, 80, 160], v)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
# spray and pool
|
|
176
|
+
if v > 0.86
|
|
177
|
+
next [240, 245, 250] if vnoise(u, v, 22) > 0.55
|
|
178
|
+
next [70, 120, 180]
|
|
179
|
+
end
|
|
180
|
+
# dark rocks and cliffs
|
|
181
|
+
if v > 0.07 + 0.04 * Math.sin(u * 9)
|
|
182
|
+
tone = vnoise(u, v, 10)
|
|
183
|
+
base = tone > 0.5 ? [40, 75, 70] : [70, 60, 45]
|
|
184
|
+
next Math.sin(u * 50 + v * 30 + tone * 6) > 0.6 ? Color.scale(base, 0.6) : base
|
|
185
|
+
end
|
|
186
|
+
# trees on top and sky
|
|
187
|
+
next [40, 80, 50] if vnoise(u, v, 30) > 0.55
|
|
188
|
+
[215, 210, 185]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Hiroshige, Sudden Shower over Shin-Ohashi Bridge and Atake
|
|
192
|
+
piece :sudden_shower_atake, "Sudden Shower over Shin-Ōhashi Bridge and Atake", "Utagawa Hiroshige", "1857", aspect: 0.68 do |u, v|
|
|
193
|
+
deck = 0.84 - u * 0.30
|
|
194
|
+
col =
|
|
195
|
+
if v < 0.14
|
|
196
|
+
mix([20, 20, 25], [90, 95, 95], smooth(0.0, 0.14, v) * (0.6 + 0.4 * vnoise(u, v, 12)))
|
|
197
|
+
elsif v > 0.34 && v < 0.41 + 0.01 * Math.sin(u * 30)
|
|
198
|
+
Math.sin(u * 60) > 0.3 ? [70, 95, 70] : [45, 70, 55] # far shore of Atake
|
|
199
|
+
elsif ellipse(u, v, 0.30, 0.47, 0.14, 0.012) < 1
|
|
200
|
+
[110, 90, 60] # a raft
|
|
201
|
+
elsif v >= 0.38
|
|
202
|
+
mix([120, 150, 160], [60, 95, 125], (v - 0.4) * 1.5)
|
|
203
|
+
else
|
|
204
|
+
mix([150, 155, 150], [190, 190, 180], (v - 0.14) * 3)
|
|
205
|
+
end
|
|
206
|
+
if v > deck - 0.03 && v < deck + 0.03
|
|
207
|
+
col = v < deck ? [185, 150, 95] : [120, 90, 55] # planks and their shadowed edge
|
|
208
|
+
elsif v > deck && v < 1.0 && ((u * 6 + 0.3) % 1.0) < 0.14
|
|
209
|
+
col = [80, 60, 40] # pilings
|
|
210
|
+
end
|
|
211
|
+
# people hurrying across under umbrellas and straw capes
|
|
212
|
+
cell = (u * 7).floor
|
|
213
|
+
fu = (u * 7) % 1.0
|
|
214
|
+
pv = deck - 0.03
|
|
215
|
+
if noise(cell, 3) > 0.25 && fu > 0.25 && fu < 0.75 && v < pv && v > pv - 0.10
|
|
216
|
+
if v < pv - 0.065
|
|
217
|
+
col = noise(cell, 5) > 0.5 ? [45, 50, 60] : [200, 175, 115] if ellipse(fu, v, 0.5, pv - 0.065, 0.35, 0.035) < 1 && v < pv - 0.065
|
|
218
|
+
else
|
|
219
|
+
col = noise(cell, 6) > 0.5 ? [55, 65, 95] : [150, 120, 70] if (fu - 0.5).abs < 0.18
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
rain = ((u * 28 + v * 4) % 1.0) < 0.2 && noise((u * 28 + v * 4).floor, (v * 6).floor) > 0.25
|
|
223
|
+
rain ||= ((u * 19 - v * 2) % 1.0) < 0.14 && noise((u * 19 - v * 2).floor + 40, (v * 5).floor) > 0.4
|
|
224
|
+
rain ? mix(col, [45, 50, 60], 0.4) : col
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Hiroshige, Plum Park in Kameido
|
|
228
|
+
piece :plum_garden_kameido, "Plum Park in Kameido", "Utagawa Hiroshige", "1857", aspect: 0.68 do |u, v|
|
|
229
|
+
x = u * 0.68
|
|
230
|
+
# blossoms along the branches
|
|
231
|
+
blossoms = [[0.12, 0.30], [0.20, 0.22], [0.30, 0.26], [0.40, 0.20], [0.50, 0.30], [0.58, 0.24], [0.64, 0.32],
|
|
232
|
+
[0.08, 0.45], [0.16, 0.12], [0.36, 0.36], [0.46, 0.14], [0.26, 0.42], [0.55, 0.40]]
|
|
233
|
+
hit = JP_HIT.(blossoms.each do |bx, by|
|
|
234
|
+
dd = Math.hypot(x - bx, v - by)
|
|
235
|
+
break [210, 60, 70] if dd < 0.008
|
|
236
|
+
break [252, 250, 245] if dd < 0.026
|
|
237
|
+
end)
|
|
238
|
+
next hit if hit
|
|
239
|
+
# the huge foreground branch: trunk rising on the left, limb sweeping right
|
|
240
|
+
trunk = 0.17 + (v - 0.95) * -0.08 + 0.02 * Math.sin(v * 20)
|
|
241
|
+
if v > 0.30 && (x - trunk).abs < 0.075 + 0.02 * Math.sin(v * 13)
|
|
242
|
+
next Math.sin(v * 60 + u * 20) > 0.4 ? [55, 35, 30] : [30, 20, 20]
|
|
243
|
+
end
|
|
244
|
+
limb_v = 0.30 - (x - 0.17) * 0.18 + 0.03 * Math.sin(x * 25)
|
|
245
|
+
next [35, 22, 20] if x > 0.12 && (v - limb_v).abs < 0.045 - (x - 0.17) * 0.05
|
|
246
|
+
twigs = [[0.30, 0.27, 0.42, 0.13], [0.45, 0.25, 0.62, 0.35], [0.20, 0.40, 0.06, 0.48], [0.18, 0.30, 0.14, 0.10]]
|
|
247
|
+
next [35, 22, 20] if twigs.any? { |ax, ay, bx, by| JP_SEG.(x, v, ax, ay, bx, by) < 0.008 }
|
|
248
|
+
# background trees and visitors
|
|
249
|
+
if v > 0.62
|
|
250
|
+
next [60, 40, 35] if ((u * 6) % 1.0) < 0.06 && v < 0.75
|
|
251
|
+
next [60, 60, 80] if v > 0.70 && v < 0.75 && noise((u * 40).floor, 2) > 0.8
|
|
252
|
+
next mix([150, 185, 110], [90, 140, 80], (v - 0.62) * 3)
|
|
253
|
+
end
|
|
254
|
+
if v > 0.52 && vnoise(u, v, 18) > 0.5
|
|
255
|
+
next noise((u * 40).floor, (v * 40).floor) > 0.8 ? [250, 245, 240] : [110, 80, 70]
|
|
256
|
+
end
|
|
257
|
+
ramp(smooth(0.0, 0.55, v), [175, 30, 40], [225, 110, 90], [250, 240, 225], [250, 248, 240])
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Hiroshige, Fireworks at Ryogoku
|
|
261
|
+
piece :fireworks_ryogoku, "Fireworks at Ryōgoku", "Utagawa Hiroshige", "1858", aspect: 0.68 do |u, v|
|
|
262
|
+
x = u * 0.68
|
|
263
|
+
# burst of sparks
|
|
264
|
+
dx = x - 0.40
|
|
265
|
+
dy = v - 0.30
|
|
266
|
+
r = Math.hypot(dx, dy)
|
|
267
|
+
a = Math.atan2(dy, dx)
|
|
268
|
+
if r < 0.16
|
|
269
|
+
ray = Math.cos(a * 11 + r * 8)
|
|
270
|
+
spark = noise((a * 18).floor, (r * 70).floor)
|
|
271
|
+
next [255, 245, 200] if r < 0.012
|
|
272
|
+
next mix([255, 230, 150], [230, 150, 70], r / 0.16) if ray > 0.85 && spark > 0.3
|
|
273
|
+
next [240, 200, 120] if spark > 0.93
|
|
274
|
+
end
|
|
275
|
+
# trailing rocket
|
|
276
|
+
next [220, 180, 110] if (x - 0.42).abs < 0.006 && v > 0.47 && v < 0.62 && noise(0, (v * 50).floor) > 0.35
|
|
277
|
+
# the bridge across the lower part
|
|
278
|
+
deck = 0.70 + (u - 0.5) * 0.05
|
|
279
|
+
if v > deck - 0.02 && v < deck + 0.015
|
|
280
|
+
next noise((u * 60).floor, 1) > 0.6 && v < deck - 0.005 ? [30, 30, 45] : [20, 20, 30]
|
|
281
|
+
end
|
|
282
|
+
next [18, 18, 28] if v > deck && v < 0.88 && ((u * 10) % 1.0) < 0.1
|
|
283
|
+
if v > 0.62
|
|
284
|
+
# boats on the river
|
|
285
|
+
hit = JP_HIT.([[0.18, 0.92], [0.55, 0.90], [0.82, 0.95], [0.38, 0.97]].each do |px, py|
|
|
286
|
+
break [25, 25, 30] if ellipse(u, v, px, py, 0.09, 0.015) < 1
|
|
287
|
+
break [210, 160, 80] if ellipse(u, v, px, py - 0.025, 0.035, 0.012) < 1
|
|
288
|
+
end)
|
|
289
|
+
next hit if hit
|
|
290
|
+
next Math.sin(v * 120 + u * 6) > 0.8 ? [50, 70, 110] : mix([25, 40, 80], [15, 25, 55], (v - 0.62) * 2)
|
|
291
|
+
end
|
|
292
|
+
# far shore
|
|
293
|
+
next [15, 20, 35] if v > 0.60 - 0.01 * Math.sin(u * 20)
|
|
294
|
+
mix([10, 15, 45], [40, 55, 105], smooth(0.0, 0.65, v))
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Hiroshige, Night Snow at Kambara
|
|
298
|
+
piece :evening_snow_kanbara, "Night Snow at Kambara", "Utagawa Hiroshige", "1833", aspect: 1.5 do |u, v|
|
|
299
|
+
snow = [238, 238, 240]
|
|
300
|
+
col =
|
|
301
|
+
if v >= 0.80
|
|
302
|
+
# the snowy road with three hunched walkers
|
|
303
|
+
walk = [[0.32, 0.88], [0.44, 0.89], [0.60, 0.87]].find { |fx, fy| ellipse(u, v, fx, fy, 0.022, 0.07) < 1 }
|
|
304
|
+
if walk
|
|
305
|
+
v < walk[1] - 0.035 ? [175, 165, 130] : [55, 55, 70]
|
|
306
|
+
else
|
|
307
|
+
mix(snow, [205, 205, 215], (v - 0.8) * 4)
|
|
308
|
+
end
|
|
309
|
+
elsif v > 0.58
|
|
310
|
+
# village: snow-laden roofs over dark walls
|
|
311
|
+
cell = (u * 7).floor
|
|
312
|
+
fu = (u * 7) % 1.0
|
|
313
|
+
roof = 0.60 + noise(cell, 2) * 0.05
|
|
314
|
+
if fu > 0.08 && fu < 0.92 && v > roof
|
|
315
|
+
if v < roof + 0.05 + 0.02 * (1 - (fu - 0.5).abs * 2)
|
|
316
|
+
snow
|
|
317
|
+
elsif v < 0.76
|
|
318
|
+
noise(cell, 4) > 0.6 && fu > 0.4 && fu < 0.6 && v > 0.68 ? [150, 120, 70] : [50, 48, 52]
|
|
319
|
+
else
|
|
320
|
+
snow
|
|
321
|
+
end
|
|
322
|
+
else
|
|
323
|
+
snow
|
|
324
|
+
end
|
|
325
|
+
elsif v > 0.10 + u * 0.55 + 0.03 * Math.sin(u * 17)
|
|
326
|
+
# the steep snowy hillside, dotted with dark trees
|
|
327
|
+
tree = vnoise(u, v, 10) > 0.66 && Math.sin(u * 120) > 0.3
|
|
328
|
+
tree ? [60, 60, 65] : snow
|
|
329
|
+
elsif u > 0.5 && v > 0.30 + 0.06 * Math.sin(u * 9)
|
|
330
|
+
[70, 70, 76] # far dark hills
|
|
331
|
+
else
|
|
332
|
+
mix([25, 25, 30], [110, 110, 115], smooth(0.0, 0.6, v))
|
|
333
|
+
end
|
|
334
|
+
noise((u * 60).floor, (v * 40).floor) > 0.965 ? [250, 250, 250] : col
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Hiroshige, Moon Pine, Ueno
|
|
338
|
+
piece :moon_pine_ueno, "Moon Pine, Ueno", "Utagawa Hiroshige", "1857", aspect: 0.68 do |u, v|
|
|
339
|
+
x = u * 0.68
|
|
340
|
+
# the pine branch twisted into a ring
|
|
341
|
+
cx = 0.34
|
|
342
|
+
cy = 0.42
|
|
343
|
+
r = Math.hypot(x - cx, v - cy)
|
|
344
|
+
if (r - 0.20).abs < 0.022 + 0.006 * Math.sin(Math.atan2(v - cy, x - cx) * 5)
|
|
345
|
+
next Math.sin(r * 300) > 0 ? [80, 55, 35] : [55, 35, 25]
|
|
346
|
+
end
|
|
347
|
+
# the trunk coming in from the top left, with needle clumps
|
|
348
|
+
next [60, 40, 28] if JP_SEG.(x, v, 0.0, 0.08, 0.20, 0.25) < 0.025
|
|
349
|
+
clumps = [[0.08, 0.06], [0.22, 0.10], [0.50, 0.20], [0.56, 0.36], [0.05, 0.22], [0.40, 0.60]]
|
|
350
|
+
if clumps.any? { |nx, ny| ellipse(x, v, nx, ny, 0.08, 0.04) < 1 }
|
|
351
|
+
next Math.sin(x * 200 + v * 80) > 0 ? [40, 90, 50] : [25, 60, 40]
|
|
352
|
+
end
|
|
353
|
+
# foreground bank
|
|
354
|
+
next mix([130, 110, 70], [90, 75, 50], (v - 0.8) * 4) if v > 0.80 + 0.02 * Math.sin(u * 10)
|
|
355
|
+
# pond and far shore with town
|
|
356
|
+
if v > 0.58
|
|
357
|
+
next [45, 80, 55] if v < 0.62 && noise((u * 30).floor, 7) > 0.3
|
|
358
|
+
next Math.sin(v * 150) > 0.7 ? [150, 180, 205] : [80, 130, 175]
|
|
359
|
+
end
|
|
360
|
+
if v > 0.53
|
|
361
|
+
next noise((u * 40).floor, 3) > 0.5 ? [80, 70, 65] : [40, 70, 55]
|
|
362
|
+
end
|
|
363
|
+
ramp(v / 0.53, [70, 120, 185], [165, 195, 220], [240, 220, 200], [225, 110, 80])
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Sharaku, Otani Oniji III as Yakko Edobei
|
|
367
|
+
piece :otani_oniji, "Ōtani Oniji III as Yakko Edobei", "Tōshūsai Sharaku", "1794", aspect: 0.68 do |u, v|
|
|
368
|
+
x = u * 0.68
|
|
369
|
+
skin = [242, 232, 220]
|
|
370
|
+
ink = [20, 18, 22]
|
|
371
|
+
# hands with claw-like fingers
|
|
372
|
+
hands = [[0.13, 0.74, 1], [0.54, 0.70, -1]]
|
|
373
|
+
claw = hands.any? do |hx, hy, dir|
|
|
374
|
+
Math.hypot(x - hx, v - hy) < 0.045 ||
|
|
375
|
+
5.times.any? do |k|
|
|
376
|
+
a = -2.7 + k * 0.38
|
|
377
|
+
JP_SEG.(x, v, hx, hy, hx + Math.cos(a) * 0.10 * dir, hy + Math.sin(a) * 0.10) < 0.014
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
next skin if claw
|
|
381
|
+
# face
|
|
382
|
+
fx = 0.34
|
|
383
|
+
fy = 0.32
|
|
384
|
+
face = ellipse(x, v, fx, fy, 0.13, 0.15)
|
|
385
|
+
if face < 1 && v > 0.22
|
|
386
|
+
dx = x - fx
|
|
387
|
+
next ink if (v - (0.29 - dx.abs * 0.35)).abs < 0.012 && dx.abs > 0.025 && dx.abs < 0.10 # brows
|
|
388
|
+
next ink if (v - 0.325).abs < 0.008 && (dx.abs - 0.055).abs < 0.022 # eyes
|
|
389
|
+
next [180, 120, 110] if dx.abs < 0.012 && v > 0.33 && v < 0.39 # nose
|
|
390
|
+
next [150, 30, 40] if (v - (0.41 + dx.abs * 0.25)).abs < 0.01 && dx.abs < 0.06 # grimace
|
|
391
|
+
next mix(skin, [215, 195, 185], face)
|
|
392
|
+
end
|
|
393
|
+
# hair: shaved blue-grey pate, black sides and topknot
|
|
394
|
+
next [120, 130, 150] if ellipse(x, v, fx, 0.20, 0.09, 0.07) < 1
|
|
395
|
+
next ink if ellipse(x, v, fx, 0.24, 0.16, 0.12) < 1 || ellipse(x, v, fx, 0.11, 0.035, 0.03) < 1
|
|
396
|
+
# dark kimono with white under-collar
|
|
397
|
+
shoulder = 0.48 + (x - fx).abs * 0.35
|
|
398
|
+
if v > shoulder - 0.02 && (x - fx).abs < 0.30
|
|
399
|
+
cdx = (x - fx).abs
|
|
400
|
+
next [235, 235, 230] if v > 0.45 && (cdx - (v - 0.45) * 0.5).abs < 0.014
|
|
401
|
+
next skin if v > 0.45 && cdx < (v - 0.45) * 0.5 && v < 0.58
|
|
402
|
+
next Math.sin(x * 90 + v * 30) > 0.85 ? [60, 55, 70] : [25, 25, 35]
|
|
403
|
+
end
|
|
404
|
+
# dark glittering mica ground
|
|
405
|
+
noise((u * 50).floor, (v * 70).floor) > 0.9 ? [120, 115, 125] : [55, 50, 58]
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Utamaro, Three Beauties of the Present Day
|
|
409
|
+
piece :three_beauties, "Three Beauties of the Present Day", "Kitagawa Utamaro", "c. 1793", aspect: 0.68 do |u, v|
|
|
410
|
+
x = u * 0.68
|
|
411
|
+
skin = [248, 240, 230]
|
|
412
|
+
hair = [20, 18, 22]
|
|
413
|
+
# [face x, face y, kimono color]; the top beauty sits behind the other two
|
|
414
|
+
beauties = [[0.17, 0.58, [70, 60, 80]], [0.51, 0.58, [200, 110, 60]], [0.34, 0.30, [40, 40, 45]]]
|
|
415
|
+
hit = JP_HIT.(beauties.each do |fx, fy, kc|
|
|
416
|
+
e = ellipse(x, v, fx, fy, 0.075, 0.095)
|
|
417
|
+
if e < 1
|
|
418
|
+
dx = x - fx
|
|
419
|
+
break hair if (v - (fy - 0.015)).abs < 0.006 && (dx.abs - 0.03).abs < 0.015
|
|
420
|
+
break [190, 40, 50] if (v - (fy + 0.055)).abs < 0.007 && dx.abs < 0.012
|
|
421
|
+
break skin
|
|
422
|
+
end
|
|
423
|
+
break hair if ellipse(x, v, fx, fy - 0.10, 0.10, 0.07) < 1 || ellipse(x, v, fx, fy - 0.03, 0.10, 0.05) < 1
|
|
424
|
+
break [220, 190, 110] if (v - (fy - 0.13)).abs < 0.008 && (x - fx).abs < 0.12 # comb
|
|
425
|
+
if v > fy + 0.09 && (x - fx).abs < 0.06 + (v - fy - 0.09) * 0.9
|
|
426
|
+
break [245, 240, 235] if ((x - fx).abs - (v - fy - 0.09) * 0.4).abs < 0.012 # collar
|
|
427
|
+
break skin if (x - fx).abs < (v - fy - 0.09) * 0.4 && v < fy + 0.16
|
|
428
|
+
break Math.sin(x * 120 + v * 60) > 0.7 ? Color.scale(kc, 1.3) : kc
|
|
429
|
+
end
|
|
430
|
+
end)
|
|
431
|
+
next hit if hit
|
|
432
|
+
jitter([225, 185, 95], u, v, 10, 20)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# Sotatsu, Wind God and Thunder God
|
|
436
|
+
piece :fujin_raijin, "Wind God and Thunder God", "Tawaraya Sōtatsu", "17th century", aspect: 2.2 do |u, v|
|
|
437
|
+
x = u * 2.2
|
|
438
|
+
dark = [35, 30, 30]
|
|
439
|
+
# [center x, skin, facing]: Fujin crouching on the left, Raijin on the right
|
|
440
|
+
gods = [[0.40, [80, 155, 105], 1], [1.80, [240, 238, 228], -1]]
|
|
441
|
+
hit = JP_HIT.(gods.each do |cx, skin, dir|
|
|
442
|
+
next if (x - cx).abs > 0.45
|
|
443
|
+
hx = cx + 0.05 * dir
|
|
444
|
+
if dir.positive?
|
|
445
|
+
# the long white wind bag, looped over his head from hand to hand
|
|
446
|
+
r = Math.hypot(x - cx, (v - 0.40) * 1.1)
|
|
447
|
+
break(Math.sin(r * 90) > 0.6 ? [190, 195, 185] : [240, 240, 232]) if (r - 0.25).abs < 0.04 && v < 0.44
|
|
448
|
+
else
|
|
449
|
+
# the ring of drums around the thunder god
|
|
450
|
+
r = Math.hypot(x - cx, v - 0.38)
|
|
451
|
+
ang = Math.atan2(v - 0.38, x - cx)
|
|
452
|
+
bead = (ang / (Math::PI / 4)).round * (Math::PI / 4)
|
|
453
|
+
bd = Math.hypot(x - (cx + Math.cos(bead) * 0.28), v - (0.38 + Math.sin(bead) * 0.28))
|
|
454
|
+
break dark if bd < 0.055 && bd > 0.04
|
|
455
|
+
break [200, 80, 45] if bd < 0.04
|
|
456
|
+
break dark if (r - 0.28).abs < 0.008 && v < 0.6
|
|
457
|
+
end
|
|
458
|
+
# wild hair: dark strokes flaring from the head
|
|
459
|
+
hair = 5.times.any? do |k|
|
|
460
|
+
a = -2.6 + k * 0.4
|
|
461
|
+
JP_SEG.(x, v, hx, 0.24, hx + Math.cos(a) * 0.12, 0.24 + Math.sin(a) * 0.12) < 0.016
|
|
462
|
+
end
|
|
463
|
+
break dark if hair
|
|
464
|
+
# head, crouched body and bent limbs, outlined in ink
|
|
465
|
+
head = ellipse(x, v, hx, 0.28, 0.065, 0.065)
|
|
466
|
+
body = ellipse(x, v, cx, 0.45, 0.10, 0.12)
|
|
467
|
+
limbs = [[cx - 0.06, 0.38, cx - 0.22, 0.40], [cx + 0.06, 0.38, cx + 0.22, 0.40],
|
|
468
|
+
[cx - 0.05, 0.52, cx - 0.16, 0.48], [cx - 0.16, 0.48, cx - 0.14, 0.62],
|
|
469
|
+
[cx + 0.05, 0.52, cx + 0.16, 0.50], [cx + 0.16, 0.50, cx + 0.18, 0.62]]
|
|
470
|
+
ld = limbs.map { |ax, ay, bx, by| JP_SEG.(x, v, ax, ay, bx, by) }.min
|
|
471
|
+
if head < 1
|
|
472
|
+
break dark if (v - 0.27).abs < 0.01 && ((x - hx) * dir - 0.025).abs < 0.012 # glaring eye
|
|
473
|
+
break [180, 40, 35] if (v - 0.31).abs < 0.008 && ((x - hx) * dir - 0.02).abs < 0.025 # open mouth
|
|
474
|
+
break head > 0.6 ? Color.scale(skin, 0.75) : skin
|
|
475
|
+
end
|
|
476
|
+
break(body > 0.65 ? Color.scale(skin, 0.75) : skin) if body < 1
|
|
477
|
+
break [160, 45, 35] if ellipse(x, v, cx, 0.55, 0.12, 0.035) < 1 # loincloth
|
|
478
|
+
break(ld > 0.022 ? Color.scale(skin, 0.6) : skin) if ld < 0.03
|
|
479
|
+
# the dark swirling cloud beneath
|
|
480
|
+
cr = ellipse(x, v, cx, 0.68, 0.36, 0.13) - (vnoise(u, v, 12) - 0.5) * 0.9
|
|
481
|
+
if cr < 1
|
|
482
|
+
sw = Math.sin(Math.hypot(x - cx, (v - 0.68) * 2.5) * 45 - Math.atan2(v - 0.68, x - cx) * 2)
|
|
483
|
+
break sw > 0.6 ? [130, 130, 135] : [55, 55, 60]
|
|
484
|
+
end
|
|
485
|
+
end)
|
|
486
|
+
next hit if hit
|
|
487
|
+
# gold leaf with faint square seams
|
|
488
|
+
gold = jitter([215, 170, 70], u, v, 10, 12)
|
|
489
|
+
((x * 6) % 1.0) < 0.04 || ((v * 4) % 1.0) < 0.04 ? Color.scale(gold, 0.92) : gold
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Hasegawa Tohaku, Pine Trees
|
|
493
|
+
piece :pine_trees_tohaku, "Pine Trees", "Hasegawa Tōhaku", "c. 1595", aspect: 2.5 do |u, v|
|
|
494
|
+
paper = jitter([238, 234, 222], u, v, 4, 10)
|
|
495
|
+
# mist: bottom and middle fade to paper, with drifting soft banks
|
|
496
|
+
mist = (1.0 - smooth(0.40, 0.95, v) * 0.97) * (0.7 + 0.3 * smooth(0.3, 0.7, vnoise(u * 0.7, v, 4)))
|
|
497
|
+
next paper if mist < 0.02
|
|
498
|
+
ink = 0.0
|
|
499
|
+
JP_PINES.each do |tx, top, dens, lean, blots|
|
|
500
|
+
next if v < top - 0.08 || (u - tx).abs > 0.16
|
|
501
|
+
cx = tx + (v - top) * lean
|
|
502
|
+
a = (u - cx).abs < 0.006 && v > top ? 0.7 : 0.0 # thin trunk
|
|
503
|
+
blots.each do |dy, dx, rx|
|
|
504
|
+
e = ellipse(u, v, cx + dx * 1.3, top + dy, rx * 1.6, 0.055) + (vnoise(u, v, 22) - 0.5) * 0.6
|
|
505
|
+
next unless e < 1
|
|
506
|
+
a = [a, (1 - e) * 2.2 * (0.45 + 0.55 * vnoise(u * 2, v, 50))].max
|
|
507
|
+
end
|
|
508
|
+
ink = [ink, a.clamp(0.0, 1.0) * dens].max
|
|
509
|
+
end
|
|
510
|
+
mix(paper, [28, 28, 28], ink * mist)
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# Ogata Korin, Irises
|
|
514
|
+
piece :irises_korin, "Irises", "Ogata Kōrin", "c. 1705", aspect: 2.4 do |u, v|
|
|
515
|
+
x = u * 2.4
|
|
516
|
+
clumps = [[0.10, 0.20], [0.35, 0.32], [0.62, 0.15], [0.90, 0.40], [1.15, 0.22], [1.42, 0.30], [1.70, 0.12], [1.98, 0.35], [2.25, 0.20]]
|
|
517
|
+
hit = JP_HIT.(clumps.each_with_index do |(cx, top), i|
|
|
518
|
+
dx = x - cx
|
|
519
|
+
next if dx.abs > 0.14
|
|
520
|
+
# blossoms at the top of the clump
|
|
521
|
+
petal = 5.times.map do |k|
|
|
522
|
+
ellipse(x, v, cx + (k - 2) * 0.045 + (noise(i, k) - 0.5) * 0.03, top + 0.06 + noise(k, i) * 0.12, 0.03, 0.07)
|
|
523
|
+
end.min
|
|
524
|
+
break(petal < 0.35 ? [120, 110, 210] : [45, 40, 140]) if petal < 1
|
|
525
|
+
# sword leaves
|
|
526
|
+
base = top + 0.15
|
|
527
|
+
if v > base && dx.abs < 0.09 + (1 - v) * 0.02
|
|
528
|
+
blade = Math.sin(dx * 110 + (v - base) * 6 * (dx < 0 ? 1 : -1))
|
|
529
|
+
break(blade > 0.6 ? [70, 140, 60] : [35, 100, 45]) if blade > -0.1
|
|
530
|
+
end
|
|
531
|
+
end)
|
|
532
|
+
next hit if hit
|
|
533
|
+
jitter([220, 175, 70], u, v, 10, 14)
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# Ogata Korin, Red and White Plum Blossoms
|
|
537
|
+
piece :red_white_plum, "Red and White Plum Blossoms", "Ogata Kōrin", "c. 1710s", aspect: 2.2 do |u, v|
|
|
538
|
+
x = u * 2.2
|
|
539
|
+
# the stylized stream, widening downward
|
|
540
|
+
sc = 1.1 + 0.08 * Math.sin(v * 4)
|
|
541
|
+
half = 0.08 + v * v * 0.45
|
|
542
|
+
if (x - sc).abs < half
|
|
543
|
+
swirl = Math.sin((x - sc) * 30 + Math.sin(v * 14 + x * 6) * 3 + v * 10)
|
|
544
|
+
next swirl > 0.75 ? [150, 150, 155] : [35, 35, 45]
|
|
545
|
+
end
|
|
546
|
+
# white plum on the left: trunk from the left edge, branch dipping and rising
|
|
547
|
+
white = [[0.30, 0.15], [0.42, 0.11], [0.18, 0.32], [0.40, 0.58], [0.48, 0.62], [0.12, 0.70], [0.24, 0.46]]
|
|
548
|
+
next [252, 252, 248] if white.any? { |bx, by| Math.hypot(x - bx, v - by) < 0.035 }
|
|
549
|
+
wb = JP_SEG.(x, v, 0.0, 0.85, 0.25, 0.55) < 0.04 || JP_SEG.(x, v, 0.25, 0.55, 0.20, 0.25) < 0.025 ||
|
|
550
|
+
JP_SEG.(x, v, 0.20, 0.25, 0.45, 0.10) < 0.018 || JP_SEG.(x, v, 0.25, 0.55, 0.50, 0.60) < 0.018
|
|
551
|
+
next [50, 55, 40] if wb
|
|
552
|
+
# red plum on the right: a thick trunk leaning in from the right
|
|
553
|
+
red = [[1.70, 0.12], [1.62, 0.40], [1.78, 0.18], [1.70, 0.43], [2.05, 0.20], [1.98, 0.40], [1.90, 0.10]]
|
|
554
|
+
next [215, 50, 60] if red.any? { |bx, by| Math.hypot(x - bx, v - by) < 0.035 }
|
|
555
|
+
rb = JP_SEG.(x, v, 2.05, 1.0, 1.85, 0.55) < 0.07 || JP_SEG.(x, v, 1.85, 0.55, 1.95, 0.25) < 0.05 ||
|
|
556
|
+
JP_SEG.(x, v, 1.95, 0.25, 1.65, 0.12) < 0.025 || JP_SEG.(x, v, 1.88, 0.45, 1.60, 0.40) < 0.02
|
|
557
|
+
next Math.sin(v * 50) > 0.6 ? [70, 80, 50] : [45, 45, 35] if rb
|
|
558
|
+
next [90, 130, 70] if v > 0.9 && (x < 0.6 || x > 1.6) # grassy bank
|
|
559
|
+
jitter([220, 180, 80], u, v, 10, 12)
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
# Toba Sojo (attr.), Choju-giga
|
|
563
|
+
piece :choju_giga, "Chōjū-giga (Frolicking Animals)", "attributed to Toba Sōjō", "12th century", aspect: 2.5 do |u, v|
|
|
564
|
+
x = u * 2.5
|
|
565
|
+
ink = [40, 35, 30]
|
|
566
|
+
line = 0.024
|
|
567
|
+
# [cx, cy, rx, ry, angle, fill]: the rabbit thrown off balance and the frog pushing it
|
|
568
|
+
parts = [
|
|
569
|
+
[0.82, 0.60, 0.15, 0.27, 0.45, [250, 246, 236]], # rabbit body
|
|
570
|
+
[0.62, 0.32, 0.12, 0.10, 0.30, [250, 246, 236]], # rabbit head
|
|
571
|
+
[0.50, 0.14, 0.045, 0.15, 0.6, [250, 246, 236]], # long ears
|
|
572
|
+
[0.62, 0.11, 0.045, 0.15, 0.3, [250, 246, 236]],
|
|
573
|
+
[1.42, 0.62, 0.24, 0.20, 0.0, [205, 200, 175]], # frog body
|
|
574
|
+
[1.22, 0.40, 0.06, 0.06, 0.0, [205, 200, 175]], # frog eyes
|
|
575
|
+
[1.40, 0.38, 0.06, 0.06, 0.0, [205, 200, 175]],
|
|
576
|
+
[1.70, 0.86, 0.12, 0.05, 0.4, [205, 200, 175]], # frog legs
|
|
577
|
+
[1.18, 0.88, 0.12, 0.05, -0.4, [205, 200, 175]]
|
|
578
|
+
]
|
|
579
|
+
dists = parts.map { |cx, cy, rx, ry, ang| JP_RING.(x, v, cx, cy, rx, ry, ang) }
|
|
580
|
+
next ink if dists.any? { |dd| dd.abs < line }
|
|
581
|
+
next ink if Math.hypot(x - 0.58, v - 0.30) < 0.024 || Math.hypot(x - 1.22, v - 0.40) < 0.025 || Math.hypot(x - 1.40, v - 0.38) < 0.025
|
|
582
|
+
# arms locked in the struggle, and the frog's wide mouth
|
|
583
|
+
next ink if JP_SEG.(x, v, 0.95, 0.42, 1.22, 0.55) < 0.02 || JP_SEG.(x, v, 1.20, 0.65, 0.95, 0.66) < 0.02
|
|
584
|
+
next ink if JP_SEG.(x, v, 1.20, 0.52, 1.42, 0.50) < 0.015
|
|
585
|
+
idx = dists.index(&:negative?)
|
|
586
|
+
next parts[idx][5] if idx
|
|
587
|
+
# grass tufts
|
|
588
|
+
tufts = [[0.25, 0.95], [1.95, 0.94], [2.30, 0.92], [0.05, 0.93]]
|
|
589
|
+
next ink if tufts.any? { |gx, gy| 3.times.any? { |k| JP_SEG.(x, v, gx, gy, gx + (k - 1) * 0.07, gy - 0.16 - k * 0.03) < 0.014 } }
|
|
590
|
+
jitter([238, 226, 196], u, v, 6, 10)
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# Hishikawa Moronobu, Beauty Looking Back
|
|
594
|
+
piece :mikaeri_bijin, "Beauty Looking Back", "Hishikawa Moronobu", "late 17th century", aspect: 0.5 do |u, v|
|
|
595
|
+
# head turned back over the shoulder
|
|
596
|
+
next [245, 232, 215] if ellipse(u, v, 0.60, 0.15, 0.06, 0.035) < 1
|
|
597
|
+
next [20, 18, 20] if ellipse(u, v, 0.48, 0.13, 0.11, 0.04) < 1 || ellipse(u, v, 0.43, 0.17, 0.05, 0.05) < 1
|
|
598
|
+
next [200, 160, 60] if (v - 0.10).abs < 0.006 && (u - 0.50).abs < 0.06 # comb
|
|
599
|
+
body = [[0.40, 0.20], [0.62, 0.19], [0.70, 0.32], [0.66, 0.50], [0.60, 0.70], [0.66, 0.88], [0.62, 0.95],
|
|
600
|
+
[0.22, 0.97], [0.16, 0.93], [0.34, 0.80], [0.40, 0.55], [0.36, 0.35]]
|
|
601
|
+
if inside?(u, v, body)
|
|
602
|
+
# obi
|
|
603
|
+
next(Math.sin(u * 60) > 0.5 ? [90, 110, 90] : [50, 70, 60]) if v > 0.38 && v < 0.45
|
|
604
|
+
# roundels of flowers on the red kimono
|
|
605
|
+
cu = (u * 7).floor
|
|
606
|
+
cv = (v * 10).floor
|
|
607
|
+
rx = (u * 7) % 1.0 - 0.5
|
|
608
|
+
ry = (v * 10) % 1.0 - 0.5
|
|
609
|
+
if Math.hypot(rx * 1.4, ry) < 0.28 && (cu + cv).even?
|
|
610
|
+
next noise(cu, cv) > 0.5 ? [240, 220, 150] : [120, 160, 120]
|
|
611
|
+
end
|
|
612
|
+
next mix([200, 45, 40], [160, 30, 35], v)
|
|
613
|
+
end
|
|
614
|
+
# a hand peeking from the sleeve
|
|
615
|
+
next [245, 232, 215] if ellipse(u, v, 0.68, 0.42, 0.03, 0.02) < 1
|
|
616
|
+
jitter([232, 220, 190], u, v, 6, 10)
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
# Zhang Zeduan, Along the River During the Qingming Festival
|
|
620
|
+
piece :qingming_scroll, "Along the River During the Qingming Festival", "Zhang Zeduan", "12th century", aspect: 2.5 do |u, v|
|
|
621
|
+
x = u * 2.5
|
|
622
|
+
silk = jitter([210, 185, 140], u, v, 8, 14)
|
|
623
|
+
# the rainbow bridge arching over the river
|
|
624
|
+
bc = 1.25
|
|
625
|
+
bw = 0.50
|
|
626
|
+
if (x - bc).abs < bw
|
|
627
|
+
t = (x - bc) / bw
|
|
628
|
+
top = 0.40 - 0.22 * (1 - t * t)
|
|
629
|
+
if v > top - 0.08 && v < top
|
|
630
|
+
next noise((x * 50).floor, (v * 40).floor) > 0.4 ? [70, 50, 40] : [180, 90, 60] if noise((x * 50).floor + 7, (v * 40).floor) > 0.35 # crowd
|
|
631
|
+
next silk
|
|
632
|
+
end
|
|
633
|
+
next [95, 65, 40] if v >= top && v < top + 0.06
|
|
634
|
+
next [80, 55, 35] if v >= top + 0.06 && v < top + 0.14 && Math.sin((x - bc) * 60 + v * 30).abs < 0.4
|
|
635
|
+
end
|
|
636
|
+
# river running across, slightly falling to the right
|
|
637
|
+
r1 = 0.44 + u * 0.10
|
|
638
|
+
r2 = 0.80 + u * 0.06
|
|
639
|
+
if v > r1 && v < r2
|
|
640
|
+
# boats with cabins, one lowering its mast under the bridge
|
|
641
|
+
hit = JP_HIT.([[0.45, 0.66, 0.24], [2.05, 0.74, 0.24], [1.62, 0.62, 0.10]].each do |bx, by, bl|
|
|
642
|
+
break [90, 60, 40] if ellipse(x, v, bx, by, bl, 0.05) < 1
|
|
643
|
+
break [150, 110, 70] if ellipse(x, v, bx, by - 0.07, bl * 0.6, 0.045) < 1
|
|
644
|
+
end)
|
|
645
|
+
next hit if hit
|
|
646
|
+
next [70, 50, 35] if JP_SEG.(x, v, 0.50, 0.60, 0.72, 0.18) < 0.014 # mast
|
|
647
|
+
next Math.sin(v * 60 + Math.sin(x * 8) * 2) > 0.9 ? [160, 165, 140] : [115, 125, 105]
|
|
648
|
+
end
|
|
649
|
+
# banks lined with tiled roofs and willows
|
|
650
|
+
cell = (x * 4).floor
|
|
651
|
+
cxf = (x * 4) % 1.0
|
|
652
|
+
far = v < r1
|
|
653
|
+
hv = far ? r1 - 0.17 : r2 + 0.04
|
|
654
|
+
if noise(cell, far ? 1 : 3) > 0.3 && cxf > 0.15 && cxf < 0.75 && v > hv && (v < r1 || !far)
|
|
655
|
+
next [80, 75, 70] if v < hv + 0.06
|
|
656
|
+
next [190, 165, 120] if v < hv + 0.15
|
|
657
|
+
end
|
|
658
|
+
if ellipse(cxf, v, 0.92, far ? hv - 0.02 : hv + 0.06, 0.28, 0.14) < 1 && noise(cell, far ? 2 : 4) > 0.35
|
|
659
|
+
next vnoise(u, v, 40) > 0.5 ? [100, 115, 70] : [70, 85, 55] # willows
|
|
660
|
+
end
|
|
661
|
+
next [60, 45, 35] if noise((x * 50).floor, (v * 30).floor) > 0.94 && v > 0.2 # people
|
|
662
|
+
silk
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
# Fan Kuan, Travelers among Mountains and Streams
|
|
666
|
+
piece :travelers_mountains_streams, "Travelers among Mountains and Streams", "Fan Kuan", "c. 1000", aspect: 0.5 do |u, v|
|
|
667
|
+
silk = jitter([180, 155, 115], u, v, 8, 12)
|
|
668
|
+
# the monumental central peak, filling the top
|
|
669
|
+
peak = [[0.03, 0.70], [0.05, 0.32], [0.12, 0.15], [0.26, 0.05], [0.50, 0.025], [0.72, 0.05], [0.87, 0.14],
|
|
670
|
+
[0.95, 0.32], [0.97, 0.70]]
|
|
671
|
+
if inside?(u, v, peak)
|
|
672
|
+
next [230, 220, 195] if (u - (0.78 + (v - 0.3) * 0.04)).abs < 0.012 && v > 0.26 && v < 0.56 # waterfall
|
|
673
|
+
next [45, 50, 35] if v < 0.09 + 0.02 * Math.sin(u * 20) && vnoise(u, v, 40) > 0.45 # scrub on the summit
|
|
674
|
+
tex = Math.sin(u * 140 + vnoise(u, v, 10) * 8) * 0.5 + 0.5
|
|
675
|
+
dot = noise((u * 80).floor, (v * 140).floor) > 0.65
|
|
676
|
+
base = mix([100, 80, 55], [45, 35, 25], tex * 0.6 + (dot ? 0.3 : 0) + (0.5 - (u - 0.5).abs) * 0.4)
|
|
677
|
+
next mix(base, [215, 200, 165], smooth(0.48, 0.66, v))
|
|
678
|
+
end
|
|
679
|
+
next mix(silk, [215, 200, 165], 0.6) if v > 0.60 && v < 0.70 # mist band
|
|
680
|
+
if v > 0.66
|
|
681
|
+
# a little caravan of travelers on the path at the lower right
|
|
682
|
+
next [35, 30, 25] if u > 0.62 && u < 0.84 && (v - 0.86).abs < 0.012 && noise((u * 60).floor, 9) > 0.35
|
|
683
|
+
# foreground boulders and groves
|
|
684
|
+
rock = ellipse(u, v, 0.30, 0.95, 0.34, 0.12) < 1 || ellipse(u, v, 0.92, 0.95, 0.20, 0.06) < 1
|
|
685
|
+
next mix([85, 68, 48], [40, 32, 22], vnoise(u, v, 20)) if rock
|
|
686
|
+
grove = ellipse(u, v, 0.78, 0.74, 0.20, 0.07) < 1 || ellipse(u, v, 0.18, 0.76, 0.18, 0.08) < 1
|
|
687
|
+
next(vnoise(u, v, 40) > 0.45 ? [40, 45, 30] : [75, 70, 45]) if grove
|
|
688
|
+
next mix(silk, [150, 125, 90], 0.4)
|
|
689
|
+
end
|
|
690
|
+
mix(silk, [215, 200, 165], 0.4) # pale sky
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
end
|