From 819741493d07adb30d44576ffb2de9c09d346a77 Mon Sep 17 00:00:00 2001
From: Svjatoslav Agejenko Opens as a popup window when F12 is pressed. Provides: The rotation is composed as yaw (around Y axis) followed by
- * pitch (around X axis).
*
For full 3-axis rotation, use {@link #fromAngles(double, double, double)}.
* * @param angleXZ the angle around the XZ axis (yaw) in radians * @param angleYZ the angle around the YZ axis (pitch) in radians * @return a quaternion representing the combined rotation */ public static Quaternion fromAngles(final double angleXZ, final double angleYZ) { - final Quaternion yaw = fromAxisAngle(new Point3D(0, 1, 0), angleXZ); - final Quaternion pitch = fromAxisAngle(new Point3D(1, 0, 0), -angleYZ); - return pitch.multiply(yaw); + return fromAngles(angleXZ, angleYZ, 0); + } + + /** + * Creates a quaternion from full Euler angles (yaw, pitch, roll). + * + *Rotation order: yaw (Y) â pitch (X) â roll (Z). This is the standard + * Y-X-Z Euler order commonly used for object placement in 3D scenes.
+ * + * @param yaw rotation around Y axis (horizontal heading) in radians + * @param pitch rotation around X axis (vertical tilt) in radians; + * positive values tilt upward + * @param roll rotation around Z axis (bank/tilt) in radians; + * positive values rotate clockwise when looking along +Z + * @return a quaternion representing the combined rotation + */ + public static Quaternion fromAngles(final double yaw, final double pitch, final double roll) { + final Quaternion qYaw = fromAxisAngle(new Point3D(0, 1, 0), yaw); + final Quaternion qPitch = fromAxisAngle(new Point3D(1, 0, 0), -pitch); + final Quaternion qRoll = fromAxisAngle(new Point3D(0, 0, 1), roll); + return qRoll.multiply(qPitch).multiply(qYaw); } /** @@ -215,4 +235,27 @@ public class Quaternion { return toMatrix3x3(); } + /** + * Extracts Euler angles (yaw, pitch, roll) from this quaternion. + * + *This is the inverse of {@link #fromAngles(double, double, double)}. + * Returns angles in the Y-X-Z Euler order used by this engine.
+ * + * @return array of {yaw, pitch, roll} in radians + */ + public double[] toAngles() { + final Matrix3x3 m = toMatrix3x3(); + + // For Rz(roll) * Rx(-pitch) * Ry(yaw) rotation order: + // m21 = sin(pitch) + // m20 = cos(pitch)*sin(yaw), m22 = cos(pitch)*cos(yaw) + // m01 = -sin(roll)*cos(pitch), m11 = cos(roll)*cos(pitch) + + final double pitch = -Math.asin(Math.max(-1, Math.min(1, m.m21))); + final double yaw = -Math.atan2(m.m20, m.m22); + final double roll = -Math.atan2(m.m01, m.m11); + + return new double[]{yaw, pitch, roll}; + } + } \ No newline at end of file diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/math/Transform.java b/src/main/java/eu/svjatoslav/sixth/e3d/math/Transform.java index 9a75ece..46c628e 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/math/Transform.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/math/Transform.java @@ -48,13 +48,32 @@ public class Transform implements Cloneable { * Creates a transform with the specified translation and rotation from Euler angles. * * @param translation the translation - * @param angleXZ the angle around the XZ axis (yaw) in radians - * @param angleYZ the angle around the YZ axis (pitch) in radians + * @param yaw the angle around the Y axis (horizontal heading) in radians + * @param pitch the angle around the X axis (vertical tilt) in radians * @return a new transform with the specified translation and rotation */ - public static Transform fromAngles(final Point3D translation, final double angleXZ, final double angleYZ) { - final Transform t = new Transform(translation); - t.rotation.set(Quaternion.fromAngles(angleXZ, angleYZ)); + public static Transform fromAngles(final Point3D translation, final double yaw, final double pitch) { + return fromAngles(translation.x, translation.y, translation.z, yaw, pitch, 0); + } + + /** + * Creates a transform with translation and full Euler rotation. + * + *Rotation order: yaw (Y) â pitch (X) â roll (Z). This is the standard + * Y-X-Z Euler order commonly used for object placement in 3D scenes.
+ * + * @param x translation X coordinate + * @param y translation Y coordinate + * @param z translation Z coordinate + * @param yaw rotation around Y axis (horizontal heading) in radians + * @param pitch rotation around X axis (vertical tilt) in radians + * @param roll rotation around Z axis (bank/tilt) in radians + * @return a new transform with the specified translation and rotation + */ + public static Transform fromAngles(final double x, final double y, final double z, + final double yaw, final double pitch, final double roll) { + final Transform t = new Transform(new Point3D(x, y, z)); + t.rotation.set(Quaternion.fromAngles(yaw, pitch, roll)); return t; } @@ -118,4 +137,27 @@ public class Transform implements Cloneable { this.translation.z = translation.z; } + /** + * Sets both translation and rotation from Euler angles. + * + *Rotation order: yaw (Y) â pitch (X) â roll (Z). This is the standard + * Y-X-Z Euler order commonly used for object placement in 3D scenes.
+ * + * @param x translation X coordinate + * @param y translation Y coordinate + * @param z translation Z coordinate + * @param yaw rotation around Y axis (horizontal heading) in radians + * @param pitch rotation around X axis (vertical tilt) in radians + * @param roll rotation around Z axis (bank/tilt) in radians + * @return this transform for chaining + */ + public Transform set(final double x, final double y, final double z, + final double yaw, final double pitch, final double roll) { + translation.x = x; + translation.y = y; + translation.z = z; + rotation.set(Quaternion.fromAngles(yaw, pitch, roll)); + return this; + } + } \ No newline at end of file diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java index 83acbb0..72c38fa 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java @@ -143,9 +143,9 @@ public class Line extends AbstractCoordinateShape { final int destG = (dest >> 8) & 0xff; final int destB = dest & 0xff; - final int newR = ((destR * backgroundAlpha) + (colorR * realLineAlpha)) / 256; - final int newG = ((destG * backgroundAlpha) + (colorG * realLineAlpha)) / 256; - final int newB = ((destB * backgroundAlpha) + (colorB * realLineAlpha)) / 256; + final int newR = ((destR * backgroundAlpha) + (colorR * realLineAlpha)) >> 8; + final int newG = ((destG * backgroundAlpha) + (colorG * realLineAlpha)) >> 8; + final int newB = ((destB * backgroundAlpha) + (colorB * realLineAlpha)) >> 8; pixels[offset++] = (newR << 16) | (newG << 8) | newB; @@ -210,9 +210,9 @@ public class Line extends AbstractCoordinateShape { final int destG = (dest >> 8) & 0xff; final int destB = dest & 0xff; - final int newR = ((destR * backgroundAlpha) + redWithAlpha) / 256; - final int newG = ((destG * backgroundAlpha) + greenWithAlpha) / 256; - final int newB = ((destB * backgroundAlpha) + blueWithAlpha) / 256; + final int newR = ((destR * backgroundAlpha) + redWithAlpha) >> 8; + final int newG = ((destG * backgroundAlpha) + greenWithAlpha) >> 8; + final int newB = ((destB * backgroundAlpha) + blueWithAlpha) >> 8; pixels[offset] = (newR << 16) | (newG << 8) | newB; } @@ -278,9 +278,9 @@ public class Line extends AbstractCoordinateShape { final int destG = (dest >> 8) & 0xff; final int destB = dest & 0xff; - final int newR = ((destR * backgroundAlpha) + redWithAlpha) / 256; - final int newG = ((destG * backgroundAlpha) + greenWithAlpha) / 256; - final int newB = ((destB * backgroundAlpha) + blueWithAlpha) / 256; + final int newR = ((destR * backgroundAlpha) + redWithAlpha) >> 8; + final int newG = ((destG * backgroundAlpha) + greenWithAlpha) >> 8; + final int newB = ((destB * backgroundAlpha) + blueWithAlpha) >> 8; pixels[offset] = (newR << 16) | (newG << 8) | newB; } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java index 164ae54..fb729c6 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java @@ -115,9 +115,9 @@ public class SolidPolygon extends AbstractCoordinateShape { final int destG = (dest >> 8) & 0xff; final int destB = dest & 0xff; - final int newR = ((destR * backgroundAlpha) + redWithAlpha) / 256; - final int newG = ((destG * backgroundAlpha) + greenWithAlpha) / 256; - final int newB = ((destB * backgroundAlpha) + blueWithAlpha) / 256; + final int newR = ((destR * backgroundAlpha) + redWithAlpha) >> 8; + final int newG = ((destG * backgroundAlpha) + greenWithAlpha) >> 8; + final int newB = ((destB * backgroundAlpha) + blueWithAlpha) >> 8; pixels[offset++] = (newR << 16) | (newG << 8) | newB; } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java index 094ec42..477c30b 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java @@ -161,18 +161,18 @@ public class TexturedPolygon extends AbstractCoordinateShape { } else { final int backgroundAlpha = 255 - srcAlpha; - final int srcR = (srcPixel >> 16) & 0xff; - final int srcG = (srcPixel >> 8) & 0xff; - final int srcB = srcPixel & 0xff; + final int srcR = ((srcPixel >> 16) & 0xff) * srcAlpha; + final int srcG = ((srcPixel >> 8) & 0xff) * srcAlpha; + final int srcB = (srcPixel & 0xff) * srcAlpha; final int destPixel = renderBufferPixels[renderBufferOffset]; final int destR = (destPixel >> 16) & 0xff; final int destG = (destPixel >> 8) & 0xff; final int destB = destPixel & 0xff; - final int r = ((destR * backgroundAlpha) + (srcR * srcAlpha)) / 256; - final int g = ((destG * backgroundAlpha) + (srcG * srcAlpha)) / 256; - final int b = ((destB * backgroundAlpha) + (srcB * srcAlpha)) / 256; + final int r = ((destR * backgroundAlpha) + srcR) >> 8; + final int g = ((destG * backgroundAlpha) + srcG) >> 8; + final int b = ((destB * backgroundAlpha) + srcB) >> 8; renderBufferPixels[renderBufferOffset] = (r << 16) | (g << 8) | b; } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/TextureBitmap.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/TextureBitmap.java index 8726bc5..ac0e1f0 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/TextureBitmap.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/TextureBitmap.java @@ -97,6 +97,9 @@ public class TextureBitmap { *This texture stores pixels in ARGB format. The target is RGB format (no alpha). * Alpha blending is performed based on the source pixel's alpha value.
* + *Performance note: Uses bit-shift instead of division for alpha blending, + * and pre-multiplies source colors to reduce per-pixel operations.
+ * * @param sourcePixelAddress Pixel index within current texture. * @param targetBitmap Target RGB pixel array. * @param targetPixelAddress Pixel index within target image. @@ -117,22 +120,71 @@ public class TextureBitmap { final int backgroundAlpha = 255 - textureAlpha; - final int srcR = (sourcePixel >> 16) & 0xff; - final int srcG = (sourcePixel >> 8) & 0xff; - final int srcB = sourcePixel & 0xff; + // Pre-multiply source colors by alpha to reduce operations in blend + final int srcR = ((sourcePixel >> 16) & 0xff) * textureAlpha; + final int srcG = ((sourcePixel >> 8) & 0xff) * textureAlpha; + final int srcB = (sourcePixel & 0xff) * textureAlpha; final int destPixel = targetBitmap[targetPixelAddress]; final int destR = (destPixel >> 16) & 0xff; final int destG = (destPixel >> 8) & 0xff; final int destB = destPixel & 0xff; - final int r = ((destR * backgroundAlpha) + (srcR * textureAlpha)) / 256; - final int g = ((destG * backgroundAlpha) + (srcG * textureAlpha)) / 256; - final int b = ((destB * backgroundAlpha) + (srcB * textureAlpha)) / 256; + // Use bit-shift instead of division for faster blending + final int r = ((destR * backgroundAlpha) + srcR) >> 8; + final int g = ((destG * backgroundAlpha) + srcG) >> 8; + final int b = ((destB * backgroundAlpha) + srcB) >> 8; targetBitmap[targetPixelAddress] = (r << 16) | (g << 8) | b; } + /** + * Renders a scanline using pre-computed source pixel addresses. + * + *This variant is optimized for cases where source addresses are computed + * externally (e.g., by a caller that already has the stepping logic). + * The sourceAddresses array must contain valid indices into {@link #pixels}.
+ * + * @param sourceAddresses array of source pixel addresses (indices into pixels array) + * @param targetBitmap target RGB pixel array + * @param targetStartAddress starting index in the target array + * @param pixelCount number of pixels to render + */ + public void drawScanlineWithAddresses(final int[] sourceAddresses, + final int[] targetBitmap, final int targetStartAddress, + final int pixelCount) { + + int targetOffset = targetStartAddress; + + for (int i = 0; i < pixelCount; i++) { + final int sourcePixel = pixels[sourceAddresses[i]]; + final int textureAlpha = (sourcePixel >> 24) & 0xff; + + if (textureAlpha == 255) { + targetBitmap[targetOffset] = sourcePixel; + } else if (textureAlpha != 0) { + final int backgroundAlpha = 255 - textureAlpha; + + final int srcR = ((sourcePixel >> 16) & 0xff) * textureAlpha; + final int srcG = ((sourcePixel >> 8) & 0xff) * textureAlpha; + final int srcB = (sourcePixel & 0xff) * textureAlpha; + + final int destPixel = targetBitmap[targetOffset]; + final int destR = (destPixel >> 16) & 0xff; + final int destG = (destPixel >> 8) & 0xff; + final int destB = destPixel & 0xff; + + final int r = ((destR * backgroundAlpha) + srcR) >> 8; + final int g = ((destG * backgroundAlpha) + srcG) >> 8; + final int b = ((destB * backgroundAlpha) + srcB) >> 8; + + targetBitmap[targetOffset] = (r << 16) | (g << 8) | b; + } + + targetOffset++; + } + } + /** * Draws a single pixel at the specified coordinates using the given color. * @@ -154,6 +206,9 @@ public class TextureBitmap { * The same applies to {@code y1} and {@code y2}. The rectangle is exclusive of the * right and bottom edges. * + *Performance: Uses {@link java.util.Arrays#fill(int[], int, int, int)} + * per scanline for optimal JVM-optimized memory writes.
+ * * @param x1 the left x coordinate * @param y1 the top y coordinate * @param x2 the right x coordinate (exclusive) @@ -175,9 +230,23 @@ public class TextureBitmap { y2 = tmp; } - for (int y = y1; y < y2; y++) - for (int x = x1; x < x2; x++) - drawPixel(x, y, color); + // Clamp to bitmap bounds + if (x1 < 0) x1 = 0; + if (y1 < 0) y1 = 0; + if (x2 > width) x2 = width; + if (y2 > height) y2 = height; + + final int pixel = (color.a << 24) | (color.r << 16) | (color.g << 8) | color.b; + final int rowWidth = x2 - x1; + + if (rowWidth <= 0) + return; + + // Fill each scanline using Arrays.fill for optimal performance + for (int y = y1; y < y2; y++) { + final int rowStart = y * width + x1; + java.util.Arrays.fill(pixels, rowStart, rowStart + rowWidth, pixel); + } } /** -- 2.20.1