Skip to content

Commit 8ac72d8

Browse files
authored
feat: prep for hytale (#128)
1 parent 4990a56 commit 8ac72d8

File tree

10 files changed

+965
-31
lines changed

10 files changed

+965
-31
lines changed

src/rust/hytale.rs

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
extern crate image;
2+
3+
use crate::utils::{apply_minecraft_transparency, fast_overlay};
4+
use image::{imageops, DynamicImage, GenericImageView, Rgba, RgbaImage};
5+
use imageproc::geometric_transformations::{warp_into, Interpolation, Projection};
6+
7+
/// Hytale skin handler - currently a stub with Minecraft-like layout
8+
/// TODO: Update texture coordinates when Hytale skin format is known
9+
pub(crate) struct HytaleSkin(DynamicImage);
10+
11+
#[allow(dead_code)] // Stub for future use when Hytale format is known
12+
#[derive(Copy, Clone, PartialEq)]
13+
pub(crate) enum HytaleSkinVersion {
14+
Standard, // Assumed standard format, update when known
15+
Invalid,
16+
}
17+
18+
#[derive(Clone, Copy, PartialEq)]
19+
pub(crate) enum SkinModel {
20+
Slim,
21+
Regular,
22+
}
23+
24+
#[derive(Copy, Clone, PartialEq)]
25+
pub(crate) enum Layer {
26+
Bottom,
27+
Top,
28+
Both,
29+
}
30+
31+
#[derive(Copy, Clone, PartialEq)]
32+
pub(crate) enum BodyPart {
33+
Head,
34+
Body,
35+
ArmLeft,
36+
ArmRight,
37+
LegLeft,
38+
LegRight,
39+
}
40+
41+
const SKEW_A: f32 = 26.0 / 45.0;
42+
const SKEW_B: f32 = SKEW_A * 2.0;
43+
44+
pub(crate) struct RenderOptions {
45+
pub armored: bool,
46+
pub model: SkinModel,
47+
}
48+
49+
impl HytaleSkin {
50+
#[inline]
51+
pub fn new(skin: DynamicImage) -> HytaleSkin {
52+
HytaleSkin(skin)
53+
}
54+
55+
#[allow(dead_code)] // Stub for future use when Hytale format is known
56+
#[inline]
57+
fn version(&self) -> HytaleSkinVersion {
58+
// TODO: Update when Hytale skin format dimensions are known
59+
// For now, accept 64x64 as the standard format (placeholder)
60+
match self.0.dimensions() {
61+
(64, 64) => HytaleSkinVersion::Standard,
62+
_ => HytaleSkinVersion::Invalid,
63+
}
64+
}
65+
66+
/// Get a body part from the skin texture
67+
/// TODO: Update texture coordinates when Hytale skin format is known
68+
/// Currently using Minecraft-like coordinates as a placeholder
69+
pub(crate) fn get_part(&self, layer: Layer, part: BodyPart, model: SkinModel) -> DynamicImage {
70+
let arm_width = match model {
71+
SkinModel::Slim => 3,
72+
SkinModel::Regular => 4,
73+
};
74+
75+
match layer {
76+
Layer::Both => {
77+
let mut bottom = self.get_part(Layer::Bottom, part, model);
78+
let mut top = self.get_part(Layer::Top, part, model);
79+
apply_minecraft_transparency(&mut top);
80+
fast_overlay(&mut bottom, &top, 0, 0);
81+
bottom
82+
}
83+
// TODO: These coordinates are placeholders - update when Hytale format is known
84+
Layer::Bottom => match part {
85+
BodyPart::Head => self.0.crop_imm(8, 8, 8, 8),
86+
BodyPart::Body => self.0.crop_imm(20, 20, 8, 12),
87+
BodyPart::ArmRight => self.0.crop_imm(36, 52, arm_width, 12),
88+
BodyPart::ArmLeft => self.0.crop_imm(44, 20, arm_width, 12),
89+
BodyPart::LegRight => self.0.crop_imm(20, 52, 4, 12),
90+
BodyPart::LegLeft => self.0.crop_imm(4, 20, 4, 12),
91+
},
92+
Layer::Top => match part {
93+
BodyPart::Head => self.0.crop_imm(40, 8, 8, 8),
94+
BodyPart::Body => self.0.crop_imm(20, 36, 8, 12),
95+
BodyPart::ArmLeft => self.0.crop_imm(52, 52, arm_width, 12),
96+
BodyPart::ArmRight => self.0.crop_imm(44, 36, arm_width, 12),
97+
BodyPart::LegLeft => self.0.crop_imm(4, 52, 4, 12),
98+
BodyPart::LegRight => self.0.crop_imm(4, 36, 4, 12),
99+
},
100+
}
101+
}
102+
103+
pub(crate) fn get_cape(&self) -> DynamicImage {
104+
// TODO: Update cape coordinates when Hytale format is known
105+
self.0.crop_imm(1, 1, 10, 16)
106+
}
107+
108+
pub(crate) fn render_body(&self, options: RenderOptions) -> DynamicImage {
109+
let layer_type = if options.armored {
110+
Layer::Both
111+
} else {
112+
Layer::Bottom
113+
};
114+
115+
let img_width = match options.model {
116+
SkinModel::Slim => 14,
117+
SkinModel::Regular => 16,
118+
};
119+
120+
let arm_width = match options.model {
121+
SkinModel::Slim => 3,
122+
SkinModel::Regular => 4,
123+
};
124+
125+
let mut image = RgbaImage::new(img_width, 32);
126+
127+
// Head (centered)
128+
imageops::overlay(
129+
&mut image,
130+
&self.get_part(layer_type, BodyPart::Head, options.model),
131+
arm_width,
132+
0,
133+
);
134+
// Body (centered)
135+
imageops::overlay(
136+
&mut image,
137+
&self.get_part(layer_type, BodyPart::Body, options.model),
138+
arm_width,
139+
8,
140+
);
141+
// Right Arm (viewer left)
142+
imageops::overlay(
143+
&mut image,
144+
&self.get_part(layer_type, BodyPart::ArmRight, options.model),
145+
0,
146+
8,
147+
);
148+
// Left Arm (viewer right)
149+
imageops::overlay(
150+
&mut image,
151+
&self.get_part(layer_type, BodyPart::ArmLeft, options.model),
152+
i64::from(img_width) - arm_width,
153+
8,
154+
);
155+
// Right Leg
156+
imageops::overlay(
157+
&mut image,
158+
&self.get_part(layer_type, BodyPart::LegLeft, options.model),
159+
arm_width,
160+
20,
161+
);
162+
// Left Leg
163+
imageops::overlay(
164+
&mut image,
165+
&self.get_part(layer_type, BodyPart::LegRight, options.model),
166+
arm_width + 4,
167+
20,
168+
);
169+
170+
DynamicImage::ImageRgba8(image)
171+
}
172+
173+
pub(crate) fn render_cube(&self, size: u32, options: RenderOptions) -> DynamicImage {
174+
let scale = (size as f32) / 20.0_f32;
175+
176+
let x_render_offset = scale.ceil() as i64;
177+
let z_render_offset = x_render_offset / 2;
178+
179+
let mut render = RgbaImage::new(size, size);
180+
181+
let z_offset = scale * 3.0;
182+
let x_offset = scale * 2.0;
183+
184+
// TODO: Update these coordinates when Hytale format is known
185+
let head_orig_top = self.0.crop_imm(8, 0, 8, 8);
186+
let head_orig_right = self.0.crop_imm(0, 8, 8, 8);
187+
let head_orig_front = self.0.crop_imm(8, 8, 8, 8);
188+
189+
let head_orig_top_overlay = self.0.crop_imm(40, 0, 8, 8);
190+
let head_orig_right_overlay = self.0.crop_imm(32, 8, 8, 8);
191+
let head_orig_front_overlay = self.0.crop_imm(40, 8, 8, 8);
192+
193+
let head_orig_right = head_orig_right.brighten(-4);
194+
let head_orig_right_overlay = head_orig_right_overlay.brighten(-4);
195+
196+
let mut scratch = RgbaImage::new(size, size);
197+
198+
// head top
199+
let head_top_skew =
200+
Projection::from_matrix([1.0, 1.0, 0.0, -SKEW_A, SKEW_A, 0.0, 0.0, 0.0, 1.0]).unwrap()
201+
* Projection::translate(-0.5 - z_offset, x_offset + z_offset - 0.5)
202+
* Projection::scale(scale, scale + (1.0 / 8.0));
203+
warp_into(
204+
&head_orig_top.into_rgba8(),
205+
&head_top_skew,
206+
Interpolation::Nearest,
207+
Rgba([0, 0, 0, 0]),
208+
&mut scratch,
209+
);
210+
imageops::overlay(&mut render, &scratch, x_render_offset, z_render_offset);
211+
212+
// head front
213+
let head_front_skew =
214+
Projection::from_matrix([1.0, 0.0, 0.0, -SKEW_A, SKEW_B, SKEW_A, 0.0, 0.0, 1.0])
215+
.unwrap() * Projection::translate(
216+
x_offset + 7.5 * scale - 0.5,
217+
(x_offset + 8.0 * scale) + z_offset - 0.5,
218+
) * Projection::scale(scale, scale);
219+
warp_into(
220+
&head_orig_front.into_rgba8(),
221+
&head_front_skew,
222+
Interpolation::Nearest,
223+
Rgba([0, 0, 0, 0]),
224+
&mut scratch,
225+
);
226+
imageops::overlay(&mut render, &scratch, x_render_offset, z_render_offset);
227+
228+
// head right
229+
let head_right_skew =
230+
Projection::from_matrix([1.0, 0.0, 0.0, SKEW_A, SKEW_B, 0.0, 0.0, 0.0, 1.0]).unwrap()
231+
* Projection::translate(x_offset - (scale / 2.0), z_offset + scale)
232+
* Projection::scale(scale + (0.5 / 8.0), scale + (1.0 / 8.0));
233+
warp_into(
234+
&head_orig_right.into_rgba8(),
235+
&head_right_skew,
236+
Interpolation::Nearest,
237+
Rgba([0, 0, 0, 0]),
238+
&mut scratch,
239+
);
240+
imageops::overlay(&mut render, &scratch, x_render_offset, z_render_offset);
241+
242+
if options.armored {
243+
warp_into(
244+
&head_orig_top_overlay.into_rgba8(),
245+
&head_top_skew,
246+
Interpolation::Nearest,
247+
Rgba([0, 0, 0, 0]),
248+
&mut scratch,
249+
);
250+
imageops::overlay(&mut render, &scratch, x_render_offset, z_render_offset);
251+
252+
warp_into(
253+
&head_orig_front_overlay.into_rgba8(),
254+
&head_front_skew,
255+
Interpolation::Nearest,
256+
Rgba([0, 0, 0, 0]),
257+
&mut scratch,
258+
);
259+
imageops::overlay(&mut render, &scratch, x_render_offset, z_render_offset);
260+
261+
warp_into(
262+
&head_orig_right_overlay.into_rgba8(),
263+
&head_right_skew,
264+
Interpolation::Nearest,
265+
Rgba([0, 0, 0, 0]),
266+
&mut scratch,
267+
);
268+
imageops::overlay(&mut render, &scratch, x_render_offset, z_render_offset);
269+
}
270+
271+
DynamicImage::ImageRgba8(render)
272+
}
273+
}

0 commit comments

Comments
 (0)