Configs, docs, CLI
|
|
@ -4,4 +4,7 @@
|
||||||
/dist
|
/dist
|
||||||
skyline-packer-output.png
|
skyline-packer-output.png
|
||||||
result.png
|
result.png
|
||||||
|
/Cargo.lock
|
||||||
|
/.idea
|
||||||
|
/.vscode
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
|
|
||||||
107
README.md
|
|
@ -27,4 +27,109 @@ Repository contains example how to use plugin in Bevy.
|
||||||
|
|
||||||
## rPack CLI
|
## rPack CLI
|
||||||
|
|
||||||
Command line interface for generating tilemaps. Code stored in `crates/rpack_cli` directory. Right now this is the part of the project that could change the most.
|
Command line interface for generating tilemaps.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Build rpack tilemaps with ease
|
||||||
|
|
||||||
|
Usage: rpack_cli <COMMAND>
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
generate Generates a tilemap
|
||||||
|
config-create Creates a tilemap generation config
|
||||||
|
generate-from-config Generates a tilemap from config
|
||||||
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help
|
||||||
|
Print help (see a summary with '-h')
|
||||||
|
|
||||||
|
-V, --version
|
||||||
|
Print version
|
||||||
|
```
|
||||||
|
|
||||||
|
Available at [crates/rpack_cli](https://github.com/Leinnan/rpack/tree/master/crates/v).
|
||||||
|
|
||||||
|
|
||||||
|
## Used formats
|
||||||
|
|
||||||
|
rpack tools provides and work with two json based files.
|
||||||
|
|
||||||
|
### Atlas files
|
||||||
|
|
||||||
|
Tilemaps are using `.rpack.json` extension.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `size`: two element array- width and height of the tilemap
|
||||||
|
- `filename`: string- path to the atlas image file, relative to the config file
|
||||||
|
- `frames`: array- contain info about each frame in tilemap, contains `key` string field and `frame` field that is made up from fields:
|
||||||
|
- `h`- image height
|
||||||
|
- `w`- image width
|
||||||
|
- `x`- x start pos of the image in the tilemap
|
||||||
|
- `y`- y start pos of the image in the tilemap
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"filename": "tilemap.png",
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 42,
|
||||||
|
"w": 42,
|
||||||
|
"x": 418,
|
||||||
|
"y": 66
|
||||||
|
},
|
||||||
|
"key": "tiles/ship/spaceBuilding_001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 44,
|
||||||
|
"w": 34,
|
||||||
|
"x": 2,
|
||||||
|
"y": 2
|
||||||
|
},
|
||||||
|
"key": "tiles/agents/spaceAstronauts_004"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
512,
|
||||||
|
512
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generation config files
|
||||||
|
|
||||||
|
Config files are using `.rpack_gen.json` extension.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `output_path`: string- path relative to the config, without extension, this is where tilemap image and `.rpack.json` config file are going to be saved
|
||||||
|
- `asset_patterns`: array of strings- search patterns for images to be included, relative paths to the config
|
||||||
|
- `format`: optional(defaults to `Png`), format of the tilemap image, currently supported values: `Png`, `Dds`
|
||||||
|
- `size`: optional(defaults to `2048`), size of the tilemap image
|
||||||
|
- `texture_padding`: optional(defaults to `2`), size of the padding between frames in pixel
|
||||||
|
- `border_padding`: optional(defaults to `0`), size of the padding on the outer edge of the packed image in pixel
|
||||||
|
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"asset_patterns": [
|
||||||
|
"tiles/agents/*",
|
||||||
|
"tiles/effects/*",
|
||||||
|
"tiles/missiles/*",
|
||||||
|
"tiles/ship/spaceBuilding_00*",
|
||||||
|
"tiles/ship/spaceBuilding_01*"
|
||||||
|
],
|
||||||
|
"output_path": "assets/tilemap",
|
||||||
|
"format": "Png",
|
||||||
|
"size": 512,
|
||||||
|
"texture_padding": 2,
|
||||||
|
"border_padding": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ name = "bevy_rpack"
|
||||||
description = "Bevy plugin with rpack atlas support"
|
description = "Bevy plugin with rpack atlas support"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://github.com/Leinnan/rpack"
|
repository = "https://github.com/Leinnan/rpack.git"
|
||||||
|
homepage = "https://github.com/Leinnan/rpack"
|
||||||
authors = ["Piotr Siuszko <siuszko@zoho.com>"]
|
authors = ["Piotr Siuszko <siuszko@zoho.com>"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy", "2d", "plugin"]
|
keywords = ["bevy", "2d", "plugin"]
|
||||||
|
exclude = ["assets","tiles","*.rpack_gen.json", "justfile"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["bevy"]
|
default = ["bevy"]
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ A Bevy plugin with support for the `rpack.json` atlases.
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
//! Simple example that loads the tilemap and once is loaded it creates a sprite with it.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_rpack::prelude::*;
|
use bevy_rpack::prelude::*;
|
||||||
|
|
||||||
|
|
@ -21,7 +23,7 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
commands.insert_resource(Holder(asset_server.load("Tilemap.rpack.json")));
|
commands.insert_resource(Holder(asset_server.load("tilemap.rpack.json")));
|
||||||
commands.spawn(Camera2d);
|
commands.spawn(Camera2d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,13 +37,13 @@ fn on_loaded(
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(sprite) = assets.make_sprite_from_atlas("Sword006") {
|
if let Ok(sprite) = assets.try_make_sprite_from_atlas("agents/spaceAstronauts_005") {
|
||||||
commands.spawn(Sprite {
|
commands.spawn(Sprite {
|
||||||
color: Color::linear_rgb(1.0, 0.0, 0.0),
|
color: Color::linear_rgb(1.0, 0.0, 0.0),
|
||||||
..sprite
|
..sprite
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if let Ok(image_node) = assets.make_image_node_from_atlas("Axe010") {
|
if let Ok(image_node) = assets.try_make_image_node_from_atlas("agents/spaceShips_006") {
|
||||||
commands.spawn(image_node);
|
commands.spawn(image_node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 59 KiB |
|
|
@ -3,70 +3,205 @@
|
||||||
"frames": [
|
"frames": [
|
||||||
{
|
{
|
||||||
"frame": {
|
"frame": {
|
||||||
"h": 34,
|
"h": 42,
|
||||||
"w": 34,
|
"w": 42,
|
||||||
"x": 74,
|
"x": 462,
|
||||||
"y": 2
|
"y": 89
|
||||||
},
|
},
|
||||||
"key": "Sword007"
|
"key": "ship/spaceBuilding_001"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"frame": {
|
"frame": {
|
||||||
"h": 34,
|
"h": 44,
|
||||||
"w": 34,
|
"w": 50,
|
||||||
"x": 38,
|
"x": 125,
|
||||||
"y": 2
|
"y": 2
|
||||||
},
|
},
|
||||||
"key": "Axe011"
|
"key": "agents/spaceAstronauts_012"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"frame": {
|
"frame": {
|
||||||
"h": 34,
|
"h": 44,
|
||||||
"w": 34,
|
"w": 37,
|
||||||
|
"x": 41,
|
||||||
|
"y": 2
|
||||||
|
},
|
||||||
|
"key": "agents/spaceAstronauts_005"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 28,
|
||||||
|
"w": 30,
|
||||||
|
"x": 439,
|
||||||
|
"y": 31
|
||||||
|
},
|
||||||
|
"key": "effects/spaceEffects_010"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 44,
|
||||||
|
"w": 50,
|
||||||
|
"x": 180,
|
||||||
|
"y": 2
|
||||||
|
},
|
||||||
|
"key": "agents/spaceAstronauts_018"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 84,
|
||||||
|
"w": 42,
|
||||||
|
"x": 146,
|
||||||
|
"y": 92
|
||||||
|
},
|
||||||
|
"key": "ship/spaceBuilding_002"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 44,
|
||||||
|
"w": 37,
|
||||||
|
"x": 83,
|
||||||
|
"y": 2
|
||||||
|
},
|
||||||
|
"key": "agents/spaceAstronauts_008"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 57,
|
||||||
|
"w": 55,
|
||||||
|
"x": 268,
|
||||||
|
"y": 101
|
||||||
|
},
|
||||||
|
"key": "ship/spaceBuilding_012"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 36,
|
||||||
|
"w": 37,
|
||||||
|
"x": 158,
|
||||||
|
"y": 51
|
||||||
|
},
|
||||||
|
"key": "effects/spaceEffects_013"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 32,
|
||||||
|
"w": 32,
|
||||||
|
"x": 121,
|
||||||
|
"y": 51
|
||||||
|
},
|
||||||
|
"key": "effects/spaceEffects_012"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 82,
|
||||||
|
"w": 114,
|
||||||
"x": 2,
|
"x": 2,
|
||||||
"y": 74
|
"y": 51
|
||||||
},
|
},
|
||||||
"key": "Axe010"
|
"key": "agents/spaceShips_009"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"frame": {
|
"frame": {
|
||||||
"h": 34,
|
"h": 46,
|
||||||
|
"w": 46,
|
||||||
|
"x": 217,
|
||||||
|
"y": 101
|
||||||
|
},
|
||||||
|
"key": "ship/spaceBuilding_006"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 21,
|
||||||
|
"w": 21,
|
||||||
|
"x": 439,
|
||||||
|
"y": 2
|
||||||
|
},
|
||||||
|
"key": "effects/spaceEffects_008"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 24,
|
||||||
|
"w": 28,
|
||||||
|
"x": 465,
|
||||||
|
"y": 2
|
||||||
|
},
|
||||||
|
"key": "effects/spaceEffects_009"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 22,
|
||||||
|
"w": 16,
|
||||||
|
"x": 474,
|
||||||
|
"y": 62
|
||||||
|
},
|
||||||
|
"key": "missiles/spaceMissiles_014"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 44,
|
||||||
"w": 34,
|
"w": 34,
|
||||||
"x": 2,
|
"x": 2,
|
||||||
"y": 2
|
"y": 2
|
||||||
},
|
},
|
||||||
"key": "Sword006"
|
"key": "agents/spaceAstronauts_004"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"frame": {
|
"frame": {
|
||||||
"h": 34,
|
"h": 148,
|
||||||
"w": 34,
|
"w": 94,
|
||||||
"x": 2,
|
"x": 340,
|
||||||
"y": 38
|
"y": 2
|
||||||
},
|
},
|
||||||
"key": "Sword009"
|
"key": "agents/spaceShips_006"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"frame": {
|
"frame": {
|
||||||
"h": 34,
|
"h": 48,
|
||||||
"w": 34,
|
"w": 12,
|
||||||
"x": 74,
|
"x": 200,
|
||||||
"y": 38
|
"y": 51
|
||||||
},
|
},
|
||||||
"key": "Axe009"
|
"key": "missiles/spaceMissiles_010"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"frame": {
|
"frame": {
|
||||||
"h": 34,
|
"h": 94,
|
||||||
"w": 34,
|
"w": 100,
|
||||||
"x": 38,
|
"x": 235,
|
||||||
"y": 38
|
"y": 2
|
||||||
},
|
},
|
||||||
"key": "Sword008"
|
"key": "agents/spaceShips_003"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 26,
|
||||||
|
"w": 26,
|
||||||
|
"x": 474,
|
||||||
|
"y": 31
|
||||||
|
},
|
||||||
|
"key": "effects/spaceEffects_011"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 33,
|
||||||
|
"w": 18,
|
||||||
|
"x": 439,
|
||||||
|
"y": 64
|
||||||
|
},
|
||||||
|
"key": "missiles/spaceMissiles_028"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": {
|
||||||
|
"h": 35,
|
||||||
|
"w": 20,
|
||||||
|
"x": 121,
|
||||||
|
"y": 88
|
||||||
|
},
|
||||||
|
"key": "missiles/spaceMissiles_040"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"size": [
|
"size": [
|
||||||
128,
|
512,
|
||||||
128
|
512
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"asset_patterns": [
|
||||||
|
"tiles/agents/*",
|
||||||
|
"tiles/effects/*",
|
||||||
|
"tiles/missiles/*",
|
||||||
|
"tiles/ship/spaceBuilding_00*",
|
||||||
|
"tiles/ship/spaceBuilding_01*"
|
||||||
|
],
|
||||||
|
"output_path": "assets/tilemap",
|
||||||
|
"format": "Png",
|
||||||
|
"texture_padding": 5,
|
||||||
|
"border_padding": 2,
|
||||||
|
"size": 512
|
||||||
|
}
|
||||||
|
|
@ -30,13 +30,13 @@ fn on_loaded(
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(sprite) = assets.make_sprite_from_atlas("Sword006") {
|
if let Ok(sprite) = assets.try_make_sprite_from_atlas("agents/spaceAstronauts_005") {
|
||||||
commands.spawn(Sprite {
|
commands.spawn(Sprite {
|
||||||
color: Color::linear_rgb(1.0, 0.0, 0.0),
|
color: Color::linear_rgb(1.0, 0.0, 0.0),
|
||||||
..sprite
|
..sprite
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if let Ok(image_node) = assets.make_image_node_from_atlas("Axe010") {
|
if let Ok(image_node) = assets.try_make_image_node_from_atlas("agents/spaceShips_006") {
|
||||||
commands.spawn(image_node);
|
commands.spawn(image_node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
build_atlas:
|
||||||
|
rpack_cli example_config.rpack_gen.json
|
||||||
|
|
||||||
|
prepare:
|
||||||
|
cargo install --path ../rpack_cli
|
||||||
|
|
@ -47,15 +47,23 @@ pub trait RpackAssetHelper {
|
||||||
key: T,
|
key: T,
|
||||||
) -> Result<(TextureAtlas, Handle<Image>), RpackAtlasError>;
|
) -> Result<(TextureAtlas, Handle<Image>), RpackAtlasError>;
|
||||||
/// Creates a [`Sprite`] component for the given atlas key, if available in any of the loaded Atlases.
|
/// Creates a [`Sprite`] component for the given atlas key, if available in any of the loaded Atlases.
|
||||||
fn make_sprite_from_atlas<T: AsRef<str>>(&self, key: T) -> Result<Sprite, RpackAtlasError>;
|
fn try_make_sprite_from_atlas<T: AsRef<str>>(&self, key: T) -> Result<Sprite, RpackAtlasError>;
|
||||||
/// Creates a [`ImageNode`] component for the given atlas key, if available in any of the loaded Atlases.
|
/// Creates a [`ImageNode`] component for the given atlas key, if available in any of the loaded Atlases.
|
||||||
fn make_image_node_from_atlas<T: AsRef<str>>(
|
fn try_make_image_node_from_atlas<T: AsRef<str>>(
|
||||||
&self,
|
&self,
|
||||||
key: T,
|
key: T,
|
||||||
) -> Result<ImageNode, RpackAtlasError>;
|
) -> Result<ImageNode, RpackAtlasError>;
|
||||||
|
|
||||||
|
/// Provides list of all loaded atlas data keys
|
||||||
|
fn atlas_data_keys(&self) -> Vec<&str>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RpackAssetHelper for Assets<RpackAtlasAsset> {
|
impl RpackAssetHelper for Assets<RpackAtlasAsset> {
|
||||||
|
fn atlas_data_keys(&self) -> Vec<&str> {
|
||||||
|
self.iter()
|
||||||
|
.flat_map(|(_, e)| e.files.keys().map(|e| e.as_ref()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
fn find_atlas_data_by_key<T: AsRef<str>>(
|
fn find_atlas_data_by_key<T: AsRef<str>>(
|
||||||
&self,
|
&self,
|
||||||
key: T,
|
key: T,
|
||||||
|
|
@ -64,26 +72,26 @@ impl RpackAssetHelper for Assets<RpackAtlasAsset> {
|
||||||
return Err(RpackAtlasError::NoAtlas);
|
return Err(RpackAtlasError::NoAtlas);
|
||||||
}
|
}
|
||||||
for (_, a) in self.iter() {
|
for (_, a) in self.iter() {
|
||||||
if let Ok(atlas_data) = a.find_atlas_data_by_key(key.as_ref()) {
|
if let Ok(atlas_data) = a.get_atlas_data(key.as_ref()) {
|
||||||
return Ok(atlas_data);
|
return Ok(atlas_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(RpackAtlasError::WrongKey)
|
Err(RpackAtlasError::WrongKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_sprite_from_atlas<T: AsRef<str>>(&self, key: T) -> Result<Sprite, RpackAtlasError> {
|
fn try_make_sprite_from_atlas<T: AsRef<str>>(&self, key: T) -> Result<Sprite, RpackAtlasError> {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
return Err(RpackAtlasError::NoAtlas);
|
return Err(RpackAtlasError::NoAtlas);
|
||||||
}
|
}
|
||||||
for (_, a) in self.iter() {
|
for (_, a) in self.iter() {
|
||||||
if let Ok(sprite) = a.make_sprite_from_atlas(key.as_ref()) {
|
if let Ok(sprite) = a.try_make_sprite(key.as_ref()) {
|
||||||
return Ok(sprite);
|
return Ok(sprite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(RpackAtlasError::WrongKey)
|
Err(RpackAtlasError::WrongKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_image_node_from_atlas<T: AsRef<str>>(
|
fn try_make_image_node_from_atlas<T: AsRef<str>>(
|
||||||
&self,
|
&self,
|
||||||
key: T,
|
key: T,
|
||||||
) -> Result<ImageNode, RpackAtlasError> {
|
) -> Result<ImageNode, RpackAtlasError> {
|
||||||
|
|
@ -91,7 +99,7 @@ impl RpackAssetHelper for Assets<RpackAtlasAsset> {
|
||||||
return Err(RpackAtlasError::NoAtlas);
|
return Err(RpackAtlasError::NoAtlas);
|
||||||
}
|
}
|
||||||
for (_, a) in self.iter() {
|
for (_, a) in self.iter() {
|
||||||
if let Ok(image_node) = a.make_image_node_from_atlas(key.as_ref()) {
|
if let Ok(image_node) = a.try_make_image_node(key.as_ref()) {
|
||||||
return Ok(image_node);
|
return Ok(image_node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,8 +107,9 @@ impl RpackAssetHelper for Assets<RpackAtlasAsset> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RpackAssetHelper for RpackAtlasAsset {
|
impl RpackAtlasAsset {
|
||||||
fn find_atlas_data_by_key<T: AsRef<str>>(
|
/// Retrieves the atlas data (texture atlas and image) for the given atlas key, if available.
|
||||||
|
pub fn get_atlas_data<T: AsRef<str>>(
|
||||||
&self,
|
&self,
|
||||||
key: T,
|
key: T,
|
||||||
) -> Result<(TextureAtlas, Handle<Image>), RpackAtlasError> {
|
) -> Result<(TextureAtlas, Handle<Image>), RpackAtlasError> {
|
||||||
|
|
@ -116,19 +125,18 @@ impl RpackAssetHelper for RpackAtlasAsset {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_sprite_from_atlas<T: AsRef<str>>(&self, key: T) -> Result<Sprite, RpackAtlasError> {
|
/// Creates a [`Sprite`] component for the given atlas key
|
||||||
if let Ok((atlas, image)) = self.find_atlas_data_by_key(key) {
|
pub fn try_make_sprite<T: AsRef<str>>(&self, key: T) -> Result<Sprite, RpackAtlasError> {
|
||||||
|
if let Ok((atlas, image)) = self.get_atlas_data(key) {
|
||||||
Ok(Sprite::from_atlas_image(image, atlas))
|
Ok(Sprite::from_atlas_image(image, atlas))
|
||||||
} else {
|
} else {
|
||||||
Err(RpackAtlasError::WrongKey)
|
Err(RpackAtlasError::WrongKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_image_node_from_atlas<T: AsRef<str>>(
|
/// Creates a [`ImageNode`] component for the given atlas key, if available in any of the loaded Atlases.
|
||||||
&self,
|
pub fn try_make_image_node<T: AsRef<str>>(&self, key: T) -> Result<ImageNode, RpackAtlasError> {
|
||||||
key: T,
|
if let Ok((atlas, image)) = self.get_atlas_data(key) {
|
||||||
) -> Result<ImageNode, RpackAtlasError> {
|
|
||||||
if let Ok((atlas, image)) = self.find_atlas_data_by_key(key) {
|
|
||||||
Ok(ImageNode::from_atlas_image(image, atlas))
|
Ok(ImageNode::from_atlas_image(image, atlas))
|
||||||
} else {
|
} else {
|
||||||
Err(RpackAtlasError::WrongKey)
|
Err(RpackAtlasError::WrongKey)
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 560 B |
|
After Width: | Height: | Size: 640 B |
|
After Width: | Height: | Size: 733 B |
|
After Width: | Height: | Size: 665 B |
|
After Width: | Height: | Size: 831 B |
|
After Width: | Height: | Size: 923 B |
|
After Width: | Height: | Size: 516 B |
|
After Width: | Height: | Size: 434 B |
|
After Width: | Height: | Size: 372 B |
|
After Width: | Height: | Size: 747 B |
|
After Width: | Height: | Size: 478 B |
|
After Width: | Height: | Size: 423 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 944 B |
|
After Width: | Height: | Size: 400 B |
|
After Width: | Height: | Size: 320 B |
|
After Width: | Height: | Size: 583 B |
|
After Width: | Height: | Size: 395 B |
|
After Width: | Height: | Size: 679 B |
|
After Width: | Height: | Size: 290 B |
|
After Width: | Height: | Size: 726 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 791 B |
|
After Width: | Height: | Size: 788 B |
|
After Width: | Height: | Size: 280 B |
|
After Width: | Height: | Size: 231 B |
|
After Width: | Height: | Size: 560 B |
|
|
@ -1,11 +1,13 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rpack"
|
name = "rpack"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
description = "GUI application for generating rpack atlases"
|
||||||
authors = ["Piotr Siuszko <siuszko@zoho.com>"]
|
authors = ["Piotr Siuszko <siuszko@zoho.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.81"
|
rust-version = "1.81"
|
||||||
repository = "https://github.com/Leinnan/rpack.git"
|
repository = "https://github.com/Leinnan/rpack.git"
|
||||||
homepage = "https://github.com/Leinnan/rpack"
|
homepage = "https://github.com/Leinnan/rpack"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = "0.30"
|
egui = "0.30"
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,12 @@ impl Default for Application {
|
||||||
|
|
||||||
impl Application {
|
impl Application {
|
||||||
pub fn rebuild_image_data(&mut self) {
|
pub fn rebuild_image_data(&mut self) {
|
||||||
let prefix = crate::helpers::get_common_prefix(&self.dropped_files);
|
let file_paths: Vec<String> = self
|
||||||
|
.dropped_files
|
||||||
|
.iter()
|
||||||
|
.map(|dropped_file| dropped_file.file_path())
|
||||||
|
.collect();
|
||||||
|
let prefix = rpack_cli::get_common_prefix(&file_paths);
|
||||||
|
|
||||||
self.image_data = self
|
self.image_data = self
|
||||||
.dropped_files
|
.dropped_files
|
||||||
|
|
@ -254,7 +259,7 @@ impl eframe::App for Application {
|
||||||
.color(MY_ACCENT_COLOR32)
|
.color(MY_ACCENT_COLOR32)
|
||||||
.strong();
|
.strong();
|
||||||
ui.allocate_space(egui::vec2(TOP_SIDE_MARGIN, HEADER_HEIGHT));
|
ui.allocate_space(egui::vec2(TOP_SIDE_MARGIN, HEADER_HEIGHT));
|
||||||
ui.add(egui::Label::new(text));
|
ui.add(egui::Label::new(text).selectable(false));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ctx.input(|i| {
|
ctx.input(|i| {
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,8 @@
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use egui::DroppedFile;
|
use egui::DroppedFile;
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use rpack_cli::ImageFile;
|
use rpack_cli::ImageFile;
|
||||||
use texture_packer::importer::ImageImporter;
|
use texture_packer::importer::ImageImporter;
|
||||||
|
|
||||||
pub fn get_common_prefix(paths: &[DroppedFile]) -> String {
|
|
||||||
if paths.is_empty() {
|
|
||||||
return String::new();
|
|
||||||
}
|
|
||||||
let full_name = paths[0].file_path();
|
|
||||||
let path = Path::new(&full_name)
|
|
||||||
.file_name()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_str()
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let mut prefix = full_name.strip_suffix(&path).unwrap_or_default().to_owned();
|
|
||||||
|
|
||||||
for s in paths.iter().skip(1) {
|
|
||||||
let s = s.file_path();
|
|
||||||
while !(s.starts_with(&prefix) || prefix.is_empty()) {
|
|
||||||
prefix.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DroppedFileHelper {
|
pub trait DroppedFileHelper {
|
||||||
fn file_path(&self) -> String;
|
fn file_path(&self) -> String;
|
||||||
fn create_image<P>(&self, prefix: P) -> Option<(String, ImageFile)>
|
fn create_image<P>(&self, prefix: P) -> Option<(String, ImageFile)>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rpack_cli"
|
name = "rpack_cli"
|
||||||
authors = ["Piotr Siuszko <siuszko@zoho.com>"]
|
authors = ["Piotr Siuszko <siuszko@zoho.com>"]
|
||||||
license = "MIT"
|
description = "CLI application for generating rpack atlases"
|
||||||
|
repository = "https://github.com/Leinnan/rpack.git"
|
||||||
|
homepage = "https://github.com/Leinnan/rpack"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
@ -21,6 +25,6 @@ thiserror = "2"
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
clap = { version = "4", features = ["derive"], optional = true }
|
clap = { version = "4", features = ["derive"], optional = true }
|
||||||
basis-universal = { version = "0.3.1", optional = true }
|
basis-universal = { version = "0.3.1", optional = true }
|
||||||
image_dds = { version = "0.6.2", optional = true }
|
image_dds = { version = "0.7", optional = true }
|
||||||
glob = { version = "0.3", optional = true }
|
glob = { version = "0.3", optional = true }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{io::Write, path::Path};
|
use std::io::Write;
|
||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use rpack_cli::{ImageFile, Spritesheet, TilemapGenerationConfig};
|
use rpack_cli::TilemapGenerationConfig;
|
||||||
|
|
||||||
use rpack_cli::SaveImageFormat;
|
use rpack_cli::SaveImageFormat;
|
||||||
|
|
||||||
|
|
@ -12,14 +12,21 @@ pub enum Commands {
|
||||||
/// Name of the tilemap to build, when no value is provided uses 'tilemap'
|
/// Name of the tilemap to build, when no value is provided uses 'tilemap'
|
||||||
#[clap(action)]
|
#[clap(action)]
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
/// size of the tilemap, default: 512
|
/// size of the tilemap, default: 2048
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
size: Option<u32>,
|
size: Option<u32>,
|
||||||
/// Image format
|
/// Image format
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
format: Option<SaveImageFormat>,
|
format: Option<SaveImageFormat>,
|
||||||
|
/// Asset sources path, argument can be passed multiple times
|
||||||
|
#[clap(short, long)]
|
||||||
|
source_paths: Vec<String>,
|
||||||
|
/// Size of the padding between frames in pixel. Default value is `2`
|
||||||
|
texture_padding: Option<u32>,
|
||||||
|
/// Size of the padding on the outer edge of the packed image in pixel. Default value is `0`.
|
||||||
|
border_padding: Option<u32>,
|
||||||
},
|
},
|
||||||
/// Creates a tilemap generation config that can be used by this tool
|
/// Creates a tilemap generation config
|
||||||
ConfigCreate {
|
ConfigCreate {
|
||||||
/// path of the config to create
|
/// path of the config to create
|
||||||
#[clap(action)]
|
#[clap(action)]
|
||||||
|
|
@ -27,7 +34,7 @@ pub enum Commands {
|
||||||
/// path of the tilemap to build, when no value is provided uses '/tilemap'
|
/// path of the tilemap to build, when no value is provided uses '/tilemap'
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
output_path: Option<String>,
|
output_path: Option<String>,
|
||||||
/// size of the tilemap, default: 512
|
/// size of the tilemap, default: 2048
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
size: Option<u32>,
|
size: Option<u32>,
|
||||||
/// Image format, png by default
|
/// Image format, png by default
|
||||||
|
|
@ -36,6 +43,10 @@ pub enum Commands {
|
||||||
/// Asset sources path, argument can be passed multiple times
|
/// Asset sources path, argument can be passed multiple times
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
source_paths: Vec<String>,
|
source_paths: Vec<String>,
|
||||||
|
/// Size of the padding between frames in pixel. Default value is `2`
|
||||||
|
texture_padding: Option<u32>,
|
||||||
|
/// Size of the padding on the outer edge of the packed image in pixel. Default value is `0`.
|
||||||
|
border_padding: Option<u32>,
|
||||||
},
|
},
|
||||||
/// Generates a tilemap from config
|
/// Generates a tilemap from config
|
||||||
GenerateFromConfig {
|
GenerateFromConfig {
|
||||||
|
|
@ -47,14 +58,38 @@ pub enum Commands {
|
||||||
impl Commands {
|
impl Commands {
|
||||||
pub(crate) fn run(&self) -> anyhow::Result<()> {
|
pub(crate) fn run(&self) -> anyhow::Result<()> {
|
||||||
match self.clone() {
|
match self.clone() {
|
||||||
Commands::Generate { name, size, format } => Self::generate_tilemap(name, size, format),
|
Commands::Generate {
|
||||||
|
name,
|
||||||
|
size,
|
||||||
|
format,
|
||||||
|
source_paths,
|
||||||
|
texture_padding,
|
||||||
|
border_padding,
|
||||||
|
} => Self::generate_tilemap(
|
||||||
|
name,
|
||||||
|
size,
|
||||||
|
format,
|
||||||
|
source_paths,
|
||||||
|
texture_padding,
|
||||||
|
border_padding,
|
||||||
|
),
|
||||||
Commands::ConfigCreate {
|
Commands::ConfigCreate {
|
||||||
config_path,
|
config_path,
|
||||||
output_path,
|
output_path,
|
||||||
size,
|
size,
|
||||||
format,
|
format,
|
||||||
source_paths,
|
source_paths,
|
||||||
} => Self::create_config(config_path, output_path, size, format, source_paths),
|
texture_padding,
|
||||||
|
border_padding,
|
||||||
|
} => Self::create_config(
|
||||||
|
config_path,
|
||||||
|
output_path,
|
||||||
|
size,
|
||||||
|
format,
|
||||||
|
source_paths,
|
||||||
|
texture_padding,
|
||||||
|
border_padding,
|
||||||
|
),
|
||||||
Commands::GenerateFromConfig { config_path } => {
|
Commands::GenerateFromConfig { config_path } => {
|
||||||
Self::generate_tilemap_from_config(config_path)
|
Self::generate_tilemap_from_config(config_path)
|
||||||
}
|
}
|
||||||
|
|
@ -65,72 +100,48 @@ impl Commands {
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
size: Option<u32>,
|
size: Option<u32>,
|
||||||
format: Option<SaveImageFormat>,
|
format: Option<SaveImageFormat>,
|
||||||
|
source_paths: Vec<String>,
|
||||||
|
texture_padding: Option<u32>,
|
||||||
|
border_padding: Option<u32>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let name = name.unwrap_or("tilemap".to_owned());
|
let name = name.unwrap_or("tilemap".to_owned());
|
||||||
let format = format.unwrap_or_default();
|
let source_paths = if source_paths.is_empty() {
|
||||||
let atlas_filename = format!("{}{}", name, format);
|
vec![".".to_owned()]
|
||||||
let atlas_json_filename = format!("{}.rpack.json", name);
|
} else {
|
||||||
let size = size.unwrap_or(512);
|
source_paths
|
||||||
|
};
|
||||||
|
let config = TilemapGenerationConfig {
|
||||||
|
asset_patterns: source_paths,
|
||||||
|
output_path: name,
|
||||||
|
format,
|
||||||
|
size,
|
||||||
|
texture_padding,
|
||||||
|
border_padding,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let images: Vec<ImageFile> = glob::glob("**/*png")?
|
config.generate()
|
||||||
.flatten()
|
}
|
||||||
.flat_map(|f| ImageFile::at_path(&f, f.to_str().unwrap_or_default()))
|
|
||||||
.collect();
|
|
||||||
let spritesheet = Spritesheet::build(
|
|
||||||
texture_packer::TexturePackerConfig {
|
|
||||||
max_width: size,
|
|
||||||
max_height: size,
|
|
||||||
allow_rotation: false,
|
|
||||||
force_max_dimensions: true,
|
|
||||||
border_padding: 2,
|
|
||||||
texture_padding: 2,
|
|
||||||
texture_extrusion: 2,
|
|
||||||
trim: false,
|
|
||||||
texture_outlines: false,
|
|
||||||
},
|
|
||||||
&images,
|
|
||||||
&atlas_filename,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if Path::new(&atlas_json_filename).exists() {
|
|
||||||
std::fs::remove_file(&atlas_json_filename).expect("Could not remove the old file");
|
|
||||||
}
|
|
||||||
if Path::new(&atlas_filename).exists() {
|
|
||||||
std::fs::remove_file(&atlas_filename).expect("Could not remove the old file");
|
|
||||||
}
|
|
||||||
match format {
|
|
||||||
SaveImageFormat::Dds => {
|
|
||||||
#[cfg(feature = "dds")]
|
|
||||||
spritesheet.save_as_dds(&atlas_filename);
|
|
||||||
#[cfg(not(feature = "dds"))]
|
|
||||||
panic!("Program is compiled without support for dds. Compile it yourself with feature `dds` enabled.");
|
|
||||||
}
|
|
||||||
f => {
|
|
||||||
spritesheet
|
|
||||||
.image_data
|
|
||||||
.save_with_format(&atlas_filename, f.into())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let json = serde_json::to_string_pretty(&spritesheet.atlas_asset_json)?;
|
|
||||||
let mut file = std::fs::File::create(&atlas_json_filename)?;
|
|
||||||
file.write_all(json.as_bytes())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn create_config(
|
fn create_config(
|
||||||
config_path: String,
|
config_path: String,
|
||||||
output_path: Option<String>,
|
output_path: Option<String>,
|
||||||
size: Option<u32>,
|
size: Option<u32>,
|
||||||
format: Option<SaveImageFormat>,
|
format: Option<SaveImageFormat>,
|
||||||
source_paths: Vec<String>,
|
source_paths: Vec<String>,
|
||||||
|
texture_padding: Option<u32>,
|
||||||
|
border_padding: Option<u32>,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
let name = output_path.unwrap_or("tilemap".to_owned());
|
let name = output_path.unwrap_or("tilemap".to_owned());
|
||||||
let format = format.unwrap_or_default();
|
|
||||||
let size = size.unwrap_or(512);
|
|
||||||
let config = TilemapGenerationConfig {
|
let config = TilemapGenerationConfig {
|
||||||
size,
|
size,
|
||||||
asset_paths: source_paths,
|
asset_patterns: source_paths,
|
||||||
output_path: name,
|
output_path: name,
|
||||||
format,
|
format,
|
||||||
|
texture_padding,
|
||||||
|
border_padding,
|
||||||
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_string_pretty(&config)?;
|
let json = serde_json::to_string_pretty(&config)?;
|
||||||
|
|
@ -141,61 +152,7 @@ impl Commands {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_tilemap_from_config(config_path: String) -> anyhow::Result<()> {
|
fn generate_tilemap_from_config(config_path: String) -> anyhow::Result<()> {
|
||||||
let config_file = std::fs::read_to_string(&config_path)?;
|
let config = TilemapGenerationConfig::read_from_file(config_path)?;
|
||||||
let config: TilemapGenerationConfig = serde_json::from_str(&config_file)?;
|
config.generate()
|
||||||
|
|
||||||
let images: Vec<ImageFile> = config
|
|
||||||
.asset_paths
|
|
||||||
.iter()
|
|
||||||
.flat_map(|path| {
|
|
||||||
let pattern = format!("{}/*png", path);
|
|
||||||
glob::glob(&pattern)
|
|
||||||
.unwrap()
|
|
||||||
.flatten()
|
|
||||||
.flat_map(|f| ImageFile::at_path(&f, f.to_str().unwrap_or_default()))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let atlas_filename = format!("{}{}", config.output_path, config.format);
|
|
||||||
let atlas_json_filename = format!("{}.rpack.json", config.output_path);
|
|
||||||
let spritesheet = Spritesheet::build(
|
|
||||||
texture_packer::TexturePackerConfig {
|
|
||||||
max_width: config.size,
|
|
||||||
max_height: config.size,
|
|
||||||
allow_rotation: false,
|
|
||||||
force_max_dimensions: true,
|
|
||||||
border_padding: 2,
|
|
||||||
texture_padding: 2,
|
|
||||||
texture_extrusion: 2,
|
|
||||||
trim: false,
|
|
||||||
texture_outlines: false,
|
|
||||||
},
|
|
||||||
&images,
|
|
||||||
&atlas_filename,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if Path::new(&atlas_json_filename).exists() {
|
|
||||||
std::fs::remove_file(&atlas_json_filename).expect("Could not remove the old file");
|
|
||||||
}
|
|
||||||
if Path::new(&atlas_filename).exists() {
|
|
||||||
std::fs::remove_file(&atlas_filename).expect("Could not remove the old file");
|
|
||||||
}
|
|
||||||
match config.format {
|
|
||||||
SaveImageFormat::Dds => {
|
|
||||||
#[cfg(feature = "dds")]
|
|
||||||
spritesheet.save_as_dds(&atlas_filename);
|
|
||||||
#[cfg(not(feature = "dds"))]
|
|
||||||
panic!("Program is compiled without support for dds. Compile it yourself with feature `dds` enabled.");
|
|
||||||
}
|
|
||||||
f => {
|
|
||||||
spritesheet
|
|
||||||
.image_data
|
|
||||||
.save_with_format(&atlas_filename, f.into())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let json = serde_json::to_string_pretty(&spritesheet.atlas_asset_json)?;
|
|
||||||
let mut file = std::fs::File::create(&atlas_json_filename)?;
|
|
||||||
file.write_all(json.as_bytes())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,13 @@ use bevy_rpack::{AtlasFrame, SerializableRect};
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{fmt::Display, path::Path};
|
#[cfg(all(feature = "cli", not(target_arch = "wasm32")))]
|
||||||
|
use std::io::Write;
|
||||||
|
use std::{
|
||||||
|
ffi::OsStr,
|
||||||
|
fmt::Display,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
use texture_packer::{importer::ImageImporter, TexturePacker, TexturePackerConfig};
|
use texture_packer::{importer::ImageImporter, TexturePacker, TexturePackerConfig};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -36,6 +42,34 @@ impl ImageFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_common_prefix<S>(paths: &[S]) -> String
|
||||||
|
where
|
||||||
|
S: AsRef<OsStr> + Sized,
|
||||||
|
{
|
||||||
|
if paths.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
let path = Path::new(paths[0].as_ref())
|
||||||
|
.file_name()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut prefix = String::from(paths[0].as_ref().to_string_lossy())
|
||||||
|
.strip_suffix(&path)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
for s in paths.iter().skip(1) {
|
||||||
|
let s = s.as_ref().to_string_lossy();
|
||||||
|
while !(s.starts_with(&prefix) || prefix.is_empty()) {
|
||||||
|
prefix.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Copy, Serialize, Deserialize)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
all(feature = "cli", not(target_arch = "wasm32")),
|
all(feature = "cli", not(target_arch = "wasm32")),
|
||||||
|
|
@ -132,7 +166,7 @@ impl Spritesheet {
|
||||||
#[cfg(all(feature = "dds", not(target_arch = "wasm32")))]
|
#[cfg(all(feature = "dds", not(target_arch = "wasm32")))]
|
||||||
pub fn save_as_dds<R>(&self, output_path: R)
|
pub fn save_as_dds<R>(&self, output_path: R)
|
||||||
where
|
where
|
||||||
R: AsRef<str>,
|
R: AsRef<Path>,
|
||||||
{
|
{
|
||||||
let rgba_image = self.image_data.to_rgba8();
|
let rgba_image = self.image_data.to_rgba8();
|
||||||
|
|
||||||
|
|
@ -152,7 +186,7 @@ impl Spritesheet {
|
||||||
#[cfg(all(feature = "basis", not(target_arch = "wasm32")))]
|
#[cfg(all(feature = "basis", not(target_arch = "wasm32")))]
|
||||||
pub fn save_as_basis<R>(&self, output_path: R)
|
pub fn save_as_basis<R>(&self, output_path: R)
|
||||||
where
|
where
|
||||||
R: AsRef<str>,
|
R: AsRef<Path>,
|
||||||
{
|
{
|
||||||
use basis_universal::{
|
use basis_universal::{
|
||||||
BasisTextureFormat, Compressor, TranscodeParameters, Transcoder,
|
BasisTextureFormat, Compressor, TranscodeParameters, Transcoder,
|
||||||
|
|
@ -268,10 +302,122 @@ impl Spritesheet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize, Default)]
|
||||||
pub struct TilemapGenerationConfig {
|
pub struct TilemapGenerationConfig {
|
||||||
pub asset_paths: Vec<String>,
|
pub asset_patterns: Vec<String>,
|
||||||
pub output_path: String,
|
pub output_path: String,
|
||||||
pub format: SaveImageFormat,
|
/// Image format, png by default
|
||||||
pub size: u32,
|
pub format: Option<SaveImageFormat>,
|
||||||
|
/// Size of the tilemap texture. Default value is `2048`.
|
||||||
|
pub size: Option<u32>,
|
||||||
|
/// Size of the padding between frames in pixel. Default value is `2`
|
||||||
|
pub texture_padding: Option<u32>,
|
||||||
|
/// Size of the padding on the outer edge of the packed image in pixel. Default value is `0`.
|
||||||
|
pub border_padding: Option<u32>,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub working_dir: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "cli", not(target_arch = "wasm32")))]
|
||||||
|
impl TilemapGenerationConfig {
|
||||||
|
pub fn read_from_file<P>(path: P) -> anyhow::Result<TilemapGenerationConfig>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let config_file = std::fs::read_to_string(path.as_ref())?;
|
||||||
|
let mut config: TilemapGenerationConfig = serde_json::from_str(&config_file)?;
|
||||||
|
config.working_dir = Path::new(path.as_ref()).parent().map(|p| p.to_path_buf());
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate(&self) -> anyhow::Result<()> {
|
||||||
|
let dir = match &self.working_dir {
|
||||||
|
None => std::env::current_dir().expect("msg"),
|
||||||
|
Some(p) => {
|
||||||
|
if p.to_string_lossy().len() == 0 {
|
||||||
|
std::env::current_dir().expect("msg")
|
||||||
|
} else {
|
||||||
|
p.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let working_dir = match std::path::absolute(dir) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => panic!("DUPA {:?}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut file_paths: Vec<PathBuf> = self
|
||||||
|
.asset_patterns
|
||||||
|
.iter()
|
||||||
|
.flat_map(|pattern| {
|
||||||
|
let p = format!("{}/{}", working_dir.to_string_lossy(), pattern);
|
||||||
|
println!("{}", p);
|
||||||
|
glob::glob(&p).expect("Wrong pattern for assets").flatten()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
file_paths.sort();
|
||||||
|
let prefix = get_common_prefix(&file_paths);
|
||||||
|
let images: Vec<ImageFile> = file_paths
|
||||||
|
.iter()
|
||||||
|
.flat_map(|f| {
|
||||||
|
let id = f
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.strip_prefix(&prefix)
|
||||||
|
.unwrap_or_default();
|
||||||
|
ImageFile::at_path(f, id)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let atlas_image_path = working_dir.join(format!(
|
||||||
|
"{}{}",
|
||||||
|
self.output_path,
|
||||||
|
self.format.unwrap_or_default()
|
||||||
|
));
|
||||||
|
let atlas_filename = Path::new(&atlas_image_path)
|
||||||
|
.file_name()
|
||||||
|
.expect("D")
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
let atlas_config_path = working_dir.join(format!("{}.rpack.json", self.output_path));
|
||||||
|
let spritesheet = Spritesheet::build(
|
||||||
|
texture_packer::TexturePackerConfig {
|
||||||
|
max_width: self.size.unwrap_or(2048),
|
||||||
|
max_height: self.size.unwrap_or(2048),
|
||||||
|
allow_rotation: false,
|
||||||
|
force_max_dimensions: true,
|
||||||
|
border_padding: self.border_padding.unwrap_or(0),
|
||||||
|
texture_padding: self.texture_padding.unwrap_or(2),
|
||||||
|
texture_extrusion: 0,
|
||||||
|
trim: false,
|
||||||
|
texture_outlines: false,
|
||||||
|
},
|
||||||
|
&images,
|
||||||
|
&atlas_filename,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if Path::new(&atlas_config_path).exists() {
|
||||||
|
std::fs::remove_file(&atlas_config_path).expect("Could not remove the old file");
|
||||||
|
}
|
||||||
|
if Path::new(&atlas_image_path).exists() {
|
||||||
|
std::fs::remove_file(&atlas_image_path).expect("Could not remove the old file");
|
||||||
|
}
|
||||||
|
match self.format.unwrap_or_default() {
|
||||||
|
SaveImageFormat::Dds => {
|
||||||
|
#[cfg(feature = "dds")]
|
||||||
|
spritesheet.save_as_dds(&atlas_image_path);
|
||||||
|
#[cfg(not(feature = "dds"))]
|
||||||
|
panic!("Program is compiled without support for dds. Compile it yourself with feature `dds` enabled.");
|
||||||
|
}
|
||||||
|
f => {
|
||||||
|
spritesheet
|
||||||
|
.image_data
|
||||||
|
.save_with_format(&atlas_image_path, f.into())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let json = serde_json::to_string_pretty(&spritesheet.atlas_asset_json)?;
|
||||||
|
let mut file = std::fs::File::create(&atlas_config_path)?;
|
||||||
|
file.write_all(json.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,29 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use rpack_cli::TilemapGenerationConfig;
|
||||||
|
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
|
|
||||||
/// Build rpack tilemaps with ease
|
/// Build rpack tilemaps with ease
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = "rpack CLI tool")]
|
#[command(version, about, long_about = "Build rpack tilemaps with ease")]
|
||||||
|
#[command(propagate_version = true)]
|
||||||
struct Args {
|
struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: crate::commands::Commands,
|
command: crate::commands::Commands,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
|
let args_os = std::env::args_os();
|
||||||
|
if args_os.len() == 2 {
|
||||||
|
let arg = format!("{}", args_os.last().expect("msg").to_string_lossy());
|
||||||
|
if Path::new(&arg).exists() && arg.ends_with("rpack_gen.json") {
|
||||||
|
let config = TilemapGenerationConfig::read_from_file(&arg)?;
|
||||||
|
config.generate()?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
args.command.run()?;
|
args.command.run()?;
|
||||||
|
|
|
||||||