Files
LemnosLife_Rust/src/main.rs
T
2022-11-03 02:35:20 +01:00

712 lines
23 KiB
Rust

#[macro_use]
extern crate glium;
#[macro_use]
extern crate load_file;
use glium::texture::SrgbTexture2d;
use phf::phf_map;
use std::f32::consts::PI;
use std::fs;
use std::io::Cursor;
use std::time::Instant;
use crate::support::CHUNK_SIZE;
use glium::vertex::VertexBufferAny;
#[allow(unused_imports)]
use glium::{glutin, Surface};
mod support;
#[derive(Copy, Clone, Debug)]
struct PerInstance {
pub w_position: (f32, f32, f32),
pub w_rotation: f32,
}
implement_vertex!(PerInstance, w_position, w_rotation);
const STRUCTURES_TEXTURES_NUMBER: usize = 78;
const STRUCTURES_TEXTURES: [&str; STRUCTURES_TEXTURES_NUMBER] = [
"redNoise.jpg",
"cargoLightBlue.jpg",
"beigeNoise.jpg",
"wallStone.jpg",
"cargoWhite.jpg",
"b_ficusc2d_f.png",
"basketball_court.jpg",
"zinc.jpg",
"solarPowerPlant.png",
"cargoSand.jpg",
"water.jpg",
"solarPowerPlantAccumulator.jpg",
"blueNoise.jpg",
"30kmh.jpg",
"white.jpg",
"blackYellow.jpg",
"stopText.jpg",
"cargoOrange.jpg",
"redWhite.jpg",
"grid.png",
"blueTile.jpg",
"blueWhiteNoise.jpg",
"reinforcedConcrete.jpg",
"log.jpg",
"green.jpg",
"fanWallGrey.jpg",
"cargoCyan.jpg",
"glass.png",
"wallGrey.jpg",
"greenGate.png",
"greyNoise.jpg",
"cattail.png",
"palmLayer.png",
"cargoLightGreen.jpg",
"palm.png",
"branch1.png",
"cargoYellow.jpg",
"blackNoise.jpg",
"pipes.jpg",
"whiteTile.jpg",
"darkBlueNoise.jpg",
"planks.png",
"trunk.jpg",
"greenNoise.jpg",
"wallBlue.jpg",
"stop.jpg",
"wallGreyATM.jpg",
"iron_bars.png",
"tile.jpg",
"horizontalBrick.jpg",
"steel.jpg",
"yellowBars.png",
"whiteDoor.jpg",
"cargoBlue.jpg",
"barbed.png",
"woodBlue.jpg",
"bamboo.png",
"b_ficusc1s_f.png",
"whiteNoise.jpg",
"fanWall.jpg",
"wood.jpg",
"crossover.jpg",
"gridHexa.png",
"lightBlue.jpg",
"helipad.png",
"binBarrel.jpg",
"brick.jpg",
"wallBlueATM.jpg",
"yellow.jpg",
"blueDoor.jpg",
"parking.jpg",
"cargoGrey.jpg",
"haystack.jpg",
"cargoRed.jpg",
"yellowNoise.jpg",
"gridFull.jpg",
"rust.jpg",
"cargo.jpg",
];
// This approach works as there isn't file names having several file extensions.
// `phf` doesn't propose any `bimap`.
static STRUCTURES_TEXTURES_REVERSED: phf::Map<&'static str, u32> = phf_map! {
"redNoise" => 0,
"cargoLightBlue" => 1,
"beigeNoise" => 2,
"wallStone" => 3,
"cargoWhite" => 4,
"b_ficusc2d_f" => 5,
"basketball_court" => 6,
"zinc" => 7,
"solarPowerPlant" => 8,
"cargoSand" => 9,
"water" => 10,
"solarPowerPlantAccumulator" => 11,
"blueNoise" => 12,
"30kmh" => 13,
"white" => 14,
"blackYellow" => 15,
"stopText" => 16,
"cargoOrange" => 17,
"redWhite" => 18,
"grid" => 19,
"blueTile" => 20,
"blueWhiteNoise" => 21,
"reinforcedConcrete" => 22,
"log" => 23,
"green" => 24,
"fanWallGrey" => 25,
"cargoCyan" => 26,
"glass" => 27,
"wallGrey" => 28,
"greenGate" => 29,
"greyNoise" => 30,
"cattail" => 31,
"palmLayer" => 32,
"cargoLightGreen" => 33,
"palm" => 34,
"branch1" => 35,
"cargoYellow" => 36,
"blackNoise" => 37,
"pipes" => 38,
"whiteTile" => 39,
"darkBlueNoise" => 40,
"planks" => 41,
"trunk" => 42,
"greenNoise" => 43,
"wallBlue" => 44,
"stop" => 45,
"wallGreyATM" => 46,
"iron_bars" => 47,
"tile" => 48,
"horizontalBrick" => 49,
"steel" => 50,
"yellowBars" => 51,
"whiteDoor" => 52,
"cargoBlue" => 53,
"barbed" => 54,
"woodBlue" => 55,
"bamboo" => 56,
"b_ficusc1s_f" => 57,
"whiteNoise" => 58,
"fanWall" => 59,
"wood" => 60,
"crossover" => 61,
"gridHexa" => 62,
"lightBlue" => 63,
"helipad" => 64,
"binBarrel" => 65,
"brick" => 66,
"wallBlueATM" => 67,
"yellow" => 68,
"blueDoor" => 69,
"parking" => 70,
"cargoGrey" => 71,
"haystack" => 72,
"cargoRed" => 73,
"yellowNoise" => 74,
"gridFull" => 75,
"rust" => 76,
"cargo" => 77,
};
const BIOMES_NUMBER: usize = 19;
const BIOMES: [&str; BIOMES_NUMBER] = [
"dirt",
"grassDry",
"grassGreen",
"concrete",
"soil",
"beach",
"seabed",
"thorn",
"wildField",
"rock",
"grassWild",
"stony",
"forestPine",
"mud",
"stonyThistle",
"marsh",
"dead",
"desert",
"weed",
];
fn main() {
// building the display, ie. the main object
let event_loop = glutin::event_loop::EventLoop::new();
let wb = glutin::window::WindowBuilder::new()
.with_title("LemnosLife")
.with_maximized(true);
let cb = glutin::ContextBuilder::new().with_depth_buffer(24);
let display = glium::Display::new(wb, cb, &event_loop).unwrap();
// building the vertex and index buffers
let ground_vertex_buffer = support::load_ground(&display);
let ground_textures: [SrgbTexture2d; BIOMES_NUMBER] = BIOMES.map(|biome| {
println!("{}", biome);
let image = image::load(
Cursor::new(&load_bytes!(&format!(
"../Extensions/LemnosLife/Assets/Pictures/Map/Ground/{}.jpg",
biome
))),
image::ImageFormat::Jpeg,
)
.unwrap()
.to_rgba8();
let image_dimensions = image.dimensions();
let image =
glium::texture::RawImage2d::from_raw_rgba_reversed(&image.into_raw(), image_dimensions);
glium::texture::SrgbTexture2d::new(&display, image).unwrap()
});
let structures_textures: [SrgbTexture2d; STRUCTURES_TEXTURES_NUMBER] =
STRUCTURES_TEXTURES.map(|structure_texture| {
println!("{}", structure_texture);
let image = image::load(
Cursor::new(&load_bytes!(&format!(
"../Extensions/LemnosLife/Assets/Downloads/{}",
structure_texture
))),
if structure_texture.ends_with(".png") {
image::ImageFormat::Png
} else {
image::ImageFormat::Jpeg
},
)
.unwrap()
.to_rgba8();
let image_dimensions = image.dimensions();
let image = glium::texture::RawImage2d::from_raw_rgba_reversed(
&image.into_raw(),
image_dimensions,
);
glium::texture::SrgbTexture2d::new(&display, image).unwrap()
});
let uniform_sampler_2d_str = (0..BIOMES_NUMBER)
.map(|i| format!("tex{}", i))
.collect::<Vec<String>>()
.join(", ");
let switch_v_biome_str = (0..BIOMES_NUMBER)
.map(|i| {
format!(
"
case {}u:
f_color = texture(tex{}, v_tex_coords);
break;
",
i, i
)
})
.collect::<Vec<String>>()
.join("")
+ &format!(
"
default: // case {}u:
f_color = texture(tex{}, v_tex_coords);
",
BIOMES_NUMBER - 1,
BIOMES_NUMBER - 1
);
let ground_fragment = &format!(
"
#version 140
in vec2 v_tex_coords;
flat in uint v_biome;
out vec4 f_color;
uniform sampler2D {};
void main() {{
switch (v_biome) {{
{}
}}
}}
",
uniform_sampler_2d_str, switch_v_biome_str
);
let uniform_sampler_2d_str = (0..STRUCTURES_TEXTURES_NUMBER)
.map(|i| format!("tex{}", i))
.collect::<Vec<String>>()
.join(", ");
let switch_v_texture_index_str = (0..STRUCTURES_TEXTURES_NUMBER)
.map(|i| {
format!(
"
case {}u:
f_color = texture(tex{}, v_texture_coordinates);
break;
",
i, i
)
})
.collect::<Vec<String>>()
.join("")
+ &format!(
"
default: // case {}u:
f_color = texture(tex{}, v_texture_coordinates);
",
STRUCTURES_TEXTURES_NUMBER - 1,
STRUCTURES_TEXTURES_NUMBER - 1
);
let structures_fragment = &format!(
"
#version 140
in vec2 v_texture_coordinates;
flat in uint v_texture_index;
out vec4 f_color;
uniform sampler2D {};
void main() {{
switch (v_texture_index) {{
{}
}}
}}
",
uniform_sampler_2d_str, switch_v_texture_index_str
);
// the program
let ground_program = program!(&display,
140 => {
vertex: "
#version 140
uniform mat4 persp_matrix;
uniform mat4 view_matrix;
in uint index;
in float altitude;
in uint biome;
in uvec2 chunk;
out vec2 v_tex_coords;
flat out uint v_biome;
const uint TILE_SIZE = 4u;
const uint TILES_PER_CHUNK_ROW = 250u;
const uint TRIANGLES_PER_CHUNK_ROW = TILES_PER_CHUNK_ROW * 2u + 2u;
void main() {
uint index_mod_triangles_per_chunk_row = index % TRIANGLES_PER_CHUNK_ROW;
uint index_div_triangles_per_chunk_row = index / TRIANGLES_PER_CHUNK_ROW;
uint index_div_triangles_per_chunk_row_mod_2 = index_div_triangles_per_chunk_row % 2u;
uint index_mod_triangles_per_chunk_row_div_2 = index_mod_triangles_per_chunk_row / 2u;
uint x = index_div_triangles_per_chunk_row_mod_2 == 0u ? index_mod_triangles_per_chunk_row_div_2 : TILES_PER_CHUNK_ROW - index_mod_triangles_per_chunk_row_div_2;
uint z = index_div_triangles_per_chunk_row + index % 2u;
switch (index % 4u) {
case 0u:
v_tex_coords = vec2(index_div_triangles_per_chunk_row_mod_2, 0);
break;
case 1u:
v_tex_coords = vec2(index_div_triangles_per_chunk_row_mod_2, 1);
break;
case 2u:
v_tex_coords = vec2(1u - index_div_triangles_per_chunk_row_mod_2, 0);
break;
default: // case 3u
v_tex_coords = vec2(1u - index_div_triangles_per_chunk_row_mod_2, 1);
break;
}
v_biome = biome;
x *= TILE_SIZE;
z *= TILE_SIZE;
x += chunk.x;
z += chunk.y;
gl_Position = persp_matrix * view_matrix * vec4(vec3(x, altitude, z), 1.0);
}
",
fragment: ground_fragment,
},
)
.unwrap();
let mut camera = support::camera::CameraState::new();
let structures_folder = "Extensions/LemnosLife/Map/Common/Structures/";
let paths = fs::read_dir(structures_folder).unwrap();
let mut structures_vertex_buffer: Vec<(usize, VertexBufferAny)> = vec![];
for path in paths {
let path = path.unwrap().path();
let file_path = path.to_str().unwrap();
if file_path.ends_with(".obj") {
let file = file_path.replace(structures_folder, "");
println!("{}", file);
structures_vertex_buffer.push((
file.replace(".obj", "").parse().unwrap(),
support::load_wavefront(&display, load_bytes!(&format!("../{}", file_path))),
))
}
}
let mut structures_per_instance: Vec<Vec<PerInstance>> = Vec::with_capacity(926); // As the maximum structure id is 925.
for _ in 0..=925 {
structures_per_instance.push(vec![]);
}
let structures_program = program!(&display,
140 => {
vertex: "
#version 140
uniform mat4 persp_matrix;
uniform mat4 view_matrix;
in vec3 w_position;
in float w_rotation;
in vec3 position;
in uint texture_index;
in vec2 texture_coordinates;
flat out uint v_texture_index;
out vec2 v_texture_coordinates;
void main() {
float cos_rotation = cos(w_rotation);
float sin_rotation = sin(w_rotation);
vec3 rotated_position = position;
rotated_position.x = cos_rotation * position.x + sin_rotation * position.z;
rotated_position.z = -sin_rotation * position.x + cos_rotation * position.z;
v_texture_index = texture_index;
v_texture_coordinates = texture_coordinates;
gl_Position = persp_matrix * view_matrix * vec4(rotated_position + w_position, 1.0);
}
",
fragment: structures_fragment,
},
)
.unwrap();
let objects_folder = "Extensions/LemnosLife/Map/Altis/Objects/";
let paths = fs::read_dir(objects_folder).unwrap();
for path in paths {
let path = path.unwrap().path();
let file_path = path.to_str().unwrap();
let file = file_path.replace(objects_folder, "");
println!("{}", file);
let file_name = file.replace(".objects", "");
let file_name_parts: Vec<&str> = file_name.split(' ').collect();
let objects_contents = fs::read_to_string(file_path)
.unwrap_or_else(|_| panic!("Unable to load {} file!", file_path));
let chunk = [0, 1].map(|file_name_parts_index| {
file_name_parts[file_name_parts_index]
.parse::<f32>()
.unwrap()
* (CHUNK_SIZE as f32)
});
let objects_lines: Vec<&str> = objects_contents.split('\n').collect();
let (mut offset_x, mut offset_y) = (0.0, 0.0);
for object_line in objects_lines {
let object_line_parts: Vec<&str> = object_line.split(' ').collect();
if object_line_parts.len() == 2 {
offset_x = object_line_parts[0].parse::<f32>().unwrap() * 100.0 + chunk[0];
offset_y = object_line_parts[1].parse::<f32>().unwrap() * 100.0 + chunk[1];
} else {
let structure_id: usize = object_line_parts[0].parse().unwrap();
let x: f32 = object_line_parts[1].parse().unwrap();
let y: f32 = object_line_parts[2].parse().unwrap();
let z = object_line_parts[3].parse().unwrap();
let rotation: f32 = object_line_parts[4].parse().unwrap();
let structure_per_instance: &mut Vec<PerInstance> =
structures_per_instance.get_mut(structure_id).unwrap();
structure_per_instance.push(PerInstance {
w_position: (offset_x + x, z, offset_y + y),
w_rotation: 2.0 * PI * rotation / 360.0,
});
}
}
}
// the main loop
support::start_loop(event_loop, move |events| {
camera.update();
// building the uniforms
let ground_uniforms = uniform! {
persp_matrix: camera.get_perspective(),
view_matrix: camera.get_view(),
tex0: &ground_textures[0],
tex1: &ground_textures[1],
tex2: &ground_textures[2],
tex3: &ground_textures[3],
tex4: &ground_textures[4],
tex5: &ground_textures[5],
tex6: &ground_textures[6],
tex7: &ground_textures[7],
tex8: &ground_textures[8],
tex9: &ground_textures[9],
tex10: &ground_textures[10],
tex11: &ground_textures[11],
tex12: &ground_textures[12],
tex13: &ground_textures[13],
tex14: &ground_textures[14],
tex15: &ground_textures[15],
tex16: &ground_textures[16],
tex17: &ground_textures[17],
tex18: &ground_textures[18],
};
// That way we *load* far too many textures per structure but until haven't treated http://gitea.lemnoslife.com:3006/Benjamin_Loison/LemnosLife_Rust/issues/6#issuecomment-240, can't do better.
// Except if we proceed with a disjunction on the number of textures used in a structure which is 8, thanks to `find Extensions/LemnosLife/Map/Common/Structures/ -name '*.obj' -exec sh -c 'grep -o usemtl {} | wc -l' \; | sort -n | tail -1`.
let structures_uniforms = uniform! {
persp_matrix: camera.get_perspective(),
view_matrix: camera.get_view(),
tex0: &structures_textures[0],
tex1: &structures_textures[1],
tex2: &structures_textures[2],
tex3: &structures_textures[3],
tex4: &structures_textures[4],
tex5: &structures_textures[5],
tex6: &structures_textures[6],
tex7: &structures_textures[7],
tex8: &structures_textures[8],
tex9: &structures_textures[9],
tex10: &structures_textures[10],
tex11: &structures_textures[11],
tex12: &structures_textures[12],
tex13: &structures_textures[13],
tex14: &structures_textures[14],
tex15: &structures_textures[15],
tex16: &structures_textures[16],
tex17: &structures_textures[17],
tex18: &structures_textures[18],
tex19: &structures_textures[19],
tex20: &structures_textures[20],
tex21: &structures_textures[21],
tex22: &structures_textures[22],
tex23: &structures_textures[23],
tex24: &structures_textures[24],
tex25: &structures_textures[25],
tex26: &structures_textures[26],
tex27: &structures_textures[27],
tex28: &structures_textures[28],
tex29: &structures_textures[29],
tex30: &structures_textures[30],
tex31: &structures_textures[31],
tex32: &structures_textures[32],
tex33: &structures_textures[33],
tex34: &structures_textures[34],
tex35: &structures_textures[35],
tex36: &structures_textures[36],
tex37: &structures_textures[37],
tex38: &structures_textures[38],
tex39: &structures_textures[39],
tex40: &structures_textures[40],
tex41: &structures_textures[41],
tex42: &structures_textures[42],
tex43: &structures_textures[43],
tex44: &structures_textures[44],
tex45: &structures_textures[45],
tex46: &structures_textures[46],
tex47: &structures_textures[47],
tex48: &structures_textures[48],
tex49: &structures_textures[49],
tex50: &structures_textures[50],
tex51: &structures_textures[51],
tex52: &structures_textures[52],
tex53: &structures_textures[53],
tex54: &structures_textures[54],
tex55: &structures_textures[55],
tex56: &structures_textures[56],
tex57: &structures_textures[57],
tex58: &structures_textures[58],
tex59: &structures_textures[59],
tex60: &structures_textures[60],
tex61: &structures_textures[61],
tex62: &structures_textures[62],
tex63: &structures_textures[63],
tex64: &structures_textures[64],
tex65: &structures_textures[65],
tex66: &structures_textures[66],
tex67: &structures_textures[67],
tex68: &structures_textures[68],
tex69: &structures_textures[69],
tex70: &structures_textures[70],
tex71: &structures_textures[71],
tex72: &structures_textures[72],
tex73: &structures_textures[73],
tex74: &structures_textures[74],
tex75: &structures_textures[75],
tex76: &structures_textures[76],
tex77: &structures_textures[77],
};
// draw parameters
let ground_params = glium::DrawParameters {
depth: glium::Depth {
test: glium::DepthTest::IfLess,
write: true,
..Default::default()
},
// Verify that it helps from the FPS point of view.
//backface_culling: glium::draw_parameters::BackfaceCullingMode::CullClockwise,
..Default::default()
};
let mut structures_params = ground_params.clone();
structures_params.blend = glium::draw_parameters::Blend::alpha_blending(); // Could disable per opaque structure to optimize.
// Doesn't seem to apply transparency from a structure instance to the other.
// drawing a frame
let start = Instant::now();
let mut target = display.draw();
target.clear_color_and_depth((0.0, 0.0, 0.0, 0.0), 1.0);
target
.draw(
&ground_vertex_buffer,
&glium::index::NoIndices(glium::index::PrimitiveType::TriangleStrip),
&ground_program,
&ground_uniforms,
&ground_params,
)
.unwrap();
for (structure_id, structure_vertex_buffer) in &structures_vertex_buffer {
let structure_per_instance = &structures_per_instance[*structure_id];
if !structure_per_instance.is_empty() {
let structure_per_instance_buffer =
glium::vertex::VertexBuffer::new(&display, structure_per_instance).unwrap();
target
.draw(
(
structure_vertex_buffer,
structure_per_instance_buffer.per_instance().unwrap(),
),
&glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList),
&structures_program,
&structures_uniforms,
&structures_params,
)
.unwrap();
}
}
target.finish().unwrap();
let duration = start.elapsed();
println!("Time elapsed is: {:?}", duration);
let mut action = support::Action::Continue;
// polling and handling the events received by the window
for event in events {
if let glutin::event::Event::WindowEvent { event, .. } = event {
match event {
glutin::event::WindowEvent::CloseRequested => action = support::Action::Stop,
ev => camera.process_input(ev),
}
}
}
action
});
}