//Transform the convex into the space of something else. /// <summary> /// Gets the bounding box of the convex shape transformed first into world space, and then into the local space of another affine transform. /// </summary> /// <param name="shapeTransform">Transform to use to put the shape into world space.</param> /// <param name="spaceTransform">Used as the frame of reference to compute the bounding box. /// In effect, the shape is transformed by the inverse of the space transform to compute its bounding box in local space.</param> /// <param name="boundingBox">Bounding box in the local space.</param> public void GetLocalBoundingBox(ref RigidTransform shapeTransform, ref AffineTransform spaceTransform, out BoundingBox boundingBox) { #if !WINDOWS boundingBox = new BoundingBox(); #endif //TODO: This method peforms quite a few sqrts because the collision margin can get scaled, and so cannot be applied as a final step. //There should be a better way to do this. //Additionally, this bounding box is not consistent in all cases with the post-add version. Adding the collision margin at the end can //slightly overestimate the size of a margin expanded shape at the corners, which is fine (and actually important for the box-box special case). //Move forward into convex's space, backwards into the new space's local space. AffineTransform transform; AffineTransform.Invert(ref spaceTransform, out transform); AffineTransform.Multiply(ref shapeTransform, ref transform, out transform); //Sample the local directions from the orientation matrix, implicitly transposed. Vector3 right; var direction = new Vector3(transform.LinearTransform.M11, transform.LinearTransform.M21, transform.LinearTransform.M31); GetLocalExtremePoint(direction, out right); Vector3 left; direction = new Vector3(-transform.LinearTransform.M11, -transform.LinearTransform.M21, -transform.LinearTransform.M31); GetLocalExtremePoint(direction, out left); Vector3 up; direction = new Vector3(transform.LinearTransform.M12, transform.LinearTransform.M22, transform.LinearTransform.M32); GetLocalExtremePoint(direction, out up); Vector3 down; direction = new Vector3(-transform.LinearTransform.M12, -transform.LinearTransform.M22, -transform.LinearTransform.M32); GetLocalExtremePoint(direction, out down); Vector3 backward; direction = new Vector3(transform.LinearTransform.M13, transform.LinearTransform.M23, transform.LinearTransform.M33); GetLocalExtremePoint(direction, out backward); Vector3 forward; direction = new Vector3(-transform.LinearTransform.M13, -transform.LinearTransform.M23, -transform.LinearTransform.M33); GetLocalExtremePoint(direction, out forward); //This could be optimized. Unnecessary transformation information gets computed. Matrix3X3.Transform(ref right, ref transform.LinearTransform, out right); Matrix3X3.Transform(ref left, ref transform.LinearTransform, out left); Matrix3X3.Transform(ref up, ref transform.LinearTransform, out up); Matrix3X3.Transform(ref down, ref transform.LinearTransform, out down); Matrix3X3.Transform(ref backward, ref transform.LinearTransform, out backward); Matrix3X3.Transform(ref forward, ref transform.LinearTransform, out forward); //These right/up/backward represent the extreme points in world space along the world space axes. boundingBox.Max.X = transform.Translation.X + right.X; boundingBox.Max.Y = transform.Translation.Y + up.Y; boundingBox.Max.Z = transform.Translation.Z + backward.Z; boundingBox.Min.X = transform.Translation.X + left.X; boundingBox.Min.Y = transform.Translation.Y + down.Y; boundingBox.Min.Z = transform.Translation.Z + forward.Z; }
//Transform the convex into the space of something else. /// <summary> /// Gets the bounding box of the convex shape transformed first into world space, and then into the local space of another affine transform. /// </summary> /// <param name="shapeTransform">Transform to use to put the shape into world space.</param> /// <param name="spaceTransform">Used as the frame of reference to compute the bounding box. /// In effect, the shape is transformed by the inverse of the space transform to compute its bounding box in local space.</param> /// <param name="boundingBox">Bounding box in the local space.</param> public void GetLocalBoundingBox(ref RigidTransform shapeTransform, ref AffineTransform spaceTransform, out BoundingBox boundingBox) { #if !WINDOWS boundingBox = new BoundingBox(); #endif //TODO: This method peforms quite a few sqrts because the collision margin can get scaled, and so cannot be applied as a final step. //There should be a better way to do this. At the very least, it should be possible to avoid the 6 square roots involved currently. //If this shows a a bottleneck, it might be best to virtualize this function and implement a per-shape variant. //Also... It might be better just to have the internal function be a GetBoundingBox that takes an AffineTransform, and an outer function //does the local space fiddling. //Move forward into convex's space, backwards into the new space's local space. AffineTransform transform; AffineTransform.Invert(ref spaceTransform, out transform); AffineTransform.Multiply(ref shapeTransform, ref transform, out transform); //Sample the local directions from the orientation matrix, implicitly transposed. Vector3 right; var direction = new Vector3(transform.LinearTransform.M11, transform.LinearTransform.M21, transform.LinearTransform.M31); GetLocalExtremePoint(direction, out right); Vector3 left; direction = new Vector3(-transform.LinearTransform.M11, -transform.LinearTransform.M21, -transform.LinearTransform.M31); GetLocalExtremePoint(direction, out left); Vector3 up; direction = new Vector3(transform.LinearTransform.M12, transform.LinearTransform.M22, transform.LinearTransform.M32); GetLocalExtremePoint(direction, out up); Vector3 down; direction = new Vector3(-transform.LinearTransform.M12, -transform.LinearTransform.M22, -transform.LinearTransform.M32); GetLocalExtremePoint(direction, out down); Vector3 backward; direction = new Vector3(transform.LinearTransform.M13, transform.LinearTransform.M23, transform.LinearTransform.M33); GetLocalExtremePoint(direction, out backward); Vector3 forward; direction = new Vector3(-transform.LinearTransform.M13, -transform.LinearTransform.M23, -transform.LinearTransform.M33); GetLocalExtremePoint(direction, out forward); //Rather than transforming each axis independently (and doing three times as many operations as required), just get the 6 required values directly. Vector3 positive, negative; TransformLocalExtremePoints(ref right, ref up, ref backward, ref transform.LinearTransform, out positive); TransformLocalExtremePoints(ref left, ref down, ref forward, ref transform.LinearTransform, out negative); //The positive and negative vectors represent the X, Y and Z coordinates of the extreme points in world space along the world space axes. boundingBox.Max.X = transform.Translation.X + positive.X; boundingBox.Max.Y = transform.Translation.Y + positive.Y; boundingBox.Max.Z = transform.Translation.Z + positive.Z; boundingBox.Min.X = transform.Translation.X + negative.X; boundingBox.Min.Y = transform.Translation.Y + negative.Y; boundingBox.Min.Z = transform.Translation.Z + negative.Z; }
///<summary> /// Tests a ray against the instance. ///</summary> ///<param name="ray">Ray to test.</param> ///<param name="maximumLength">Maximum length of the ray to test; in units of the ray's direction's length.</param> ///<param name="sidedness">Sidedness to use during the ray cast. This does not have to be the same as the mesh's sidedness.</param> ///<param name="rayHit">The hit location of the ray on the mesh, if any.</param> ///<returns>Whether or not the ray hit the mesh.</returns> public bool RayCast(Ray ray, float maximumLength, TriangleSidedness sidedness, out RayHit rayHit) { //Put the ray into local space. Ray localRay; AffineTransform inverse; AffineTransform.Invert(ref worldTransform, out inverse); Matrix3x3.Transform(ref ray.Direction, ref inverse.LinearTransform, out localRay.Direction); AffineTransform.Transform(ref ray.Position, ref inverse, out localRay.Position); if (Shape.TriangleMesh.RayCast(localRay, maximumLength, sidedness, out rayHit)) { //Transform the hit into world space. Vector3.Multiply(ref ray.Direction, rayHit.T, out rayHit.Location); Vector3.Add(ref rayHit.Location, ref ray.Position, out rayHit.Location); Matrix3x3.TransformTranspose(ref rayHit.Normal, ref inverse.LinearTransform, out rayHit.Normal); return(true); } rayHit = new RayHit(); return(false); }
//public unsafe static void TestMultiplyCorrectness() //{ // const int iterationCount = 100000; // Random random = new Random(5); // for (int iterationIndex = 0; iterationIndex < iterationCount; ++iterationIndex) // { // AffineTransform simdA, simdB; // bAffineTransform scalarA, scalarB; // var simdPointerA = (float*)&simdA; // var scalarPointerA = (float*)&scalarA; // var simdPointerB = (float*)&simdB; // var scalarPointerB = (float*)&scalarB; // for (int i = 0; i < 12; ++i) // { // scalarPointerA[i] = simdPointerA[i] = (float)(random.NextDouble() * 4 - 2); // scalarPointerB[i] = simdPointerB[i] = (float)(random.NextDouble() * 4 - 2); // } // AffineTransform simdResult; // AffineTransform.Multiply(ref simdA, ref simdB, out simdResult); // bAffineTransform scalarResult; // bAffineTransform.Multiply(ref scalarA, ref scalarB, out scalarResult); // var simdPointerResult = (float*)&simdResult; // var scalarPointerResult = (float*)&scalarResult; // for (int i = 0; i < 12; ++i) // { // const float threshold = 1e-5f; // var simdScalarError = Math.Abs(simdPointerResult[i] - scalarPointerResult[i]); // if (simdScalarError > threshold) // { // Console.WriteLine($"Excess error for {i}"); // } // } // } // for (int iterationIndex = 0; iterationIndex < iterationCount; ++iterationIndex) // { // AffineTransform simdA, simdB; // bAffineTransform scalarA, scalarB; // var simdPointerA = (float*)&simdA; // var scalarPointerA = (float*)&scalarA; // var simdPointerB = (float*)&simdB; // var scalarPointerB = (float*)&scalarB; // for (int i = 0; i < 12; ++i) // { // scalarPointerA[i] = simdPointerA[i] = (float)(random.NextDouble() * 4 - 2); // scalarPointerB[i] = simdPointerB[i] = (float)(random.NextDouble() * 4 - 2); // } // AffineTransform.Multiply(ref simdA, ref simdB, out simdA); // bAffineTransform.Multiply(ref scalarA, ref scalarB, out scalarA); // for (int i = 0; i < 12; ++i) // { // const float threshold = 1e-5f; // var simdScalarError = Math.Abs(simdPointerA[i] - scalarPointerA[i]); // if (simdScalarError > threshold) // { // Console.WriteLine($"Excess error for {i}"); // } // } // } // for (int iterationIndex = 0; iterationIndex < iterationCount; ++iterationIndex) // { // AffineTransform simdA, simdB; // bAffineTransform scalarA, scalarB; // var simdPointerA = (float*)&simdA; // var scalarPointerA = (float*)&scalarA; // var simdPointerB = (float*)&simdB; // var scalarPointerB = (float*)&scalarB; // for (int i = 0; i < 12; ++i) // { // scalarPointerA[i] = simdPointerA[i] = (float)(random.NextDouble() * 4 - 2); // scalarPointerB[i] = simdPointerB[i] = (float)(random.NextDouble() * 4 - 2); // } // AffineTransform.Multiply(ref simdA, ref simdB, out simdB); // bAffineTransform.Multiply(ref scalarA, ref scalarB, out scalarB); // for (int i = 0; i < 12; ++i) // { // const float threshold = 1e-5f; // var simdScalarError = Math.Abs(simdPointerB[i] - scalarPointerB[i]); // if (simdScalarError > threshold) // { // Console.WriteLine($"Excess error for {i}"); // } // } // } // for (int iterationIndex = 0; iterationIndex < iterationCount; ++iterationIndex) // { // AffineTransform simd; // bAffineTransform scalar; // var simdPointer = (float*)&simd; // var scalarPointer = (float*)&scalar; // for (int i = 0; i < 12; ++i) // { // scalarPointer[i] = simdPointer[i] = (float)(random.NextDouble() * 4 - 2); // } // AffineTransform.Multiply(ref simd, ref simd, out simd); // bAffineTransform.Multiply(ref scalar, ref scalar, out scalar); // for (int i = 0; i < 12; ++i) // { // const float threshold = 1e-5f; // var simdScalarError = Math.Abs(simdPointer[i] - scalarPointer[i]); // if (simdScalarError > threshold) // { // Console.WriteLine($"Excess error for {i}"); // } // } // } //} //public static float TestScalarInvert(int iterationCount) //{ // bAffineTransform m = bAffineTransform.Identity; // float accumulator = 0; // for (int i = 0; i < iterationCount; ++i) // { // bAffineTransform r0, r1; // bAffineTransform.Invert(ref m, out r0); // bAffineTransform.Invert(ref r0, out r1); // bAffineTransform.Invert(ref r1, out r0); // bAffineTransform.Invert(ref r0, out r1); // bAffineTransform.Invert(ref r1, out r0); // bAffineTransform.Invert(ref r0, out r1); // bAffineTransform.Invert(ref r1, out r0); // bAffineTransform.Invert(ref r0, out r1); // bAffineTransform.Invert(ref r1, out r0); // bAffineTransform.Invert(ref r0, out r1); // accumulator += r1.Translation.X; // } // return accumulator; //} public static float TestSIMDInvert(int iterationCount) { AffineTransform m = AffineTransform.Identity; float accumulator = 0; for (int i = 0; i < iterationCount; ++i) { AffineTransform r0, r1; AffineTransform.Invert(m, out r0); AffineTransform.Invert(r0, out r1); AffineTransform.Invert(r1, out r0); AffineTransform.Invert(r0, out r1); AffineTransform.Invert(r1, out r0); AffineTransform.Invert(r0, out r1); AffineTransform.Invert(r1, out r0); AffineTransform.Invert(r0, out r1); AffineTransform.Invert(r1, out r0); AffineTransform.Invert(r0, out r1); accumulator += r1.Translation.X; } return(accumulator); }
/// <summary> /// Gets the bounding box of the mesh transformed first into world space, and then into the local space of another affine transform. /// </summary> /// <param name="shapeTransform">Transform to use to put the shape into world space.</param> /// <param name="spaceTransform">Used as the frame of reference to compute the bounding box. /// In effect, the shape is transformed by the inverse of the space transform to compute its bounding box in local space.</param> /// <param name="boundingBox">Bounding box in the local space.</param> public void GetLocalBoundingBox(ref RigidTransform shapeTransform, ref AffineTransform spaceTransform, out BoundingBox boundingBox) { #if !WINDOWS boundingBox = new BoundingBox(); #endif //TODO: This method peforms quite a few sqrts because the collision margin can get scaled, and so cannot be applied as a final step. //There should be a better way to do this. //Additionally, this bounding box is not consistent in all cases with the post-add version. Adding the collision margin at the end can //slightly overestimate the size of a margin expanded shape at the corners, which is fine (and actually important for the box-box special case). //Move forward into convex's space, backwards into the new space's local space. AffineTransform transform; AffineTransform.Invert(ref spaceTransform, out transform); AffineTransform.Multiply(ref shapeTransform, ref transform, out transform); GetBoundingBox(ref transform.LinearTransform, out boundingBox); boundingBox.Max.X += transform.Translation.X; boundingBox.Max.Y += transform.Translation.Y; boundingBox.Max.Z += transform.Translation.Z; boundingBox.Min.X += transform.Translation.X; boundingBox.Min.Y += transform.Translation.Y; boundingBox.Min.Z += transform.Translation.Z; }
public unsafe static void TestInversionCorrectness() { Random random = new Random(5); for (int iterationIndex = 0; iterationIndex < 1000; ++iterationIndex) { bAffineTransform scalar; AffineTransform simd; var scalarPointer = (float *)&scalar; var simdPointer = (float *)&simd; //Create a guaranteed invertible transform. scalar.LinearTransform = bMatrix3x3.CreateFromAxisAngle( bVector3.Normalize(new bVector3( 0.1f + (float)random.NextDouble(), 0.1f + (float)random.NextDouble(), 0.1f + (float)random.NextDouble())), (float)random.NextDouble()); scalar.Translation = new bVector3((float)random.NextDouble() * 10, (float)random.NextDouble() * 10, (float)random.NextDouble() * 10); for (int i = 0; i < 12; ++i) { simdPointer[i] = scalarPointer[i]; } bAffineTransform.Invert(ref scalar, out scalar); AffineTransform.Invert(ref simd, out simd); for (int i = 0; i < 12; ++i) { var errorSimd = Math.Abs(simdPointer[i] - scalarPointer[i]); Assert.IsTrue(errorSimd < 1e-5f); } } }
///<summary> /// Tests a ray against the terrain shape. ///</summary> ///<param name="ray">Ray to test against the shape.</param> ///<param name="maximumLength">Maximum length of the ray in units of the ray direction's length.</param> ///<param name="transform">Transform to apply to the terrain shape during the test.</param> ///<param name="sidedness">Sidedness of the triangles to use when raycasting.</param> ///<param name="hit">Hit data of the ray cast, if any.</param> ///<returns>Whether or not the ray hit the transformed terrain shape.</returns> public bool RayCast(ref Ray ray, float maximumLength, ref AffineTransform transform, TriangleSidedness sidedness, out RayHit hit) { hit = new RayHit(); //Put the ray into local space. Ray localRay; AffineTransform inverse; AffineTransform.Invert(ref transform, out inverse); Matrix3X3.Transform(ref ray.Direction, ref inverse.LinearTransform, out localRay.Direction); AffineTransform.Transform(ref ray.Position, ref inverse, out localRay.Position); //Use rasterizey traversal. //The origin is at 0,0,0 and the map goes +X, +Y, +Z. //if it's before the origin and facing away, or outside the max and facing out, early out. float maxX = heights.GetLength(0) - 1; float maxZ = heights.GetLength(1) - 1; Vector3 progressingOrigin = localRay.Position; float distance = 0; //Check the outside cases first. if (progressingOrigin.X < 0) { if (localRay.Direction.X > 0) { //Off the left side. float timeToMinX = -progressingOrigin.X / localRay.Direction.X; distance += timeToMinX; Vector3 increment; Vector3.Multiply(ref localRay.Direction, timeToMinX, out increment); Vector3.Add(ref increment, ref progressingOrigin, out progressingOrigin); } else { return(false); //Outside and pointing away from the terrain. } } else if (progressingOrigin.X > maxX) { if (localRay.Direction.X < 0) { //Off the left side. float timeToMinX = -(progressingOrigin.X - maxX) / localRay.Direction.X; distance += timeToMinX; Vector3 increment; Vector3.Multiply(ref localRay.Direction, timeToMinX, out increment); Vector3.Add(ref increment, ref progressingOrigin, out progressingOrigin); } else { return(false); //Outside and pointing away from the terrain. } } if (progressingOrigin.Z < 0) { if (localRay.Direction.Z > 0) { float timeToMinZ = -progressingOrigin.Z / localRay.Direction.Z; distance += timeToMinZ; Vector3 increment; Vector3.Multiply(ref localRay.Direction, timeToMinZ, out increment); Vector3.Add(ref increment, ref progressingOrigin, out progressingOrigin); } else { return(false); } } else if (progressingOrigin.Z > maxZ) { if (localRay.Direction.Z < 0) { float timeToMinZ = -(progressingOrigin.Z - maxZ) / localRay.Direction.Z; distance += timeToMinZ; Vector3 increment; Vector3.Multiply(ref localRay.Direction, timeToMinZ, out increment); Vector3.Add(ref increment, ref progressingOrigin, out progressingOrigin); } else { return(false); } } if (distance > maximumLength) { return(false); } //By now, we should be entering the main body of the terrain. int xCell = (int)progressingOrigin.X; int zCell = (int)progressingOrigin.Z; //If it's hitting the border and going in, then correct the index //so that it will initially target a valid quad. //Without this, a quad beyond the border would be tried and failed. if (xCell == heights.GetLength(0) - 1 && localRay.Direction.X < 0) { xCell = heights.GetLength(0) - 2; } if (zCell == heights.GetLength(1) - 1 && localRay.Direction.Z < 0) { zCell = heights.GetLength(1) - 2; } while (true) { //Check for a miss. if (xCell < 0 || zCell < 0 || xCell >= heights.GetLength(0) - 1 || zCell >= heights.GetLength(1) - 1) { return(false); } //Test the triangles of this cell. Vector3 v1, v2, v3, v4; // v3 v4 // v1 v2 GetLocalPosition(xCell, zCell, out v1); GetLocalPosition(xCell + 1, zCell, out v2); GetLocalPosition(xCell, zCell + 1, out v3); GetLocalPosition(xCell + 1, zCell + 1, out v4); RayHit hit1, hit2; bool didHit1; bool didHit2; //Don't bother doing ray intersection tests if the ray can't intersect it. float highest = v1.Y; float lowest = v1.Y; if (v2.Y > highest) { highest = v2.Y; } else if (v2.Y < lowest) { lowest = v2.Y; } if (v3.Y > highest) { highest = v3.Y; } else if (v3.Y < lowest) { lowest = v3.Y; } if (v4.Y > highest) { highest = v4.Y; } else if (v4.Y < lowest) { lowest = v4.Y; } if (!(progressingOrigin.Y > highest && localRay.Direction.Y > 0 || progressingOrigin.Y < lowest && localRay.Direction.Y < 0)) { if (quadTriangleOrganization == QuadTriangleOrganization.BottomLeftUpperRight) { //Always perform the raycast as if Y+ in local space is the way the triangles are facing. didHit1 = Toolbox.FindRayTriangleIntersection(ref localRay, maximumLength, sidedness, ref v1, ref v2, ref v3, out hit1); didHit2 = Toolbox.FindRayTriangleIntersection(ref localRay, maximumLength, sidedness, ref v2, ref v4, ref v3, out hit2); } else //if (quadTriangleOrganization == CollisionShapes.QuadTriangleOrganization.BottomRightUpperLeft) { didHit1 = Toolbox.FindRayTriangleIntersection(ref localRay, maximumLength, sidedness, ref v1, ref v2, ref v4, out hit1); didHit2 = Toolbox.FindRayTriangleIntersection(ref localRay, maximumLength, sidedness, ref v1, ref v4, ref v3, out hit2); } if (didHit1 && didHit2) { if (hit1.T < hit2.T) { Vector3.Multiply(ref ray.Direction, hit1.T, out hit.Location); Vector3.Add(ref hit.Location, ref ray.Position, out hit.Location); Matrix3X3.TransformTranspose(ref hit1.Normal, ref inverse.LinearTransform, out hit.Normal); hit.T = hit1.T; return(true); } Vector3.Multiply(ref ray.Direction, hit2.T, out hit.Location); Vector3.Add(ref hit.Location, ref ray.Position, out hit.Location); Matrix3X3.TransformTranspose(ref hit2.Normal, ref inverse.LinearTransform, out hit.Normal); hit.T = hit2.T; } else if (didHit1) { Vector3.Multiply(ref ray.Direction, hit1.T, out hit.Location); Vector3.Add(ref hit.Location, ref ray.Position, out hit.Location); Matrix3X3.TransformTranspose(ref hit1.Normal, ref inverse.LinearTransform, out hit.Normal); hit.T = hit1.T; return(true); } else if (didHit2) { Vector3.Multiply(ref ray.Direction, hit2.T, out hit.Location); Vector3.Add(ref hit.Location, ref ray.Position, out hit.Location); Matrix3X3.TransformTranspose(ref hit2.Normal, ref inverse.LinearTransform, out hit.Normal); hit.T = hit2.T; return(true); } } //Move to the next cell. float timeToX; if (localRay.Direction.X < 0) { timeToX = -(progressingOrigin.X - xCell) / localRay.Direction.X; } else if (ray.Direction.X > 0) { timeToX = (xCell + 1 - progressingOrigin.X) / localRay.Direction.X; } else { timeToX = float.MaxValue; } float timeToZ; if (localRay.Direction.Z < 0) { timeToZ = -(progressingOrigin.Z - zCell) / localRay.Direction.Z; } else if (localRay.Direction.Z > 0) { timeToZ = (zCell + 1 - progressingOrigin.Z) / localRay.Direction.Z; } else { timeToZ = float.MaxValue; } //Move to the next cell. if (timeToX < timeToZ) { if (localRay.Direction.X < 0) { xCell--; } else { xCell++; } distance += timeToX; if (distance > maximumLength) { return(false); } Vector3 increment; Vector3.Multiply(ref localRay.Direction, timeToX, out increment); Vector3.Add(ref increment, ref progressingOrigin, out progressingOrigin); } else { if (localRay.Direction.Z < 0) { zCell--; } else { zCell++; } distance += timeToZ; if (distance > maximumLength) { return(false); } Vector3 increment; Vector3.Multiply(ref localRay.Direction, timeToZ, out increment); Vector3.Add(ref increment, ref progressingOrigin, out progressingOrigin); } } }
/// <summary> /// Constructor for MultipleGradientPaintContext superclass. /// </summary> protected internal MultipleGradientPaintContext(MultipleGradientPaint mgp, ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform t, RenderingHints hints, float[] fractions, Color[] colors, CycleMethod cycleMethod, ColorSpaceType colorSpace) { if (deviceBounds == null) { throw new NullPointerException("Device bounds cannot be null"); } if (userBounds == null) { throw new NullPointerException("User bounds cannot be null"); } if (t == null) { throw new NullPointerException("Transform cannot be null"); } if (hints == null) { throw new NullPointerException("RenderingHints cannot be null"); } // The inverse transform is needed to go from device to user space. // Get all the components of the inverse transform matrix. AffineTransform tInv; try { // the following assumes that the caller has copied the incoming // transform and is not concerned about it being modified t.Invert(); tInv = t; } catch (NoninvertibleTransformException) { // just use identity transform in this case; better to show // (incorrect) results than to throw an exception and/or no-op tInv = new AffineTransform(); } double[] m = new double[6]; tInv.GetMatrix(m); A00 = (float)m[0]; A10 = (float)m[1]; A01 = (float)m[2]; A11 = (float)m[3]; A02 = (float)m[4]; A12 = (float)m[5]; // copy some flags this.CycleMethod = cycleMethod; this.ColorSpace = colorSpace; // we can avoid copying this array since we do not modify its values this.Fractions = fractions; // note that only one of these values can ever be non-null (we either // store the fast gradient array or the slow one, but never both // at the same time) int[] gradient = (mgp.Gradient != null) ? mgp.Gradient.get() : null; int[][] gradients = (mgp.Gradients != null) ? mgp.Gradients.get() : null; if (gradient == null && gradients == null) { // we need to (re)create the appropriate values CalculateLookupData(colors); // now cache the calculated values in the // MultipleGradientPaint instance for future use mgp.Model = this.Model; mgp.NormalizedIntervals = this.NormalizedIntervals; mgp.IsSimpleLookup = this.IsSimpleLookup; if (IsSimpleLookup) { // only cache the fast array mgp.FastGradientArraySize = this.FastGradientArraySize; mgp.Gradient = new SoftReference <int[]>(this.Gradient); } else { // only cache the slow array mgp.Gradients = new SoftReference <int[][]>(this.Gradients); } } else { // use the values cached in the MultipleGradientPaint instance this.Model = mgp.Model; this.NormalizedIntervals = mgp.NormalizedIntervals; this.IsSimpleLookup = mgp.IsSimpleLookup; this.Gradient = gradient; this.FastGradientArraySize = mgp.FastGradientArraySize; this.Gradients = gradients; } }