@@ -3,7 +3,7 @@ use bevy_derive::EnumVariantMeta;
33use bevy_ecs:: resource:: Resource ;
44use bevy_math:: {
55 bounding:: { Aabb2d , Aabb3d , BoundingVolume } ,
6- vec2, Vec2 , Vec3 , Vec3A , Vec3Swizzles ,
6+ ops , vec2, vec3 , vec4 , Mat3 , Quat , Vec2 , Vec3 , Vec3A , Vec3Swizzles , Vec4 , Vec4Swizzles ,
77} ;
88#[ cfg( feature = "serialize" ) ]
99use bevy_platform:: collections:: HashMap ;
@@ -1155,8 +1155,10 @@ pub fn octahedral_encode_signed(v: Vec3) -> Vec2 {
11551155
11561156/// Encode tangent vectors as octahedral coordinates with range [-1, 1]. The sign is encoded in y component. Use [`octahedral_decode_tangent`] to decode.
11571157pub fn octahedral_encode_tangent ( v : Vec3 , sign : f32 ) -> Vec2 {
1158- // Bias to ensure that encoding as unorm16 preserves the sign. See https://github.com/godotengine/godot/pull/73265
1159- let bias = 1.0 / 32767.0 ;
1158+ // Bias to ensure that encoding as snorm16 preserves the sign.
1159+ let bits = 16. ;
1160+ let bias = 1. / ( ops:: powf ( 2.0 , bits - 1. ) - 1. ) ;
1161+
11601162 let mut n_xy = octahedral_encode_signed ( v) ;
11611163 // Map y to always be positive.
11621164 n_xy. y = n_xy. y * 0.5 + 0.5 ;
@@ -1184,12 +1186,60 @@ pub fn octahedral_decode_tangent(v: Vec2) -> (Vec3, f32) {
11841186 ( octahedral_decode_signed ( f) , sign)
11851187}
11861188
1189+ /// Convert the normal and tangent to the equivalent axis-angle representation.
1190+ /// The range of angle is [-2pi, 2pi], where the sign represents the handedness of the tangent.
1191+ pub fn normal_tangent_to_axis_angle ( normal : Vec3 , tangent_signed : Vec4 ) -> ( Vec3 , f32 ) {
1192+ // Bias to ensure that encoding as snorm16 preserves the sign.
1193+ let bits = 16. ;
1194+ let bias = 1. / ( ops:: powf ( 2.0 , bits - 1. ) - 1. ) ;
1195+
1196+ let tangent = tangent_signed. xyz ( ) ;
1197+ let bitangent = normal. cross ( tangent) ;
1198+ let ( axis, angle) =
1199+ Quat :: from_mat3 ( & Mat3 :: from_cols ( tangent, bitangent, normal) ) . to_axis_angle ( ) ;
1200+ let angle = angle
1201+ . rem_euclid ( 2.0 * core:: f32:: consts:: PI )
1202+ . max ( bias * 2.0 * core:: f32:: consts:: PI ) ;
1203+
1204+ ( axis, angle * tangent_signed. w )
1205+ }
1206+
1207+ /// Convert the axis-angle representation back to normal and tangent.
1208+ /// The range of angle is [-2pi, 2pi], where the sign represents the handedness of the tangent.
1209+ pub fn axis_angle_to_normal_tangent ( axis : Vec3 , angle : f32 ) -> ( Vec3 , Vec4 ) {
1210+ let sign = if angle >= 0.0 { 1.0 } else { -1.0 } ;
1211+ // References the source code of `Mat3::from_quat(Quat::from_axis_angle(axis, angle))`
1212+ let angle = angle * 0.5 * sign;
1213+ let c = ops:: cos ( angle) ;
1214+ let s = ops:: sin ( angle) ;
1215+ let v = axis * s;
1216+ let rotation = vec4 ( v. x , v. y , v. z , c) ;
1217+ let x2 = rotation. x + rotation. x ;
1218+ let y2 = rotation. y + rotation. y ;
1219+ let z2 = rotation. z + rotation. z ;
1220+ let xx = rotation. x * x2;
1221+ let xy = rotation. x * y2;
1222+ let xz = rotation. x * z2;
1223+ let yy = rotation. y * y2;
1224+ let yz = rotation. y * z2;
1225+ let zz = rotation. z * z2;
1226+ let wx = rotation. w * x2;
1227+ let wy = rotation. w * y2;
1228+ let wz = rotation. w * z2;
1229+
1230+ let tangent = vec3 ( 1.0 - ( yy + zz) , xy + wz, xz - wy) ;
1231+ let normal = vec3 ( xz + wy, yz - wx, 1.0 - ( xx + yy) ) ;
1232+
1233+ ( normal, tangent. extend ( sign) )
1234+ }
1235+
11871236#[ cfg( test) ]
11881237mod tests {
1189- use bevy_math:: { vec2, vec3, Vec4Swizzles } ;
1238+ use bevy_math:: { vec2, vec3, Mat3 , Quat , Vec3 , Vec4 , Vec4Swizzles } ;
11901239
11911240 use crate :: {
1192- octahedral_decode_signed, octahedral_decode_tangent,
1241+ axis_angle_to_normal_tangent, normal_tangent_to_axis_angle, octahedral_decode_signed,
1242+ octahedral_decode_tangent,
11931243 vertex:: { octahedral_encode_signed, octahedral_encode_tangent} ,
11941244 } ;
11951245
@@ -1198,18 +1248,21 @@ mod tests {
11981248 let vectors = [
11991249 vec3 ( 1.0 , 2.0 , 3.0 ) . normalize ( ) . extend ( 1.0 ) ,
12001250 vec3 ( 1.0 , 0.0 , 0.0 ) . extend ( -1.0 ) ,
1251+ vec3 ( 0.0 , 1.0 , 0.0 ) . extend ( -1.0 ) ,
12011252 vec3 ( 0.0 , 0.0 , -1.0 ) . extend ( 1.0 ) ,
12021253 vec3 ( 0.0 , 0.0 , -1.0 ) . extend ( -1.0 ) ,
12031254 ] ;
12041255 let expected_encoded_normals = [
12051256 vec2 ( 0.16666667 , 0.33333334 ) ,
12061257 vec2 ( 1.0 , 0.0 ) ,
1258+ vec2 ( 0.0 , 1.0 ) ,
12071259 vec2 ( -1.0 , -1.0 ) ,
12081260 vec2 ( -1.0 , -1.0 ) ,
12091261 ] ;
12101262 let expected_encoded_tangents = [
12111263 vec2 ( 0.16666667 , 0.6666667 ) ,
12121264 vec2 ( 1.0 , -0.5 ) ,
1265+ vec2 ( 0.0 , -1.0 ) ,
12131266 vec2 ( -1.0 , 3.051851e-5 ) ,
12141267 vec2 ( -1.0 , -3.051851e-5 ) ,
12151268 ] ;
@@ -1226,4 +1279,75 @@ mod tests {
12261279 assert ! ( decoded_tangent. distance( v. xyz( ) ) < 1e-4 ) ;
12271280 }
12281281 }
1282+
1283+ pub fn axis_angle_to_normal_tangent_glam ( axis : Vec3 , angle : f32 ) -> ( Vec3 , Vec4 ) {
1284+ let sign = if angle >= 0.0 { 1.0 } else { -1.0 } ;
1285+ let tbn = Mat3 :: from_quat ( Quat :: from_axis_angle ( axis, angle * sign) ) ;
1286+ ( tbn. col ( 2 ) , tbn. col ( 0 ) . extend ( sign) )
1287+ }
1288+
1289+ #[ test]
1290+ fn normal_tangent_axis_angle_encode_decode ( ) {
1291+ let normal_tangent = [
1292+ ( vec3 ( 1.0 , 0.0 , 0.0 ) , vec3 ( 0.0 , 0.0 , 1.0 ) . extend ( 1.0 ) ) ,
1293+ ( vec3 ( 1.0 , 0.0 , 0.0 ) , vec3 ( 0.0 , 0.0 , 1.0 ) . extend ( -1.0 ) ) ,
1294+ ( vec3 ( 0.0 , 0.0 , 1.0 ) , vec3 ( 1.0 , 0.0 , 0.0 ) . extend ( 1.0 ) ) ,
1295+ ( vec3 ( 0.0 , 0.0 , 1.0 ) , vec3 ( 1.0 , 0.0 , 0.0 ) . extend ( -1.0 ) ) ,
1296+ ( vec3 ( 0.0 , 1.0 , 0.0 ) , vec3 ( 0.0 , 0.0 , 1.0 ) . extend ( 1.0 ) ) ,
1297+ ( vec3 ( 1.0 , 0.0 , 0.0 ) , vec3 ( 0.0 , -1.0 , 0.0 ) . extend ( 1.0 ) ) ,
1298+ (
1299+ vec3 ( 1.0 , 1.0 , 1.0 ) . normalize ( ) ,
1300+ vec3 ( 1.0 , -1.0 , 0.0 ) . normalize ( ) . extend ( 1.0 ) ,
1301+ ) ,
1302+ (
1303+ vec3 ( 1.0 , 2.0 , 0.0 ) . normalize ( ) ,
1304+ vec3 ( 0.0 , 0.0 , 1.0 ) . extend ( -1.0 ) ,
1305+ ) ,
1306+ (
1307+ vec3 ( 0.0 , 1.0 , 1.0 ) . normalize ( ) ,
1308+ vec3 ( 1.0 , 0.0 , 0.0 ) . extend ( 1.0 ) ,
1309+ ) ,
1310+ (
1311+ vec3 ( -1.0 , 1.0 , 1.0 ) . normalize ( ) ,
1312+ vec3 ( 1.0 , 1.0 , 0.0 ) . normalize ( ) . extend ( -1.0 ) ,
1313+ ) ,
1314+ (
1315+ vec3 ( 3.0 , 1.0 , 2.0 ) . normalize ( ) ,
1316+ vec3 ( 0.0 , 1.0 , -0.5 ) . normalize ( ) . extend ( 1.0 ) ,
1317+ ) ,
1318+ ] ;
1319+
1320+ #[ expect(
1321+ clippy:: approx_constant,
1322+ reason = "The values are taken from the test results"
1323+ ) ]
1324+ let expected_axis_angle = [
1325+ ( vec3 ( 0.7071068 , 0.0 , 0.7071068 ) , 3.1415927 ) ,
1326+ ( vec3 ( 0.7071068 , 0.0 , 0.7071068 ) , -3.1415927 ) ,
1327+ ( vec3 ( 1.0 , 0.0 , 0.0 ) , 3.051851e-5 ) ,
1328+ ( vec3 ( 1.0 , 0.0 , 0.0 ) , -3.051851e-5 ) ,
1329+ ( vec3 ( 0.57735026 , 0.57735026 , 0.57735026 ) , 4.1887903 ) ,
1330+ ( vec3 ( 0.57735026 , -0.57735026 , 0.57735026 ) , 4.1887903 ) ,
1331+ ( vec3 ( -0.7429061 , 0.3077218 , -0.5944728 ) , 1.2171159 ) ,
1332+ ( vec3 ( 0.64793617 , 0.40044653 , 0.64793617 ) , -3.9033751 ) ,
1333+ ( vec3 ( -1.0 , 0.0 , 0.0 ) , 0.7853981 ) ,
1334+ ( vec3 ( -0.7429061 , -0.3077218 , 0.5944728 ) , -1.2171159 ) ,
1335+ ( vec3 ( 0.22525999 , 0.6253928 , 0.7470889 ) , 1.6242763 ) ,
1336+ ] ;
1337+
1338+ for ( i, & ( normal, tangent) ) in normal_tangent. iter ( ) . enumerate ( ) {
1339+ let ( axis, angle) = normal_tangent_to_axis_angle ( normal, tangent) ;
1340+ assert_eq ! ( angle. signum( ) , tangent. w. signum( ) ) ;
1341+ assert ! ( axis. distance( expected_axis_angle[ i] . 0 ) < 1e-8 ) ;
1342+
1343+ let ( decoded_normal, decoded_tangent) = axis_angle_to_normal_tangent ( axis, angle) ;
1344+ let ( decoded_normal_glam, decoded_tangent_glam) =
1345+ axis_angle_to_normal_tangent_glam ( axis, angle) ;
1346+
1347+ assert ! ( decoded_normal. distance( normal) < 1e-3 ) ;
1348+ assert ! ( decoded_tangent. distance( tangent) < 1e-3 ) ;
1349+ assert ! ( decoded_normal_glam. distance( normal) < 1e-3 ) ;
1350+ assert ! ( decoded_tangent_glam. distance( tangent) < 1e-3 ) ;
1351+ }
1352+ }
12291353}
0 commit comments