-- This program automates the process of creating simple fly-by animations.
-- The rasters are saved in the `frames' directory, and if ffmpeg is
-- installed, the frames will also be combined into a video file. This script
--- must be called from the same directory where the rasterize program is.
-
--- Set the width and height of the viewport.
-local size = {w = 640, h = 480}
+-- must be called from the same directory where the raytrace program is.
-- Set the number of frames to be rendered for the animation.
local frames = 360
-- Define the code to calculate where the camera is, in world coordinates.
local eye = function(frame)
-- just rotate around the center of the scene on the XZ plane
- local center = vec_new(0, 1, 0)
+ local center = vec_new(0, -0.5, 0)
local distance = 4
local start = math.pi
local t = start + 2 * math.pi * frame / frames
-- Define the code to calculate where the focal point of the scene is.
local look = function(frame)
- -- keep the camera focused on the buddha
- return vec_new(2, 1, 2)
+ -- keep the camera focused at the center
+ return vec_new(0, 0, 0)
end
-- Define the actual objects of the scene that will be rendered, in the
--- extended u3d format.
+-- extended urt format.
local scene = [[
-L 0 1000000 0 1 1 1 1 1 1
-L 0 0 1000000 1 1 1 1 1 1
-g triangle.raw
-c 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1
-t 2 0 -2
-s 1 1 1
-g dragon.raw
-M 0.3 0.3 0.3 5
-c 0.7 0.3 0.2 0.8 0.2 0.1 0.9 0.2 0.2
-t -2 -1 -2
-s 2 2 2
-g budda.raw
-c 0.1 0.2 0.7 0.1 0.3 0.9 0.2 0.1 0.8
-t 2 -1.5 2
-s 10 10 10
-g bunny.raw
-M 0.2 0.2 0.2 1
-c 0.9 0.8 0.9 0.8 0.7 0.9 0.9 0.8 0.7
-t -2 -1 2
-s 10 10 10
-g teapot2.raw
-M 1 1 1 128
-c 0 1 0 0 1 0 0 1 0
-t 0 -1 0
-s 0.6 0.6 0.6
+l 0.0 10.0 1.0 0.8 0.7 0.8
+p 0.0 -5.0 0.0 0.0 1.0 0.0 0.4 0.8 0.4
+s 0.0 0.0 0.0 1.25 1.0 0.0 0.0
+s -1.0 -1.0 0.0 1.0 0.0 1.0 0.0
+s 1.0 -1.0 0.0 1.0 0.0 0.0 1.0
+t -1.5 0.0 1.5 -1.0 1.0 0.0 -0.5 0.0 1.5 0.8 0.9 0.2
+t 1.5 0.0 1.5 1.0 1.0 0.0 0.5 0.0 1.5 0.2 0.9 0.8
]]
+-- Set the number of samples for supersampling. If this is set to a value
+-- of two or greater, each frame will be rendered larger by the factor
+-- specified. ImageMagick is used to scale the frames back down to size;
+-- if ImageMagick is not installed, this script will exit with error 1.
+local supersample = 2
+
+-- Set the width and height of the viewport.
+local size = {w = 800, h = 600}
+
-- Set the number of concurrent renderings (for multi-core machines).
local jobs = 6
+-- Set the options to be passed directly to ffmpeg.
+local ffmpeg_opts = "-b 1024k"
-- end of configuration
---------------------------------------------------------------------------
-local fmt = string.format
-local write = function(...) io.write(fmt(...)) end
-
function vec_new(x, y, z)
return {x = x, y = y, z = z}
end
return vec_new(v.x * s, v.y * s, v.z * s)
end
+local fmt = string.format
+local write = function(...) io.write(fmt(...)) io.flush() end
+local run = function(...) return os.execute(fmt(...)) end
+
function render(i)
while i do
+ local w, h = size.w, size.h
+ if 1 < supersample then
+ w = w * supersample
+ h = h * supersample
+ end
local filename = fmt("frames/anim%04d.bmp", i)
- local command = fmt("./rasterize -o %s >/dev/null", filename)
+ local command = fmt("./raytrace -o %s >/dev/null", filename)
local out = io.popen(command, "w")
local e = eye(i)
local l = look(i)
- write("\27[80D\27[2Kframe\t %4d / %d", i + 1, frames)
+ write("\27[80D\27[2Kframe\t %4d / %d", i, frames)
out:write(fmt([[
-U3
+U5
%d %d
%f %f %f
%f %f %f
0 1 0
-1.57 %f 0.1 1000
+1.57 %f
+0.4 0.4 0.4
%s
X
-]], size.w, size.h, e.x, e.y, e.z, l.x, l.y, l.z, size.w/size.h, scene))
+]], w, h, e.x, e.y, e.z, l.x, l.y, l.z, size.w/size.h, scene))
i = coroutine.yield()
out:close()
+ if 1 < supersample and
+ run("convert %s -scale %dx%d %s", filename, size.w, size.h, filename) ~= 0 then
+ print()
+ os.exit(1)
+ end
+ end
+end
+
+function renderings()
+ local t = {}
+ for i = 1,jobs do
+ table.insert(t, coroutine.wrap(render))
+ end
+ local iterations = frames + jobs
+ local frame = 0
+ return function()
+ frame = frame + 1
+ if frame <= iterations then
+ local i = frame
+ if frames < i then i = null end
+ return t[1 + frame % jobs], i
+ end
end
end
print("Animating scene...")
-local threads = {}
-for i = 1,jobs do
- table.insert(threads, coroutine.wrap(render))
-end
+run("rm -rf frames && mkdir frames >/dev/null 2>&1")
+for render,frame in renderings() do render(frame) end
-os.execute("rm -rf frames && mkdir frames >/dev/null 2>&1")
-for i = 0,(frames-1) do
- threads[1 + (i % jobs)](i)
-end
-for _,thread in ipairs(threads) do thread(null) end
print()
-if os.execute("ffmpeg -i frames/anim%04d.bmp -b 1024k -y -an scene.avi") == 0 then
+if run("ffmpeg -i frames/anim%%04d.bmp %s -y -an scene.avi", ffmpeg_opts) == 0 then
print("Animation written to scene.avi.")
+else
+ print("The animation could not be created. Is ffmpeg installed?")
+ os.exit(2)
end