private void ManipulateMe_ManipulationDelta(
            object sender, ManipulationDeltaRoutedEventArgs e)
        {
            if (forceManipulationsToEnd)
            {
                e.Complete();
                return;
            }

            _previousTransform.Matrix = _transformGroup.Value;

            Point center = _previousTransform.TransformPoint(
                new Point(e.Position.X, e.Position.Y));

            _compositeTransform.CenterX = center.X;
            _compositeTransform.CenterY = center.Y;

            _compositeTransform.Rotation   = (e.Delta.Rotation * 180) / Math.PI;
            _compositeTransform.ScaleX     =
                _compositeTransform.ScaleY = e.Delta.Scale;
            _compositeTransform.TranslateX = e.Delta.Translation.X;
            _compositeTransform.TranslateY = e.Delta.Translation.Y;

            e.Handled = true;
        }
Exemple #2
0
        public Task When_Identity_And_TransformPoint() =>
        RunOnUIThread.ExecuteAsync(() =>
        {
            var SUT = new MatrixTransform();

            Assert.AreEqual(new Point(0, 0), SUT.TransformPoint(new Point(0, 0)));
        });
Exemple #3
0
        public async Task When_Identity_And_TransformPoint()
        {
            await Dispatch(() =>
            {
                var SUT = new MatrixTransform();

                Assert.AreEqual(new Point(0, 0), SUT.TransformPoint(new Point(0, 0)));
            });
        }
Exemple #4
0
        public Task When_Rotate_And_TransformPoint() =>
        RunOnUIThread.ExecuteAsync(() =>
        {
            var SUT = new MatrixTransform()
            {
                Matrix = MatrixHelper.FromMatrix3x2(Matrix3x2.CreateRotation((float)Math.PI / 2))
            };

            Assert.AreEqual(new Point(-1, 1), SUT.TransformPoint(new Point(1, 1)));
        });
Exemple #5
0
        public Task When_Translate_And_TransformPoint() =>
        RunOnUIThread.ExecuteAsync(() =>
        {
            var SUT = new MatrixTransform()
            {
                Matrix = MatrixHelper.Create(1, 0, 0, 1, 10, 20)
            };

            Assert.AreEqual(new Point(10, 20), SUT.TransformPoint(new Point(0, 0)));
        });
Exemple #6
0
        /// <summary>
        /// Store existing transformation in matrix
        /// and return current center position in respect to this
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        private Point StoreTransformationsAndGetCenter(Point pos)
        {
            System.Diagnostics.Debug.WriteLine($"Pos: {pos.ToVector2()}");

            // store transformation into matrix
            previousTransform.Matrix = transformGroup.Value;

            // return center in respect to previous transformations
            return(previousTransform.TransformPoint(pos));
        }
    void Update()
    {
        matrix = new MatrixTransform(position, rotation, scale);

        for (int i = 0; i < points.Length; i++)
        {
            Vector3 transformedPosition = matrix.TransformPoint(positions[i]);
            points[i].transform.position = transformedPosition;
        }
    }
Exemple #8
0
        public async Task When_Translate_And_TransformPoint()
        {
            await Dispatch(() =>
            {
                var SUT = new MatrixTransform()
                {
                    Matrix = new Matrix(1, 0, 0, 1, 10, 20)
                };

                Assert.AreEqual(new Point(10, 20), SUT.TransformPoint(new Point(0, 0)));
            });
        }
Exemple #9
0
        public async Task When_Rotate_And_TransformPoint()
        {
            await Dispatch(() =>
            {
                var SUT = new MatrixTransform()
                {
                    Matrix = MatrixHelper.FromMatrix3x2(Matrix3x2.CreateRotation((float)Math.PI / 2))
                };

                Assert.AreEqual(new Point(-1, 1), SUT.TransformPoint(new Point(1, 1)));
            });
        }
Exemple #10
0
        public Task When_RotateQuarter_And_TransformPoint() =>
        RunOnUIThread.ExecuteAsync(() =>
        {
            var SUT = new MatrixTransform()
            {
                Matrix = MatrixHelper.FromMatrix3x2(Matrix3x2.CreateRotation((float)Math.PI / 4))
            };

            var expected = new Point(0, 1.41421353816986);
            var res      = SUT.TransformPoint(new Point(1, 1));
            Assert.AreEqual(expected.X, res.X, 1e-10, $"{expected} != {res}");
            Assert.AreEqual(expected.Y, res.Y, 1e-10, $"{expected} != {res}");
        });
        public void AccumulateDelta(Point position, ManipulationDelta delta)
        {
            matrixXform.Matrix = xformGroup.Value;
            Point center = matrixXform.TransformPoint(position);

            compositeXform.CenterX    = center.X;
            compositeXform.CenterY    = center.Y;
            compositeXform.TranslateX = delta.Translation.X;
            compositeXform.TranslateY = delta.Translation.Y;
            compositeXform.ScaleX     = delta.Scale;
            compositeXform.ScaleY     = delta.Scale;
            compositeXform.Rotation   = delta.Rotation;
            this.Matrix = xformGroup.Value;
        }
Exemple #12
0
        // Process the change resulting from a manipulation
        void MovingTarget_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
        {
            previousTransform.Matrix = transforms.Value;

            // Get center point for rotation
            Point center = previousTransform.TransformPoint(new Point(e.Position.X, e.Position.Y));

            deltaTransform.CenterX = center.X;
            deltaTransform.CenterY = center.Y;

            // Look at the Delta property of the ManipulationDeltaRoutedEventArgs to retrieve
            // the rotation, scale, X, and Y changes
            deltaTransform.TranslateX = e.Delta.Translation.X;
            deltaTransform.TranslateY = e.Delta.Translation.Y;
        }
Exemple #13
0
        void rectangle_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
        {
            _previousTransform.Matrix = _transformGroup.Value;

            // 获取操作点相对于此 GeneralTransform 的位置
            Point center = _previousTransform.TransformPoint(new Point(e.Position.X, e.Position.Y));

            _compositeTransform.CenterX = center.X;
            _compositeTransform.CenterY = center.Y;

            _compositeTransform.Rotation   = e.Delta.Rotation;
            _compositeTransform.ScaleX     = e.Delta.Scale;
            _compositeTransform.ScaleY     = e.Delta.Scale;
            _compositeTransform.TranslateX = e.Delta.Translation.X;
            _compositeTransform.TranslateY = e.Delta.Translation.Y;
        }
        void NewRectangle_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
        {
            //sender = new Rectangle();
            _previousTransform.Matrix = _transformGroup.Value;

            Point center = _previousTransform.TransformPoint(new Point(e.Position.X, e.Position.Y));

            _compositeTransform.CenterX = center.X;
            _compositeTransform.CenterY = center.Y;

            _compositeTransform.Rotation   = e.Delta.Rotation;
            _compositeTransform.ScaleX     = _compositeTransform.ScaleY = e.Delta.Scale;
            _compositeTransform.TranslateX = e.Delta.Translation.X;
            _compositeTransform.TranslateY = e.Delta.Translation.Y;

            e.Handled = true;
        }
        //private bool _pressing_label = false;
        //private void B_PointerPressed(object sender, PointerRoutedEventArgs e) {
        //    Debug.WriteLine("label pressed");
        //    _pressing_label = true;
        //}

        //private void B_PointerReleased(object sender, PointerRoutedEventArgs e) {
        //    Debug.WriteLine("label release");
        //    _pressing_label = false;
        //}

        private void B_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
        {
            TransformGroup     transforms        = (sender as Button).RenderTransform as TransformGroup;
            MatrixTransform    previousTransform = transforms.Children[0] as MatrixTransform;
            CompositeTransform deltaTransform    = transforms.Children[1] as CompositeTransform;

            previousTransform.Matrix = transforms.Value;

            // Get center point for rotation
            Point center = previousTransform.TransformPoint(new Point(e.Position.X, e.Position.Y));

            deltaTransform.CenterX = center.X;
            deltaTransform.CenterY = center.Y;

            // Look at the Delta property of the ManipulationDeltaRoutedEventArgs to retrieve
            // the rotation, scale, X, and Y changes
            deltaTransform.Rotation   = e.Delta.Rotation;
            deltaTransform.TranslateX = e.Delta.Translation.X;
            deltaTransform.TranslateY = e.Delta.Translation.Y;
        }
Exemple #16
0
        void Viewbox_OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
        {
            _previousTransform.Matrix = _transformGroup.Value;

            var center = _previousTransform.TransformPoint(e.Position);

            _deltaTransform.CenterX = center.X;
            _deltaTransform.CenterY = center.Y;

            _deltaTransform.Rotation = e.Delta.Rotation;

            _deltaTransform.ScaleX = e.Delta.Scale;
            _deltaTransform.ScaleY = e.Delta.Scale;

            _deltaTransform.TranslateX = e.Delta.Translation.X;
            _deltaTransform.TranslateY = e.Delta.Translation.Y;

            var control     = e.Container as FrameworkElement;
            var controlRect = new Rect(0, 0, control.DesiredSize.Width, control.DesiredSize.Height);

            BoundingRect = _transformGroup.TransformBounds(controlRect);
        }
        // Process the change resulting from a manipulation
        void ManipulateMe_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
        {
            // If the reset button has been pressed, mark the manipulation as completed
            if (forceManipulationsToEnd)
            {
                e.Complete();
                return;
            }

            previousTransform.Matrix = transforms.Value;

            // Get center point for rotation
            Point center = previousTransform.TransformPoint(new Point(e.Position.X, e.Position.Y));

            deltaTransform.CenterX = center.X;
            deltaTransform.CenterY = center.Y;

            // Look at the Delta property of the ManipulationDeltaRoutedEventArgs to retrieve
            // the rotation, scale, X, and Y changes
            deltaTransform.Rotation   = e.Delta.Rotation;
            deltaTransform.TranslateX = e.Delta.Translation.X;
            deltaTransform.TranslateY = e.Delta.Translation.Y;
        }
    /// <summary>
    /// Turn a base mesh into a building and surrounding street
    /// </summary>
    /// <param name="elevation">Used for specifying the offset of upper layers</param>
    /// <param name="upsidedown">Set true to generate the underside of a layer of buildings</param>
    public void Generate(bool upsidedown = false)
    {
        //Determine height based on size of base or some global variable
        float height = Random.Range(minHeight, maxHeight);

        color = new Color(Random.value, Random.value, Random.value);

        int baseVertLength = meshFilter.mesh.vertexCount;

        List <Vector3> vertices = new List <Vector3>();

        vertices.AddRange(meshFilter.mesh.vertices);

        List <int> triangles = new List <int>();

        int originalVertCount   = vertices.Count;
        int edges               = originalVertCount - 1;
        int originOfCurrentEdge = 1; //Keeps track of the first point on the currently worked on edge

        //Create ring of verts around base
        for (int edgeVert = 0; edgeVert < edges; edgeVert++)
        {
            int vertIndex = edgeVert + originOfCurrentEdge;
            //Lerp ring inwards by bottomTaper
            Vector3 newCorner = Vector3.Lerp(vertices[vertIndex], vertices[0], bottomTaper);
            vertices.Add(newCorner);

            //Create triangles linking bottom and top of current quad
            int p1 = vertIndex;
            int p2 = edgeVert + 1 < edges ? p1 + 1 : originOfCurrentEdge;
            int p3 = vertIndex + edges;
            int p4 = edgeVert + 1 < edges ? p3 + 1 : originOfCurrentEdge + edges;

            triangles.Add(p1);
            triangles.Add(p2);
            triangles.Add(p3);

            triangles.Add(p4);
            triangles.Add(p3);
            triangles.Add(p2);
        }
        originOfCurrentEdge += edges; //Move to top edge of current quad

        //Create sides of building; sides are composed of seperated quads, unlike bases, so need twice the number of vertices

        //Duplicate current edge ring for base of side quads
        for (int edgeVert = 0; edgeVert < edges; edgeVert++)
        {
            int     vertIndex     = edgeVert + originOfCurrentEdge;
            int     nextVertIndex = edgeVert + 1 < edges ? vertIndex + 1 : originOfCurrentEdge;
            Vector3 newCorner     = vertices[vertIndex];
            Vector3 nextNewCorner = vertices[nextVertIndex];
            vertices.Add(newCorner);
            vertices.Add(nextNewCorner);
        }
        originOfCurrentEdge += edges;               //Move to bottom edge of next quad

        int originOfSideEdge = originOfCurrentEdge; //Cache reference to vert index at base of the side quads, for use after basic shape generation is done

        //Move 0th vertex to the top of the extrude
        vertices[0] += Vector3.up * height;

        //Create ring of verts around top
        for (int edgeVert = 0; edgeVert < edges; edgeVert++)
        {
            int vertIndex     = (edgeVert * 2) + originOfCurrentEdge; //Increase vertIndex by 2 each loop because number of vertices are doubled
            int nextVertIndex = vertIndex + 1;
            //Duplicate vertex from bottom ring and move upwards
            Vector3 newCorner     = vertices[vertIndex] + (Vector3.up * height);
            Vector3 nextNewCorner = vertices[nextVertIndex] + (Vector3.up * height);
            //Lerp vertex inwards by topTaper
            newCorner     = Vector3.Lerp(newCorner, vertices[0], topTaper);
            nextNewCorner = Vector3.Lerp(nextNewCorner, vertices[0], topTaper);
            vertices.Add(newCorner);
            vertices.Add(nextNewCorner);

            //Skip this step because sides of buildings should be replaced with handmade meshes

            /*
             */
        }
        originOfCurrentEdge += edges * 2; //Move to top edge of current quad, multiply by 2 because the edge loop is twice as long for the sides of the building

        //Duplicate current edge ring for edge of roof quads
        for (int edgeVert = 0; edgeVert < edges; edgeVert++)
        {
            int     vertIndex = (edgeVert * 2) + originOfCurrentEdge; //Increase vertIndex by 2 each loop because number of vertices are doubled
            Vector3 newCorner = vertices[vertIndex];
            vertices.Add(newCorner);
        }
        originOfCurrentEdge += edges * 2; //Move to bottom edge of next quad, multiply by 2 because the edge loop is twice as long for the sides of the building

        //Create triangles linking ring 3 and 0
        for (int edgeVert = 0; edgeVert < edges; edgeVert++)
        {
            int vertIndex = edgeVert + originOfCurrentEdge;

            int p1 = 0;
            int p2 = vertIndex;
            int p3 = edgeVert + 1 < edges ? p2 + 1 : originOfCurrentEdge;

            triangles.Add(p1);
            triangles.Add(p2);
            triangles.Add(p3);
        }

        //Fill sides of the building with randomly chosen replacement meshes, or quads if no replacements available
        for (int edgeVert = 0; edgeVert < edges; edgeVert++)
        {
            int vertIndex = (edgeVert * 2) + originOfSideEdge;

            int p1 = vertIndex;
            int p2 = p1 + 1;
            int p3 = vertIndex + (edges * 2);
            int p4 = p3 + 1;

            if (replacementMeshes != null && replacementMeshes.Length > 0)
            {
                //Pick a replacement mesh to use for this side of the building, and add its vertices and triangles to the mesh
                int  meshIndex       = Random.Range(0, replacementMeshes.Length);
                Mesh replacementMesh = replacementMeshes[meshIndex];

                Vector3 minExtents = replacementMesh.bounds.min;
                Vector3 maxExtents = replacementMesh.bounds.max;

                Vector3 v1 = vertices[p1];
                Vector3 v2 = vertices[p2];
                Vector3 v3 = vertices[p3];
                Vector3 v4 = vertices[p4];

                //Add triangles of replacement mesh, increasing the index by the current vertex list length
                int triangleIndexOffset = vertices.Count;
                for (int tri = 0; tri < replacementMesh.triangles.Length; tri++)
                {
                    triangles.Add(replacementMesh.triangles[tri] + triangleIndexOffset);
                }

                //Create a transformation matrix to convert points from the [x, z] plane to the plane of the face
                Vector3 midpointBottom = Vector3.Lerp(v1, v2, 0.5f);
                Vector3 midpointTop    = Vector3.Lerp(v3, v4, 0.5f);
                float   halfHeight     = (midpointTop.y - midpointBottom.y) / 2f;

                float rotationY = (Mathf.Atan2(v1.x - v2.x, v1.z - v2.z) * Mathf.Rad2Deg) - 90f;
                //float rotationYTop = (Mathf.Atan2(v3.x - v4.x, v3.z - v4.z) * Mathf.Rad2Deg) - 90f;
                float   relativeX         = Mathf.Sqrt(Mathf.Pow(midpointTop.z - midpointBottom.z, 2) + Mathf.Pow(midpointTop.x - midpointBottom.x, 2));
                float   rotationX         = Mathf.Atan2(midpointTop.y - midpointBottom.y, relativeX) * Mathf.Rad2Deg * -1f;
                float   rotationZ         = 0f; //Mathf.Atan2(midpointTop.y - midpointBottom.y, midpointTop.x - midpointBottom.x) * Mathf.Rad2Deg;
                Vector3 transformRotation = new Vector3(rotationX, rotationY, rotationZ);
                //Vector3 topTransformRotation = new Vector3(rotationX, rotationYTop, rotationZ);

                //Need to find way to move positions so they rest on the surface
                float   distanceToFace = halfHeight * Mathf.Tan((90f + rotationX) * Mathf.Deg2Rad);
                Vector3 towardsFace    = Vector3.Cross(v1 - v2, Vector3.up);
                towardsFace.Normalize();
                //Debug.Log(halfHeight + " / tan(" + -rotationX + ") = " + distanceToFace);
                towardsFace *= distanceToFace;

                Vector3 bottomTransformPosition = midpointBottom + (Vector3.up * halfHeight) + towardsFace; //Move up and forward
                Vector3 topTransformPosition    = midpointTop + (Vector3.up * -halfHeight) - towardsFace;   //Move down and backward

                float meshWidth     = maxExtents.x - minExtents.x;
                float sideWidth     = Vector3.Distance(v1, v2);
                float relativeWidth = sideWidth / meshWidth;

                float topSideWidth     = Vector3.Distance(v3, v4);
                float topRelativeWidth = topSideWidth / meshWidth;

                float meshHeight     = maxExtents.z - minExtents.z;
                float sideHeight     = Vector3.Distance(midpointBottom, midpointTop);
                float relativeHeight = sideHeight / meshHeight;

                float relativeDepth = Mathf.Min(relativeWidth, relativeHeight);

                Vector3 transformScale    = new Vector3(relativeWidth, relativeDepth, relativeHeight);
                Vector3 topTransformScale = new Vector3(topRelativeWidth, relativeDepth, relativeHeight);

                MatrixTransform transformMatrix    = new MatrixTransform(bottomTransformPosition, transformRotation, transformScale);
                MatrixTransform topTransformMatrix = new MatrixTransform(topTransformPosition, transformRotation, topTransformScale);

                //Use positions of defined points to lerp the vertex positions of a replacement mesh, like uv coordinates
                for (int vertex = 0; vertex < replacementMesh.vertices.Length; vertex++) //Redoing this to use a matrix transformation instead
                {
                    Vector3 oldVertex       = replacementMesh.vertices[vertex];
                    Vector3 newVertexBottom = transformMatrix.TransformPoint(oldVertex);
                    Vector3 newVertexTop    = topTransformMatrix.TransformPoint(oldVertex);

                    //Interpolate between top and bottom vertices by the height of the vertex
                    float   interpolant = Mathf.InverseLerp(minExtents.z, maxExtents.z, oldVertex.z);
                    Vector3 newVertex   = Vector3.LerpUnclamped(newVertexBottom, newVertexTop, interpolant);

                    vertices.Add(newVertex);
                }
            }
            else
            {
                //Fill with triangles instead
                triangles.Add(p1);
                triangles.Add(p2);
                triangles.Add(p3);

                triangles.Add(p4);
                triangles.Add(p3);
                triangles.Add(p2);
            }
        }

        //Flip if upsidedown is true
        if (upsidedown)
        {
            //Flip sign of y position
            for (int vert = 0; vert < vertices.Count; vert++)
            {
                Vector3 flipped = vertices[vert];
                flipped.y      = -flipped.y;
                vertices[vert] = flipped;
            }

            //Reverse triangles so buildings are visable from outside
            for (int tri = 0; tri < triangles.Count; tri += 3)
            {
                int temp = triangles[tri + 1];           //Cache first vertex in triangle
                triangles[tri + 1] = triangles[tri + 2]; //Assign first vertex equal to second vertex
                triangles[tri + 2] = temp;               //Assign second vertex to cached first vertex
            }
        }

        List <Color> vertexColors = new List <Color>();

        //Color vertices
        for (int vert = 0; vert < vertices.Count; vert++)
        {
            vertexColors.Add(color);
        }

        meshFilter.mesh.SetVertices(vertices);
        meshFilter.mesh.SetColors(vertexColors); //Why are you blue
        meshFilter.mesh.SetTriangles(triangles, 0);
        meshFilter.mesh.RecalculateNormals();
    }
        /// <summary>
        /// Renders a bitmap using any affine transformation and transparency into this bitmap
        /// Unlike Silverlight's Render() method, this one uses 2-3 times less memory, and is the same or better quality
        /// The algorithm is simple dx/dy (bresenham-like) step by step painting, optimized with fixed point and fast bilinear filtering
        /// It's used in Fantasia Painter for drawing stickers and 3D objects on screen
        /// </summary>
        /// <param name="bmp">Destination bitmap.</param>
        /// <param name="source">The source WriteableBitmap.</param>
        /// <param name="shouldClear">If true, the the destination bitmap will be set to all clear (0) before rendering.</param>
        /// <param name="opacity">opacity of the source bitmap to render, between 0 and 1 inclusive</param>
        /// <param name="transform">Transformation to apply</param>
        public static void BlitRender(this BitmapBuffer bmp, BitmapBuffer source, bool shouldClear = true, float opacity = 1f, GeneralTransform transform = null)
        {
            const int PRECISION_SHIFT = 10;
            const int PRECISION_VALUE = (1 << PRECISION_SHIFT);
            const int PRECISION_MASK  = PRECISION_VALUE - 1;

            using (BitmapContext destContext = bmp.GetBitmapContext())
            {
                if (transform == null)
                {
                    transform = new MatrixTransform(Affine.IdentityMatrix);
                }

                int[]           destPixels = destContext.Pixels;
                int             destWidth  = destContext.Width;
                int             destHeight = destContext.Height;
                MatrixTransform inverse    = transform.Inverse;
                if (shouldClear)
                {
                    destContext.Clear();
                }

                using (BitmapContext sourceContext = source.GetBitmapContext(ReadWriteMode.ReadOnly))
                {
                    var sourcePixels = sourceContext.Pixels;
                    int sourceWidth  = sourceContext.Width;
                    int sourceHeight = sourceContext.Height;

                    RectD sourceRect = new RectD(0, 0, sourceWidth, sourceHeight);
                    RectD destRect   = new RectD(0, 0, destWidth, destHeight);
                    RectD bounds     = transform.TransformBounds(sourceRect);
                    bounds.Intersect(destRect);

                    int startX = (int)bounds.Left;
                    int startY = (int)bounds.Top;
                    int endX   = (int)bounds.Right;
                    int endY   = (int)bounds.Bottom;

#if NETFX_CORE
                    Point zeroZero = inverse.TransformPoint(new Point(startX, startY));
                    Point oneZero  = inverse.TransformPoint(new Point(startX + 1, startY));
                    Point zeroOne  = inverse.TransformPoint(new Point(startX, startY + 1));
#else
                    PointD zeroZero = inverse.Transform(new PointD(startX, startY));
                    PointD oneZero  = inverse.Transform(new PointD(startX + 1, startY));
                    PointD zeroOne  = inverse.Transform(new PointD(startX, startY + 1));
#endif
                    float sourceXf = ((float)zeroZero.X);
                    float sourceYf = ((float)zeroZero.Y);
                    int   dxDx     = (int)((((float)oneZero.X) - sourceXf) * PRECISION_VALUE); // for 1 unit in X coord, how much does X change in source texture?
                    int   dxDy     = (int)((((float)oneZero.Y) - sourceYf) * PRECISION_VALUE); // for 1 unit in X coord, how much does Y change in source texture?
                    int   dyDx     = (int)((((float)zeroOne.X) - sourceXf) * PRECISION_VALUE); // for 1 unit in Y coord, how much does X change in source texture?
                    int   dyDy     = (int)((((float)zeroOne.Y) - sourceYf) * PRECISION_VALUE); // for 1 unit in Y coord, how much does Y change in source texture?

                    int sourceX           = (int)(((float)zeroZero.X) * PRECISION_VALUE);
                    int sourceY           = (int)(((float)zeroZero.Y) * PRECISION_VALUE);
                    int sourceWidthFixed  = sourceWidth << PRECISION_SHIFT;
                    int sourceHeightFixed = sourceHeight << PRECISION_SHIFT;

                    int opacityInt = (int)(opacity * 255);

                    int index = 0;
                    for (int destY = startY; destY < endY; destY++)
                    {
                        index = destY * destWidth + startX;
                        int savedSourceX = sourceX;
                        int savedSourceY = sourceY;

                        for (int destX = startX; destX < endX; destX++)
                        {
                            if ((sourceX >= 0) && (sourceX < sourceWidthFixed) && (sourceY >= 0) && (sourceY < sourceHeightFixed))
                            {
                                // bilinear filtering
                                int xFloor = sourceX >> PRECISION_SHIFT;
                                int yFloor = sourceY >> PRECISION_SHIFT;

                                if (xFloor < 0)
                                {
                                    xFloor = 0;
                                }
                                if (yFloor < 0)
                                {
                                    yFloor = 0;
                                }

                                int xCeil = xFloor + 1;
                                int yCeil = yFloor + 1;

                                if (xCeil >= sourceWidth)
                                {
                                    xFloor = sourceWidth - 1;
                                    xCeil  = 0;
                                }
                                else
                                {
                                    xCeil = 1;
                                }

                                if (yCeil >= sourceHeight)
                                {
                                    yFloor = sourceHeight - 1;
                                    yCeil  = 0;
                                }
                                else
                                {
                                    yCeil = sourceWidth;
                                }

                                int i1 = yFloor * sourceWidth + xFloor;
                                int p1 = sourcePixels[i1];
                                int p2 = sourcePixels[i1 + xCeil];
                                int p3 = sourcePixels[i1 + yCeil];
                                int p4 = sourcePixels[i1 + yCeil + xCeil];

                                int xFrac = sourceX & PRECISION_MASK;
                                int yFrac = sourceY & PRECISION_MASK;

                                // alpha
                                byte a1 = (byte)(p1 >> 24);
                                byte a2 = (byte)(p2 >> 24);
                                byte a3 = (byte)(p3 >> 24);
                                byte a4 = (byte)(p4 >> 24);

                                int  comp1, comp2;
                                byte a;

                                if ((a1 == a2) && (a1 == a3) && (a1 == a4))
                                {
                                    if (a1 == 0)
                                    {
                                        destPixels[index] = 0;

                                        sourceX += dxDx;
                                        sourceY += dxDy;
                                        index++;
                                        continue;
                                    }

                                    a = a1;
                                }
                                else
                                {
                                    comp1 = a1 + ((xFrac * (a2 - a1)) >> PRECISION_SHIFT);
                                    comp2 = a3 + ((xFrac * (a4 - a3)) >> PRECISION_SHIFT);
                                    a     = (byte)(comp1 + ((yFrac * (comp2 - comp1)) >> PRECISION_SHIFT));
                                }

                                // red
                                comp1 = ((byte)(p1 >> 16)) + ((xFrac * (((byte)(p2 >> 16)) - ((byte)(p1 >> 16)))) >> PRECISION_SHIFT);
                                comp2 = ((byte)(p3 >> 16)) + ((xFrac * (((byte)(p4 >> 16)) - ((byte)(p3 >> 16)))) >> PRECISION_SHIFT);
                                byte r = (byte)(comp1 + ((yFrac * (comp2 - comp1)) >> PRECISION_SHIFT));

                                // green
                                comp1 = ((byte)(p1 >> 8)) + ((xFrac * (((byte)(p2 >> 8)) - ((byte)(p1 >> 8)))) >> PRECISION_SHIFT);
                                comp2 = ((byte)(p3 >> 8)) + ((xFrac * (((byte)(p4 >> 8)) - ((byte)(p3 >> 8)))) >> PRECISION_SHIFT);
                                byte g = (byte)(comp1 + ((yFrac * (comp2 - comp1)) >> PRECISION_SHIFT));

                                // blue
                                comp1 = ((byte)p1) + ((xFrac * (((byte)p2) - ((byte)p1))) >> PRECISION_SHIFT);
                                comp2 = ((byte)p3) + ((xFrac * (((byte)p4) - ((byte)p3))) >> PRECISION_SHIFT);
                                byte b = (byte)(comp1 + ((yFrac * (comp2 - comp1)) >> PRECISION_SHIFT));

                                // save updated pixel
                                if (opacityInt != 255)
                                {
                                    a = (byte)((a * opacityInt) >> 8);
                                    r = (byte)((r * opacityInt) >> 8);
                                    g = (byte)((g * opacityInt) >> 8);
                                    b = (byte)((b * opacityInt) >> 8);
                                }
                                destPixels[index] = (a << 24) | (r << 16) | (g << 8) | b;
                            }

                            sourceX += dxDx;
                            sourceY += dxDy;
                            index++;
                        }

                        sourceX = savedSourceX + dyDx;
                        sourceY = savedSourceY + dyDy;
                    }
                }
            }
        }