@@ -24,7 +24,7 @@ public static void interpolate(MutableQuadView quad, Sprite oldSprite, Sprite ne
2424 }
2525 }
2626
27- public static void assignLerpedUVs (MutableQuadView quad , Sprite sprite ) {
27+ public static void assignLerpedUvs (MutableQuadView quad , Sprite sprite ) {
2828 float delta = sprite .getAnimationFrameDelta ();
2929 float centerU = (sprite .getMinU () + sprite .getMaxU ()) * 0.5f ;
3030 float centerV = (sprite .getMinV () + sprite .getMaxV ()) * 0.5f ;
@@ -41,7 +41,7 @@ public static void assignLerpedUVs(MutableQuadView quad, Sprite sprite) {
4141 public static void emitOverlayQuad (QuadEmitter emitter , Direction face , Sprite sprite , int color , RenderMaterial material ) {
4242 emitter .square (face , 0 , 0 , 1 , 1 , 0 );
4343 emitter .color (color , color , color , color );
44- assignLerpedUVs (emitter , sprite );
44+ assignLerpedUvs (emitter , sprite );
4545 emitter .material (material );
4646 emitter .emit ();
4747 }
@@ -79,56 +79,96 @@ public static boolean isQuadUnitSquare(QuadView quad) {
7979 return true ;
8080 }
8181
82+ /**
83+ * Returns an int in range [0, 7] representing the texture orientation of the given quad relative to the world.
84+ *
85+ * <ul>
86+ * <li>0 - 0 degree counterclockwise rotation, counterclockwise UV winding order</li>
87+ * <li>1 - 90 degree counterclockwise rotation, counterclockwise UV winding order</li>
88+ * <li>2 - 180 degree counterclockwise rotation, counterclockwise UV winding order</li>
89+ * <li>3 - 270 degree counterclockwise rotation, counterclockwise UV winding order</li>
90+ * <li>4 - 0 degree counterclockwise rotation, clockwise UV winding order</li>
91+ * <li>5 - 90 degree counterclockwise rotation, clockwise UV winding order</li>
92+ * <li>6 - 180 degree counterclockwise rotation, clockwise UV winding order</li>
93+ * <li>7 - 270 degree counterclockwise rotation, clockwise UV winding order</li>
94+ * </ul>
95+ */
8296 public static int getTextureOrientation (QuadView quad ) {
83- int rotation = getUVRotation (quad );
84- if (getUVWinding (quad ) == Winding .CLOCKWISE ) {
85- return rotation + 4 ;
97+ // Texture matrix
98+ float tm00 = quad .u (3 ) - quad .u (1 );
99+ float tm01 = quad .v (3 ) - quad .v (1 );
100+ float tm10 = quad .u (2 ) - quad .u (0 );
101+ float tm11 = quad .v (2 ) - quad .v (0 );
102+ // Determinant of texture matrix; also cross product of its column vectors
103+ float determinant = tm00 * tm11 - tm10 * tm01 ;
104+ if (determinant == 0 ) {
105+ return 0 ;
86106 }
87- return rotation ;
88- }
107+ float s = 1 / determinant ;
108+ // Second column of inverse texture matrix
109+ float itm10 = -tm10 * s ;
110+ float itm11 = tm00 * s ;
89111
90- public static int getUVRotation (QuadView quad ) {
91- int minVertex = 0 ;
92- float minDistance = 3.0f ;
93- for (int vertexId = 0 ; vertexId < 4 ; vertexId ++) {
94- float u = quad .u (vertexId );
95- float v = quad .v (vertexId );
96- float distance = u * u + v * v ;
97- if (distance < minDistance ) {
98- minDistance = distance ;
99- minVertex = vertexId ;
112+ int xAxis ;
113+ int xAxisSign ;
114+ int yAxis ;
115+ int yAxisSign ;
116+ switch (quad .lightFace ()) {
117+ case DOWN -> {
118+ xAxis = 0 ; // +X
119+ xAxisSign = 1 ;
120+ yAxis = 2 ; // +Z
121+ yAxisSign = 1 ;
122+ }
123+ case UP -> {
124+ xAxis = 0 ; // +X
125+ xAxisSign = 1 ;
126+ yAxis = 2 ; // -Z
127+ yAxisSign = -1 ;
128+ }
129+ case NORTH -> {
130+ xAxis = 0 ; // -X
131+ xAxisSign = -1 ;
132+ yAxis = 1 ; // +Y
133+ yAxisSign = 1 ;
134+ }
135+ case SOUTH -> {
136+ xAxis = 0 ; // +X
137+ xAxisSign = 1 ;
138+ yAxis = 1 ; // +Y
139+ yAxisSign = 1 ;
140+ }
141+ case WEST -> {
142+ xAxis = 2 ; // +Z
143+ xAxisSign = 1 ;
144+ yAxis = 1 ; // +Y
145+ yAxisSign = 1 ;
146+ }
147+ case EAST -> {
148+ xAxis = 2 ; // -Z
149+ xAxisSign = -1 ;
150+ yAxis = 1 ; // +Y
151+ yAxisSign = 1 ;
152+ }
153+ default -> {
154+ return 0 ;
100155 }
101156 }
102- return minVertex ;
103- }
104-
105- public static Winding getUVWinding (QuadView quad ) {
106- float u3 = quad .u (3 );
107- float v3 = quad .v (3 );
108- float u0 = quad .u (0 );
109- float v0 = quad .v (0 );
110- float u1 = quad .u (1 );
111- float v1 = quad .v (1 );
112-
113- float value = (u3 - u0 ) * (v1 - v0 ) - (v3 - v0 ) * (u1 - u0 );
114- if (value > 0 ) {
115- return Winding .COUNTERCLOCKWISE ;
116- } else if (value < 0 ) {
117- return Winding .CLOCKWISE ;
118- }
119- return Winding .UNDEFINED ;
120- }
157+ // Position matrix
158+ float pm00 = quad .posByIndex (3 , xAxis ) - quad .posByIndex (1 , xAxis );
159+ float pm01 = quad .posByIndex (3 , yAxis ) - quad .posByIndex (1 , yAxis );
160+ float pm10 = quad .posByIndex (2 , xAxis ) - quad .posByIndex (0 , xAxis );
161+ float pm11 = quad .posByIndex (2 , yAxis ) - quad .posByIndex (0 , yAxis );
121162
122- public enum Winding {
123- COUNTERCLOCKWISE ,
124- CLOCKWISE ,
125- UNDEFINED ;
163+ // Texture up vector in projected world space
164+ // Computed as (position matrix * inverse texture matrix * [0; -1]); [0; -1] is the texture up vector in texture space
165+ // Axis signs should be multiplied into position matrix values, but multiplying here instead saves 2 multiplications
166+ float x = -(pm00 * itm10 + pm10 * itm11 ) * xAxisSign ;
167+ float y = -(pm01 * itm10 + pm11 * itm11 ) * yAxisSign ;
126168
127- public Winding reverse () {
128- if (this == UNDEFINED ) {
129- return this ;
130- }
131- return this == CLOCKWISE ? COUNTERCLOCKWISE : CLOCKWISE ;
132- }
169+ // Clamp vector to nearest axis-aligned direction
170+ // up/+y -> 0, left/-x -> 1, down/-y -> 2, right/+x -> 3
171+ // Add 4 if the UV winding order is clockwise
172+ return (Math .abs (y ) >= Math .abs (x ) ? (y > 0 ? 0 : 2 ) : (x > 0 ? 3 : 1 )) + (determinant < 0 ? 4 : 0 );
133173 }
134174}
0 commit comments