private Vec3[] verts; // the vertices #endregion Fields #region Constructors // Makes a ModelInput object // Verts is a list of all the vertices of the model, triangle_vertex_indices contains, for each triangle (first []) the indices of the 3 vertices (second []) public ModelInput(Vec3[] verts, int[][] triangle_vertex_indices) { int num_verts = verts.Length; // copy verts this.verts = new Vec3[num_verts]; for (int i = 0; i < num_verts; i++) this.verts[i] = verts[i]; int num_tris = triangle_vertex_indices.Length; // go from [][] to [,] array format t_v = new int[num_tris, 3]; t_e = new int[num_tris, 3]; for (int i = 0; i < num_tris; i++) for (int j = 0; j < 3; j++) t_v[i, j] = triangle_vertex_indices[i][j]; // find what pairs of verts have edges between 'em bool[,] edge_existence = new bool[num_verts, num_verts]; int[,] edge_index = new int[num_verts, num_verts]; for (int i = 0; i < num_tris; i++) for (int j = 0; j < 3; j++) { int x = t_v[i, j], y = t_v[i, (j + 1) % 3]; edge_existence[x, y] = edge_existence[y, x] = true; } // assign each edge an index int next_index = 0; for (int i = 0; i < num_verts; i++) for (int j = i + 1; j < num_verts; j++) if (edge_existence[i, j]) edge_index[i, j] = edge_index[j, i] = next_index++; int num_edges = next_index; // find out the indices of the two endpoints of each edge e_t = new int[num_edges, 2]; e_v = new int[num_edges, 2]; for (int i = 0; i < num_verts; i++) for (int j = i + 1; j < num_verts; j++) if (edge_existence[i, j]) { int index = edge_index[i, j]; e_v[index, 0] = i; e_v[index, 1] = j; e_t[index, 0] = e_t[index, 1] = -1; } // find out which edges separate which triangles for (int i = 0; i < num_tris; i++) for (int j = 0; j < 3; j++) { int index = t_e[i, j] = edge_index[t_v[i, j], t_v[i, (j + 1) % 3]]; if (e_t[index, 0] == -1) e_t[index, 0] = i; else e_t[index, 1] = i; } }
public VInfoReference[] InterpolateVInfos(Vec3 targetPosition) { VInfoReference[] vinfo = new VInfoReference[3]; Vec2 ij = Util.VectorToTriangleCoords(new Vec3[] { verts[0].position.xyz, verts[1].position.xyz, verts[2].position.xyz }, targetPosition); double[] weights = new double[] { 1.0 - (ij.x + ij.y), ij.x, ij.y }; vinfo[0] = new VInfoReference { objID = objID, index = verts[0].vinfo[0].index, weight = weights[0]}; vinfo[1] = new VInfoReference { objID = objID, index = verts[1].vinfo[0].index, weight = weights[1] }; vinfo[2] = new VInfoReference { objID = objID, index = verts[2].vinfo[0].index, weight = weights[2] }; return vinfo; }
// Returns an AABB expanded from the input AABB as necessary in order to include the specified point public static AABB ExpandedToFit(AABB input, Vec3 point) { AABB result = new AABB { array = EmptyBoundsArray }; double[] point_array = new double[] { point.x, point.y, point.z }; for (int dim = 0; dim < 3; dim++) { result.array[dim][0] = Math.Min(input.array[dim][0], point_array[dim]); result.array[dim][1] = Math.Max(input.array[dim][1], point_array[dim]); } return result; }
// adds the specified vert if it is not yet present, and returns the new vert's index // otherwise returns the index of the existing vert it's a duplicate of public int AddVertex(Vec3 vert) { int index = uniqueVerts.FindIndex((v) => (v - vert).ComputeMagnitudeSquared() < 0.0000000001); if (index == -1) { uniqueVerts.Add(vert); return uniqueVerts.Count - 1; } else return index; }
// Same, but as a member function public void Expand(Vec3 b) { Vec3 dif = b - center; double distance = dif.ComputeMagnitude(); // degenerate cases... one sphere completely inside the other if (radius > distance) return; // otherwise some actual work must be done... not too bad though double oldradius = radius; radius = (distance + radius) * 0.5; center += Vec3.Normalize(dif, radius - oldradius); }
public static BasicModelVert Interpolate(BasicModelVert[] verts, double[] weights) { Vec3 position = Vec3.Zero; Vec3 normal = Vec3.Zero; Vec2 uv = Vec2.Zero; for (int i = 0; i < weights.Length; i++) { position += verts[i].position * weights[i]; normal += verts[i].normal * weights[i]; uv += verts[i].uv * weights[i]; } normal /= normal.ComputeMagnitude(); return new BasicModelVert { position = position, normal = normal, uv = uv }; }
// Return a BoudingSphere which fully contains the specified bounding sphere and vertex // Like Combine, but simpler because the added point hasn't got a radius public static BoundingSphere Expand(BoundingSphere a, Vec3 b) { Vec3 dif = b - a.center; double distance = dif.ComputeMagnitude(); // degenerate cases... one sphere completely inside the other if (a.radius > distance) return new BoundingSphere { center = a.center, radius = a.radius }; // otherwise some actual work must be done... not too bad though double radius = (distance + a.radius) * 0.5; Vec3 center = a.center + Vec3.Normalize(dif, radius - a.radius); return new BoundingSphere { center = center, radius = radius }; }
// Finds the intersection of the line and plane, and returns true if there is one // If there is no intersection, or the line is entirely within the plane, it returns false and the output position is the origin of the line public static bool IntersectPlane(Line line, Plane plane, out Vec3 pos) { Vec3 dir = Vec3.Normalize(line.direction); double dir_dot = Vec3.Dot(ref dir, ref plane.normal); if (dir_dot == 0.0) { pos = line.origin; return false; } else { double origin_dot = Vec3.Dot(ref line.origin, ref plane.normal); double tti = (plane.offset - origin_dot) / dir_dot; pos = line.origin + dir * tti; return true; } }
// Utility function to figure out how well a subdivision point splits stuff public int[,,] CheckSubdivision(Vec3 split) { int[, ,] results = new int[2, 2, 2]; double[][] array = new double[][] { new double[] { bounds.array[0][0], split.x, bounds.array[0][1] }, new double[] { bounds.array[1][0], split.y, bounds.array[1][1] }, new double[] { bounds.array[2][0], split.z, bounds.array[2][1] } }; for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) { AABB subregion = new AABB { array = new double[][] { new double[] { array[0][i], array[0][i + 1] }, new double[] { array[1][j], array[1][j + 1] }, new double[] { array[2][k], array[2][k + 1] } } }; foreach (Item item in items) if (AABB.CheckIntersection(item.aabb, subregion)) results[i, j, k]++; } return results; }
// Subdivides this PartitioningGrid, splitting it at the specified point (which ought to be insidwe this grid's AABB) public void Subdivide(Vec3 split) { this.split = split; int max_child_subd = max_subdivisions - 1; children = new PartitioningGrid[2, 2, 2]; double[][] array = new double[][] { new double[] { bounds.array[0][0], split.x, bounds.array[0][1] }, new double[] { bounds.array[1][0], split.y, bounds.array[1][1] }, new double[] { bounds.array[2][0], split.z, bounds.array[2][1] } }; for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) { children[i, j, k] = new PartitioningGrid(new AABB { array = new double[][] { new double[] { array[0][i], array[0][i + 1] }, new double[] { array[1][j], array[1][j + 1] }, new double[] { array[2][k], array[2][k + 1] } } }, max_child_subd); } }
// Creates a Plane object matching the plane of the specified triangle public static Plane FromTriangleVertices(Vec3 a, Vec3 b, Vec3 c) { Vec3 normal = Vec3.Cross(b - a, c - a); return FromPositionNormal(a, normal); }
// Creates a plane with the specified normal, containing the specified position public static Plane FromPositionNormal(Vec3 pos, Vec3 normal) { Vec3 uNorm = Vec3.Normalize(normal); double dot = Vec3.Dot(pos, uNorm); return new Plane { normal = uNorm, offset = dot }; }
public CSGVertex GetVertex(Vec3 position, bool create) { int index = vertices.FindIndex((v) => (v.position - position).ComputeMagnitudeSquared() < 0.0001); if (index == -1) if (!create) return null; else { vertices.Add(new CSGVertex(position)); return vertices[vertices.Count - 1]; } else return vertices[index]; }
// Get an orthonormal matrix as close to the given matrix as possible public static Mat3 Normalize(Mat3 mat) { Vec3 a = new Vec3 { x = mat[0], y = mat[1], z = mat[2] }; Vec3 b = new Vec3 { x = mat[3], y = mat[4], z = mat[5] }; Vec3 c = Vec3.Cross(a, b); a = a * (1.0 / a.ComputeMagnitude()); return new Mat3 { values = new double[] { a.x, a.y, a.z, b.x, b.y, b.z, c.x, c.y, c.z } }; }
// Subdivides this ModelIntersectTree, splitting it at the specified point (which ought to be insidwe this grid's AABB) public void Subdivide(Vec3 split) { this.split = split; int max_child_subd = max_subdivisions - 1; children = new ModelIntersectTree[2, 2, 2]; double[][] array = new double[][] { new double[] { bounds.array[0][0], split.x, bounds.array[0][1] }, new double[] { bounds.array[1][0], split.y, bounds.array[1][1] }, new double[] { bounds.array[2][0], split.z, bounds.array[2][1] } }; for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) { children[i, j, k] = new ModelIntersectTree(new AABB { array = new double[][] { new double[] { array[0][i], array[0][i + 1] }, new double[] { array[1][j], array[1][j + 1] }, new double[] { array[2][k], array[2][k + 1] } } }, max_child_subd); } foreach (ModelIntersectTree child in children) { foreach (Octree.Item item in items[0]) child.InsertItem(0, item); foreach (Octree.Item item in items[1]) child.InsertItem(1, item); } }
// Creates a translation matrix public static Mat4 Translation(Vec3 translation) { return Translation(translation.x, translation.y, translation.z); }
public static Mat4 FromPosOriScale(Vec3 pos, Mat3 ori, double scale) { return new Mat4 { values = new double[] { ori[0] * scale, ori[3] * scale, ori[6] * scale, pos.x, ori[1] * scale, ori[4] * scale, ori[7] * scale, pos.y, ori[2] * scale, ori[5] * scale, ori[8] * scale, pos.z, 0, 0, 0, 1 } }; }
// Subdivides this PartitioningGrid, splitting it at the specified point (which ought to be insidwe this grid's AABB) public void Subdivide(Vec3 split) { this.split = split; children = new Octree[2, 2, 2]; double[][] array = new double[][] { new double[] { bounds.array[0][0], split.x, bounds.array[0][1] }, new double[] { bounds.array[1][0], split.y, bounds.array[1][1] }, new double[] { bounds.array[2][0], split.z, bounds.array[2][1] } }; for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) { children[i, j, k] = Subdivision(new AABB { array = new double[][] { new double[] { array[0][i], array[0][i + 1] }, new double[] { array[1][j], array[1][j + 1] }, new double[] { array[2][k], array[2][k + 1] } } }); } foreach (Octree child in children) foreach (Item item in items) child.InsertItem(item); }
// Finds the intersection of two triangles // If the triangles are coplanar or on parallel planes, returns null // If there is an intersection, returns the A and B triangles' IJ coordinates of the endpoints of the intersecting line segment // The first indexer is which triangle, and the second is which of the two endpoints public static Vec2[,] TriangleTriangleIntersection(Vec3[] a_verts, Vec3[] b_verts) { Plane a_plane = Plane.FromTriangleVertices(a_verts[0], a_verts[1], a_verts[2]); Plane b_plane = Plane.FromTriangleVertices(b_verts[0], b_verts[1], b_verts[2]); Line line; if (!Plane.Intersect(a_plane, b_plane, out line)) return null; Vec2 a_line_origin = VectorToTriangleCoords(a_verts, a_plane.normal, line.origin); Vec2 a_line_direction = VectorToTriangleCoords(a_verts, a_plane.normal, line.origin + line.direction) - a_line_origin; Vec2 b_line_origin = VectorToTriangleCoords(b_verts, b_plane.normal, line.origin); Vec2 b_line_direction = VectorToTriangleCoords(b_verts, b_plane.normal, line.origin + line.direction) - b_line_origin; double a_dot = Vec3.Dot(line.direction, a_plane.normal), b_dot = Vec3.Dot(line.direction, b_plane.normal); double a_min, a_max; if (!LineIntersectIJTriangle(a_line_origin, a_line_direction, out a_min, out a_max)) return null; double b_min, b_max; if (!LineIntersectIJTriangle(b_line_origin, b_line_direction, out b_min, out b_max)) return null; if (a_max < b_min || b_max < a_min) return null; double min = Math.Max(a_min, b_min), max = Math.Min(a_max, b_max); Vec2[,] result = new Vec2[2, 2]; result[0, 0] = a_line_origin + a_line_direction * min; result[0, 1] = a_line_origin + a_line_direction * max; result[1, 0] = b_line_origin + b_line_direction * min; result[1, 1] = b_line_origin + b_line_direction * max; return result; }
private static void TransformAndAddVec3(List<Vec3> list, Vec3 input, CopyAndTransformVert xform) { Vec3 result; xform(input, out result); list.Add(result); }
// Basically the same function as below, but it will auto-compute the normal vector // Use the other one if you're going to be using this on the same triangle repeatedly, so you can cache that value instead of recomputing it every time public static Vec2 VectorToTriangleCoords(Vec3[] tri_verts, Vec3 point_of_interest) { return VectorToTriangleCoords(tri_verts, Vec3.Normalize(Vec3.Cross(tri_verts[1] - tri_verts[0], tri_verts[2] - tri_verts[0])), point_of_interest); }
// Returns a Vec2 representing the PoI in the triangle's coordinate system // A value of (0,0) corresponds to the 1st vertex, (1,0) corresponds to the 2nd vertex, (0,1) corresponds to the 3rd vertex public static Vec2 VectorToTriangleCoords(Vec3[] tri_verts, Vec3 tri_normal, Vec3 point_of_interest) { Vec3 relative = point_of_interest - tri_verts[0]; Vec3 b_minus_a = tri_verts[1] - tri_verts[0]; Vec3 c_minus_a = tri_verts[2] - tri_verts[0]; Vec3 P = Vec3.Cross(c_minus_a, tri_normal), Q = Vec3.Cross(b_minus_a, tri_normal); double div_x = 1.0 / Vec3.Dot(P, b_minus_a); double div_y = 1.0 / Vec3.Dot(Q, c_minus_a); Vec2 result = new Vec2 { x = Vec3.Dot(P, relative) * div_x, y = Vec3.Dot(Q, relative) * div_y }; return result; }
// Returns distance of the point from the plane // It's signed, so one side has negative values public double PointDistance(Vec3 point) { return Vec3.Dot(normal, point) - offset; }
public CSGVertex(Vec3 pos) : this() { position = pos; }
// A 4x4 matrix representing an object at a specific position, with a specific orientation public static Mat4 FromPositionAndOrientation(Vec3 pos, Mat3 mat) { return new Mat4 { values = new double[] { mat[0], mat[3], mat[6], pos.x, mat[1], mat[4], mat[7], pos.y, mat[2], mat[5], mat[8], pos.z, 0, 0, 0, 1 } }; }
public static Mat3 FromScaledAxis(Vec3 xyz) { return FromScaledAxis(xyz.x, xyz.y, xyz.z); }
// Creates a rotation around a point // TODO: check whether this actually works or not !!! public static Mat4 RotationAroundPoint(Mat3 rot, Vec3 point) { Mat3 ident = Mat3.Identity; return Mat4.FromPositionAndOrientation((rot * point), ident) * Mat4.FromMat3(rot) * Mat4.FromPositionAndOrientation(-(point), ident); }
// Does a proper inverse (as opposed to a transpose, which conveniently happens to be the same as inverse IF we're using an orthonormal matrix) public static Mat3 Invert(Mat3 matrix) { /* double det = matrix.Determinant, inv = 1.0 / det, ninv = -inv; double[] v = matrix.values; // for convenience (maybe also faster?) return new Mat3 { values = new double[] { (v[4] * v[8] - v[5] * v[7]) * inv, (v[3] * v[8] - v[5] * v[6]) * ninv, (v[3] * v[7] - v[4] * v[6]) * inv, (v[1] * v[8] - v[2] * v[7]) * ninv, (v[0] * v[8] - v[2] * v[6]) * inv, (v[0] * v[7] - v[1] * v[6]) * ninv, (v[1] * v[5] - v[2] * v[4]) * inv, (v[0] * v[5] - v[2] * v[3]) * ninv, (v[0] * v[4] - v[1] * v[3]) * inv } }; */ double inv = 1.0 / matrix.Determinant; Vec3 x0 = new Vec3 { x = matrix[0], y = matrix[3], z = matrix[6] }; Vec3 x1 = new Vec3 { x = matrix[1], y = matrix[4], z = matrix[7] }; Vec3 x2 = new Vec3 { x = matrix[2], y = matrix[5], z = matrix[8] }; Vec3 y0 = Vec3.Cross(x1, x2); Vec3 y1 = Vec3.Cross(x2, x0); Vec3 y2 = Vec3.Cross(x0, x1); return new Mat3 { values = new double[] { y0.x * inv, y0.y * inv, y0.z * inv, y1.x * inv, y1.y * inv, y1.z * inv, y2.x * inv, y2.y * inv, y2.z * inv } }; }
// Transforms a 4-component vector by this Mat4 // Returns the xyz of the result divided by the w of the result public Vec3 TransformVec3(Vec3 xyz, double w) { Vec4 result = this * Vec4.FromVec3(xyz, w); return result.w == 0 ? result.Xyz : result.Xyz / result.w; }
// Note: result is number of times direction must be repeated to hit plane public static double RayPlaneIntersect(Vec3 rayOrigin, Vec3 rayDirection, Vec3 planeNormal, double planeOffset) { double dir_dot = Vec3.Dot(ref rayDirection, ref planeNormal); double origin_dot = Vec3.Dot(ref rayOrigin, ref planeNormal); return (planeOffset - origin_dot) / dir_dot; }