use bevy::asset::RenderAssetUsages;
use bevy::image::{Image, ImageAddressMode, ImageSampler, ImageSamplerDescriptor};
use bevy::math::Affine2;
use bevy::prelude::*;
use bevy::render::mesh::Indices;
use bevy::render::render_resource::{Extent3d, PrimitiveTopology, TextureDimension, TextureFormat};
use crate::paint::Rgb;
use crate::ui::ArenaStyle;
use tinhorn_core::physics::{HX, HY, HZ};
fn col(c: Rgb) -> Color {
Color::srgb_u8(c.0, c.1, c.2)
}
fn tex_image(t: &crate::paint::Texture) -> Image {
Image::new(
Extent3d {
width: t.width,
height: t.height,
depth_or_array_layers: 1,
},
TextureDimension::D2,
t.data.clone(),
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::RENDER_WORLD,
)
}
fn textured(
materials: &mut Assets<StandardMaterial>,
image: Handle<Image>,
rough: f32,
) -> Handle<StandardMaterial> {
materials.add(StandardMaterial {
base_color: Color::WHITE,
base_color_texture: Some(image),
perceptual_roughness: rough,
..default()
})
}
fn repeat(mut img: Image) -> Image {
img.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
..default()
});
img
}
fn textured_tiled(
materials: &mut Assets<StandardMaterial>,
images: &mut Assets<Image>,
tex: &crate::paint::Texture,
rough: f32,
scale: Vec2,
) -> Handle<StandardMaterial> {
let image = images.add(repeat(tex_image(tex)));
materials.add(StandardMaterial {
base_color: Color::WHITE,
base_color_texture: Some(image),
uv_transform: Affine2::from_scale(scale),
perceptual_roughness: rough,
..default()
})
}
const RAIL: Rgb = Rgb(128, 86, 58); const TABLE: Rgb = Rgb(92, 60, 40); const APRON: Rgb = Rgb(74, 47, 31); const OAK: Rgb = Rgb(150, 112, 72); const RUG_BORDER: Rgb = Rgb(68, 20, 22); const RUG_FIELD: Rgb = Rgb(112, 30, 32); const CURTAIN: Rgb = Rgb(82, 22, 24);
const WALL_T: f32 = 0.35; const FELT_TOP: f32 = -HY;
pub fn spawn(
commands: &mut Commands,
meshes: &mut Assets<Mesh>,
materials: &mut Assets<StandardMaterial>,
images: &mut Assets<Image>,
) {
let style = ArenaStyle::DEFAULT;
let lip = style.lip_top;
let matte = |mats: &mut Assets<StandardMaterial>, c: Rgb, rough: f32| {
mats.add(StandardMaterial {
base_color: col(c),
perceptual_roughness: rough,
..default()
})
};
let felt_tex = images.add(tex_image(&crate::ui::felt_texture(style.floor)));
let felt = textured(materials, felt_tex, 0.95);
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(HX * 2.0, 0.25, HZ * 2.0))),
MeshMaterial3d(felt),
Transform::from_xyz(0.0, FELT_TOP - 0.125, 0.0),
));
let wall = textured_tiled(
materials,
images,
&crate::ui::grain_texture(style.wall),
0.75,
Vec2::new(5.0, 2.0),
);
let rail = textured_tiled(
materials,
images,
&crate::ui::grain_texture(RAIL),
0.5,
Vec2::new(6.0, 1.0),
);
let inner = matte(materials, lerp_rgb(style.wall, RAIL, 0.4), 0.7);
let wall_y = FELT_TOP + lip * 0.5;
let rail_y = FELT_TOP + lip + 0.05;
let spans: [(Vec3, Vec3, Vec3); 3] = [
(
Vec3::new(0.0, wall_y, -HZ - WALL_T * 0.5),
Vec3::new(HX * 2.0 + WALL_T * 2.0, lip, WALL_T),
Vec3::new(0.0, 0.0, WALL_T * 0.5),
),
(
Vec3::new(-HX - WALL_T * 0.5, wall_y, 0.0),
Vec3::new(WALL_T, lip, HZ * 2.0),
Vec3::new(WALL_T * 0.5, 0.0, 0.0),
),
(
Vec3::new(HX + WALL_T * 0.5, wall_y, 0.0),
Vec3::new(WALL_T, lip, HZ * 2.0),
Vec3::new(-WALL_T * 0.5, 0.0, 0.0),
),
];
for (pos, size, inface) in spans {
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(size.x, size.y, size.z))),
MeshMaterial3d(wall.clone()),
Transform::from_translation(pos),
));
let inner_size = if size.x > size.z {
Vec3::new(size.x - WALL_T, lip * 0.9, 0.06)
} else {
Vec3::new(0.06, lip * 0.9, size.z - WALL_T)
};
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(inner_size.x, inner_size.y, inner_size.z))),
MeshMaterial3d(inner.clone()),
Transform::from_translation(pos + inface),
));
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(size.x + 0.12, 0.2, size.z + 0.12))),
MeshMaterial3d(rail.clone()),
Transform::from_xyz(pos.x, rail_y, pos.z),
));
}
let table_top = FELT_TOP - 0.2;
let table = matte(materials, TABLE, 0.65);
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(HX * 2.0 + 2.6, 0.4, HZ * 2.0 + 2.6))),
MeshMaterial3d(table),
Transform::from_xyz(0.0, table_top - 0.2, 0.7),
));
let apron = matte(materials, APRON, 0.7);
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(HX * 2.0 + 2.6, 1.0, HZ * 2.0 + 2.6))),
MeshMaterial3d(apron),
Transform::from_xyz(0.0, table_top - 0.9, 0.7),
));
let rug_y = table_top - 1.05;
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(HX * 2.0 + 6.0, 0.04, HZ * 2.0 + 5.0))),
MeshMaterial3d(mat_unlit(materials, RUG_BORDER)),
Transform::from_xyz(0.0, rug_y, 1.0),
));
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(HX * 2.0 + 4.4, 0.05, HZ * 2.0 + 3.6))),
MeshMaterial3d(mat_unlit(materials, RUG_FIELD)),
Transform::from_xyz(0.0, rug_y + 0.01, 1.0),
));
let mut floor_img = tex_image(&crate::ui::floor_texture(OAK));
floor_img.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
..default()
});
let floor_tex = images.add(floor_img);
let floor = materials.add(StandardMaterial {
base_color: Color::WHITE,
base_color_texture: Some(floor_tex),
uv_transform: Affine2::from_scale(Vec2::new(16.0, 5.0)),
unlit: true,
..default()
});
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(64.0, 0.1, 64.0))),
MeshMaterial3d(floor),
Transform::from_xyz(0.0, rug_y - 0.12, -8.0),
));
let mut wall_img = repeat(tex_image(&crate::ui::backdrop_texture()));
wall_img.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::ClampToEdge, ..default()
});
let backdrop = materials.add(StandardMaterial {
base_color: Color::WHITE,
base_color_texture: Some(images.add(wall_img)),
uv_transform: Affine2::from_scale(Vec2::new(4.0, 1.0)),
unlit: true,
..default()
});
commands.spawn((
Mesh3d(meshes.add(Rectangle::new(100.0, 42.0))),
MeshMaterial3d(backdrop),
Transform::from_xyz(0.0, rug_y + 16.0, -18.0),
));
let velvet = images.add(tex_image(&crate::ui::velvet_texture(CURTAIN)));
let curtain = textured(materials, velvet, 0.92);
for side in [-1.0f32, 1.0] {
commands.spawn((
Mesh3d(meshes.add(curtain_mesh(16.0, 30.0, 14))),
MeshMaterial3d(curtain.clone()),
Transform::from_xyz(side * 10.5, rug_y + 13.0, -6.0),
));
}
let lamp_y = FELT_TOP + 4.4;
let lamp_z = -0.6;
let shade = materials.add(StandardMaterial {
base_color: col(Rgb(38, 26, 16)),
emissive: LinearRgba::rgb(1.2, 0.72, 0.32), perceptual_roughness: 0.5,
metallic: 0.3,
..default()
});
commands.spawn((
Mesh3d(meshes.add(Cone {
radius: 0.5,
height: 0.5,
})),
MeshMaterial3d(shade),
Transform::from_xyz(0.0, lamp_y, lamp_z),
));
let chain = matte(materials, Rgb(20, 16, 12), 0.6);
commands.spawn((
Mesh3d(meshes.add(Cylinder::new(0.025, 2.4))),
MeshMaterial3d(chain),
Transform::from_xyz(0.0, lamp_y + 1.5, lamp_z),
));
for (i, side) in [-1.0f32, 1.0].into_iter().enumerate() {
let stacks = 2 + i; for s in 0..stacks {
let sx = side * (HX + 0.7 + s as f32 * 0.55);
let sz = HZ + 0.5 + s as f32 * 0.3;
let height = 4 + ((s + i) * 3) % 5; for k in 0..height {
let chip = chip_color(materials, (s + k + i) % 4);
commands.spawn((
Mesh3d(meshes.add(Cylinder::new(0.34, 0.06))),
MeshMaterial3d(chip),
Transform::from_xyz(sx, FELT_TOP - 0.24 + 0.06 * k as f32, sz),
));
}
}
}
}
fn mat_unlit(materials: &mut Assets<StandardMaterial>, c: Rgb) -> Handle<StandardMaterial> {
materials.add(StandardMaterial {
base_color: col(c),
unlit: true,
..default()
})
}
fn chip_color(materials: &mut Assets<StandardMaterial>, idx: usize) -> Handle<StandardMaterial> {
let c = [
Rgb(220, 220, 224), Rgb(190, 54, 54), Rgb(46, 92, 180), Rgb(40, 42, 46), ][idx % 4];
materials.add(StandardMaterial {
base_color: col(c),
perceptual_roughness: 0.42,
reflectance: 0.55,
..default()
})
}
fn curtain_mesh(w: f32, h: f32, folds: u32) -> Mesh {
let cols = folds * 8; let rows = 24u32; let tau = std::f32::consts::TAU;
let f = folds as f32;
let profile = |u: f32, v: f32| -> f32 {
let amp = 0.5 * (0.6 + 0.7 * v); let a = (u * f * tau).sin();
let b = (u * f * 2.3 * tau + 1.7).sin();
let c = (u * f * 0.55 * tau + 4.1).sin();
amp * (0.62 * a + 0.26 * b + 0.34 * c)
};
let belly = |v: f32| -> f32 { 0.6 * v * v };
let mut positions: Vec<[f32; 3]> = Vec::new();
let mut normals: Vec<[f32; 3]> = Vec::new();
let mut uvs: Vec<[f32; 2]> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
let eps = 1.0e-3;
for r in 0..=rows {
let v = r as f32 / rows as f32; let y = (0.5 - v) * h;
for c in 0..=cols {
let u = c as f32 / cols as f32;
let x = (u - 0.5) * w;
let z = profile(u, v) + belly(v);
positions.push([x, y, z]);
uvs.push([u, v]);
let dz_du = (profile(u + eps, v) - profile(u - eps, v)) / (2.0 * eps);
let dz_dv = (profile(u, v + eps) - profile(u, v - eps) + belly(v + eps)
- belly(v - eps))
/ (2.0 * eps);
let tu = Vec3::new(w, 0.0, dz_du);
let tv = Vec3::new(0.0, -h, dz_dv);
let n = tv.cross(tu).normalize();
normals.push([n.x, n.y, n.z]);
}
}
let stride = cols + 1;
for r in 0..rows {
for c in 0..cols {
let i = r * stride + c;
indices.extend_from_slice(&[i, i + stride, i + 1, i + 1, i + stride, i + stride + 1]);
}
}
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD,
)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
.with_inserted_indices(Indices::U32(indices))
}
fn lerp_rgb(a: Rgb, b: Rgb, t: f32) -> Rgb {
let l = |x: u8, y: u8| (x as f32 + (y as f32 - x as f32) * t) as u8;
Rgb(l(a.0, b.0), l(a.1, b.1), l(a.2, b.2))
}