1use super::*;
2
3use bevy::render::{camera::ScalingMode, Extract};
4use bevy::sprite::{Anchor, ExtractedSprite, ExtractedSprites};
5use bevy::utils::HashMap;
6use bevy::window::WindowMode;
7use bevy_prototype_lyon::prelude as lyon;
8use bones::{BitSet, ComponentIterBitset};
9use glam::*;
10
11#[derive(Component)]
13pub struct BevyBonesEntity;
14
15#[derive(Resource)]
17pub struct BonesGameEntity(pub Entity);
18impl FromWorld for BonesGameEntity {
19 fn from_world(world: &mut World) -> Self {
20 Self(world.spawn(VisibilityBundle::default()).id())
21 }
22}
23
24#[derive(Resource, Debug, Deref, DerefMut)]
26pub struct BonesImageIds {
27 #[deref]
28 map: HashMap<u32, Handle<Image>>,
29 next_id: u32,
30}
31impl Default for BonesImageIds {
32 fn default() -> Self {
33 Self {
34 map: Default::default(),
35 next_id: 1,
36 }
37 }
38}
39
40impl BonesImageIds {
41 pub fn load_bones_images(
43 &mut self,
44 bones_assets: &bones::AssetServer,
45 bones_egui_textures: &mut bones::EguiTextures,
46 bevy_images: &mut Assets<Image>,
47 bevy_egui_textures: &mut bevy_egui::EguiUserTextures,
48 ) {
49 for entry in bones_assets.store.asset_ids.iter() {
50 let handle = entry.key();
51 let cid = entry.value();
52 let mut asset = bones_assets.store.assets.get_mut(cid).unwrap();
53 if let Ok(image) = asset.data.try_cast_mut::<bones::Image>() {
54 self.load_bones_image(
55 handle.typed(),
56 image,
57 bones_egui_textures,
58 bevy_images,
59 bevy_egui_textures,
60 )
61 }
62 }
63 }
64
65 pub fn load_bones_image(
67 &mut self,
68 bones_handle: bones::Handle<bones::Image>,
69 image: &mut bones::Image,
70 bones_egui_textures: &mut bones::EguiTextures,
71 bevy_images: &mut Assets<Image>,
72 bevy_egui_textures: &mut bevy_egui::EguiUserTextures,
73 ) {
74 let Self { map, next_id } = self;
75 let mut taken_image = bones::Image::External(0); std::mem::swap(image, &mut taken_image);
77 if let bones::Image::Data(data) = taken_image {
78 let handle = bevy_images.add(Image::from_dynamic(data, true));
79 let egui_texture = bevy_egui_textures.add_image(handle.clone());
80 bones_egui_textures.insert(bones_handle, egui_texture);
81 map.insert(*next_id, handle);
82 *image = bones::Image::External(*next_id);
83 *next_id += 1;
84
85 } else {
88 std::mem::swap(image, &mut taken_image);
90 }
91 }
92}
93
94pub fn sync_clear_color(mut clear_color: ResMut<ClearColor>, game: Res<BonesGame>) {
96 for name in &game.sorted_session_keys {
97 let session = game.sessions.get(*name).unwrap();
98 if !session.visible {
99 continue;
100 }
101 if let Some(bones_clear_color) = session.world.get_resource::<bones::ClearColor>() {
102 clear_color.0 = bones_clear_color.0.into_bevy();
103 }
104 }
105}
106
107pub fn sync_bones_window(mut game: ResMut<BonesGame>, mut window_query: Query<&mut Window>) {
109 let mut window = window_query.get_single_mut().unwrap();
110 let bones_window = match game.shared_resource_cell::<bones::Window>() {
111 Some(w) => w,
112 None => {
113 game.insert_shared_resource(bones::Window {
114 size: vec2(window.width(), window.height()),
115 fullscreen: matches!(&window.mode, WindowMode::BorderlessFullscreen),
116 focused: window.focused,
117 });
118 game.shared_resource_cell().unwrap()
119 }
120 };
121 let mut bones_window = bones_window.borrow_mut().unwrap();
122
123 let is_fullscreen = matches!(&window.mode, WindowMode::BorderlessFullscreen);
124 if is_fullscreen != bones_window.fullscreen {
125 window.mode = if bones_window.fullscreen {
126 WindowMode::BorderlessFullscreen
127 } else {
128 WindowMode::Windowed
129 };
130 }
131 bones_window.focused = window.focused;
132 bones_window.size = vec2(window.width(), window.height());
133}
134
135pub fn sync_cameras(
137 game: Res<BonesGame>,
138 mut commands: Commands,
139 mut bevy_bones_cameras: Query<Entity, (With<BevyBonesEntity>, With<Camera>)>,
140) {
141 let mut bevy_bones_cameras = bevy_bones_cameras.iter_mut();
143
144 let mut add_bones_camera = |bones_camera: &bones::Camera,
146 bones_transform: &bones::Transform| {
147 let mut camera_ent = match bevy_bones_cameras.next() {
149 Some(ent) => commands.entity(ent),
150 None => commands.spawn((Camera2dBundle::default(), BevyBonesEntity)),
151 };
152
153 camera_ent.insert((
155 Camera {
156 is_active: bones_camera.active,
157 viewport: bones_camera.viewport.option().map(|x| x.into_bevy()),
158 order: bones_camera.priority as isize,
159 ..default()
160 },
161 OrthographicProjection {
162 scaling_mode: match bones_camera.size {
163 bones::CameraSize::Fixed { width, height } => {
164 ScalingMode::Fixed { width, height }
165 }
166 bones::CameraSize::Max {
167 max_width,
168 max_height,
169 } => ScalingMode::AutoMax {
170 max_width,
171 max_height,
172 },
173 bones::CameraSize::Min {
174 min_width,
175 min_height,
176 } => ScalingMode::AutoMin {
177 min_width,
178 min_height,
179 },
180 bones::CameraSize::Window { pixels_per_unit } => {
181 ScalingMode::WindowSize(pixels_per_unit)
182 }
183 bones::CameraSize::FixedHeight(h) => ScalingMode::FixedVertical(h),
184 bones::CameraSize::FixedWidth(w) => ScalingMode::FixedHorizontal(w),
185 },
186 ..default()
187 },
188 bones_transform.into_bevy(),
189 ));
190 };
191
192 for session_name in &game.sorted_session_keys {
194 let session = game.sessions.get(*session_name).unwrap();
195 if !session.visible {
196 continue;
197 }
198
199 let world = &session.world;
200
201 if !(world
203 .components
204 .get::<bones::Transform>()
205 .borrow()
206 .bitset()
207 .bit_any()
208 && world
209 .components
210 .get::<bones::Camera>()
211 .borrow()
212 .bitset()
213 .bit_any())
214 {
215 continue;
216 }
217
218 let entities = world.resource::<bones::Entities>();
219 let transforms = world.components.get::<bones::Transform>().borrow();
220 let cameras = world.components.get::<bones::Camera>().borrow();
221
222 for (_ent, (transform, camera)) in entities.iter_with((&transforms, &cameras)) {
224 add_bones_camera(camera, transform)
226 }
227 }
228
229 for remaining_ent in bevy_bones_cameras {
231 commands.entity(remaining_ent).despawn()
232 }
233}
234
235pub fn extract_bones_sprites(
237 mut extracted_sprites: ResMut<ExtractedSprites>,
238 game: Extract<Res<BonesGame>>,
239 bones_image_ids: Extract<Res<BonesImageIds>>,
240 bones_renderable_entity: Extract<Res<BonesGameEntity>>,
241) {
242 let Some(bones_assets) = &game.asset_server() else {
243 return;
244 };
245
246 for session_name in &game.sorted_session_keys {
247 let session = game.sessions.get(*session_name).unwrap();
248 if !session.visible {
249 continue;
250 }
251
252 let world = &session.world;
253
254 if !(world
256 .components
257 .get::<bones::Transform>()
258 .borrow()
259 .bitset()
260 .bit_any()
261 && world
262 .components
263 .get::<bones::Camera>()
264 .borrow()
265 .bitset()
266 .bit_any()
267 && (world
268 .components
269 .get::<bones::Sprite>()
270 .borrow()
271 .bitset()
272 .bit_any()
273 || world
274 .components
275 .get::<bones::AtlasSprite>()
276 .borrow()
277 .bitset()
278 .bit_any()))
279 {
280 continue;
281 }
282
283 let entities = world.resource::<bones::Entities>();
284 let transforms = world.components.get::<bones::Transform>().borrow();
285 let sprites = world.components.get::<bones::Sprite>().borrow();
286 let atlas_sprites = world.components.get::<bones::AtlasSprite>().borrow();
287
288 let mut z_offset = 0.0;
290 for (_, (sprite, transform)) in entities.iter_with((&sprites, &transforms)) {
291 let sprite_image = match bones_assets.try_get(sprite.image) {
292 Some(Ok(image)) => image,
293 Some(Err(err)) => {
294 warn!("Sprite {:?} has invalid handle: {err:?}", sprite.image);
295 continue;
296 }
297 None => {
298 warn!("Sprite not loaded: {:?}", sprite.image);
299 continue;
300 }
301 };
302
303 let image_id = if let bones::Image::External(id) = &*sprite_image {
304 *id
305 } else {
306 panic!(
307 "Images added at runtime not supported yet, \
308 please open an issue."
309 );
310 };
311 extracted_sprites.sprites.push(ExtractedSprite {
312 entity: bones_renderable_entity.0,
313 transform: {
314 let mut t: Transform = transform.into_bevy();
315 t.translation.z += z_offset;
317 z_offset += 0.00001;
318 t.into()
319 },
320 color: sprite.color.into_bevy(),
321 rect: None,
322 custom_size: None,
323 image_handle_id: bones_image_ids.get(&image_id).unwrap().id(),
324 flip_x: sprite.flip_x,
325 flip_y: sprite.flip_y,
326 anchor: Anchor::Center.as_vec(),
327 });
328 }
329
330 for (_, (atlas_sprite, transform)) in entities.iter_with((&atlas_sprites, &transforms)) {
332 let atlas = bones_assets.get(atlas_sprite.atlas);
333 let atlas_image = bones_assets.get(atlas.image);
334 let image_id = if let bones::Image::External(id) = &*atlas_image {
335 *id
336 } else {
337 panic!(
338 "Images added at runtime not supported yet, \
339 please open an issue."
340 );
341 };
342 let index = atlas_sprite.index;
343 let y = index / atlas.columns;
344 let x = index - (y * atlas.columns);
345 let cell = Vec2::new(x as f32, y as f32);
346 let current_padding = atlas.padding
347 * Vec2::new(if x > 0 { 1.0 } else { 0.0 }, if y > 0 { 1.0 } else { 0.0 });
348 let min = (atlas.tile_size + current_padding) * cell + atlas.offset;
349 let rect = Rect {
350 min,
351 max: min + atlas.tile_size,
352 };
353 extracted_sprites.sprites.push(ExtractedSprite {
354 entity: bones_renderable_entity.0,
355 transform: transform.into_bevy().into(),
356 color: atlas_sprite.color.into_bevy(),
357 rect: Some(rect),
358 custom_size: None,
359 image_handle_id: bones_image_ids.get(&image_id).unwrap().id(),
360 flip_x: atlas_sprite.flip_x,
361 flip_y: atlas_sprite.flip_y,
362 anchor: Anchor::Center.as_vec(),
363 });
364 }
365 }
366}
367
368pub fn extract_bones_tilemaps(
370 mut extracted_sprites: ResMut<ExtractedSprites>,
371 game: Extract<Res<BonesGame>>,
372 bones_image_ids: Extract<Res<BonesImageIds>>,
373 bones_renderable_entity: Extract<Res<BonesGameEntity>>,
374) {
375 let Some(bones_assets) = &game.asset_server() else {
376 return;
377 };
378
379 for session_name in &game.sorted_session_keys {
380 let session = game.sessions.get(*session_name).unwrap();
381 if !session.visible {
382 continue;
383 }
384
385 let world = &session.world;
386
387 if !(world
389 .components
390 .get::<bones::Transform>()
391 .borrow()
392 .bitset()
393 .bit_any()
394 && world
395 .components
396 .get::<bones::Camera>()
397 .borrow()
398 .bitset()
399 .bit_any()
400 && world
401 .components
402 .get::<bones::TileLayer>()
403 .borrow()
404 .bitset()
405 .bit_any())
406 {
407 continue;
408 }
409
410 let entities = world.resource::<bones::Entities>();
411 let transforms = world.components.get::<bones::Transform>().borrow();
412 let tile_layers = world.components.get::<bones::TileLayer>().borrow();
413 let tiles = world.components.get::<bones::Tile>().borrow();
414
415 for (_, (tile_layer, transform)) in entities.iter_with((&tile_layers, &transforms)) {
417 let atlas = bones_assets.get(tile_layer.atlas);
418 let atlas_image = bones_assets.get(atlas.image);
419 let image_id = if let bones::Image::External(id) = &*atlas_image {
420 *id
421 } else {
422 panic!(
423 "Images added at runtime not supported yet, \
424 please open an issue."
425 );
426 };
427
428 for (tile_pos_idx, tile_ent) in tile_layer.tiles.iter().enumerate() {
429 let Some(tile_ent) = tile_ent else { continue };
430 let tile = tiles.get(*tile_ent).unwrap();
431
432 let tile_pos = tile_layer.pos(tile_pos_idx as u32);
433 let tile_offset = tile_pos.as_vec2() * tile_layer.tile_size;
434
435 let sprite_idx = tile.idx;
436 let y = sprite_idx / atlas.columns;
437 let x = sprite_idx - (y * atlas.columns);
438 let cell = Vec2::new(x as f32, y as f32);
439 let current_padding = atlas.padding
440 * Vec2::new(if x > 0 { 1.0 } else { 0.0 }, if y > 0 { 1.0 } else { 0.0 });
441 let min = (atlas.tile_size + current_padding) * cell + atlas.offset;
442 let rect = Rect {
443 min,
444 max: min + atlas.tile_size,
445 };
446 let mut transform = transform.into_bevy();
447 transform.translation += tile_offset.extend(0.0);
448 transform.scale += Vec3::new(0.01, 0.01, 0.0);
455 extracted_sprites.sprites.push(ExtractedSprite {
456 entity: bones_renderable_entity.0,
457 transform: transform.into(),
458 color: Color::WHITE,
459 rect: Some(rect),
460 custom_size: None,
461 image_handle_id: bones_image_ids.get(&image_id).unwrap().id(),
462 flip_x: tile.flip_x,
463 flip_y: tile.flip_y,
464 anchor: Anchor::BottomLeft.as_vec(),
465 });
466 }
467 }
468 }
469}
470
471pub fn sync_bones_path2ds(
473 game: Res<BonesGame>,
474 mut commands: Commands,
475 mut bevy_bones_path2ds: Query<
476 (Entity, &mut lyon::Path, &mut lyon::Stroke, &mut Transform),
477 With<BevyBonesEntity>,
478 >,
479) {
480 let mut bevy_bones_path2ds = bevy_bones_path2ds.iter_mut();
482
483 let mut add_bones_path2d = |bones_path2d: &bones::Path2d,
485 bones_transform: &bones::Transform| {
486 let mut new_components = None;
488 let mut existing_components;
489 let (path, stroke, transform) = match bevy_bones_path2ds.next() {
490 Some((_ent, path, stroke, transform)) => {
491 existing_components = (path, stroke, transform);
492 let (path, stroke, transform) = &mut existing_components;
493 (&mut **path, &mut **stroke, &mut **transform)
494 }
495 None => {
496 let bundle = lyon::ShapeBundle::default();
497 new_components = Some((
498 bundle.path,
499 lyon::Stroke::new(Color::default(), 1.0),
500 Transform::default(),
501 ));
502 let (path, stroke, transform) = new_components.as_mut().unwrap();
503 (path, stroke, transform)
504 }
505 };
506
507 *stroke = lyon::Stroke::new(bones_path2d.color.into_bevy(), bones_path2d.thickness);
509 *path = bones_path2d
510 .points
511 .iter()
512 .copied()
513 .enumerate()
514 .fold(lyon::PathBuilder::new(), |mut builder, (i, point)| {
515 if i > 0 && !bones_path2d.line_breaks.contains(&i) {
516 builder.line_to(point);
517 }
518 builder.move_to(point);
519
520 builder
521 })
522 .build();
523 *transform = bones_transform.into_bevy();
524 transform.translation.z += 0.0001;
527
528 if let Some((path, stroke, transform)) = new_components {
530 commands
531 .spawn(lyon::ShapeBundle { path, ..default() })
532 .insert(transform)
533 .insert(stroke)
534 .insert(BevyBonesEntity);
535 }
536 };
537
538 for session_name in &game.sorted_session_keys {
539 let session = game.sessions.get(*session_name).unwrap();
540 if !session.visible {
541 continue;
542 }
543
544 let world = &session.world;
545
546 if !(world
548 .components
549 .get::<bones::Transform>()
550 .borrow()
551 .bitset()
552 .bit_any()
553 && world
554 .components
555 .get::<bones::Camera>()
556 .borrow()
557 .bitset()
558 .bit_any()
559 && world
560 .components
561 .get::<bones::Path2d>()
562 .borrow()
563 .bitset()
564 .bit_any())
565 {
566 continue;
567 }
568
569 let entities = world.resource::<bones::Entities>();
570 let transforms = world.components.get::<bones::Transform>().borrow();
571 let path2ds = world.components.get::<bones::Path2d>().borrow();
572
573 for (_, (path2d, transform)) in entities.iter_with((&path2ds, &transforms)) {
575 add_bones_path2d(path2d, transform);
576 }
577 }
578
579 for (ent, ..) in bevy_bones_path2ds {
581 commands.entity(ent).despawn()
582 }
583}