Live preview: glTF body UVs unflipped (top-left origin), equipment converted once; GPU probe + unit test; translate Skeleton header (Tim t1009u)
@@ -1847,7 +1847,7 @@ impl EditorApp {
1847
1847
)));
1848
1848
if let Some(reason) = body.and_then(PreviewCharacter::static_reason) {
1849
1849
ui.add_enabled_ui(false, |ui| {
1850
ui.collapsing("Skeleton / botten", |ui| {
1850
ui.collapsing(crate::i18n::t("Skeleton / botten"), |ui| {
1851
1851
ui.label(crate::i18n::t("Geen botten"));
1852
1852
});
1853
1853
});
@@ -1864,7 +1864,7 @@ impl EditorApp {
1864
1864
crate::i18n::t("Rigbinding ontbreekt of wijkt af; selecteer een bot."),
1865
1865
);
1866
1866
}
1867
ui.collapsing("Skeleton / botten", |ui| {
1867
ui.collapsing(crate::i18n::t("Skeleton / botten"), |ui| {
1868
1868
if let Some(joint) = bone_tree(ui, body, selected.as_deref()) {
1869
1869
if let Err(error) =
1870
1870
preset.bind(sex, body, self.equipment_part, joint)
@@ -1847,7 +1847,7 @@ impl EditorApp {1847
)));1847
)));1848
if let Some(reason) = body.and_then(PreviewCharacter::static_reason) {1848
if let Some(reason) = body.and_then(PreviewCharacter::static_reason) {1849
ui.add_enabled_ui(false, |ui| {1849
ui.add_enabled_ui(false, |ui| {1850
ui.collapsing("Skeleton / botten", |ui| {1850
ui.collapsing(crate::i18n::t("Skeleton / botten"), |ui| {1851
ui.label(crate::i18n::t("Geen botten"));1851
ui.label(crate::i18n::t("Geen botten"));1852
});1852
});1853
});1853
});@@ -1864,7 +1864,7 @@ impl EditorApp {1864
crate::i18n::t("Rigbinding ontbreekt of wijkt af; selecteer een bot."),1864
crate::i18n::t("Rigbinding ontbreekt of wijkt af; selecteer een bot."),1865
);1865
);1866
}1866
}1867
ui.collapsing("Skeleton / botten", |ui| {1867
ui.collapsing(crate::i18n::t("Skeleton / botten"), |ui| {1868
if let Some(joint) = bone_tree(ui, body, selected.as_deref()) {1868
if let Some(joint) = bone_tree(ui, body, selected.as_deref()) {1869
if let Err(error) =1869
if let Err(error) =1870
preset.bind(sex, body, self.equipment_part, joint)1870
preset.bind(sex, body, self.equipment_part, joint)@@ -813,6 +813,9 @@ pub const TABLE: &[(&str, &str)] = &[
813
813
("Instellingen", "Settings"),
814
814
("Taal", "Language"),
815
815
("Sluiten", "Close"),
816
("body-UV niet volgens glTF (v=0 boven): {body_uv:?}", "body UV not per glTF (v=0 at top): {body_uv:?}"),
817
("equipment-UV verkeerd georiënteerd: {equipment_uv:?}", "equipment UV wrongly oriented: {equipment_uv:?}"),
818
("GPU depth PASS: native {native_bits}-bit, front={near:?}, next-frame={next:?}, reversed={reverse:?}, behind-layer={hidden:?}, textured-front={textured:?}, alpha-cutout={cutout:?}, shiny={shiny:?}, matte={matte:?}, body-surface texture×factor={surface_front:?}, body-surface factor-only={surface_swap:?}, body-uv-top={body_uv:?}, equipment-uv-bottom={equipment_uv:?}", "GPU depth PASS: native {native_bits}-bit, front={near:?}, next-frame={next:?}, reversed={reverse:?}, behind-layer={hidden:?}, textured-front={textured:?}, alpha-cutout={cutout:?}, shiny={shiny:?}, matte={matte:?}, body-surface texture×factor={surface_front:?}, body-surface factor-only={surface_swap:?}, body-uv-top={body_uv:?}, equipment-uv-bottom={equipment_uv:?}"),
816
819
("Auto midden", "Auto centre"),
817
820
("Wis handmatige pivot; gebruik het midden van het model (rustpose)", "Clear manual pivot; use the model's centre (rest pose)"),
818
821
("Laad een glTF/GLB-, FBX- of OBJ-lichaam om de pivot te bepalen.", "Load a glTF/GLB, FBX or OBJ body to determine the pivot."),
@@ -813,6 +813,9 @@ pub const TABLE: &[(&str, &str)] = &[813
("Instellingen", "Settings"),813
("Instellingen", "Settings"),814
("Taal", "Language"),814
("Taal", "Language"),815
("Sluiten", "Close"),815
("Sluiten", "Close"),816
("body-UV niet volgens glTF (v=0 boven): {body_uv:?}", "body UV not per glTF (v=0 at top): {body_uv:?}"),817
("equipment-UV verkeerd georiënteerd: {equipment_uv:?}", "equipment UV wrongly oriented: {equipment_uv:?}"),818
("GPU depth PASS: native {native_bits}-bit, front={near:?}, next-frame={next:?}, reversed={reverse:?}, behind-layer={hidden:?}, textured-front={textured:?}, alpha-cutout={cutout:?}, shiny={shiny:?}, matte={matte:?}, body-surface texture×factor={surface_front:?}, body-surface factor-only={surface_swap:?}, body-uv-top={body_uv:?}, equipment-uv-bottom={equipment_uv:?}", "GPU depth PASS: native {native_bits}-bit, front={near:?}, next-frame={next:?}, reversed={reverse:?}, behind-layer={hidden:?}, textured-front={textured:?}, alpha-cutout={cutout:?}, shiny={shiny:?}, matte={matte:?}, body-surface texture×factor={surface_front:?}, body-surface factor-only={surface_swap:?}, body-uv-top={body_uv:?}, equipment-uv-bottom={equipment_uv:?}"),816
("Auto midden", "Auto centre"),819
("Auto midden", "Auto centre"),817
("Wis handmatige pivot; gebruik het midden van het model (rustpose)", "Clear manual pivot; use the model's centre (rest pose)"),820
("Wis handmatige pivot; gebruik het midden van het model (rustpose)", "Clear manual pivot; use the model's centre (rest pose)"),818
("Laad een glTF/GLB-, FBX- of OBJ-lichaam om de pivot te bepalen.", "Load a glTF/GLB, FBX or OBJ body to determine the pivot."),821
("Laad een glTF/GLB-, FBX- of OBJ-lichaam om de pivot te bepalen.", "Load a glTF/GLB, FBX or OBJ body to determine the pivot."),@@ -442,7 +442,9 @@ impl PendingFrame {
442
442
positions: layer.mesh.positions,
443
443
normals: layer.mesh.normals,
444
444
indices: layer.mesh.indices,
445
uvs: layer.mesh.uvs,
445
// Equipment meshes keep the OBJ/FBX bottom-left UV origin; the GPU
446
// path uses the glTF top-left origin (row 0 = top), like production.
447
uvs: layer.mesh.uvs.iter().map(|[u, v]| [*u, 1. - *v]).collect(),
446
448
color: layer.mesh.color,
447
449
joints: Vec::new(),
448
450
weights: Vec::new(),
@@ -844,7 +846,7 @@ void main() {
844
846
+ a_weights.z * u_bones[j2]
845
847
+ a_weights.w * u_bones[j3];
846
848
vec4 world = u_model * skin * vec4(a_pos, 1.0);
847
v_uv = vec2(a_uv.x,1.0-a_uv.y); v_world=world.xyz;
849
v_uv = a_uv; v_world=world.xyz;
848
850
v_normal = transpose(inverse(mat3(u_model * skin))) * a_normal;
849
851
gl_Position = u_view_proj * world;
850
852
}
@@ -1145,7 +1147,47 @@ pub unsafe fn depth_regression_probe(gl: &glow::Context) -> Result<(), String> {
1145
1147
"materiaal per primitive verwisseld: {surface_swap:?}"
1146
1148
));
1147
1149
}
1148
println!("GPU depth PASS: native {native_bits}-bit, front={near:?}, next-frame={next:?}, reversed={reverse:?}, behind-layer={hidden:?}, textured-front={textured:?}, alpha-cutout={cutout:?}, shiny={shiny:?}, matte={matte:?}, body-surface texture×factor={surface_front:?}, body-surface factor-only={surface_swap:?}");
1150
// UV origin (Tim t1009u): body UVs are glTF (v = 0 is the top row of
1151
// the image), equipment UVs are bottom-left (v = 0 is the bottom row).
1152
let red_top_blue_bottom = Arc::new(crate::equipment::Texture {
1153
content_hash: 6,
1154
width: 1,
1155
height: 2,
1156
rgba: vec![255, 0, 0, 255, 0, 0, 255, 255],
1157
});
1158
let mut body = make_frame(vec![0, 1, 2], 14);
1159
body.uvs = vec![[0.5, 0.25]; 6];
1160
body.surfaces = vec![SurfaceDraw {
1161
first: 0,
1162
count: 3,
1163
color: [1.; 4],
1164
albedo: Some(red_top_blue_bottom.clone()),
1165
cutoff: 0.001,
1166
specular: 0.,
1167
shiny: None,
1168
}];
1169
cache.pending = Some(body);
1170
cache.paint(gl, &info, true);
1171
let body_uv = read();
1172
if body_uv[0] < 250 || body_uv[2] > 2 {
1173
return Err(format!(
1174
"body-UV niet volgens glTF (v=0 boven): {body_uv:?}"
1175
));
1176
}
1177
let mut body = make_frame(vec![3, 4, 5], 15);
1178
let mut front = make_frame(vec![0, 1, 2], 16);
1179
front.uvs = vec![[0.5, 0.75]; 6]; // already converted from bottom-left v = 0.25
1180
front.albedo = Some(red_top_blue_bottom);
1181
body.equipment = vec![front];
1182
cache.pending = Some(body);
1183
cache.paint(gl, &info, true);
1184
let equipment_uv = read();
1185
if equipment_uv[2] < 250 || equipment_uv[0] > 2 {
1186
return Err(format!(
1187
"equipment-UV verkeerd georiënteerd: {equipment_uv:?}"
1188
));
1189
}
1190
println!("GPU depth PASS: native {native_bits}-bit, front={near:?}, next-frame={next:?}, reversed={reverse:?}, behind-layer={hidden:?}, textured-front={textured:?}, alpha-cutout={cutout:?}, shiny={shiny:?}, matte={matte:?}, body-surface texture×factor={surface_front:?}, body-surface factor-only={surface_swap:?}, body-uv-top={body_uv:?}, equipment-uv-bottom={equipment_uv:?}");
1149
1191
Ok(())
1150
1192
})();
1151
1193
cache.destroy(gl);
@@ -1177,6 +1219,8 @@ mod tests {
1177
1219
);
1178
1220
assert_eq!(frame.surfaces.len(), 3);
1179
1221
assert_eq!(frame.uvs.len(), frame.positions.len());
1222
// glTF UVs reach the GPU unchanged (top-left origin, no V flip).
1223
assert_eq!(frame.uvs, body.uvs);
1180
1224
let ranges: Vec<_> = frame.surfaces.iter().map(|s| (s.first, s.count)).collect();
1181
1225
assert_eq!(ranges, [(0, 6), (6, 6), (12, 6)]);
1182
1226
assert_eq!(frame.surfaces[0].color, [0.2, 0.4, 0.9, 1.0]);
@@ -442,7 +442,9 @@ impl PendingFrame {442
positions: layer.mesh.positions,442
positions: layer.mesh.positions,443
normals: layer.mesh.normals,443
normals: layer.mesh.normals,444
indices: layer.mesh.indices,444
indices: layer.mesh.indices,445
uvs: layer.mesh.uvs,445
// Equipment meshes keep the OBJ/FBX bottom-left UV origin; the GPU446
// path uses the glTF top-left origin (row 0 = top), like production.447
uvs: layer.mesh.uvs.iter().map(|[u, v]| [*u, 1. - *v]).collect(),446
color: layer.mesh.color,448
color: layer.mesh.color,447
joints: Vec::new(),449
joints: Vec::new(),448
weights: Vec::new(),450
weights: Vec::new(),@@ -844,7 +846,7 @@ void main() {844
+ a_weights.z * u_bones[j2]846
+ a_weights.z * u_bones[j2]845
+ a_weights.w * u_bones[j3];847
+ a_weights.w * u_bones[j3];846
vec4 world = u_model * skin * vec4(a_pos, 1.0);848
vec4 world = u_model * skin * vec4(a_pos, 1.0);847
v_uv = vec2(a_uv.x,1.0-a_uv.y); v_world=world.xyz;849
v_uv = a_uv; v_world=world.xyz;848
v_normal = transpose(inverse(mat3(u_model * skin))) * a_normal;850
v_normal = transpose(inverse(mat3(u_model * skin))) * a_normal;849
gl_Position = u_view_proj * world;851
gl_Position = u_view_proj * world;850
}852
}@@ -1145,7 +1147,47 @@ pub unsafe fn depth_regression_probe(gl: &glow::Context) -> Result<(), String> {1145
"materiaal per primitive verwisseld: {surface_swap:?}"1147
"materiaal per primitive verwisseld: {surface_swap:?}"1146
));1148
));1147
}1149
}1148
println!("GPU depth PASS: native {native_bits}-bit, front={near:?}, next-frame={next:?}, reversed={reverse:?}, behind-layer={hidden:?}, textured-front={textured:?}, alpha-cutout={cutout:?}, shiny={shiny:?}, matte={matte:?}, body-surface texture×factor={surface_front:?}, body-surface factor-only={surface_swap:?}");1150
// UV origin (Tim t1009u): body UVs are glTF (v = 0 is the top row of1151
// the image), equipment UVs are bottom-left (v = 0 is the bottom row).1152
let red_top_blue_bottom = Arc::new(crate::equipment::Texture {1153
content_hash: 6,1154
width: 1,1155
height: 2,1156
rgba: vec![255, 0, 0, 255, 0, 0, 255, 255],1157
});1158
let mut body = make_frame(vec![0, 1, 2], 14);1159
body.uvs = vec![[0.5, 0.25]; 6];1160
body.surfaces = vec![SurfaceDraw {1161
first: 0,1162
count: 3,1163
color: [1.; 4],1164
albedo: Some(red_top_blue_bottom.clone()),1165
cutoff: 0.001,1166
specular: 0.,1167
shiny: None,1168
}];1169
cache.pending = Some(body);1170
cache.paint(gl, &info, true);1171
let body_uv = read();1172
if body_uv[0] < 250 || body_uv[2] > 2 {1173
return Err(format!(1174
"body-UV niet volgens glTF (v=0 boven): {body_uv:?}"1175
));1176
}1177
let mut body = make_frame(vec![3, 4, 5], 15);1178
let mut front = make_frame(vec![0, 1, 2], 16);1179
front.uvs = vec![[0.5, 0.75]; 6]; // already converted from bottom-left v = 0.251180
front.albedo = Some(red_top_blue_bottom);1181
body.equipment = vec![front];1182
cache.pending = Some(body);1183
cache.paint(gl, &info, true);1184
let equipment_uv = read();1185
if equipment_uv[2] < 250 || equipment_uv[0] > 2 {1186
return Err(format!(1187
"equipment-UV verkeerd georiënteerd: {equipment_uv:?}"1188
));1189
}1190
println!("GPU depth PASS: native {native_bits}-bit, front={near:?}, next-frame={next:?}, reversed={reverse:?}, behind-layer={hidden:?}, textured-front={textured:?}, alpha-cutout={cutout:?}, shiny={shiny:?}, matte={matte:?}, body-surface texture×factor={surface_front:?}, body-surface factor-only={surface_swap:?}, body-uv-top={body_uv:?}, equipment-uv-bottom={equipment_uv:?}");1149
Ok(())1191
Ok(())1150
})();1192
})();1151
cache.destroy(gl);1193
cache.destroy(gl);@@ -1177,6 +1219,8 @@ mod tests {1177
);1219
);1178
assert_eq!(frame.surfaces.len(), 3);1220
assert_eq!(frame.surfaces.len(), 3);1179
assert_eq!(frame.uvs.len(), frame.positions.len());1221
assert_eq!(frame.uvs.len(), frame.positions.len());1222
// glTF UVs reach the GPU unchanged (top-left origin, no V flip).1223
assert_eq!(frame.uvs, body.uvs);1180
let ranges: Vec<_> = frame.surfaces.iter().map(|s| (s.first, s.count)).collect();1224
let ranges: Vec<_> = frame.surfaces.iter().map(|s| (s.first, s.count)).collect();1181
assert_eq!(ranges, [(0, 6), (6, 6), (12, 6)]);1225
assert_eq!(ranges, [(0, 6), (6, 6), (12, 6)]);1182
assert_eq!(frame.surfaces[0].color, [0.2, 0.4, 0.9, 1.0]);1226
assert_eq!(frame.surfaces[0].color, [0.2, 0.4, 0.9, 1.0]);