Compare commits

..

No commits in common. "1992bd7bb8eed297f47f28c4d22126fd40c19304" and "c7077fc7018015f15528e4173a733d9fb2db4048" have entirely different histories.

23 changed files with 308 additions and 504 deletions

11
TODO.md
View File

@ -1,5 +1,6 @@
## Specific Jobs ## Specific Jobs
- Particle variation - Particle variation
- Particle physics
- UI: health, shield, fuel, heat, energy bars - UI: health, shield, fuel, heat, energy bars
- UI: text arranger - UI: text arranger
- Sound system - Sound system
@ -8,10 +9,12 @@
- Passive engine glow - Passive engine glow
- Ship death damage and force events - Ship death damage and force events
- Fix particle inherit velocity - Fix particle inherit velocity
- Global shader variable prefix
---------------------------------- ----------------------------------
## Game & Story ## Game & Story
- Landmarks to determine speed?
- How to keep player in system bounds, what to do if they fly far away - How to keep player in system bounds, what to do if they fly far away
- Max chase distance, physics-enforced area? - Max chase distance, physics-enforced area?
- (Soft limits, you shouldn't go too far unless you decide to.) - (Soft limits, you shouldn't go too far unless you decide to.)
@ -59,11 +62,11 @@
- how to target them - how to target them
- where to go - where to go
- etc, extra flags - etc, extra flags
- Conditional animations: on fire, on death, etc (tempest)
- Higher texture limit (16 x 8096 x 8096 isn't enough) - Higher texture limit (16 x 8096 x 8096 isn't enough)
- Fast-load menu, progress bar for the rest - Fast-load menu, progress bar for the rest
- Only load what is needed? - Only load what is needed?
- GPU limits? (texture size, texture number) - GPU limits? (texture size, texture number)
- Advanced particle physics (must move to cpu. Maybe both?)
## Faction interaction ## Faction interaction
@ -71,6 +74,7 @@
- Static and dynamic faction relationships (change with player actions/game events?) - Static and dynamic faction relationships (change with player actions/game events?)
- Dynamic relationships only for player? Other governments may be hard-coded - Dynamic relationships only for player? Other governments may be hard-coded
- Opinion towards player -> how to handle well? - Opinion towards player -> how to handle well?
- Player faction is special
- Actions against one faction affect another - Actions against one faction affect another
@ -101,6 +105,9 @@
- Weapons with ammunition - Weapons with ammunition
- Enable/disable weapons - Enable/disable weapons
- Cargo space - Cargo space
- List of ES content--which to implement?
- Ship stats
- Weapon stats
- Display name != internal key - Display name != internal key
- Conversations - Conversations
- Trade - Trade
@ -151,10 +158,10 @@
- More interesting missions - More interesting missions
- Big fleets shouldn't be broken (should the player even have a fleet?) - Big fleets shouldn't be broken (should the player even have a fleet?)
- ES Enemies don't have a real motivation. - ES Enemies don't have a real motivation.
- ES story is a bit basic
- Death matters - save-scumming shouldn't be normal - Death matters - save-scumming shouldn't be normal
- More interesting trading? - More interesting trading?
- Death penalty - Death penalty
- Find your wreckage when you die (dark souls/HK) - Find your wreckage when you die (dark souls/HK)
- Lose some outfits, lose ship? Real risk for going out! (HK does this well) - Lose some outfits, lose ship? Real risk for going out! (HK does this well)
- Damage to ship subsystems - Damage to ship subsystems
- Repair your own ship

View File

@ -3,61 +3,32 @@ sprite = "particle::explosion::small"
lifetime = "inherit" lifetime = "inherit"
inherit_velocity = "target" inherit_velocity = "target"
size = 8.0 size = 8.0
size_rng = 1.6
angle_rng = 360
velocity_scale_parent = 1.0
[effect."large explosion"] [effect."large explosion"]
sprite = "particle::explosion::large" sprite = "particle::explosion::large"
lifetime = "inherit" lifetime = "inherit"
inherit_velocity = "target" inherit_velocity = "target"
size = 25.0 size = 25.0
size_rng = 5.0
angle_rng = 360
velocity_scale_parent = 1.0
[effect."huge explosion"] [effect."huge explosion"]
sprite = "particle::explosion::huge" sprite = "particle::explosion::huge"
lifetime = "inherit" lifetime = "inherit"
inherit_velocity = "target" inherit_velocity = "target"
size = 50.0 size = 50.0
size_rng = 10.0
angle_rng = 360
velocity_scale_parent = 1.0
# Every effect has a parent, some effects have a target
[effect."blaster impact"] [effect."blaster impact"]
sprite = "particle::blaster" sprite = "particle::blaster"
lifetime = "inherit" # number in seconds or inherit from sprite lifetime = "inherit"
lifetime_rng = 0.0 # Random variation of lifetime (up to this value) inherit_velocity = "target"
size = 3.0
size = 3.0 # sprite size, in game units
size_rng = 1.0 # random size variation
angle = 0.0 # absolute starting angle. always added to parent angle.
angle_rng = 90.0 # Starting angle randomness (up to this value)
# Does not affect velocity, only sprite angle
angvel_rng = 0.0 # Angvel randomness, applied to angvel
angvel = 0.0 # Angular velocity at creation
# Total velocity is sum of parent + target velocities with scale applied
velocity_scale_parent = 0.0 # Multiply velocity by this value
velocity_scale_parent_rng = 0.0 # random variation of scale
velocity_scale_target = 1.0
velocity_scale_target_rng = 1.0
direction_rng = 1.0 # Random variation of travel direction, in degrees, applied to velocity vector (/2 each side?)
# TODO: # TODO:
# inherit velocity scale
# absolute velocity/angle (no inherit)
# random lifetime, velocity, angle, spin
# bullet bounce effect: inherit and change velocity
# effect probabilities & variants # effect probabilities & variants
# multiple particles in one effect # multiple particles in one effect
# fade # fade
# document: effect vs particle # document: effect vs particle
# sprite lifetime/fps variation (and effects inherit lifetime later)
# universal effect creator

View File

@ -33,5 +33,5 @@ projectile.impact_effect = "blaster impact"
projectile.expire_effect.sprite = "particle::blaster" projectile.expire_effect.sprite = "particle::blaster"
projectile.expire_effect.lifetime = "inherit" projectile.expire_effect.lifetime = "inherit"
projectile.expire_effect.inherit_velocity = "projectile"
projectile.expire_effect.size = 3.0 projectile.expire_effect.size = 3.0
projectile.expire_effect.velocity_scale_parent = 1.0

View File

@ -57,7 +57,7 @@ collision = [
# Scripted explosion # Scripted explosion
[[ship."Gypsum".collapse.event]] [[ship."Gypsum".collapse.event]]
time = 4.9 time = 5.0
effects = [ effects = [
#[rustfmt:skip], #[rustfmt:skip],
{ effect = "small explosion", count = 8 }, { effect = "small explosion", count = 8 },

View File

@ -26,7 +26,7 @@ file = "projectile/blaster.png"
file = "ship/gypsum.png" file = "ship/gypsum.png"
[sprite."ship::peregrine"] [sprite."ship::peregrine"]
timing.duration = 2 timing.duration = 1.3
repeat = "reverse" repeat = "reverse"
random_start_frame = true random_start_frame = true
frames = [ frames = [

View File

@ -1,12 +1,11 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use cgmath::Rad; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use crate::{handle::SpriteHandle, Content, ContentBuildContext, EffectHandle}; use crate::{handle::SpriteHandle, Content, ContentBuildContext, EffectHandle};
pub(crate) mod syntax { pub(crate) mod syntax {
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use cgmath::Deg;
use serde::Deserialize; use serde::Deserialize;
use crate::{Content, ContentBuildContext, EffectHandle}; use crate::{Content, ContentBuildContext, EffectHandle};
@ -16,26 +15,9 @@ pub(crate) mod syntax {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Effect { pub struct Effect {
pub sprite: String, pub sprite: String,
pub lifetime: EffectLifetime,
pub inherit_velocity: super::ImpactInheritVelocity,
pub size: f32, pub size: f32,
pub size_rng: Option<f32>,
pub lifetime: TextOrFloat,
pub lifetime_rng: Option<f32>,
pub angle: Option<f32>,
pub angle_rng: Option<f32>,
pub angvel: Option<f32>,
pub angvel_rng: Option<f32>,
pub velocity_scale_parent: Option<f32>,
pub velocity_scale_parent_rng: Option<f32>,
pub velocity_scale_target: Option<f32>,
pub velocity_scale_target_rng: Option<f32>,
pub direction_rng: Option<f32>,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum TextOrFloat {
Text(String),
Float(f32),
} }
// We implement building here instead of in super::Effect because // We implement building here instead of in super::Effect because
@ -52,8 +34,8 @@ pub(crate) mod syntax {
}; };
let lifetime = match self.lifetime { let lifetime = match self.lifetime {
TextOrFloat::Float(f) => f, EffectLifetime::Seconds(s) => s,
TextOrFloat::Text(s) => { EffectLifetime::Inherit(s) => {
if s == "inherit" { if s == "inherit" {
let sprite = content.get_sprite(sprite); let sprite = content.get_sprite(sprite);
sprite.fps * sprite.frames.len() as f32 sprite.fps * sprite.frames.len() as f32
@ -67,27 +49,24 @@ pub(crate) mod syntax {
index: content.effects.len(), index: content.effects.len(),
}; };
content.effects.push(super::Effect { content.effects.push(super::Effect {
handle,
sprite, sprite,
size: self.size,
size_rng: self.size_rng.unwrap_or(0.0),
lifetime, lifetime,
lifetime_rng: self.lifetime_rng.unwrap_or(0.0), handle,
angle: Deg(self.angle.unwrap_or(0.0) / 2.0).into(), inherit_velocity: self.inherit_velocity,
angle_rng: Deg(self.angle_rng.unwrap_or(0.0) / 2.0).into(), size: self.size,
angvel: Deg(self.angvel.unwrap_or(0.0)).into(),
angvel_rng: Deg(self.angle_rng.unwrap_or(0.0)).into(),
velocity_scale_parent: self.velocity_scale_parent.unwrap_or(0.0),
velocity_scale_parent_rng: self.velocity_scale_parent_rng.unwrap_or(0.0),
velocity_scale_target: self.velocity_scale_target.unwrap_or(0.0),
velocity_scale_target_rng: self.velocity_scale_target_rng.unwrap_or(0.0),
direction_rng: Deg(self.direction_rng.unwrap_or(0.0) / 2.0).into(),
}); });
return Ok(handle); return Ok(handle);
} }
} }
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum EffectLifetime {
Inherit(String),
Seconds(f32),
}
// This isn't used here, but is pulled in by other content items. // This isn't used here, but is pulled in by other content items.
/// A reference to an effect by name, or an inline definition. /// A reference to an effect by name, or an inline definition.
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -116,54 +95,43 @@ pub(crate) mod syntax {
} }
} }
/// How we should set an effect's velocity
#[derive(Debug, Deserialize, Clone)]
pub enum ImpactInheritVelocity {
/// Don't inherit any velocity.
/// This impact particle will be still.
#[serde(rename = "don't")]
Dont,
/// Inherit target velocity.
/// This impact particle will stick to the object it hits.
#[serde(rename = "target")]
Target,
/// Inherit projectile velocity.
/// This impact particle will continue on its projectile's path.
#[serde(rename = "projectile")]
Projectile,
}
/// The particle a projectile will spawn when it hits something /// The particle a projectile will spawn when it hits something
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Effect { pub struct Effect {
/// This effect's handle
pub handle: EffectHandle,
/// The sprite to use for this particle. /// The sprite to use for this particle.
/// This is most likely animated.
pub sprite: SpriteHandle, pub sprite: SpriteHandle,
/// The height of this particle, in game units. /// This effect's handle
pub size: f32, pub handle: EffectHandle,
/// Random size variation
pub size_rng: f32,
/// How many seconds this particle should live /// How many seconds this particle should live
pub lifetime: f32, pub lifetime: f32,
/// Random lifetime variation /// How we should set this particle's velocity
pub lifetime_rng: f32, pub inherit_velocity: ImpactInheritVelocity,
/// The angle this particle points once spawned /// The height of this particle, in game units.
pub angle: Rad<f32>, pub size: f32,
/// Random angle variation
pub angle_rng: Rad<f32>,
/// How fast this particle spins
pub angvel: Rad<f32>,
/// Random angvel variation
pub angvel_rng: Rad<f32>,
/// The amount of this particle's parent's velocity to inherit
pub velocity_scale_parent: f32,
/// Parent velocity random variation
pub velocity_scale_parent_rng: f32,
/// The amount of this particle's parent's target velocity to inherit.
/// If there is no target, this is zero.
pub velocity_scale_target: f32,
/// Target velocity random variation
pub velocity_scale_target_rng: f32,
/// Travel direction random variation
pub direction_rng: Rad<f32>,
} }
impl crate::Build for Effect { impl crate::Build for Effect {

View File

@ -9,7 +9,7 @@ pub(crate) mod ship;
pub(crate) mod sprite; pub(crate) mod sprite;
pub(crate) mod system; pub(crate) mod system;
pub use effect::Effect; pub use effect::{Effect, ImpactInheritVelocity};
pub use faction::{Faction, Relationship}; pub use faction::{Faction, Relationship};
pub use gun::{Gun, Projectile, ProjectileCollider}; pub use gun::{Gun, Projectile, ProjectileCollider};
pub use outfit::Outfit; pub use outfit::Outfit;

View File

@ -1,41 +1,43 @@
// Pick a frame of a sprite animation from an instance. // Pick frame of animation from an instance.
fn animate(sprite_index: u32, age: f32, offset: f32) -> f32 { //
// This function assumes that the uniform header has been loaded,
// and that `InstanceInput` contains a field `texture_index`
fn animate(instance: InstanceInput, age: f32) -> u32 {
let idx = instance.texture_index;
let len = sprites[idx].frame_count;
let rep = sprites[idx].repeatmode;
let fps = sprites[idx].fps;
var frame: u32 = u32(0);
let len = global_sprites[sprite_index].frame_count;
let rep = global_sprites[sprite_index].repeatmode;
let fps = global_sprites[sprite_index].fps;
var frame: f32 = 0.0;
// Once // Once
if rep == u32(1) { if rep == u32(1) {
frame = min( frame = u32(min(
age / fps + offset, age / fps,
f32(len) - 1.0 f32(len) - 1.0
); ));
// Reverse // Reverse
} else if rep == u32(2) { } else if rep == u32(2) {
let x = age / fps + offset; let x = age / fps;
let m = f32(len) * 2.0 - 1.0; let m = f32(len) * 2.0 - 1.0;
// x fmod m // x fmod m
frame = x - floor(x / m) * m; frame = u32(x - floor(x / m) * m);
if frame >= f32(len) { if frame >= len {
frame = ( frame = len + len - frame - u32(1);
f32(len) + f32(len) - 1.0
// Split integer and fractional part so tweening works properly
- floor(frame)
+ fract(frame)
);
} }
// Repeat (default) // Repeat (default)
} else { } else {
let x = age / fps + offset; let x = age / fps;
let m = f32(len); let m = f32(len);
frame = x - floor(x / m) * m; // x fmod m
frame = u32(x - floor(x / m) * m);
} }
return frame + f32(global_sprites[sprite_index].first_frame); return frame + sprites[idx].first_frame;
} }

View File

@ -1,7 +1,7 @@
// INCLUDE: global uniform header // INCLUDE: global uniform header
struct InstanceInput { struct InstanceInput {
@location(2) sprite_index: u32, @location(2) texture_index: u32,
@location(3) object_index: u32, @location(3) object_index: u32,
}; };
@ -12,11 +12,8 @@ struct VertexInput {
struct VertexOutput { struct VertexOutput {
@builtin(position) position: vec4<f32>, @builtin(position) position: vec4<f32>,
@location(0) tween: f32, @location(0) texture_coords: vec2<f32>,
@location(1) texture_index_a: u32, @location(1) texture_index: u32,
@location(2) texture_coords_a: vec2<f32>,
@location(3) texture_index_b: u32,
@location(4) texture_coords_b: vec2<f32>,
}; };
@ -28,15 +25,15 @@ var sampler_array: binding_array<sampler>;
// INCLUDE: animate.wgsl // INCLUDE: animate.wgsl
fn transform_vertex(obj: ObjectData, vertex_position: vec2<f32>, sprite_index: u32) -> vec4<f32> { fn transform_vertex(obj: ObjectLocation, vertex: VertexInput, instance: InstanceInput) -> vec4<f32> {
// Object scale // Object scale
let scale = obj.size / (global_data.camera_zoom.x * obj.zpos); let scale = obj.size / (global.camera_zoom.x * obj.zpos);
// Apply scale and sprite aspect // Apply scale and sprite aspect
// Note that our mesh starts centered at (0, 0). This is important! // Note that our mesh starts centered at (0, 0). This is important!
var pos: vec2<f32> = vec2( var pos: vec2<f32> = vec2(
vertex_position.x * scale * global_sprites[sprite_index].aspect, vertex.position.x * scale * sprites[instance.texture_index].aspect,
vertex_position.y * scale vertex.position.y * scale
); );
// Apply rotation // Apply rotation
@ -45,59 +42,25 @@ fn transform_vertex(obj: ObjectData, vertex_position: vec2<f32>, sprite_index: u
vec2(-sin(obj.angle), cos(obj.angle)) vec2(-sin(obj.angle), cos(obj.angle))
) * pos; ) * pos;
// Correct for screen aspect, preserving height // Correct for screen aspect, preserving height
// This must be done AFTER rotation. // This must be done AFTER rotation.
// (It's thus done later if this is a child)
if obj.is_child == u32(0) {
pos = vec2( pos = vec2(
pos.x / global_data.window_aspect.x, pos.x / global.window_aspect.x,
pos.y pos.y
); );
}
// Translate // Distance-adjusted world position
let trans = (vec2(obj.xpos, obj.ypos) - global.camera_position) / obj.zpos;
// Finally, translate
// //
// Note that we divide camera zoom by two. // Note that we divide camera zoom by two.
// The height of the viewport is `zoom` in game units, // The height of the viewport is `zoom` in game units,
// but it's 2 in screen units (since coordinates range from -1 to 1) // but it's 2 in screen units (since coordinates range from -1 to 1)
if obj.is_child == u32(0) {
let trans = (vec2(obj.xpos, obj.ypos) - global_data.camera_position) / obj.zpos;
pos = pos + vec2( pos = pos + vec2(
trans.x / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x, trans.x / (global.camera_zoom.x / 2.0) / global.window_aspect.x,
trans.y / (global_data.camera_zoom.x / 2.0) trans.y / (global.camera_zoom.x / 2.0)
); );
}
if obj.is_child == u32(1) {
// Apply translation relative to parent
// Note that obj.zpos is ignored
pos = pos + vec2(
obj.xpos / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x,
obj.ypos / (global_data.camera_zoom.x / 2.0)
);
let parent = objects[obj.parent];
// Apply parent's rotation
pos = mat2x2(
vec2(cos(parent.angle), sin(parent.angle)),
vec2(-sin(parent.angle), cos(parent.angle))
) * pos;
// Correct for screen aspect, preserving height
pos = vec2(
pos.x / global_data.window_aspect.x,
pos.y
);
// Apply parent's translation
let ptrans = (vec2(parent.xpos, parent.ypos) - global_data.camera_position) / parent.zpos;
pos = pos + vec2(
ptrans.x / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x,
ptrans.y / (global_data.camera_zoom.x / 2.0)
);
}
return vec4<f32>(pos, 0.0, 1.0);; return vec4<f32>(pos, 0.0, 1.0);;
} }
@ -108,36 +71,16 @@ fn vertex_main(
instance: InstanceInput, instance: InstanceInput,
) -> VertexOutput { ) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
out.position = transform_vertex(objects[instance.object_index], vertex, instance);
let t = atlas[animate(instance, global.current_time.x)];
out.texture_index = t.atlas_texture;
out.position = transform_vertex(objects[instance.object_index], vertex.position.xy, instance.sprite_index); out.texture_coords = vec2(t.xpos, t.ypos);
// Compute texture coordinates
let age = global_data.current_time.x;
let frame = animate(instance.sprite_index, age, 0.0);
out.tween = fract(frame);
let t = global_atlas[u32(floor(frame))];
out.texture_index_a = u32(t.atlas_texture);
out.texture_coords_a = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 { if vertex.texture_coords.x == 1.0 {
out.texture_coords_a = out.texture_coords_a + vec2(t.width, 0.0); out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);
} }
if vertex.texture_coords.y == 1.0 { if vertex.texture_coords.y == 1.0 {
out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height); out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height);
}
let b = global_atlas[u32(floor(animate(instance.sprite_index, age, 1.0)))];
out.texture_index_b = u32(b.atlas_texture);
out.texture_coords_b = vec2(b.xpos, b.ypos);
if vertex.texture_coords.x == 1.0 {
out.texture_coords_b = out.texture_coords_b + vec2(b.width, 0.0);
}
if vertex.texture_coords.y == 1.0 {
out.texture_coords_b = out.texture_coords_b + vec2(0.0, b.height);
} }
return out; return out;
@ -146,19 +89,10 @@ fn vertex_main(
@fragment @fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
return mix( return textureSampleLevel(
textureSampleLevel( texture_array[in.texture_index],
texture_array[in.texture_index_a],
sampler_array[0], sampler_array[0],
in.texture_coords_a, in.texture_coords,
0.0 0.0
).rgba, ).rgba;
textureSampleLevel(
texture_array[in.texture_index_b],
sampler_array[0],
in.texture_coords_b,
0.0
).rgba,
in.tween
);
} }

View File

@ -3,12 +3,12 @@
struct InstanceInput { struct InstanceInput {
@location(2) position: vec2<f32>, @location(2) position: vec2<f32>,
@location(3) velocity: vec2<f32>, @location(3) velocity: vec2<f32>,
@location(4) angle: f32, @location(4) rotation_0: vec2<f32>,
@location(5) angvel: f32, @location(5) rotation_1: vec2<f32>,
@location(6) size: f32, @location(6) size: f32,
@location(7) created: f32, @location(7) created: f32,
@location(8) expires: f32, @location(8) expires: f32,
@location(9) sprite_index: u32, @location(9) texture_index: u32,
}; };
struct VertexInput { struct VertexInput {
@ -18,11 +18,8 @@ struct VertexInput {
struct VertexOutput { struct VertexOutput {
@builtin(position) position: vec4<f32>, @builtin(position) position: vec4<f32>,
@location(0) tween: f32, @location(0) texture_coords: vec2<f32>,
@location(1) texture_index_a: u32, @location(1) texture_index: u32,
@location(2) texture_coords_a: vec2<f32>,
@location(3) texture_index_b: u32,
@location(4) texture_coords_b: vec2<f32>,
} }
@ -41,95 +38,64 @@ fn vertex_main(
) -> VertexOutput { ) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
out.texture_coords = vertex.texture_coords;
// Skip expired particles // Skip expired particles
if instance.expires < global_data.current_time.x { if instance.expires < global.current_time.x {
out.tween = 0.0; out.texture_index = u32(0);
out.texture_index_a = u32(0); // Draw off screen
out.texture_index_b = u32(0);
out.texture_coords_a = vec2(0.0, 0.0);
out.texture_coords_b = vec2(0.0, 0.0);
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0); out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
return out; return out;
} }
let age = global_data.current_time.x - instance.created; let age = global.current_time.x - instance.created;
// Apply transformations // Apply transformations
let angle = instance.angle + age * instance.angvel; let rotation = mat2x2(instance.rotation_0, instance.rotation_1);
var scale: f32 = instance.size / global.camera_zoom.x;
var scale: f32 = instance.size / global_data.camera_zoom.x; var pos: vec2<f32> = vec2(vertex.position.x, vertex.position.y);
var pos: vec2<f32> = vec2( pos = pos * vec2<f32>(
vertex.position.x * scale * global_sprites[instance.sprite_index].aspect, sprites[instance.texture_index].aspect * scale / global.window_aspect.x,
vertex.position.y * scale scale
);
pos = mat2x2(
vec2(cos(angle), sin(angle)),
vec2(-sin(angle), cos(angle))
) * pos;
var trans: vec2<f32> = (instance.position + (instance.velocity * age) - global_data.camera_position);
pos = vec2(
pos.x / global_data.window_aspect.x,
pos.y
); );
pos = pos + vec2( pos = rotation * pos;
trans.x / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x,
trans.y / (global_data.camera_zoom.x / 2.0) var ipos: vec2<f32> = (
vec2(instance.position.x, instance.position.y)
+ (instance.velocity * age)
- global.camera_position
); );
pos = pos + vec2<f32>(
ipos.x / (global.camera_zoom.x/2.0) / global.window_aspect.x,
ipos.y / (global.camera_zoom.x/2.0)
);
out.position = vec4(pos, 0.0, 1.0); out.position = vec4<f32>(pos, 0.0, 1.0);
// Compute texture coordinates // Compute texture coordinates
let frame = animate(instance.sprite_index, age, 0.0); let t = atlas[animate(instance, age)];
out.tween = fract(frame); out.texture_index = u32(t.atlas_texture);
out.texture_coords = vec2(t.xpos, t.ypos);
let t = global_atlas[u32(floor(frame))];
out.texture_index_a = u32(t.atlas_texture);
out.texture_coords_a = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 { if vertex.texture_coords.x == 1.0 {
out.texture_coords_a = out.texture_coords_a + vec2(t.width, 0.0); out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);
} }
if vertex.texture_coords.y == 1.0 { if vertex.texture_coords.y == 1.0 {
out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height); out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height);
} }
let b = global_atlas[u32(floor(animate(instance.sprite_index, age, 1.0)))];
out.texture_index_b = u32(b.atlas_texture);
out.texture_coords_b = vec2(b.xpos, b.ypos);
if vertex.texture_coords.x == 1.0 {
out.texture_coords_b = out.texture_coords_b + vec2(b.width, 0.0);
}
if vertex.texture_coords.y == 1.0 {
out.texture_coords_b = out.texture_coords_b + vec2(0.0, b.height);
}
return out; return out;
} }
@fragment @fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSampleLevel(
return mix( texture_array[in.texture_index],
textureSampleLevel(
texture_array[in.texture_index_a],
sampler_array[0], sampler_array[0],
in.texture_coords_a, in.texture_coords,
0.0 0.0
).rgba, ).rgba;
textureSampleLevel(
texture_array[in.texture_index_b],
sampler_array[0],
in.texture_coords_b,
0.0
).rgba,
in.tween
);
} }

View File

@ -41,18 +41,18 @@ fn vertex_main(
// Center of the tile the camera is currently in, in game coordinates. // Center of the tile the camera is currently in, in game coordinates.
// x div y = x - (x mod y) // x div y = x - (x mod y)
let tile_center = ( let tile_center = (
global_data.camera_position.xy global.camera_position.xy
- ( - (
fmod( fmod(
global_data.camera_position.xy + global_data.starfield_tile_size.x / 2.0, global.camera_position.xy + global.starfield_tile_size.x / 2.0,
global_data.starfield_tile_size.x global.starfield_tile_size.x
) - global_data.starfield_tile_size.x / 2.0 ) - global.starfield_tile_size.x / 2.0
) )
); );
let zoom_min_times = ( let zoom_min_times = (
global_data.camera_zoom.x / global_data.camera_zoom_limits.x global.camera_zoom.x / global.camera_zoom_limits.x
); );
// Hide n% of the smallest stars // Hide n% of the smallest stars
@ -64,8 +64,8 @@ fn vertex_main(
if ( if (
instance.size < ( instance.size < (
hide_fraction * (global_data.starfield_size_limits.y - global_data.starfield_size_limits.x) hide_fraction * (global.starfield_size_limits.y - global.starfield_size_limits.x)
+ (global_data.starfield_size_limits.x) + (global.starfield_size_limits.x)
) )
) { ) {
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0); out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
@ -76,41 +76,41 @@ fn vertex_main(
// Apply sprite aspect ratio & scale factor // Apply sprite aspect ratio & scale factor
// also applies screen aspect ratio // also applies screen aspect ratio
// Note that we do NOT scale for distance here---this is intentional. // Note that we do NOT scale for distance here---this is intentional.
var scale: f32 = instance.size / global_data.camera_zoom.x; var scale: f32 = instance.size / global.camera_zoom.x;
// Minimum scale to prevent flicker at large zoom levels // Minimum scale to prevent flicker at large zoom levels
var real_size = scale * global_data.window_size.xy; var real_size = scale * global.window_size.xy;
if (real_size.x < 2.0 || real_size.y < 2.0) { if (real_size.x < 2.0 || real_size.y < 2.0) {
// Otherwise, clamp to a minimum scale // Otherwise, clamp to a minimum scale
scale = 2.0 / max(global_data.window_size.x, global_data.window_size.y); scale = 2.0 / max(global.window_size.x, global.window_size.y);
} }
// Divide by two, because viewport height is 2 in screen units // Divide by two, because viewport height is 2 in screen units
// (coordinates go from -1 to 1) // (coordinates go from -1 to 1)
var pos: vec2<f32> = vec2<f32>( var pos: vec2<f32> = vec2<f32>(
vertex.position.x * (scale/2.0) / global_data.window_aspect.x, vertex.position.x * (scale/2.0) / global.window_aspect.x,
vertex.position.y * (scale/2.0) vertex.position.y * (scale/2.0)
); );
// World position relative to camera // World position relative to camera
// (Note that instance position is in a different // (Note that instance position is in a different
// coordinate system than usual) // coordinate system than usual)
let camera_pos = (instance.position.xy + tile_center) - global_data.camera_position.xy; let camera_pos = (instance.position.xy + tile_center) - global.camera_position.xy;
// Translate // Translate
pos = pos + ( pos = pos + (
// Don't forget to correct distance for screen aspect ratio too! // Don't forget to correct distance for screen aspect ratio too!
(camera_pos / (global_data.camera_zoom.x * instance.position.z)) (camera_pos / (global.camera_zoom.x * instance.position.z))
/ vec2<f32>(global_data.window_aspect.x, 1.0) / vec2<f32>(global.window_aspect.x, 1.0)
); );
out.position = vec4<f32>(pos, 0.0, 1.0) * instance.position.z; out.position = vec4<f32>(pos, 0.0, 1.0) * instance.position.z;
// Starfield sprites may not be animated // Starfield sprites may not be animated
let i = global_sprites[global_data.starfield_sprite.x].first_frame; let i = sprites[global.starfield_sprite.x].first_frame;
let t = global_atlas[i]; let t = atlas[i];
out.texture_index = u32(t.atlas_texture); out.texture_index = u32(t.atlas_texture);
out.texture_coords = vec2(t.xpos, t.ypos); out.texture_coords = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 { if vertex.texture_coords.x == 1.0 {

View File

@ -6,7 +6,7 @@ struct InstanceInput {
@location(4) transform_matrix_2: vec4<f32>, @location(4) transform_matrix_2: vec4<f32>,
@location(5) transform_matrix_3: vec4<f32>, @location(5) transform_matrix_3: vec4<f32>,
@location(6) color_transform: vec4<f32>, @location(6) color_transform: vec4<f32>,
@location(7) sprite_index: u32, @location(7) texture_index: u32,
}; };
struct VertexInput { struct VertexInput {
@ -48,7 +48,7 @@ fn vertex_main(
out.color_transform = instance.color_transform; out.color_transform = instance.color_transform;
// Pick texture frame // Pick texture frame
let t = global_atlas[u32(animate(instance.sprite_index, global_data.current_time.x, 0.0))]; let t = atlas[animate(instance, global.current_time.x)];
out.texture_index = u32(t.atlas_texture); out.texture_index = u32(t.atlas_texture);
out.texture_coords = vec2(t.xpos, t.ypos); out.texture_coords = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 { if vertex.texture_coords.x == 1.0 {

View File

@ -5,7 +5,7 @@ use wgpu;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, Pod, Zeroable, Default)] #[derive(Debug, Copy, Clone, Pod, Zeroable, Default)]
pub struct AtlasImageLocation { pub struct ImageLocation {
// Image box, in texture coordinates // Image box, in texture coordinates
pub xpos: f32, pub xpos: f32,
pub ypos: f32, pub ypos: f32,
@ -20,14 +20,14 @@ pub struct AtlasImageLocation {
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct AtlasArray { pub struct AtlasArray {
pub data: [AtlasImageLocation; IMAGE_LIMIT as usize], pub data: [ImageLocation; IMAGE_LIMIT as usize],
} }
unsafe impl Pod for AtlasArray {} unsafe impl Pod for AtlasArray {}
unsafe impl Zeroable for AtlasArray { unsafe impl Zeroable for AtlasArray {
fn zeroed() -> Self { fn zeroed() -> Self {
Self { Self {
data: [AtlasImageLocation::zeroed(); IMAGE_LIMIT as usize], data: [ImageLocation::zeroed(); IMAGE_LIMIT as usize],
} }
} }
} }

View File

@ -9,7 +9,7 @@ use wgpu;
// all smaller values must be padded. // all smaller values must be padded.
// also, [f32; 3] are aligned as [f32; 4] // also, [f32; 3] are aligned as [f32; 4]
// (since alignments must be powers of two) // (since alignments must be powers of two)
pub struct GlobalDataContent { pub struct DataContent {
/// Camera position, in game units /// Camera position, in game units
pub camera_position: [f32; 2], pub camera_position: [f32; 2],
@ -43,6 +43,6 @@ pub struct GlobalDataContent {
pub current_time: [f32; 2], pub current_time: [f32; 2],
} }
impl GlobalDataContent { impl DataContent {
pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress; pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
} }

View File

@ -1,7 +1,7 @@
use galactica_constants::{IMAGE_LIMIT, OBJECT_SPRITE_INSTANCE_LIMIT, SPRITE_LIMIT}; use galactica_constants::{IMAGE_LIMIT, OBJECT_SPRITE_INSTANCE_LIMIT, SPRITE_LIMIT};
use wgpu; use wgpu;
use super::{object::ObjectLocationArray, AtlasArray, GlobalDataContent, SpriteDataArray}; use super::{object::ObjectLocationArray, AtlasArray, DataContent, SpriteDataArray};
pub struct GlobalUniform { pub struct GlobalUniform {
pub data_buffer: wgpu::Buffer, pub data_buffer: wgpu::Buffer,
@ -10,7 +10,7 @@ pub struct GlobalUniform {
pub object_buffer: wgpu::Buffer, pub object_buffer: wgpu::Buffer,
pub bind_group: wgpu::BindGroup, pub bind_group: wgpu::BindGroup,
pub bind_group_layout: wgpu::BindGroupLayout, pub bind_group_layout: wgpu::BindGroupLayout,
pub content: GlobalDataContent, pub content: DataContent,
} }
impl GlobalUniform { impl GlobalUniform {
@ -21,8 +21,8 @@ impl GlobalUniform {
out.push_str(&format!("@group({group}) @binding(0)\n")); out.push_str(&format!("@group({group}) @binding(0)\n"));
out.push_str( out.push_str(
r#" r#"
var<uniform> global_data: GlobalData; var<uniform> global: GlobalUniform;
struct GlobalData { struct GlobalUniform {
camera_position: vec2<f32>, camera_position: vec2<f32>,
camera_zoom: vec2<f32>, camera_zoom: vec2<f32>,
camera_zoom_limits: vec2<f32>, camera_zoom_limits: vec2<f32>,
@ -41,13 +41,13 @@ impl GlobalUniform {
out.push_str(&format!( out.push_str(&format!(
r#" r#"
@group({group}) @binding(1) @group({group}) @binding(1)
var<uniform> global_atlas: array<AtlasImageLocation, {IMAGE_LIMIT}>; var<uniform> atlas: array<ImageLocation, {IMAGE_LIMIT}>;
"# "#
)); ));
out.push_str("\n"); out.push_str("\n");
out.push_str( out.push_str(
r#" r#"
struct AtlasImageLocation { struct ImageLocation {
xpos: f32, xpos: f32,
ypos: f32, ypos: f32,
width: f32, width: f32,
@ -73,7 +73,7 @@ impl GlobalUniform {
out.push_str(&format!( out.push_str(&format!(
r#" r#"
@group({group}) @binding(2) @group({group}) @binding(2)
var<uniform> global_sprites: array<SpriteData, {SPRITE_LIMIT}>; var<uniform> sprites: array<SpriteData, {SPRITE_LIMIT}>;
"# "#
)); ));
out.push_str("\n"); out.push_str("\n");
@ -98,22 +98,22 @@ impl GlobalUniform {
out.push_str(&format!( out.push_str(&format!(
r#" r#"
@group({group}) @binding(3) @group({group}) @binding(3)
var<uniform> objects: array<ObjectData, {OBJECT_SPRITE_INSTANCE_LIMIT}>; var<uniform> objects: array<ObjectLocation, {OBJECT_SPRITE_INSTANCE_LIMIT}>;
"# "#
)); ));
out.push_str("\n"); out.push_str("\n");
out.push_str( out.push_str(
r#" r#"
struct ObjectData { struct ObjectLocation {
xpos: f32, xpos: f32,
ypos: f32, ypos: f32,
zpos: f32, zpos: f32,
angle: f32, angle: f32,
size: f32, size: f32,
parent: u32,
is_child: u32,
padding_a: f32, padding_a: f32,
padding_b: f32,
padding_c: f32,
}; };
"#, "#,
); );
@ -125,7 +125,7 @@ impl GlobalUniform {
pub fn new(device: &wgpu::Device) -> Self { pub fn new(device: &wgpu::Device) -> Self {
let data_buffer = device.create_buffer(&wgpu::BufferDescriptor { let data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("global uniform data buffer"), label: Some("global uniform data buffer"),
size: GlobalDataContent::SIZE, size: DataContent::SIZE,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false, mapped_at_creation: false,
}); });
@ -227,7 +227,7 @@ impl GlobalUniform {
object_buffer, object_buffer,
bind_group, bind_group,
bind_group_layout, bind_group_layout,
content: GlobalDataContent::default(), content: DataContent::default(),
}; };
} }
} }

View File

@ -4,8 +4,8 @@ mod globaluniform;
mod object; mod object;
mod sprite; mod sprite;
pub use atlas::{AtlasArray, AtlasImageLocation}; pub use atlas::{AtlasArray, ImageLocation};
pub use data::GlobalDataContent; pub use data::DataContent;
pub use globaluniform::GlobalUniform; pub use globaluniform::GlobalUniform;
pub use object::ObjectData; pub use object::ObjectLocation;
pub use sprite::{SpriteData, SpriteDataArray}; pub use sprite::{SpriteData, SpriteDataArray};

View File

@ -5,36 +5,30 @@ use wgpu;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, Pod, Zeroable, Default)] #[derive(Debug, Copy, Clone, Pod, Zeroable, Default)]
pub struct ObjectData { pub struct ObjectLocation {
pub xpos: f32, pub xpos: f32,
pub ypos: f32, pub ypos: f32,
pub zpos: f32, pub zpos: f32,
pub angle: f32, pub angle: f32,
pub size: f32, pub size: f32,
/// Index of parent object pub _padding: [f32; 3],
pub parent: u32,
/// 1 if has parent, 0 if not
pub is_child: u32,
pub _padding: [f32; 1],
} }
impl ObjectData { impl ObjectLocation {
pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress; pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct ObjectLocationArray { pub struct ObjectLocationArray {
pub data: [ObjectData; OBJECT_SPRITE_INSTANCE_LIMIT as usize], pub data: [ObjectLocation; OBJECT_SPRITE_INSTANCE_LIMIT as usize],
} }
unsafe impl Pod for ObjectLocationArray {} unsafe impl Pod for ObjectLocationArray {}
unsafe impl Zeroable for ObjectLocationArray { unsafe impl Zeroable for ObjectLocationArray {
fn zeroed() -> Self { fn zeroed() -> Self {
Self { Self {
data: [ObjectData::zeroed(); OBJECT_SPRITE_INSTANCE_LIMIT as usize], data: [ObjectLocation::zeroed(); OBJECT_SPRITE_INSTANCE_LIMIT as usize],
} }
} }
} }

View File

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use bytemuck; use bytemuck;
use cgmath::{EuclideanSpace, Matrix4, Point2, Rad, Vector3}; use cgmath::{EuclideanSpace, Matrix2, Matrix4, Point2, Rad, Vector3};
use galactica_constants; use galactica_constants;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use std::{iter, rc::Rc}; use std::{iter, rc::Rc};
@ -9,8 +9,9 @@ use winit::{self, dpi::LogicalSize, window::Window};
use crate::{ use crate::{
content, content,
globaluniform::{GlobalDataContent, GlobalUniform, ObjectData}, globaluniform::{DataContent, GlobalUniform, ObjectLocation},
pipeline::PipelineBuilder, pipeline::PipelineBuilder,
sprite::ObjectSubSprite,
starfield::Starfield, starfield::Starfield,
texturearray::TextureArray, texturearray::TextureArray,
vertexbuffer::{ vertexbuffer::{
@ -301,6 +302,7 @@ impl GPUState {
} }
/// Create a ObjectInstance for an object and add it to `instances`. /// Create a ObjectInstance for an object and add it to `instances`.
/// Also handles child sprites.
fn push_object_sprite( fn push_object_sprite(
&self, &self,
state: &RenderState, state: &RenderState,
@ -326,7 +328,8 @@ impl GPUState {
// We take the maximum to account for rotated sprites. // We take the maximum to account for rotated sprites.
let m = (s.size / s.pos.z) * s.sprite.aspect.max(1.0); let m = (s.size / s.pos.z) * s.sprite.aspect.max(1.0);
// Don't draw sprites that are off the screen // Don't draw (or compute matrices for)
// sprites that are off the screen
if pos.x < clip_ne.x - m if pos.x < clip_ne.x - m
|| pos.y > clip_ne.y + m || pos.y > clip_ne.y + m
|| pos.x > clip_sw.x + m || pos.x > clip_sw.x + m
@ -335,19 +338,16 @@ impl GPUState {
return; return;
} }
let idx = instances.len();
// Write this object's location data // Write this object's location data
self.queue.write_buffer( self.queue.write_buffer(
&self.global_uniform.object_buffer, &self.global_uniform.object_buffer,
ObjectData::SIZE * idx as u64, ObjectLocation::SIZE * instances.len() as u64,
bytemuck::cast_slice(&[ObjectData { bytemuck::cast_slice(&[ObjectLocation {
xpos: s.pos.x, xpos: s.pos.x,
ypos: s.pos.y, ypos: s.pos.y,
zpos: s.pos.z, zpos: s.pos.z,
angle: Rad::from(s.angle).0, angle: Rad::from(s.angle).0,
size: s.size, size: s.size,
parent: 0,
is_child: 0,
_padding: Default::default(), _padding: Default::default(),
}]), }]),
); );
@ -355,36 +355,62 @@ impl GPUState {
// Push this object's instance // Push this object's instance
instances.push(ObjectInstance { instances.push(ObjectInstance {
sprite_index: s.sprite.get_index(), sprite_index: s.sprite.get_index(),
object_index: idx as u32,
});
// Add children
if let Some(children) = &s.children {
for c in children {
self.queue.write_buffer(
&self.global_uniform.object_buffer,
ObjectData::SIZE * instances.len() as u64,
bytemuck::cast_slice(&[ObjectData {
xpos: c.pos.x,
ypos: c.pos.y,
zpos: c.pos.z,
angle: Rad::from(c.angle).0,
size: c.size,
parent: idx as u32,
is_child: 1,
_padding: Default::default(),
}]),
);
instances.push(ObjectInstance {
sprite_index: c.sprite.get_index(),
object_index: instances.len() as u32, object_index: instances.len() as u32,
}); });
} }
/*
// Add children
if let Some(children) = &s.children {
for c in children {
self.push_object_subsprite(&state, instances, c, pos, s.angle);
}
} }
} }
/// Create a UiInstance for a ui sprite and add it to `instances` /// Add an object sprite's subsprite to `instances`.
/// Only called by `self.push_object_sprite`.
fn push_object_subsprite(
&self,
state: &RenderState,
instances: &mut Vec<ObjectInstance>,
s: &ObjectSubSprite,
parent_pos: Point2<f32>,
parent_angle: Deg<f32>,
) {
let scale = s.size / (s.pos.z * state.camera_zoom);
let sprite_aspect_and_scale =
Matrix4::from_nonuniform_scale(s.sprite.aspect * scale, scale, 1.0);
let rotate = Matrix4::from_angle_z(s.angle);
let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0);
let ptranslate = Matrix4::from_translation(Vector3 {
x: parent_pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
y: parent_pos.y / (state.camera_zoom / 2.0),
z: 0.0,
});
let protate = Matrix4::from_angle_z(parent_angle);
let translate = Matrix4::from_translation(Vector3 {
x: s.pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
y: s.pos.y / (state.camera_zoom / 2.0),
z: 0.0,
});
// Order matters!
// The rightmost matrix is applied first.
let t = OPENGL_TO_WGPU_MATRIX
* ptranslate * screen_aspect
* protate * translate
* rotate * sprite_aspect_and_scale;
instances.push(ObjectInstance {
transform: t.into(),
sprite_index: s.sprite.get_index(),
});
}
*/
/// Create a ObjectInstance for a ui sprite and add it to `instances`
fn push_ui_sprite(&self, instances: &mut Vec<UiInstance>, s: &UiSprite) { fn push_ui_sprite(&self, instances: &mut Vec<UiInstance>, s: &UiSprite) {
let logical_size: LogicalSize<f32> = let logical_size: LogicalSize<f32> =
self.window_size.to_logical(self.window.scale_factor()); self.window_size.to_logical(self.window.scale_factor());
@ -552,7 +578,7 @@ impl GPUState {
self.queue.write_buffer( self.queue.write_buffer(
&self.global_uniform.data_buffer, &self.global_uniform.data_buffer,
0, 0,
bytemuck::cast_slice(&[GlobalDataContent { bytemuck::cast_slice(&[DataContent {
camera_position: state.camera_pos.into(), camera_position: state.camera_pos.into(),
camera_zoom: [state.camera_zoom, 0.0], camera_zoom: [state.camera_zoom, 0.0],
camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX], camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX],
@ -580,8 +606,7 @@ impl GPUState {
bytemuck::cast_slice(&[ParticleInstance { bytemuck::cast_slice(&[ParticleInstance {
position: [i.pos.x, i.pos.y], position: [i.pos.x, i.pos.y],
velocity: i.velocity.into(), velocity: i.velocity.into(),
angle: i.angle.0, rotation: Matrix2::from_angle(i.angle).into(),
angvel: i.angvel.0,
size: i.size, size: i.size,
sprite_index: i.sprite.get_index(), sprite_index: i.sprite.get_index(),
created: state.current_time, created: state.current_time,

View File

@ -1,5 +1,5 @@
use crate::content; use crate::content;
use cgmath::{Deg, Point2, Point3, Rad, Vector2}; use cgmath::{Deg, Point2, Point3, Vector2};
/// Instructions to create a new particle /// Instructions to create a new particle
pub struct ParticleBuilder { pub struct ParticleBuilder {
@ -12,11 +12,8 @@ pub struct ParticleBuilder {
/// This particle's velocity, in world coordinates /// This particle's velocity, in world coordinates
pub velocity: Vector2<f32>, pub velocity: Vector2<f32>,
/// This particle's angle /// This particle's angle, in world coordinates
pub angle: Rad<f32>, pub angle: Deg<f32>,
/// This particle's angular velocity (rad/sec)
pub angvel: Rad<f32>,
/// This particle's lifetime, in seconds /// This particle's lifetime, in seconds
pub lifetime: f32, pub lifetime: f32,

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
content, content,
globaluniform::{AtlasArray, AtlasImageLocation, SpriteData, SpriteDataArray}, globaluniform::{AtlasArray, ImageLocation, SpriteData, SpriteDataArray},
}; };
use anyhow::Result; use anyhow::Result;
use bytemuck::Zeroable; use bytemuck::Zeroable;
@ -129,7 +129,7 @@ impl TextureArray {
// Insert texture location data // Insert texture location data
for path in &t.frames { for path in &t.frames {
let image = ct.get_image(&path); let image = ct.get_image(&path);
image_locations.data[image_counter as usize] = AtlasImageLocation { image_locations.data[image_counter as usize] = ImageLocation {
xpos: image.x, xpos: image.x,
ypos: image.y, ypos: image.y,
width: image.w, width: image.w,

View File

@ -186,14 +186,11 @@ pub struct ParticleInstance {
/// World position of this particle /// World position of this particle
pub position: [f32; 2], pub position: [f32; 2],
/// Velocity of this particle, in world coordinates /// Velocity of this sprite, in world coordinates
pub velocity: [f32; 2], pub velocity: [f32; 2],
/// Angle of this particle, in radians /// Rotation matrix for this sprite, in world coordinates
pub angle: f32, pub rotation: [[f32; 2]; 2],
/// Angular velocity of this particle, rad/sec
pub angvel: f32,
/// The height of this particle, in world units /// The height of this particle, in world units
pub size: f32, pub size: f32,
@ -228,39 +225,38 @@ impl BufferObject for ParticleInstance {
shader_location: 3, shader_location: 3,
format: wgpu::VertexFormat::Float32x2, format: wgpu::VertexFormat::Float32x2,
}, },
// Angle // Rotation
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
shader_location: 4, shader_location: 4,
format: wgpu::VertexFormat::Float32, format: wgpu::VertexFormat::Float32x2,
}, },
// Angvel
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
shader_location: 5, shader_location: 5,
format: wgpu::VertexFormat::Float32, format: wgpu::VertexFormat::Float32x2,
}, },
// Size // Size
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
shader_location: 6, shader_location: 6,
format: wgpu::VertexFormat::Float32, format: wgpu::VertexFormat::Float32,
}, },
// Created // Created
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 7]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
shader_location: 7, shader_location: 7,
format: wgpu::VertexFormat::Float32, format: wgpu::VertexFormat::Float32,
}, },
// Expires // Expires
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 10]>() as wgpu::BufferAddress,
shader_location: 8, shader_location: 8,
format: wgpu::VertexFormat::Float32, format: wgpu::VertexFormat::Float32,
}, },
// Sprite // Sprite
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
shader_location: 9, shader_location: 9,
format: wgpu::VertexFormat::Uint32, format: wgpu::VertexFormat::Uint32,
}, },

View File

@ -100,48 +100,16 @@ impl ShipCollapseSequence {
}; };
// Position, adjusted for ship rotation // Position, adjusted for ship rotation
let pos = ship_pos let pos =
+ Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos; Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos;
let velocity = {
let a = self.rng.gen_range(
-effect.velocity_scale_parent_rng
..=effect.velocity_scale_parent_rng,
);
let velocity = (effect.velocity_scale_parent + a)
* rigid_body.velocity_at_point(&point![pos.x, pos.y]);
Matrix2::from_angle(Rad(self.rng.gen_range(
-effect.direction_rng.0..=effect.direction_rng.0,
))) * Vector2 {
x: velocity.x,
y: velocity.y,
}
};
particles.push(ParticleBuilder { particles.push(ParticleBuilder {
sprite: effect.sprite, sprite: effect.sprite,
pos, pos: ship_pos + pos,
velocity, velocity: Vector2::zero(),
angle: Deg::zero(),
angle: effect.angle lifetime: effect.lifetime,
+ Rad(self size: effect.size,
.rng
.gen_range(-effect.angle_rng.0..=effect.angle_rng.0)),
angvel: Rad(effect.angvel.0
+ self
.rng
.gen_range(-effect.angvel_rng.0..=effect.angvel_rng.0)),
lifetime: effect.lifetime
+ self
.rng
.gen_range(-effect.lifetime_rng..=effect.lifetime_rng),
size: effect.size
+ self.rng.gen_range(-effect.size_rng..=effect.size_rng),
}); });
} }
} }
@ -177,15 +145,13 @@ impl ShipCollapseSequence {
}; };
// Position, adjusted for ship rotation // Position, adjusted for ship rotation
let pos = ship_pos + Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos; let pos = Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos;
let vel = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
particles.push(ParticleBuilder { particles.push(ParticleBuilder {
sprite: effect.sprite, sprite: effect.sprite,
pos, pos: ship_pos + pos,
velocity: Vector2 { x: vel.x, y: vel.y }, velocity: Vector2::zero(),
angle: Rad::zero(), angle: Deg::zero(),
angvel: Rad::zero(),
lifetime: effect.lifetime, lifetime: effect.lifetime,
size: effect.size, size: effect.size,
}); });

View File

@ -133,7 +133,6 @@ impl<'a> World {
projectile_h: ColliderHandle, projectile_h: ColliderHandle,
ship_h: ColliderHandle, ship_h: ColliderHandle,
) { ) {
let mut rng = rand::thread_rng();
let projectile = self.projectiles.get(&projectile_h); let projectile = self.projectiles.get(&projectile_h);
let ship = self.ships.get_mut(&ship_h); let ship = self.ships.get_mut(&ship_h);
if projectile.is_none() || ship.is_none() { if projectile.is_none() || ship.is_none() {
@ -174,38 +173,29 @@ impl<'a> World {
None => {} None => {}
Some(x) => { Some(x) => {
let x = ct.get_effect(*x); let x = ct.get_effect(*x);
let velocity = match x.inherit_velocity {
let velocity = { content::ImpactInheritVelocity::Dont => Vector2 { x: 0.0, y: 0.0 },
content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(pr),
content::ImpactInheritVelocity::Target => {
// Match target ship velocity.
// Particles will fly off if the ship is spinning fast, but I
// haven't found a good way to fix that.
let (_, sr) = self.get_ship_body(s).unwrap(); let (_, sr) = self.get_ship_body(s).unwrap();
let target_velocity = let velocity =
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y)); sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
let a = rng Vector2 {
.gen_range(-x.velocity_scale_parent_rng..=x.velocity_scale_parent_rng); x: velocity.x,
let b = rng y: velocity.y,
.gen_range(-x.velocity_scale_target_rng..=x.velocity_scale_target_rng); }
}
let velocity = ((x.velocity_scale_parent + a)
* util::rigidbody_velocity(pr))
+ ((x.velocity_scale_target + b)
* Vector2 {
x: target_velocity.x,
y: target_velocity.y,
});
Matrix2::from_angle(Rad(
rng.gen_range(-x.direction_rng.0..=x.direction_rng.0)
)) * velocity
}; };
particles.push(ParticleBuilder { particles.push(ParticleBuilder {
sprite: x.sprite, sprite: x.sprite,
pos: Point2 { x: pos.x, y: pos.y }, pos: Point2 { x: pos.x, y: pos.y },
velocity, velocity,
angle: Rad::from(-angle) angle: -angle,
+ Rad(rng.gen_range(-x.angle_rng.0..=x.angle_rng.0)), lifetime: x.lifetime,
angvel: Rad(x.angvel.0 + rng.gen_range(-x.angvel_rng.0..=x.angvel_rng.0)), size: x.size,
lifetime: x.lifetime + rng.gen_range(-x.lifetime_rng..=x.lifetime_rng),
size: x.size + rng.gen_range(-x.size_rng..=x.size_rng),
}); });
} }
}; };
@ -330,8 +320,6 @@ impl<'a> World {
to_remove.push(*c); to_remove.push(*c);
} }
} }
let mut rng = rand::thread_rng();
for c in to_remove { for c in to_remove {
let (pr, p) = self.remove_projectile(c).unwrap(); let (pr, p) = self.remove_projectile(c).unwrap();
@ -340,32 +328,22 @@ impl<'a> World {
Some(x) => { Some(x) => {
let x = ct.get_effect(*x); let x = ct.get_effect(*x);
let pos = util::rigidbody_position(&pr); let pos = util::rigidbody_position(&pr);
let vel = util::rigidbody_velocity(&pr);
let angle: Deg<f32> = util::rigidbody_rotation(&pr) let angle: Deg<f32> = util::rigidbody_rotation(&pr)
.angle(Vector2 { x: 1.0, y: 0.0 }) .angle(Vector2 { x: 1.0, y: 0.0 })
.into(); .into();
let velocity = { let velocity = match x.inherit_velocity {
let a = rng content::ImpactInheritVelocity::Target
.gen_range(-x.velocity_scale_parent_rng..=x.velocity_scale_parent_rng); | content::ImpactInheritVelocity::Dont => Vector2 { x: 0.0, y: 0.0 },
content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(&pr),
let velocity = (x.velocity_scale_parent + a) * vel;
velocity
//Matrix2::from_angle(Rad(
// rng.gen_range(-x.direction_rng.0..=x.direction_rng.0)
//)) * velocity
}; };
particles.push(ParticleBuilder { particles.push(ParticleBuilder {
sprite: x.sprite, sprite: x.sprite,
pos: Point2 { x: pos.x, y: pos.y }, pos: Point2 { x: pos.x, y: pos.y },
velocity, velocity,
angle: Rad::from(-angle) angle: -angle,
+ x.angle + Rad(rng.gen_range(-x.angle_rng.0..=x.angle_rng.0)), lifetime: x.lifetime,
angvel: Rad(x.angvel.0 + rng.gen_range(-x.angvel_rng.0..=x.angvel_rng.0)), size: x.size,
lifetime: x.lifetime + rng.gen_range(-x.lifetime_rng..=x.lifetime_rng),
size: x.size + rng.gen_range(-x.size_rng..=x.size_rng),
}); });
} }
}; };