summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2026-09-16 12:42:45 +0200
committerHampusM <hampus@hampusmat.com>2026-09-16 12:42:45 +0200
commitf85f5afc5a4a50c5c87a236bc76f8a08d50e7137 (patch)
tree335f55580ec706cd00ee937995355252b3304dfd
parentedd3261058c3417cf04f95dbfbc51a64e35426aa (diff)
fix(engine): make textures not flipped
-rw-r--r--engine/res/imgui_shader.slang2
-rw-r--r--engine/res/main_3d_shader.slang2
-rw-r--r--engine/src/file_format/wavefront/obj.rs43
-rw-r--r--engine/src/mesh.rs179
-rw-r--r--engine/src/mesh/vertex_buffer.rs4
-rw-r--r--engine/src/model/asset.rs42
-rw-r--r--engine/src/rendering/shader.rs30
-rw-r--r--engine/src/ui/dear_imgui.rs4
8 files changed, 235 insertions, 71 deletions
diff --git a/engine/res/imgui_shader.slang b/engine/res/imgui_shader.slang
index 29ad9bf..0277226 100644
--- a/engine/res/imgui_shader.slang
+++ b/engine/res/imgui_shader.slang
@@ -2,7 +2,7 @@ struct Vertex
{
float3 pos : STD_POSITION;
float4 color : STD_COLOR;
- float2 texture_coords : STD_UV;
+ float2 texture_coords : STD_UV_FROM_TOP_LEFT;
};
struct VertexData
diff --git a/engine/res/main_3d_shader.slang b/engine/res/main_3d_shader.slang
index 7da164c..d1a1ef3 100644
--- a/engine/res/main_3d_shader.slang
+++ b/engine/res/main_3d_shader.slang
@@ -225,7 +225,7 @@ struct VertexStageOutput
struct Vertex
{
float3 pos : STD_POSITION;
- float2 texture_coords : STD_UV;
+ float2 texture_coords : STD_UV_FROM_TOP_LEFT;
float3 normal : STD_NORMAL;
};
diff --git a/engine/src/file_format/wavefront/obj.rs b/engine/src/file_format/wavefront/obj.rs
index bebe291..dbcee33 100644
--- a/engine/src/file_format/wavefront/obj.rs
+++ b/engine/src/file_format/wavefront/obj.rs
@@ -92,7 +92,7 @@ impl Obj
/// - A face's texture position cannot be found
/// - A face's vertex normal cannot be found
/// - A face index does not fit in a [`u32`]
- pub fn to_mesh(&self) -> Result<Mesh, Error>
+ pub fn to_mesh(&self, options: ToMeshOptions) -> Result<Mesh, Error>
{
let mut vertex_buf = MeshVertexBuffer::with_capacity(
&[
@@ -101,7 +101,7 @@ impl Obj
ty: VertexAttrType::Float32Array { length: 3 },
},
VertexAttrInfo {
- label: VertexLabel::Uv,
+ label: VertexLabel::UvFromTopLeft,
ty: VertexAttrType::Float32Array { length: 2 },
},
VertexAttrInfo {
@@ -154,6 +154,11 @@ impl Obj
},
)?;
+ let texture_pos = options
+ .y_flip_uvs
+ .then(|| Vec2 { x: texture_pos.x, y: -texture_pos.y })
+ .unwrap_or(texture_pos);
+
let normal = face_vertex.normal.map_or_else(
|| {
if !self.vertex_normals.is_empty() {
@@ -181,7 +186,7 @@ impl Obj
value: pos.into_array(),
},
NamedVertexAttr {
- label: VertexLabel::Uv,
+ label: VertexLabel::UvFromTopLeft,
value: texture_pos.into_array(),
},
NamedVertexAttr {
@@ -437,6 +442,38 @@ impl Obj
}
}
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct ToMeshOptions
+{
+ /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs
+ /// have their origin at the bottom left corner of the image
+ ///
+ /// The default is `true`.
+ pub y_flip_uvs: bool,
+}
+
+impl ToMeshOptions
+{
+ /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs have
+ /// their origin at the bottom left corner of the image
+ ///
+ /// The default is `true`.
+ pub fn y_flip_uvs(mut self, y_flip_uvs: bool) -> Self
+ {
+ self.y_flip_uvs = y_flip_uvs;
+ self
+ }
+}
+
+impl Default for ToMeshOptions
+{
+ fn default() -> Self
+ {
+ Self { y_flip_uvs: true }
+ }
+}
+
#[derive(Debug)]
#[non_exhaustive]
pub struct Face
diff --git a/engine/src/mesh.rs b/engine/src/mesh.rs
index fff5804..c1c0f77 100644
--- a/engine/src/mesh.rs
+++ b/engine/src/mesh.rs
@@ -28,33 +28,33 @@ impl Mesh
size,
top_face:
CubeFaceOptions {
- uv_max: top_uv_max,
- uv_min: top_uv_min,
+ uv_upper_left: top_uv_upper_left,
+ uv_lower_right: top_uv_lower_right,
},
bottom_face:
CubeFaceOptions {
- uv_max: bot_uv_max,
- uv_min: bot_uv_min,
+ uv_upper_left: bot_uv_upper_left,
+ uv_lower_right: bot_uv_lower_right,
},
left_face:
CubeFaceOptions {
- uv_max: left_uv_max,
- uv_min: left_uv_min,
+ uv_upper_left: left_uv_upper_left,
+ uv_lower_right: left_uv_lower_right,
},
right_face:
CubeFaceOptions {
- uv_max: right_uv_max,
- uv_min: right_uv_min,
+ uv_upper_left: right_uv_upper_left,
+ uv_lower_right: right_uv_lower_right,
},
back_face:
CubeFaceOptions {
- uv_max: back_uv_max,
- uv_min: back_uv_min,
+ uv_upper_left: back_uv_upper_left,
+ uv_lower_right: back_uv_lower_right,
},
front_face:
CubeFaceOptions {
- uv_max: front_uv_max,
- uv_min: front_uv_min,
+ uv_upper_left: front_uv_upper_left,
+ uv_lower_right: front_uv_lower_right,
},
} = options;
@@ -74,50 +74,122 @@ impl Mesh
}
let top_vertices = [
- ([half_w, half_h, half_d], [top_uv_max.x, top_uv_max.y]),
- ([-half_w, half_h, half_d], [top_uv_min.x, top_uv_max.y]),
- ([half_w, half_h, -half_d], [top_uv_max.x, top_uv_min.y]),
- ([-half_w, half_h, -half_d], [top_uv_min.x, top_uv_min.y]),
+ (
+ [half_w, half_h, half_d],
+ [top_uv_upper_left.x, top_uv_upper_left.y],
+ ),
+ (
+ [-half_w, half_h, half_d],
+ [top_uv_lower_right.x, top_uv_upper_left.y],
+ ),
+ (
+ [half_w, half_h, -half_d],
+ [top_uv_upper_left.x, top_uv_lower_right.y],
+ ),
+ (
+ [-half_w, half_h, -half_d],
+ [top_uv_lower_right.x, top_uv_lower_right.y],
+ ),
]
.map(|(pos, uv)| vertex(pos, uv, Vec3::UP));
let bottom_vertices = [
- ([half_w, -half_h, half_d], [bot_uv_max.x, bot_uv_max.y]),
- ([-half_w, -half_h, half_d], [bot_uv_min.x, bot_uv_max.y]),
- ([half_w, -half_h, -half_d], [bot_uv_max.x, bot_uv_min.y]),
- ([-half_w, -half_h, -half_d], [bot_uv_min.x, bot_uv_min.y]),
+ (
+ [half_w, -half_h, half_d],
+ [bot_uv_upper_left.x, bot_uv_upper_left.y],
+ ),
+ (
+ [-half_w, -half_h, half_d],
+ [bot_uv_lower_right.x, bot_uv_upper_left.y],
+ ),
+ (
+ [half_w, -half_h, -half_d],
+ [bot_uv_upper_left.x, bot_uv_lower_right.y],
+ ),
+ (
+ [-half_w, -half_h, -half_d],
+ [bot_uv_lower_right.x, bot_uv_lower_right.y],
+ ),
]
.map(|(pos, uv)| vertex(pos, uv, Vec3::DOWN));
let left_vertices = [
- ([-half_w, half_h, half_d], [left_uv_max.x, left_uv_max.y]),
- ([-half_w, half_h, -half_d], [left_uv_min.x, left_uv_max.y]),
- ([-half_w, -half_h, half_d], [left_uv_max.x, left_uv_min.y]),
- ([-half_w, -half_h, -half_d], [left_uv_min.x, left_uv_min.y]),
+ (
+ [-half_w, half_h, half_d],
+ [left_uv_upper_left.x, left_uv_upper_left.y],
+ ),
+ (
+ [-half_w, half_h, -half_d],
+ [left_uv_lower_right.x, left_uv_upper_left.y],
+ ),
+ (
+ [-half_w, -half_h, half_d],
+ [left_uv_upper_left.x, left_uv_lower_right.y],
+ ),
+ (
+ [-half_w, -half_h, -half_d],
+ [left_uv_lower_right.x, left_uv_lower_right.y],
+ ),
]
.map(|(pos, uv)| vertex(pos, uv, Vec3::LEFT));
let right_vertices = [
- ([half_w, half_h, half_d], [right_uv_max.x, right_uv_max.y]),
- ([half_w, half_h, -half_d], [right_uv_min.x, right_uv_max.y]),
- ([half_w, -half_h, half_d], [right_uv_max.x, right_uv_min.y]),
- ([half_w, -half_h, -half_d], [right_uv_min.x, right_uv_min.y]),
+ (
+ [half_w, half_h, half_d],
+ [right_uv_upper_left.x, right_uv_upper_left.y],
+ ),
+ (
+ [half_w, half_h, -half_d],
+ [right_uv_lower_right.x, right_uv_upper_left.y],
+ ),
+ (
+ [half_w, -half_h, half_d],
+ [right_uv_upper_left.x, right_uv_lower_right.y],
+ ),
+ (
+ [half_w, -half_h, -half_d],
+ [right_uv_lower_right.x, right_uv_lower_right.y],
+ ),
]
.map(|(pos, uv)| vertex(pos, uv, Vec3::RIGHT));
let back_vertices = [
- ([half_w, half_h, -half_d], [back_uv_max.x, back_uv_max.y]),
- ([-half_w, half_h, -half_d], [back_uv_min.x, back_uv_max.y]),
- ([half_w, -half_h, -half_d], [back_uv_max.x, back_uv_min.y]),
- ([-half_w, -half_h, -half_d], [back_uv_min.x, back_uv_min.y]),
+ (
+ [half_w, half_h, -half_d],
+ [back_uv_upper_left.x, back_uv_upper_left.y],
+ ),
+ (
+ [-half_w, half_h, -half_d],
+ [back_uv_lower_right.x, back_uv_upper_left.y],
+ ),
+ (
+ [half_w, -half_h, -half_d],
+ [back_uv_upper_left.x, back_uv_lower_right.y],
+ ),
+ (
+ [-half_w, -half_h, -half_d],
+ [back_uv_lower_right.x, back_uv_lower_right.y],
+ ),
]
.map(|(pos, uv)| vertex(pos, uv, Vec3::BACK));
let front_vertices = [
- ([half_w, half_h, half_d], [front_uv_max.x, front_uv_max.y]),
- ([-half_w, half_h, half_d], [front_uv_min.x, front_uv_max.y]),
- ([half_w, -half_h, half_d], [front_uv_max.x, front_uv_min.y]),
- ([-half_w, -half_h, half_d], [front_uv_min.x, front_uv_min.y]),
+ (
+ [half_w, half_h, half_d],
+ [front_uv_upper_left.x, front_uv_upper_left.y],
+ ),
+ (
+ [-half_w, half_h, half_d],
+ [front_uv_lower_right.x, front_uv_upper_left.y],
+ ),
+ (
+ [half_w, -half_h, half_d],
+ [front_uv_upper_left.x, front_uv_lower_right.y],
+ ),
+ (
+ [-half_w, -half_h, half_d],
+ [front_uv_lower_right.x, front_uv_lower_right.y],
+ ),
]
.map(|(pos, uv)| vertex(pos, uv, Vec3::FRONT));
@@ -132,7 +204,7 @@ impl Mesh
byte_offset: 0,
},
VertexAttrProperties {
- label: VertexLabel::Uv,
+ label: VertexLabel::UvFromTopLeft,
ty: VertexAttrType::Float32Array { length: 2 },
layout: Layout::new::<[f32; 2]>(),
byte_offset: size_of::<[f32; 3]>(),
@@ -378,21 +450,38 @@ impl Default for CubeOptions
#[non_exhaustive]
pub struct CubeFaceOptions
{
- pub uv_max: Vec2<f32>,
- pub uv_min: Vec2<f32>,
+ /// Seeing the face head on, this is the texture coordinate for the corner that
+ /// would be on the upper left of the face.
+ ///
+ /// The default is `(0, 0)`
+ pub uv_upper_left: Vec2<f32>,
+
+ /// Seeing the face head on, this is the texture coordinate for the corner that
+ /// would be on the lower right of the face.
+ ///
+ /// The default is `(1, 1)`
+ pub uv_lower_right: Vec2<f32>,
}
impl CubeFaceOptions
{
- pub fn uv_max(mut self, uv_max: Vec2<f32>) -> Self
+ /// Seeing the face head on, this is the texture coordinate for the corner that
+ /// would be on the upper left of the face.
+ ///
+ /// The default is `(0, 0)`
+ pub fn uv_upper_left(mut self, uv_upper_left: Vec2<f32>) -> Self
{
- self.uv_max = uv_max;
+ self.uv_upper_left = uv_upper_left;
self
}
- pub fn uv_min(mut self, uv_min: Vec2<f32>) -> Self
+ /// Seeing the face head on, this is the texture coordinate for the corner that
+ /// would be on the lower right of the face.
+ ///
+ /// The default is `(1, 1)`
+ pub fn uv_lower_right(mut self, uv_lower_right: Vec2<f32>) -> Self
{
- self.uv_min = uv_min;
+ self.uv_lower_right = uv_lower_right;
self
}
}
@@ -402,8 +491,8 @@ impl Default for CubeFaceOptions
fn default() -> Self
{
Self {
- uv_max: Vec2 { x: 1.0, y: 1.0 },
- uv_min: Vec2 { x: 0.0, y: 0.0 },
+ uv_upper_left: Vec2 { x: 0.0, y: 0.0 },
+ uv_lower_right: Vec2 { x: 1.0, y: 1.0 },
}
}
}
diff --git a/engine/src/mesh/vertex_buffer.rs b/engine/src/mesh/vertex_buffer.rs
index 90e5752..5c03b45 100644
--- a/engine/src/mesh/vertex_buffer.rs
+++ b/engine/src/mesh/vertex_buffer.rs
@@ -58,7 +58,9 @@ pub enum VertexLabel
{
Position,
Normal,
- Uv,
+
+ /// Texture coordinate with (0, 0) being at the top left.
+ UvFromTopLeft,
Color,
Other(Cow<'static, str>),
}
diff --git a/engine/src/model/asset.rs b/engine/src/model/asset.rs
index 69a06aa..5487203 100644
--- a/engine/src/model/asset.rs
+++ b/engine/src/model/asset.rs
@@ -7,7 +7,35 @@ use crate::model::{Materials, Spec};
#[derive(Debug, Clone)]
#[non_exhaustive]
-pub struct Settings {}
+pub struct Settings
+{
+ /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs
+ /// have their origin at the bottom left corner of the image
+ ///
+ /// The default is `true`.
+ pub y_flip_uvs: bool,
+}
+
+impl Settings
+{
+ /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs have
+ /// their origin at the bottom left corner of the image
+ ///
+ /// The default is `true`.
+ pub fn y_flip_uvs(mut self, y_flip_uvs: bool) -> Self
+ {
+ self.y_flip_uvs = y_flip_uvs;
+ self
+ }
+}
+
+impl Default for Settings
+{
+ fn default() -> Self
+ {
+ Self { y_flip_uvs: true }
+ }
+}
pub fn add_importers(assets: &mut Assets)
{
@@ -17,9 +45,14 @@ pub fn add_importers(assets: &mut Assets)
fn import_wavefront_obj_asset(
asset_submitter: &mut AssetSubmitter<'_>,
path: &Path,
- _settings: Option<&'_ Settings>,
+ settings: Option<&Settings>,
) -> Result<(), Error>
{
+ let settings = match settings {
+ Some(settings) => settings,
+ None => &Settings::default(),
+ };
+
let parent_path = path
.parent()
.ok_or_else(|| Error::InvalidPath(path.to_path_buf()))?;
@@ -29,7 +62,10 @@ fn import_wavefront_obj_asset(
.map_err(|err| Error::ReadFailed(err, path.to_path_buf()))?,
)?;
- let mesh = obj.to_mesh()?;
+ let mesh = obj.to_mesh(
+ crate::file_format::wavefront::obj::ToMeshOptions::default()
+ .y_flip_uvs(settings.y_flip_uvs),
+ )?;
let mesh_asset = asset_submitter.submit_store_named("mesh", mesh);
diff --git a/engine/src/rendering/shader.rs b/engine/src/rendering/shader.rs
index 12fc9c1..3f07381 100644
--- a/engine/src/rendering/shader.rs
+++ b/engine/src/rendering/shader.rs
@@ -38,10 +38,10 @@ use crate::mesh::vertex_buffer::VertexLabel;
pub mod cursor;
-pub const STD_VERTEX_INPUT_SEMANTIC_NAME_POSITION: &str = "STD_POSITION";
-pub const STD_VERTEX_INPUT_SEMANTIC_NAME_NORMAL: &str = "STD_NORMAL";
-pub const STD_VERTEX_INPUT_SEMANTIC_NAME_UV: &str = "STD_UV";
-pub const STD_VERTEX_INPUT_SEMANTIC_NAME_COLOR: &str = "STD_COLOR";
+pub const STD_VERT_IN_SEM_NAME_POSITION: &str = "STD_POSITION";
+pub const STD_VERT_IN_SEM_NAME_NORMAL: &str = "STD_NORMAL";
+pub const STD_VERT_IN_SEM_NAME_UV_FROM_TOP_LEFT: &str = "STD_UV_FROM_TOP_LEFT";
+pub const STD_VERT_IN_SEM_NAME_COLOR: &str = "STD_COLOR";
/// Shader module.
#[derive(Debug)]
@@ -1054,7 +1054,7 @@ pub enum VertexInputSemName
{
Position,
Normal,
- Uv,
+ UvFromTopLeft,
Color,
Other(Box<str>),
}
@@ -1064,10 +1064,10 @@ impl VertexInputSemName
fn from_semantic_name(semantic_name: &str) -> Self
{
match semantic_name {
- STD_VERTEX_INPUT_SEMANTIC_NAME_POSITION => Self::Position,
- STD_VERTEX_INPUT_SEMANTIC_NAME_NORMAL => Self::Normal,
- STD_VERTEX_INPUT_SEMANTIC_NAME_UV => Self::Uv,
- STD_VERTEX_INPUT_SEMANTIC_NAME_COLOR => Self::Color,
+ STD_VERT_IN_SEM_NAME_POSITION => Self::Position,
+ STD_VERT_IN_SEM_NAME_NORMAL => Self::Normal,
+ STD_VERT_IN_SEM_NAME_UV_FROM_TOP_LEFT => Self::UvFromTopLeft,
+ STD_VERT_IN_SEM_NAME_COLOR => Self::Color,
_ => Self::Other(semantic_name.to_lowercase().into_boxed_str()),
}
}
@@ -1077,7 +1077,7 @@ impl VertexInputSemName
match (self, vertex_label) {
(Self::Position, VertexLabel::Position)
| (Self::Normal, VertexLabel::Normal)
- | (Self::Uv, VertexLabel::Uv)
+ | (Self::UvFromTopLeft, VertexLabel::UvFromTopLeft)
| (Self::Color, VertexLabel::Color) => true,
(Self::Other(other), VertexLabel::Other(other_vertex_label)) => {
**other == *other_vertex_label
@@ -1092,12 +1092,12 @@ impl Display for VertexInputSemName
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
match self {
- Self::Position => {
- formatter.write_str(STD_VERTEX_INPUT_SEMANTIC_NAME_POSITION)
+ Self::Position => formatter.write_str(STD_VERT_IN_SEM_NAME_POSITION),
+ Self::Normal => formatter.write_str(STD_VERT_IN_SEM_NAME_NORMAL),
+ Self::UvFromTopLeft => {
+ formatter.write_str(STD_VERT_IN_SEM_NAME_UV_FROM_TOP_LEFT)
}
- Self::Normal => formatter.write_str(STD_VERTEX_INPUT_SEMANTIC_NAME_NORMAL),
- Self::Uv => formatter.write_str(STD_VERTEX_INPUT_SEMANTIC_NAME_UV),
- Self::Color => formatter.write_str(STD_VERTEX_INPUT_SEMANTIC_NAME_COLOR),
+ Self::Color => formatter.write_str(STD_VERT_IN_SEM_NAME_COLOR),
Self::Other(other) => {
for character in other.chars() {
formatter.write_char(character.to_ascii_uppercase())?;
diff --git a/engine/src/ui/dear_imgui.rs b/engine/src/ui/dear_imgui.rs
index 4da5c5f..0a50ff7 100644
--- a/engine/src/ui/dear_imgui.rs
+++ b/engine/src/ui/dear_imgui.rs
@@ -395,7 +395,7 @@ fn initialize_context(
ty: MeshVertexAttrType::Float32Array { length: 4 },
},
MeshVertexAttrInfo {
- label: VertexLabel::Uv,
+ label: VertexLabel::UvFromTopLeft,
ty: MeshVertexAttrType::Float32Array { length: 2 },
},
],
@@ -718,7 +718,7 @@ fn add_drawing_render_pass(
value: vertex.rgba().map(|elem| (elem as f32) / 255.0),
},
MeshNamedVertexAttr {
- label: VertexLabel::Uv,
+ label: VertexLabel::UvFromTopLeft,
value: vertex.uv,
},
));