1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
const std = @import("std");
const Image = @import("zigimg").Image;
const Color = @import("vec.zig").Color;
const Vec3 = @import("vec.zig").Vec3;
const rgb = @import("vec.zig").rgb;
const Random = @import("rand.zig").Random;
const Perlin = @import("perlin.zig").Perlin;
const TextureTag = enum {
solid,
checker,
noise,
image,
};
pub const Texture = union(TextureTag) {
solid: SolidTexture,
checker: CheckerTexture,
noise: NoiseTexture,
image: ImageTexture,
pub fn makeSolid(color: Color) Texture {
return .{ .solid = .{ .color = color } };
}
pub fn makeChecker(allocator: std.mem.Allocator, odd: Color, even: Color) !Texture {
return .{ .checker = try CheckerTexture.init(
allocator,
Texture.makeSolid(odd),
Texture.makeSolid(even),
) };
}
pub fn makeNoise(allocator: std.mem.Allocator, scale: f64, rng: Random) !Texture {
return .{ .noise = try NoiseTexture.init(allocator, rng, scale) };
}
pub fn makeImage(allocator: std.mem.Allocator, file_path: []const u8) !Texture {
return .{ .image = try ImageTexture.init(allocator, file_path) };
}
pub fn value(tx: Texture, u: f64, v: f64, p: Vec3) Color {
return switch (tx) {
TextureTag.solid => |solid_tx| solid_tx.value(u, v, p),
TextureTag.checker => |checker_tx| checker_tx.value(u, v, p),
TextureTag.noise => |noise_tx| noise_tx.value(u, v, p),
TextureTag.image => |image_tx| image_tx.value(u, v, p),
};
}
};
pub const SolidTexture = struct {
color: Color,
fn value(tx: SolidTexture, u: f64, v: f64, p: Vec3) Color {
_ = u;
_ = v;
_ = p;
return tx.color;
}
};
pub const CheckerTexture = struct {
allocator: std.mem.Allocator,
odd: *const Texture,
even: *const Texture,
fn init(allocator: std.mem.Allocator, odd: Texture, even: Texture) !CheckerTexture {
var odd_ = try allocator.create(Texture);
var even_ = try allocator.create(Texture);
odd_.* = odd;
even_.* = even;
return .{
.allocator = allocator,
.odd = odd_,
.even = even_,
};
}
fn deinit(tx: CheckerTexture) void {
tx.allocator.destroy(tx.even);
tx.allocator.destroy(tx.odd);
}
fn value(tx: CheckerTexture, u: f64, v: f64, p: Vec3) Color {
const sines = @sin(10 * p.x) * @sin(10 * p.y) * @sin(10 * p.z);
return if (sines < 0) tx.odd.value(u, v, p) else tx.even.value(u, v, p);
}
};
pub const NoiseTexture = struct {
perlin: Perlin,
scale: f64,
fn init(allocator: std.mem.Allocator, rng: Random, scale: f64) !NoiseTexture {
return .{
.perlin = try Perlin.init(allocator, rng),
.scale = scale,
};
}
fn deinit(tx: NoiseTexture) void {
tx.perlin.deinit();
}
fn value(tx: NoiseTexture, u: f64, v: f64, p: Vec3) Color {
_ = u;
_ = v;
return rgb(1, 1, 1).mul(0.5 * (1.0 + @sin(tx.scale * p.z + 10.0 * tx.perlin.turb(p, 7))));
}
};
pub const ImageTexture = struct {
image: Image,
fn init(allocator: std.mem.Allocator, file_path: []const u8) !ImageTexture {
const image = try Image.fromFilePath(allocator, file_path);
return .{
.image = image,
};
}
fn deinit(tx: ImageTexture) void {
tx.image.deinit();
}
fn value(tx: ImageTexture, u: f64, v: f64, p: Vec3) Color {
_ = p;
// Clamp input texture coordinates to [0, 1] x [1, 0]
const u_ = std.math.clamp(u, 0.0, 1.0);
const v_ = 1.0 - std.math.clamp(v, 0.0, 1.0); // Flip v to image coordinates
const i = @as(usize, @intFromFloat(u_ * @as(f64, @floatFromInt(tx.image.width))));
const j = @as(usize, @intFromFloat(v_ * @as(f64, @floatFromInt(tx.image.height))));
// Clamp integer mapping, since actual coordinates should be less than 1.0
const i_ = @min(i, tx.image.width - 1);
const j_ = @min(j, tx.image.width - 1);
const color_scale = 1.0 / 255.0;
const pixels = tx.image.pixels.asBytes();
const offset = j_ * tx.image.width * 4 + i_ * 4;
const r = @as(f64, @floatFromInt(pixels[offset + 0]));
const g = @as(f64, @floatFromInt(pixels[offset + 1]));
const b = @as(f64, @floatFromInt(pixels[offset + 2]));
const a = @as(f64, @floatFromInt(pixels[offset + 3]));
if (a == 0) {
// Ocean
return rgb(0, 0, 1.0);
} else {
return rgb(color_scale * r, color_scale * g, color_scale * b);
}
}
};
|