fix screenshot
[maximus:mandulia.git] / config / main.lua
1 --[[--
2
3 Mandulia -- Mandelbrot/Julia explorer
4 Copyright (C) 2010  Claude Heiland-Allen <claudiusmaximus@goto10.org>
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19 --]]--
20
21 require("defaults")
22 require("transition")
23 require("distance")
24
25 do
26
27   -- frame count without interaction
28   local mode = "interact"
29   local unattended = 0
30   local attentionSpan = 25 * 60
31
32   -- frame counts for recording
33   local frames = 0
34   local frameLimit = 0
35
36   -- magic numbers
37   math.randomseed(os.time())
38   local phi  = (math.sqrt(5) + 1) / 2
39   local phi1 = (math.sqrt(5) - 1) / 2
40
41   -- animation parameters
42   local defaultSpeed = 0.125
43   local defaultWeight = 8
44   local speed = defaultSpeed
45   local weight = defaultWeight
46
47   -- view bounds
48   local minimum = { x = -4, y = -4, z =  0 }
49   local maximum = { x =  4, y =  4, z = 42 }
50   local function clamp(x, mi, ma)
51     return math.min(math.max(x, mi), ma)
52   end
53
54   -- attract mode for interesting behaviour when unattended
55   local attract = { }
56   do
57
58     local function walk(x, mi, ma, d)
59       local p = (x - mi) / (ma - mi)
60       local s = math.random()
61       return x + d * (s - p)
62     end
63
64     -- default view
65     local function zero() return { x = 0, y = 0, z = minimum.z } end
66
67     -- animation variables
68     local source = zero()
69     local target = zero()
70     local t = 0
71     local s = 0
72     local f = function(t) return zero() end
73     local dz = minimum.z
74
75     local history = { }
76     for i = 1,48 do
77       history[i] = { x = 0, y = 0 }
78     end
79     local historyptr = 1
80
81     attract.enter = function()
82       mode = "attract"
83       target.x = mandulia.view.x
84       target.y = mandulia.view.y
85       target.z = mandulia.view.z
86       t = 0
87       s = 0
88     end
89
90     attract.render = function()
91       t = t + speed * (phi1 ^ dz)
92       if t > s then -- we reached the target, new target needed
93         t = t - s
94         source.x = target.x
95         source.y = target.y
96         source.z = target.z
97         local interesting
98         local x, y, e, v, d2, dx, dy
99         local r = 25 * phi1 ^ dz
100         e = exteriordistance(source.x, source.y, 1000, 1000)
101         interesting = e ~= nil and e < 0.00001
102         if interesting then
103           interesting = false
104           while not interesting do
105             x = source.x + r * (2 * math.random() - 1)
106             y = source.y + r * (2 * math.random() - 1)
107             e = exteriordistance(x, y, 1000, 1000)
108             if e ~= nil and e < 0.00001 then
109               v = false
110               for i,p in ipairs(history) do
111                 dx = x - p.x
112                 dy = y - p.y
113                 d2 = dx * dx + dy * dy
114                 v = v or d2 < 0.01
115               end
116               if v then r = r * 1.1 end
117               interesting = not v
118             end
119           end
120         else
121           interesting = false
122           while not interesting do
123             x = math.random() * (maximum.x - minimum.x) + minimum.x
124             y = math.random() * (maximum.y - minimum.y) + minimum.y
125             e = exteriordistance(x, y, 1000, 1000)
126             interesting = e ~= nil and e < 0.00001
127           end
128         end
129         target.x = clamp(x, minimum.x, maximum.x)
130         target.y = clamp(y, minimum.y, maximum.y)
131         target.z = walk(source.z, minimum.z, maximum.z, 3)
132         history[historyptr].x = target.x
133         history[historyptr].y = target.y
134         historyptr = historyptr + 1
135         if historyptr > #history then historyptr = 1 end
136         s, f = transition(source, target, weight, phi)
137       end
138       mandulia.view = f(t)
139       mandulia.view.z = clamp(mandulia.view.z, minimum.z, maximum.z)
140       dz = mandulia.view.z
141     end -- render
142
143   end -- attract
144
145   -- interact mode for engagement and control
146   local interact = { }
147   do
148
149     local delta = { x = 0, y = 0, z = 0 }
150
151     interact.enter = function()
152       mode = "interact"
153       unattended = 0
154     end
155
156     interact.render = function()
157       local x = mandulia.view.x + speed * delta.x * phi1 ^ mandulia.view.z
158       local y = mandulia.view.y + speed * delta.y * phi1 ^ mandulia.view.z
159       local z = mandulia.view.z + speed * delta.z
160       mandulia.view.x = clamp(x, minimum.x, maximum.x)
161       mandulia.view.y = clamp(y, minimum.y, maximum.y)
162       mandulia.view.z = clamp(z, minimum.z, maximum.z)
163     end
164
165     local keys =
166       { Right    = function() delta.x = delta.x + 1 end
167       , Left     = function() delta.x = delta.x - 1 end
168       , Up       = function() delta.y = delta.y + 1 end
169       , Down     = function() delta.y = delta.y - 1 end
170       , PageUp   = function() delta.z = delta.z + 1 end
171       , PageDown = function() delta.z = delta.z - 1 end
172       , End      = function() delta         = { x = 0, y = 0, z = 0 } end
173       , Home     = function() delta         = { x = 0, y = 0, z = 0 }
174                               mandulia.view = { x = 0, y = 0, z = 0 } end
175       , ["["]    = function() speed = speed * 0.95 end
176       , ["]"]    = function() speed = speed / 0.95 end
177       , ["#"]    = function() speed = defaultSpeed end
178       , ["{"]    = function() weight = weight * 0.95 end
179       , ["}"]    = function() weight = weight / 0.95 end
180       , ["~"]    = function() weight = defaultWeight end
181       }
182
183     interact.keyboard = function(key)
184       if type(keys[key]) == "function" then keys[key]() end
185     end
186
187   end -- interact
188
189   local screenshot = { }
190   local record = { }
191   do
192
193     local recording = false
194
195     screenshot.enter = function()
196       frames = 0
197       frameLimit = 1
198       frameStep = 1
199       recording = true
200     end
201
202     record.enter = function()
203       frames = 0
204       frameLimit = 25 * 60 * 10
205       frameStep = 1
206       recording = true
207     end
208
209     record.render = function()
210       if recording then
211         mandulia.record = (frames % frameStep) == 0 and frames < frameLimit
212         frames = frames + 1
213       end
214     end
215
216   end -- screenshot/record
217
218
219   function mandulia.render()
220     if mode == "interact" then
221       interact.render()
222       unattended = unattended + 1
223       if unattended >= attentionSpan then attract.enter() end
224     elseif mode == "attract" then
225       attract.render()
226     end
227     record.render()
228   end
229
230   function mandulia.keyboard(key)
231     if     key == "Escape" then mandulia.quit()
232     elseif key == "F11"    then mandulia.fullscreen = not mandulia.fullscreen
233     elseif key == "a"      then attract.enter()
234     elseif key == "r"      then record.enter()
235     elseif key == "s"      then screenshot.enter()
236     else
237       interact.enter()
238       interact.keyboard(key)
239     end
240   end
241
242 end
243
244 pcall(function() require("config") end)