Merge branch 'zesterer/small-fixes' into 'master'

Performance Improvements

See merge request veloren/veloren!597
This commit is contained in:
Joshua Barretto
2019-10-17 20:56:37 +00:00
11 changed files with 173 additions and 68 deletions

View File

@ -4,7 +4,7 @@
#include <random.glsl> #include <random.glsl>
in vec3 f_pos; in vec3 f_pos;
flat in vec3 f_norm; flat in uint f_pos_norm;
in vec3 f_col; in vec3 f_col;
in float f_light; in float f_light;
@ -14,6 +14,8 @@ uniform u_locals {
float load_time; float load_time;
}; };
uniform sampler2D t_waves;
out vec4 tgt_color; out vec4 tgt_color;
#include <sky.glsl> #include <sky.glsl>
@ -26,6 +28,16 @@ vec3 warp_normal(vec3 norm, vec3 pos, float time) {
} }
void main() { void main() {
// First 3 normals are negative, next 3 are positive
vec3 normals[6] = vec3[]( vec3(-1,0,0), vec3(0,-1,0), vec3(0,0,-1), vec3(1,0,0), vec3(0,1,0), vec3(0,0,1) );
// 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 = (f_pos_norm >> 30) & 0x3u;
// Increase array access by 3 to access positive values
uint norm_dir = ((f_pos_norm >> 29) & 0x1u) * 3u;
// Use an array to avoid conditional branching
vec3 f_norm = normals[norm_axis + norm_dir];
/* /*
// Round the position to the nearest triangular grid cell // Round the position to the nearest triangular grid cell
vec3 hex_pos = f_pos * 2.0; vec3 hex_pos = f_pos * 2.0;
@ -36,7 +48,25 @@ void main() {
hex_pos = floor(hex_pos); hex_pos = floor(hex_pos);
*/ */
vec3 norm = warp_normal(f_norm, f_pos, tick.x); vec3 b_norm;
if (f_norm.z > 0.0) {
b_norm = vec3(1, 0, 0);
} else if (f_norm.x > 0.0) {
b_norm = vec3(0, 1, 0);
} else {
b_norm = vec3(0, 0, 1);
}
vec3 c_norm = cross(f_norm, b_norm);
vec3 nmap = normalize(
(srgb_to_linear(texture(t_waves, fract(f_pos.xy * 0.3 + tick.x * 0.04)).rgb) - 0.0) * 0.05
+ (srgb_to_linear(texture(t_waves, fract(f_pos.xy * 0.1 - tick.x * 0.08)).rgb) - 0.0) * 0.1
+ (srgb_to_linear(texture(t_waves, fract(-f_pos.yx * 0.06 - tick.x * 0.1)).rgb) - 0.0) * 0.1
+ (srgb_to_linear(texture(t_waves, fract(-f_pos.yx * 0.03 - tick.x * 0.01)).rgb) - 0.0) * 0.2
+ vec3(0, 0, 0.0)
);
vec3 norm = f_norm * nmap.z + b_norm * nmap.x + c_norm * nmap.y;
vec3 light, diffuse_light, ambient_light; vec3 light, diffuse_light, ambient_light;
get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 0.0); get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 0.0);
@ -46,7 +76,7 @@ void main() {
vec3 point_light = light_at(f_pos, f_norm); vec3 point_light = light_at(f_pos, f_norm);
light += point_light; light += point_light;
diffuse_light += point_light; diffuse_light += point_light;
vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light); vec3 surf_color = illuminate(srgb_to_linear(f_col), light, diffuse_light, ambient_light);
float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); 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); vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x, true);
@ -57,10 +87,11 @@ void main() {
reflect_ray_dir.z = max(reflect_ray_dir.z, 0.05); reflect_ray_dir.z = max(reflect_ray_dir.z, 0.05);
vec3 reflect_color = get_sky_color(reflect_ray_dir, time_of_day.x, false) * f_light; vec3 reflect_color = get_sky_color(reflect_ray_dir, time_of_day.x, false) * f_light;
//reflect_color = vec3(reflect_color.r + reflect_color.g + reflect_color.b) / 3.0;
// 0 = 100% reflection, 1 = translucent water // 0 = 100% reflection, 1 = translucent water
float passthrough = pow(dot(faceforward(norm, norm, cam_to_frag), -cam_to_frag), 1.0); float passthrough = pow(dot(faceforward(f_norm, f_norm, cam_to_frag), -cam_to_frag), 0.3);
vec4 color = mix(vec4(reflect_color, 1.0), vec4(surf_color, 3.0 / (1.0 + diffuse_light * 2.0)), passthrough); vec4 color = mix(vec4(reflect_color * 2.0, 1.0), vec4(surf_color, 4.0 / (1.0 + diffuse_light * 2.0)), passthrough);
tgt_color = mix(color, vec4(fog_color, 0.0), fog_level); tgt_color = mix(color, vec4(fog_color, 0.0), fog_level);
} }

View File

@ -13,13 +13,11 @@ uniform u_locals {
}; };
out vec3 f_pos; out vec3 f_pos;
flat out uint f_pos_norm;
flat out vec3 f_norm; flat out vec3 f_norm;
out vec3 f_col; out vec3 f_col;
out float f_light; out float f_light;
// First 3 normals are negative, next 3 are positive
vec3 normals[6] = vec3[]( vec3(-1,0,0), vec3(0,-1,0), vec3(0,0,-1), vec3(1,0,0), vec3(0,1,0), vec3(0,0,1) );
void main() { void main() {
f_pos = vec3( f_pos = vec3(
float((v_pos_norm >> 0) & 0x00FFu), float((v_pos_norm >> 0) & 0x00FFu),
@ -27,23 +25,16 @@ void main() {
float((v_pos_norm >> 16) & 0x1FFFu) float((v_pos_norm >> 16) & 0x1FFFu)
) + model_offs; ) + model_offs;
// TODO: last 3 bits in v_pos_norm should be a number between 0 and 5, rather than 0-2 and a direction. f_col = vec3(
uint norm_axis = (v_pos_norm >> 30) & 0x3u;
// Increase array access by 3 to access positive values
uint norm_dir = ((v_pos_norm >> 29) & 0x1u) * 3u;
// Use an array to avoid conditional branching
f_norm = normals[norm_axis + norm_dir];
f_col = srgb_to_linear(vec3(
float((v_col_light >> 8) & 0xFFu), float((v_col_light >> 8) & 0xFFu),
float((v_col_light >> 16) & 0xFFu), float((v_col_light >> 16) & 0xFFu),
float((v_col_light >> 24) & 0xFFu) float((v_col_light >> 24) & 0xFFu)
) / 255.0); ) / 255.0;
f_light = float(v_col_light & 0xFFu) / 255.0; f_light = float(v_col_light & 0xFFu) / 255.0;
f_pos_norm = v_pos_norm;
gl_Position = gl_Position =
proj_mat * proj_mat *
view_mat * view_mat *

View File

@ -31,11 +31,11 @@ vec3 get_sun_dir(float time_of_day) {
const float PERSISTENT_AMBIANCE = 0.1; const float PERSISTENT_AMBIANCE = 0.1;
float get_sun_brightness(vec3 sun_dir) { 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.9;
} }
void get_sun_diffuse(vec3 norm, float time_of_day, out vec3 light, out vec3 diffuse_light, out vec3 ambient_light, float diffusion) { void get_sun_diffuse(vec3 norm, float time_of_day, out vec3 light, out vec3 diffuse_light, out vec3 ambient_light, float diffusion) {
const float SUN_AMBIANCE = 0.0; const float SUN_AMBIANCE = 0.1;
vec3 sun_dir = get_sun_dir(time_of_day); vec3 sun_dir = get_sun_dir(time_of_day);

View File

@ -3,7 +3,7 @@
#include <globals.glsl> #include <globals.glsl>
in vec3 f_pos; in vec3 f_pos;
flat in vec3 f_norm; flat in uint f_pos_norm;
in vec3 f_col; in vec3 f_col;
in float f_light; in float f_light;
@ -19,6 +19,16 @@ out vec4 tgt_color;
#include <light.glsl> #include <light.glsl>
void main() { void main() {
// First 3 normals are negative, next 3 are positive
vec3 normals[6] = vec3[]( vec3(-1,0,0), vec3(0,-1,0), vec3(0,0,-1), vec3(1,0,0), vec3(0,1,0), vec3(0,0,1) );
// 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 = (f_pos_norm >> 30) & 0x3u;
// Increase array access by 3 to access positive values
uint norm_dir = ((f_pos_norm >> 29) & 0x1u) * 3u;
// Use an array to avoid conditional branching
vec3 f_norm = normals[norm_axis + norm_dir];
vec3 light, diffuse_light, ambient_light; vec3 light, diffuse_light, ambient_light;
get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0); get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0);
float point_shadow = shadow_at(f_pos, f_norm); float point_shadow = shadow_at(f_pos, f_norm);
@ -27,7 +37,7 @@ void main() {
vec3 point_light = light_at(f_pos, f_norm); vec3 point_light = light_at(f_pos, f_norm);
light += point_light; light += point_light;
diffuse_light += point_light; diffuse_light += point_light;
vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light); vec3 surf_color = illuminate(srgb_to_linear(f_col), light, diffuse_light, ambient_light);
float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); 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); vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x, true);

View File

@ -13,13 +13,10 @@ uniform u_locals {
}; };
out vec3 f_pos; out vec3 f_pos;
flat out vec3 f_norm; flat out uint f_pos_norm;
out vec3 f_col; out vec3 f_col;
out float f_light; out float f_light;
// First 3 normals are negative, next 3 are positive
vec3 normals[6] = vec3[]( vec3(-1,0,0), vec3(0,-1,0), vec3(0,0,-1), vec3(1,0,0), vec3(0,1,0), vec3(0,0,1) );
void main() { void main() {
f_pos = vec3( f_pos = vec3(
float((v_pos_norm >> 0) & 0x00FFu), float((v_pos_norm >> 0) & 0x00FFu),
@ -29,23 +26,16 @@ void main() {
f_pos.z *= min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0); 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. f_col = vec3(
uint norm_axis = (v_pos_norm >> 30) & 0x3u;
// Increase array access by 3 to access positive values
uint norm_dir = ((v_pos_norm >> 29) & 0x1u) * 3u;
// Use an array to avoid conditional branching
f_norm = normals[norm_axis + norm_dir];
f_col = srgb_to_linear(vec3(
float((v_col_light >> 8) & 0xFFu), float((v_col_light >> 8) & 0xFFu),
float((v_col_light >> 16) & 0xFFu), float((v_col_light >> 16) & 0xFFu),
float((v_col_light >> 24) & 0xFFu) float((v_col_light >> 24) & 0xFFu)
) / 255.0); ) / 255.0;
f_light = float(v_col_light & 0xFFu) / 255.0; f_light = float(v_col_light & 0xFFu) / 255.0;
f_pos_norm = v_pos_norm;
gl_Position = gl_Position =
proj_mat * proj_mat *
view_mat * view_mat *

BIN
assets/voxygen/texture/waves.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -99,11 +99,14 @@ impl<'a> System<'a> for Sys {
if character.movement.is_roll() { if character.movement.is_roll() {
vel.0 = Vec3::new(0.0, 0.0, vel.0.z) vel.0 = Vec3::new(0.0, 0.0, vel.0.z)
+ controller + (vel.0 * Vec3::new(1.0, 1.0, 0.0)
.move_dir + 1.5
.try_normalized() * controller
.unwrap_or(Vec2::from(vel.0).try_normalized().unwrap_or_default()) .move_dir
* ROLL_SPEED .try_normalized()
.unwrap_or(Vec2::from(vel.0).try_normalized().unwrap_or_default()))
.normalized()
* ROLL_SPEED;
} }
if character.action.is_block() || character.action.is_attack() { if character.action.is_block() || character.action.is_attack() {
vel.0 += Vec2::broadcast(dt.0) vel.0 += Vec2::broadcast(dt.0)

View File

@ -29,6 +29,8 @@ gfx_defines! {
lights: gfx::ConstantBuffer<Light> = "u_lights", lights: gfx::ConstantBuffer<Light> = "u_lights",
shadows: gfx::ConstantBuffer<Shadow> = "u_shadows", shadows: gfx::ConstantBuffer<Shadow> = "u_shadows",
waves: gfx::TextureSampler<[f32; 4]> = "t_waves",
tgt_color: gfx::BlendTarget<TgtColorFmt> = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA), tgt_color: gfx::BlendTarget<TgtColorFmt> = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA),
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::LESS_EQUAL_TEST, tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::LESS_EQUAL_TEST,
} }

View File

@ -354,8 +354,10 @@ impl Renderer {
pub fn create_texture<P: Pipeline>( pub fn create_texture<P: Pipeline>(
&mut self, &mut self,
image: &image::DynamicImage, image: &image::DynamicImage,
filter_method: Option<gfx::texture::FilterMethod>,
wrap_mode: Option<gfx::texture::WrapMode>,
) -> Result<Texture<P>, RenderError> { ) -> Result<Texture<P>, RenderError> {
Texture::new(&mut self.factory, image) Texture::new(&mut self.factory, image, filter_method, wrap_mode)
} }
/// Create a new dynamic texture (gfx::memory::Usage::Dynamic) with the specified dimensions. /// Create a new dynamic texture (gfx::memory::Usage::Dynamic) with the specified dimensions.
@ -518,6 +520,7 @@ impl Renderer {
locals: &Consts<terrain::Locals>, locals: &Consts<terrain::Locals>,
lights: &Consts<Light>, lights: &Consts<Light>,
shadows: &Consts<Shadow>, shadows: &Consts<Shadow>,
waves: &Texture<fluid::FluidPipeline>,
) { ) {
self.encoder.draw( self.encoder.draw(
&gfx::Slice { &gfx::Slice {
@ -534,6 +537,7 @@ impl Renderer {
globals: globals.buf.clone(), globals: globals.buf.clone(),
lights: lights.buf.clone(), lights: lights.buf.clone(),
shadows: shadows.buf.clone(), shadows: shadows.buf.clone(),
waves: (waves.srv.clone(), waves.sampler.clone()),
tgt_color: self.tgt_color_view.clone(), tgt_color: self.tgt_color_view.clone(),
tgt_depth: self.tgt_depth_view.clone(), tgt_depth: self.tgt_depth_view.clone(),
}, },

View File

@ -24,6 +24,8 @@ impl<P: Pipeline> Texture<P> {
pub fn new( pub fn new(
factory: &mut gfx_backend::Factory, factory: &mut gfx_backend::Factory,
image: &DynamicImage, image: &DynamicImage,
filter_method: Option<gfx::texture::FilterMethod>,
wrap_mode: Option<gfx::texture::WrapMode>,
) -> Result<Self, RenderError> { ) -> Result<Self, RenderError> {
let (tex, srv) = factory let (tex, srv) = factory
.create_texture_immutable_u8::<ShaderFormat>( .create_texture_immutable_u8::<ShaderFormat>(
@ -41,12 +43,13 @@ impl<P: Pipeline> Texture<P> {
tex, tex,
srv, srv,
sampler: factory.create_sampler(gfx::texture::SamplerInfo::new( sampler: factory.create_sampler(gfx::texture::SamplerInfo::new(
gfx::texture::FilterMethod::Scale, filter_method.unwrap_or(gfx::texture::FilterMethod::Scale),
gfx::texture::WrapMode::Clamp, wrap_mode.unwrap_or(gfx::texture::WrapMode::Clamp),
)), )),
_phantom: PhantomData, _phantom: PhantomData,
}) })
} }
pub fn new_dynamic( pub fn new_dynamic(
factory: &mut gfx_backend::Factory, factory: &mut gfx_backend::Factory,
width: u16, width: u16,

View File

@ -2,7 +2,7 @@ use crate::{
mesh::Meshable, mesh::Meshable,
render::{ render::{
Consts, FluidPipeline, Globals, Instances, Light, Mesh, Model, Renderer, Shadow, Consts, FluidPipeline, Globals, Instances, Light, Mesh, Model, Renderer, Shadow,
SpriteInstance, SpritePipeline, TerrainLocals, TerrainPipeline, SpriteInstance, SpritePipeline, TerrainLocals, TerrainPipeline, Texture,
}, },
}; };
@ -10,7 +10,7 @@ use client::Client;
use common::{ use common::{
assets, assets,
figure::Segment, figure::Segment,
terrain::{Block, BlockKind}, terrain::{Block, BlockKind, TerrainChunk},
vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol, Vox}, vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol, Vox},
volumes::vol_grid_2d::{VolGrid2d, VolGrid2dError}, volumes::vol_grid_2d::{VolGrid2d, VolGrid2dError},
}; };
@ -21,11 +21,11 @@ use hashbrown::HashMap;
use std::{f32, fmt::Debug, i32, marker::PhantomData, ops::Mul, time::Duration}; use std::{f32, fmt::Debug, i32, marker::PhantomData, ops::Mul, time::Duration};
use vek::*; use vek::*;
struct TerrainChunk { struct TerrainChunkData {
// GPU data // GPU data
load_time: f32, load_time: f32,
opaque_model: Model<TerrainPipeline>, opaque_model: Model<TerrainPipeline>,
fluid_model: Model<FluidPipeline>, fluid_model: Option<Model<FluidPipeline>>,
sprite_instances: HashMap<(BlockKind, usize), Instances<SpriteInstance>>, sprite_instances: HashMap<(BlockKind, usize), Instances<SpriteInstance>>,
locals: Consts<TerrainLocals>, locals: Consts<TerrainLocals>,
@ -198,7 +198,7 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug>(
} }
pub struct Terrain<V: RectRasterableVol> { pub struct Terrain<V: RectRasterableVol> {
chunks: HashMap<Vec2<i32>, TerrainChunk>, chunks: HashMap<Vec2<i32>, TerrainChunkData>,
// The mpsc sender and receiver used for talking to meshing worker threads. // The mpsc sender and receiver used for talking to meshing worker threads.
// We keep the sender component for no reason other than to clone it and send it to new workers. // We keep the sender component for no reason other than to clone it and send it to new workers.
@ -208,6 +208,7 @@ pub struct Terrain<V: RectRasterableVol> {
// GPU data // GPU data
sprite_models: HashMap<(BlockKind, usize), Model<SpritePipeline>>, sprite_models: HashMap<(BlockKind, usize), Model<SpritePipeline>>,
waves: Texture<FluidPipeline>,
phantom: PhantomData<V>, phantom: PhantomData<V>,
} }
@ -656,6 +657,13 @@ impl<V: RectRasterableVol> Terrain<V> {
] ]
.into_iter() .into_iter()
.collect(), .collect(),
waves: renderer
.create_texture(
&assets::load_expect("voxygen.texture.waves"),
Some(gfx::texture::FilterMethod::Bilinear),
Some(gfx::texture::WrapMode::Tile),
)
.expect("Failed to create wave texture"),
phantom: PhantomData, phantom: PhantomData,
} }
} }
@ -862,14 +870,20 @@ impl<V: RectRasterableVol> Terrain<V> {
.unwrap_or(current_time as f32); .unwrap_or(current_time as f32);
self.chunks.insert( self.chunks.insert(
response.pos, response.pos,
TerrainChunk { TerrainChunkData {
load_time, load_time,
opaque_model: renderer opaque_model: renderer
.create_model(&response.opaque_mesh) .create_model(&response.opaque_mesh)
.expect("Failed to upload chunk mesh to the GPU!"), .expect("Failed to upload chunk mesh to the GPU!"),
fluid_model: renderer fluid_model: if response.fluid_mesh.vertices().len() > 0 {
.create_model(&response.fluid_mesh) Some(
.expect("Failed to upload chunk mesh to the GPU!"), renderer
.create_model(&response.fluid_mesh)
.expect("Failed to upload chunk mesh to the GPU!"),
)
} else {
None
},
sprite_instances: response sprite_instances: response
.sprite_instances .sprite_instances
.into_iter() .into_iter()
@ -954,8 +968,25 @@ impl<V: RectRasterableVol> Terrain<V> {
shadows: &Consts<Shadow>, shadows: &Consts<Shadow>,
focus_pos: Vec3<f32>, focus_pos: Vec3<f32>,
) { ) {
let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
(e as i32).div_euclid(sz as i32)
});
let chunks = &self.chunks;
let chunk_iter = Spiral2d::new()
.scan(0, |n, rpos| {
if *n >= chunks.len() {
None
} else {
*n += 1;
let pos = focus_chunk + rpos;
Some(chunks.get(&pos).map(|c| (pos, c)))
}
})
.filter_map(|x| x);
// Opaque // Opaque
for (_, chunk) in &self.chunks { for (_, chunk) in chunk_iter.clone() {
if chunk.visible { if chunk.visible {
renderer.render_terrain_chunk( renderer.render_terrain_chunk(
&chunk.opaque_model, &chunk.opaque_model,
@ -968,7 +999,7 @@ impl<V: RectRasterableVol> Terrain<V> {
} }
// Terrain sprites // Terrain sprites
for (pos, chunk) in &self.chunks { for (pos, chunk) in chunk_iter.clone() {
if chunk.visible { if chunk.visible {
const SPRITE_RENDER_DISTANCE: f32 = 128.0; const SPRITE_RENDER_DISTANCE: f32 = 128.0;
@ -991,16 +1022,53 @@ impl<V: RectRasterableVol> Terrain<V> {
} }
// Translucent // Translucent
for (_, chunk) in &self.chunks { chunk_iter
if chunk.visible { .clone()
renderer.render_fluid_chunk( .filter(|(_, chunk)| chunk.visible)
&chunk.fluid_model, .filter_map(|(_, chunk)| {
globals, chunk
&chunk.locals, .fluid_model
lights, .as_ref()
shadows, .map(|model| (model, &chunk.locals))
); })
} .for_each(|(model, locals)| {
} renderer.render_fluid_chunk(model, globals, locals, lights, shadows, &self.waves)
});
}
}
#[derive(Clone)]
struct Spiral2d {
layer: i32,
i: i32,
}
impl Spiral2d {
pub fn new() -> Self {
Self { layer: 0, i: 0 }
}
}
impl Iterator for Spiral2d {
type Item = Vec2<i32>;
fn next(&mut self) -> Option<Self::Item> {
let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1);
if self.i >= layer_size {
self.layer += 1;
self.i = 0;
}
let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1);
let pos = Vec2::new(
-self.layer + (self.i - (layer_size / 4) * 0).max(0).min(self.layer * 2)
- (self.i - (layer_size / 4) * 2).max(0).min(self.layer * 2),
-self.layer + (self.i - (layer_size / 4) * 1).max(0).min(self.layer * 2)
- (self.i - (layer_size / 4) * 3).max(0).min(self.layer * 2),
);
self.i += 1;
Some(pos)
} }
} }