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),
|
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);
|
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
|
#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;
|
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() {
|
void main() {
|
||||||
// Matrix to transform this sprite instance from model space to chunk space
|
// Matrix to transform this sprite instance from model space to chunk space
|
||||||
mat4 inst_mat;
|
mat4 inst_mat;
|
||||||
|
@ -35,6 +35,8 @@ macro_rules! synced_components {
|
|||||||
is_mount: IsMount,
|
is_mount: IsMount,
|
||||||
is_rider: IsRider,
|
is_rider: IsRider,
|
||||||
is_volume_rider: IsVolumeRider,
|
is_volume_rider: IsVolumeRider,
|
||||||
|
is_leader: IsLeader,
|
||||||
|
is_follower: IsFollower,
|
||||||
mass: Mass,
|
mass: Mass,
|
||||||
density: Density,
|
density: Density,
|
||||||
collider: Collider,
|
collider: Collider,
|
||||||
@ -74,7 +76,11 @@ macro_rules! reexport_comps {
|
|||||||
mod inner {
|
mod inner {
|
||||||
pub use common::comp::*;
|
pub use common::comp::*;
|
||||||
use common::link::Is;
|
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
|
// We alias these because the identifier used for the
|
||||||
// component's type is reused as an enum variant name
|
// component's type is reused as an enum variant name
|
||||||
// in the macro's that we pass to `synced_components!`.
|
// 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 IsMount = Is<Mount>;
|
||||||
pub type IsRider = Is<Rider>;
|
pub type IsRider = Is<Rider>;
|
||||||
pub type IsVolumeRider = Is<VolumeRider>;
|
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
|
// 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;
|
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 {
|
impl NetSync for Mass {
|
||||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||||
}
|
}
|
||||||
|
@ -688,6 +688,11 @@ impl ServerChatCommand {
|
|||||||
.collect(),
|
.collect(),
|
||||||
Optional,
|
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),
|
Float("destination_degrees_ccw_of_east", 90.0, Optional),
|
||||||
],
|
],
|
||||||
"Spawns a ship",
|
"Spawns a ship",
|
||||||
@ -723,6 +728,7 @@ impl ServerChatCommand {
|
|||||||
Integer("amount", 1, Optional),
|
Integer("amount", 1, Optional),
|
||||||
Boolean("ai", "true".to_string(), Optional),
|
Boolean("ai", "true".to_string(), Optional),
|
||||||
Float("scale", 1.0, Optional),
|
Float("scale", 1.0, Optional),
|
||||||
|
Boolean("tethered", "false".to_string(), Optional),
|
||||||
],
|
],
|
||||||
"Spawn a test entity",
|
"Spawn a test entity",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
|
@ -1198,6 +1198,7 @@ impl Body {
|
|||||||
ship::Body::Skiff => [1.0, -2.0, 2.0],
|
ship::Body::Skiff => [1.0, -2.0, 2.0],
|
||||||
ship::Body::Submarine => [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::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],
|
ship::Body::Volume => [0.0, 0.0, 0.0],
|
||||||
},
|
},
|
||||||
_ => [0.0, 0.0, 0.0],
|
_ => [0.0, 0.0, 0.0],
|
||||||
@ -1214,6 +1215,14 @@ impl Body {
|
|||||||
.into()
|
.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 {
|
pub fn localize(&self) -> Content {
|
||||||
match self {
|
match self {
|
||||||
Self::BipedLarge(biped_large) => biped_large.localize(),
|
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_AIRSHIPS: [Body; 2] = [Body::DefaultAirship, Body::AirBalloon];
|
||||||
pub const ALL_SHIPS: [Body; 5] = [
|
pub const ALL_SHIPS: [Body; 6] = [
|
||||||
Body::SailBoat,
|
Body::SailBoat,
|
||||||
Body::Galleon,
|
Body::Galleon,
|
||||||
Body::Skiff,
|
Body::Skiff,
|
||||||
Body::Submarine,
|
Body::Submarine,
|
||||||
Body::Carriage,
|
Body::Carriage,
|
||||||
|
Body::Cart,
|
||||||
];
|
];
|
||||||
|
|
||||||
make_case_elim!(
|
make_case_elim!(
|
||||||
@ -40,6 +41,7 @@ make_case_elim!(
|
|||||||
Skiff = 5,
|
Skiff = 5,
|
||||||
Submarine = 6,
|
Submarine = 6,
|
||||||
Carriage = 7,
|
Carriage = 7,
|
||||||
|
Cart = 8,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -72,6 +74,7 @@ impl Body {
|
|||||||
Body::Skiff => Some("skiff.structure"),
|
Body::Skiff => Some("skiff.structure"),
|
||||||
Body::Submarine => Some("submarine.structure"),
|
Body::Submarine => Some("submarine.structure"),
|
||||||
Body::Carriage => Some("carriage.structure"),
|
Body::Carriage => Some("carriage.structure"),
|
||||||
|
Body::Cart => Some("cart.structure"),
|
||||||
Body::Volume => None,
|
Body::Volume => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,7 +87,8 @@ impl Body {
|
|||||||
Body::Galleon => Vec3::new(14.0, 48.0, 10.0),
|
Body::Galleon => Vec3::new(14.0, 48.0, 10.0),
|
||||||
Body::Skiff => Vec3::new(7.0, 15.0, 10.0),
|
Body::Skiff => Vec3::new(7.0, 15.0, 10.0),
|
||||||
Body::Submarine => Vec3::new(2.0, 15.0, 8.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::DefaultAirship | Body::AirBalloon | Body::Volume => Density(AIR_DENSITY),
|
||||||
Body::Submarine => Density(WATER_DENSITY), // Neutrally buoyant
|
Body::Submarine => Density(WATER_DENSITY), // Neutrally buoyant
|
||||||
Body::Carriage => Density(WATER_DENSITY * 0.5),
|
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
|
_ => Density(AIR_DENSITY * 0.95 + WATER_DENSITY * 0.05), /* Most boats should be very
|
||||||
* buoyant */
|
* 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 {
|
pub fn can_fly(&self) -> bool {
|
||||||
matches!(self, Body::DefaultAirship | Body::AirBalloon | Body::Volume)
|
matches!(self, Body::DefaultAirship | Body::AirBalloon | Body::Volume)
|
||||||
@ -136,7 +148,7 @@ impl Body {
|
|||||||
matches!(self, Body::SailBoat | Body::Galleon | Body::Skiff)
|
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 {
|
pub fn make_collider(&self) -> Collider {
|
||||||
match self.manifest_entry() {
|
match self.manifest_entry() {
|
||||||
|
@ -57,6 +57,7 @@ pub mod spiral;
|
|||||||
pub mod states;
|
pub mod states;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
pub mod terrain;
|
pub mod terrain;
|
||||||
|
pub mod tether;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod trade;
|
pub mod trade;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
@ -2,6 +2,7 @@ use crate::{
|
|||||||
comp::{self, pet::is_mountable, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
|
comp::{self, pet::is_mountable, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
|
||||||
link::{Is, Link, LinkHandle, Role},
|
link::{Is, Link, LinkHandle, Role},
|
||||||
terrain::{Block, TerrainGrid},
|
terrain::{Block, TerrainGrid},
|
||||||
|
tether,
|
||||||
uid::{IdMaps, Uid},
|
uid::{IdMaps, Uid},
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
@ -45,6 +46,7 @@ impl Link for Mounting {
|
|||||||
WriteStorage<'a, Is<Mount>>,
|
WriteStorage<'a, Is<Mount>>,
|
||||||
WriteStorage<'a, Is<Rider>>,
|
WriteStorage<'a, Is<Rider>>,
|
||||||
ReadStorage<'a, Is<VolumeRider>>,
|
ReadStorage<'a, Is<VolumeRider>>,
|
||||||
|
ReadStorage<'a, Is<tether::Follower>>,
|
||||||
);
|
);
|
||||||
type DeleteData<'a> = (
|
type DeleteData<'a> = (
|
||||||
Read<'a, IdMaps>,
|
Read<'a, IdMaps>,
|
||||||
@ -67,7 +69,7 @@ impl Link for Mounting {
|
|||||||
|
|
||||||
fn create(
|
fn create(
|
||||||
this: &LinkHandle<Self>,
|
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> {
|
) -> Result<(), Self::Error> {
|
||||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||||
|
|
||||||
@ -79,7 +81,7 @@ impl Link for Mounting {
|
|||||||
// relationship
|
// relationship
|
||||||
if !is_mounts.contains(mount)
|
if !is_mounts.contains(mount)
|
||||||
&& !is_riders.contains(rider)
|
&& !is_riders.contains(rider)
|
||||||
&& !is_riders.contains(rider)
|
&& !is_followers.contains(rider)
|
||||||
// TODO: Does this definitely prevent mount cycles?
|
// TODO: Does this definitely prevent mount cycles?
|
||||||
&& (!is_mounts.contains(rider) || !is_riders.contains(mount))
|
&& (!is_mounts.contains(rider) || !is_riders.contains(mount))
|
||||||
&& !is_volume_rider.contains(rider)
|
&& !is_volume_rider.contains(rider)
|
||||||
|
@ -150,7 +150,7 @@ impl Body {
|
|||||||
quadruped_low::Species::Driggle => 120.0,
|
quadruped_low::Species::Driggle => 120.0,
|
||||||
quadruped_low::Species::HermitAlligator => 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::Ship(_) => 0.0,
|
||||||
Body::Arthropod(arthropod) => match arthropod.species {
|
Body::Arthropod(arthropod) => match arthropod.species {
|
||||||
arthropod::Species::Tarantula => 135.0,
|
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,
|
shared_server_config::ServerConstants,
|
||||||
slowjob::SlowJobPool,
|
slowjob::SlowJobPool,
|
||||||
terrain::{Block, MapSizeLg, TerrainChunk, TerrainGrid},
|
terrain::{Block, MapSizeLg, TerrainChunk, TerrainGrid},
|
||||||
|
tether,
|
||||||
time::DayPeriod,
|
time::DayPeriod,
|
||||||
trade::Trades,
|
trade::Trades,
|
||||||
vol::{ReadVol, WriteVol},
|
vol::{ReadVol, WriteVol},
|
||||||
@ -202,6 +203,8 @@ impl State {
|
|||||||
ecs.register::<Is<Mount>>();
|
ecs.register::<Is<Mount>>();
|
||||||
ecs.register::<Is<Rider>>();
|
ecs.register::<Is<Rider>>();
|
||||||
ecs.register::<Is<VolumeRider>>();
|
ecs.register::<Is<VolumeRider>>();
|
||||||
|
ecs.register::<Is<tether::Leader>>();
|
||||||
|
ecs.register::<Is<tether::Follower>>();
|
||||||
ecs.register::<comp::Mass>();
|
ecs.register::<comp::Mass>();
|
||||||
ecs.register::<comp::Density>();
|
ecs.register::<comp::Density>();
|
||||||
ecs.register::<comp::Collider>();
|
ecs.register::<comp::Collider>();
|
||||||
|
@ -13,6 +13,7 @@ pub mod phys;
|
|||||||
pub mod projectile;
|
pub mod projectile;
|
||||||
mod shockwave;
|
mod shockwave;
|
||||||
mod stats;
|
mod stats;
|
||||||
|
mod tether;
|
||||||
|
|
||||||
// External
|
// External
|
||||||
use common_ecs::{dispatch, System};
|
use common_ecs::{dispatch, System};
|
||||||
@ -21,6 +22,7 @@ use specs::DispatcherBuilder;
|
|||||||
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||||
//TODO: don't run interpolation on server
|
//TODO: don't run interpolation on server
|
||||||
dispatch::<interpolation::Sys>(dispatch_builder, &[]);
|
dispatch::<interpolation::Sys>(dispatch_builder, &[]);
|
||||||
|
dispatch::<tether::Sys>(dispatch_builder, &[]);
|
||||||
dispatch::<mount::Sys>(dispatch_builder, &[]);
|
dispatch::<mount::Sys>(dispatch_builder, &[]);
|
||||||
dispatch::<controller::Sys>(dispatch_builder, &[&mount::Sys::sys_name()]);
|
dispatch::<controller::Sys>(dispatch_builder, &[&mount::Sys::sys_name()]);
|
||||||
dispatch::<character_behavior::Sys>(dispatch_builder, &[&controller::Sys::sys_name()]);
|
dispatch::<character_behavior::Sys>(dispatch_builder, &[&controller::Sys::sys_name()]);
|
||||||
|
@ -863,6 +863,18 @@ impl<'a> PhysicsData<'a> {
|
|||||||
let climbing =
|
let climbing =
|
||||||
character_state.map_or(false, |cs| matches!(cs, CharacterState::Climb(_)));
|
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 {
|
match &collider {
|
||||||
Collider::Voxel { .. } | Collider::Volume(_) => {
|
Collider::Voxel { .. } | Collider::Volume(_) => {
|
||||||
// For now, treat entities with voxel colliders
|
// For now, treat entities with voxel colliders
|
||||||
@ -894,6 +906,7 @@ impl<'a> PhysicsData<'a> {
|
|||||||
},
|
},
|
||||||
read,
|
read,
|
||||||
&ori,
|
&ori,
|
||||||
|
friction_factor,
|
||||||
);
|
);
|
||||||
tgt_pos = cpos.0;
|
tgt_pos = cpos.0;
|
||||||
},
|
},
|
||||||
@ -928,6 +941,7 @@ impl<'a> PhysicsData<'a> {
|
|||||||
},
|
},
|
||||||
read,
|
read,
|
||||||
&ori,
|
&ori,
|
||||||
|
friction_factor,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Sticky things shouldn't move when on a surface
|
// Sticky things shouldn't move when on a surface
|
||||||
@ -1214,6 +1228,7 @@ impl<'a> PhysicsData<'a> {
|
|||||||
},
|
},
|
||||||
read,
|
read,
|
||||||
&ori,
|
&ori,
|
||||||
|
|vel| friction_factor(previous_cache_other.ori * vel),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Transform entity attributes back into world space now
|
// 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>),
|
mut land_on_ground: impl FnMut(Entity, Vel, Vec3<f32>),
|
||||||
read: &PhysicsRead,
|
read: &PhysicsRead,
|
||||||
ori: &Ori,
|
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
|
// 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));
|
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
|
} * physics_state
|
||||||
.on_ground
|
.on_ground
|
||||||
.map(|b| b.get_friction())
|
.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 {
|
let wall_fric = if physics_state.on_wall.is_some() && climbing {
|
||||||
FRIC_GROUND
|
FRIC_GROUND
|
||||||
} else {
|
} 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,
|
effect::Effect,
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
generation::{EntityConfig, EntityInfo},
|
generation::{EntityConfig, EntityInfo},
|
||||||
|
link::Is,
|
||||||
|
mounting::{Rider, Volume, VolumeRider},
|
||||||
npc::{self, get_npc_name},
|
npc::{self, get_npc_name},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
parse_cmd_args,
|
parse_cmd_args,
|
||||||
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale},
|
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale},
|
||||||
rtsim::{Actor, Role},
|
rtsim::{Actor, Role},
|
||||||
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
|
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
|
||||||
|
tether::Tethered,
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
||||||
@ -1558,8 +1561,15 @@ fn handle_spawn(
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
match parse_cmd_args!(args, String, npc::NpcBody, u32, bool, f32) {
|
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) => {
|
(
|
||||||
|
Some(opt_align),
|
||||||
|
Some(npc::NpcBody(id, mut body)),
|
||||||
|
opt_amount,
|
||||||
|
opt_ai,
|
||||||
|
opt_scale,
|
||||||
|
opt_tethered,
|
||||||
|
) => {
|
||||||
let uid = uid(server, target, "target")?;
|
let uid = uid(server, target, "target")?;
|
||||||
let alignment = parse_alignment(uid, &opt_align)?;
|
let alignment = parse_alignment(uid, &opt_align)?;
|
||||||
let amount = opt_amount.filter(|x| *x > 0).unwrap_or(1).min(50);
|
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();
|
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
|
// Add to group system if a pet
|
||||||
if matches!(alignment, comp::Alignment::Owned { .. }) {
|
if matches!(alignment, comp::Alignment::Owned { .. }) {
|
||||||
let server_eventbus =
|
let server_eventbus =
|
||||||
@ -1748,7 +1780,7 @@ fn handle_spawn_ship(
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
_action: &ServerChatCommand,
|
_action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> 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")?;
|
let mut pos = position(server, target, "target")?;
|
||||||
pos.0.z += 2.0;
|
pos.0.z += 2.0;
|
||||||
const DESTINATION_RADIUS: f32 = 2000.0;
|
const DESTINATION_RADIUS: f32 = 2000.0;
|
||||||
@ -1767,6 +1799,7 @@ fn handle_spawn_ship(
|
|||||||
let mut builder = server
|
let mut builder = server
|
||||||
.state
|
.state
|
||||||
.create_ship(pos, ori, ship, |ship| ship.make_collider());
|
.create_ship(pos, ori, ship, |ship| ship.make_collider());
|
||||||
|
|
||||||
if let Some(pos) = destination {
|
if let Some(pos) = destination {
|
||||||
let (kp, ki, kd) =
|
let (kp, ki, kd) =
|
||||||
comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.0, 0.0, 0.0));
|
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));
|
.with_position_pid_controller(comp::PidController::new(kp, ki, kd, pos, 0.0, pure_z));
|
||||||
builder = builder.with(agent);
|
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(
|
server.notify_client(
|
||||||
client,
|
client,
|
||||||
|
@ -116,6 +116,7 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
|||||||
Skiff => (0.0, 0.0, 0.0),
|
Skiff => (0.0, 0.0, 0.0),
|
||||||
Submarine => (0.0, 0.0, 0.0),
|
Submarine => (0.0, 0.0, 0.0),
|
||||||
Carriage => (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),
|
Volume => (0.0, 0.0, 0.0),
|
||||||
},
|
},
|
||||||
bone1: match body {
|
bone1: match body {
|
||||||
@ -126,6 +127,7 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
|||||||
Skiff => (0.0, 0.0, 0.0),
|
Skiff => (0.0, 0.0, 0.0),
|
||||||
Submarine => (0.0, -15.0, 3.5),
|
Submarine => (0.0, -15.0, 3.5),
|
||||||
Carriage => (0.0, 3.0, 2.0),
|
Carriage => (0.0, 3.0, 2.0),
|
||||||
|
Cart => (0.0, 1.0, 1.0),
|
||||||
Volume => (0.0, 0.0, 0.0),
|
Volume => (0.0, 0.0, 0.0),
|
||||||
},
|
},
|
||||||
bone2: match body {
|
bone2: match body {
|
||||||
@ -136,6 +138,7 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
|||||||
Skiff => (0.0, 0.0, 0.0),
|
Skiff => (0.0, 0.0, 0.0),
|
||||||
Submarine => (0.0, 0.0, 0.0),
|
Submarine => (0.0, 0.0, 0.0),
|
||||||
Carriage => (0.0, -3.0, 2.0),
|
Carriage => (0.0, -3.0, 2.0),
|
||||||
|
Cart => (0.0, -2.5, 1.0),
|
||||||
Volume => (0.0, 0.0, 0.0),
|
Volume => (0.0, 0.0, 0.0),
|
||||||
},
|
},
|
||||||
bone3: match body {
|
bone3: match body {
|
||||||
@ -146,18 +149,20 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
|||||||
Skiff => (0.0, 0.0, 0.0),
|
Skiff => (0.0, 0.0, 0.0),
|
||||||
Submarine => (0.0, -18.0, 3.5),
|
Submarine => (0.0, -18.0, 3.5),
|
||||||
Carriage => (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),
|
Volume => (0.0, 0.0, 0.0),
|
||||||
},
|
},
|
||||||
bone1_ori: match body {
|
bone1_ori: match body {
|
||||||
Carriage => std::f32::consts::PI * 0.5,
|
Carriage | Cart => std::f32::consts::PI * 0.5,
|
||||||
_ => 0.0,
|
_ => 0.0,
|
||||||
},
|
},
|
||||||
bone2_ori: match body {
|
bone2_ori: match body {
|
||||||
Carriage => std::f32::consts::PI * -0.5,
|
Carriage | Cart => std::f32::consts::PI * -0.5,
|
||||||
_ => 0.0,
|
_ => 0.0,
|
||||||
},
|
},
|
||||||
bone_rotation_rate: match body {
|
bone_rotation_rate: match body {
|
||||||
Carriage => 0.25,
|
Carriage => 0.25,
|
||||||
|
Cart => 0.4,
|
||||||
_ => 0.8,
|
_ => 0.8,
|
||||||
},
|
},
|
||||||
bone1_prop_trail_offset: match body {
|
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.
|
/// Represents a triangle stored on the CPU.
|
||||||
pub struct Tri<V: Vertex> {
|
pub struct Tri<V: Vertex> {
|
||||||
a: V,
|
pub a: V,
|
||||||
b: V,
|
pub b: V,
|
||||||
c: V,
|
pub c: V,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Vertex> Tri<V> {
|
impl<V: Vertex> Tri<V> {
|
||||||
@ -162,10 +162,10 @@ impl<V: Vertex> Tri<V> {
|
|||||||
|
|
||||||
/// Represents a quad stored on the CPU.
|
/// Represents a quad stored on the CPU.
|
||||||
pub struct Quad<V: Vertex> {
|
pub struct Quad<V: Vertex> {
|
||||||
a: V,
|
pub a: V,
|
||||||
b: V,
|
pub b: V,
|
||||||
c: V,
|
pub c: V,
|
||||||
d: V,
|
pub d: V,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Vertex> Quad<V> {
|
impl<V: Vertex> Quad<V> {
|
||||||
|
@ -9,6 +9,7 @@ pub mod lod_terrain;
|
|||||||
pub mod particle;
|
pub mod particle;
|
||||||
pub mod postprocess;
|
pub mod postprocess;
|
||||||
pub mod rain_occlusion;
|
pub mod rain_occlusion;
|
||||||
|
pub mod rope;
|
||||||
pub mod shadow;
|
pub mod shadow;
|
||||||
pub mod skybox;
|
pub mod skybox;
|
||||||
pub mod sprite;
|
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,
|
mesh::Mesh,
|
||||||
model::{DynamicModel, Model},
|
model::{DynamicModel, Model},
|
||||||
pipelines::{
|
pipelines::{
|
||||||
blit, bloom, clouds, debug, figure, postprocess, rain_occlusion, shadow, sprite, terrain,
|
blit, bloom, clouds, debug, figure, postprocess, rain_occlusion, rope, shadow, sprite,
|
||||||
ui, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup,
|
terrain, ui, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup,
|
||||||
},
|
},
|
||||||
texture::Texture,
|
texture::Texture,
|
||||||
AddressMode, FilterMode, OtherModes, PipelineModes, RenderError, RenderMode, ShadowMapMode,
|
AddressMode, FilterMode, OtherModes, PipelineModes, RenderError, RenderMode, ShadowMapMode,
|
||||||
@ -54,6 +54,7 @@ struct ImmutableLayouts {
|
|||||||
rain_occlusion: rain_occlusion::RainOcclusionLayout,
|
rain_occlusion: rain_occlusion::RainOcclusionLayout,
|
||||||
sprite: sprite::SpriteLayout,
|
sprite: sprite::SpriteLayout,
|
||||||
terrain: terrain::TerrainLayout,
|
terrain: terrain::TerrainLayout,
|
||||||
|
rope: rope::RopeLayout,
|
||||||
clouds: clouds::CloudsLayout,
|
clouds: clouds::CloudsLayout,
|
||||||
bloom: bloom::BloomLayout,
|
bloom: bloom::BloomLayout,
|
||||||
ui: ui::UiLayout,
|
ui: ui::UiLayout,
|
||||||
@ -383,6 +384,7 @@ impl Renderer {
|
|||||||
let rain_occlusion = rain_occlusion::RainOcclusionLayout::new(&device);
|
let rain_occlusion = rain_occlusion::RainOcclusionLayout::new(&device);
|
||||||
let sprite = sprite::SpriteLayout::new(&device);
|
let sprite = sprite::SpriteLayout::new(&device);
|
||||||
let terrain = terrain::TerrainLayout::new(&device);
|
let terrain = terrain::TerrainLayout::new(&device);
|
||||||
|
let rope = rope::RopeLayout::new(&device);
|
||||||
let clouds = clouds::CloudsLayout::new(&device);
|
let clouds = clouds::CloudsLayout::new(&device);
|
||||||
let bloom = bloom::BloomLayout::new(&device);
|
let bloom = bloom::BloomLayout::new(&device);
|
||||||
let postprocess = Arc::new(postprocess::PostProcessLayout::new(
|
let postprocess = Arc::new(postprocess::PostProcessLayout::new(
|
||||||
@ -402,6 +404,7 @@ impl Renderer {
|
|||||||
rain_occlusion,
|
rain_occlusion,
|
||||||
sprite,
|
sprite,
|
||||||
terrain,
|
terrain,
|
||||||
|
rope,
|
||||||
clouds,
|
clouds,
|
||||||
bloom,
|
bloom,
|
||||||
ui,
|
ui,
|
||||||
|
@ -3,7 +3,7 @@ use crate::render::pipelines::rain_occlusion;
|
|||||||
use super::{
|
use super::{
|
||||||
super::{
|
super::{
|
||||||
pipelines::{
|
pipelines::{
|
||||||
debug, figure, lod_terrain, shadow, sprite, terrain, ui, AtlasTextures,
|
debug, figure, lod_terrain, rope, shadow, sprite, terrain, ui, AtlasTextures,
|
||||||
FigureSpriteAtlasData, GlobalModel, GlobalsBindGroup, TerrainAtlasData,
|
FigureSpriteAtlasData, GlobalModel, GlobalsBindGroup, TerrainAtlasData,
|
||||||
},
|
},
|
||||||
texture::Texture,
|
texture::Texture,
|
||||||
@ -67,6 +67,11 @@ impl Renderer {
|
|||||||
.bind_locals(&self.device, locals, bone_data)
|
.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(
|
pub fn create_terrain_bound_locals(
|
||||||
&mut self,
|
&mut self,
|
||||||
locals: &[terrain::Locals],
|
locals: &[terrain::Locals],
|
||||||
|
@ -6,8 +6,8 @@ use super::{
|
|||||||
instances::Instances,
|
instances::Instances,
|
||||||
model::{DynamicModel, Model, SubModel},
|
model::{DynamicModel, Model, SubModel},
|
||||||
pipelines::{
|
pipelines::{
|
||||||
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle, shadow,
|
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle, rope,
|
||||||
skybox, sprite, terrain, trail, ui, AtlasTextures, FigureSpriteAtlasData,
|
shadow, skybox, sprite, terrain, trail, ui, AtlasTextures, FigureSpriteAtlasData,
|
||||||
GlobalsBindGroup, TerrainAtlasData,
|
GlobalsBindGroup, TerrainAtlasData,
|
||||||
},
|
},
|
||||||
AltIndices, CullingMode,
|
AltIndices, CullingMode,
|
||||||
@ -966,6 +966,15 @@ impl<'pass> FirstPassDrawer<'pass> {
|
|||||||
ParticleDrawer { render_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>(
|
pub fn draw_sprites<'data: 'pass>(
|
||||||
&mut self,
|
&mut self,
|
||||||
globals: &'data sprite::SpriteGlobalsBindGroup,
|
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]
|
#[must_use]
|
||||||
pub struct SpriteDrawer<'pass_ref, 'pass: 'pass_ref> {
|
pub struct SpriteDrawer<'pass_ref, 'pass: 'pass_ref> {
|
||||||
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
|
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
|
||||||
|
@ -4,7 +4,7 @@ use super::{
|
|||||||
super::{
|
super::{
|
||||||
pipelines::{
|
pipelines::{
|
||||||
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle,
|
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,
|
AaMode, BloomMode, CloudMode, FluidMode, LightingMode, PipelineModes, ReflectionMode,
|
||||||
RenderError, ShadowMode,
|
RenderError, ShadowMode,
|
||||||
@ -23,6 +23,7 @@ pub struct Pipelines {
|
|||||||
pub fluid: fluid::FluidPipeline,
|
pub fluid: fluid::FluidPipeline,
|
||||||
pub lod_terrain: lod_terrain::LodTerrainPipeline,
|
pub lod_terrain: lod_terrain::LodTerrainPipeline,
|
||||||
pub particle: particle::ParticlePipeline,
|
pub particle: particle::ParticlePipeline,
|
||||||
|
pub rope: rope::RopePipeline,
|
||||||
pub trail: trail::TrailPipeline,
|
pub trail: trail::TrailPipeline,
|
||||||
pub clouds: clouds::CloudsPipeline,
|
pub clouds: clouds::CloudsPipeline,
|
||||||
pub bloom: Option<bloom::BloomPipelines>,
|
pub bloom: Option<bloom::BloomPipelines>,
|
||||||
@ -46,6 +47,7 @@ pub struct IngamePipelines {
|
|||||||
fluid: fluid::FluidPipeline,
|
fluid: fluid::FluidPipeline,
|
||||||
lod_terrain: lod_terrain::LodTerrainPipeline,
|
lod_terrain: lod_terrain::LodTerrainPipeline,
|
||||||
particle: particle::ParticlePipeline,
|
particle: particle::ParticlePipeline,
|
||||||
|
rope: rope::RopePipeline,
|
||||||
trail: trail::TrailPipeline,
|
trail: trail::TrailPipeline,
|
||||||
clouds: clouds::CloudsPipeline,
|
clouds: clouds::CloudsPipeline,
|
||||||
pub bloom: Option<bloom::BloomPipelines>,
|
pub bloom: Option<bloom::BloomPipelines>,
|
||||||
@ -93,6 +95,7 @@ impl Pipelines {
|
|||||||
fluid: ingame.fluid,
|
fluid: ingame.fluid,
|
||||||
lod_terrain: ingame.lod_terrain,
|
lod_terrain: ingame.lod_terrain,
|
||||||
particle: ingame.particle,
|
particle: ingame.particle,
|
||||||
|
rope: ingame.rope,
|
||||||
trail: ingame.trail,
|
trail: ingame.trail,
|
||||||
clouds: ingame.clouds,
|
clouds: ingame.clouds,
|
||||||
bloom: ingame.bloom,
|
bloom: ingame.bloom,
|
||||||
@ -127,6 +130,8 @@ struct ShaderModules {
|
|||||||
lod_object_frag: wgpu::ShaderModule,
|
lod_object_frag: wgpu::ShaderModule,
|
||||||
particle_vert: wgpu::ShaderModule,
|
particle_vert: wgpu::ShaderModule,
|
||||||
particle_frag: wgpu::ShaderModule,
|
particle_frag: wgpu::ShaderModule,
|
||||||
|
rope_vert: wgpu::ShaderModule,
|
||||||
|
rope_frag: wgpu::ShaderModule,
|
||||||
trail_vert: wgpu::ShaderModule,
|
trail_vert: wgpu::ShaderModule,
|
||||||
trail_frag: wgpu::ShaderModule,
|
trail_frag: wgpu::ShaderModule,
|
||||||
ui_vert: wgpu::ShaderModule,
|
ui_vert: wgpu::ShaderModule,
|
||||||
@ -339,6 +344,8 @@ impl ShaderModules {
|
|||||||
lod_object_frag: create_shader("lod-object-frag", ShaderKind::Fragment)?,
|
lod_object_frag: create_shader("lod-object-frag", ShaderKind::Fragment)?,
|
||||||
particle_vert: create_shader("particle-vert", ShaderKind::Vertex)?,
|
particle_vert: create_shader("particle-vert", ShaderKind::Vertex)?,
|
||||||
particle_frag: create_shader("particle-frag", ShaderKind::Fragment)?,
|
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_vert: create_shader("trail-vert", ShaderKind::Vertex)?,
|
||||||
trail_frag: create_shader("trail-frag", ShaderKind::Fragment)?,
|
trail_frag: create_shader("trail-frag", ShaderKind::Fragment)?,
|
||||||
ui_vert: create_shader("ui-vert", ShaderKind::Vertex)?,
|
ui_vert: create_shader("ui-vert", ShaderKind::Vertex)?,
|
||||||
@ -500,7 +507,7 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
needs: PipelineNeeds,
|
needs: PipelineNeeds,
|
||||||
pool: &rayon::ThreadPool,
|
pool: &rayon::ThreadPool,
|
||||||
// TODO: Reduce the boilerplate in this file
|
// TODO: Reduce the boilerplate in this file
|
||||||
tasks: [Task; 19],
|
tasks: [Task; 20],
|
||||||
) -> IngameAndShadowPipelines {
|
) -> IngameAndShadowPipelines {
|
||||||
prof_span!(_guard, "create_ingame_and_shadow_pipelines");
|
prof_span!(_guard, "create_ingame_and_shadow_pipelines");
|
||||||
|
|
||||||
@ -521,6 +528,7 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
sprite_task,
|
sprite_task,
|
||||||
lod_object_task,
|
lod_object_task,
|
||||||
particle_task,
|
particle_task,
|
||||||
|
rope_task,
|
||||||
trail_task,
|
trail_task,
|
||||||
lod_terrain_task,
|
lod_terrain_task,
|
||||||
clouds_task,
|
clouds_task,
|
||||||
@ -665,6 +673,22 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
"particle pipeline creation",
|
"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
|
// Pipeline for rendering weapon trails
|
||||||
let create_trail = || {
|
let create_trail = || {
|
||||||
trail_task.run(
|
trail_task.run(
|
||||||
@ -888,6 +912,7 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
let j8 = create_rope;
|
||||||
|
|
||||||
// Ignore this
|
// Ignore this
|
||||||
let (
|
let (
|
||||||
@ -900,11 +925,11 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
(postprocess, point_shadow),
|
(postprocess, point_shadow),
|
||||||
(terrain_directed_shadow, (figure_directed_shadow, debug_directed_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(|| pool.join(j1, j2), || pool.join(j3, j4)),
|
|| 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 {
|
IngameAndShadowPipelines {
|
||||||
@ -914,6 +939,7 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
fluid,
|
fluid,
|
||||||
lod_terrain,
|
lod_terrain,
|
||||||
particle,
|
particle,
|
||||||
|
rope,
|
||||||
trail,
|
trail,
|
||||||
clouds,
|
clouds,
|
||||||
bloom,
|
bloom,
|
||||||
|
@ -59,6 +59,8 @@ impl assets::Compound for Shaders {
|
|||||||
"debug-vert",
|
"debug-vert",
|
||||||
"debug-frag",
|
"debug-frag",
|
||||||
"figure-frag",
|
"figure-frag",
|
||||||
|
"rope-vert",
|
||||||
|
"rope-frag",
|
||||||
"terrain-vert",
|
"terrain-vert",
|
||||||
"terrain-frag",
|
"terrain-frag",
|
||||||
"fluid-vert",
|
"fluid-vert",
|
||||||
|
@ -7,6 +7,7 @@ pub mod particle;
|
|||||||
pub mod simple;
|
pub mod simple;
|
||||||
pub mod smoke_cycle;
|
pub mod smoke_cycle;
|
||||||
pub mod terrain;
|
pub mod terrain;
|
||||||
|
pub mod tether;
|
||||||
pub mod trail;
|
pub mod trail;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
@ -16,6 +17,7 @@ pub use self::{
|
|||||||
lod::Lod,
|
lod::Lod,
|
||||||
particle::ParticleMgr,
|
particle::ParticleMgr,
|
||||||
terrain::{SpriteRenderContextLazy, Terrain},
|
terrain::{SpriteRenderContextLazy, Terrain},
|
||||||
|
tether::TetherMgr,
|
||||||
trail::TrailMgr,
|
trail::TrailMgr,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -105,6 +107,7 @@ pub struct Scene {
|
|||||||
particle_mgr: ParticleMgr,
|
particle_mgr: ParticleMgr,
|
||||||
trail_mgr: TrailMgr,
|
trail_mgr: TrailMgr,
|
||||||
figure_mgr: FigureMgr,
|
figure_mgr: FigureMgr,
|
||||||
|
tether_mgr: TetherMgr,
|
||||||
pub sfx_mgr: SfxMgr,
|
pub sfx_mgr: SfxMgr,
|
||||||
pub music_mgr: MusicMgr,
|
pub music_mgr: MusicMgr,
|
||||||
ambient_mgr: AmbientMgr,
|
ambient_mgr: AmbientMgr,
|
||||||
@ -340,6 +343,7 @@ impl Scene {
|
|||||||
particle_mgr: ParticleMgr::new(renderer),
|
particle_mgr: ParticleMgr::new(renderer),
|
||||||
trail_mgr: TrailMgr::default(),
|
trail_mgr: TrailMgr::default(),
|
||||||
figure_mgr: FigureMgr::new(renderer),
|
figure_mgr: FigureMgr::new(renderer),
|
||||||
|
tether_mgr: TetherMgr::new(renderer),
|
||||||
sfx_mgr: SfxMgr::default(),
|
sfx_mgr: SfxMgr::default(),
|
||||||
music_mgr: MusicMgr::new(&calendar),
|
music_mgr: MusicMgr::new(&calendar),
|
||||||
ambient_mgr: AmbientMgr {
|
ambient_mgr: AmbientMgr {
|
||||||
@ -880,6 +884,9 @@ impl Scene {
|
|||||||
// Maintain LoD.
|
// Maintain LoD.
|
||||||
self.lod.maintain(renderer, client, focus_pos, &self.camera);
|
self.lod.maintain(renderer, client, focus_pos, &self.camera);
|
||||||
|
|
||||||
|
// Maintain tethers.
|
||||||
|
self.tether_mgr.maintain(renderer, client, focus_pos);
|
||||||
|
|
||||||
// Maintain debug shapes
|
// Maintain debug shapes
|
||||||
self.debug.maintain(renderer);
|
self.debug.maintain(renderer);
|
||||||
|
|
||||||
@ -1439,6 +1446,9 @@ impl Scene {
|
|||||||
);
|
);
|
||||||
drop(sprite_drawer);
|
drop(sprite_drawer);
|
||||||
|
|
||||||
|
// Render tethers.
|
||||||
|
self.tether_mgr.render(&mut first_pass);
|
||||||
|
|
||||||
// Draws translucent
|
// Draws translucent
|
||||||
self.terrain.render_translucent(&mut first_pass, focus_pos);
|
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…
x
Reference in New Issue
Block a user