use sevenx_engine::*;
struct Complete3DShowcase {
camera: Camera3D,
skybox: Skybox,
terrain: Terrain,
physics: Physics3D,
octree: Octree<usize>,
billboards: BillboardBatch,
frustum: Frustum,
time: f32,
show_stats: bool,
}
impl GameState for Complete3DShowcase {
fn new() -> Self {
let mut camera = Camera3D::new(800.0 / 600.0);
camera.position = Vec3::new(0.0, 20.0, 30.0);
camera.look_at(Vec3::new(0.0, 0.0, 0.0));
let mut skybox = Skybox::day_sky();
skybox.set_rotation_speed(0.1);
let terrain = Terrain::hills(50, 50, 15.0);
let mut physics = Physics3D::new();
for i in 0..5 {
let pos = Vec3::new(
(i as f32 - 2.0) * 3.0,
20.0 + i as f32 * 2.0,
0.0,
);
let body = RigidBody3D::dynamic(pos, 1.0);
let collider = Collider3D::sphere(1.0);
physics.add_body(body, collider);
}
let ground_body = RigidBody3D::static_body(Vec3::new(0.0, -1.0, 0.0));
let ground_collider = Collider3D::box_collider(Vec3::new(50.0, 1.0, 50.0));
physics.add_body(ground_body, ground_collider);
let bounds = BoundingBox::new(
Vec3::new(-50.0, -10.0, -50.0),
Vec3::new(50.0, 50.0, 50.0),
);
let mut octree = Octree::new(bounds, 4, 8);
for i in 0..100 {
let x = (i % 10) as f32 * 5.0 - 25.0;
let z = (i / 10) as f32 * 5.0 - 25.0;
octree.insert(Vec3::new(x, 0.0, z), i);
}
let mut billboards = BillboardBatch::new();
for i in 0..20 {
let angle = (i as f32 / 20.0) * std::f32::consts::PI * 2.0;
let radius = 15.0;
let pos = Vec3::new(
angle.cos() * radius,
5.0,
angle.sin() * radius,
);
billboards.add(
Billboard::cylindrical(pos, 2.0, 4.0)
.with_color([50, 150, 50, 255])
);
}
let frustum = Frustum::from_camera(&camera, 800.0 / 600.0);
Self {
camera,
skybox,
terrain,
physics,
octree,
billboards,
frustum,
time: 0.0,
show_stats: true,
}
}
fn update(&mut self, dt: f32, input: &InputHandler, _world: &mut World) {
self.time += dt;
let speed = 15.0;
if input.is_key_down(KeyCode::KeyW) {
self.camera.position = self.camera.position + self.camera.get_forward() * speed * dt;
}
if input.is_key_down(KeyCode::KeyS) {
self.camera.position = self.camera.position - self.camera.get_forward() * speed * dt;
}
if input.is_key_down(KeyCode::KeyA) {
self.camera.position = self.camera.position - self.camera.get_right() * speed * dt;
}
if input.is_key_down(KeyCode::KeyD) {
self.camera.position = self.camera.position + self.camera.get_right() * speed * dt;
}
if input.is_key_down(KeyCode::Space) {
self.camera.position.y += speed * dt;
}
if input.is_key_down(KeyCode::ShiftLeft) {
self.camera.position.y -= speed * dt;
}
if input.is_key_down(KeyCode::ArrowLeft) {
self.camera.yaw -= 90.0 * dt;
}
if input.is_key_down(KeyCode::ArrowRight) {
self.camera.yaw += 90.0 * dt;
}
if input.is_key_down(KeyCode::ArrowUp) {
self.camera.pitch += 90.0 * dt;
}
if input.is_key_down(KeyCode::ArrowDown) {
self.camera.pitch -= 90.0 * dt;
}
if input.is_key_pressed(KeyCode::F1) {
self.show_stats = !self.show_stats;
}
if input.is_key_pressed(KeyCode::Digit1) {
self.skybox = Skybox::day_sky();
}
if input.is_key_pressed(KeyCode::Digit2) {
self.skybox = Skybox::night_sky();
}
if input.is_key_pressed(KeyCode::Digit3) {
self.skybox = Skybox::sunset_sky();
}
if input.is_mouse_button_pressed(MouseBtn::Left) {
let ray_origin = self.camera.position;
let ray_direction = self.camera.get_forward();
if let Some(hit) = self.physics.raycast(ray_origin, ray_direction, 100.0) {
println!("Hit at: {:?}, distance: {:.2}", hit.point, hit.distance);
}
}
self.physics.update(dt);
self.skybox.update(dt);
self.frustum = Frustum::from_camera(&self.camera, 800.0 / 600.0);
self.billboards.sort_by_distance(self.camera.position);
}
fn draw(&mut self, _world: &World, frame_buffer: &mut [u8]) {
for pixel in frame_buffer.chunks_exact_mut(4) {
pixel[0] = 135;
pixel[1] = 206;
pixel[2] = 235;
pixel[3] = 255;
}
if self.show_stats {
self.draw_stats();
}
}
}
impl Complete3DShowcase {
fn draw_stats(&self) {
println!("\n=== Complete 3D Showcase Stats ===");
println!("Camera: ({:.1}, {:.1}, {:.1})",
self.camera.position.x,
self.camera.position.y,
self.camera.position.z
);
println!("\n--- Physics ---");
println!("Bodies: {}", self.physics.bodies.len());
println!("Gravity: ({:.1}, {:.1}, {:.1})",
self.physics.gravity.x,
self.physics.gravity.y,
self.physics.gravity.z
);
let octree_stats = self.octree.get_stats();
println!("\n--- Octree ---");
println!("Nodes: {}", octree_stats.total_nodes);
println!("Objects: {}", octree_stats.total_objects);
println!("Max Depth: {}", octree_stats.max_depth_reached);
println!("Avg Objects/Node: {:.2}", octree_stats.average_objects_per_node);
println!("\n--- Terrain ---");
println!("Size: {}x{}", self.terrain.width, self.terrain.depth);
println!("Vertices: {}", self.terrain.mesh.vertices.len());
println!("Triangles: {}", self.terrain.mesh.indices.len() / 3);
println!("\n--- Billboards ---");
println!("Count: {}", self.billboards.billboards.len());
println!("\n--- Controls ---");
println!("WASD - Move | Space/Shift - Up/Down");
println!("Arrows - Rotate Camera");
println!("1/2/3 - Change Skybox");
println!("Left Click - Raycast");
println!("F1 - Toggle Stats");
}
}
fn main() {
let config = EngineConfig {
window_title: "Complete 3D Showcase - v0.2.8".to_string(),
window_width: 800,
window_height: 600,
clear_color: [135, 206, 235, 255],
..Default::default()
};
Engine::with_config(config).run::<Complete3DShowcase>();
}