Death Star: Difference between revisions

no edit summary
m (syntax highlighting fixup automation)
No edit summary
Line 2,948:
wend
</syntaxhighlight>
=={{header|Zig}}==
{{trans|C}}
Primitive ray tracing. Writes a PGM to stdout.
<syntaxhighlight lang="zig">const std = @import("std");
const Allocator = std.mem.Allocator;
 
pub fn main() !void {
// buffer stdout --------------------------------------
const stdout_file = std.io.getStdOut().writer();
var bw = std.io.bufferedWriter(stdout_file);
const stdout = bw.writer();
 
// allocator ------------------------------------------
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const ok = gpa.deinit();
std.debug.assert(ok == .ok);
}
const allocator = gpa.allocator();
 
// deathstar ------------------------------------------
var dstar = try DeathStar(f32).init(allocator);
defer dstar.deinit();
 
// print deathstar PGM to stdout ----------------------
const comments = [_][]const u8{
"Rosetta Code",
"DeathStar",
"https://rosettacode.org/wiki/Death_Star",
};
try dstar.print(stdout, comments[0..]);
 
// ----------------------------------------------------
try bw.flush();
}
 
fn Vector(comptime T: type) type {
return struct {
const Self = @This();
x: T,
y: T,
z: T,
 
pub fn init(x: T, y: T, z: T) Self {
return Self{ .x = x, .y = y, .z = z };
}
pub fn zero() Self {
return Self{ .x = 0.0, .y = 0.0, .z = 0.0 };
}
fn dot(a: *const Self, b: *const Self) T {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
fn length(self: *const Self) T {
return std.math.sqrt(self.dot(self));
}
pub fn normalize(self: *Self) void {
const inv_length = 1 / self.length();
self.*.x *= inv_length;
self.*.y *= inv_length;
self.*.z *= inv_length;
}
};
}
 
fn SphereHit(comptime T: type) type {
return struct { z1: T, z2: T };
}
 
fn Sphere(comptime T: type) type {
return struct {
const Self = @This();
cx: T,
cy: T,
cz: T,
r: T,
 
pub fn init(cx: T, cy: T, cz: T, r: T) Self {
return Self{ .cx = cx, .cy = cy, .cz = cz, .r = r };
}
/// Check if a ray (x,y, -inf)->(x, y, inf) hits a sphere.
/// If so, return the intersecting z values. z1 is closer to the eye.
pub fn hit(self: *const Self, xx: T, yy: T) ?SphereHit(T) {
const x = xx - self.cx;
const y = yy - self.cy;
var zsq = self.r * self.r - x * x - y * y;
if (zsq >= 0) {
var zsqrt = std.math.sqrt(zsq);
return .{ .z1 = self.cz - zsqrt, .z2 = self.cz + zsqrt };
}
return null;
}
};
}
 
fn DeathStar(comptime T: type) type {
return struct {
const Self = @This();
allocator: Allocator,
w: usize,
h: usize,
img: ImageData(),
 
const Hit = enum { background, neg, pos };
 
pub fn init(allocator: Allocator) !Self {
var dir = Vector(T).init(20, -40, 10);
dir.normalize();
// positive sphere and negative sphere
const pos = Sphere(T).init(180, 240, 220, 120);
const neg = Sphere(T).init(60, 150, 100, 100);
 
const k: T = 1.5;
const amb: T = 0.2;
 
var w = @floatToInt(usize, pos.r * 4);
var h = @floatToInt(usize, pos.r * 3);
var img = try ImageData().init(allocator, "deathStar", w, h);
 
var vec = Vector(T).zero();
 
const start_y = @floatToInt(usize, pos.cy - pos.r);
const end_y = @floatToInt(usize, pos.cy + pos.r);
const start_x = @floatToInt(usize, pos.cx - pos.r);
const end_x = @floatToInt(usize, pos.cx + pos.r);
 
for (start_y..end_y + 1) |j| {
for (start_x..end_x + 1) |i| {
const x = @intToFloat(T, i);
const y = @intToFloat(T, j);
 
const result_pos = pos.hit(x, y);
// ray lands in blank space, show bg
if (result_pos == null)
continue;
 
const zb1 = result_pos.?.z1;
const zb2 = result_pos.?.z2;
 
const result_neg = neg.hit(x, y);
 
switch (calcHit(result_neg, zb1, zb2)) {
.background => continue,
.neg => {
vec.x = neg.cx - x;
vec.y = neg.cy - y;
vec.z = neg.cz - result_neg.?.z2; // zs2
},
.pos => {
vec.x = x - pos.cx;
vec.y = y - pos.cy;
vec.z = zb1 - pos.cz;
},
}
vec.normalize();
var s = dir.dot(&vec);
if (s < 0) s = 0;
const lum = 255 * (std.math.pow(T, s, k) + amb) / (1 + amb);
const lumi = @floatToInt(u8, std.math.clamp(lum, 0, 255));
img.pset(i, j, Gray{ .w = lumi });
}
}
return Self{ .allocator = allocator, .w = w, .h = h, .img = img };
}
pub fn deinit(self: *Self) void {
self.img.deinit();
}
pub fn print(self: *Self, writer: anytype, optional_comments: ?[]const []const u8) !void {
try self.img.print(writer, optional_comments);
}
/// Ray has hit the positive sphere.
/// How does it intersect the negative sphere ?
fn calcHit(neg_hit: ?SphereHit(T), zb1: T, zb2: T) Hit {
if (neg_hit) |result| {
const zs1 = result.z1;
const zs2 = result.z2;
if (zs1 > zb1) {
// ray hits both, but pos front surface is closer
return Hit.pos;
} else if (zs2 > zb2) {
// pos sphere surface is inside neg sphere, show bg
return Hit.background;
} else if (zs2 > zb1) {
// back surface on neg sphere is inside pos sphere,
// the only place where neg sphere surface will be shown
return Hit.neg;
} else {
return Hit.pos;
}
} else {
// ray hits pos sphere but not neg, draw pos sphere surface
return Hit.pos;
}
}
};
}
 
const Gray = struct {
w: u8,
const black = Gray{ .w = 0 };
};
 
fn ImageData() type {
return struct {
const Self = @This();
allocator: Allocator,
name: []const u8,
w: usize,
h: usize,
image: []Gray,
 
pub fn init(allocator: Allocator, name: []const u8, w: usize, h: usize) !Self {
const image = try allocator.alloc(Gray, h * w);
// black background fill
for (image) |*pixel| pixel.* = Gray.black;
return Self{ .allocator = allocator, .image = image, .name = name, .w = w, .h = h };
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.image);
}
pub fn pset(self: *Self, x: usize, y: usize, gray: Gray) void {
self.image[x * self.w + y] = gray;
}
/// Write PGM P2 ASCII to 'writer'
pub fn print(self: *const Self, writer: anytype, optional_comments: ?[]const []const u8) !void {
_ = try writer.print("P2\n", .{});
 
if (optional_comments) |lines| {
for (lines) |line|
_ = try writer.print("# {s}\n", .{line});
}
 
_ = try writer.print("{d} {d}\n{d}\n", .{ self.w, self.h, 255 });
 
for (self.image, 0..) |pixel, i| {
const sep = if (i % self.w == self.w - 1) "\n" else " ";
_ = try writer.print("{d}{s}", .{ pixel.w, sep });
}
}
};
}</syntaxhighlight>
59

edits