mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/tethering' into 'master'
Allow entities to be tethered to one-another See merge request veloren/veloren!3953
This commit is contained in:
commit
dfd63bf23b
@ -233,4 +233,40 @@
|
||||
19: Air(Lantern, 4),
|
||||
},
|
||||
),
|
||||
Cart: (
|
||||
bone0: (
|
||||
offset: (-3.5, -3.5, 0.5),
|
||||
central: ("cart.structure"),
|
||||
),
|
||||
bone1: (
|
||||
offset: (-1.0, -2.5, -1.0),
|
||||
central: ("cart.axle"),
|
||||
),
|
||||
bone2: (
|
||||
offset: (-1.0, -2.5, -1.0),
|
||||
central: ("cart.axle"),
|
||||
),
|
||||
bone3: (
|
||||
offset: (0.0, 0.0, 0.0),
|
||||
central: ("empty"),
|
||||
),
|
||||
|
||||
custom_indices: {
|
||||
1: Air(ChairSingle, 4),
|
||||
2: Air(Helm, 0),
|
||||
3: Air(ChairSingle, 2),
|
||||
4: Air(ChairSingle, 6),
|
||||
9: Air(CraftingBench, 0),
|
||||
10: Air(Window1, 0),
|
||||
11: Air(RepairBench, 0),
|
||||
12: Air(DismantlingBench, 4),
|
||||
13: Air(Window1, 2),
|
||||
14: Air(Crate, 0),
|
||||
15: Air(Cauldron, 2),
|
||||
16: Air(ChairSingle, 0),
|
||||
17: Air(CookingPot, 0),
|
||||
18: Air(WallLampSmall, 4),
|
||||
19: Air(Lantern, 4),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
BIN
assets/common/voxel/cart/axle.vox
(Stored with Git LFS)
Normal file
BIN
assets/common/voxel/cart/axle.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/common/voxel/cart/structure.vox
(Stored with Git LFS)
Normal file
BIN
assets/common/voxel/cart/structure.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -804,4 +804,17 @@ vec3 simple_lighting(vec3 pos, vec3 col, float shade) {
|
||||
return col * clamp(2.5 / dot(d, d), shade * (get_sun_brightness() + 0.01), 1);
|
||||
}
|
||||
|
||||
float wind_wave(float off, float scaling, float speed, float strength) {
|
||||
float aspeed = abs(speed);
|
||||
|
||||
// TODO: Right now, the wind model is pretty simplistic. This means that there is frequently no wind at all, which
|
||||
// looks bad. For now, we add a lower bound on the wind speed to keep things looking nice.
|
||||
strength = max(strength, 6.0);
|
||||
aspeed = max(aspeed, 5.0);
|
||||
|
||||
return (sin(tick_loop(2.0 * PI, 0.35 * scaling * floor(aspeed), off)) * (1.0 - fract(aspeed))
|
||||
+ sin(tick_loop(2.0 * PI, 0.35 * scaling * ceil(aspeed), off)) * fract(aspeed)) * abs(strength) * 0.25;
|
||||
//return sin(tick.x * 1.5 * scaling + off) + sin(tick.x * 0.35 * scaling + off);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
237
assets/voxygen/shaders/rope-frag.glsl
Normal file
237
assets/voxygen/shaders/rope-frag.glsl
Normal file
@ -0,0 +1,237 @@
|
||||
#version 420 core
|
||||
|
||||
#define FIGURE_SHADER
|
||||
|
||||
#include <constants.glsl>
|
||||
|
||||
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
|
||||
|
||||
#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY
|
||||
|
||||
#if (FLUID_MODE == FLUID_MODE_LOW)
|
||||
#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE
|
||||
#elif (FLUID_MODE >= FLUID_MODE_MEDIUM)
|
||||
#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_RADIANCE
|
||||
#endif
|
||||
|
||||
#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET
|
||||
|
||||
#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN
|
||||
|
||||
#define HAS_SHADOW_MAPS
|
||||
|
||||
#include <globals.glsl>
|
||||
#include <light.glsl>
|
||||
#include <cloud.glsl>
|
||||
#include <lod.glsl>
|
||||
|
||||
layout(location = 0) in vec3 f_pos;
|
||||
// in float dummy;
|
||||
// in vec3 f_col;
|
||||
// in float f_ao;
|
||||
// flat in uint f_pos_norm;
|
||||
layout(location = 1) in vec3 f_norm;
|
||||
layout(location = 2) in vec3 m_pos;
|
||||
// in float f_alt;
|
||||
// in vec4 f_shadow;
|
||||
// in vec3 light_pos[2];
|
||||
|
||||
// #if (SHADOW_MODE == SHADOW_MODE_MAP)
|
||||
// in vec4 sun_pos;
|
||||
// #elif (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_NONE)
|
||||
// const vec4 sun_pos = vec4(0.0);
|
||||
// #endif
|
||||
|
||||
//struct ShadowLocals {
|
||||
// mat4 shadowMatrices;
|
||||
// mat4 texture_mat;
|
||||
//};
|
||||
//
|
||||
//layout (std140)
|
||||
//uniform u_light_shadows {
|
||||
// ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192];
|
||||
//};
|
||||
|
||||
layout (std140, set = 2, binding = 0)
|
||||
uniform u_locals {
|
||||
vec4 pos_a;
|
||||
vec4 pos_b;
|
||||
};
|
||||
|
||||
layout(location = 0) out vec4 tgt_color;
|
||||
layout(location = 1) out uvec4 tgt_mat;
|
||||
|
||||
void main() {
|
||||
// vec2 texSize = textureSize(t_col_light, 0);
|
||||
// vec4 col_light = texture(t_col_light, (f_uv_pos + 0.5) / texSize);
|
||||
// vec3 f_col = col_light.rgb;
|
||||
// float f_ao = col_light.a;
|
||||
|
||||
// vec4 f_col_light = texture(t_col_light, (f_uv_pos + 0.5) / textureSize(t_col_light, 0));
|
||||
// vec3 f_col = f_col_light.rgb;
|
||||
// float f_ao = f_col_light.a;
|
||||
|
||||
float f_ao = 1.0;
|
||||
vec3 f_col = mix(
|
||||
vec3(0.05, 0.03, 0.01),
|
||||
vec3(0.1, 0.07, 0.05),
|
||||
floor(abs(fract(m_pos.z * 10.0 + atan(m_pos.x, m_pos.y) * 0.159) - 0.5) * 6.0) / 3.0
|
||||
);
|
||||
|
||||
#ifdef EXPERIMENTAL_BAREMINIMUM
|
||||
tgt_color = vec4(simple_lighting(f_pos.xyz, f_col, f_ao), 1);
|
||||
return;
|
||||
#endif
|
||||
|
||||
// float /*f_light*/f_ao = textureProj(t_col_light, vec3(f_uv_pos, texSize)).a;//1.0;//f_col_light.a * 4.0;// f_light = float(v_col_light & 0x3Fu) / 64.0;
|
||||
|
||||
// vec3 my_chunk_pos = (vec3((uvec3(f_pos_norm) >> uvec3(0, 9, 18)) & uvec3(0x1FFu)) - 256.0) / 2.0;
|
||||
// tgt_color = vec4(hash(floor(vec4(my_chunk_pos.x, 0, 0, 0))), hash(floor(vec4(0, my_chunk_pos.y, 0, 1))), hash(floor(vec4(0, 0, my_chunk_pos.z, 2))), 1.0);
|
||||
// float f_ao = 0;
|
||||
// tgt_color = vec4(vec3(f_ao), 1.0);
|
||||
// tgt_color = vec4(f_col, 1.0);
|
||||
// return;
|
||||
|
||||
// vec3 du = dFdx(f_pos);
|
||||
// vec3 dv = dFdy(f_pos);
|
||||
// vec3 f_norm = normalize(cross(du, dv));
|
||||
|
||||
// vec4 light_pos[2];
|
||||
//#if (SHADOW_MODE == SHADOW_MODE_MAP)
|
||||
// // for (uint i = 0u; i < light_shadow_count.z; ++i) {
|
||||
// // light_pos[i] = /*vec3(*/shadowMats[i].texture_mat * vec4(f_pos, 1.0)/*)*/;
|
||||
// // }
|
||||
// vec4 sun_pos = /*vec3(*/shadowMats[0].texture_mat * vec4(f_pos, 1.0)/*)*/;
|
||||
//#elif (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_NONE)
|
||||
// vec4 sun_pos = vec4(0.0);
|
||||
//#endif
|
||||
|
||||
vec3 cam_to_frag = normalize(f_pos - cam_pos.xyz);
|
||||
// vec4 vert_pos4 = view_mat * vec4(f_pos, 1.0);
|
||||
// vec3 view_dir = normalize(-vec3(vert_pos4)/* / vert_pos4.w*/);
|
||||
vec3 view_dir = -cam_to_frag;
|
||||
|
||||
/* vec3 sun_dir = get_sun_dir(time_of_day.x);
|
||||
vec3 moon_dir = get_moon_dir(time_of_day.x); */
|
||||
// float sun_light = get_sun_brightness(sun_dir);
|
||||
// float moon_light = get_moon_brightness(moon_dir);
|
||||
/* float sun_shade_frac = horizon_at(f_pos, sun_dir);
|
||||
float moon_shade_frac = horizon_at(f_pos, moon_dir); */
|
||||
#if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP || FLUID_MODE >= FLUID_MODE_MEDIUM)
|
||||
float f_alt = alt_at(f_pos.xy);
|
||||
#elif (SHADOW_MODE == SHADOW_MODE_NONE || FLUID_MODE == FLUID_MODE_LOW)
|
||||
float f_alt = f_pos.z;
|
||||
#endif
|
||||
|
||||
#if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP)
|
||||
vec4 f_shadow = textureBicubic(t_horizon, s_horizon, pos_to_tex(f_pos.xy));
|
||||
float sun_shade_frac = horizon_at2(f_shadow, f_alt, f_pos, sun_dir);
|
||||
#elif (SHADOW_MODE == SHADOW_MODE_NONE)
|
||||
float sun_shade_frac = 1.0;//horizon_at2(f_shadow, f_alt, f_pos, sun_dir);
|
||||
#endif
|
||||
float moon_shade_frac = 1.0;// horizon_at2(f_shadow, f_alt, f_pos, moon_dir);
|
||||
// Globbal illumination "estimate" used to light the faces of voxels which are parallel to the sun or moon (which is a very common occurrence).
|
||||
// Will be attenuated by k_d, which is assumed to carry any additional ambient occlusion information (e.g. about shadowing).
|
||||
// float ambient_sides = clamp(mix(0.5, 0.0, abs(dot(-f_norm, sun_dir)) * 10000.0), 0.0, 0.5);
|
||||
// NOTE: current assumption is that moon and sun shouldn't be out at the sae time.
|
||||
// This assumption is (or can at least easily be) wrong, but if we pretend it's true we avoids having to explicitly pass in a separate shadow
|
||||
// for the sun and moon (since they have different brightnesses / colors so the shadows shouldn't attenuate equally).
|
||||
// float shade_frac = /*1.0;*/sun_shade_frac + moon_shade_frac;
|
||||
|
||||
// DirectionalLight sun_info = get_sun_info(sun_dir, sun_shade_frac, light_pos);
|
||||
DirectionalLight sun_info = get_sun_info(sun_dir, sun_shade_frac, /*sun_pos*/f_pos);
|
||||
DirectionalLight moon_info = get_moon_info(moon_dir, moon_shade_frac/*, light_pos*/);
|
||||
|
||||
vec3 surf_color = f_col;
|
||||
|
||||
float alpha = 1.0;
|
||||
const float n2 = 1.5;
|
||||
|
||||
const float R_s2s0 = pow((1.0 - n2) / (1.0 + n2), 2);
|
||||
const float R_s1s0 = pow((1.3325 - n2) / (1.3325 + n2), 2);
|
||||
const float R_s2s1 = pow((1.0 - 1.3325) / (1.0 + 1.3325), 2);
|
||||
const float R_s1s2 = pow((1.3325 - 1.0) / (1.3325 + 1.0), 2);
|
||||
float R_s = (f_pos.z < f_alt) ? mix(R_s2s1 * R_s1s0, R_s1s0, medium.x) : mix(R_s2s0, R_s1s2 * R_s2s0, medium.x);
|
||||
|
||||
vec3 k_a = vec3(1.0);
|
||||
vec3 k_d = vec3(1.0);
|
||||
vec3 k_s = vec3(R_s);
|
||||
|
||||
vec3 emitted_light, reflected_light;
|
||||
|
||||
// vec3 light_frac = /*vec3(1.0);*//*vec3(max(dot(f_norm, -sun_dir) * 0.5 + 0.5, 0.0));*/light_reflection_factor(f_norm, view_dir, vec3(0, 0, -1.0), vec3(1.0), vec3(R_s), alpha);
|
||||
// vec3 point_light = light_at(f_pos, f_norm);
|
||||
// vec3 light, diffuse_light, ambient_light;
|
||||
//get_sun_diffuse(f_norm, time_of_day.x, view_dir, k_a * point_shadow * (shade_frac * 0.5 + light_frac * 0.5), k_d * point_shadow * shade_frac, k_s * point_shadow * shade_frac, alpha, emitted_light, reflected_light);
|
||||
float max_light = 0.0;
|
||||
// reflected_light *= point_shadow * shade_frac;
|
||||
// emitted_light *= point_shadow * max(shade_frac, MIN_SHADOW);
|
||||
// max_light *= point_shadow * shade_frac;
|
||||
// reflected_light *= point_shadow;
|
||||
// emitted_light *= point_shadow;
|
||||
// max_light *= point_shadow;
|
||||
|
||||
vec3 cam_attenuation = vec3(1);
|
||||
float fluid_alt = max(f_pos.z + 1, floor(f_alt + 1));
|
||||
vec3 mu = medium.x == MEDIUM_WATER ? MU_WATER : vec3(0.0);
|
||||
#if (FLUID_MODE >= FLUID_MODE_MEDIUM)
|
||||
cam_attenuation =
|
||||
medium.x == MEDIUM_WATER ? compute_attenuation_point(cam_pos.xyz, view_dir, mu, fluid_alt, /*cam_pos.z <= fluid_alt ? cam_pos.xyz : f_pos*/f_pos)
|
||||
: compute_attenuation_point(f_pos, -view_dir, mu, fluid_alt, /*cam_pos.z <= fluid_alt ? cam_pos.xyz : f_pos*/cam_pos.xyz);
|
||||
#endif
|
||||
|
||||
// Prevent the sky affecting light when underground
|
||||
float not_underground = clamp((f_pos.z - f_alt) / 128.0 + 1.0, 0.0, 1.0);
|
||||
|
||||
max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, view_dir, f_pos, mu, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light);
|
||||
|
||||
max_light += lights_at(f_pos, f_norm, view_dir, mu, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light);
|
||||
|
||||
// TODO: Hack to add a small amount of underground ambient light to the scene
|
||||
reflected_light += vec3(0.01, 0.02, 0.03) * (1.0 - not_underground);
|
||||
|
||||
// Apply baked AO
|
||||
float ao = f_ao * sqrt(f_ao);//0.25 + f_ao * 0.75; ///*pow(f_ao, 0.5)*/f_ao * 0.85 + 0.15;
|
||||
reflected_light *= ao;
|
||||
emitted_light *= ao;
|
||||
|
||||
// Apply point light AO
|
||||
float point_shadow = shadow_at(f_pos, f_norm);
|
||||
reflected_light *= point_shadow;
|
||||
emitted_light *= point_shadow;
|
||||
|
||||
/* reflected_light *= cloud_shadow(f_pos); */
|
||||
/* vec3 point_light = light_at(f_pos, f_norm);
|
||||
emitted_light += point_light;
|
||||
reflected_light += point_light; */
|
||||
// get_sun_diffuse(f_norm, time_of_day.x, cam_to_frag, surf_color * f_light * point_shadow, 0.5 * surf_color * f_light * point_shadow, 0.5 * surf_color * f_light * point_shadow, 2.0, emitted_light, reflected_light);
|
||||
|
||||
// get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0);
|
||||
// diffuse_light *= point_shadow;
|
||||
// ambient_light *= point_shadow;
|
||||
// vec3 point_light = light_at(f_pos, f_norm);
|
||||
// light += point_light;
|
||||
// diffuse_light += point_light;
|
||||
// reflected_light += point_light;
|
||||
// vec3 surf_color = illuminate(srgb_to_linear(highlight_col.rgb * f_col), light, diffuse_light, ambient_light);
|
||||
|
||||
float reflectance = 0.0;
|
||||
// TODO: Do reflectance properly like this later
|
||||
vec3 reflect_color = vec3(0);
|
||||
|
||||
surf_color = illuminate(max_light, view_dir, mix(surf_color * emitted_light, reflect_color, reflectance), mix(surf_color * reflected_light, reflect_color, reflectance));
|
||||
|
||||
// if ((flags & 1) == 1 && int(cam_mode) == 1) {
|
||||
// float distance = distance(vec3(cam_pos), focus_pos.xyz) - 2;
|
||||
|
||||
// float opacity = clamp(distance / distance_divider, 0, 1);
|
||||
|
||||
// // if(threshold_matrix[int(gl_FragCoord.x) % 4][int(gl_FragCoord.y) % 4] > opacity) {
|
||||
// // discard;
|
||||
// // return;
|
||||
// // }
|
||||
// }
|
||||
|
||||
tgt_color = vec4(surf_color, 1.0);
|
||||
tgt_mat = uvec4(uvec3((f_norm + 1.0) * 127.0), MAT_FIGURE);
|
||||
}
|
58
assets/voxygen/shaders/rope-vert.glsl
Normal file
58
assets/voxygen/shaders/rope-vert.glsl
Normal file
@ -0,0 +1,58 @@
|
||||
#version 420 core
|
||||
|
||||
#include <constants.glsl>
|
||||
|
||||
#define FIGURE_SHADER
|
||||
|
||||
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
|
||||
|
||||
#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY
|
||||
|
||||
#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE
|
||||
|
||||
#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET
|
||||
|
||||
#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN
|
||||
|
||||
#include <globals.glsl>
|
||||
#include <sky.glsl>
|
||||
|
||||
layout(location = 0) in vec3 v_pos;
|
||||
layout(location = 1) in vec3 v_norm;
|
||||
|
||||
layout (std140, set = 2, binding = 0)
|
||||
uniform u_locals {
|
||||
vec4 pos_a;
|
||||
vec4 pos_b;
|
||||
float rope_length;
|
||||
};
|
||||
|
||||
layout(location = 0) out vec3 f_pos;
|
||||
layout(location = 1) out vec3 f_norm;
|
||||
layout(location = 2) out vec3 m_pos;
|
||||
|
||||
void main() {
|
||||
m_pos = v_pos;
|
||||
|
||||
vec3 rz = normalize(pos_b.xyz - pos_a.xyz);
|
||||
vec3 rx = normalize(cross(vec3(0, 0, 1), rz));
|
||||
vec3 ry = normalize(cross(rz, rx));
|
||||
float dist = distance(pos_a.xyz, pos_b.xyz);
|
||||
vec3 pos = pos_a.xyz + (rx * v_pos.x + ry * v_pos.y) * 0.1 + rz * v_pos.z * dist;
|
||||
vec2 ideal_wind_sway = wind_vel * vec2(
|
||||
wind_wave(pos.y * 1.5, 1.9, wind_vel.x, wind_vel.y),
|
||||
wind_wave(pos.x * 1.5, 2.1, wind_vel.y, wind_vel.x)
|
||||
);
|
||||
float dip = (1 - pow(abs(v_pos.z - 0.5) * 2.0, 2)) * max(rope_length - dist, 0.0);
|
||||
pos += vec3(ideal_wind_sway * min(pow(dip, 2), 0.005), -0.5 * dip);
|
||||
|
||||
f_pos = pos + focus_pos.xyz;
|
||||
|
||||
#ifdef EXPERIMENTAL_CURVEDWORLD
|
||||
f_pos.z -= pow(distance(f_pos.xy + focus_off.xy, focus_pos.xy + focus_off.xy) * 0.05, 2);
|
||||
#endif
|
||||
|
||||
f_norm = rx * v_norm.x + ry * v_norm.y + rz * v_norm.z;
|
||||
|
||||
gl_Position = all_mat * vec4(f_pos, 1);
|
||||
}
|
@ -76,19 +76,6 @@ vec4 nearest_entity(in vec3 sprite_pos, const float entity_radius_factor) {
|
||||
return closest;
|
||||
}
|
||||
|
||||
float wind_wave(float off, float scaling, float speed, float strength) {
|
||||
float aspeed = abs(speed);
|
||||
|
||||
// TODO: Right now, the wind model is pretty simplistic. This means that there is frequently no wind at all, which
|
||||
// looks bad. For now, we add a lower bound on the wind speed to keep things looking nice.
|
||||
strength = max(strength, 6.0);
|
||||
aspeed = max(aspeed, 5.0);
|
||||
|
||||
return (sin(tick_loop(2.0 * PI, 0.35 * scaling * floor(aspeed), off)) * (1.0 - fract(aspeed))
|
||||
+ sin(tick_loop(2.0 * PI, 0.35 * scaling * ceil(aspeed), off)) * fract(aspeed)) * abs(strength) * 0.25;
|
||||
//return sin(tick.x * 1.5 * scaling + off) + sin(tick.x * 0.35 * scaling + off);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Matrix to transform this sprite instance from model space to chunk space
|
||||
mat4 inst_mat;
|
||||
|
@ -35,6 +35,8 @@ macro_rules! synced_components {
|
||||
is_mount: IsMount,
|
||||
is_rider: IsRider,
|
||||
is_volume_rider: IsVolumeRider,
|
||||
is_leader: IsLeader,
|
||||
is_follower: IsFollower,
|
||||
mass: Mass,
|
||||
density: Density,
|
||||
collider: Collider,
|
||||
@ -74,7 +76,11 @@ macro_rules! reexport_comps {
|
||||
mod inner {
|
||||
pub use common::comp::*;
|
||||
use common::link::Is;
|
||||
use common::mounting::{Mount, Rider, VolumeRider};
|
||||
use common::{
|
||||
mounting::{Mount, Rider, VolumeRider},
|
||||
tether::{Leader, Follower},
|
||||
};
|
||||
|
||||
// We alias these because the identifier used for the
|
||||
// component's type is reused as an enum variant name
|
||||
// in the macro's that we pass to `synced_components!`.
|
||||
@ -84,6 +90,8 @@ macro_rules! reexport_comps {
|
||||
pub type IsMount = Is<Mount>;
|
||||
pub type IsRider = Is<Rider>;
|
||||
pub type IsVolumeRider = Is<VolumeRider>;
|
||||
pub type IsLeader = Is<Leader>;
|
||||
pub type IsFollower = Is<Follower>;
|
||||
}
|
||||
|
||||
// Re-export all the component types. So that uses of `synced_components!` outside this
|
||||
@ -182,6 +190,14 @@ impl NetSync for IsVolumeRider {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for IsLeader {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for IsFollower {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Mass {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
@ -688,6 +688,11 @@ impl ServerChatCommand {
|
||||
.collect(),
|
||||
Optional,
|
||||
),
|
||||
Boolean(
|
||||
"Whether the ship should be tethered to the target (or its mount)",
|
||||
"false".to_string(),
|
||||
Optional,
|
||||
),
|
||||
Float("destination_degrees_ccw_of_east", 90.0, Optional),
|
||||
],
|
||||
"Spawns a ship",
|
||||
@ -723,6 +728,7 @@ impl ServerChatCommand {
|
||||
Integer("amount", 1, Optional),
|
||||
Boolean("ai", "true".to_string(), Optional),
|
||||
Float("scale", 1.0, Optional),
|
||||
Boolean("tethered", "false".to_string(), Optional),
|
||||
],
|
||||
"Spawn a test entity",
|
||||
Some(Admin),
|
||||
|
@ -1198,6 +1198,7 @@ impl Body {
|
||||
ship::Body::Skiff => [1.0, -2.0, 2.0],
|
||||
ship::Body::Submarine => [1.0, -2.0, 2.0],
|
||||
ship::Body::Carriage => [1.0, -2.0, 2.0],
|
||||
ship::Body::Cart => [1.0, -2.0, 2.0],
|
||||
ship::Body::Volume => [0.0, 0.0, 0.0],
|
||||
},
|
||||
_ => [0.0, 0.0, 0.0],
|
||||
@ -1214,6 +1215,14 @@ impl Body {
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn tether_offset_leader(&self) -> Vec3<f32> {
|
||||
Vec3::new(0.0, self.dimensions().y * -0.4, self.dimensions().z * 0.7)
|
||||
}
|
||||
|
||||
pub fn tether_offset_follower(&self) -> Vec3<f32> {
|
||||
Vec3::new(0.0, self.dimensions().y * 0.6, self.dimensions().z * 0.7)
|
||||
}
|
||||
|
||||
pub fn localize(&self) -> Content {
|
||||
match self {
|
||||
Self::BipedLarge(biped_large) => biped_large.localize(),
|
||||
|
@ -19,12 +19,13 @@ pub const ALL_BODIES: [Body; 6] = [
|
||||
];
|
||||
|
||||
pub const ALL_AIRSHIPS: [Body; 2] = [Body::DefaultAirship, Body::AirBalloon];
|
||||
pub const ALL_SHIPS: [Body; 5] = [
|
||||
pub const ALL_SHIPS: [Body; 6] = [
|
||||
Body::SailBoat,
|
||||
Body::Galleon,
|
||||
Body::Skiff,
|
||||
Body::Submarine,
|
||||
Body::Carriage,
|
||||
Body::Cart,
|
||||
];
|
||||
|
||||
make_case_elim!(
|
||||
@ -40,6 +41,7 @@ make_case_elim!(
|
||||
Skiff = 5,
|
||||
Submarine = 6,
|
||||
Carriage = 7,
|
||||
Cart = 8,
|
||||
}
|
||||
);
|
||||
|
||||
@ -72,6 +74,7 @@ impl Body {
|
||||
Body::Skiff => Some("skiff.structure"),
|
||||
Body::Submarine => Some("submarine.structure"),
|
||||
Body::Carriage => Some("carriage.structure"),
|
||||
Body::Cart => Some("cart.structure"),
|
||||
Body::Volume => None,
|
||||
}
|
||||
}
|
||||
@ -84,7 +87,8 @@ impl Body {
|
||||
Body::Galleon => Vec3::new(14.0, 48.0, 10.0),
|
||||
Body::Skiff => Vec3::new(7.0, 15.0, 10.0),
|
||||
Body::Submarine => Vec3::new(2.0, 15.0, 8.0),
|
||||
Body::Carriage => Vec3::new(6.0, 12.0, 8.0),
|
||||
Body::Carriage => Vec3::new(5.0, 12.0, 2.0),
|
||||
Body::Cart => Vec3::new(3.0, 6.0, 1.0),
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,12 +123,20 @@ impl Body {
|
||||
Body::DefaultAirship | Body::AirBalloon | Body::Volume => Density(AIR_DENSITY),
|
||||
Body::Submarine => Density(WATER_DENSITY), // Neutrally buoyant
|
||||
Body::Carriage => Density(WATER_DENSITY * 0.5),
|
||||
Body::Cart => Density(500.0 / self.dimensions().product()), /* Carts get a constant */
|
||||
// mass
|
||||
_ => Density(AIR_DENSITY * 0.95 + WATER_DENSITY * 0.05), /* Most boats should be very
|
||||
* buoyant */
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mass(&self) -> Mass { Mass((self.hull_vol() + self.balloon_vol()) * self.density().0) }
|
||||
pub fn mass(&self) -> Mass {
|
||||
if self.can_fly() {
|
||||
Mass((self.hull_vol() + self.balloon_vol()) * self.density().0)
|
||||
} else {
|
||||
Mass(self.density().0 * self.dimensions().product())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_fly(&self) -> bool {
|
||||
matches!(self, Body::DefaultAirship | Body::AirBalloon | Body::Volume)
|
||||
@ -136,7 +148,7 @@ impl Body {
|
||||
matches!(self, Body::SailBoat | Body::Galleon | Body::Skiff)
|
||||
}
|
||||
|
||||
pub fn has_wheels(&self) -> bool { matches!(self, Body::Carriage) }
|
||||
pub fn has_wheels(&self) -> bool { matches!(self, Body::Carriage | Body::Cart) }
|
||||
|
||||
pub fn make_collider(&self) -> Collider {
|
||||
match self.manifest_entry() {
|
||||
|
@ -57,6 +57,7 @@ pub mod spiral;
|
||||
pub mod states;
|
||||
pub mod store;
|
||||
pub mod terrain;
|
||||
pub mod tether;
|
||||
pub mod time;
|
||||
pub mod trade;
|
||||
pub mod util;
|
||||
|
@ -2,6 +2,7 @@ use crate::{
|
||||
comp::{self, pet::is_mountable, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
|
||||
link::{Is, Link, LinkHandle, Role},
|
||||
terrain::{Block, TerrainGrid},
|
||||
tether,
|
||||
uid::{IdMaps, Uid},
|
||||
vol::ReadVol,
|
||||
};
|
||||
@ -45,6 +46,7 @@ impl Link for Mounting {
|
||||
WriteStorage<'a, Is<Mount>>,
|
||||
WriteStorage<'a, Is<Rider>>,
|
||||
ReadStorage<'a, Is<VolumeRider>>,
|
||||
ReadStorage<'a, Is<tether::Follower>>,
|
||||
);
|
||||
type DeleteData<'a> = (
|
||||
Read<'a, IdMaps>,
|
||||
@ -67,7 +69,7 @@ impl Link for Mounting {
|
||||
|
||||
fn create(
|
||||
this: &LinkHandle<Self>,
|
||||
(id_maps, is_mounts, is_riders, is_volume_rider): &mut Self::CreateData<'_>,
|
||||
(id_maps, is_mounts, is_riders, is_volume_rider, is_followers): &mut Self::CreateData<'_>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||
|
||||
@ -79,7 +81,7 @@ impl Link for Mounting {
|
||||
// relationship
|
||||
if !is_mounts.contains(mount)
|
||||
&& !is_riders.contains(rider)
|
||||
&& !is_riders.contains(rider)
|
||||
&& !is_followers.contains(rider)
|
||||
// TODO: Does this definitely prevent mount cycles?
|
||||
&& (!is_mounts.contains(rider) || !is_riders.contains(mount))
|
||||
&& !is_volume_rider.contains(rider)
|
||||
|
@ -150,7 +150,7 @@ impl Body {
|
||||
quadruped_low::Species::Driggle => 120.0,
|
||||
quadruped_low::Species::HermitAlligator => 120.0,
|
||||
},
|
||||
Body::Ship(ship::Body::Carriage) => 200.0,
|
||||
Body::Ship(ship::Body::Carriage) => 40.0,
|
||||
Body::Ship(_) => 0.0,
|
||||
Body::Arthropod(arthropod) => match arthropod.species {
|
||||
arthropod::Species::Tarantula => 135.0,
|
||||
|
123
common/src/tether.rs
Normal file
123
common/src/tether.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use crate::{
|
||||
comp,
|
||||
link::{Is, Link, LinkHandle, Role},
|
||||
mounting::{Rider, VolumeRider},
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Entities, Read, ReadStorage, WriteStorage};
|
||||
use vek::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Leader;
|
||||
|
||||
impl Role for Leader {
|
||||
type Link = Tethered;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Follower;
|
||||
|
||||
impl Role for Follower {
|
||||
type Link = Tethered;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Tethered {
|
||||
pub leader: Uid,
|
||||
pub follower: Uid,
|
||||
pub tether_length: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TetherError {
|
||||
NoSuchEntity,
|
||||
NotTetherable,
|
||||
}
|
||||
|
||||
impl Link for Tethered {
|
||||
type CreateData<'a> = (
|
||||
Read<'a, IdMaps>,
|
||||
WriteStorage<'a, Is<Leader>>,
|
||||
WriteStorage<'a, Is<Follower>>,
|
||||
ReadStorage<'a, Is<Rider>>,
|
||||
ReadStorage<'a, Is<VolumeRider>>,
|
||||
);
|
||||
type DeleteData<'a> = (
|
||||
Read<'a, IdMaps>,
|
||||
WriteStorage<'a, Is<Leader>>,
|
||||
WriteStorage<'a, Is<Follower>>,
|
||||
);
|
||||
type Error = TetherError;
|
||||
type PersistData<'a> = (
|
||||
Read<'a, IdMaps>,
|
||||
Entities<'a>,
|
||||
ReadStorage<'a, comp::Health>,
|
||||
ReadStorage<'a, Is<Leader>>,
|
||||
ReadStorage<'a, Is<Follower>>,
|
||||
);
|
||||
|
||||
fn create(
|
||||
this: &LinkHandle<Self>,
|
||||
(id_maps, is_leaders, is_followers, is_riders, is_volume_rider): &mut Self::CreateData<'_>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||
|
||||
if this.leader == this.follower {
|
||||
// Forbid self-tethering
|
||||
Err(TetherError::NotTetherable)
|
||||
} else if let Some((leader, follower)) = entity(this.leader).zip(entity(this.follower)) {
|
||||
// Ensure that neither leader or follower are already part of a conflicting
|
||||
// relationship
|
||||
if !is_riders.contains(follower)
|
||||
&& !is_volume_rider.contains(follower)
|
||||
&& !is_followers.contains(follower)
|
||||
// TODO: Does this definitely prevent tether cycles?
|
||||
&& (!is_leaders.contains(follower) || !is_followers.contains(leader))
|
||||
{
|
||||
let _ = is_leaders.insert(leader, this.make_role());
|
||||
let _ = is_followers.insert(follower, this.make_role());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TetherError::NotTetherable)
|
||||
}
|
||||
} else {
|
||||
Err(TetherError::NoSuchEntity)
|
||||
}
|
||||
}
|
||||
|
||||
fn persist(
|
||||
this: &LinkHandle<Self>,
|
||||
(id_maps, entities, healths, is_leaders, is_followers): &mut Self::PersistData<'_>,
|
||||
) -> bool {
|
||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||
|
||||
if let Some((leader, follower)) = entity(this.leader).zip(entity(this.follower)) {
|
||||
let is_alive = |entity| {
|
||||
entities.is_alive(entity) && healths.get(entity).map_or(true, |h| !h.is_dead)
|
||||
};
|
||||
|
||||
// Ensure that both entities are alive and that they continue to be linked
|
||||
is_alive(leader)
|
||||
&& is_alive(follower)
|
||||
&& is_leaders.get(leader).is_some()
|
||||
&& is_followers.get(follower).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn delete(
|
||||
this: &LinkHandle<Self>,
|
||||
(id_maps, is_leaders, is_followers): &mut Self::DeleteData<'_>,
|
||||
) {
|
||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||
|
||||
let leader = entity(this.leader);
|
||||
let follower = entity(this.follower);
|
||||
|
||||
// Delete link components
|
||||
leader.map(|leader| is_leaders.remove(leader));
|
||||
follower.map(|follower| is_followers.remove(follower));
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ use common::{
|
||||
shared_server_config::ServerConstants,
|
||||
slowjob::SlowJobPool,
|
||||
terrain::{Block, MapSizeLg, TerrainChunk, TerrainGrid},
|
||||
tether,
|
||||
time::DayPeriod,
|
||||
trade::Trades,
|
||||
vol::{ReadVol, WriteVol},
|
||||
@ -202,6 +203,8 @@ impl State {
|
||||
ecs.register::<Is<Mount>>();
|
||||
ecs.register::<Is<Rider>>();
|
||||
ecs.register::<Is<VolumeRider>>();
|
||||
ecs.register::<Is<tether::Leader>>();
|
||||
ecs.register::<Is<tether::Follower>>();
|
||||
ecs.register::<comp::Mass>();
|
||||
ecs.register::<comp::Density>();
|
||||
ecs.register::<comp::Collider>();
|
||||
|
@ -13,6 +13,7 @@ pub mod phys;
|
||||
pub mod projectile;
|
||||
mod shockwave;
|
||||
mod stats;
|
||||
mod tether;
|
||||
|
||||
// External
|
||||
use common_ecs::{dispatch, System};
|
||||
@ -21,6 +22,7 @@ use specs::DispatcherBuilder;
|
||||
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
//TODO: don't run interpolation on server
|
||||
dispatch::<interpolation::Sys>(dispatch_builder, &[]);
|
||||
dispatch::<tether::Sys>(dispatch_builder, &[]);
|
||||
dispatch::<mount::Sys>(dispatch_builder, &[]);
|
||||
dispatch::<controller::Sys>(dispatch_builder, &[&mount::Sys::sys_name()]);
|
||||
dispatch::<character_behavior::Sys>(dispatch_builder, &[&controller::Sys::sys_name()]);
|
||||
|
@ -863,6 +863,18 @@ impl<'a> PhysicsData<'a> {
|
||||
let climbing =
|
||||
character_state.map_or(false, |cs| matches!(cs, CharacterState::Climb(_)));
|
||||
|
||||
let friction_factor = |vel: Vec3<f32>| {
|
||||
if let Some(Body::Ship(ship)) = body && ship.has_wheels() {
|
||||
vel
|
||||
.try_normalized()
|
||||
.and_then(|dir| Some(orientations.get(entity)?.right().dot(dir).abs()))
|
||||
.unwrap_or(1.0)
|
||||
.max(0.2)
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
};
|
||||
|
||||
match &collider {
|
||||
Collider::Voxel { .. } | Collider::Volume(_) => {
|
||||
// For now, treat entities with voxel colliders
|
||||
@ -894,6 +906,7 @@ impl<'a> PhysicsData<'a> {
|
||||
},
|
||||
read,
|
||||
&ori,
|
||||
friction_factor,
|
||||
);
|
||||
tgt_pos = cpos.0;
|
||||
},
|
||||
@ -928,6 +941,7 @@ impl<'a> PhysicsData<'a> {
|
||||
},
|
||||
read,
|
||||
&ori,
|
||||
friction_factor,
|
||||
);
|
||||
|
||||
// Sticky things shouldn't move when on a surface
|
||||
@ -1214,6 +1228,7 @@ impl<'a> PhysicsData<'a> {
|
||||
},
|
||||
read,
|
||||
&ori,
|
||||
|vel| friction_factor(previous_cache_other.ori * vel),
|
||||
);
|
||||
|
||||
// Transform entity attributes back into world space now
|
||||
@ -1433,6 +1448,8 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
|
||||
mut land_on_ground: impl FnMut(Entity, Vel, Vec3<f32>),
|
||||
read: &PhysicsRead,
|
||||
ori: &Ori,
|
||||
// Get the proportion of surface friction that should be applied based on the current velocity
|
||||
friction_factor: impl Fn(Vec3<f32>) -> f32,
|
||||
) {
|
||||
// We cap out scale at 10.0 to prevent an enormous amount of lag
|
||||
let scale = read.scales.get(entity).map_or(1.0, |s| s.0.min(10.0));
|
||||
@ -1905,7 +1922,8 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
|
||||
} * physics_state
|
||||
.on_ground
|
||||
.map(|b| b.get_friction())
|
||||
.unwrap_or(0.0);
|
||||
.unwrap_or(0.0)
|
||||
* friction_factor(vel.0);
|
||||
let wall_fric = if physics_state.on_wall.is_some() && climbing {
|
||||
FRIC_GROUND
|
||||
} else {
|
||||
|
136
common/systems/src/tether.rs
Normal file
136
common/systems/src/tether.rs
Normal file
@ -0,0 +1,136 @@
|
||||
use common::{
|
||||
comp::{Body, Mass, Ori, Pos, Scale, Vel},
|
||||
link::Is,
|
||||
resources::DeltaTime,
|
||||
tether::Follower,
|
||||
uid::IdMaps,
|
||||
util::Dir,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{Entities, Join, LendJoin, Read, ReadStorage, WriteStorage};
|
||||
use vek::*;
|
||||
|
||||
/// This system is responsible for controlling mounts
|
||||
#[derive(Default)]
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Read<'a, IdMaps>,
|
||||
Entities<'a>,
|
||||
Read<'a, DeltaTime>,
|
||||
ReadStorage<'a, Is<Follower>>,
|
||||
ReadStorage<'a, Pos>,
|
||||
WriteStorage<'a, Vel>,
|
||||
WriteStorage<'a, Ori>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Mass>,
|
||||
);
|
||||
|
||||
const NAME: &'static str = "tether";
|
||||
const ORIGIN: Origin = Origin::Common;
|
||||
const PHASE: Phase = Phase::Create;
|
||||
|
||||
fn run(
|
||||
_job: &mut Job<Self>,
|
||||
(
|
||||
id_maps,
|
||||
entities,
|
||||
dt,
|
||||
is_followers,
|
||||
positions,
|
||||
mut velocities,
|
||||
mut orientations,
|
||||
bodies,
|
||||
scales,
|
||||
masses,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
for (follower, is_follower, follower_body, follower_scale) in
|
||||
(&entities, &is_followers, bodies.maybe(), scales.maybe()).join()
|
||||
{
|
||||
let Some(leader) = id_maps.uid_entity(is_follower.leader) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let (Some(leader_pos), Some(follower_pos)) = (
|
||||
positions.get(leader).copied(),
|
||||
positions.get(follower).copied(),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let (Some(leader_mass), Some(follower_mass)) =
|
||||
(masses.get(leader).copied(), masses.get(follower).copied())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if velocities.contains(follower) && velocities.contains(leader) {
|
||||
let attach_offset = orientations
|
||||
.get(leader)
|
||||
.map(|ori| {
|
||||
ori.to_quat()
|
||||
* bodies
|
||||
.get(leader)
|
||||
.map(|b| {
|
||||
b.tether_offset_leader()
|
||||
* scales.get(leader).copied().unwrap_or(Scale(1.0)).0
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let attach_pos = leader_pos.0 + attach_offset;
|
||||
|
||||
let tether_offset = orientations
|
||||
.get(follower)
|
||||
.map(|ori| {
|
||||
ori.to_quat()
|
||||
* follower_body
|
||||
.map(|b| {
|
||||
b.tether_offset_follower()
|
||||
* follower_scale.copied().unwrap_or(Scale(1.0)).0
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let tether_pos = follower_pos.0 + tether_offset;
|
||||
let pull_factor =
|
||||
(attach_pos.distance(tether_pos) - is_follower.tether_length).max(0.0);
|
||||
let strength = pull_factor * 50000.0;
|
||||
let pull_dir = (leader_pos.0 - follower_pos.0)
|
||||
.try_normalized()
|
||||
.unwrap_or(Vec3::unit_y());
|
||||
let impulse = pull_dir * strength * dt.0;
|
||||
|
||||
// Can't fail
|
||||
velocities.get_mut(follower).unwrap().0 += impulse / follower_mass.0;
|
||||
velocities.get_mut(leader).unwrap().0 -= impulse / leader_mass.0;
|
||||
|
||||
if let Some(follower_ori) = orientations.get_mut(follower) {
|
||||
let turn_strength = pull_factor
|
||||
* (tether_offset.magnitude() * tether_pos.distance(attach_pos)
|
||||
- tether_offset.dot(attach_pos - tether_pos).abs())
|
||||
// TODO: proper moment of inertia
|
||||
* 500.0
|
||||
/ follower_mass.0;
|
||||
// TODO: Should consider the offset
|
||||
let target_ori = follower_ori.yawed_towards(Dir::new(pull_dir));
|
||||
*follower_ori = follower_ori.slerped_towards(target_ori, turn_strength * dt.0);
|
||||
}
|
||||
|
||||
if let Some(leader_ori) = orientations.get_mut(leader) {
|
||||
let turn_strength = pull_factor
|
||||
* (attach_offset.magnitude() * tether_pos.distance(attach_pos)
|
||||
- attach_offset.dot(tether_pos - attach_pos).abs())
|
||||
// TODO: proper moment of inertia
|
||||
* 500.0
|
||||
/ leader_mass.0;
|
||||
// TODO: Should consider the offset
|
||||
let target_ori = leader_ori.yawed_towards(Dir::new(pull_dir));
|
||||
*leader_ori = leader_ori.slerped_towards(target_ori, turn_strength * dt.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -40,12 +40,15 @@ use common::{
|
||||
effect::Effect,
|
||||
event::{EventBus, ServerEvent},
|
||||
generation::{EntityConfig, EntityInfo},
|
||||
link::Is,
|
||||
mounting::{Rider, Volume, VolumeRider},
|
||||
npc::{self, get_npc_name},
|
||||
outcome::Outcome,
|
||||
parse_cmd_args,
|
||||
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale},
|
||||
rtsim::{Actor, Role},
|
||||
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
|
||||
tether::Tethered,
|
||||
uid::Uid,
|
||||
vol::ReadVol,
|
||||
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
||||
@ -1558,8 +1561,15 @@ fn handle_spawn(
|
||||
args: Vec<String>,
|
||||
action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
match parse_cmd_args!(args, String, npc::NpcBody, u32, bool, f32) {
|
||||
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount, opt_ai, opt_scale) => {
|
||||
match parse_cmd_args!(args, String, npc::NpcBody, u32, bool, f32, bool) {
|
||||
(
|
||||
Some(opt_align),
|
||||
Some(npc::NpcBody(id, mut body)),
|
||||
opt_amount,
|
||||
opt_ai,
|
||||
opt_scale,
|
||||
opt_tethered,
|
||||
) => {
|
||||
let uid = uid(server, target, "target")?;
|
||||
let alignment = parse_alignment(uid, &opt_align)?;
|
||||
let amount = opt_amount.filter(|x| *x > 0).unwrap_or(1).min(50);
|
||||
@ -1608,6 +1618,28 @@ fn handle_spawn(
|
||||
|
||||
let new_entity = entity_base.build();
|
||||
|
||||
if opt_tethered == Some(true) {
|
||||
let tether_leader = server
|
||||
.state
|
||||
.read_component_cloned::<Is<Rider>>(target)
|
||||
.map(|is_rider| is_rider.mount)
|
||||
.or_else(|| server.state.ecs().uid_from_entity(target));
|
||||
let tether_follower = server.state.ecs().uid_from_entity(new_entity);
|
||||
|
||||
if let (Some(leader), Some(follower)) = (tether_leader, tether_follower) {
|
||||
server
|
||||
.state
|
||||
.link(Tethered {
|
||||
leader,
|
||||
follower,
|
||||
tether_length: 4.0,
|
||||
})
|
||||
.map_err(|_| "Failed to tether entities")?;
|
||||
} else {
|
||||
return Err("Tether members don't have Uids.".into());
|
||||
}
|
||||
}
|
||||
|
||||
// Add to group system if a pet
|
||||
if matches!(alignment, comp::Alignment::Owned { .. }) {
|
||||
let server_eventbus =
|
||||
@ -1748,7 +1780,7 @@ fn handle_spawn_ship(
|
||||
args: Vec<String>,
|
||||
_action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
let (body_name, angle) = parse_cmd_args!(args, String, f32);
|
||||
let (body_name, tethered, angle) = parse_cmd_args!(args, String, bool, f32);
|
||||
let mut pos = position(server, target, "target")?;
|
||||
pos.0.z += 2.0;
|
||||
const DESTINATION_RADIUS: f32 = 2000.0;
|
||||
@ -1767,6 +1799,7 @@ fn handle_spawn_ship(
|
||||
let mut builder = server
|
||||
.state
|
||||
.create_ship(pos, ori, ship, |ship| ship.make_collider());
|
||||
|
||||
if let Some(pos) = destination {
|
||||
let (kp, ki, kd) =
|
||||
comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.0, 0.0, 0.0));
|
||||
@ -1776,7 +1809,47 @@ fn handle_spawn_ship(
|
||||
.with_position_pid_controller(comp::PidController::new(kp, ki, kd, pos, 0.0, pure_z));
|
||||
builder = builder.with(agent);
|
||||
}
|
||||
builder.build();
|
||||
|
||||
let new_entity = builder.build();
|
||||
|
||||
if tethered == Some(true) {
|
||||
let tether_leader = server
|
||||
.state
|
||||
.read_component_cloned::<Is<Rider>>(target)
|
||||
.map(|is_rider| is_rider.mount)
|
||||
.or_else(|| {
|
||||
server
|
||||
.state
|
||||
.read_component_cloned::<Is<VolumeRider>>(target)
|
||||
.and_then(|is_volume_rider| {
|
||||
if let Volume::Entity(uid) = is_volume_rider.pos.kind {
|
||||
Some(uid)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.or_else(|| server.state.ecs().uid_from_entity(target));
|
||||
let tether_follower = server.state.ecs().uid_from_entity(new_entity);
|
||||
|
||||
if let (Some(leader), Some(follower)) = (tether_leader, tether_follower) {
|
||||
let tether_length = tether_leader
|
||||
.and_then(|uid| server.state.ecs().entity_from_uid(uid))
|
||||
.and_then(|e| server.state.read_component_cloned::<comp::Body>(e))
|
||||
.map(|b| b.dimensions().y * 1.5 + 1.0)
|
||||
.unwrap_or(6.0);
|
||||
server
|
||||
.state
|
||||
.link(Tethered {
|
||||
leader,
|
||||
follower,
|
||||
tether_length,
|
||||
})
|
||||
.map_err(|_| "Failed to tether entities")?;
|
||||
} else {
|
||||
return Err("Tether members don't have Uids.".into());
|
||||
}
|
||||
}
|
||||
|
||||
server.notify_client(
|
||||
client,
|
||||
|
@ -116,6 +116,7 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
||||
Skiff => (0.0, 0.0, 0.0),
|
||||
Submarine => (0.0, 0.0, 0.0),
|
||||
Carriage => (0.0, 0.0, 0.0),
|
||||
Cart => (0.0, 0.0, 0.0),
|
||||
Volume => (0.0, 0.0, 0.0),
|
||||
},
|
||||
bone1: match body {
|
||||
@ -126,6 +127,7 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
||||
Skiff => (0.0, 0.0, 0.0),
|
||||
Submarine => (0.0, -15.0, 3.5),
|
||||
Carriage => (0.0, 3.0, 2.0),
|
||||
Cart => (0.0, 1.0, 1.0),
|
||||
Volume => (0.0, 0.0, 0.0),
|
||||
},
|
||||
bone2: match body {
|
||||
@ -136,6 +138,7 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
||||
Skiff => (0.0, 0.0, 0.0),
|
||||
Submarine => (0.0, 0.0, 0.0),
|
||||
Carriage => (0.0, -3.0, 2.0),
|
||||
Cart => (0.0, -2.5, 1.0),
|
||||
Volume => (0.0, 0.0, 0.0),
|
||||
},
|
||||
bone3: match body {
|
||||
@ -146,18 +149,20 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
||||
Skiff => (0.0, 0.0, 0.0),
|
||||
Submarine => (0.0, -18.0, 3.5),
|
||||
Carriage => (0.0, 0.0, 0.0),
|
||||
Cart => (0.0, 0.0, 0.0),
|
||||
Volume => (0.0, 0.0, 0.0),
|
||||
},
|
||||
bone1_ori: match body {
|
||||
Carriage => std::f32::consts::PI * 0.5,
|
||||
Carriage | Cart => std::f32::consts::PI * 0.5,
|
||||
_ => 0.0,
|
||||
},
|
||||
bone2_ori: match body {
|
||||
Carriage => std::f32::consts::PI * -0.5,
|
||||
Carriage | Cart => std::f32::consts::PI * -0.5,
|
||||
_ => 0.0,
|
||||
},
|
||||
bone_rotation_rate: match body {
|
||||
Carriage => 0.25,
|
||||
Cart => 0.4,
|
||||
_ => 0.8,
|
||||
},
|
||||
bone1_prop_trail_offset: match body {
|
||||
|
@ -151,9 +151,9 @@ impl<V: Vertex> FromIterator<Quad<V>> for Mesh<V> {
|
||||
|
||||
/// Represents a triangle stored on the CPU.
|
||||
pub struct Tri<V: Vertex> {
|
||||
a: V,
|
||||
b: V,
|
||||
c: V,
|
||||
pub a: V,
|
||||
pub b: V,
|
||||
pub c: V,
|
||||
}
|
||||
|
||||
impl<V: Vertex> Tri<V> {
|
||||
@ -162,10 +162,10 @@ impl<V: Vertex> Tri<V> {
|
||||
|
||||
/// Represents a quad stored on the CPU.
|
||||
pub struct Quad<V: Vertex> {
|
||||
a: V,
|
||||
b: V,
|
||||
c: V,
|
||||
d: V,
|
||||
pub a: V,
|
||||
pub b: V,
|
||||
pub c: V,
|
||||
pub d: V,
|
||||
}
|
||||
|
||||
impl<V: Vertex> Quad<V> {
|
||||
|
@ -9,6 +9,7 @@ pub mod lod_terrain;
|
||||
pub mod particle;
|
||||
pub mod postprocess;
|
||||
pub mod rain_occlusion;
|
||||
pub mod rope;
|
||||
pub mod shadow;
|
||||
pub mod skybox;
|
||||
pub mod sprite;
|
||||
|
203
voxygen/src/render/pipelines/rope.rs
Normal file
203
voxygen/src/render/pipelines/rope.rs
Normal file
@ -0,0 +1,203 @@
|
||||
use super::super::{AaMode, Bound, Consts, GlobalsLayouts, Vertex as VertexTrait};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use std::mem;
|
||||
use vek::*;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
||||
pub struct Locals {
|
||||
pos_a: [f32; 4],
|
||||
pos_b: [f32; 4],
|
||||
rope_length: f32,
|
||||
}
|
||||
|
||||
impl Locals {
|
||||
pub fn new(pos_a: Vec3<f32>, pos_b: Vec3<f32>, rope_length: f32) -> Self {
|
||||
Self {
|
||||
pos_a: pos_a.with_w(0.0).into_array(),
|
||||
pos_b: pos_b.with_w(0.0).into_array(),
|
||||
rope_length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Locals {
|
||||
fn default() -> Self { Self::new(Vec3::zero(), Vec3::zero(), 0.0) }
|
||||
}
|
||||
|
||||
pub type BoundLocals = Bound<Consts<Locals>>;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
||||
pub struct Vertex {
|
||||
pub pos: [f32; 3],
|
||||
pub norm: [f32; 3],
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
pub fn new(pos: Vec3<f32>, norm: Vec3<f32>) -> Self {
|
||||
Self {
|
||||
pos: pos.into_array(),
|
||||
norm: norm.into_array(),
|
||||
}
|
||||
}
|
||||
|
||||
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
const ATTRIBUTES: [wgpu::VertexAttribute; 2] =
|
||||
wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3];
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: Self::STRIDE,
|
||||
step_mode: wgpu::InputStepMode::Vertex,
|
||||
attributes: &ATTRIBUTES,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VertexTrait for Vertex {
|
||||
const QUADS_INDEX: Option<wgpu::IndexFormat> = Some(wgpu::IndexFormat::Uint16);
|
||||
const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
|
||||
}
|
||||
|
||||
pub struct RopeLayout {
|
||||
pub locals: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
impl RopeLayout {
|
||||
pub fn new(device: &wgpu::Device) -> Self {
|
||||
Self {
|
||||
locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: None,
|
||||
entries: &[
|
||||
// locals
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts<Locals>) -> BoundLocals {
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: None,
|
||||
layout: &self.locals,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: locals.buf().as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
|
||||
BoundLocals {
|
||||
bind_group,
|
||||
with: locals,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RopePipeline {
|
||||
pub pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl RopePipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
vs_module: &wgpu::ShaderModule,
|
||||
fs_module: &wgpu::ShaderModule,
|
||||
global_layout: &GlobalsLayouts,
|
||||
layout: &RopeLayout,
|
||||
aa_mode: AaMode,
|
||||
) -> Self {
|
||||
common_base::span!(_guard, "RopePipeline::new");
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Rope pipeline layout"),
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[
|
||||
&global_layout.globals,
|
||||
&global_layout.shadow_textures,
|
||||
&layout.locals,
|
||||
],
|
||||
});
|
||||
|
||||
let samples = aa_mode.samples();
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Rope pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: vs_module,
|
||||
entry_point: "main",
|
||||
buffers: &[Vertex::desc()],
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
clamp_depth: false,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::GreaterEqual,
|
||||
stencil: wgpu::StencilState {
|
||||
front: wgpu::StencilFaceState::IGNORE,
|
||||
back: wgpu::StencilFaceState::IGNORE,
|
||||
read_mask: !0,
|
||||
write_mask: !0,
|
||||
},
|
||||
bias: wgpu::DepthBiasState {
|
||||
constant: 0,
|
||||
slope_scale: 0.0,
|
||||
clamp: 0.0,
|
||||
},
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: samples,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: fs_module,
|
||||
entry_point: "main",
|
||||
targets: &[
|
||||
wgpu::ColorTargetState {
|
||||
// TODO: use a constant and/or pass in this format on pipeline construction
|
||||
format: wgpu::TextureFormat::Rgba16Float,
|
||||
blend: Some(wgpu::BlendState {
|
||||
color: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::SrcAlpha,
|
||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
alpha: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::One,
|
||||
dst_factor: wgpu::BlendFactor::One,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
}),
|
||||
write_mask: wgpu::ColorWrite::ALL,
|
||||
},
|
||||
wgpu::ColorTargetState {
|
||||
format: wgpu::TextureFormat::Rgba8Uint,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrite::ALL,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
Self {
|
||||
pipeline: render_pipeline,
|
||||
}
|
||||
}
|
||||
}
|
@ -24,8 +24,8 @@ use super::{
|
||||
mesh::Mesh,
|
||||
model::{DynamicModel, Model},
|
||||
pipelines::{
|
||||
blit, bloom, clouds, debug, figure, postprocess, rain_occlusion, shadow, sprite, terrain,
|
||||
ui, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup,
|
||||
blit, bloom, clouds, debug, figure, postprocess, rain_occlusion, rope, shadow, sprite,
|
||||
terrain, ui, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup,
|
||||
},
|
||||
texture::Texture,
|
||||
AddressMode, FilterMode, OtherModes, PipelineModes, RenderError, RenderMode, ShadowMapMode,
|
||||
@ -54,6 +54,7 @@ struct ImmutableLayouts {
|
||||
rain_occlusion: rain_occlusion::RainOcclusionLayout,
|
||||
sprite: sprite::SpriteLayout,
|
||||
terrain: terrain::TerrainLayout,
|
||||
rope: rope::RopeLayout,
|
||||
clouds: clouds::CloudsLayout,
|
||||
bloom: bloom::BloomLayout,
|
||||
ui: ui::UiLayout,
|
||||
@ -383,6 +384,7 @@ impl Renderer {
|
||||
let rain_occlusion = rain_occlusion::RainOcclusionLayout::new(&device);
|
||||
let sprite = sprite::SpriteLayout::new(&device);
|
||||
let terrain = terrain::TerrainLayout::new(&device);
|
||||
let rope = rope::RopeLayout::new(&device);
|
||||
let clouds = clouds::CloudsLayout::new(&device);
|
||||
let bloom = bloom::BloomLayout::new(&device);
|
||||
let postprocess = Arc::new(postprocess::PostProcessLayout::new(
|
||||
@ -402,6 +404,7 @@ impl Renderer {
|
||||
rain_occlusion,
|
||||
sprite,
|
||||
terrain,
|
||||
rope,
|
||||
clouds,
|
||||
bloom,
|
||||
ui,
|
||||
|
@ -3,7 +3,7 @@ use crate::render::pipelines::rain_occlusion;
|
||||
use super::{
|
||||
super::{
|
||||
pipelines::{
|
||||
debug, figure, lod_terrain, shadow, sprite, terrain, ui, AtlasTextures,
|
||||
debug, figure, lod_terrain, rope, shadow, sprite, terrain, ui, AtlasTextures,
|
||||
FigureSpriteAtlasData, GlobalModel, GlobalsBindGroup, TerrainAtlasData,
|
||||
},
|
||||
texture::Texture,
|
||||
@ -67,6 +67,11 @@ impl Renderer {
|
||||
.bind_locals(&self.device, locals, bone_data)
|
||||
}
|
||||
|
||||
pub fn create_rope_bound_locals(&mut self, locals: &[rope::Locals]) -> rope::BoundLocals {
|
||||
let locals = self.create_consts(locals);
|
||||
self.layouts.rope.bind_locals(&self.device, locals)
|
||||
}
|
||||
|
||||
pub fn create_terrain_bound_locals(
|
||||
&mut self,
|
||||
locals: &[terrain::Locals],
|
||||
|
@ -6,8 +6,8 @@ use super::{
|
||||
instances::Instances,
|
||||
model::{DynamicModel, Model, SubModel},
|
||||
pipelines::{
|
||||
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle, shadow,
|
||||
skybox, sprite, terrain, trail, ui, AtlasTextures, FigureSpriteAtlasData,
|
||||
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle, rope,
|
||||
shadow, skybox, sprite, terrain, trail, ui, AtlasTextures, FigureSpriteAtlasData,
|
||||
GlobalsBindGroup, TerrainAtlasData,
|
||||
},
|
||||
AltIndices, CullingMode,
|
||||
@ -966,6 +966,15 @@ impl<'pass> FirstPassDrawer<'pass> {
|
||||
ParticleDrawer { render_pass }
|
||||
}
|
||||
|
||||
pub fn draw_ropes(&mut self) -> RopeDrawer<'_, 'pass> {
|
||||
let mut render_pass = self.render_pass.scope("ropes", self.borrow.device);
|
||||
|
||||
render_pass.set_pipeline(&self.pipelines.rope.pipeline);
|
||||
set_quad_index_buffer::<rope::Vertex>(&mut render_pass, self.borrow);
|
||||
|
||||
RopeDrawer { render_pass }
|
||||
}
|
||||
|
||||
pub fn draw_sprites<'data: 'pass>(
|
||||
&mut self,
|
||||
globals: &'data sprite::SpriteGlobalsBindGroup,
|
||||
@ -1111,6 +1120,28 @@ impl<'pass_ref, 'pass: 'pass_ref> ParticleDrawer<'pass_ref, 'pass> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub struct RopeDrawer<'pass_ref, 'pass: 'pass_ref> {
|
||||
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
|
||||
}
|
||||
|
||||
impl<'pass_ref, 'pass: 'pass_ref> RopeDrawer<'pass_ref, 'pass> {
|
||||
// Note: if we ever need to draw less than the whole model, these APIs can be
|
||||
// changed
|
||||
pub fn draw<'data: 'pass>(
|
||||
&mut self,
|
||||
model: &'data Model<rope::Vertex>,
|
||||
locals: &'data rope::BoundLocals,
|
||||
) {
|
||||
self.render_pass.set_vertex_buffer(0, model.buf().slice(..));
|
||||
self.render_pass.set_bind_group(2, &locals.bind_group, &[]);
|
||||
// TODO: since we cast to u32 maybe this should returned by the len/count
|
||||
// functions?
|
||||
self.render_pass
|
||||
.draw_indexed(0..model.len() as u32 / 4 * 6, 0, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub struct SpriteDrawer<'pass_ref, 'pass: 'pass_ref> {
|
||||
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
|
||||
|
@ -4,7 +4,7 @@ use super::{
|
||||
super::{
|
||||
pipelines::{
|
||||
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle,
|
||||
postprocess, shadow, skybox, sprite, terrain, trail, ui,
|
||||
postprocess, rope, shadow, skybox, sprite, terrain, trail, ui,
|
||||
},
|
||||
AaMode, BloomMode, CloudMode, FluidMode, LightingMode, PipelineModes, ReflectionMode,
|
||||
RenderError, ShadowMode,
|
||||
@ -23,6 +23,7 @@ pub struct Pipelines {
|
||||
pub fluid: fluid::FluidPipeline,
|
||||
pub lod_terrain: lod_terrain::LodTerrainPipeline,
|
||||
pub particle: particle::ParticlePipeline,
|
||||
pub rope: rope::RopePipeline,
|
||||
pub trail: trail::TrailPipeline,
|
||||
pub clouds: clouds::CloudsPipeline,
|
||||
pub bloom: Option<bloom::BloomPipelines>,
|
||||
@ -46,6 +47,7 @@ pub struct IngamePipelines {
|
||||
fluid: fluid::FluidPipeline,
|
||||
lod_terrain: lod_terrain::LodTerrainPipeline,
|
||||
particle: particle::ParticlePipeline,
|
||||
rope: rope::RopePipeline,
|
||||
trail: trail::TrailPipeline,
|
||||
clouds: clouds::CloudsPipeline,
|
||||
pub bloom: Option<bloom::BloomPipelines>,
|
||||
@ -93,6 +95,7 @@ impl Pipelines {
|
||||
fluid: ingame.fluid,
|
||||
lod_terrain: ingame.lod_terrain,
|
||||
particle: ingame.particle,
|
||||
rope: ingame.rope,
|
||||
trail: ingame.trail,
|
||||
clouds: ingame.clouds,
|
||||
bloom: ingame.bloom,
|
||||
@ -127,6 +130,8 @@ struct ShaderModules {
|
||||
lod_object_frag: wgpu::ShaderModule,
|
||||
particle_vert: wgpu::ShaderModule,
|
||||
particle_frag: wgpu::ShaderModule,
|
||||
rope_vert: wgpu::ShaderModule,
|
||||
rope_frag: wgpu::ShaderModule,
|
||||
trail_vert: wgpu::ShaderModule,
|
||||
trail_frag: wgpu::ShaderModule,
|
||||
ui_vert: wgpu::ShaderModule,
|
||||
@ -339,6 +344,8 @@ impl ShaderModules {
|
||||
lod_object_frag: create_shader("lod-object-frag", ShaderKind::Fragment)?,
|
||||
particle_vert: create_shader("particle-vert", ShaderKind::Vertex)?,
|
||||
particle_frag: create_shader("particle-frag", ShaderKind::Fragment)?,
|
||||
rope_vert: create_shader("rope-vert", ShaderKind::Vertex)?,
|
||||
rope_frag: create_shader("rope-frag", ShaderKind::Fragment)?,
|
||||
trail_vert: create_shader("trail-vert", ShaderKind::Vertex)?,
|
||||
trail_frag: create_shader("trail-frag", ShaderKind::Fragment)?,
|
||||
ui_vert: create_shader("ui-vert", ShaderKind::Vertex)?,
|
||||
@ -500,7 +507,7 @@ fn create_ingame_and_shadow_pipelines(
|
||||
needs: PipelineNeeds,
|
||||
pool: &rayon::ThreadPool,
|
||||
// TODO: Reduce the boilerplate in this file
|
||||
tasks: [Task; 19],
|
||||
tasks: [Task; 20],
|
||||
) -> IngameAndShadowPipelines {
|
||||
prof_span!(_guard, "create_ingame_and_shadow_pipelines");
|
||||
|
||||
@ -521,6 +528,7 @@ fn create_ingame_and_shadow_pipelines(
|
||||
sprite_task,
|
||||
lod_object_task,
|
||||
particle_task,
|
||||
rope_task,
|
||||
trail_task,
|
||||
lod_terrain_task,
|
||||
clouds_task,
|
||||
@ -665,6 +673,22 @@ fn create_ingame_and_shadow_pipelines(
|
||||
"particle pipeline creation",
|
||||
)
|
||||
};
|
||||
// Pipeline for rendering ropes
|
||||
let create_rope = || {
|
||||
rope_task.run(
|
||||
|| {
|
||||
rope::RopePipeline::new(
|
||||
device,
|
||||
&shaders.rope_vert,
|
||||
&shaders.rope_frag,
|
||||
&layouts.global,
|
||||
&layouts.rope,
|
||||
pipeline_modes.aa,
|
||||
)
|
||||
},
|
||||
"rope pipeline creation",
|
||||
)
|
||||
};
|
||||
// Pipeline for rendering weapon trails
|
||||
let create_trail = || {
|
||||
trail_task.run(
|
||||
@ -888,6 +912,7 @@ fn create_ingame_and_shadow_pipelines(
|
||||
)
|
||||
})
|
||||
};
|
||||
let j8 = create_rope;
|
||||
|
||||
// Ignore this
|
||||
let (
|
||||
@ -900,11 +925,11 @@ fn create_ingame_and_shadow_pipelines(
|
||||
(postprocess, point_shadow),
|
||||
(terrain_directed_shadow, (figure_directed_shadow, debug_directed_shadow)),
|
||||
),
|
||||
(lod_object, (terrain_directed_rain_occlusion, figure_directed_rain_occlusion)),
|
||||
((lod_object, (terrain_directed_rain_occlusion, figure_directed_rain_occlusion)), rope),
|
||||
),
|
||||
) = pool.join(
|
||||
|| pool.join(|| pool.join(j1, j2), || pool.join(j3, j4)),
|
||||
|| pool.join(|| pool.join(j5, j6), j7),
|
||||
|| pool.join(|| pool.join(j5, j6), || pool.join(j7, j8)),
|
||||
);
|
||||
|
||||
IngameAndShadowPipelines {
|
||||
@ -914,6 +939,7 @@ fn create_ingame_and_shadow_pipelines(
|
||||
fluid,
|
||||
lod_terrain,
|
||||
particle,
|
||||
rope,
|
||||
trail,
|
||||
clouds,
|
||||
bloom,
|
||||
|
@ -59,6 +59,8 @@ impl assets::Compound for Shaders {
|
||||
"debug-vert",
|
||||
"debug-frag",
|
||||
"figure-frag",
|
||||
"rope-vert",
|
||||
"rope-frag",
|
||||
"terrain-vert",
|
||||
"terrain-frag",
|
||||
"fluid-vert",
|
||||
|
@ -7,6 +7,7 @@ pub mod particle;
|
||||
pub mod simple;
|
||||
pub mod smoke_cycle;
|
||||
pub mod terrain;
|
||||
pub mod tether;
|
||||
pub mod trail;
|
||||
|
||||
pub use self::{
|
||||
@ -16,6 +17,7 @@ pub use self::{
|
||||
lod::Lod,
|
||||
particle::ParticleMgr,
|
||||
terrain::{SpriteRenderContextLazy, Terrain},
|
||||
tether::TetherMgr,
|
||||
trail::TrailMgr,
|
||||
};
|
||||
use crate::{
|
||||
@ -105,6 +107,7 @@ pub struct Scene {
|
||||
particle_mgr: ParticleMgr,
|
||||
trail_mgr: TrailMgr,
|
||||
figure_mgr: FigureMgr,
|
||||
tether_mgr: TetherMgr,
|
||||
pub sfx_mgr: SfxMgr,
|
||||
pub music_mgr: MusicMgr,
|
||||
ambient_mgr: AmbientMgr,
|
||||
@ -340,6 +343,7 @@ impl Scene {
|
||||
particle_mgr: ParticleMgr::new(renderer),
|
||||
trail_mgr: TrailMgr::default(),
|
||||
figure_mgr: FigureMgr::new(renderer),
|
||||
tether_mgr: TetherMgr::new(renderer),
|
||||
sfx_mgr: SfxMgr::default(),
|
||||
music_mgr: MusicMgr::new(&calendar),
|
||||
ambient_mgr: AmbientMgr {
|
||||
@ -880,6 +884,9 @@ impl Scene {
|
||||
// Maintain LoD.
|
||||
self.lod.maintain(renderer, client, focus_pos, &self.camera);
|
||||
|
||||
// Maintain tethers.
|
||||
self.tether_mgr.maintain(renderer, client, focus_pos);
|
||||
|
||||
// Maintain debug shapes
|
||||
self.debug.maintain(renderer);
|
||||
|
||||
@ -1439,6 +1446,9 @@ impl Scene {
|
||||
);
|
||||
drop(sprite_drawer);
|
||||
|
||||
// Render tethers.
|
||||
self.tether_mgr.render(&mut first_pass);
|
||||
|
||||
// Draws translucent
|
||||
self.terrain.render_translucent(&mut first_pass, focus_pos);
|
||||
|
||||
|
130
voxygen/src/scene/tether.rs
Normal file
130
voxygen/src/scene/tether.rs
Normal file
@ -0,0 +1,130 @@
|
||||
use crate::render::{
|
||||
pipelines::rope::{BoundLocals, Locals, Vertex},
|
||||
FirstPassDrawer, Mesh, Model, Quad, Renderer,
|
||||
};
|
||||
use client::Client;
|
||||
use common::{
|
||||
comp,
|
||||
link::Is,
|
||||
tether::Follower,
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use specs::{Join, LendJoin, WorldExt};
|
||||
use vek::*;
|
||||
|
||||
pub struct TetherMgr {
|
||||
model: Model<Vertex>,
|
||||
/// Used to garbage-collect tethers that no longer exist.
|
||||
///
|
||||
/// Because a tether is not an entity, but instead a relationship between
|
||||
/// two entities, there is no single 'event' that we can listen to in
|
||||
/// order to determine that a tether has been broken. Instead, every tick,
|
||||
/// we go through the set of tethers that we observe in the world and
|
||||
/// mark their entries in the `tethers` map below with a flag.
|
||||
/// At the end of the tick, every unmarked tether in the `tethers` map below
|
||||
/// can be deleted.
|
||||
///
|
||||
/// Every tick, the 'alive' state of the flag flips between `true` and
|
||||
/// `false` to avoid the need to wastefully reset the flag of every
|
||||
/// alive tether on each tick (this is a common optimisation in some garbage
|
||||
/// collection algorithms too).
|
||||
stale_flag: bool,
|
||||
tethers: HashMap<(Uid, Uid), (BoundLocals, bool)>,
|
||||
}
|
||||
|
||||
impl TetherMgr {
|
||||
pub fn new(renderer: &mut Renderer) -> Self {
|
||||
Self {
|
||||
model: renderer.create_model(&create_tether_mesh()).unwrap(),
|
||||
stale_flag: true,
|
||||
tethers: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client, focus_off: Vec3<f32>) {
|
||||
let interpolated = client
|
||||
.state()
|
||||
.read_storage::<crate::ecs::comp::Interpolated>();
|
||||
let scales = client.state().read_storage::<comp::Scale>();
|
||||
let bodies = client.state().read_storage::<comp::Body>();
|
||||
let id_maps = client.state().ecs().read_resource::<IdMaps>();
|
||||
let is_followers = client.state().read_storage::<Is<Follower>>();
|
||||
for (interp, is_follower, body, scale) in
|
||||
(&interpolated, &is_followers, bodies.maybe(), scales.maybe()).join()
|
||||
{
|
||||
let Some(leader) = id_maps.uid_entity(is_follower.leader) else {
|
||||
continue;
|
||||
};
|
||||
let pos_a = interpolated.get(leader).map_or(Vec3::zero(), |i| i.pos)
|
||||
+ interpolated.get(leader).zip(bodies.get(leader)).map_or(
|
||||
Vec3::zero(),
|
||||
|(i, body)| {
|
||||
i.ori.to_quat()
|
||||
* body.tether_offset_leader()
|
||||
* scales.get(leader).copied().unwrap_or(comp::Scale(1.0)).0
|
||||
},
|
||||
);
|
||||
let pos_b = interp.pos
|
||||
+ body.map_or(Vec3::zero(), |body| {
|
||||
interp.ori.to_quat()
|
||||
* body.tether_offset_follower()
|
||||
* scale.copied().unwrap_or(comp::Scale(1.0)).0
|
||||
});
|
||||
|
||||
let (locals, stale_flag) = self
|
||||
.tethers
|
||||
.entry((is_follower.leader, is_follower.follower))
|
||||
.or_insert_with(|| {
|
||||
(
|
||||
renderer.create_rope_bound_locals(&[Locals::default()]),
|
||||
self.stale_flag,
|
||||
)
|
||||
});
|
||||
|
||||
renderer.update_consts(locals, &[Locals::new(
|
||||
pos_a - focus_off,
|
||||
pos_b - focus_off,
|
||||
is_follower.tether_length,
|
||||
)]);
|
||||
|
||||
*stale_flag = self.stale_flag;
|
||||
}
|
||||
|
||||
self.tethers.retain(|_, (_, flag)| *flag == self.stale_flag);
|
||||
|
||||
self.stale_flag ^= true;
|
||||
}
|
||||
|
||||
pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>) {
|
||||
let mut rope_drawer = drawer.draw_ropes();
|
||||
for (locals, _) in self.tethers.values() {
|
||||
rope_drawer.draw(&self.model, locals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tether_mesh() -> Mesh<Vertex> {
|
||||
const SEGMENTS: usize = 10;
|
||||
const RADIAL_SEGMENTS: usize = 6;
|
||||
|
||||
(0..RADIAL_SEGMENTS)
|
||||
.flat_map(|i| {
|
||||
let at_angle = |x: f32| {
|
||||
let theta = x / RADIAL_SEGMENTS as f32 * std::f32::consts::TAU;
|
||||
Vec2::new(theta.sin(), theta.cos())
|
||||
};
|
||||
let start = at_angle(i as f32);
|
||||
let end = at_angle(i as f32 + 1.0);
|
||||
(0..SEGMENTS).map(move |s| {
|
||||
let z = s as f32 / SEGMENTS as f32;
|
||||
Quad {
|
||||
a: Vertex::new(start.with_z(z), start.with_z(0.0)),
|
||||
b: Vertex::new(start.with_z(z + 1.0 / SEGMENTS as f32), start.with_z(0.0)),
|
||||
c: Vertex::new(end.with_z(z + 1.0 / SEGMENTS as f32), end.with_z(0.0)),
|
||||
d: Vertex::new(end.with_z(z), end.with_z(0.0)),
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
Loading…
Reference in New Issue
Block a user