├── .gitignore ├── Cargo.toml ├── assets └── models │ ├── BoxTextured.glb │ ├── Lantern.glb │ ├── cube.bin │ └── cube.gltf └── src ├── anima ├── actions.rs ├── animation.rs ├── armature.rs ├── asset │ └── mod.rs ├── controller.rs ├── example.rs ├── gizmo.rs ├── grid.rs ├── mod.rs ├── runtime │ ├── animation.rs │ ├── armature.rs │ ├── math.rs │ └── mod.rs ├── timeline.rs └── viewport.rs ├── main.rs ├── scene ├── asset.rs ├── component │ ├── editor.rs │ ├── handle.rs │ ├── light.rs │ ├── meta.rs │ ├── mod.rs │ ├── proxy.rs │ └── transform.rs ├── context.rs ├── filebrowser.rs ├── gizmo.rs ├── gizmo.wgsl ├── hierarchy.rs ├── inspector.rs ├── mod.rs └── tab.rs ├── ui ├── app.rs ├── field.rs ├── icon.rs ├── icon.ttf ├── mod.rs ├── placeholder.rs ├── shell │ ├── clipboard.rs │ ├── convert.rs │ ├── mod.rs │ ├── node.rs │ ├── render.rs │ ├── shader.wgsl │ ├── systems.rs │ └── tests.rs ├── style.rs └── tabs.rs ├── util.rs ├── util ├── anymap.rs ├── reflect.rs └── slice.rs └── workspace ├── builder.rs ├── builder ├── expr.rs └── types.rs ├── builtin.wgsl ├── graph.rs ├── link.rs ├── mod.rs ├── node.rs ├── nodes.rs ├── nodes ├── builtin.rs ├── channel.rs ├── input.rs ├── logic.rs ├── master.rs ├── math.rs ├── util.rs └── uv.rs ├── port.rs └── preview.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shaderlab" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | reui = { version = "0.0.1", features = ["bevy"] } 8 | 9 | bitflags = "1" 10 | slotmap = "1" 11 | arrayvec = "0.7" 12 | 13 | async-executor = "1.4" 14 | pollster = "0.2" 15 | egui = { version = "0.18.1", features = ["bytemuck"] } 16 | egui-winit = "0.18" 17 | winit = "0.26.1" 18 | webbrowser = "0.7.0" 19 | 20 | bytemuck = "1.9" 21 | 22 | wgpu = "0.13" 23 | naga = { version = "0.9", features = ["wgsl-in", "wgsl-out"] } 24 | downcast-rs = "1.2" 25 | 26 | tracing = "0.1" 27 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 28 | 29 | ahash = "0.8.0" 30 | sublime_fuzzy = "0.7.0" 31 | 32 | undo = "0.47" 33 | 34 | bevy = "0.8.0" 35 | 36 | smallvec = "1" 37 | 38 | anyhow = "1.0" 39 | thiserror = "1.0" 40 | serde = "1" 41 | uuid = { version = "1", features = ["v4", "serde"] } 42 | ron = "0.8" 43 | 44 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 45 | arboard = "2.0.1" 46 | thread_local = "1.1.0" 47 | 48 | [profile.dev] 49 | opt-level = 1 50 | 51 | [profile.dev.package] 52 | wgpu = { opt-level = 3 } 53 | naga = { opt-level = 3 } 54 | winit = { opt-level = 3 } 55 | egui = { opt-level = 3 } 56 | bevy = { opt-level = 3 } 57 | -------------------------------------------------------------------------------- /assets/models/BoxTextured.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lain-dono/shaderlab/fb496dc0178b955330c1cd4a8590c44b4a9b9cd9/assets/models/BoxTextured.glb -------------------------------------------------------------------------------- /assets/models/Lantern.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lain-dono/shaderlab/fb496dc0178b955330c1cd4a8590c44b4a9b9cd9/assets/models/Lantern.glb -------------------------------------------------------------------------------- /assets/models/cube.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lain-dono/shaderlab/fb496dc0178b955330c1cd4a8590c44b4a9b9cd9/assets/models/cube.bin -------------------------------------------------------------------------------- /assets/models/cube.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset" : { 3 | "generator" : "Khronos glTF Blender I/O v1.1.46", 4 | "version" : "2.0" 5 | }, 6 | "scene" : 0, 7 | "scenes" : [ 8 | { 9 | "name" : "Scene", 10 | "nodes" : [ 11 | 0 12 | ] 13 | } 14 | ], 15 | "nodes" : [ 16 | { 17 | "mesh" : 0, 18 | "name" : "Cube" 19 | } 20 | ], 21 | "materials" : [ 22 | { 23 | "doubleSided" : true, 24 | "emissiveFactor" : [ 25 | 0, 26 | 0, 27 | 0 28 | ], 29 | "name" : "Material", 30 | "pbrMetallicRoughness" : { 31 | "baseColorFactor" : [ 32 | 0.800000011920929, 33 | 0.800000011920929, 34 | 0.800000011920929, 35 | 1 36 | ], 37 | "metallicFactor" : 0, 38 | "roughnessFactor" : 0.4000000059604645 39 | } 40 | } 41 | ], 42 | "meshes" : [ 43 | { 44 | "name" : "Cube", 45 | "primitives" : [ 46 | { 47 | "attributes" : { 48 | "POSITION" : 0, 49 | "NORMAL" : 1, 50 | "TEXCOORD_0" : 2 51 | }, 52 | "indices" : 3, 53 | "material" : 0 54 | } 55 | ] 56 | } 57 | ], 58 | "accessors" : [ 59 | { 60 | "bufferView" : 0, 61 | "componentType" : 5126, 62 | "count" : 24, 63 | "max" : [ 64 | 1, 65 | 1, 66 | 1 67 | ], 68 | "min" : [ 69 | -1, 70 | -1, 71 | -1 72 | ], 73 | "type" : "VEC3" 74 | }, 75 | { 76 | "bufferView" : 1, 77 | "componentType" : 5126, 78 | "count" : 24, 79 | "type" : "VEC3" 80 | }, 81 | { 82 | "bufferView" : 2, 83 | "componentType" : 5126, 84 | "count" : 24, 85 | "type" : "VEC2" 86 | }, 87 | { 88 | "bufferView" : 3, 89 | "componentType" : 5123, 90 | "count" : 36, 91 | "type" : "SCALAR" 92 | } 93 | ], 94 | "bufferViews" : [ 95 | { 96 | "buffer" : 0, 97 | "byteLength" : 288, 98 | "byteOffset" : 0 99 | }, 100 | { 101 | "buffer" : 0, 102 | "byteLength" : 288, 103 | "byteOffset" : 288 104 | }, 105 | { 106 | "buffer" : 0, 107 | "byteLength" : 192, 108 | "byteOffset" : 576 109 | }, 110 | { 111 | "buffer" : 0, 112 | "byteLength" : 72, 113 | "byteOffset" : 768 114 | } 115 | ], 116 | "buffers" : [ 117 | { 118 | "byteLength" : 840, 119 | "uri" : "cube.bin" 120 | } 121 | ] 122 | } 123 | -------------------------------------------------------------------------------- /src/anima/actions.rs: -------------------------------------------------------------------------------- 1 | use super::{Armature, Transform}; 2 | 3 | pub struct TransformBone { 4 | pub bone: usize, 5 | pub transform: Transform, 6 | } 7 | 8 | impl undo::Action for TransformBone { 9 | type Target = Armature; 10 | type Output = (); 11 | type Error = (); 12 | 13 | fn apply(&mut self, target: &mut Self::Target) -> undo::Result { 14 | let bone = &mut target.bones[self.bone]; 15 | bone.transform = bone.transform.mul_transform(self.transform); 16 | Ok(()) 17 | } 18 | 19 | fn undo(&mut self, target: &mut Self::Target) -> undo::Result { 20 | let bone = &mut target.bones[self.bone]; 21 | bone.transform = bone.transform.mul_transform(self.transform.inverse()); 22 | Ok(()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/anima/animation.rs: -------------------------------------------------------------------------------- 1 | use super::Transform; 2 | use egui::Vec2; 3 | use std::ops::{Add, Mul}; 4 | 5 | #[derive(Clone, Copy)] 6 | pub enum Interpolation { 7 | Linear, 8 | Spline(T, T), 9 | } 10 | 11 | pub struct Animation { 12 | pub name: String, 13 | pub bones: Vec, 14 | } 15 | 16 | impl Animation { 17 | pub fn duration(&self) -> u32 { 18 | self.bones 19 | .iter() 20 | .filter_map(|f| f.keys.last().copied()) 21 | .max() 22 | .unwrap_or(0) 23 | } 24 | 25 | pub fn resolve_bone_tranform(&self, bone_index: usize, time: f32) -> Transform { 26 | self.bones 27 | .get(bone_index) 28 | .map(|clip| clip.resolve(time)) 29 | .unwrap_or_default() 30 | } 31 | } 32 | 33 | pub struct Keyframe { 34 | pub time: u32, 35 | pub curve: Interpolation, 36 | pub value: T, 37 | } 38 | 39 | impl Keyframe { 40 | pub fn linear(time: u32, value: T) -> Self { 41 | Self { 42 | time, 43 | curve: Interpolation::Linear, 44 | value, 45 | } 46 | } 47 | } 48 | 49 | pub struct BoneTimeline { 50 | pub label: String, 51 | pub open: bool, 52 | 53 | pub keys: Vec, 54 | 55 | pub rotate: Timeline, 56 | pub translate: Timeline, 57 | pub scale: Timeline, 58 | pub shear: Timeline, 59 | } 60 | 61 | impl BoneTimeline { 62 | pub fn update_keys(&mut self) { 63 | self.keys.clear(); 64 | 65 | self.keys.extend(self.rotate.time_iter()); 66 | self.keys.extend(self.translate.time_iter()); 67 | self.keys.extend(self.scale.time_iter()); 68 | self.keys.extend(self.shear.time_iter()); 69 | 70 | self.keys.dedup(); 71 | self.keys.sort_unstable() 72 | } 73 | 74 | pub fn resolve(&self, time: f32) -> Transform { 75 | let rotate = self.rotate.resolve_or(time, 0.0); 76 | let translate = self.translate.resolve_or(time, Vec2::new(0.0, 0.0)); 77 | let scale = self.scale.resolve_or(time, Vec2::new(1.0, 1.0)); 78 | let shear = self.shear.resolve_or(time, Vec2::new(0.0, 0.0)); 79 | let translate = translate.to_pos2(); 80 | 81 | Transform { 82 | rotate, 83 | translate, 84 | scale, 85 | shear, 86 | } 87 | } 88 | } 89 | 90 | pub trait TimelineValue: Copy + Mul + Add { 91 | #[inline] 92 | fn linear(p0: Self, p1: Self, t: f32) -> Self { 93 | p0 * (1.0 - t) + p1 * t 94 | } 95 | 96 | #[inline] 97 | fn spline(p0: Self, p1: Self, p2: Self, p3: Self, t: f32) -> Self { 98 | let h = 1.0 - t; 99 | let p0 = p0 * (h * h * h); 100 | let p1 = p1 * (t * h * h * 3.0); 101 | let p2 = p2 * (t * t * h * 3.0); 102 | let p3 = p3 * (t * t * t); 103 | p0 + p1 + p2 + p3 104 | } 105 | } 106 | 107 | impl TimelineValue for T where T: Copy + Mul + Add {} 108 | 109 | pub struct Timeline { 110 | frames: Vec>, 111 | } 112 | 113 | impl Timeline { 114 | pub fn new(frames: Vec>) -> Self { 115 | Self { frames } 116 | } 117 | 118 | pub fn get(&self, index: usize) -> (u32, Interpolation, Option) { 119 | let curr = &self.frames[index]; 120 | let next = self.frames.get(index + 1).map(|key| key.time); 121 | (curr.time, curr.curve, next) 122 | } 123 | 124 | pub fn add(&mut self, key: Keyframe) { 125 | self.frames.push(key); 126 | self.frames.sort_by_key(|k| k.time); 127 | } 128 | 129 | pub fn remove(&mut self, time: u32) { 130 | self.frames.retain(|k| k.time != time); 131 | } 132 | 133 | pub fn len(&self) -> usize { 134 | self.frames.len() 135 | } 136 | 137 | pub fn is_empty(&self) -> bool { 138 | self.frames.is_empty() 139 | } 140 | 141 | pub fn find(&self, time: f32) -> Option<&Keyframe> { 142 | self.find_pair(time).map(|[a, _]| a) 143 | } 144 | 145 | pub fn at(&self, time: u32) -> Option<&Keyframe> { 146 | self.frames.iter().find(|a| a.time == time) 147 | } 148 | 149 | pub fn find_pair(&self, time: f32) -> Option<&[Keyframe; 2]> { 150 | let frame = time.floor() as u32; 151 | 152 | crate::util::slice::array_windows(&self.frames) 153 | .find(|&[a, b]| a.time <= frame && frame < b.time) 154 | } 155 | 156 | pub fn resolve(&self, time: f32) -> Option { 157 | self.find_pair(time).map(|[a, b]| { 158 | let (p0, p3) = (a.value, b.value); 159 | let (t0, t1) = (a.time as f32, b.time as f32); 160 | let t = (time - t0) / (t1 - t0); 161 | match a.curve { 162 | Interpolation::Linear => TimelineValue::linear(p0, p3, t), 163 | Interpolation::Spline(p1, p2) => TimelineValue::spline(p0, p1, p2, p3, t), 164 | } 165 | }) 166 | } 167 | 168 | pub fn resolve_or(&self, time: f32, default: T) -> T { 169 | self.resolve(time) 170 | .or_else(|| self.frames.last().map(|a| a.value)) 171 | .unwrap_or(default) 172 | } 173 | 174 | pub fn time_iter(&self) -> impl Iterator + '_ { 175 | self.frames.iter().map(|k| k.time) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/anima/armature.rs: -------------------------------------------------------------------------------- 1 | use super::runtime::armature::Armature; 2 | use super::{Controller, GridViewport}; 3 | use egui::*; 4 | 5 | pub fn paint_bones( 6 | ui: &mut Ui, 7 | viewport: GridViewport, 8 | armature: &Armature, 9 | controller: &Controller, 10 | shapes: &mut Vec, 11 | ) -> Option { 12 | let px = ui.ctx().pixels_per_point().recip(); 13 | let mut hovered = None; 14 | 15 | for (index, (bone, &transform)) in armature 16 | .bones 17 | .iter() 18 | .zip(&controller.local_to_screen) 19 | .enumerate() 20 | { 21 | let [b, g, r, a] = bone.color.to_le_bytes(); 22 | let color = Color32::from_rgba_unmultiplied(r, g, b, a); 23 | let length = bone.length; 24 | 25 | let extra = vec2(length * 0.2, length * 0.1); 26 | let outer_radius = (viewport.zoom * length * 0.075).max(2.0); 27 | let inner_radius = outer_radius * 0.8; 28 | 29 | let a = transform.apply(0.0, 0.0).into(); 30 | let b = transform.apply(extra.x, -extra.y).into(); 31 | let c = transform.apply(length, 0.0).into(); 32 | let d = transform.apply(extra.x, extra.y).into(); 33 | 34 | let points = vec![a, b, c, d]; 35 | 36 | let distance = Pos2::distance(b, d); 37 | let is_hovered = check_distance([a, c], viewport.pointer, distance); 38 | let is_selected = Some(index) == controller.selected; 39 | 40 | if is_hovered { 41 | hovered = Some(index); 42 | } 43 | 44 | let stroke = if is_selected || is_hovered { 45 | Stroke::new(px, Color32::WHITE) 46 | } else { 47 | Stroke::default() 48 | }; 49 | 50 | let path = epaint::PathShape::convex_polygon(points, color, stroke); 51 | shapes.push(Shape::Path(path)); 52 | shapes.push(Shape::circle_filled(a, outer_radius, color)); 53 | shapes.push(Shape::circle_filled(a, inner_radius, Color32::BLACK)); 54 | 55 | if let Some(parent) = armature.bones.get(bone.parent as usize) { 56 | let stroke = Stroke::new(px, Color32::RED); 57 | 58 | let parent_transform = controller.local_to_screen[bone.parent as usize]; 59 | let src: Pos2 = parent_transform.apply(parent.length / 2.0, 0.0).into(); 60 | let dst: Pos2 = transform.apply(length / 2.0, 0.0).into(); 61 | parent_arrow(shapes, src, dst, stroke, viewport.zoom); 62 | } 63 | } 64 | 65 | hovered 66 | } 67 | 68 | fn check_distance([a, b]: [Pos2; 2], pointer: Option, distance: f32) -> bool { 69 | if let Some(pointer) = pointer { 70 | let diff = b - a; 71 | let len_sq = diff.length_sq(); 72 | if len_sq <= 0.0 { 73 | pointer.distance(a) < distance 74 | } else { 75 | let t = Vec2::dot(pointer - a, diff) / len_sq; 76 | let projection = a + diff * t.clamp(0.0, 1.0); 77 | pointer.distance(projection) < distance 78 | } 79 | } else { 80 | false 81 | } 82 | } 83 | 84 | fn parent_arrow(shapes: &mut Vec, src: Pos2, dst: Pos2, stroke: Stroke, zoom: f32) { 85 | use egui::emath::*; 86 | let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0); 87 | let len = 8.0 * zoom; 88 | let dir = (dst - src).normalized(); 89 | let (dash, gap) = (4.0, 2.0); 90 | shapes.extend(Shape::dashed_line(&[src, dst], stroke, dash, gap)); 91 | shapes.push(Shape::LineSegment { 92 | points: [dst, dst - len * (rot * dir)], 93 | stroke, 94 | }); 95 | shapes.push(Shape::LineSegment { 96 | points: [dst, dst - len * (rot.inverse() * dir)], 97 | stroke, 98 | }); 99 | } 100 | -------------------------------------------------------------------------------- /src/anima/asset/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/anima/controller.rs: -------------------------------------------------------------------------------- 1 | use super::Matrix; 2 | 3 | pub enum PlayControl { 4 | First, 5 | Prev, 6 | PlayReverse, 7 | Pause, 8 | Play, 9 | Next, 10 | Last, 11 | } 12 | 13 | #[derive(Clone, Copy)] 14 | pub enum PlayState { 15 | Stop, 16 | Play, 17 | PlayReverse, 18 | } 19 | 20 | impl Default for PlayState { 21 | fn default() -> Self { 22 | Self::Stop 23 | } 24 | } 25 | 26 | #[derive(Default)] 27 | pub struct Controller { 28 | pub current_time: u32, 29 | //pub max_time: u32, 30 | pub selected: Option, 31 | 32 | pub state: PlayState, 33 | 34 | pub local_to_world: Vec, 35 | pub local_to_screen: Vec, 36 | } 37 | 38 | impl Controller { 39 | pub fn is_playing(&self) -> bool { 40 | matches!(self.state, PlayState::Play | PlayState::PlayReverse) 41 | } 42 | 43 | pub fn action(&mut self, control: PlayControl, max_time: u32) { 44 | match control { 45 | PlayControl::First => self.current_time = 0, 46 | PlayControl::Prev if self.current_time > 0 => self.current_time -= 1, 47 | PlayControl::Prev => self.current_time = max_time, 48 | 49 | PlayControl::PlayReverse => self.state = PlayState::PlayReverse, 50 | PlayControl::Play => self.state = PlayState::Play, 51 | PlayControl::Pause => self.state = PlayState::Stop, 52 | 53 | PlayControl::Next if self.current_time < max_time => self.current_time += 1, 54 | PlayControl::Next => self.current_time = 0, 55 | PlayControl::Last => self.current_time = max_time, 56 | } 57 | } 58 | 59 | pub fn world_to_screen(&mut self, screen: Matrix) { 60 | let iter = self 61 | .local_to_world 62 | .iter() 63 | .map(|&world| screen.prepend(world)); 64 | 65 | self.local_to_screen.clear(); 66 | self.local_to_screen.extend(iter); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/anima/example.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | runtime::Transform, Animation, Armature, Bone, BoneTimeline, Interpolation, Keyframe, Timeline, 3 | }; 4 | use egui::*; 5 | 6 | pub fn armature() -> Armature { 7 | Armature { 8 | slots: vec![], 9 | bones: vec![ 10 | Bone { 11 | transform: Transform { 12 | translate: pos2(100.0, 30.0), 13 | ..Default::default() 14 | }, 15 | length: 50.0, 16 | color: 0x99_00EECC, 17 | ..Default::default() 18 | }, 19 | Bone { 20 | transform: Transform { 21 | translate: pos2(50.0, 0.0), 22 | ..Default::default() 23 | }, 24 | length: 50.0, 25 | color: 0x99_EE00CC, 26 | parent: 0, 27 | ..Default::default() 28 | }, 29 | Bone { 30 | transform: Transform { 31 | translate: pos2(50.0, 0.0), 32 | ..Default::default() 33 | }, 34 | length: 50.0, 35 | color: 0x99_00EECC, 36 | parent: 1, 37 | ..Default::default() 38 | }, 39 | ], 40 | } 41 | } 42 | 43 | pub fn animation() -> Animation { 44 | let bones = vec![ 45 | BoneTimeline { 46 | label: String::from("bone0"), 47 | open: false, 48 | keys: vec![], 49 | translate: Timeline::new(vec![]), 50 | rotate: Timeline::new(vec![ 51 | Keyframe { 52 | time: 0, 53 | curve: Interpolation::Linear, 54 | value: 0.0, 55 | }, 56 | Keyframe { 57 | time: 5, 58 | curve: Interpolation::Linear, 59 | value: std::f32::consts::FRAC_PI_2, 60 | }, 61 | Keyframe { 62 | time: 8, 63 | curve: Interpolation::Linear, 64 | value: std::f32::consts::PI, 65 | }, 66 | ]), 67 | scale: Timeline::new(vec![]), 68 | shear: Timeline::new(vec![]), 69 | }, 70 | BoneTimeline { 71 | label: String::from("bone1"), 72 | open: false, 73 | keys: vec![], 74 | translate: Timeline::new(vec![]), 75 | rotate: Timeline::new(vec![]), 76 | scale: Timeline::new(vec![]), 77 | shear: Timeline::new(vec![]), 78 | }, 79 | BoneTimeline { 80 | label: String::from("bone2"), 81 | open: false, 82 | keys: vec![], 83 | translate: Timeline::new(vec![]), 84 | rotate: Timeline::new(vec![]), 85 | scale: Timeline::new(vec![]), 86 | shear: Timeline::new(vec![]), 87 | }, 88 | ]; 89 | 90 | let name = String::from("some animation"); 91 | 92 | Animation { name, bones } 93 | } 94 | -------------------------------------------------------------------------------- /src/anima/gizmo.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | 3 | use reui::{plugin::Recorder, Color, FillRule, Offset, Path, Solidity, Stroke, Transform}; 4 | 5 | pub enum Tool {} 6 | 7 | pub struct Gizmos<'a> { 8 | pub recorder: &'a mut Recorder, 9 | pub viewport: Transform, 10 | pub transform: Transform, 11 | pub path: Path, 12 | } 13 | 14 | impl<'a> Gizmos<'a> { 15 | pub fn new(recorder: &'a mut Recorder, viewport: Transform) -> Self { 16 | Self { 17 | recorder, 18 | viewport, 19 | transform: viewport, 20 | path: Path::new(), 21 | } 22 | } 23 | 24 | pub fn set_transform(&mut self, tx: f32, ty: f32, rotation: f32, scale: f32) { 25 | self.transform = self.viewport * Transform::compose(tx, ty, rotation, scale); 26 | } 27 | 28 | pub fn rotate(&mut self, color: Color) { 29 | use std::f32::consts::TAU; 30 | 31 | let a = f32::to_radians(10.0); 32 | let b = f32::to_radians(14.0); 33 | 34 | let inner_radius = 19.0; 35 | let outer_radius = 23.0; 36 | 37 | let center = Offset::zero(); 38 | 39 | self.path.clear(); 40 | self.path 41 | .arc(center, outer_radius, b, TAU - b, Solidity::Hole); 42 | self.path 43 | .arc(center, inner_radius, TAU - a, a, Solidity::Solid); 44 | 45 | self.path.move_to(Offset::new(outer_radius - 5.0, 0.0)); 46 | self.path.line_to(Offset::new(outer_radius, 3.0)); 47 | self.path.line_to(Offset::new(outer_radius + 5.0, 0.0)); 48 | self.path.line_to(Offset::new(outer_radius, -3.0)); 49 | self.path.close(); 50 | 51 | self.shadow(); 52 | self.fill(color); 53 | } 54 | 55 | pub fn arrow_translate(&mut self, color: Color) { 56 | let main = (10.0, 27.0, 28.0, 40.0); 57 | let cross = (5.0, 8.0); 58 | 59 | let control_a = Offset::new(20.0, -1.0); 60 | let control_b = Offset::new(20.0, 1.0); 61 | 62 | self.path.clear(); 63 | self.path.move_to(Offset::new(main.3, 0.0)); 64 | self.path.line_to(Offset::new(main.2, -cross.1)); 65 | self.path.line_to(Offset::new(main.1, -cross.0)); 66 | self.path.quad_to(control_a, Offset::new(main.0, -1.0)); 67 | self.path.line_to(Offset::new(main.0, 1.0)); 68 | self.path.quad_to(control_b, Offset::new(main.1, cross.0)); 69 | self.path.line_to(Offset::new(main.2, cross.1)); 70 | self.path.close(); 71 | 72 | self.shadow(); 73 | self.fill(color); 74 | } 75 | 76 | pub fn arrow_scale(&mut self, color: Color) { 77 | let main = (10.0, 27.0, 28.0, 40.0); 78 | let cross = (5.0, 8.0); 79 | 80 | let control_a = Offset::new(20.0, -1.0); 81 | let control_b = Offset::new(20.0, 1.0); 82 | 83 | self.path.clear(); 84 | self.path.move_to(Offset::new(main.3, cross.1)); 85 | self.path.line_to(Offset::new(main.3, -cross.1)); 86 | self.path.line_to(Offset::new(main.2, -cross.1)); 87 | self.path.line_to(Offset::new(main.1, -cross.0)); 88 | self.path.quad_to(control_a, Offset::new(main.0, -1.0)); 89 | self.path.line_to(Offset::new(main.0, 1.0)); 90 | self.path.quad_to(control_b, Offset::new(main.1, cross.0)); 91 | self.path.line_to(Offset::new(main.2, cross.1)); 92 | self.path.close(); 93 | 94 | self.shadow(); 95 | self.fill(color); 96 | } 97 | 98 | pub fn length(&mut self, color: Color) { 99 | let inner_radius = 4.0; 100 | let outer_radius = 5.0; 101 | let extra_radius = 6.0; 102 | 103 | self.path.clear(); 104 | self.path.solidity(Solidity::Solid); 105 | self.path.circle(Offset::zero(), outer_radius); 106 | self.path.solidity(Solidity::Hole); 107 | self.path.circle(Offset::zero(), inner_radius); 108 | 109 | self.path.move_to(Offset::new(11.0, 0.0)); 110 | self.path.line_to(Offset::new(extra_radius, 3.0)); 111 | self.path.quad_to( 112 | Offset::new(extra_radius + 1.0, 0.0), 113 | Offset::new(extra_radius, -3.0), 114 | ); 115 | self.path.close(); 116 | 117 | self.path.move_to(Offset::new(-11.0, 0.0)); 118 | self.path.line_to(Offset::new(-extra_radius, 3.0)); 119 | self.path.quad_to( 120 | Offset::new(-extra_radius - 1.0, 0.0), 121 | Offset::new(-extra_radius, -3.0), 122 | ); 123 | self.path.close(); 124 | 125 | self.shadow(); 126 | self.fill(color); 127 | } 128 | 129 | pub fn pose(&mut self, color: Color) { 130 | let inner_radius = 4.0; 131 | let outer_radius = 5.0; 132 | let extra_radius = 5.0; 133 | let end = 9.0; 134 | let cross = 2.0; 135 | 136 | self.path.clear(); 137 | self.path.solidity(Solidity::Solid); 138 | 139 | self.path.move_to(Offset::new(end, 0.0)); 140 | self.path.line_to(Offset::new(extra_radius, cross)); 141 | self.path.line_to(Offset::new(extra_radius, -cross)); 142 | 143 | self.path.move_to(-Offset::new(end, 0.0)); 144 | self.path.line_to(-Offset::new(extra_radius, cross)); 145 | self.path.line_to(-Offset::new(extra_radius, -cross)); 146 | 147 | self.path.move_to(Offset::new(0.0, end)); 148 | self.path.line_to(Offset::new(-cross, extra_radius)); 149 | self.path.line_to(Offset::new(cross, extra_radius)); 150 | 151 | self.path.move_to(-Offset::new(0.0, end)); 152 | self.path.line_to(-Offset::new(-cross, extra_radius)); 153 | self.path.line_to(-Offset::new(cross, extra_radius)); 154 | 155 | self.path.circle(Offset::zero(), outer_radius); 156 | 157 | self.path.solidity(Solidity::Hole); 158 | self.path.circle(Offset::zero(), inner_radius); 159 | 160 | self.shadow(); 161 | self.fill(color); 162 | } 163 | 164 | pub fn shear(&mut self, color: Color, value: f32) { 165 | let min = 15.0; 166 | let max = 60.0; 167 | 168 | let w = 1.0; 169 | let s = 3.0; 170 | 171 | { 172 | self.path.clear(); 173 | 174 | self.path.move_to(Offset::new(0.0, 0.0)); 175 | let a = if value > 0.0 { 176 | (0.0, -value % PI) 177 | } else { 178 | (-value % PI, 0.0) 179 | }; 180 | self.path 181 | .arc(Offset::zero(), 38.0, a.0, a.1, Solidity::Solid); 182 | self.path.close(); 183 | 184 | let mut color = color; 185 | color.alpha *= 0.25; 186 | 187 | self.fill(color); 188 | 189 | self.path.clear(); 190 | self.path.move_to(Offset::new(min, 0.0)); 191 | self.path.line_to(Offset::new(min + 1.0, 1.0)); 192 | self.path.line_to(Offset::new(48.0, 1.0)); 193 | self.path.line_to(Offset::new(48.0, -1.0)); 194 | self.path.line_to(Offset::new(min + 1.0, -1.0)); 195 | 196 | self.fill(color); 197 | } 198 | 199 | let tx = Transform::rotation(-value); 200 | 201 | self.path.clear(); 202 | 203 | self.path.move_to(tx.apply(Offset::new(min, 0.0))); 204 | 205 | self.path.line_to(tx.apply(Offset::new(min + w, w))); 206 | self.path.line_to(tx.apply(Offset::new(max, w))); 207 | self.path.line_to(tx.apply(Offset::new(max + s, s))); 208 | self.path.line_to(tx.apply(Offset::new(max + s * 2.0, 0.0))); 209 | self.path.line_to(tx.apply(Offset::new(max + s, -s))); 210 | self.path.line_to(tx.apply(Offset::new(max, -w))); 211 | self.path.line_to(tx.apply(Offset::new(min + w, -w))); 212 | 213 | self.shadow(); 214 | self.fill(color); 215 | } 216 | 217 | fn fill(&mut self, color: Color) { 218 | self.recorder 219 | .fill(&self.path, color, self.transform, FillRule::EvenOdd, true); 220 | } 221 | 222 | fn shadow(&mut self) { 223 | self.recorder.stroke( 224 | &self.path, 225 | Color::bgra(0x99000000), 226 | Stroke::width(2.0), 227 | self.transform, 228 | true, 229 | ); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/anima/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod actions; 2 | pub mod animation; 3 | pub mod armature; 4 | pub mod controller; 5 | pub mod example; 6 | pub mod gizmo; 7 | pub mod grid; 8 | pub mod runtime; 9 | pub mod timeline; 10 | pub mod viewport; 11 | 12 | pub mod asset; 13 | 14 | pub use self::animation::{ 15 | Animation, BoneTimeline, Interpolation, Keyframe, Timeline, TimelineValue, 16 | }; 17 | pub use self::controller::{Controller, PlayControl, PlayState}; 18 | pub use self::grid::{Grid, GridViewport}; 19 | pub use self::runtime::armature::{Armature, Bone}; 20 | pub use self::runtime::math::{Matrix, Transform}; 21 | pub use self::timeline::TimelinePanel; 22 | pub use self::viewport::Animation2d; 23 | 24 | use crate::ui::{AddEditorTab, EditorPanel, PanelRenderTarget}; 25 | use bevy::core_pipeline::clear_color::ClearColorConfig; 26 | use bevy::prelude::*; 27 | use bevy::window::WindowId; 28 | use reui::plugin::Recorder; 29 | 30 | #[derive(Default)] 31 | pub struct AnimaPlugin; 32 | 33 | impl bevy::app::Plugin for AnimaPlugin { 34 | fn build(&self, app: &mut bevy::app::App) { 35 | app.add_plugin(reui::plugin::ReuiPlugin) 36 | .insert_resource(Controller { 37 | current_time: 0, 38 | ..default() 39 | }) 40 | .insert_resource(self::example::armature()) 41 | .insert_resource(self::example::animation()) 42 | .add_system(sync_frame) 43 | .add_system_to_stage(CoreStage::PostUpdate, draw) 44 | .add_editor_tab::() 45 | .add_editor_tab::(); 46 | } 47 | } 48 | 49 | fn sync_frame(mut ctrl: ResMut, armature: Res, animation: Res) { 50 | ctrl.local_to_world.clear(); 51 | 52 | let time = ctrl.current_time as f32; 53 | for (index, bone) in armature.bones.iter().enumerate() { 54 | let clip = animation.resolve_bone_tranform(index, time); 55 | 56 | //bone.transform.to_matrix().prepend(clip.to_matrix()) 57 | let transform = bone.transform.mul_transform(clip).to_matrix(); 58 | 59 | let parent = bone.parent as usize; 60 | let parent = *ctrl.local_to_world.get(parent).unwrap_or(&Matrix::IDENTITY); 61 | ctrl.local_to_world.push(parent.prepend(transform)); 62 | } 63 | } 64 | 65 | impl Animation2d { 66 | pub fn spawn(commands: &mut Commands, images: &mut Assets) -> crate::ui::Tab { 67 | use bevy::prelude::*; 68 | 69 | let target = PanelRenderTarget::create_render_target(images); 70 | 71 | let clear_color = bevy::prelude::Color::rgba(0.0, 0.0, 0.0, 0.0); 72 | let clear_color = ClearColorConfig::Custom(clear_color); 73 | 74 | let entity = commands 75 | .spawn_bundle(Camera2dBundle { 76 | camera_2d: Camera2d { clear_color }, 77 | camera: Camera { 78 | target, 79 | ..default() 80 | }, 81 | ..default() 82 | }) 83 | .insert(Self::default()) 84 | .insert(EditorPanel::default()) 85 | .insert(Recorder::default()) 86 | .insert(PanelRenderTarget::default()) 87 | .id(); 88 | 89 | crate::ui::Tab::new(crate::ui::icon::VIEW_ORTHO, "Animate 2d", entity) 90 | } 91 | } 92 | 93 | fn draw( 94 | windows: Res, 95 | time: Res