private void Split(DoubleVector3 cameraLocation, DoubleVector3 planetLocation) { // TODO: should we bother to write specs for the threading behavior? Interlocked.Increment(ref _statistics.NumberOfSplitsScheduledPerInterval); Interlocked.Increment(ref _statistics.NumberOfPendingSplits); CreateCancellationTokenSource(); var splitTasks = CreateBackgroundSplitTasks(cameraLocation, planetLocation); CreateSplitCompletionTask(splitTasks); }
public void Update(DoubleVector3 cameraLocation, DoubleVector3 planetLocation) { // TODO: I don't like this class's public member design. In order to get properties like IsVisibleToCamera, // you must first call Update with appropriate information. Internally, a lot of stuff is also // order-dependent. However, this seems to be the most performant design at the moment. var meshDistance = GetDistanceFrom(cameraLocation); var distanceFromCamera = meshDistance.ClosestDistance; CameraDistanceToWidthRatio = distanceFromCamera / WidthInRealSpaceUnits(); IsAboveHorizonToCamera = CalculateIsAboveHorizonToCamera(cameraLocation, planetLocation, meshDistance.ClosestVertex); }
double GetRegionalModifier(DoubleVector3 location) { var lowFrequencyRegionalModifier = _regionalGenerator1.GetSpectralNoise(location, 21, 4, 2.0, 0.5); lowFrequencyRegionalModifier += 1; var mediumFrequencyRegionalModifier = _regionalGenerator2.GetSpectralNoise(location, 30, 4, 2.0, 0.5); mediumFrequencyRegionalModifier += 1; var regionalModifier = DoubleMathHelper.Clamp(lowFrequencyRegionalModifier * mediumFrequencyRegionalModifier, 0, 2); return(regionalModifier); }
public IPlanet Create(DoubleVector3 location, double radius) { // TODO: should we inject into the factory everything it will need to inject // into the planets? That reduces calls to the container but makes an assumption // about dependency lifetimes. var terrain = _terrainFactory.Create(radius); var renderer = CreateRenderer(radius); var generator = ObjectFactory.GetInstance <IHeightfieldGenerator>(); var statistics = ObjectFactory.GetInstance <Statistics>(); var planet = new Planet(location, radius, terrain, renderer, generator, _settings, statistics); return(planet); }
public Settings(IEventAggregator eventAggregator) { _eventAggregator = eventAggregator; ShouldUpdate = true; ShouldSingleStep = false; ShouldDrawWireframe = false; CameraStartingLocation = new DoubleVector3(0, PhysicalConstants.RadiusOfEarth * 1.002, 0); CameraStartingLookAt = CameraStartingLocation + DoubleVector3.Backward + DoubleVector3.Down * .2; CameraMoveSpeedPerSecond = PhysicalConstants.RadiusOfEarth / 1000; CameraMouseLookDamping = 300f; MaximumQuadNodeLevel = 19; ShowQuadBoundaries = true; FarClippingPlaneDistance = 10000000; ShouldDrawMeshBoundingBoxes = false; }
// TODO: push this data in through the constructor, probably in a QuadNodeDefintion class, and make // this method private. Except that would do real work in construction. Hmmm. public void Initialize(double planetRadius, DoubleVector3 planeNormalVector, DoubleVector3 uVector, DoubleVector3 vVector, QuadNodeExtents extents, int level) { _planetRadius = planetRadius; _planeNormalVector = planeNormalVector; _uVector = uVector; _vVector = vVector; _extents = extents; Level = level; _locationRelativeToPlanet = (_planeNormalVector) + (_uVector * (_extents.North + (_extents.Width / 2.0))) + (_vVector * (_extents.West + (_extents.Width / 2.0))); _locationRelativeToPlanet = _locationRelativeToPlanet.ProjectUnitPlaneToUnitSphere() * _planetRadius; _mesh.Initialize(planetRadius, planeNormalVector, uVector, vVector, extents, level); Interlocked.Increment(ref _statistics.NumberOfQuadNodes); Interlocked.Increment(ref _statistics.NumberOfQuadNodesAtLevel[Level]); }
// Ridged multifractal // See "Texturing & Modeling, A Procedural Approach", Chapter 12 public double AccumulateNoise(DoubleVector3 sampleLocation, int numberOfOctaves, double lacunarity, double gain, double offset) { double sum = 0; // The return result double amp = 0.5; // Reduce the amplitude per octave of noise double prev = 1.0; for (int i = 0; i < numberOfOctaves; i++) { double n = ridge(_noiseGenerator.GetNoise(sampleLocation), offset); sum += n * amp * prev; prev = n; sampleLocation *= lacunarity; amp *= gain; } // Return the result return(sum * 1.25 * 2 - 1); }
public void Draw(DoubleVector3 location, DoubleVector3 cameraLocation, Matrix originBasedViewMatrix, Matrix projectionMatrix) { _effect.View = originBasedViewMatrix; _effect.Projection = projectionMatrix; _effect.World = GetWorldMatrix(location, cameraLocation); // TODO: we shouldn't have to set all of these every time - could be optimized SetLightingEffects(); SetFogEffects(); SetFillMode(); DrawMesh(); if (_settings.ShouldDrawMeshBoundingBoxes) { DrawBoundingBox(); } _statistics.NumberOfQuadMeshesRendered++; }
public void Update(DoubleVector3 cameraLocation, DoubleVector3 planetLocation) { _mesh.Update(cameraLocation, planetLocation); if (ShouldSplit()) { Split(cameraLocation, planetLocation); } else if (ShouldMerge()) { Merge(); } else if (ShouldCancelSplit()) { CancelSplit(); } UpdateSubnodes(cameraLocation, planetLocation); }
bool CalculateIsAboveHorizonToCamera(DoubleVector3 cameraLocation, DoubleVector3 planetLocation, DoubleVector3 closestVertex) { // Taken from http://www.crappycoding.com/2009/04/ // TODO: This algorithm is poor. Implement this algorithm instead: // http://www.gamedev.net/community/forums/mod/journal/journal.asp?jn=263350&reply_id=3173799 // TODO: sometimes the mesh is so large and we're so close the surface that none of the sampled // vertices are above the horizon. That causes problems when we want to do early termination // when we do a draw walk on the QuadNode tree (see comments there). Can we add a test here to // see if we're inside the mesh's bounding box? var planetToCamera = DoubleVector3.Normalize(cameraLocation - planetLocation); var planetToMesh = DoubleVector3.Normalize(closestVertex - planetLocation); var horizonAngle = Math.Acos(_planetRadius * 0.99 / DoubleVector3.Distance(planetLocation, cameraLocation)); var angleToMesh = Math.Acos(DoubleVector3.Dot(planetToCamera, planetToMesh)); return(horizonAngle > angleToMesh); }
public void Draw(DoubleVector3 cameraLocation, BoundingFrustum originBasedViewFrustum, Matrix originBasedViewMatrix, Matrix projectionMatrix) { if (_hasSubnodes) { // TODO: we'd like to stop traversing into subnodes if this node's mesh isn't visibile, but our // horizon culling algorithm isn't that great right now and the primary faces are so large that // sometimes all of their sample points are below the horizon even though we're above that face // and would want to draw its children. For now, we'll scan all subnodes regardless. The child // node's meshes will do visibility culling on an individual basis. foreach (var subnode in _subnodes) { subnode.Draw(cameraLocation, originBasedViewFrustum, originBasedViewMatrix, projectionMatrix); } } else { _renderer.Draw(_locationRelativeToPlanet, cameraLocation, originBasedViewMatrix, projectionMatrix); _mesh.Draw(cameraLocation, originBasedViewFrustum, originBasedViewMatrix, projectionMatrix); } }
public double GetNoise(DoubleVector3 location) { // Taken from Interactive Visualization paper uint xHash = (uint)location.X.GetHashCode(); uint yHash = (uint)location.Y.GetHashCode(); uint zHash = (uint)location.Z.GetHashCode(); uint result = GetFromPool(0); while (xHash > 0 || yHash > 0 || zHash > 0) { result += GetFromPool(xHash + GetFromPool(yHash + GetFromPool(zHash))); xHash >>= 16; yHash >>= 16; zHash >>= 16; } return((result % _poolSize) / (double)(_poolSize / 2) - 1.0); }
double AccumulateNoise(DoubleVector3 location, int numberOfOctaves, double lacunarity, double gain) { double noiseSum = 0; double amplitude = 1; double amplitudeSum = 0; DoubleVector3 sampleLocation = location; for (int x = 0; x < numberOfOctaves; x++) { noiseSum += amplitude * _noiseGenerator.GetNoise(sampleLocation); amplitudeSum += amplitude; amplitude *= gain; sampleLocation *= lacunarity; } noiseSum /= amplitudeSum; return(noiseSum * 1.35); }
public double GetHeight(DoubleVector3 location, int level, double scale) { var heightVariance1 = _varianceGenerator1.GetNoise(location, 5, 4, 2.0, 0.5); heightVariance1 = (heightVariance1 + 1); var heightVariance2 = _varianceGenerator2.GetNoise(location, 15, 4, 3.0, 0.5); heightVariance2 = (heightVariance2 + 1); var heightVariance = DoubleMathHelper.Clamp(heightVariance1 * heightVariance2, 0, 2); _sampleCount++; if (_sampleCount % 1000 == 0) { Debug.WriteLine(heightVariance); } var height = heightVariance * scale; return(height); }
double AccumulateNoise(DoubleVector3 location, int numberOfOctaves, double lacunarity, double gain) { double weight = 1; double noise = 0; double amplitude = 1; for (int x = 0; x < numberOfOctaves; x++) { double signal = _noiseGenerator.GetNoise(location); signal = 1 - Math.Abs(signal); signal *= signal * weight; weight = signal / gain; weight = DoubleMathHelper.Clamp(weight, 0, 1); noise += (signal * amplitude); location *= lacunarity; amplitude *= gain; } return((noise * 1.25) - 1.0);; }
public void SetViewParameters(DoubleVector3 location, DoubleVector3 lookAt) { _cameraLocation = location; // To convert from a look-at vector to Euler angles, we'll go // via a rotation matrix. There may be a shorter way, but // this works. Matrix rotationMatrix = CreateOriginBasedLookAt(location, lookAt); DoubleVector3 lookDirection = lookAt - location; if (IsStraightUp(lookDirection)) { // singularity at north pole _cameraYaw = (float)Math.Atan2(rotationMatrix.M13, rotationMatrix.M33); _cameraPitch = MaximumPitch; _cameraRoll = 0; } else if (IsStraightDown(lookDirection)) { // singularity at south pole _cameraYaw = (float)Math.Atan2(rotationMatrix.M13, rotationMatrix.M33); _cameraPitch = MinimumPitch; _cameraRoll = 0; } else { // Normal conversion _cameraYaw = (float)Math.Atan2(-rotationMatrix.M31, rotationMatrix.M11); _cameraPitch = (float)Math.Atan2(-rotationMatrix.M23, rotationMatrix.M22); _cameraRoll = (float)Math.Asin(rotationMatrix.M21); } ClampPitch(); CreateViewMatrix(); }
Matrix GetWorldMatrix(DoubleVector3 location, DoubleVector3 cameraLocation) { // The mesh stored in the vertex buffer is centered at the origin in order to take it easy on // the float number system. The camera view matrix is also generated as though the camera were // at the origin. In order to correctly render the mesh we translate it away from the origin // by the same vector that the mesh (in double space) is displaced from the camera (in double space). // We also translate and scale distant meshes to bring them inside the far clipping plane. For // every mesh that's further than the start of the scaled space, we calcuate a new distance // using an exponential downscale function to make it fall in the view frustum. We also scale // it down proportionally so that it appears perspective-wise to be identical to the original // location. See the Interactive Visualization paper, page 24. Matrix scaleMatrix; Matrix translationMatrix; var locationRelativeToCamera = location - cameraLocation; var distanceFromCamera = locationRelativeToCamera.Length(); var unscaledViewSpace = _settings.FarClippingPlaneDistance * 0.25; if (distanceFromCamera > unscaledViewSpace) { var scaledViewSpace = _settings.FarClippingPlaneDistance - unscaledViewSpace; double scaledDistanceFromCamera = unscaledViewSpace + (scaledViewSpace * (1.0 - Math.Exp((scaledViewSpace - distanceFromCamera) / 1000000000))); var scaledLocationRelativeToCamera = DoubleVector3.Normalize(locationRelativeToCamera) * scaledDistanceFromCamera; scaleMatrix = Matrix.CreateScale((float)(scaledDistanceFromCamera / distanceFromCamera)); translationMatrix = Matrix.CreateTranslation(scaledLocationRelativeToCamera); } else { scaleMatrix = Matrix.Identity; translationMatrix = Matrix.CreateTranslation(locationRelativeToCamera); } return(scaleMatrix * translationMatrix); }
bool IsStraightUp(DoubleVector3 vector) { return(vector.X == 0 && vector.Z == 0 && vector.Y > 0); }
public void Update(DoubleVector3 cameraLocation) { _terrain.Update(cameraLocation, _location); UpdateStatistics(cameraLocation); }
Matrix CreateOriginBasedLookAt(DoubleVector3 location, DoubleVector3 lookAt) { Matrix rotationMatrix = Matrix.CreateLookAt(Vector3.Zero, lookAt - location, Vector3.Up); return(rotationMatrix); }
bool IsStraightDown(DoubleVector3 vector) { return(vector.X == 0 && vector.Z == 0 && vector.Y < 0); }
public double GetNoise(DoubleVector3 location, int startingOctave, int numberOfOctaves, double lacunarity, double gain) { var sampleLocation = location * (1 << (startingOctave)); return(AccumulateNoise(sampleLocation, numberOfOctaves, lacunarity, gain)); }
List <Task <IQuadNode> > CreateBackgroundSplitTasks(DoubleVector3 cameraLocation, DoubleVector3 planetLocation) { // TODO: there's a problem with this algorithm. If we need to split very deeply because for example // the camera is teleported to the surface, we only split one level at a time and wait for that level // to finish and for the next update sweep to occur before queuing splits for the next level. There // may be wasted time in there (not clear yet). We also spend time generating meshes for quads that // we know we don't need. Ideally we'd jump straight to rendering meshes for the new leaves and worry // about rendering meshes for the intermediate nodes later when we need them. This means we need a // general way to delay completing a merge (continuing to render its children in the meantime) until // a mesh is rendered for that node. Once we have that behavior // we can maybe throw away the vertex buffers for all non-leaf meshes to save memory and regenerate them // as needed. That would take more CPU overall but it's not time sensitive because we'd just keep // rendering the child nodes until we got around to it. That would be a problem only if we end up // overloading the GPU but in many cases that wouldn't happen because the merging nodes would be behind // the camera as it travels and thus not rendered. // Potential problem: we don't know for sure if a node needs to be split until after we generate its mesh. _splitInProgress = true; var subextents = _extents.Split(); return(subextents.Select(extent => Task <IQuadNode> .Factory.StartNew(() => { var node = _quadNodeFactory.Create(); node.Initialize(_planetRadius, _planeNormalVector, _uVector, _vVector, extent, Level + 1); node.Update(cameraLocation, planetLocation); return node; }, _cancellationTokenSource.Token, TaskCreationOptions.None, _taskSchedulerFactory.CreateForLevel(Level))).ToList()); }
public double GetSpectralNoise(DoubleVector3 location, double initialFrequencyMultiplier, int numberOfOctaves, double lacunarity, double gain) { var sampleLocation = location * initialFrequencyMultiplier; return(AccumulateNoise(sampleLocation, numberOfOctaves, lacunarity, gain, 1)); }
public double GetNoise(DoubleVector3 location) { double xin = location.X; double yin = location.Y; double zin = location.Z; double n0, n1, n2, n3; // Noise contributions from the four corners // Skew the input space to determine which simplex cell we're in const double F3 = 1.0 / 3.0; double s = (xin + yin + zin) * F3; // Very nice and simple skew factor for 3D int i = fastfloor(xin + s); int j = fastfloor(yin + s); int k = fastfloor(zin + s); const double G3 = 1.0 / 6.0; // Very nice and simple unskew factor, too double t = (i + j + k) * G3; double X0 = i - t; // Unskew the cell origin back to (x,y,z) space double Y0 = j - t; double Z0 = k - t; double x0 = xin - X0; // The x,y,z distances from the cell origin double y0 = yin - Y0; double z0 = zin - Z0; // For the 3D case, the simplex shape is a slightly irregular tetrahedron. // Determine which simplex we are in. int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords if (x0 >= y0) { if (y0 >= z0) { // X Y Z order i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0; } else if (x0 >= z0) { // X Z Y order i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1; } else { // Z X Y order i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1; } } else { // x0 < y0 if (y0 < z0) { // Z Y X order i1 = 0; j1 = 0; k1 = 1; i2 = 0; j2 = 1; k2 = 1; } else if (x0 < z0) { // Y Z X order i1 = 0; j1 = 1; k1 = 0; i2 = 0; j2 = 1; k2 = 1; } else { // Y X Z order i1 = 0; j1 = 1; k1 = 0; i2 = 1; j2 = 1; k2 = 0; } } // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z), // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where // c = 1/6. double x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords double y1 = y0 - j1 + G3; double z1 = z0 - k1 + G3; double x2 = x0 - i2 + 2.0 * G3; // Offsets for third corner in (x,y,z) coords double y2 = y0 - j2 + 2.0 * G3; double z2 = z0 - k2 + 2.0 * G3; double x3 = x0 - 1.0 + 3.0 * G3; // Offsets for last corner in (x,y,z) coords double y3 = y0 - 1.0 + 3.0 * G3; double z3 = z0 - 1.0 + 3.0 * G3; // Work out the hashed gradient indices of the four simplex corners int ii = i & 255; int jj = j & 255; int kk = k & 255; int gi0 = perm[ii + perm[jj + perm[kk]]] % 12; int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1]]] % 12; int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2]]] % 12; int gi3 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1]]] % 12; // Calculate the contribution from the four corners double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0; if (t0 < 0) { n0 = 0.0; } else { t0 *= t0; n0 = t0 * t0 * dot(grad3[gi0], x0, y0, z0); } double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; if (t1 < 0) { n1 = 0.0; } else { t1 *= t1; n1 = t1 * t1 * dot(grad3[gi1], x1, y1, z1); } double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; if (t2 < 0) { n2 = 0.0; } else { t2 *= t2; n2 = t2 * t2 * dot(grad3[gi2], x2, y2, z2); } double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; if (t3 < 0) { n3 = 0.0; } else { t3 *= t3; n3 = t3 * t3 * dot(grad3[gi3], x3, y3, z3); } // Add contributions from each corner to get the final noise value. // The result is scaled to stay just inside [-1,1] return(32.0 * (n0 + n1 + n2 + n3)); }
DoubleVector3 ConvertToMeshSpace(DoubleVector3 planetSpaceVector) { return(planetSpaceVector - _locationRelativeToPlanet); }
// TODO: push this data in through the constructor, probably in a QuadMeshDefintion class, and make // this method private. Except that would do real work in construction. Hmmm. public void Initialize(double planetRadius, DoubleVector3 planeNormalVector, DoubleVector3 uVector, DoubleVector3 vVector, QuadNodeExtents extents, int level) { _planetRadius = planetRadius; _extents = extents; // TODO: get this from the QuadNode instead _locationRelativeToPlanet = (planeNormalVector) + (uVector * (_extents.North + (_extents.Width / 2.0))) + (vVector * (_extents.West + (_extents.Width / 2.0))); _locationRelativeToPlanet = _locationRelativeToPlanet.ProjectUnitPlaneToUnitSphere() * _planetRadius; // TODO: cover this in specs _boundingBox.Min = new Vector3(float.MaxValue); _boundingBox.Max = new Vector3(float.MinValue); GenerateIndices(); var heightmap = _generator.GenerateHeightmapSamples(new HeightmapDefinition() { GridSize = _gridSize, Stride = extents.Width / (_gridSize - 1), PlaneNormalVector = planeNormalVector, UVector = uVector, VVector = vVector, Extents = _extents, QuadLevel = level, PlanetRadius = planetRadius }); var vertices = GenerateMeshVertices(heightmap); CollectHeightmapSamples(heightmap); _renderer.Initialize(vertices, _indices, _boundingBox); }
void UpdateStatistics(DoubleVector3 cameraLocation) { _statistics.CameraAltitude = DoubleVector3.Distance(_location, cameraLocation) - _radius; }
public void Draw(DoubleVector3 location, DoubleVector3 cameraLocation, Matrix originBasedViewMatrix, Matrix projectionMatrix) { }
// TODO: push this data in through the constructor, probably in a QuadMeshDefintion class, and make // this method private. Except that would do real work in construction. Hmmm. public void Initialize(double planetRadius, DoubleVector3 planeNormalVector, DoubleVector3 uVector, DoubleVector3 vVector, QuadNodeExtents extents, int level) { _planetRadius = planetRadius; _planeNormalVector = planeNormalVector; _uVector = uVector; _vVector = vVector; _extents = extents; _level = level; // TODO: get this from the QuadNode instead _locationRelativeToPlanet = (_planeNormalVector) + (_uVector * (_extents.North + (_extents.Width / 2.0))) + (_vVector * (_extents.West + (_extents.Width / 2.0))); _locationRelativeToPlanet = _locationRelativeToPlanet.ProjectUnitPlaneToUnitSphere() * _planetRadius; _meshStride = _extents.Width / (_gridSize - 1); // TODO: cover this in specs _boundingBox.Min = new Vector3(float.MaxValue); _boundingBox.Max = new Vector3(float.MinValue); GenerateIndices(); GenerateMeshVertices(); CollectMeshSamples(); _renderer.Initialize(_vertices, _indices, _boundingBox); TrimUnneededMemory(); }