rtiow: Many changes
Add debugging hit DebugHit. Move some color helpers from mandelbrot to colors mod. Implement Texture trait for [f32;3]
This commit is contained in:
parent
deb46acb5a
commit
e19ec20c7b
129
rtiow/Cargo.lock
generated
129
rtiow/Cargo.lock
generated
@ -450,6 +450,12 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
@ -467,6 +473,37 @@ dependencies = [
|
||||
"iovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c77df041dc383319cc661b428b6961a005db4d6808d5e12536931b1ca9556055"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-platform"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo_metadata"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver 1.0.16",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
@ -1028,6 +1065,15 @@ dependencies = [
|
||||
"backtrace 0.3.67",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-chain"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
|
||||
dependencies = [
|
||||
"version_check 0.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.8"
|
||||
@ -1195,6 +1241,12 @@ version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.1.26"
|
||||
@ -2015,6 +2067,33 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f76678828272f177ac33b7e2ac2e3e73cc6c1cd1e3e387928aa69562fa51367"
|
||||
dependencies = [
|
||||
"peg-macros",
|
||||
"peg-runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg-macros"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "636d60acf97633e48d266d7415a9355d4389cea327a193f87df395d88cd2b14d"
|
||||
dependencies = [
|
||||
"peg-runtime",
|
||||
"proc-macro2 1.0.50",
|
||||
"quote 1.0.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg-runtime"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555b1514d2d99d78150d3c799d4c357a3e2c2a8062cd108e93a06d9057629c5"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "1.0.1"
|
||||
@ -2094,6 +2173,18 @@ dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ply-rs"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbadf9cb4a79d516de4c64806fe64ffbd8161d1ac685d000be789fb628b88963"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"linked-hash-map",
|
||||
"peg",
|
||||
"skeptic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.15.3"
|
||||
@ -2198,6 +2289,17 @@ dependencies = [
|
||||
"unarray",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"memchr",
|
||||
"unicase 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
@ -2555,7 +2657,7 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver",
|
||||
"semver 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2636,6 +2738,15 @@ dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
@ -2752,6 +2863,21 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
|
||||
|
||||
[[package]]
|
||||
name = "skeptic"
|
||||
version = "0.13.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8"
|
||||
dependencies = [
|
||||
"bytecount",
|
||||
"cargo_metadata",
|
||||
"error-chain 0.12.4",
|
||||
"glob",
|
||||
"pulldown-cmark",
|
||||
"tempfile",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.7"
|
||||
@ -2807,6 +2933,7 @@ dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"ply-rs",
|
||||
"structopt 0.3.26",
|
||||
"thiserror",
|
||||
"vec3",
|
||||
|
||||
46
rtiow/renderer/src/colors.rs
Normal file
46
rtiow/renderer/src/colors.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use rand::{self, Rng};
|
||||
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
// HSV values in [0..1]
|
||||
// returns [r, g, b] values from 0 to 255
|
||||
//From https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
|
||||
pub fn hsv_to_rgb(h: f32, s: f32, v: f32) -> Vec3 {
|
||||
let h_i = (h * 6.) as i32;
|
||||
let f = h * 6. - h_i as f32;
|
||||
let p = v * (1. - s);
|
||||
let q = v * (1. - f * s);
|
||||
let t = v * (1. - (1. - f) * s);
|
||||
match h_i {
|
||||
0 => Vec3::new(v, t, p),
|
||||
1 => Vec3::new(q, v, p),
|
||||
2 => Vec3::new(p, v, t),
|
||||
3 => Vec3::new(p, q, v),
|
||||
4 => Vec3::new(t, p, v),
|
||||
5 => Vec3::new(v, p, q),
|
||||
_ => panic!("Unknown H value {}", h_i),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_rainbow(num: usize) -> Vec<Vec3> {
|
||||
(0..num)
|
||||
.map(|n| {
|
||||
let h = n as f32 / num as f32;
|
||||
hsv_to_rgb(h, 0.99, 0.99)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
pub fn generate_palette(num: usize) -> Vec<Vec3> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut random = || rng.gen();
|
||||
// use golden ratio
|
||||
let golden_ratio_conjugate = 0.618_034;
|
||||
let mut h = random();
|
||||
(0..num)
|
||||
.map(|_| {
|
||||
h += golden_ratio_conjugate;
|
||||
h %= 1.0;
|
||||
hsv_to_rgb(h, 0.99, 0.99)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
46
rtiow/renderer/src/debug_hit.rs
Normal file
46
rtiow/renderer/src/debug_hit.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use crate::{
|
||||
aabb::AABB,
|
||||
hitable::{Hit, HitRecord},
|
||||
material::Lambertian,
|
||||
ray::Ray,
|
||||
texture::{self, ConstantTexture},
|
||||
vec3::Vec3,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebugHit<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
hitable: H,
|
||||
material: Lambertian<[f32; 3]>,
|
||||
}
|
||||
|
||||
impl<H> DebugHit<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
pub fn new(hitable: H) -> DebugHit<H>
|
||||
where {
|
||||
DebugHit {
|
||||
hitable,
|
||||
material: Lambertian::new([0.2, 0.2, 0.2]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> Hit for DebugHit<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
if let Some(hit) = self.hitable.hit(r, t_min, t_max) {
|
||||
return Some(HitRecord { t: hit.t, ..hit });
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
|
||||
self.hitable.bounding_box(t_min, t_max)
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,10 @@ pub mod aabb;
|
||||
pub mod bvh;
|
||||
pub mod bvh_triangles;
|
||||
pub mod camera;
|
||||
pub mod colors;
|
||||
pub mod constant_medium;
|
||||
pub mod cuboid;
|
||||
pub mod debug_hit;
|
||||
pub mod flip_normals;
|
||||
pub mod glowybox;
|
||||
pub mod hitable;
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
use std::io::{BufReader, Cursor};
|
||||
use std::{
|
||||
f32::consts::PI,
|
||||
io::{BufReader, Cursor},
|
||||
iter::Inspect,
|
||||
};
|
||||
|
||||
use stl::STL;
|
||||
|
||||
use crate::{
|
||||
bvh_triangles::BVHTriangles,
|
||||
camera::Camera,
|
||||
colors::generate_rainbow,
|
||||
hitable::Hit,
|
||||
hitable_list::HitableList,
|
||||
kdtree::KDTree,
|
||||
material::{Dielectric, Lambertian},
|
||||
material::{Dielectric, Lambertian, Metal},
|
||||
renderer::{Opt, Scene},
|
||||
rotate::RotateY,
|
||||
scale::Scale,
|
||||
sphere::Sphere,
|
||||
texture::{ConstantTexture, EnvMap},
|
||||
@ -18,8 +24,8 @@ use crate::{
|
||||
};
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(0., 40., -100.);
|
||||
let lookat = Vec3::new(0., 10., 0.);
|
||||
let lookfrom = Vec3::new(0., 80., 80.);
|
||||
let lookat = Vec3::new(0., 0., 0.);
|
||||
let dist_to_focus = 10.0;
|
||||
let aperture = 0.0;
|
||||
let time_min = 0.;
|
||||
@ -35,6 +41,10 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
//let dragon_material = Dielectric::new(1.5);
|
||||
let dragon_material = Metal::new(Vec3::new(0.6, 0.6, 0.6), 0.0);
|
||||
//let dragon_material = Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 1.0, 0.2)));
|
||||
|
||||
let ground_color = if opt.use_accel {
|
||||
ConstantTexture::new(Vec3::new(1.0, 0.4, 0.4))
|
||||
} else {
|
||||
@ -42,46 +52,51 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
};
|
||||
|
||||
let stl_cube = STL::parse(
|
||||
BufReader::new(Cursor::new(include_bytes!(
|
||||
"../../stls/stanford_dragon-lowres.stl" //"../../stls/stanford_dragon.stl"
|
||||
))),
|
||||
BufReader::new(Cursor::new(include_bytes!("../../stls/dragon.stl"))),
|
||||
//BufReader::new(Cursor::new(include_bytes!("../../stls/cube.stl"))),
|
||||
false,
|
||||
)
|
||||
.expect("failed to parse cube");
|
||||
let _light_size = 50.;
|
||||
let _light_height = 200.;
|
||||
let objects: Vec<Box<dyn Hit>> = vec![
|
||||
// Light from above - white
|
||||
let sphere_radius = 5.;
|
||||
let circle_radius = 40.;
|
||||
let num_spheres = 16;
|
||||
let palette = generate_rainbow(num_spheres);
|
||||
let spheres: Vec<Box<dyn Hit>> = (0..num_spheres)
|
||||
.map(|i| (i, i as f32, num_spheres as f32))
|
||||
.map(|(idx, idx_f, n)| (idx, idx_f * 2. * PI / n))
|
||||
.map(|(idx, rad)| -> Box<dyn Hit> {
|
||||
let x = circle_radius * rad.cos();
|
||||
let y = 4. * sphere_radius;
|
||||
let z = circle_radius * rad.sin();
|
||||
let c = palette[idx];
|
||||
Box::new(Sphere::new(
|
||||
[x, y, z],
|
||||
sphere_radius,
|
||||
Lambertian::new(ConstantTexture::new(c)),
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
let mut objects: Vec<Box<dyn Hit>> = vec![
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 0.1, -0.5),
|
||||
0.1,
|
||||
Lambertian::new([0., 1., 1.]),
|
||||
)),
|
||||
// Earth sized sphere
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., -10000., 0.),
|
||||
10000.,
|
||||
Lambertian::new(ground_color),
|
||||
)),
|
||||
// Blue sphere
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 20., 40.),
|
||||
20.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.2, 0.2, 1.))),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(40., 20., 40.),
|
||||
20.,
|
||||
//Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.2, 1.0, 0.2))),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(-40., 20., 40.),
|
||||
20.,
|
||||
//Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
|
||||
)),
|
||||
//Box::new(Sphere::new( Vec3::new(0., -10000., 0.), 10000., Lambertian::new(ground_color),)),
|
||||
// STL Mesh
|
||||
Box::new(Translate::new(
|
||||
Scale::new(BVHTriangles::new(&stl_cube, Dielectric::new(1.5)), 250.),
|
||||
[0., -10., 0.],
|
||||
)),
|
||||
Box::new(crate::debug_hit::DebugHit::new(RotateY::new(
|
||||
Translate::new(
|
||||
Scale::new(BVHTriangles::new(&stl_cube, dragon_material), 250.),
|
||||
[0., -10., 0.],
|
||||
),
|
||||
180.,
|
||||
))),
|
||||
];
|
||||
objects.extend(spheres);
|
||||
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
Box::new(KDTree::new(objects, time_min, time_max))
|
||||
} else {
|
||||
|
||||
@ -16,6 +16,7 @@ use crate::{
|
||||
renderer::{Opt, Scene},
|
||||
sphere::Sphere,
|
||||
texture::{ConstantTexture, EnvMap},
|
||||
translate::Translate,
|
||||
vec3::Vec3,
|
||||
};
|
||||
|
||||
@ -47,7 +48,7 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
let metal = Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2);
|
||||
let red = Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2)));
|
||||
|
||||
let box_material = glass;
|
||||
//let box_material = glass;
|
||||
let _ = glass;
|
||||
let _ = metal;
|
||||
let _ = red;
|
||||
@ -65,6 +66,11 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
10000.,
|
||||
Lambertian::new(ground_color),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 20., -40.),
|
||||
10.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(1., 0.2, 1.))),
|
||||
)),
|
||||
// Blue sphere
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 20., 40.),
|
||||
@ -84,11 +90,15 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
|
||||
)),
|
||||
// STL Mesh
|
||||
Box::new(BVHTriangles::new(&stl_cube, box_material.clone())),
|
||||
Box::new(Translate::new(
|
||||
BVHTriangles::new(&stl_cube, glass),
|
||||
[0., 10., 0.],
|
||||
)),
|
||||
//Box::new(BVHTriangles::new(&stl_cube, box_material.clone())),
|
||||
Box::new(Cuboid::new(
|
||||
[-20., 0., 0.].into(),
|
||||
[0., 20., 20.].into(),
|
||||
Arc::new(box_material),
|
||||
Arc::new(red),
|
||||
)),
|
||||
];
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
|
||||
@ -1,48 +1,12 @@
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
use rand::{self, Rng};
|
||||
|
||||
use crate::{texture::Texture, vec3::Vec3};
|
||||
use crate::{colors::generate_palette, texture::Texture, vec3::Vec3};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mandelbrot {
|
||||
palette: Vec<Vec3>,
|
||||
}
|
||||
|
||||
// HSV values in [0..1]
|
||||
// returns [r, g, b] values from 0 to 255
|
||||
//From https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
|
||||
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> Vec3 {
|
||||
let h_i = (h * 6.) as i32;
|
||||
let f = h * 6. - h_i as f32;
|
||||
let p = v * (1. - s);
|
||||
let q = v * (1. - f * s);
|
||||
let t = v * (1. - (1. - f) * s);
|
||||
match h_i {
|
||||
0 => Vec3::new(v, t, p),
|
||||
1 => Vec3::new(q, v, p),
|
||||
2 => Vec3::new(p, v, t),
|
||||
3 => Vec3::new(p, q, v),
|
||||
4 => Vec3::new(t, p, v),
|
||||
5 => Vec3::new(v, p, q),
|
||||
_ => panic!("Unknown H value {}", h_i),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_palette(num: usize) -> Vec<Vec3> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut random = || rng.gen();
|
||||
// use golden ratio
|
||||
let golden_ratio_conjugate = 0.618_034;
|
||||
let mut h = random();
|
||||
(0..num)
|
||||
.map(|_| {
|
||||
h += golden_ratio_conjugate;
|
||||
h %= 1.0;
|
||||
hsv_to_rgb(h, 0.99, 0.99)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Default for Mandelbrot {
|
||||
fn default() -> Self {
|
||||
Mandelbrot {
|
||||
|
||||
@ -29,6 +29,12 @@ impl Texture for Box<dyn Texture> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Texture for [f32; 3] {
|
||||
fn value(&self, u: f32, v: f32, p: Vec3) -> Vec3 {
|
||||
(*self).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
1
rtiow/renderer/stls/dragon.stl
Symbolic link
1
rtiow/renderer/stls/dragon.stl
Symbolic link
@ -0,0 +1 @@
|
||||
stanford_dragon.stl
|
||||
Loading…
x
Reference in New Issue
Block a user