Merge branch 'zesterer/worldgen' into 'master'

Lighting, worldgen, smooth shadows, colour blending

See merge request veloren/veloren!511
This commit is contained in:
Joshua Barretto 2019-09-26 16:10:11 +00:00
commit b57031804e
72 changed files with 1252 additions and 308 deletions

View File

@ -28,8 +28,15 @@ uniform u_bones {
out vec4 tgt_color;
void main() {
vec3 light = get_sun_diffuse(f_norm, time_of_day.x) + light_at(f_pos, f_norm);
vec3 surf_color = srgb_to_linear(model_col.rgb * f_col) * 4.0 * light;
vec3 light, diffuse_light, ambient_light;
get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light);
float point_shadow = shadow_at(f_pos, f_norm);
diffuse_light *= point_shadow;
ambient_light *= point_shadow;
vec3 point_light = light_at(f_pos, f_norm);
light += point_light;
diffuse_light += point_light;
vec3 surf_color = illuminate(srgb_to_linear(model_col.rgb * f_col), light, diffuse_light, ambient_light);
float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x);
vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x, true);

View File

@ -31,16 +31,16 @@ void main() {
mat4 combined_mat = model_mat * bones[v_bone_idx].bone_mat;
f_pos = (
combined_mat *
combined_mat *
vec4(v_pos, 1)).xyz;
f_col = v_col;
// Calculate normal here rather than for each pixel in the fragment shader
f_norm = (
combined_mat *
f_norm = normalize((
combined_mat *
vec4(v_norm, 0.0)
).xyz;
).xyz);
gl_Position = proj_mat * view_mat * vec4(f_pos, 1);
}

View File

@ -11,6 +11,7 @@ in float f_light;
layout (std140)
uniform u_locals {
vec3 model_offs;
float load_time;
};
out vec4 tgt_color;
@ -37,8 +38,15 @@ void main() {
vec3 norm = warp_normal(f_norm, f_pos, tick.x);
vec3 light = get_sun_diffuse(norm, time_of_day.x) * f_light + light_at(f_pos, norm);
vec3 surf_color = f_col * light;
vec3 light, diffuse_light, ambient_light;
get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light);
float point_shadow = shadow_at(f_pos, f_norm);
diffuse_light *= f_light * point_shadow;
ambient_light *= f_light, point_shadow;
vec3 point_light = light_at(f_pos, f_norm);
light += point_light;
diffuse_light += point_light;
vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light);
float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x);
vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x, true);
@ -52,7 +60,7 @@ void main() {
// 0 = 100% reflection, 1 = translucent water
float passthrough = pow(dot(faceforward(norm, norm, cam_to_frag), -cam_to_frag), 1.0);
vec4 color = mix(vec4(reflect_color, 1.0), vec4(surf_color, 0.5 / (1.0 + light * 2.0)), passthrough);
vec4 color = mix(vec4(reflect_color, 1.0), vec4(surf_color, 0.5 / (1.0 + diffuse_light * 2.0)), passthrough);
tgt_color = mix(color, vec4(fog_color, 0.0), fog_level);
}

View File

@ -8,6 +8,7 @@ in uint v_col_light;
layout (std140)
uniform u_locals {
vec3 model_offs;
float load_time;
};
out vec3 f_pos;

View File

@ -8,6 +8,7 @@ uniform u_globals {
vec4 time_of_day;
vec4 tick;
vec4 screen_res;
uvec4 light_count;
uvec4 light_shadow_count;
uvec4 medium;
ivec4 select_pos;
};

View File

@ -8,8 +8,22 @@ uniform u_lights {
Light lights[32];
};
struct Shadow {
vec4 shadow_pos_radius;
};
layout (std140)
uniform u_shadows {
Shadow shadows[24];
};
#include <srgb.glsl>
vec3 illuminate(vec3 color, vec3 light, vec3 diffuse, vec3 ambience) {
float avg_col = (color.r + color.g + color.b) / 3.0;
return ((color - avg_col) * light + (diffuse + ambience) * avg_col) * diffuse;
}
float attenuation_strength(vec3 rpos) {
return 1.0 / (rpos.x * rpos.x + rpos.y * rpos.y + rpos.z * rpos.z);
}
@ -19,7 +33,7 @@ vec3 light_at(vec3 wpos, vec3 wnorm) {
vec3 light = vec3(0);
for (uint i = 0u; i < light_count.x; i++) {
for (uint i = 0u; i < light_shadow_count.x; i ++) {
// Only access the array once
Light L = lights[i];
@ -29,21 +43,35 @@ vec3 light_at(vec3 wpos, vec3 wnorm) {
// Pre-calculate difference between light and fragment
vec3 difference = light_pos - wpos;
float strength = attenuation_strength(difference);
float strength = pow(attenuation_strength(difference), 0.6);
// Multiply the vec3 only once
vec3 color = srgb_to_linear(L.light_col.rgb) * (strength * L.light_col.a);
// This is commented out to avoid conditional branching. See here: https://community.khronos.org/t/glsl-float-multiply-by-zero/104391
// if (max(max(color.r, color.g), color.b) < 0.002) {
// continue;
// }
// Old: light += color * clamp(dot(normalize(difference), wnorm), LIGHT_AMBIENCE, 1.0);
// The dot product cannot be greater than one, so no need to clamp max value
// Also, rather than checking if it is smaller than LIGHT_AMBIENCE, add LIGHT_AMBIENCE instead
light += color * (max(0, dot(normalize(difference), wnorm)) + LIGHT_AMBIENCE);
}
return light;
}
}
float shadow_at(vec3 wpos, vec3 wnorm) {
float shadow = 1.0;
for (uint i = 0u; i < light_shadow_count.y; i ++) {
// Only access the array once
Shadow S = shadows[i];
vec3 shadow_pos = S.shadow_pos_radius.xyz;
float radius = S.shadow_pos_radius.w;
vec3 diff = shadow_pos - wpos;
if (diff.z >= 0.0) {
diff.z = diff.z * 0.1;
}
float shade = max(pow(diff.x * diff.x + diff.y * diff.y + diff.z * diff.z, 0.25) / pow(radius * radius * 0.5, 0.25), 0.5);
shadow = min(shadow, shade);
}
return min(shadow, 1.0);
}

View File

@ -5,12 +5,14 @@ const float PI = 3.141592;
const vec3 SKY_DAY_TOP = vec3(0.1, 0.2, 0.9);
const vec3 SKY_DAY_MID = vec3(0.02, 0.08, 0.8);
const vec3 SKY_DAY_BOT = vec3(0.02, 0.01, 0.3);
const vec3 DAY_LIGHT = vec3(1.3, 0.9, 1.1);
const vec3 DAY_LIGHT = vec3(1.2, 1.0, 1.0);
const vec3 SUN_HALO_DAY = vec3(0.35, 0.35, 0.0);
const vec3 SKY_DUSK_TOP = vec3(0.06, 0.1, 0.20);
const vec3 SKY_DUSK_MID = vec3(0.35, 0.1, 0.15);
const vec3 SKY_DUSK_BOT = vec3(0.0, 0.1, 0.13);
const vec3 DUSK_LIGHT = vec3(3.0, 0.65, 0.3);
const vec3 DUSK_LIGHT = vec3(3.0, 1.0, 0.3);
const vec3 SUN_HALO_DUSK = vec3(0.6, 0.1, 0.0);
const vec3 SKY_NIGHT_TOP = vec3(0.001, 0.001, 0.0025);
const vec3 SKY_NIGHT_MID = vec3(0.001, 0.005, 0.02);
@ -26,14 +28,14 @@ vec3 get_sun_dir(float time_of_day) {
return sun_dir;
}
const float PERSISTENT_AMBIANCE = 0.1;
float get_sun_brightness(vec3 sun_dir) {
return max(-sun_dir.z + 0.6, 0.0);
return max(-sun_dir.z + 0.6, 0.0) * 0.8;
}
const float PERSISTENT_AMBIANCE = 0.008;
vec3 get_sun_diffuse(vec3 norm, float time_of_day) {
const float SUN_AMBIANCE = 0.15;
void get_sun_diffuse(vec3 norm, float time_of_day, out vec3 light, out vec3 diffuse_light, out vec3 ambient_light) {
const float SUN_AMBIANCE = 0.1;
vec3 sun_dir = get_sun_dir(time_of_day);
@ -51,9 +53,11 @@ vec3 get_sun_diffuse(vec3 norm, float time_of_day) {
max(-sun_dir.z, 0)
);
vec3 diffuse_light = (SUN_AMBIANCE + max(dot(-norm, sun_dir), 0.0) * sun_color) * sun_light + PERSISTENT_AMBIANCE;
vec3 sun_chroma = sun_color * sun_light;
return diffuse_light;
light = sun_chroma + PERSISTENT_AMBIANCE;
diffuse_light = sun_chroma * (dot(-norm, sun_dir) * 0.5 + 0.5) + PERSISTENT_AMBIANCE;
ambient_light = vec3(SUN_AMBIANCE * sun_light);
}
// This has been extracted into a function to allow quick exit when detecting a star.
@ -99,7 +103,7 @@ vec3 get_sky_color(vec3 dir, float time_of_day, bool with_stars) {
mix(
SKY_DUSK_TOP + star,
SKY_NIGHT_TOP + star,
max(sun_dir.z, 0)
max(pow(sun_dir.z, 0.2), 0)
),
SKY_DAY_TOP,
max(-sun_dir.z, 0)
@ -109,7 +113,7 @@ vec3 get_sky_color(vec3 dir, float time_of_day, bool with_stars) {
mix(
SKY_DUSK_MID,
SKY_NIGHT_MID,
max(sun_dir.z, 0)
max(pow(sun_dir.z, 0.2), 0)
),
SKY_DAY_MID,
max(-sun_dir.z, 0)
@ -119,7 +123,7 @@ vec3 get_sky_color(vec3 dir, float time_of_day, bool with_stars) {
mix(
SKY_DUSK_BOT,
SKY_NIGHT_BOT,
max(sun_dir.z, 0)
max(pow(sun_dir.z, 0.2), 0)
),
SKY_DAY_BOT,
max(-sun_dir.z, 0)
@ -135,12 +139,17 @@ vec3 get_sky_color(vec3 dir, float time_of_day, bool with_stars) {
max(dir.z, 0)
);
vec3 sun_halo_color = mix(
SUN_HALO_DUSK,
SUN_HALO_DAY,
max(-sun_dir.z, 0)
);
// Sun
const vec3 SUN_HALO_COLOR = vec3(1.5, 0.35, 0.0) * 0.3;
const vec3 SUN_SURF_COLOR = vec3(1.5, 0.9, 0.35) * 200.0;
vec3 sun_halo = pow(max(dot(dir, -sun_dir) + 0.1, 0.0), 8.0) * SUN_HALO_COLOR;
vec3 sun_halo = pow(max(dot(dir, -sun_dir) + 0.1, 0.0), 8.0) * sun_halo_color;
vec3 sun_surf = pow(max(dot(dir, -sun_dir) - 0.0045, 0.0), 1000.0) * SUN_SURF_COLOR;
vec3 sun_light = (sun_halo + sun_surf) * clamp(dir.z * 10.0, 0, 1);

View File

@ -160,6 +160,8 @@ vec3 hsv2rgb(vec3 c) {
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
const float FXAA_SCALE = 1.5;
void main() {
vec2 uv = (f_pos + 1.0) * 0.5;
@ -167,7 +169,7 @@ void main() {
uv = clamp(uv + vec2(sin(uv.y * 16.0 + tick.x), sin(uv.x * 24.0 + tick.x)) * 0.005, 0, 1);
}
vec4 fxaa_color = fxaa_apply(src_color, uv * screen_res.xy, screen_res.xy);
vec4 fxaa_color = fxaa_apply(src_color, uv * screen_res.xy * FXAA_SCALE, screen_res.xy * FXAA_SCALE);
//vec4 fxaa_color = texture(src_color, uv);
vec4 hsva_color = vec4(rgb2hsv(fxaa_color.rgb), fxaa_color.a);

View File

@ -16,8 +16,15 @@ const float RENDER_DIST = 112.0;
const float FADE_DIST = 32.0;
void main() {
vec3 light = get_sun_diffuse(f_norm, time_of_day.x) * f_light + light_at(f_pos, f_norm);
vec3 surf_color = f_col * light;
vec3 light, diffuse_light, ambient_light;
get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light);
float point_shadow = shadow_at(f_pos, f_norm);
diffuse_light *= f_light * point_shadow;
ambient_light *= f_light, point_shadow;
vec3 point_light = light_at(f_pos, f_norm);
light += point_light;
diffuse_light += point_light;
vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light);
float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x);
vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x, true);

View File

@ -27,6 +27,8 @@ void main() {
inst_mat[2] = inst_mat2;
inst_mat[3] = inst_mat3;
vec3 sprite_pos = (inst_mat * vec4(0, 0, 0, 1)).xyz;
f_pos = (inst_mat * vec4(v_pos * SCALE, 1)).xyz;
// Wind waving
@ -40,6 +42,11 @@ void main() {
f_col = srgb_to_linear(v_col) * srgb_to_linear(inst_col);
// Select glowing
if (select_pos.w > 0 && select_pos.xyz == floor(sprite_pos)) {
f_col *= 4.0;
}
f_light = 1.0;
gl_Position =

View File

@ -10,6 +10,7 @@ in float f_light;
layout (std140)
uniform u_locals {
vec3 model_offs;
float load_time;
};
out vec4 tgt_color;
@ -18,8 +19,15 @@ out vec4 tgt_color;
#include <light.glsl>
void main() {
vec3 light = get_sun_diffuse(f_norm, time_of_day.x) * f_light + light_at(f_pos, f_norm);
vec3 surf_color = f_col * light;
vec3 light, diffuse_light, ambient_light;
get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light);
float point_shadow = shadow_at(f_pos, f_norm);
diffuse_light *= f_light * point_shadow;
ambient_light *= f_light * point_shadow;
vec3 point_light = light_at(f_pos, f_norm);
light += point_light;
diffuse_light += point_light;
vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light);
float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x);
vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x, true);

View File

@ -9,6 +9,7 @@ in uint v_col_light;
layout (std140)
uniform u_locals {
vec3 model_offs;
float load_time;
};
out vec3 f_pos;
@ -26,6 +27,8 @@ void main() {
float((v_pos_norm >> 16) & 0x1FFFu)
) + model_offs;
f_pos.z *= min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0);
// TODO: last 3 bits in v_pos_norm should be a number between 0 and 5, rather than 0-2 and a direction.
uint norm_axis = (v_pos_norm >> 30) & 0x3u;

BIN
assets/voxygen/voxel/sprite/velorite/velorite.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/world/module/human/window_corner_ground.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/world/module/human/window_corner_upstairs.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/world/module/misc/well.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -250,6 +250,10 @@ impl Client {
self.postbox.send_message(ClientMsg::BreakBlock(pos));
}
pub fn collect_block(&mut self, pos: Vec3<i32>) {
self.postbox.send_message(ClientMsg::CollectBlock(pos));
}
/// Execute a single client tick, handle input and update the game state by the given duration.
#[allow(dead_code)]
pub fn tick(

View File

@ -1,3 +1,8 @@
use crate::{
comp,
effect::Effect,
terrain::{Block, BlockKind},
};
use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage;
@ -70,10 +75,38 @@ impl Armor {
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ConsumptionEffect {
Health(i32),
Xp(i32),
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Consumable {
Apple,
Potion,
Mushroom,
Velorite,
}
impl Consumable {
pub fn name(&self) -> &'static str {
match self {
Consumable::Apple => "apple",
Consumable::Potion => "potion",
Consumable::Mushroom => "mushroom",
Consumable::Velorite => "velorite",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Ingredient {
Flower,
Grass,
}
impl Ingredient {
pub fn name(&self) -> &'static str {
match self {
Ingredient::Flower => "flower",
Ingredient::Grass => "grass",
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
@ -93,9 +126,12 @@ pub enum Item {
health_bonus: i32,
},
Consumable {
effect: ConsumptionEffect,
kind: Consumable,
effect: Effect,
},
Ingredient {
kind: Ingredient,
},
Ingredient,
Debug(Debug),
}
@ -104,8 +140,8 @@ impl Item {
match self {
Item::Tool { kind, .. } => kind.name(),
Item::Armor { kind, .. } => kind.name(),
Item::Consumable { .. } => "<consumable>",
Item::Ingredient => "<ingredient>",
Item::Consumable { kind, .. } => kind.name(),
Item::Ingredient { kind } => kind.name(),
Item::Debug(_) => "Debugging item",
}
}
@ -115,7 +151,7 @@ impl Item {
Item::Tool { .. } => "tool",
Item::Armor { .. } => "armour",
Item::Consumable { .. } => "consumable",
Item::Ingredient => "ingredient",
Item::Ingredient { .. } => "ingredient",
Item::Debug(_) => "debug",
}
}
@ -123,6 +159,60 @@ impl Item {
pub fn description(&self) -> String {
format!("{} ({})", self.name(), self.category())
}
pub fn try_reclaim_from_block(block: Block) -> Option<Self> {
match block.kind() {
BlockKind::Apple => Some(Self::apple()),
BlockKind::Mushroom => Some(Self::mushroom()),
BlockKind::Velorite => Some(Self::velorite()),
BlockKind::BlueFlower => Some(Self::flower()),
BlockKind::PinkFlower => Some(Self::flower()),
BlockKind::PurpleFlower => Some(Self::flower()),
BlockKind::RedFlower => Some(Self::flower()),
BlockKind::WhiteFlower => Some(Self::flower()),
BlockKind::YellowFlower => Some(Self::flower()),
BlockKind::Sunflower => Some(Self::flower()),
BlockKind::LongGrass => Some(Self::grass()),
BlockKind::MediumGrass => Some(Self::grass()),
BlockKind::ShortGrass => Some(Self::grass()),
_ => None,
}
}
// General item constructors
pub fn apple() -> Self {
Item::Consumable {
kind: Consumable::Apple,
effect: Effect::Health(20, comp::HealthSource::Item),
}
}
pub fn mushroom() -> Self {
Item::Consumable {
kind: Consumable::Mushroom,
effect: Effect::Health(10, comp::HealthSource::Item),
}
}
pub fn velorite() -> Self {
Item::Consumable {
kind: Consumable::Mushroom,
effect: Effect::Xp(250),
}
}
pub fn flower() -> Self {
Item::Ingredient {
kind: Ingredient::Flower,
}
}
pub fn grass() -> Self {
Item::Ingredient {
kind: Ingredient::Grass,
}
}
}
impl Default for Item {

View File

@ -5,7 +5,6 @@ pub mod item;
pub use self::item::{Debug, Item, Tool};
use specs::{Component, HashMapStorage, NullStorage};
//use specs_idvs::IDVStorage;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Inventory {
@ -46,6 +45,10 @@ impl Inventory {
}
}
pub fn is_full(&self) -> bool {
self.slots.iter().all(|slot| slot.is_some())
}
/// Get content of a slot
pub fn get(&self, cell: usize) -> Option<&Item> {
self.slots.get(cell).and_then(Option::as_ref)

View File

@ -0,0 +1,22 @@
use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage;
use vek::*;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Waypoint {
pos: Vec3<f32>,
}
impl Waypoint {
pub fn new(pos: Vec3<f32>) -> Self {
Self { pos }
}
pub fn get_pos(&self) -> Vec3<f32> {
self.pos
}
}
impl Component for Waypoint {
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
}

View File

@ -6,6 +6,7 @@ mod controller;
mod inputs;
mod inventory;
mod last;
mod location;
mod phys;
mod player;
mod stats;
@ -20,7 +21,8 @@ pub use controller::{ControlEvent, Controller, MountState, Mounting};
pub use inputs::CanBuild;
pub use inventory::{item, Inventory, InventoryUpdate, Item};
pub use last::Last;
pub use phys::{ForceUpdate, Ori, PhysicsState, Pos, Scale, Vel};
pub use location::Waypoint;
pub use phys::{ForceUpdate, Mass, Ori, PhysicsState, Pos, Scale, Vel};
pub use player::Player;
pub use stats::{Equipment, Exp, HealthSource, Level, Stats};
pub use visual::LightEmitter;

View File

@ -34,6 +34,14 @@ impl Component for Scale {
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
}
// Mass
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct Mass(pub f32);
impl Component for Mass {
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
}
// PhysicsState
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct PhysicsState {

View File

@ -2,7 +2,7 @@ use crate::{comp, state::Uid};
use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage;
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum HealthSource {
Attack { by: Uid }, // TODO: Implement weapon
Suicide,
@ -10,6 +10,7 @@ pub enum HealthSource {
Revive,
Command,
LevelUp,
Item,
Unknown,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]

8
common/src/effect.rs Normal file
View File

@ -0,0 +1,8 @@
use crate::comp;
/// An effect that may be applied to an entity
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Effect {
Health(i32, comp::HealthSource),
Xp(i64),
}

View File

@ -16,6 +16,7 @@ extern crate log;
pub mod assets;
pub mod clock;
pub mod comp;
pub mod effect;
pub mod event;
pub mod figure;
pub mod msg;

View File

@ -19,6 +19,7 @@ pub enum ClientMsg {
SetViewDistance(u32),
BreakBlock(Vec3<i32>),
PlaceBlock(Vec3<i32>, Block),
CollectBlock(Vec3<i32>),
Ping,
Pong,
ChatMsg {

View File

@ -29,6 +29,7 @@ sphynx::sum_type! {
Scale(comp::Scale),
MountState(comp::MountState),
Mounting(comp::Mounting),
Mass(comp::Mass),
}
}
// Automatically derive From<T> for EcsCompPhantom
@ -48,6 +49,7 @@ sphynx::sum_type! {
Scale(PhantomData<comp::Scale>),
MountState(PhantomData<comp::MountState>),
Mounting(PhantomData<comp::Mounting>),
Mass(PhantomData<comp::Mass>),
}
}
impl sphynx::CompPacket for EcsCompPacket {

View File

@ -55,6 +55,15 @@ impl BlockChange {
self.blocks.insert(pos, block);
}
pub fn try_set(&mut self, pos: Vec3<i32>, block: Block) -> Option<()> {
if !self.blocks.contains_key(&pos) {
self.blocks.insert(pos, block);
Some(())
} else {
None
}
}
pub fn clear(&mut self) {
self.blocks.clear();
}
@ -122,6 +131,7 @@ impl State {
ecs.register_synced::<comp::Scale>();
ecs.register_synced::<comp::Mounting>();
ecs.register_synced::<comp::MountState>();
ecs.register_synced::<comp::Mass>();
// Register components send from clients -> server
ecs.register::<comp::Controller>();
@ -146,6 +156,7 @@ impl State {
ecs.register::<comp::InventoryUpdate>();
ecs.register::<comp::Inventory>();
ecs.register::<comp::Admin>();
ecs.register::<comp::Waypoint>();
// Register synced resources used by the ECS.
ecs.insert_synced(TimeOfDay(0.0));
@ -234,11 +245,16 @@ impl State {
self.ecs.write_resource()
}
/// Get a writable reference to this state's terrain.
/// Set a block in this state's terrain.
pub fn set_block(&mut self, pos: Vec3<i32>, block: Block) {
self.ecs.write_resource::<BlockChange>().set(pos, block);
}
/// Set a block in this state's terrain. Will return `None` if the block has already been modified this tick.
pub fn try_set_block(&mut self, pos: Vec3<i32>, block: Block) -> Option<()> {
self.ecs.write_resource::<BlockChange>().try_set(pos, block)
}
/// Removes every chunk of the terrain.
pub fn clear_terrain(&mut self) {
let keys = self

View File

@ -1,6 +1,6 @@
use {
crate::{
comp::{Body, Mounting, Ori, PhysicsState, Pos, Scale, Vel},
comp::{Body, Mass, Mounting, Ori, PhysicsState, Pos, Scale, Vel},
event::{EventBus, LocalEvent},
state::DeltaTime,
terrain::{Block, TerrainGrid},
@ -45,6 +45,7 @@ impl<'a> System<'a> for Sys {
Read<'a, DeltaTime>,
Read<'a, EventBus<LocalEvent>>,
ReadStorage<'a, Scale>,
ReadStorage<'a, Mass>,
ReadStorage<'a, Body>,
WriteStorage<'a, PhysicsState>,
WriteStorage<'a, Pos>,
@ -61,6 +62,7 @@ impl<'a> System<'a> for Sys {
dt,
event_bus,
scales,
masses,
bodies,
mut physics_states,
mut positions,
@ -320,9 +322,10 @@ impl<'a> System<'a> for Sys {
}
// Apply pushback
for (pos, scale, vel, _, _) in (
for (pos, scale, mass, vel, _, _) in (
&positions,
scales.maybe(),
masses.maybe(),
&mut velocities,
&bodies,
!&mountings,
@ -330,10 +333,18 @@ impl<'a> System<'a> for Sys {
.join()
{
let scale = scale.map(|s| s.0).unwrap_or(1.0);
for (pos_other, scale_other, _, _) in
(&positions, scales.maybe(), &bodies, !&mountings).join()
let mass = mass.map(|m| m.0).unwrap_or(scale);
for (pos_other, scale_other, mass_other, _, _) in (
&positions,
scales.maybe(),
masses.maybe(),
&bodies,
!&mountings,
)
.join()
{
let scale_other = scale_other.map(|s| s.0).unwrap_or(1.0);
let mass_other = mass_other.map(|m| m.0).unwrap_or(scale_other);
let diff = Vec2::<f32>::from(pos.0 - pos_other.0);
let collision_dist = 0.95 * (scale + scale_other);
@ -343,8 +354,9 @@ impl<'a> System<'a> for Sys {
&& pos.0.z + 1.6 * scale > pos_other.0.z
&& pos.0.z < pos_other.0.z + 1.6 * scale_other
{
vel.0 +=
Vec3::from(diff.normalized()) * (collision_dist - diff.magnitude()) * 1.0;
let force = (collision_dist - diff.magnitude()) * 2.0 * mass_other
/ (mass + mass_other);
vel.0 += Vec3::from(diff.normalized()) * force;
}
}
}

View File

@ -29,9 +29,17 @@ pub enum BlockKind {
Apple,
Mushroom,
Liana,
Velorite,
}
impl BlockKind {
pub fn is_tangible(&self) -> bool {
match self {
BlockKind::Air => false,
kind => !kind.is_fluid(),
}
}
pub fn is_air(&self) -> bool {
match self {
BlockKind::Air => true,
@ -54,6 +62,7 @@ impl BlockKind {
BlockKind::Apple => true,
BlockKind::Mushroom => true,
BlockKind::Liana => true,
BlockKind::Velorite => true,
_ => false,
}
}
@ -88,6 +97,7 @@ impl BlockKind {
BlockKind::Apple => false,
BlockKind::Mushroom => false,
BlockKind::Liana => false,
BlockKind::Velorite => false,
_ => true,
}
}
@ -118,6 +128,25 @@ impl BlockKind {
_ => true,
}
}
pub fn is_collectible(&self) -> bool {
match self {
BlockKind::BlueFlower => true,
BlockKind::PinkFlower => true,
BlockKind::PurpleFlower => true,
BlockKind::RedFlower => true,
BlockKind::WhiteFlower => true,
BlockKind::YellowFlower => true,
BlockKind::Sunflower => true,
BlockKind::LongGrass => true,
BlockKind::MediumGrass => true,
BlockKind::ShortGrass => true,
BlockKind::Apple => true,
BlockKind::Mushroom => true,
BlockKind::Velorite => true,
_ => false,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]

View File

@ -14,39 +14,44 @@ pub enum DynaError {
// V = Voxel
// S = Size (replace when const generics are a thing)
// M = Metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dyna<V: Vox, M> {
#[derive(Debug, Serialize, Deserialize)]
pub struct Dyna<V: Vox, M, A: Access = ColumnAccess> {
vox: Vec<V>,
meta: M,
sz: Vec3<u32>,
_phantom: std::marker::PhantomData<A>,
}
impl<V: Vox, M> Dyna<V, M> {
impl<V: Vox, M: Clone, A: Access> Clone for Dyna<V, M, A> {
fn clone(&self) -> Self {
Self {
vox: self.vox.clone(),
meta: self.meta.clone(),
sz: self.sz,
_phantom: std::marker::PhantomData,
}
}
}
impl<V: Vox, M, A: Access> Dyna<V, M, A> {
/// Used to transform a voxel position in the volume into its corresponding index
/// in the voxel array.
#[inline(always)]
fn idx_for(sz: Vec3<u32>, pos: Vec3<i32>) -> Option<usize> {
if pos.map(|e| e >= 0).reduce_and() && pos.map2(sz, |e, lim| e < lim as i32).reduce_and() {
Some(Self::idx_for_unchecked(sz, pos))
Some(A::idx(pos, sz))
} else {
None
}
}
/// Used to transform a voxel position in the volume into its corresponding index
/// in the voxel array.
#[inline(always)]
fn idx_for_unchecked(sz: Vec3<u32>, pos: Vec3<i32>) -> usize {
(pos.x * sz.y as i32 * sz.z as i32 + pos.y * sz.z as i32 + pos.z) as usize
}
}
impl<V: Vox, M> BaseVol for Dyna<V, M> {
impl<V: Vox, M, A: Access> BaseVol for Dyna<V, M, A> {
type Vox = V;
type Error = DynaError;
}
impl<V: Vox, M> SizedVol for Dyna<V, M> {
impl<V: Vox, M, A: Access> SizedVol for Dyna<V, M, A> {
#[inline(always)]
fn lower_bound(&self) -> Vec3<i32> {
Vec3::zero()
@ -58,7 +63,7 @@ impl<V: Vox, M> SizedVol for Dyna<V, M> {
}
}
impl<V: Vox, M> ReadVol for Dyna<V, M> {
impl<V: Vox, M, A: Access> ReadVol for Dyna<V, M, A> {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&V, DynaError> {
Self::idx_for(self.sz, pos)
@ -67,7 +72,7 @@ impl<V: Vox, M> ReadVol for Dyna<V, M> {
}
}
impl<V: Vox, M> WriteVol for Dyna<V, M> {
impl<V: Vox, M, A: Access> WriteVol for Dyna<V, M, A> {
#[inline(always)]
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), DynaError> {
Self::idx_for(self.sz, pos)
@ -77,7 +82,7 @@ impl<V: Vox, M> WriteVol for Dyna<V, M> {
}
}
impl<'a, V: Vox, M> IntoPosIterator for &'a Dyna<V, M> {
impl<'a, V: Vox, M, A: Access> IntoPosIterator for &'a Dyna<V, M, A> {
type IntoIter = DefaultPosIterator;
fn pos_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self::IntoIter {
@ -85,15 +90,15 @@ impl<'a, V: Vox, M> IntoPosIterator for &'a Dyna<V, M> {
}
}
impl<'a, V: Vox, M> IntoVolIterator<'a> for &'a Dyna<V, M> {
type IntoIter = DefaultVolIterator<'a, Dyna<V, M>>;
impl<'a, V: Vox, M, A: Access> IntoVolIterator<'a> for &'a Dyna<V, M, A> {
type IntoIter = DefaultVolIterator<'a, Dyna<V, M, A>>;
fn vol_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self::IntoIter {
Self::IntoIter::new(self, lower_bound, upper_bound)
}
}
impl<V: Vox + Clone, M> Dyna<V, M> {
impl<V: Vox + Clone, M, A: Access> Dyna<V, M, A> {
/// Create a new `Dyna` with the provided dimensions and all voxels filled with duplicates of
/// the provided voxel.
pub fn filled(sz: Vec3<u32>, vox: V, meta: M) -> Self {
@ -101,6 +106,7 @@ impl<V: Vox + Clone, M> Dyna<V, M> {
vox: vec![vox; sz.product() as usize],
meta,
sz,
_phantom: std::marker::PhantomData,
}
}
@ -114,3 +120,15 @@ impl<V: Vox + Clone, M> Dyna<V, M> {
&mut self.meta
}
}
pub trait Access {
fn idx(pos: Vec3<i32>, sz: Vec3<u32>) -> usize;
}
pub struct ColumnAccess;
impl Access for ColumnAccess {
fn idx(pos: Vec3<i32>, sz: Vec3<u32>) -> usize {
(pos.x * sz.y as i32 * sz.z as i32 + pos.y * sz.z as i32 + pos.z) as usize
}
}

View File

@ -189,14 +189,21 @@ lazy_static! {
"explosion",
"{}",
"/explosion <radius> : Explodes the ground around you",
false,
true,
handle_explosion,
),
ChatCommand::new(
"waypoint",
"{}",
"/waypoint : Set your waypoint to your current position",
true,
handle_waypoint,
),
ChatCommand::new(
"adminify",
"{}",
"/adminify <playername> : Temporarily gives a player admin permissions or removes them",
true,
false, // TODO: NO
handle_adminify,
),
ChatCommand::new(
@ -699,7 +706,7 @@ fn handle_lantern(server: &mut Server, entity: EcsEntity, args: String, action:
.write_storage::<comp::LightEmitter>()
.get_mut(entity)
{
light.strength = s.max(0.1).min(20.0);
light.strength = s.max(0.1).min(10.0);
server.clients.notify(
entity,
ServerMsg::private(String::from("You played with flame strength.")),
@ -727,9 +734,9 @@ fn handle_lantern(server: &mut Server, entity: EcsEntity, args: String, action:
offset: Vec3::new(0.5, 0.2, 0.8),
col: Rgb::new(1.0, 0.75, 0.3),
strength: if let Some(s) = opt_s {
s.max(0.0).min(20.0)
s.max(0.0).min(10.0)
} else {
6.0
3.0
},
},
);
@ -757,6 +764,25 @@ fn handle_explosion(server: &mut Server, entity: EcsEntity, args: String, action
}
}
fn handle_waypoint(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) {
match server.state.read_component_cloned::<comp::Pos>(entity) {
Some(pos) => {
let _ = server
.state
.ecs()
.write_storage::<comp::Waypoint>()
.insert(entity, comp::Waypoint::new(pos.0));
server
.clients
.notify(entity, ServerMsg::private(String::from("Waypoint set!")));
}
None => server.clients.notify(
entity,
ServerMsg::private(String::from("You have no position!")),
),
}
}
fn handle_adminify(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
if let Ok(alias) = scan_fmt!(&args, action.arg_fmt, String) {
let ecs = server.state.ecs();

View File

@ -19,6 +19,7 @@ use crate::{
};
use common::{
comp,
effect::Effect,
event::{EventBus, ServerEvent},
msg::{ClientMsg, ClientState, RequestStateError, ServerError, ServerInfo, ServerMsg},
net::PostOffice,
@ -355,6 +356,11 @@ impl Server {
ServerEvent::Respawn(entity) => {
// Only clients can respawn
if let Some(client) = clients.get_mut(&entity) {
let respawn_point = state
.read_component_cloned::<comp::Waypoint>(entity)
.map(|wp| wp.get_pos())
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);
client.allow_state(ClientState::Character);
state
.ecs_mut()
@ -365,7 +371,7 @@ impl Server {
.ecs_mut()
.write_storage::<comp::Pos>()
.get_mut(entity)
.map(|pos| pos.0.z += 20.0);
.map(|pos| pos.0 = respawn_point);
let _ = state
.ecs_mut()
.write_storage()
@ -765,7 +771,6 @@ impl Server {
let mut new_chat_msgs = Vec::new();
let mut disconnected_clients = Vec::new();
let mut requested_chunks = Vec::new();
let mut modified_blocks = Vec::new();
let mut dropped_items = Vec::new();
self.clients.remove_if(|entity, client| {
@ -867,6 +872,17 @@ impl Server {
stats.equipment.main = item;
}
}
Some(comp::Item::Consumable { effect, .. }) => {
state.apply_effect(entity, effect);
}
Some(item) => {
// Re-insert it if unused
let _ = state
.ecs()
.write_storage::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.insert(x, item));
}
_ => {}
}
state.write_component(entity, comp::InventoryUpdate);
@ -1003,7 +1019,7 @@ impl Server {
.get(entity)
.is_some()
{
modified_blocks.push((pos, Block::empty()));
state.set_block(pos, Block::empty());
}
}
ClientMsg::PlaceBlock(pos, block) => {
@ -1013,7 +1029,25 @@ impl Server {
.get(entity)
.is_some()
{
modified_blocks.push((pos, block));
state.try_set_block(pos, block);
}
}
ClientMsg::CollectBlock(pos) => {
let block = state.terrain().get(pos).ok().copied();
if let Some(block) = block {
if block.is_collectible()
&& state
.ecs()
.read_storage::<comp::Inventory>()
.get(entity)
.map(|inv| !inv.is_full())
.unwrap_or(false)
{
if state.try_set_block(pos, Block::empty()).is_some() {
comp::Item::try_reclaim_from_block(block)
.map(|item| state.give_item(entity, item));
}
}
}
}
ClientMsg::TerrainChunkRequest { key } => match client.client_state {
@ -1117,10 +1151,6 @@ impl Server {
self.generate_chunk(entity, key);
}
for (pos, block) in modified_blocks {
self.state.set_block(pos, block);
}
for (pos, ori, item) in dropped_items {
let vel = ori.0.normalized() * 5.0
+ Vec3::unit_z() * 10.0
@ -1384,3 +1414,40 @@ impl Drop for Server {
self.clients.notify_registered(ServerMsg::Shutdown);
}
}
trait StateExt {
fn give_item(&mut self, entity: EcsEntity, item: comp::Item) -> bool;
fn apply_effect(&mut self, entity: EcsEntity, effect: Effect);
}
impl StateExt for State {
fn give_item(&mut self, entity: EcsEntity, item: comp::Item) -> bool {
let success = self
.ecs()
.write_storage::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.push(item).is_none())
.unwrap_or(false);
if success {
self.write_component(entity, comp::InventoryUpdate);
}
success
}
fn apply_effect(&mut self, entity: EcsEntity, effect: Effect) {
match effect {
Effect::Health(hp, source) => {
self.ecs_mut()
.write_storage::<comp::Stats>()
.get_mut(entity)
.map(|stats| stats.health.change_by(hp, source));
}
Effect::Xp(xp) => {
self.ecs_mut()
.write_storage::<comp::Stats>()
.get_mut(entity)
.map(|stats| stats.exp.change_by(xp));
}
}
}
}

View File

@ -1,10 +1,14 @@
use super::{img_ids::Imgs, Event as HudEvent, Fonts, TEXT_COLOR};
use super::{
img_ids::{Imgs, ImgsRot},
Event as HudEvent, Fonts, TEXT_COLOR,
};
use crate::ui::{ImageFrame, Tooltip, TooltipManager, Tooltipable};
use client::Client;
use conrod_core::{
color,
position::Relative,
widget::{self, Button, Image, Rectangle /*, Scrollbar*/},
widget_ids, /*Color, Colorable,*/ Labelable, Positionable, Sizeable, Widget, WidgetCommon,
widget_ids, Color, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
};
widget_ids! {
@ -21,6 +25,7 @@ widget_ids! {
map_title,
inv_slots[],
items[],
tooltip[],
}
}
@ -31,15 +36,25 @@ pub struct Bag<'a> {
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
}
impl<'a> Bag<'a> {
pub fn new(client: &'a Client, imgs: &'a Imgs, fonts: &'a Fonts) -> Self {
pub fn new(
client: &'a Client,
imgs: &'a Imgs,
fonts: &'a Fonts,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
) -> Self {
Self {
client,
imgs,
fonts,
common: widget::CommonBuilder::default(),
rot_imgs,
tooltip_manager,
}
}
}
@ -82,6 +97,23 @@ impl<'a> Widget for Bag<'a> {
Some(inv) => inv,
None => return None,
};
// Tooltips
let item_tooltip = Tooltip::new({
// Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl]
let edge = &self.rot_imgs.tt_side;
let corner = &self.rot_imgs.tt_corner;
ImageFrame::new(
[edge.cw180, edge.none, edge.cw270, edge.cw90],
[corner.none, corner.cw270, corner.cw90, corner.cw180],
Color::Rgba(0.08, 0.07, 0.04, 1.0),
5.0,
)
})
.title_font_size(15)
.desc_font_size(10)
.title_text_color(TEXT_COLOR)
.desc_text_color(TEXT_COLOR);
// Bag parts
Image::new(self.imgs.bag_bot)
@ -133,7 +165,7 @@ impl<'a> Widget for Bag<'a> {
let is_selected = Some(i) == state.selected_slot;
// Slot
if Button::image(self.imgs.inv_slot)
let slot_widget = Button::image(self.imgs.inv_slot)
.top_left_with_margins_on(
state.ids.inv_alignment,
4.0 + y as f64 * (40.0 + 4.0),
@ -146,10 +178,23 @@ impl<'a> Widget for Bag<'a> {
} else {
color::DARK_YELLOW
})
.floating(true)
.set(state.ids.inv_slots[i], ui)
.was_clicked()
{
.floating(true);
let slot_widget = if let Some(item) = item {
slot_widget
.with_tooltip(
self.tooltip_manager,
&item.description(),
&item.category(),
&item_tooltip,
)
.set(state.ids.inv_slots[i], ui)
} else {
slot_widget.set(state.ids.inv_slots[i], ui)
};
// Item
if slot_widget.was_clicked() {
let selected_slot = match state.selected_slot {
Some(a) => {
if a == i {
@ -164,7 +209,6 @@ impl<'a> Widget for Bag<'a> {
};
state.update(|s| s.selected_slot = selected_slot);
}
// Item
if item.is_some() {
Button::image(self.imgs.potion_red) // TODO: Insert variable image depending on the item displayed in that slot

View File

@ -1,5 +1,16 @@
use crate::ui::img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic, VoxelMs9Graphic};
// TODO: Combine with image_ids, see macro definition
rotation_image_ids! {
pub struct ImgsRot {
<VoxelGraphic>
// Tooltip Test
tt_side: "voxygen/element/frames/tt_test_edge",
tt_corner: "voxygen/element/frames/tt_test_corner_tr",
}
}
image_ids! {
pub struct Imgs {
<VoxelGraphic>

View File

@ -12,6 +12,7 @@ mod skillbar;
mod social;
mod spell;
use crate::hud::img_ids::ImgsRot;
pub use settings_window::ScaleChange;
use bag::Bag;
@ -365,6 +366,7 @@ pub struct Hud {
ids: Ids,
imgs: Imgs,
fonts: Fonts,
rot_imgs: ImgsRot,
new_messages: VecDeque<ClientEvent>,
inventory_space: usize,
show: Show,
@ -385,12 +387,15 @@ impl Hud {
let ids = Ids::new(ui.id_generator());
// Load images.
let imgs = Imgs::load(&mut ui).expect("Failed to load images!");
// Load rotation images.
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load rot images!");
// Load fonts.
let fonts = Fonts::load(&mut ui).expect("Failed to load fonts!");
Self {
ui,
imgs,
rot_imgs,
fonts,
ids,
new_messages: VecDeque::new(),
@ -428,7 +433,7 @@ impl Hud {
debug_info: DebugInfo,
) -> Vec<Event> {
let mut events = Vec::new();
let ref mut ui_widgets = self.ui.set_widgets().0;
let (ref mut ui_widgets, ref mut tooltip_manager) = self.ui.set_widgets();
let version = format!("{}-{}", env!("CARGO_PKG_VERSION"), common::util::GIT_HASH);
@ -723,7 +728,15 @@ impl Hud {
// Bag contents
if self.show.bag {
match Bag::new(client, &self.imgs, &self.fonts).set(self.ids.bag, ui_widgets) {
match Bag::new(
client,
&self.imgs,
&self.fonts,
&self.rot_imgs,
tooltip_manager,
)
.set(self.ids.bag, ui_widgets)
{
Some(bag::Event::HudEvent(event)) => events.push(event),
Some(bag::Event::Close) => {
self.show.bag(false);

View File

@ -6,7 +6,7 @@ use crate::{
},
render::{
create_pp_mesh, create_skybox_mesh, Consts, FigurePipeline, Globals, Light, Model,
PostProcessLocals, PostProcessPipeline, Renderer, SkyboxLocals, SkyboxPipeline,
PostProcessLocals, PostProcessPipeline, Renderer, Shadow, SkyboxLocals, SkyboxPipeline,
},
scene::{
camera::{Camera, CameraMode},
@ -36,6 +36,7 @@ struct PostProcess {
pub struct Scene {
globals: Consts<Globals>,
lights: Consts<Light>,
shadows: Consts<Shadow>,
camera: Camera,
skybox: Skybox,
@ -57,6 +58,7 @@ impl Scene {
Self {
globals: renderer.create_consts(&[Globals::default()]).unwrap(),
lights: renderer.create_consts(&[Light::default(); 32]).unwrap(),
shadows: renderer.create_consts(&[Shadow::default(); 32]).unwrap(),
camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson),
skybox: Skybox {
@ -120,7 +122,8 @@ impl Scene {
.set_orientation(Vec3::new(client.state().get_time() as f32 * 0.0, 0.0, 0.0));
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(client);
const CHAR_SELECT_TIME_OF_DAY: f32 = 80000.0; // 12*3600 seconds
const VD: f32 = 115.0; //View Distance
const TIME: f64 = 36000.0; // hours*3600 seconds
if let Err(err) = renderer.update_consts(
&mut self.globals,
&[Globals::new(
@ -128,12 +131,14 @@ impl Scene {
proj_mat,
cam_pos,
self.camera.get_focus_pos(),
CHAR_SELECT_TIME_OF_DAY,
55800.0,
VD,
TIME,
client.state().get_time(),
renderer.get_resolution(),
0,
0,
BlockKind::Air,
None,
)],
) {
error!("Renderer failed to update: {:?}", err);
@ -192,6 +197,7 @@ impl Scene {
self.figure_state.locals(),
self.figure_state.bone_consts(),
&self.lights,
&self.shadows,
);
renderer.render_figure(
@ -200,6 +206,7 @@ impl Scene {
self.backdrop_state.locals(),
self.backdrop_state.bone_consts(),
&self.lights,
&self.shadows,
);
renderer.render_post_process(

View File

@ -5,7 +5,7 @@ use crate::{
use common::{
figure::Segment,
util::{linear_to_srgb, srgb_to_linear},
vol::{IntoFullVolIterator, Vox},
vol::{IntoFullVolIterator, ReadVol, Vox},
};
use vek::*;
@ -32,17 +32,35 @@ impl Meshable<FigurePipeline, FigurePipeline> for Segment {
self,
pos,
offs + pos.map(|e| e as f32),
col,
&[[[Some(col); 3]; 3]; 3],
|origin, norm, col, ao, light| {
FigureVertex::new(
origin,
norm,
linear_to_srgb(srgb_to_linear(col) * ao * light),
linear_to_srgb(srgb_to_linear(col) * light.min(ao)),
0,
)
},
true,
&[[[1.0; 3]; 3]; 3],
&{
let mut ls = [[[0.0; 3]; 3]; 3];
for x in 0..3 {
for y in 0..3 {
for z in 0..3 {
ls[z][y][x] = if self
.get(pos + Vec3::new(x as i32, y as i32, z as i32) - 1)
.map(|v| v.is_empty())
.unwrap_or(true)
{
1.0
} else {
0.0
};
}
}
}
ls
},
|vox| vox.is_empty(),
|vox| !vox.is_empty(),
);
@ -73,7 +91,7 @@ impl Meshable<SpritePipeline, SpritePipeline> for Segment {
self,
pos,
offs + pos.map(|e| e as f32),
col,
&[[[Some(col); 3]; 3]; 3],
|origin, norm, col, ao, light| {
SpriteVertex::new(
origin,

View File

@ -3,24 +3,124 @@ use crate::{
render::{self, FluidPipeline, Mesh, TerrainPipeline},
};
use common::{
terrain::{Block, BlockKind},
vol::{ReadVol, RectRasterableVol},
terrain::Block,
vol::{ReadVol, RectRasterableVol, Vox},
volumes::vol_grid_2d::VolGrid2d,
};
use hashbrown::{HashMap, HashSet};
use std::fmt::Debug;
use vek::*;
type TerrainVertex = <TerrainPipeline as render::Pipeline>::Vertex;
type FluidVertex = <FluidPipeline as render::Pipeline>::Vertex;
fn block_shadow_density(kind: BlockKind) -> (f32, f32) {
// (density, cap)
match kind {
BlockKind::Normal => (0.085, 0.3),
BlockKind::Dense => (0.3, 0.0),
BlockKind::Water => (0.15, 0.0),
kind if kind.is_air() => (0.0, 0.0),
_ => (1.0, 0.0),
const DIRS: [Vec2<i32>; 4] = [
Vec2 { x: 1, y: 0 },
Vec2 { x: 0, y: 1 },
Vec2 { x: -1, y: 0 },
Vec2 { x: 0, y: -1 },
];
const DIRS_3D: [Vec3<i32>; 6] = [
Vec3 { x: 1, y: 0, z: 0 },
Vec3 { x: 0, y: 1, z: 0 },
Vec3 { x: 0, y: 0, z: 1 },
Vec3 { x: -1, y: 0, z: 0 },
Vec3 { x: 0, y: -1, z: 0 },
Vec3 { x: 0, y: 0, z: -1 },
];
fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
bounds: Aabb<i32>,
vol: &VolGrid2d<V>,
) -> impl Fn(Vec3<i32>) -> f32 {
let sunlight = 24;
let outer = Aabb {
min: bounds.min - sunlight,
max: bounds.max + sunlight,
};
let mut voids = HashMap::new();
let mut rays = vec![outer.size().d; outer.size().product() as usize];
for x in 0..outer.size().w {
for y in 0..outer.size().h {
let mut outside = true;
for z in (0..outer.size().d).rev() {
let block = vol
.get(outer.min + Vec3::new(x, y, z))
.ok()
.copied()
.unwrap_or(Block::empty());
if !block.is_air() && outside {
rays[(outer.size().w * y + x) as usize] = z;
outside = false;
}
if (block.is_air() || block.is_fluid()) && !outside {
voids.insert(Vec3::new(x, y, z), None);
}
}
}
}
let mut opens = HashSet::new();
'voids: for (pos, l) in &mut voids {
for dir in &DIRS {
let col = Vec2::<i32>::from(*pos) + dir;
if pos.z
> *rays
.get(((outer.size().w * col.y) + col.x) as usize)
.unwrap_or(&0)
{
*l = Some(sunlight - 1);
opens.insert(*pos);
continue 'voids;
}
}
if pos.z
>= *rays
.get(((outer.size().w * pos.y) + pos.x) as usize)
.unwrap_or(&0)
{
*l = Some(sunlight - 1);
opens.insert(*pos);
}
}
while opens.len() > 0 {
let mut new_opens = HashSet::new();
for open in &opens {
let parent_l = voids[open].unwrap_or(0);
for dir in &DIRS_3D {
let other = *open + *dir;
if !opens.contains(&other) {
if let Some(l) = voids.get_mut(&other) {
if l.unwrap_or(0) < parent_l - 1 {
new_opens.insert(other);
}
*l = Some(parent_l - 1);
}
}
}
}
opens = new_opens;
}
move |wpos| {
let pos = wpos - outer.min;
rays.get(((outer.size().w * pos.y) + pos.x) as usize)
.and_then(|ray| if pos.z > *ray { Some(1.0) } else { None })
.or_else(|| {
if let Some(Some(l)) = voids.get(&pos) {
Some(*l as f32 / sunlight as f32)
} else {
None
}
})
.unwrap_or(0.0)
}
}
@ -38,85 +138,98 @@ impl<V: RectRasterableVol<Vox = Block> + ReadVol + Debug> Meshable<TerrainPipeli
let mut opaque_mesh = Mesh::new();
let mut fluid_mesh = Mesh::new();
let light = calc_light(range, self);
for x in range.min.x + 1..range.max.x - 1 {
for y in range.min.y + 1..range.max.y - 1 {
let mut neighbour_light = [[[1.0f32; 3]; 3]; 3];
let mut lights = [[[0.0; 3]; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
lights[k][j][i] = light(
Vec3::new(x, y, range.min.z)
+ Vec3::new(i as i32, j as i32, k as i32)
- 1,
);
}
}
}
for z in (range.min.z..range.max.z).rev() {
let get_color = |pos| {
self.get(pos)
.ok()
.filter(|vox| vox.is_opaque())
.and_then(|vox| vox.get_color())
.map(|col| col.map(|e| e as f32 / 255.0))
};
let mut colors = [[[None; 3]; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
colors[k][j][i] = get_color(
Vec3::new(x, y, range.min.z)
+ Vec3::new(i as i32, j as i32, k as i32)
- 1,
);
}
}
}
for z in range.min.z..range.max.z {
let pos = Vec3::new(x, y, z);
let offs = (pos - (range.min + 1) * Vec3::new(1, 1, 0)).map(|e| e as f32);
lights[0] = lights[1];
lights[1] = lights[2];
colors[0] = colors[1];
colors[1] = colors[2];
for i in 0..3 {
for j in 0..3 {
lights[2][j][i] = light(pos + Vec3::new(i as i32, j as i32, 2) - 1);
}
}
for i in 0..3 {
for j in 0..3 {
colors[2][j][i] = get_color(pos + Vec3::new(i as i32, j as i32, 2) - 1);
}
}
let block = self.get(pos).ok();
// Create mesh polygons
if let Some(col) = block
.filter(|vox| vox.is_opaque())
.and_then(|vox| vox.get_color())
{
let col = col.map(|e| e as f32 / 255.0);
if block.map(|vox| vox.is_opaque()).unwrap_or(false) {
vol::push_vox_verts(
&mut opaque_mesh,
self,
pos,
offs,
col,
&colors,
|pos, norm, col, ao, light| {
TerrainVertex::new(pos, norm, col, light * ao)
TerrainVertex::new(pos, norm, col, light.min(ao))
},
false,
&neighbour_light,
&lights,
|vox| !vox.is_opaque(),
|vox| vox.is_opaque(),
);
} else if let Some(col) = block
.filter(|vox| vox.is_fluid())
.and_then(|vox| vox.get_color())
{
let col = col.map(|e| e as f32 / 255.0);
} else if block.map(|vox| vox.is_fluid()).unwrap_or(false) {
vol::push_vox_verts(
&mut fluid_mesh,
self,
pos,
offs,
col,
&colors,
|pos, norm, col, ao, light| {
FluidVertex::new(pos, norm, col, light * ao, 0.3)
FluidVertex::new(pos, norm, col, light.min(ao), 0.3)
},
false,
&neighbour_light,
&lights,
|vox| vox.is_air(),
|vox| vox.is_opaque(),
);
}
// Shift lighting
neighbour_light[2] = neighbour_light[1];
neighbour_light[1] = neighbour_light[0];
// Accumulate shade under opaque blocks
for i in 0..3 {
for j in 0..3 {
let (density, cap) = self
.get(pos + Vec3::new(i as i32 - 1, j as i32 - 1, -1))
.ok()
.map(|vox| block_shadow_density(vox.kind()))
.unwrap_or((0.0, 0.0));
neighbour_light[0][i][j] = (neighbour_light[0][i][j] * (1.0 - density))
.max(cap.min(neighbour_light[1][i][j]));
}
}
// Spread light
neighbour_light[0] = [[neighbour_light[0]
.iter()
.map(|col| col.iter())
.flatten()
.copied()
.fold(0.0, |a, x| a + x)
/ 9.0; 3]; 3];
}
}
}

View File

@ -29,12 +29,15 @@ fn get_ao_quad<V: ReadVol>(
.unwrap_or(false),
);
let darkness = darknesses
.iter()
.map(|x| x.iter().map(|y| y.iter()))
.flatten()
.flatten()
.fold(0.0, |a: f32, x| a.max(*x));
let mut darkness = 0.0;
for x in 0..2 {
for y in 0..2 {
let dark_pos = shift + offs[0] * x + offs[1] * y + 1;
darkness += darknesses[dark_pos.z as usize][dark_pos.y as usize]
[dark_pos.x as usize]
/ 4.0;
}
}
(
darkness,
@ -57,34 +60,85 @@ fn get_ao_quad<V: ReadVol>(
.collect::<Vec4<(f32, f32)>>()
}
#[allow(unsafe_code)]
fn get_col_quad<V: ReadVol>(
_vol: &V,
_pos: Vec3<i32>,
_shift: Vec3<i32>,
dirs: &[Vec3<i32>],
cols: &[[[Option<Rgb<f32>>; 3]; 3]; 3],
_is_opaque: impl Fn(&V::Vox) -> bool,
) -> Vec4<Rgb<f32>> {
dirs.windows(2)
.map(|offs| {
let primary_col = cols[1][1][1].unwrap_or(Rgb::zero());
let mut color = Rgb::zero();
let mut total = 0.0;
for x in 0..2 {
for y in 0..2 {
let col_pos = offs[0] * x + offs[1] * y + 1;
if let Some(col) = unsafe {
cols.get_unchecked(col_pos.z as usize)
.get_unchecked(col_pos.y as usize)
.get_unchecked(col_pos.x as usize)
} {
if Vec3::<f32>::from(primary_col).distance_squared(Vec3::from(*col))
< 0.25 * 0.25
{
color += *col;
total += 1.0;
}
}
}
}
color / total
})
.collect()
}
// Utility function
fn create_quad<P: Pipeline, F: Fn(Vec3<f32>, Vec3<f32>, Rgb<f32>, f32, f32) -> P::Vertex>(
origin: Vec3<f32>,
unit_x: Vec3<f32>,
unit_y: Vec3<f32>,
norm: Vec3<f32>,
col: Rgb<f32>,
cols: Vec4<Rgb<f32>>,
darkness_ao: Vec4<(f32, f32)>,
vcons: &F,
) -> Quad<P> {
let darkness = darkness_ao.map(|e| e.0);
let ao = darkness_ao.map(|e| e.1);
let ao_map = ao.map(|e| 0.05 + e.powf(1.6) * 0.95);
let ao_map = ao.map(|e| e); //0.05 + e.powf(1.2) * 0.95);
if ao[0].min(ao[2]) < ao[1].min(ao[3]) {
if ao[0].min(ao[2]).min(darkness[0]).min(darkness[2])
< ao[1].min(ao[3]).min(darkness[1]).min(darkness[3])
{
Quad::new(
vcons(origin + unit_y, norm, col, darkness[3], ao_map[3]),
vcons(origin, norm, col, darkness[0], ao_map[0]),
vcons(origin + unit_x, norm, col, darkness[1], ao_map[1]),
vcons(origin + unit_x + unit_y, norm, col, darkness[2], ao_map[2]),
vcons(origin + unit_y, norm, cols[3], darkness[3], ao_map[3]),
vcons(origin, norm, cols[0], darkness[0], ao_map[0]),
vcons(origin + unit_x, norm, cols[1], darkness[1], ao_map[1]),
vcons(
origin + unit_x + unit_y,
norm,
cols[2],
darkness[2],
ao_map[2],
),
)
} else {
Quad::new(
vcons(origin, norm, col, darkness[0], ao_map[0]),
vcons(origin + unit_x, norm, col, darkness[1], ao_map[1]),
vcons(origin + unit_x + unit_y, norm, col, darkness[2], ao_map[2]),
vcons(origin + unit_y, norm, col, darkness[3], ao_map[3]),
vcons(origin, norm, cols[0], darkness[0], ao_map[0]),
vcons(origin + unit_x, norm, cols[1], darkness[1], ao_map[1]),
vcons(
origin + unit_x + unit_y,
norm,
cols[2],
darkness[2],
ao_map[2],
),
vcons(origin + unit_y, norm, cols[3], darkness[3], ao_map[3]),
)
}
}
@ -94,7 +148,7 @@ pub fn push_vox_verts<V: ReadVol, P: Pipeline>(
vol: &V,
pos: Vec3<i32>,
offs: Vec3<f32>,
col: Rgb<f32>,
cols: &[[[Option<Rgb<f32>>; 3]; 3]; 3],
vcons: impl Fn(Vec3<f32>, Vec3<f32>, Rgb<f32>, f32, f32) -> P::Vertex,
error_makes_face: bool,
darknesses: &[[[f32; 3]; 3]; 3],
@ -114,7 +168,14 @@ pub fn push_vox_verts<V: ReadVol, P: Pipeline>(
Vec3::unit_z(),
Vec3::unit_y(),
-Vec3::unit_x(),
col,
get_col_quad(
vol,
pos,
-Vec3::unit_x(),
&[-z, -y, z, y, -z],
cols,
&is_opaque,
),
get_ao_quad(
vol,
pos,
@ -137,7 +198,14 @@ pub fn push_vox_verts<V: ReadVol, P: Pipeline>(
Vec3::unit_y(),
Vec3::unit_z(),
Vec3::unit_x(),
col,
get_col_quad(
vol,
pos,
Vec3::unit_x(),
&[-y, -z, y, z, -y],
cols,
&is_opaque,
),
get_ao_quad(
vol,
pos,
@ -160,7 +228,14 @@ pub fn push_vox_verts<V: ReadVol, P: Pipeline>(
Vec3::unit_x(),
Vec3::unit_z(),
-Vec3::unit_y(),
col,
get_col_quad(
vol,
pos,
-Vec3::unit_y(),
&[-x, -z, x, z, -x],
cols,
&is_opaque,
),
get_ao_quad(
vol,
pos,
@ -183,7 +258,14 @@ pub fn push_vox_verts<V: ReadVol, P: Pipeline>(
Vec3::unit_z(),
Vec3::unit_x(),
Vec3::unit_y(),
col,
get_col_quad(
vol,
pos,
Vec3::unit_y(),
&[-z, -x, z, x, -z],
cols,
&is_opaque,
),
get_ao_quad(
vol,
pos,
@ -206,7 +288,14 @@ pub fn push_vox_verts<V: ReadVol, P: Pipeline>(
Vec3::unit_y(),
Vec3::unit_x(),
-Vec3::unit_z(),
col,
get_col_quad(
vol,
pos,
-Vec3::unit_z(),
&[-y, -x, y, x, -y],
cols,
&is_opaque,
),
get_ao_quad(
vol,
pos,
@ -229,7 +318,14 @@ pub fn push_vox_verts<V: ReadVol, P: Pipeline>(
Vec3::unit_x(),
Vec3::unit_y(),
Vec3::unit_z(),
col,
get_col_quad(
vol,
pos,
Vec3::unit_z(),
&[-x, -y, x, y, -x],
cols,
&is_opaque,
),
get_ao_quad(
vol,
pos,

View File

@ -26,7 +26,7 @@ pub use self::{
create_quad as create_ui_quad, create_tri as create_ui_tri, Locals as UiLocals,
Mode as UiMode, UiPipeline,
},
Globals, Light,
Globals, Light, Shadow,
},
renderer::{Renderer, TgtColorFmt, TgtDepthFmt, WinColorFmt, WinDepthFmt},
texture::Texture,

View File

@ -1,6 +1,6 @@
use super::{
super::{util::arr_to_mat, Pipeline, TgtColorFmt, TgtDepthFmt},
Globals, Light,
Globals, Light, Shadow,
};
use gfx::{
self,
@ -38,6 +38,7 @@ gfx_defines! {
globals: gfx::ConstantBuffer<Globals> = "u_globals",
bones: gfx::ConstantBuffer<BoneData> = "u_bones",
lights: gfx::ConstantBuffer<Light> = "u_lights",
shadows: gfx::ConstantBuffer<Shadow> = "u_shadows",
tgt_color: gfx::RenderTarget<TgtColorFmt> = "tgt_color",
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::LESS_EQUAL_WRITE,

View File

@ -1,6 +1,6 @@
use super::{
super::{Pipeline, TerrainLocals, TgtColorFmt, TgtDepthFmt},
Globals, Light,
Globals, Light, Shadow,
};
use gfx::{
self,
@ -27,6 +27,7 @@ gfx_defines! {
locals: gfx::ConstantBuffer<TerrainLocals> = "u_locals",
globals: gfx::ConstantBuffer<Globals> = "u_globals",
lights: gfx::ConstantBuffer<Light> = "u_lights",
shadows: gfx::ConstantBuffer<Shadow> = "u_shadows",
tgt_color: gfx::BlendTarget<TgtColorFmt> = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA),
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::LESS_EQUAL_TEST,

View File

@ -28,14 +28,19 @@ gfx_defines! {
time_of_day: [f32; 4] = "time_of_day", // TODO: Make this f64.
tick: [f32; 4] = "tick",
screen_res: [f32; 4] = "screen_res",
light_count: [u32; 4] = "light_count",
light_shadow_count: [u32; 4] = "light_shadow_count",
medium: [u32; 4] = "medium",
select_pos: [i32; 4] = "select_pos",
}
constant Light {
pos: [f32; 4] = "light_pos",
col: [f32; 4] = "light_col",
}
constant Shadow {
pos_radius: [f32; 4] = "shadow_pos_radius",
}
}
impl Globals {
@ -50,7 +55,9 @@ impl Globals {
tick: f64,
screen_res: Vec2<u16>,
light_count: usize,
shadow_count: usize,
medium: BlockKind,
select_pos: Option<Vec3<i32>>,
) -> Self {
Self {
view_mat: arr_to_mat(view_mat.into_col_array()),
@ -61,8 +68,12 @@ impl Globals {
time_of_day: [time_of_day as f32; 4],
tick: [tick as f32; 4],
screen_res: Vec4::from(screen_res.map(|e| e as f32)).into_array(),
light_count: [light_count as u32; 4],
light_shadow_count: [light_count as u32, shadow_count as u32, 0, 0],
medium: [if medium.is_fluid() { 1 } else { 0 }; 4],
select_pos: select_pos
.map(|sp| Vec4::from(sp) + Vec4::unit_w())
.unwrap_or(Vec4::zero())
.into_array(),
}
}
}
@ -79,7 +90,9 @@ impl Default for Globals {
0.0,
Vec2::new(800, 500),
0,
0,
BlockKind::Air,
None,
)
}
}
@ -91,6 +104,10 @@ impl Light {
col: Rgba::new(col.r, col.g, col.b, strength).into_array(),
}
}
pub fn get_pos(&self) -> Vec3<f32> {
Vec3::new(self.pos[0], self.pos[1], self.pos[2])
}
}
impl Default for Light {
@ -98,3 +115,21 @@ impl Default for Light {
Self::new(Vec3::zero(), Rgb::zero(), 0.0)
}
}
impl Shadow {
pub fn new(pos: Vec3<f32>, radius: f32) -> Self {
Self {
pos_radius: [pos.x, pos.y, pos.z, radius],
}
}
pub fn get_pos(&self) -> Vec3<f32> {
Vec3::new(self.pos_radius[0], self.pos_radius[1], self.pos_radius[2])
}
}
impl Default for Shadow {
fn default() -> Self {
Self::new(Vec3::zero(), 0.0)
}
}

View File

@ -1,6 +1,6 @@
use super::{
super::{util::arr_to_mat, Pipeline, TgtColorFmt, TgtDepthFmt},
Globals, Light,
Globals, Light, Shadow,
};
use gfx::{
self,
@ -36,6 +36,7 @@ gfx_defines! {
globals: gfx::ConstantBuffer<Globals> = "u_globals",
lights: gfx::ConstantBuffer<Light> = "u_lights",
shadows: gfx::ConstantBuffer<Shadow> = "u_shadows",
tgt_color: gfx::BlendTarget<TgtColorFmt> = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA),
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::LESS_EQUAL_WRITE,

View File

@ -1,6 +1,6 @@
use super::{
super::{Pipeline, TgtColorFmt, TgtDepthFmt},
Globals, Light,
Globals, Light, Shadow,
};
use gfx::{
self,
@ -23,6 +23,7 @@ gfx_defines! {
constant Locals {
model_offs: [f32; 3] = "model_offs",
load_time: f32 = "load_time",
}
pipeline pipe {
@ -31,6 +32,7 @@ gfx_defines! {
locals: gfx::ConstantBuffer<Locals> = "u_locals",
globals: gfx::ConstantBuffer<Globals> = "u_globals",
lights: gfx::ConstantBuffer<Light> = "u_lights",
shadows: gfx::ConstantBuffer<Shadow> = "u_shadows",
tgt_color: gfx::RenderTarget<TgtColorFmt> = "tgt_color",
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::LESS_EQUAL_WRITE,
@ -66,6 +68,7 @@ impl Locals {
pub fn default() -> Self {
Self {
model_offs: [0.0; 3],
load_time: 0.0,
}
}
}

View File

@ -4,7 +4,7 @@ use super::{
instances::Instances,
mesh::Mesh,
model::{DynamicModel, Model},
pipelines::{figure, fluid, postprocess, skybox, sprite, terrain, ui, Globals, Light},
pipelines::{figure, fluid, postprocess, skybox, sprite, terrain, ui, Globals, Light, Shadow},
texture::Texture,
Pipeline, RenderError,
};
@ -392,6 +392,7 @@ impl Renderer {
locals: &Consts<figure::Locals>,
bones: &Consts<figure::BoneData>,
lights: &Consts<Light>,
shadows: &Consts<Shadow>,
) {
self.encoder.draw(
&gfx::Slice {
@ -408,6 +409,7 @@ impl Renderer {
globals: globals.buf.clone(),
bones: bones.buf.clone(),
lights: lights.buf.clone(),
shadows: shadows.buf.clone(),
tgt_color: self.tgt_color_view.clone(),
tgt_depth: self.tgt_depth_view.clone(),
},
@ -421,6 +423,7 @@ impl Renderer {
globals: &Consts<Globals>,
locals: &Consts<terrain::Locals>,
lights: &Consts<Light>,
shadows: &Consts<Shadow>,
) {
self.encoder.draw(
&gfx::Slice {
@ -436,6 +439,7 @@ impl Renderer {
locals: locals.buf.clone(),
globals: globals.buf.clone(),
lights: lights.buf.clone(),
shadows: shadows.buf.clone(),
tgt_color: self.tgt_color_view.clone(),
tgt_depth: self.tgt_depth_view.clone(),
},
@ -449,6 +453,7 @@ impl Renderer {
globals: &Consts<Globals>,
locals: &Consts<terrain::Locals>,
lights: &Consts<Light>,
shadows: &Consts<Shadow>,
) {
self.encoder.draw(
&gfx::Slice {
@ -464,6 +469,7 @@ impl Renderer {
locals: locals.buf.clone(),
globals: globals.buf.clone(),
lights: lights.buf.clone(),
shadows: shadows.buf.clone(),
tgt_color: self.tgt_color_view.clone(),
tgt_depth: self.tgt_depth_view.clone(),
},
@ -477,6 +483,7 @@ impl Renderer {
globals: &Consts<Globals>,
instances: &Instances<sprite::Instance>,
lights: &Consts<Light>,
shadows: &Consts<Shadow>,
) {
self.encoder.draw(
&gfx::Slice {
@ -492,6 +499,7 @@ impl Renderer {
ibuf: instances.ibuf.clone(),
globals: globals.buf.clone(),
lights: lights.buf.clone(),
shadows: shadows.buf.clone(),
tgt_color: self.tgt_color_view.clone(),
tgt_depth: self.tgt_depth_view.clone(),
},

View File

@ -9,7 +9,7 @@ use crate::{
self, character::CharacterSkeleton, object::ObjectSkeleton, quadruped::QuadrupedSkeleton,
quadrupedmedium::QuadrupedMediumSkeleton, Animation, Skeleton,
},
render::{Consts, FigureBoneData, FigureLocals, Globals, Light, Renderer},
render::{Consts, FigureBoneData, FigureLocals, Globals, Light, Renderer, Shadow},
scene::camera::{Camera, CameraMode},
};
use client::Client;
@ -108,7 +108,7 @@ impl FigureMgr {
.and_then(|stats| stats.health.last_change)
.map(|(_, time, _)| {
Rgba::broadcast(1.0)
+ Rgba::new(0.0, -1.0, -1.0, 0.0)
+ Rgba::new(2.0, 2.0, 2.0, 0.0)
.map(|c| (c / (1.0 + DAMAGE_FADE_COEFFICIENT * time)) as f32)
})
.unwrap_or(Rgba::broadcast(1.0));
@ -408,6 +408,7 @@ impl FigureMgr {
client: &mut Client,
globals: &Consts<Globals>,
lights: &Consts<Light>,
shadows: &Consts<Shadow>,
camera: &Camera,
) {
let tick = client.get_tick();
@ -480,7 +481,7 @@ impl FigureMgr {
)
.0;
renderer.render_figure(model, globals, locals, bone_consts, lights);
renderer.render_figure(model, globals, locals, bone_consts, lights, shadows);
} else {
debug!("Body has no saved figure");
}

View File

@ -13,7 +13,7 @@ use crate::{
audio::AudioFrontend,
render::{
create_pp_mesh, create_skybox_mesh, Consts, Globals, Light, Model, PostProcessLocals,
PostProcessPipeline, Renderer, SkyboxLocals, SkyboxPipeline,
PostProcessPipeline, Renderer, Shadow, SkyboxLocals, SkyboxPipeline,
},
window::Event,
};
@ -30,7 +30,10 @@ use vek::*;
const CURSOR_PAN_SCALE: f32 = 0.005;
const MAX_LIGHT_COUNT: usize = 32;
const LIGHT_DIST_RADIUS: f32 = 64.0; // The distance beyond which lights may not be visible
const MAX_SHADOW_COUNT: usize = 24;
const LIGHT_DIST_RADIUS: f32 = 64.0; // The distance beyond which lights may not emit light from their origin
const SHADOW_DIST_RADIUS: f32 = 8.0;
const SHADOW_MAX_DIST: f32 = 96.0; // The distance beyond which shadows may not be visible
struct Skybox {
model: Model<SkyboxPipeline>,
@ -45,12 +48,14 @@ struct PostProcess {
pub struct Scene {
globals: Consts<Globals>,
lights: Consts<Light>,
shadows: Consts<Shadow>,
camera: Camera,
skybox: Skybox,
postprocess: PostProcess,
terrain: Terrain<TerrainChunk>,
loaded_distance: f32,
select_pos: Option<Vec3<i32>>,
figure_mgr: FigureMgr,
sound_mgr: SoundMgr,
@ -63,7 +68,12 @@ impl Scene {
Self {
globals: renderer.create_consts(&[Globals::default()]).unwrap(),
lights: renderer.create_consts(&[Light::default(); 32]).unwrap(),
lights: renderer
.create_consts(&[Light::default(); MAX_LIGHT_COUNT])
.unwrap(),
shadows: renderer
.create_consts(&[Shadow::default(); MAX_SHADOW_COUNT])
.unwrap(),
camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson),
skybox: Skybox {
@ -78,6 +88,8 @@ impl Scene {
},
terrain: Terrain::new(renderer),
loaded_distance: 0.0,
select_pos: None,
figure_mgr: FigureMgr::new(),
sound_mgr: SoundMgr::new(),
}
@ -98,6 +110,11 @@ impl Scene {
&mut self.camera
}
/// Set the block position that the player is interacting with
pub fn set_select_pos(&mut self, pos: Option<Vec3<i32>>) {
self.select_pos = pos;
}
/// Handle an incoming user input event (e.g.: cursor moved, key pressed, window closed).
///
/// If the event is handled, return true.
@ -200,14 +217,33 @@ impl Scene {
)
})
.collect::<Vec<_>>();
lights.sort_by_key(|light| {
Vec3::from(Vec4::from(light.pos)).distance_squared(player_pos) as i32
});
lights.sort_by_key(|light| light.get_pos().distance_squared(player_pos) as i32);
lights.truncate(MAX_LIGHT_COUNT);
renderer
.update_consts(&mut self.lights, &lights)
.expect("Failed to update light constants");
// Update shadow constants
let mut shadows = (
&client.state().ecs().read_storage::<comp::Pos>(),
client.state().ecs().read_storage::<comp::Scale>().maybe(),
&client.state().ecs().read_storage::<comp::Body>(),
&client.state().ecs().read_storage::<comp::Stats>(),
)
.join()
.filter(|(_, _, _, stats)| !stats.is_dead)
.filter(|(pos, _, _, _)| {
(pos.0.distance_squared(player_pos) as f32)
< (self.loaded_distance.min(SHADOW_MAX_DIST) + SHADOW_DIST_RADIUS).powf(2.0)
})
.map(|(pos, scale, _, _)| Shadow::new(pos.0, scale.map(|s| s.0).unwrap_or(1.0)))
.collect::<Vec<_>>();
shadows.sort_by_key(|shadow| shadow.get_pos().distance_squared(player_pos) as i32);
shadows.truncate(MAX_SHADOW_COUNT);
renderer
.update_consts(&mut self.shadows, &shadows)
.expect("Failed to update light constants");
// Update global constants.
renderer
.update_consts(
@ -222,12 +258,14 @@ impl Scene {
client.state().get_time(),
renderer.get_resolution(),
lights.len(),
shadows.len(),
client
.state()
.terrain()
.get(cam_pos.map(|e| e.floor() as i32))
.map(|b| b.kind())
.unwrap_or(BlockKind::Air),
self.select_pos,
)],
)
.expect("Failed to update global constants");
@ -258,12 +296,19 @@ impl Scene {
renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals);
// Render terrain and figures.
self.figure_mgr
.render(renderer, client, &self.globals, &self.lights, &self.camera);
self.figure_mgr.render(
renderer,
client,
&self.globals,
&self.lights,
&self.shadows,
&self.camera,
);
self.terrain.render(
renderer,
&self.globals,
&self.lights,
&self.shadows,
self.camera.get_focus_pos(),
);

View File

@ -1,8 +1,8 @@
use crate::{
mesh::Meshable,
render::{
Consts, FluidPipeline, Globals, Instances, Light, Mesh, Model, Renderer, SpriteInstance,
SpritePipeline, TerrainLocals, TerrainPipeline,
Consts, FluidPipeline, Globals, Instances, Light, Mesh, Model, Renderer, Shadow,
SpriteInstance, SpritePipeline, TerrainLocals, TerrainPipeline,
},
};
@ -23,6 +23,7 @@ use vek::*;
struct TerrainChunk {
// GPU data
load_time: f32,
opaque_model: Model<TerrainPipeline>,
fluid_model: Model<FluidPipeline>,
sprite_instances: HashMap<(BlockKind, usize), Instances<SpriteInstance>>,
@ -130,6 +131,10 @@ fn sprite_config_for(kind: BlockKind) -> Option<SpriteConfig> {
variations: 2,
wind_sway: 0.05,
}),
BlockKind::Velorite => Some(SpriteConfig {
variations: 1,
wind_sway: 0.0,
}),
_ => None,
}
}
@ -595,6 +600,13 @@ impl<V: RectRasterableVol> Terrain<V> {
Vec3::new(-1.0, -0.5, -55.0),
),
),
(
(BlockKind::Velorite, 0),
make_model(
"voxygen.voxel.sprite.velorite.velorite",
Vec3::new(-5.0, -5.0, -0.0),
),
),
]
.into_iter()
.collect(),
@ -613,6 +625,7 @@ impl<V: RectRasterableVol> Terrain<V> {
proj_mat: Mat4<f32>,
) {
let current_tick = client.get_tick();
let current_time = client.state().get_time();
// Add any recently created or changed chunks to the list of chunks to be meshed.
for (modified, pos) in client
@ -700,6 +713,17 @@ impl<V: RectRasterableVol> Terrain<V> {
},
);
}
// TODO: Remesh all neighbours because we have complex lighting now
/*self.mesh_todo.insert(
neighbour_chunk_pos,
ChunkMeshState {
pos: chunk_pos + Vec2::new(x, y),
started_tick: current_tick,
active_worker: None,
},
);
*/
}
}
}
@ -785,9 +809,15 @@ impl<V: RectRasterableVol> Terrain<V> {
// It's the mesh we want, insert the newly finished model into the terrain model
// data structure (convert the mesh to a model first of course).
Some(todo) if response.started_tick <= todo.started_tick => {
let load_time = self
.chunks
.get(&response.pos)
.map(|chunk| chunk.load_time)
.unwrap_or(current_time as f32);
self.chunks.insert(
response.pos,
TerrainChunk {
load_time,
opaque_model: renderer
.create_model(&response.opaque_mesh)
.expect("Failed to upload chunk mesh to the GPU!"),
@ -814,6 +844,7 @@ impl<V: RectRasterableVol> Terrain<V> {
}),
)
.into_array(),
load_time,
}])
.expect("Failed to upload chunk locals to the GPU!"),
visible: false,
@ -874,12 +905,19 @@ impl<V: RectRasterableVol> Terrain<V> {
renderer: &mut Renderer,
globals: &Consts<Globals>,
lights: &Consts<Light>,
shadows: &Consts<Shadow>,
focus_pos: Vec3<f32>,
) {
// Opaque
for (_, chunk) in &self.chunks {
if chunk.visible {
renderer.render_terrain_chunk(&chunk.opaque_model, globals, &chunk.locals, lights);
renderer.render_terrain_chunk(
&chunk.opaque_model,
globals,
&chunk.locals,
lights,
shadows,
);
}
}
@ -899,6 +937,7 @@ impl<V: RectRasterableVol> Terrain<V> {
globals,
&instances,
lights,
shadows,
);
}
}
@ -908,7 +947,13 @@ impl<V: RectRasterableVol> Terrain<V> {
// Translucent
for (_, chunk) in &self.chunks {
if chunk.visible {
renderer.render_fluid_chunk(&chunk.fluid_model, globals, &chunk.locals, lights);
renderer.render_fluid_chunk(
&chunk.fluid_model,
globals,
&chunk.locals,
lights,
shadows,
);
}
}
}

View File

@ -117,6 +117,27 @@ impl PlayState for SessionState {
.compute_dependents(&self.client.borrow());
let cam_dir: Vec3<f32> = Vec3::from(view_mat.inverted() * -Vec4::unit_z());
// Check to see whether we're aiming at anything
let (build_pos, select_pos) = {
let client = self.client.borrow();
let terrain = client.state().terrain();
let ray = terrain
.ray(cam_pos, cam_pos + cam_dir * 100.0)
.until(|block| block.is_tangible())
.cast();
let dist = ray.0;
if let Ok(Some(_)) = ray.1 {
// Hit something!
(
Some((cam_pos + cam_dir * (dist - 0.01)).map(|e| e.floor() as i32)),
Some((cam_pos + cam_dir * dist).map(|e| e.floor() as i32)),
)
} else {
(None, None)
}
};
self.scene.set_select_pos(select_pos);
// Reset controller events
self.controller.clear_events();
@ -142,16 +163,8 @@ impl PlayState for SessionState {
.get(client.entity())
.is_some()
{
let (d, b) = {
let terrain = client.state().terrain();
let ray = terrain.ray(cam_pos, cam_pos + cam_dir * 100.0).cast();
(ray.0, if let Ok(Some(_)) = ray.1 { true } else { false })
};
if b {
let pos =
(cam_pos + cam_dir * (d - 0.01)).map(|e| e.floor() as i32);
client.place_block(pos, self.selected_block);
if let Some(build_pos) = build_pos {
client.place_block(build_pos, self.selected_block);
}
} else {
self.controller.primary = state
@ -159,7 +172,10 @@ impl PlayState for SessionState {
}
Event::InputUpdate(GameInput::Secondary, state) => {
self.controller.secondary = false; // To be changed later on
let mut client = self.client.borrow_mut();
if state
&& client
.state()
@ -167,18 +183,21 @@ impl PlayState for SessionState {
.get(client.entity())
.is_some()
{
let (d, b) = {
let terrain = client.state().terrain();
let ray = terrain.ray(cam_pos, cam_pos + cam_dir * 100.0).cast();
(ray.0, if let Ok(Some(_)) = ray.1 { true } else { false })
};
if b {
let pos = (cam_pos + cam_dir * d).map(|e| e.floor() as i32);
client.remove_block(pos);
if let Some(select_pos) = select_pos {
client.remove_block(select_pos);
}
} else {
} else if client
.state()
.read_storage::<comp::CharacterState>()
.get(client.entity())
.map(|cs| cs.action.is_wield())
.unwrap_or(false)
{
self.controller.secondary = state;
} else {
if let Some(select_pos) = select_pos {
client.collect_block(select_pos);
}
}
}
Event::InputUpdate(GameInput::Roll, state) => {
@ -190,14 +209,10 @@ impl PlayState for SessionState {
.is_some()
{
if state {
if let Ok(Some(block)) = client
.state()
.terrain()
.ray(cam_pos, cam_pos + cam_dir * 100.0)
.cast()
.1
if let Some(block) = select_pos
.and_then(|sp| client.state().terrain().get(sp).ok().copied())
{
self.selected_block = *block;
self.selected_block = block;
}
}
} else {

View File

@ -64,7 +64,7 @@ impl Default for ControlSettings {
jump: KeyMouse::Key(VirtualKeyCode::Space),
sit: KeyMouse::Key(VirtualKeyCode::K),
glide: KeyMouse::Key(VirtualKeyCode::LShift),
climb: KeyMouse::Key(VirtualKeyCode::Space),
climb: KeyMouse::Key(VirtualKeyCode::LControl),
climb_down: KeyMouse::Key(VirtualKeyCode::LShift),
wall_leap: KeyMouse::Mouse(MouseButton::Middle),
mount: KeyMouse::Key(VirtualKeyCode::F),
@ -193,7 +193,7 @@ impl Default for AudioSettings {
music_volume: 0.4,
sfx_volume: 0.6,
audio_device: None,
audio_on: false,
audio_on: true,
}
}
}

View File

@ -16,7 +16,7 @@ pub use widgets::{
image_slider::ImageSlider,
ingame::{Ingame, IngameAnchor, Ingameable},
toggle_button::ToggleButton,
tooltip::{Tooltip, Tooltipable},
tooltip::{Tooltip, TooltipManager, Tooltipable},
};
use crate::{
@ -49,7 +49,6 @@ use std::{
time::Duration,
};
use vek::*;
use widgets::tooltip::TooltipManager;
#[derive(Debug)]
pub enum UiError {

View File

@ -54,19 +54,23 @@ impl<'a> BlockGen<'a> {
cache,
Vec2::from(*cliff_pos),
) {
Some(cliff_sample) if cliff_sample.is_cliffs && cliff_sample.spawn_rate > 0.5 => {
Some(cliff_sample)
if cliff_sample.is_cliffs
&& cliff_sample.spawn_rate > 0.5
&& cliff_sample.spawn_rules.cliffs =>
{
let cliff_pos3d = Vec3::from(*cliff_pos);
let height = RandomField::new(seed + 1).get(cliff_pos3d) % 48;
let height = (RandomField::new(seed + 1).get(cliff_pos3d) % 64) as f32
/ (1.0 + 3.0 * cliff_sample.chaos)
+ 3.0;
let radius = RandomField::new(seed + 2).get(cliff_pos3d) % 48 + 8;
max_height.max(
if cliff_pos.map(|e| e as f32).distance_squared(wpos)
< (radius as f32 + tolerance).powf(2.0)
{
cliff_sample.alt
+ height as f32 * (1.0 - cliff_sample.chaos)
+ cliff_hill
cliff_sample.alt + height * (1.0 - cliff_sample.chaos) + cliff_hill
} else {
0.0
},
@ -178,12 +182,13 @@ impl<'a> BlockGen<'a> {
(true, alt, CONFIG.sea_level /*water_level*/)
} else {
// Apply warping
let warp = (world.sim().gen_ctx.warp_nz.get(wposf.div(48.0)) as f32)
.mul((chaos - 0.1).max(0.0))
.mul(48.0)
+ (world.sim().gen_ctx.warp_nz.get(wposf.div(15.0)) as f32)
.mul((chaos - 0.1).max(0.0))
.mul(24.0);
let warp = world
.sim()
.gen_ctx
.warp_nz
.get(wposf.div(24.0))
.mul((chaos - 0.1).max(0.0).powf(2.0))
.mul(48.0);
let height = if (wposf.z as f32) < alt + warp - 10.0 {
// Shortcut cliffs
@ -310,6 +315,14 @@ impl<'a> BlockGen<'a> {
},
Rgb::broadcast(0),
))
} else if (wposf.z as f32) < height + 0.9
&& chaos > 0.6
&& (wposf.z as f32 > water_height + 3.0)
&& marble > 0.75
&& marble_small > 0.3
&& (marble * 7323.07).fract() < 0.75
{
Some(Block::new(BlockKind::Velorite, Rgb::broadcast(0)))
} else {
None
}
@ -379,10 +392,9 @@ impl<'a> BlockGen<'a> {
let (st, st_sample) = st.as_ref()?;
st.get(wpos, st_sample)
})
.or(block)
.unwrap_or(Block::empty());
.or(block);
Some(block)
Some(block.unwrap_or(Block::empty()))
}
}
@ -413,7 +425,8 @@ impl<'a> ZCache<'a> {
let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 };
let warp = self.sample.chaos * 24.0;
let warp = self.sample.chaos * 32.0;
let (structure_min, structure_max) = self
.structures
.iter()
@ -432,7 +445,7 @@ impl<'a> ZCache<'a> {
}
});
let ground_max = (self.sample.alt + 2.0 + warp + rocks).max(cliff);
let ground_max = (self.sample.alt + warp + rocks).max(cliff) + 2.0;
let min = min + structure_min;
let max = (ground_max + structure_max)

View File

@ -168,18 +168,18 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
+ (sim
.gen_ctx
.small_nz
.get((wposf_turb.div(150.0)).into_array()) as f32)
.get((wposf_turb.div(200.0)).into_array()) as f32)
.abs()
.mul(chaos.max(0.025))
.mul(64.0)
.mul(chaos.max(0.05))
.mul(55.0)
+ (sim
.gen_ctx
.small_nz
.get((wposf_turb.div(450.0)).into_array()) as f32)
.get((wposf_turb.div(400.0)).into_array()) as f32)
.abs()
.mul(1.0 - chaos)
.mul((1.0 - chaos).max(0.3))
.mul(1.0 - humidity)
.mul(96.0);
.mul(65.0);
let is_cliffs = sim_chunk.is_cliffs;
let near_cliffs = sim_chunk.near_cliffs;
@ -207,6 +207,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let wposf3d = Vec3::new(wposf.x, wposf.y, alt as f64);
let marble_small = (sim.gen_ctx.hill_nz.get((wposf3d.div(3.0)).into_array()) as f32)
.powf(3.0)
.add(1.0)
.mul(0.5);
let marble = (sim.gen_ctx.hill_nz.get((wposf3d.div(48.0)).into_array()) as f32)
@ -225,13 +226,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let wet_grass = Rgb::new(0.1, 0.8, 0.2);
let cold_stone = Rgb::new(0.57, 0.67, 0.8);
let warm_stone = Rgb::new(0.77, 0.77, 0.64);
let beach_sand = Rgb::new(0.89, 0.87, 0.64);
let desert_sand = Rgb::new(0.93, 0.80, 0.54);
let beach_sand = Rgb::new(0.9, 0.82, 0.6);
let desert_sand = Rgb::new(0.95, 0.75, 0.5);
let snow = Rgb::new(0.8, 0.85, 1.0);
let dirt = Lerp::lerp(
Rgb::new(0.078, 0.078, 0.20),
Rgb::new(0.61, 0.49, 0.0),
Rgb::new(0.075, 0.07, 0.3),
Rgb::new(0.75, 0.55, 0.1),
marble,
);
let tundra = Lerp::lerp(snow, Rgb::new(0.01, 0.3, 0.0), 0.4 + marble * 0.6);

View File

@ -10,11 +10,15 @@ use vek::*;
#[derive(Copy, Clone, Debug)]
pub struct SpawnRules {
pub trees: bool,
pub cliffs: bool,
}
impl Default for SpawnRules {
fn default() -> Self {
Self { trees: true }
Self {
trees: true,
cliffs: true,
}
}
}
@ -22,6 +26,7 @@ impl SpawnRules {
pub fn and(self, other: Self) -> Self {
Self {
trees: self.trees && other.trees,
cliffs: self.cliffs && other.cliffs,
}
}
}

View File

@ -6,6 +6,7 @@ use crate::{
block::block_from_structure,
column::{ColumnGen, ColumnSample},
util::Sampler,
CONFIG,
};
use common::{
assets,
@ -70,6 +71,7 @@ impl<'a> Sampler<'a> for TownGen {
CellKind::Park => None,
CellKind::Rock => Some(Block::new(BlockKind::Normal, Rgb::broadcast(100))),
CellKind::Wall => Some(Block::new(BlockKind::Normal, Rgb::broadcast(175))),
CellKind::Well => Some(Block::new(BlockKind::Normal, Rgb::broadcast(0))),
CellKind::Road => {
if (wpos.z as f32) < height - 1.0 {
Some(Block::new(
@ -101,8 +103,11 @@ impl<'a> Generator<'a, TownState> for TownGen {
(sample.alt - 32.0, sample.alt + 75.0)
}
fn spawn_rules(&self, _town: &'a TownState, _wpos: Vec2<i32>) -> SpawnRules {
SpawnRules { trees: false }
fn spawn_rules(&self, town: &'a TownState, wpos: Vec2<i32>) -> SpawnRules {
SpawnRules {
trees: wpos.distance_squared(town.center.into()) > (town.radius + 32).pow(2),
cliffs: false,
}
}
}
@ -122,6 +127,12 @@ impl TownState {
let radius = rng.gen_range(18, 20) * 9;
let size = Vec2::broadcast(radius * 2 / 9 - 2);
if gen.get(center).map(|sample| sample.chaos).unwrap_or(0.0) > 0.35
|| gen.get(center).map(|sample| sample.alt).unwrap_or(0.0) < CONFIG.sea_level + 10.0
{
return None;
}
let alt = gen.get(center).map(|sample| sample.alt).unwrap_or(0.0) as i32;
let mut vol = TownVol::generate_from(
@ -154,6 +165,7 @@ impl TownState {
vol.gen_parks(rng, 3);
vol.emplace_columns();
let houses = vol.gen_houses(rng, 50);
vol.gen_wells(rng, 5);
vol.gen_walls(rng);
vol.resolve_modules(rng);
vol.cull_unused();
@ -301,10 +313,7 @@ impl TownVol {
self.set_col_kind(cell, Some(ColumnKind::Internal));
let col = self.col(cell).unwrap();
let ground = col.ground;
for z in 0..2 {
let _ =
self.set(Vec3::new(cell.x, cell.y, ground + z), CellKind::Park.into());
}
let _ = self.set(Vec3::new(cell.x, cell.y, ground), CellKind::Park.into());
}
break;
@ -394,6 +403,20 @@ impl TownVol {
}
}
fn gen_wells(&mut self, rng: &mut impl Rng, n: usize) {
for _ in 0..n {
if let Some(cell) = self.choose_cell(rng, |_, cell| {
if let CellKind::Park = cell.kind {
true
} else {
false
}
}) {
let _ = self.set(cell, CellKind::Well.into());
}
}
}
fn gen_houses(&mut self, rng: &mut impl Rng, n: usize) -> Vec<House> {
const ATTEMPTS: usize = 10;
@ -401,7 +424,12 @@ impl TownVol {
for _ in 0..n {
for _ in 0..ATTEMPTS {
let entrance = {
let start = self.choose_cell(rng, |_, cell| cell.is_road()).unwrap();
let start_col = self.choose_column(rng, |_, col| col.is_road()).unwrap();;
let start = Vec3::new(
start_col.x,
start_col.y,
self.col(start_col).unwrap().ground,
);
let dir = Vec3::from(util::gen_dir(rng));
if self
@ -419,13 +447,13 @@ impl TownVol {
}
};
let mut cells: HashSet<_> = Some(entrance).into_iter().collect();
let mut cells = HashSet::new();
let mut energy = 1000;
let mut energy = 2300;
while energy > 0 {
energy -= 1;
let parent = *cells.iter().choose(rng).unwrap();
let parent = *cells.iter().choose(rng).unwrap_or(&entrance);
let dir = util::UNITS_3D
.choose_weighted(rng, |pos| 1 + pos.z.max(0))
.unwrap();
@ -441,6 +469,8 @@ impl TownVol {
|| cells.contains(&(parent + dir - Vec3::unit_z()))
})
.unwrap_or(false)
&& parent.z + dir.z <= entrance.z + 2
// Maximum house height
{
cells.insert(parent + dir);
energy -= 10;
@ -602,6 +632,7 @@ fn modules_from_kind(kind: &CellKind) -> Option<&'static [(Arc<Structure>, [Modu
match kind {
CellKind::House(_) => Some(&HOUSE_MODULES),
CellKind::Wall => Some(&WALL_MODULES),
CellKind::Well => Some(&WELL_MODULES),
_ => None,
}
}
@ -613,6 +644,10 @@ lazy_static! {
module("human.floor_ground", [This, This, This, This, This, That]),
module("human.stair_ground", [This, This, This, This, This, That]),
module("human.corner_ground", [This, This, That, That, This, That]),
module(
"human.window_corner_ground",
[This, This, That, That, This, That],
),
module("human.wall_ground", [This, This, This, That, This, That]),
module("human.door_ground", [This, This, This, That, This, That]),
module("human.window_ground", [This, This, This, That, This, That]),
@ -629,6 +664,10 @@ lazy_static! {
"human.corner_upstairs",
[This, This, That, That, This, This],
),
module(
"human.window_corner_upstairs",
[This, This, That, That, This, This],
),
module("human.wall_upstairs", [This, This, This, That, This, This]),
module(
"human.window_upstairs",
@ -649,4 +688,29 @@ lazy_static! {
module("wall.single_top", [That, That, That, That, That, This]),
]
};
pub static ref WELL_MODULES: Vec<(Arc<Structure>, [ModuleKind; 6])> = {
use ModuleKind::*;
vec![module("misc.well", [That; 6])]
};
}
/*
// TODO
struct ModuleModel {
near: u64,
mask: u64,
vol: Arc<Structure>,
}
#[derive(Copy, Clone)]
pub enum NearKind {
This,
That,
}
impl ModuleModel {
pub fn generate_list(_details: &[(&str, &[([i32; 3], NearKind)])]) -> Vec<Self> {
unimplemented!()
}
}
*/

View File

@ -48,6 +48,7 @@ pub enum CellKind {
Road,
Wall,
House(usize),
Well,
}
#[derive(Clone, PartialEq)]
@ -57,13 +58,6 @@ pub struct TownCell {
}
impl TownCell {
pub fn is_road(&self) -> bool {
match self.kind {
CellKind::Road => true,
_ => false,
}
}
pub fn is_space(&self) -> bool {
match self.kind {
CellKind::Empty => true,

View File

@ -128,7 +128,7 @@ impl WorldSim {
cave_0_nz: SuperSimplex::new().set_seed(rng.gen()),
cave_1_nz: SuperSimplex::new().set_seed(rng.gen()),
structure_gen: StructureGen2d::new(rng.gen(), 32, 24),
structure_gen: StructureGen2d::new(rng.gen(), 32, 16),
region_gen: StructureGen2d::new(rng.gen(), 400, 96),
cliff_gen: StructureGen2d::new(rng.gen(), 80, 56),
humid_nz: Billow::new()
@ -150,9 +150,9 @@ impl WorldSim {
// but value here is from -0.275 to 0.225).
let alt_base = uniform_noise(|_, wposf| {
Some(
(gen_ctx.alt_nz.get((wposf.div(12_000.0)).into_array()) as f32)
.sub(0.1)
.mul(0.25),
(gen_ctx.alt_nz.get((wposf.div(10_000.0)).into_array()) as f32)
.sub(0.05)
.mul(0.35),
)
});
@ -174,14 +174,14 @@ impl WorldSim {
.max(0.0);
Some(
(gen_ctx.chaos_nz.get((wposf.div(3_000.0)).into_array()) as f32)
(gen_ctx.chaos_nz.get((wposf.div(3_500.0)).into_array()) as f32)
.add(1.0)
.mul(0.5)
// [0, 1] * [0.25, 1] = [0, 1] (but probably towards the lower end)
.mul(
(gen_ctx.chaos_nz.get((wposf.div(6_000.0)).into_array()) as f32)
.abs()
.max(0.25)
.max(0.4)
.min(1.0),
)
// Chaos is always increased by a little when we're on a hill (but remember that
@ -189,7 +189,7 @@ impl WorldSim {
// [0, 1] + 0.15 * [0, 1.6] = [0, 1.24]
.add(0.2 * hill)
// We can't have *no* chaos!
.max(0.1),
.max(0.12),
)
});
@ -214,12 +214,17 @@ impl WorldSim {
.abs()
.powf(1.35);
fn spring(x: f32, pow: f32) -> f32 {
x.abs().powf(pow) * x.signum()
}
(0.0 + alt_main
+ (gen_ctx.small_nz.get((wposf.div(300.0)).into_array()) as f32)
.mul(alt_main.max(0.25))
.mul(alt_main.powf(0.8).max(0.15))
.mul(0.3)
.add(1.0)
.mul(0.5))
.mul(0.4)
+ spring(alt_main.abs().powf(0.5).min(0.75).mul(60.0).sin(), 4.0).mul(0.045))
};
// Now we can compute the final altitude using chaos.
@ -227,7 +232,10 @@ impl WorldSim {
// alt_pre, then multiply by CONFIG.mountain_scale and add to the base and sea level to
// get an adjusted value, then multiply the whole thing by map_edge_factor
// (TODO: compute final bounds).
Some((alt_base[posi].1 + alt_main.mul(chaos[posi].1)).mul(map_edge_factor(posi)))
Some(
(alt_base[posi].1 + alt_main.mul(chaos[posi].1.powf(1.2)))
.mul(map_edge_factor(posi)),
)
});
// Check whether any tiles around this tile are not water (since Lerp will ensure that they
@ -704,7 +712,7 @@ impl SimChunk {
} else {
// For now we don't take humidity into account for cold climates (but we really
// should!) except that we make sure we only have snow pines when there is snow.
if temp <= CONFIG.snow_temp && humidity > CONFIG.forest_hum {
if temp <= CONFIG.snow_temp {
ForestKind::SnowPine
} else if humidity > CONFIG.desert_hum {
ForestKind::Pine