alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
root crates client src render scene player.rs
use std::f32::consts::PI;
use common::entity::flags;
use common::inventory::PlayerInventory;
use common::item::{Item, ItemKind, ItemStack};
use common::math::{cos, sin};
use common::world::texture;
use glam::{Mat4, Vec3, Vec4};
use super::{
Geometry, lerp,
models::{Model, Part},
};
use crate::gui::Portrait;
use crate::player::LocalPlayer;
const SKIN: i32 = crate::assets::entity_texture("mob/char.png");
const ARMOR: i32 = crate::assets::ARMOR_TEXTURES;
#[derive(Clone, Copy, Default)]
struct Joint {
position: Vec3,
angles: Vec3,
}
impl Joint {
fn transform(self) -> Mat4 {
Mat4::from_translation(self.position)
* Mat4::from_rotation_z(self.angles.z)
* Mat4::from_rotation_y(self.angles.y)
* Mat4::from_rotation_x(self.angles.x)
}
fn part(self, min: [f32; 3], size: [f32; 3], uv: [f32; 2]) -> Part {
Part::new(min, size, self.position.to_array(), uv).rotate(self.angles.to_array())
}
}
struct Biped {
head: Joint,
body: Joint,
arms: [Joint; 2],
legs: [Joint; 2],
}
impl Biped {
fn pose(entity: &common::entity::Entity, held: bool, head: [f32; 2], partial: f32) -> Self {
let living = entity.living.as_ref().expect("living player");
let phase = living.walk_animation_progress - living.walk_animation_speed * (1.0 - partial);
let speed = lerp(
living.prev_walk_animation_speed,
living.walk_animation_speed,
partial,
)
.min(1.0);
let age = entity.ticks_existed as f32 + partial;
let swing = super::swing(living, partial);
let mut pose = Self {
head: Joint {
angles: Vec3::new(head[1].to_radians(), head[0].to_radians(), 0.0),
..Default::default()
},
body: Joint::default(),
arms: std::array::from_fn(|i| Joint {
position: Vec3::new(if i == 0 { -5.0 } else { 5.0 }, 2.0, 0.0),
angles: Vec3::new(
cos(phase * 0.6662 + if i == 0 { PI } else { 0.0 }) * 2.0 * speed * 0.5,
0.0,
0.0,
),
}),
legs: std::array::from_fn(|i| Joint {
position: Vec3::new(if i == 0 { -2.0 } else { 2.0 }, 12.0, 0.0),
angles: Vec3::new(
cos(phase * 0.6662 + if i == 0 { 0.0 } else { PI }) * 1.4 * speed,
0.0,
0.0,
),
}),
};
if entity.flag(flags::RIDING) {
for i in 0..2 {
pose.arms[i].angles.x -= PI * 0.2;
pose.legs[i].angles.x = -PI * 0.4;
pose.legs[i].angles.y = if i == 0 { PI * 0.1 } else { -PI * 0.1 };
}
}
if held {
pose.arms[0].angles.x = pose.arms[0].angles.x * 0.5 - PI * 0.1;
}
let twist = sin(swing.sqrt() * PI * 2.0) * 0.2;
pose.body.angles.y = twist;
for (i, arm) in pose.arms.iter_mut().enumerate() {
let side = if i == 0 { -1.0 } else { 1.0 };
arm.position.x = side * cos(twist) * 5.0;
arm.position.z = -side * sin(twist) * 5.0;
arm.angles.y = twist;
}
pose.arms[1].angles.x += twist;
let mut strike = 1.0 - swing;
strike *= strike;
strike *= strike;
let strike = sin((1.0 - strike) * PI);
let head = sin(swing * PI) * -(pose.head.angles.x - 0.7) * 0.75;
pose.arms[0].angles.x =
(f64::from(pose.arms[0].angles.x) - (f64::from(strike) * 1.2 + f64::from(head))) as f32;
pose.arms[0].angles.y += twist * 2.0;
pose.arms[0].angles.z = sin(swing * PI) * -0.4;
if entity.is_sneaking() {
pose.body.angles.x = 0.5;
pose.head.position.y = 1.0;
for i in 0..2 {
pose.arms[i].angles.x += 0.4;
pose.legs[i].position.y = 9.0;
pose.legs[i].position.z = 4.0;
}
}
let sway = cos(age * 0.09) * 0.05 + 0.05;
let breathe = sin(age * 0.067) * 0.05;
pose.arms[0].angles.z += sway;
pose.arms[1].angles.z -= sway;
pose.arms[0].angles.x += breathe;
pose.arms[1].angles.x -= breathe;
pose
}
fn render(&self, model: &mut Model<'_>, dilation: f32, mask: u8) {
if mask & 1 != 0 {
for (uv, extra) in [([0.0; 2], 0.0), ([32.0, 0.0], 0.5)] {
let head = self.head.part([-4.0, -8.0, -4.0], [8.0; 3], uv);
model.part(head.inflate(dilation + extra));
}
}
if mask & 2 != 0 {
model.part(
self.body
.part([-4.0, 0.0, -2.0], [8.0, 12.0, 4.0], [16.0; 2])
.inflate(dilation),
);
}
if mask & 4 != 0 {
for (i, arm) in self.arms.iter().enumerate() {
model.part(
arm.part(
[if i == 0 { -3.0 } else { -1.0 }, -2.0, -2.0],
[4.0, 12.0, 4.0],
[40.0, 16.0],
)
.inflate(dilation)
.mirror(i == 1),
);
}
}
if mask & 8 != 0 {
for (i, leg) in self.legs.iter().enumerate() {
model.part(
leg.part([-2.0, 0.0, -2.0], [4.0, 12.0, 4.0], [0.0, 16.0])
.inflate(dilation)
.mirror(i == 1),
);
}
}
}
}
impl Geometry {
#[allow(clippy::too_many_arguments)]
pub(super) fn player_model(
&mut self,
entity: &common::entity::Entity,
inventory: &PlayerInventory,
partial: f32,
position: Vec3,
brightness: f32,
rotation: Option<[f32; 3]>,
bed: Option<usize>,
) {
let Some(living) = &entity.living else {
return;
};
let [yaw, pitch, body] = rotation.unwrap_or([
lerp(entity.prev_yaw, entity.yaw, partial),
lerp(entity.prev_pitch, entity.pitch, partial),
lerp(
living.prev_render_yaw_offset,
living.render_yaw_offset,
partial,
),
]);
let pose = Biped::pose(
entity,
inventory.held().is_some(),
[yaw - body, pitch],
partial,
);
let death = if living.death_time > 0 {
((living.death_time as f32 + partial - 1.0) / 20.0 * 1.6)
.max(0.0)
.sqrt()
.min(1.0)
* PI
/ 2.0
} else {
0.0
};
let corpse = match bed {
Some(direction) => {
let (offset, orientation) = [
(Vec3::new(0.0, 0.0, -1.8), 90.0_f32),
(Vec3::new(1.8, 0.0, 0.0), 0.0),
(Vec3::new(0.0, 0.0, 1.8), 270.0),
(Vec3::new(-1.8, 0.0, 0.0), 180.0),
][direction];
Mat4::from_translation(position + offset)
* Mat4::from_rotation_y(orientation.to_radians())
* Mat4::from_rotation_z(PI / 2.0)
* Mat4::from_rotation_y(270_f32.to_radians())
}
None => {
Mat4::from_translation(position)
* Mat4::from_rotation_y((180.0 - body).to_radians())
* Mat4::from_rotation_z(death)
}
};
let transform = corpse
* Mat4::from_scale(Vec3::new(-0.9375, -0.9375, 0.9375))
* Mat4::from_translation(Vec3::new(0.0, -1.5078125, 0.0))
* Mat4::from_scale(Vec3::splat(1.0 / 16.0));
let hurt = living.hurt_time > 0 || living.death_time > 0;
let mut model = Model {
overlays: super::models::hurt_overlay(hurt, brightness),
..Model::new(self, transform, SKIN, brightness)
};
pose.render(&mut model, 0.0, 15);
for (pass, mask) in [1, 6, 10, 8].into_iter().enumerate() {
let Some(stack) = inventory.armor[3 - pass] else {
continue;
};
let Some(ItemKind::Armor { render, .. }) = stack.item.properties().map(|p| p.kind)
else {
continue;
};
model.texture = ARMOR + i32::from(render) * 2 + i32::from(pass == 2);
pose.render(&mut model, if pass == 2 { 0.5 } else { 1.0 }, mask);
}
if let Some(helmet) = inventory.armor[3]
&& helmet.item.block().is_some()
{
let transform = transform
* pose.head.transform()
* Mat4::from_scale(Vec3::splat(16.0))
* Mat4::from_translation(Vec3::new(0.0, -0.25, 0.0))
* Mat4::from_rotation_y(PI)
* Mat4::from_scale(Vec3::new(0.625, -0.625, 0.625));
self.held_item(transform, helmet, brightness, None);
}
if let Some(stack) = inventory.held() {
let mut transform = transform
* pose.arms[0].transform()
* Mat4::from_scale(Vec3::splat(16.0))
* Mat4::from_translation(Vec3::new(-1.0 / 16.0, 7.0 / 16.0, 1.0 / 16.0));
if item_in_3d(stack) {
transform *= Mat4::from_translation(Vec3::new(0.0, 3.0 / 16.0, -5.0 / 16.0))
* Mat4::from_rotation_x(20_f32.to_radians())
* Mat4::from_rotation_y(45_f32.to_radians())
* Mat4::from_scale(Vec3::new(0.375, -0.375, 0.375));
} else if stack.item.properties().is_some_and(|p| p.full_3d) {
if stack.item == Item::FISHING_ROD {
transform *= Mat4::from_rotation_z(PI)
* Mat4::from_translation(Vec3::new(0.0, -0.125, 0.0));
}
transform *= Mat4::from_translation(Vec3::new(0.0, 3.0 / 16.0, 0.0))
* Mat4::from_scale(Vec3::new(0.625, -0.625, 0.625))
* Mat4::from_rotation_x(-100_f32.to_radians())
* Mat4::from_rotation_y(45_f32.to_radians());
} else {
transform *= Mat4::from_translation(Vec3::new(0.25, 3.0 / 16.0, -3.0 / 16.0))
* Mat4::from_scale(Vec3::splat(0.375))
* Mat4::from_rotation_z(60_f32.to_radians())
* Mat4::from_rotation_x(-PI / 2.0)
* Mat4::from_rotation_z(20_f32.to_radians());
}
self.held_item(transform, stack, brightness, Some(entity.id));
}
}
pub fn names(
&mut self,
entities: &crate::entities::Entities,
viewer: &LocalPlayer,
font: &crate::gui::Geometry,
partial: f32,
hidden: bool,
) {
let eye = viewer.eye(partial);
let yaw = lerp(viewer.entity.prev_yaw, viewer.entity.yaw, partial);
let pitch = lerp(viewer.entity.prev_pitch, viewer.entity.pitch, partial);
let mut ranges = [0..0, 0..0];
for (pass, range) in ranges.iter_mut().enumerate() {
range.start = self.vertices.len() as u32;
for remote in entities.0.values().filter(|_| !hidden) {
let Some(player) = &remote.player else {
continue;
};
let sneaking = player.entity.is_sneaking();
let position = remote.previous.lerp(remote.position, f64::from(partial));
if (sneaking && pass == 0)
|| position.distance_squared(viewer.entity.pos)
>= if sneaking { 1024.0 } else { 4096.0 }
{
continue;
}
let height = if player.entity.sleep.sleeping {
0.8
} else if sneaking {
2.05 - 0.125
} else {
2.3
};
let transform =
Mat4::from_translation((position - eye).as_vec3() + Vec3::Y * height)
* Mat4::from_rotation_y(-yaw.to_radians())
* Mat4::from_rotation_x(pitch.to_radians())
* Mat4::from_scale(Vec3::new(-1.6 / 60.0, -1.6 / 60.0, 1.6 / 60.0));
let faint = pass == 0 || sneaking;
let (glyphs, width) =
font.layout(&player.name, if faint { 0x20ffffff } else { 0xffffffff });
let left = -(width / 2.0).floor();
if faint {
self.quad(
[
[-1.0 + left, -1.0],
[-1.0 + left, 8.0],
[-left + 1.0, 8.0],
[-left + 1.0, -1.0],
]
.map(|[x, y]| transform.transform_point3(Vec3::new(x, y, 0.0))),
[0.0; 4],
-1,
0x40000000,
);
}
self.glyphs(glyphs, Vec3::new(left, 0.0, 0.0), transform);
}
range.end = self.vertices.len() as u32;
}
[self.names_hidden, self.names] = ranges;
}
pub fn build_portrait(
&mut self,
player: &LocalPlayer,
inventory: &PlayerInventory,
portrait: Option<Portrait>,
) {
let start = self.vertices.len() as u32;
self.portrait = start..start;
let Some(Portrait { origin, cursor, .. }) = portrait else {
return;
};
let horizontal = ((origin[0] - cursor[0]) / 40.0).atan();
let vertical = ((origin[1] - 50.0 - cursor[1]) / 40.0).atan();
self.player_model(
&player.entity,
inventory,
1.0,
Vec3::ZERO,
1.0,
Some([horizontal * 40.0, -vertical * 20.0, horizontal * 20.0]),
None,
);
let transform = Mat4::from_translation(Vec3::new(origin[0], origin[1], 0.0))
* Mat4::from_scale(Vec3::new(30.0, -30.0, 30.0))
* Mat4::from_rotation_x((-vertical * 20.0).to_radians());
for vertex in &mut self.vertices[start as usize..] {
vertex.position = transform
.transform_point3(Vec3::from_array(vertex.position))
.to_array();
}
self.portrait.end = self.vertices.len() as u32;
}
/// `holder` is the entity holding the item: a fishing rod with its hook out looks cast.
pub(super) fn held_item(
&mut self,
mut transform: Mat4,
stack: ItemStack,
brightness: f32,
holder: Option<i32>,
) {
let cast = stack.item == common::item::Item::FISHING_ROD
&& holder.is_some_and(|id| self.anglers.contains(&id));
if item_in_3d(stack) {
let block = stack.item.block().expect("block item");
self.block_item(block, stack.damage as u8, transform, brightness);
} else {
transform *= Mat4::from_translation(Vec3::new(0.0, -0.3, 0.0))
* Mat4::from_scale(Vec3::splat(1.5))
* Mat4::from_rotation_y(50_f32.to_radians())
* Mat4::from_rotation_z(335_f32.to_radians())
* Mat4::from_translation(Vec3::new(-15.0 / 16.0, -1.0 / 16.0, 0.0));
let (atlas, icon) = stack.item.block().map_or_else(
|| {
let icon = super::super::item::icon(stack);
(crate::gui::ITEMS, if cast { icon + 16 } else { icon })
},
|block| (0, texture::inventory_face(block, stack.damage as u8, 0)),
);
self.item_sprite(transform, atlas, icon, brightness);
}
}
}
fn item_in_3d(stack: ItemStack) -> bool {
stack.item.block().is_some_and(super::super::item::in_3d)
}
pub(crate) fn portrait_projection([width, height]: [f32; 2]) -> Mat4 {
Mat4::from_cols(
Vec4::new(2.0 / width, 0.0, 0.0, 0.0),
Vec4::new(0.0, -2.0 / height, 0.0, 0.0),
Vec4::new(0.0, 0.0, 1.0 / 200.0, 0.0),
Vec4::new(-1.0, 1.0, 0.5, 1.0),
)
}