class PPM def initialize nx, ny, maxv = 255 @nx = nx @ny = ny @maxv = maxv @buf = "\0" * (nx * ny * 3) end attr_reader :nx, :ny, :maxv def saveto file if !file.respond_to?(:binmode) then open(file, 'w') { |fp| saveto(fp) } else file.binmode file.printf("P6\n%u %u %u\n", @nx, @ny, @maxv) file.write(@buf) file.flush end end def point x, y, r, g, b raise Errno::EDOM, "x = #{x} >= #{@nx}" if x >= @nx raise Errno::EDOM, "y = #{y} >= #{@ny}" if y >= @ny ptr = (x + @nx * y) * 3 @buf[ptr] = r ptr = ptr.succ @buf[ptr] = g ptr = ptr.succ @buf[ptr] = b end end class ViewPort def initialize nx, ny, ax, ay, az @wid = 255 @ppm = PPM.new(nx, ny, @wid) @ox = @oy = 0.0 @fx = @fy = 1.0 degree = Math::PI / 180.0 @cosax = Math::cos(degree * ax) @sinax = Math::sin(degree * ax) @cosay = Math::cos(degree * ay) @sinay = Math::sin(degree * ay) @cosaz = Math::cos(degree * az) @sinaz = Math::sin(degree * az) adjust end def saveto file @ppm.saveto(file) end def rotate x0, y0, z0 x1 = @cosay * x0 + @sinay * z0 y1 = y0 z1 = -@sinay * x0 + @cosay * z0 # x2 = x1 y2 = @cosax * y1 - @sinax * z1 z2 = @sinax * y1 + @cosax * z1 # x3 = @cosaz * x2 - @sinaz * y2 y3 = @sinaz * x2 + @cosaz * y2 z3 = z2 =begin p [:xx0, x0, y0, z0] p [:xx1, x1, y1, z1] p [:xx2, x2, y2, z2] p [:xx3, x3, y3, z3] =end # [x3, y3, z3] end def adjust xs = [] ys = [] [ [0, 0, 0], [@wid, 0, 0], [0, @wid, 0], [@wid, @wid, 0], [0, 0, @wid], [@wid, 0, @wid], [0, @wid, @wid], [@wid, @wid, @wid] ].each { |x, y, z| rx, ry, rz = rotate(x, y, z) xs.push rx ys.push ry } xmin, xmax, ymin, ymax = xs.min, xs.max, ys.min, ys.max # p [xs.min, xs.max, ys.min, ys.max] @fx = @ppm.nx / (xmax - xmin + 9) @fy = @ppm.ny / (ymax - ymin + 9) @fx = @fy = [@fx, @fy].min unless Math::log(@fx / @fy).abs > 1.0 @ox = (@ppm.nx - @fx * (xmin + xmax)) * 0.5 @oy = (@ppm.ny - @fy * (ymin + ymax)) * 0.5 # p [@fx, @fy, @ox, @oy] end def proj x, y, z x3, y3, z3 = rotate(x, y, z) [(@fx * x3 + @ox).floor.to_i, (@fy * y3 + @oy).floor.to_i] end def point r, g, b x, y = proj(r, g, b) # p [x, y, r, g, b] @ppm.point x, y, r.to_i, g.to_i, b.to_i end def box r, g, b x, y = proj(r, g, b) [-3, -2, -1, 0, 1, 2, 3].each {|dx| [-3, -2, -1, 0, 1, 2, 3].each {|dy| @ppm.point x+dx, y+dy, r, g, b } } @ppm.point x, y, 0, 0, 0 end def line r1, g1, b1, r2, g2, b2, skip = 1 n = [r2 - r1, g2 - g1, b2 - b1].map{|d| d.abs}.max n = (n + skip - 1) / skip 0.upto(n) { |i| frac = i.to_f / n r = r1 + (r2 - r1) * frac g = g1 + (g2 - g1) * frac b = b1 + (b2 - b1) * frac point(r, g, b) } end def cube [ [0, 0, 0, @wid, 0, 0], [0, 0, 0, 0, @wid, 0], [0, 0, 0, 0, 0, @wid], [@wid, 0, 0, @wid, @wid, 0], [@wid, 0, 0, @wid, 0, @wid], [0, @wid, 0, 0, @wid, @wid], [0, @wid, 0, @wid, @wid, 0], [0, 0, @wid, @wid, 0, @wid], [0, 0, @wid, 0, @wid, @wid], [@wid, @wid, 0, @wid, @wid, @wid], [0, @wid, @wid, @wid, @wid, @wid], [@wid, 0, @wid, @wid, @wid, @wid], [0, 0, 0, @wid, @wid, @wid], ].each{ |a| a = a.dup a.push 5 line(*a) } end end cf = { :nx => 300, :ny => 300, :ax => nil, :ay => nil, :az => nil, :if => nil, :of => nil } cfdfl = { :ax => -15, :ay => 134, :az => 180, :of => 'foo.png' } class << cf def setopt arg case arg when /^#/ then # do nothing when /^(n[xy])=/ then self[$1.to_sym] = $'.to_i when /^(a[xyz])=/ then self[$1.to_sym] = $'.to_f unless self[$1.to_sym] when /^(of)=/ then self[$1.to_sym] = $' when /^(?:if=)?([^=]+)/ then self[:if] = $1 self[:of] = self[:if].sub(/\.[^.]+$/, '.png') unless self[:of] else raise "bad option #{arg}" end end end for arg in ARGV cf.setopt arg end $stdin.reopen(cf[:if], 'r') if cf[:if] a = [] while line = $stdin.gets case line when /^\s*#:/ then line.strip.split(/\s+/).each { |arg| cf.setopt(arg) } when /^\s*#/ then true # do nothing else r = line.strip.split(/\t/).map{|s| s.to_i} a.push r end end for k in cfdfl.keys cf[k] = cfdfl[k] unless cf[k] end p cf if $DEBUG vp = ViewPort.new(cf[:nx], cf[:ny], cf[:ax], cf[:ay], cf[:az]) vp.cube 0.upto(a.size - 2) {|i| next if a[i].min < 0 or a[i + 1].min < 0 line = a[i] + a[i + 1] p line if $DEBUG vp.line(*line) } a.each {|r| next if r.min < 0 vp.box(*r) } vp.saveto("|convert ppm:- #{cf[:of]}")