//todo -- this needs to fleshed out more once weapons work. //probably dont want an object for each point. Probably want //to add a gunslot component here to contain this data instead. private GameObject CreateMissilePoints() { if (!model.HasMissileSlots) { return(null); } GameObject retn = new GameObject("Missile Points"); List <MissileSlot> missileSlots = model.missileSlots; for (int i = 0; i < missileSlots.Count; i++) { MissileSlot slot = missileSlots[i]; for (int j = 0; j < slot.missilePoints.Length; j++) { GameObject go = new GameObject("Missile Point " + i + " -- " + j); PositionNormal missilePoint = slot.missilePoints[j]; go.transform.parent = retn.transform; go.transform.position = missilePoint.point; if (missilePoint.normal == Vector3.zero) { missilePoint.normal = Vector3.forward; } go.transform.rotation = Quaternion.LookRotation(missilePoint.normal, Vector3.up); } } return(retn); }
//todo -- this needs to fleshed out more once weapons work. //probably dont want an object for each point. Probably want //to add a gunslot component here to contain this data instead. private GameObject CreateGunPoints() { if (!model.HasGunSlots) { return(null); } GameObject retn = new GameObject("Gun Points"); List <GunSlot> gunSlots = model.gunSlots; for (int i = 0; i < gunSlots.Count; i++) { GunSlot slot = gunSlots[i]; for (int j = 0; j < slot.gunPoints.Length; j++) { GameObject go = new GameObject("Gun Point " + i + " -- " + j); PositionNormal gunPoint = slot.gunPoints[j]; go.transform.parent = retn.transform; go.transform.position = gunPoint.point; if (gunPoint.normal == Vector3.zero) { gunPoint.normal = Vector3.forward; } go.transform.rotation = Quaternion.LookRotation(gunPoint.normal, Vector3.up); } } return(retn); }
private void CalculateNormal(PositionNormal v1, PositionNormal v2, PositionNormal v3) { Vector3 vu = v3.Position - v1.Position; Vector3 vt = v2.Position - v1.Position; Vector3 normal = Vector3.Cross(vu, vt); normal.Normalize(); v1.Normal += normal; v2.Normal += normal; v3.Normal += normal; }
public void GetObjectPositionOnWater(ObjectGeometry obj, WaterShader shader) { PositionNormal p11 = new PositionNormal(); PositionNormal p12 = new PositionNormal(); PositionNormal p13 = new PositionNormal(); PositionNormal p21 = new PositionNormal(); PositionNormal p22 = new PositionNormal(); PositionNormal p23 = new PositionNormal(); PositionNormal p31 = new PositionNormal(); PositionNormal p32 = new PositionNormal(); PositionNormal p33 = new PositionNormal(); Vector3 position = obj.ModelMatrix.Translation; position.Y = 20; p11.Position = CalculatePosition(Vector3.Transform(position + new Vector3(-5, 0, 5), obj.RotationMatrix), shader); p12.Position = CalculatePosition(Vector3.Transform(position + new Vector3(0, 0, 5), obj.RotationMatrix), shader); p13.Position = CalculatePosition(Vector3.Transform(position + new Vector3(5, 0, 5), obj.RotationMatrix), shader); p21.Position = CalculatePosition(Vector3.Transform(position + new Vector3(-5, 0, 0), obj.RotationMatrix), shader); p22.Position = CalculatePosition(Vector3.Transform(position + new Vector3(0, 0, 0), obj.RotationMatrix), shader); p23.Position = CalculatePosition(Vector3.Transform(position + new Vector3(5, 0, 0), obj.RotationMatrix), shader); p31.Position = CalculatePosition(Vector3.Transform(position + new Vector3(-5, 0, -5), obj.RotationMatrix), shader); p32.Position = CalculatePosition(Vector3.Transform(position + new Vector3(0, 0, -5), obj.RotationMatrix), shader); p33.Position = CalculatePosition(Vector3.Transform(position + new Vector3(5, 0, -5), obj.RotationMatrix), shader); CalculateNormal(p11, p22, p23); CalculateNormal(p11, p12, p23); CalculateNormal(p12, p22, p23); CalculateNormal(p12, p13, p23); CalculateNormal(p21, p31, p32); CalculateNormal(p21, p22, p32); CalculateNormal(p22, p32, p33); CalculateNormal(p22, p23, p33); obj.Translate(new Vector3(position.X,p22.Position.Y,position.Z)); obj.Update(); obj.UpVector = p22.Normal; obj.RightVector = Vector3.Cross(obj.ForwardVector, obj.UpVector); obj.RightVector = Vector3.Normalize(obj.RightVector); obj.ForwardVector = Vector3.Cross(obj.UpVector, obj.RightVector); obj.ForwardVector = Vector3.Normalize(obj.ForwardVector); }
private PositionNormal[] CreatePointCloudData() { // Performance note for NVIDIA GTX 970 with 4 GB ram: // 20 mio points: CompleteRenderTime takes around 10 ms // 10 mio points: CompleteRenderTime is less than 0.1 ms // // On NVIDIA 1080 GTX with 8 GB ram the big performance drop happens around 40 mio points. int overallPointCount = (int)PointsCountComboBox.SelectedItem; //int overallPointCount = 100000; int sliceCount = 500; // 500 points in one horizontal circle if ((long)overallPointCount * (long)PositionNormal.SizeInBytes > (long)Int32.MaxValue) { throw new Exception("shadedPoints array too big. Split data into multiple arrays."); } var shadedPoints = new PositionNormal[overallPointCount]; //for (int i = 0; i < overallPointCount / sliceCount; i++) // Parallel improves performance of 50 mio points from 4378ms to 821ms Parallel.For(0, overallPointCount / sliceCount, (i) => { for (int j = 0; j < sliceCount; j++) { int index = sliceCount * i + j; float x = 30.0f * (float)Math.Cos(2 * Math.PI * (double)j / sliceCount); float y = 0.02f * i; float z = 30.0f * (float)Math.Sin(2 * Math.PI * (double)j / sliceCount); var normal = new Vector3(x, 0, z); normal.Normalize(); shadedPoints[index] = new PositionNormal() { Position = new Vector3(x, y, z), Normal = normal }; } }); return(shadedPoints); }
// Load position and normal data from a text file. // The first line in the file must be number of points. // Then each line represents one point with tab separated values (floats are in InvariantCulture with . as decimal separator). private PositionNormal[] LoadPointCloudData(string fileName) { PositionNormal[] shadedPoints = null; using (var fs = System.IO.File.OpenText(fileName)) { int index = -1; try { string oneLine = fs.ReadLine(); // First line should have number of rows int rowsCount = Int32.Parse(oneLine); index++; shadedPoints = new PositionNormal[rowsCount]; for (int i = 0; i < rowsCount; i++) { oneLine = fs.ReadLine(); var oneLineParts = oneLine.Split('\t'); // IMPORTANT: In WPF and DXEngine y is up so we need to swap y and z values shadedPoints[index].Position = new Vector3(float.Parse(oneLineParts[0], System.Globalization.CultureInfo.InvariantCulture), float.Parse(oneLineParts[2], System.Globalization.CultureInfo.InvariantCulture), float.Parse(oneLineParts[1], System.Globalization.CultureInfo.InvariantCulture)); shadedPoints[index].Normal = new Vector3(float.Parse(oneLineParts[3], System.Globalization.CultureInfo.InvariantCulture), float.Parse(oneLineParts[5], System.Globalization.CultureInfo.InvariantCulture), float.Parse(oneLineParts[4], System.Globalization.CultureInfo.InvariantCulture)); index++; } } catch (Exception ex) { MessageBox.Show(string.Format("Error reading point cloud data in line {0} of file:\r\n{1}\r\n\r\nError message:\r\n{2}", index + 1, fileName, ex.Message)); return(shadedPoints); } } return(shadedPoints); }
private MeshBase CreateTubePathMesh(Vector3[] pathPositions, float radius, int segmentsCount, bool isTubeClosed, Color4 tubeColor) { int pathPositionsCount = pathPositions.Length; // Preallocate the positions, indices, normals and textures collections - this prevents resizing the collection when the elements are added to them int totalPositionsCount = segmentsCount * pathPositionsCount; int shownSegmentsCount = pathPositionsCount - 1; int totalTriangleIndicesCount = shownSegmentsCount * segmentsCount * 2 * 3; // 2 triangles for each segment * No. of shown segments * 3 indices per triangle if (isTubeClosed) { totalTriangleIndicesCount += (segmentsCount - 2) * 2 * 3; // Add closing triangles (for triangle strip) } var vertexBuffer = new PositionNormal[totalPositionsCount]; var indexBuffer = new int[totalTriangleIndicesCount]; AddTubePathMesh(pathPositions, radius, segmentsCount, isTubeClosed, vertexBuffer, indexBuffer, vertexBufferStartIndex: 0, indexBufferStartIndex: 0); var simpleMesh = new SimpleMesh <PositionNormal>(vertexBuffer, indexBuffer, inputLayoutType: InputLayoutType.Position | InputLayoutType.Normal); // Quickly calculate mesh BoundingBox // If this is not done here, then BoundingBox is calculated in SimpleMesh with checking all the tube path's positions. // To prevent checking all the positions, we can simplify the calculation of bounding box with using // all path positions and then extending this for radius into all directions. // This will create slightly bigger bounding box but this is not a problem. var positionsBoundingBox = BoundingBox.FromPoints(pathPositions); simpleMesh.Bounds = new Bounds(new Vector3(positionsBoundingBox.Minimum.X - radius, positionsBoundingBox.Minimum.Y - radius, positionsBoundingBox.Minimum.Z - radius), new Vector3(positionsBoundingBox.Maximum.X + radius, positionsBoundingBox.Maximum.Y + radius, positionsBoundingBox.Maximum.Z + radius)); _disposables.Add(simpleMesh); return(simpleMesh); }
private void AddTubePathMesh(Vector3[] pathPositions, float radius, int segmentsCount, bool isTubeClosed, PositionNormal[] vertexBuffer, int[] indexBuffer, int vertexBufferStartIndex, int indexBufferStartIndex) { if (_lastSegmentsCount != segmentsCount) { _sinuses = new float[segmentsCount]; _cosines = new float[segmentsCount]; double angle = 0; double angleStep = 2 * Math.PI / segmentsCount; for (int i = 0; i < segmentsCount; i++) { _sinuses[i] = (float)Math.Sin(angle) * radius; _cosines[i] = (float)Math.Cos(angle) * radius; angle += angleStep; } _lastSegmentsCount = segmentsCount; } int pathPositionsCount = pathPositions.Length; int totalPositionsCount = segmentsCount * pathPositionsCount; Vector3 pathPosition1 = pathPositions[0]; Vector3 pathPosition2 = pathPositions[1]; Vector3 lastDirection = new Vector3(); // Calculate the perpendicular vectors for the first segment // First get the direction of the Vector3 pathSegmentDirection = pathPositions[1] - pathPosition1; pathSegmentDirection.Normalize(); // Now we calculate the vectors that are perpendicular (90 degrees) to the averageSegmentDirection // For heightDirection = (0, 1, 0) the values are: // p1 = (1, 0, 0) - right // p2 = (0, 0, -1) - into the screen Vector3 p1, p2; // two perpendicular vectors GetPerpendicularVectors(pathSegmentDirection, out p1, out p2); int vertexBufferIndex = vertexBufferStartIndex; int indexBufferIndex = indexBufferStartIndex; for (int i = 0; i < pathPositionsCount; i++) { // Get the used direction for segment - this is the average of directions of the previous and the next segment (except for first and last segment) Vector3 averageSegmentDirection; if (i == 0) { // first segment: use the direction of the first segment except when the path is closed - the we need to average the first and last segment averageSegmentDirection = pathSegmentDirection; } else if (i == pathPositionsCount - 1) { averageSegmentDirection = lastDirection; } else { // inner segments: average the direction of the previous and the next segment pathPosition2 = pathPositions[i + 1]; pathSegmentDirection = pathPosition2 - pathPosition1; pathSegmentDirection.Normalize(); // The direction of the inter-section circle is the average direction of the previous and next segment averageSegmentDirection = (lastDirection + pathSegmentDirection) * 0.5f; } // Calculate the new perpendicular vectors (p1 and p2) // We should not use the MathUtils.GetPerpendicularVectors because this could lead to sudden "flip" of a vector // Therefore we reuse the previous p1 and p2 vectors and use the new averageSegmentDirection to update the p1 and p2 values p1 = Vector3.Cross(p2, averageSegmentDirection); var p1LengthSquared = p1.X * p1.X + p1.Y * p1.Y + p1.Z * p1.Z; if (p1LengthSquared > 1e-6) { p2 = Vector3.Cross(averageSegmentDirection, p1); } else if (p1LengthSquared <= 1e-6) { // This could happen for the following data: //pathPositions = new Point3DCollection(new Point3D[] //{ // new Point3D(0, 0, 0), // new Point3D(10, 0, 0), // new Point3D(10, 0, 10), // new Point3D(-10, 0, 10) //}); GetPerpendicularVectors(averageSegmentDirection, out p1, out p2); } p1.Normalize(); p2.Normalize(); for (int j = 0; j < segmentsCount; j++) { float sin = _sinuses[j]; float cos = _cosines[j]; float x = sin * p1.X - cos * p2.X; float y = sin * p1.Y - cos * p2.Y; float z = sin * p1.Z - cos * p2.Z; var onePosition = new Vector3(pathPosition1.X + x, pathPosition1.Y + y, pathPosition1.Z + z); var oneNormal = new Vector3(x, y, z); oneNormal.Normalize(); vertexBuffer[vertexBufferIndex] = new PositionNormal(onePosition, oneNormal); vertexBufferIndex++; } pathPosition1 = pathPosition2; lastDirection = pathSegmentDirection; } if (isTubeClosed) { // Fill the start circle with simple triangle strip for (int i = 1; i < segmentsCount - 1; i++) { indexBuffer[indexBufferIndex] = 0; indexBuffer[indexBufferIndex + 1] = i + 1; indexBuffer[indexBufferIndex + 2] = i; indexBufferIndex += 3; } } // Setup triangle indices int startPos1 = 0; // Start position on the previous circle int startPos2 = segmentsCount; // Start position on this circle // Code for no texture coordinates for (int i = 0; i < pathPositionsCount - 1; i++) { int pos1 = startPos1; int pos2 = startPos2; for (int j = 1; j < segmentsCount; j++) { indexBuffer[indexBufferIndex] = pos1; indexBuffer[indexBufferIndex + 1] = pos1 + 1; indexBuffer[indexBufferIndex + 2] = pos2; indexBuffer[indexBufferIndex + 3] = pos1 + 1; indexBuffer[indexBufferIndex + 4] = pos2 + 1; indexBuffer[indexBufferIndex + 5] = pos2; pos1++; pos2++; indexBufferIndex += 6; } // No texture coordinates indexBuffer[indexBufferIndex] = pos1; indexBuffer[indexBufferIndex + 1] = startPos1; indexBuffer[indexBufferIndex + 2] = pos2; indexBuffer[indexBufferIndex + 3] = pos2; indexBuffer[indexBufferIndex + 4] = startPos1; indexBuffer[indexBufferIndex + 5] = startPos2; startPos1 += segmentsCount; startPos2 += segmentsCount; indexBufferIndex += 6; } if (isTubeClosed) { // Fill the end circle with simple triangle strip int lastCircleIndex = totalPositionsCount - segmentsCount; for (int i = 1; i < segmentsCount - 1; i++) { indexBuffer[indexBufferIndex] = lastCircleIndex; indexBuffer[indexBufferIndex + 1] = lastCircleIndex + i; indexBuffer[indexBufferIndex + 2] = lastCircleIndex + i + 1; indexBufferIndex += 3; } } }
private void CreateMorphedMesh() { if (_simpleMesh == null) { return; } var meshPositions = _meshGeometry3D.Positions; var meshNormals = _meshGeometry3D.Normals; var positionsCount = meshPositions.Count; // First store original positions into _originalMesh // This array will provide much faster access to morphed positions then if we would access the data from MeshGeometry3D object. var originalMesh = new PositionNormal[positionsCount]; for (int i = 0; i < positionsCount; i++) { originalMesh[i].Position = meshPositions[i].ToVector3(); originalMesh[i].Normal = meshNormals[i].ToVector3(); } _originalMesh = originalMesh; // Now create a copy of the original MeshGeometry3D (this is needed because after adjusting positions we also need to re-calculate normals) var morphedMeshGeometry3D = new MeshGeometry3D(); // Copy positions from original MeshGeometry3D - we also adjust the y position of all positions that are above the center of the mesh double yOffset = _originalMeshSizeY; double yMiddle = _meshGeometry3D.Bounds.Y + _originalMeshSizeY / 2.0f; // get mesh center y value morphedMeshGeometry3D.Positions = null; // Disconnect positions before changing (to prevent change notifications from slowing things down) for (int i = 0; i < positionsCount; i++) { var onePosition = meshPositions[i]; if (onePosition.Y > yMiddle) { onePosition.Y += yOffset; } meshPositions[i] = onePosition; } morphedMeshGeometry3D.Positions = meshPositions; // Because we have changed the positions, this also changed the normal vectors. // We need to calculate normals again meshNormals = Ab3d.Utilities.MeshUtils.CalculateNormals(morphedMeshGeometry3D); morphedMeshGeometry3D.Normals = meshNormals; // After we have a morphed MeshGeometry, we can prepared an optimized list of positions and normals. _morphedMesh = new PositionNormal[positionsCount]; for (int i = 0; i < positionsCount; i++) { _morphedMesh[i] = new PositionNormal(meshPositions[i].ToVector3(), meshNormals[i].ToVector3()); } // We also need to store original and morphed bounding box (bounds) _originalBoundingBox = _meshGeometry3D.Bounds.ToBoundingBox(); _morphedBoundingBox = new BoundingBox(_originalBoundingBox.Minimum, new Vector3(_originalBoundingBox.Maximum.X, _originalBoundingBox.Maximum.Y + (float)yOffset, _originalBoundingBox.Maximum.Z)); }