public DirectBitmap RenderTileFrom(DataReader.Region region, DirectBitmap bitmap) { var watch = System.Diagnostics.Stopwatch.StartNew(); // Draw the terrain. LOG.Debug($"Rendering Maptile Terrain (Textured) for region {region.Id} named '{region.Name}'"); watch.Restart(); var terrain = TerrainToBitmap(region, bitmap); watch.Stop(); LOG.Info($"Completed terrain for region {region.Id} named '{region.Name}' in {watch.ElapsedMilliseconds}ms"); // Get the prims LOG.Debug($"Getting prims for region {region.Id} named '{region.Name}'"); watch.Restart(); var prims = region.GetPrims(); watch.Stop(); LOG.Debug($"Completed getting {prims?.Count()} prims for region {region.Id} named '{region.Name}' in {watch.ElapsedMilliseconds}ms"); if (prims != null) { // Draw the prims. LOG.Debug($"Rendering OBB prims for region {region.Id} named '{region.Name}'"); watch.Restart(); DrawObjects(prims, terrain, bitmap); watch.Stop(); LOG.Debug($"Completed OBB prims for region {region.Id} named '{region.Name}' in {watch.ElapsedMilliseconds}ms"); } else { LOG.Debug($"Unable to render OBB prims for region {region.Id} named '{region.Name}': there was a problem getting the prims from the DB."); } return(bitmap); }
private Terrain TerrainToBitmap(DataReader.Region region, DirectBitmap mapbmp) { var terrain = region.GetTerrain(); var textures = new Texture[4]; try { textures[0] = Texture.GetByUUID(terrain.TerrainTexture1, Texture.TERRAIN_TEXTURE_1_COLOR); } catch (InvalidOperationException e) { LOG.Warn($"Error decoding image asset {terrain.TerrainTexture1} for terrain texture 1 in region {region.Id}, continuing using default texture color.", e); textures[0] = new Texture(color: Texture.TERRAIN_TEXTURE_1_COLOR); } try { textures[1] = Texture.GetByUUID(terrain.TerrainTexture2, Texture.TERRAIN_TEXTURE_2_COLOR); } catch (InvalidOperationException e) { LOG.Warn($"Error decoding image asset {terrain.TerrainTexture2} for terrain texture 2 in region {region.Id}, continuing using default texture color.", e); textures[1] = new Texture(color: Texture.TERRAIN_TEXTURE_2_COLOR); } try { textures[2] = Texture.GetByUUID(terrain.TerrainTexture3, Texture.TERRAIN_TEXTURE_3_COLOR); } catch (InvalidOperationException e) { LOG.Warn($"Error decoding image asset {terrain.TerrainTexture3} for terrain texture 3 in region {region.Id}, continuing using default texture color.", e); textures[2] = new Texture(color: Texture.TERRAIN_TEXTURE_3_COLOR); } try { textures[3] = Texture.GetByUUID(terrain.TerrainTexture4, Texture.TERRAIN_TEXTURE_4_COLOR); } catch (InvalidOperationException e) { LOG.Warn($"Error decoding image asset {terrain.TerrainTexture4} for terrain texture 4 in region {region.Id}, continuing using default texture color.", e); textures[3] = new Texture(color: Texture.TERRAIN_TEXTURE_4_COLOR); } // the four terrain colors as HSVs for interpolation var hsv1 = new HSV(textures[0].AverageColor); var hsv2 = new HSV(textures[1].AverageColor); var hsv3 = new HSV(textures[2].AverageColor); var hsv4 = new HSV(textures[3].AverageColor); for (int x = 0; x < mapbmp.Width; x++) { var columnRatio = (double)x / (mapbmp.Width - 1); // 0 - 1, for interpolation for (int y = 0; y < mapbmp.Height; y++) { var rowRatio = (double)y / (mapbmp.Height - 1); // 0 - 1, for interpolation // Y flip the cordinates for the bitmap: hf origin is lower left, bm origin is upper left var yr = (mapbmp.Height - 1) - y; var heightvalue = mapbmp.Width == 256 ? terrain.GetBlendedHeight(x, y) : terrain.GetBlendedHeight(255 * columnRatio, 255 * rowRatio) ; if (double.IsInfinity(heightvalue) || double.IsNaN(heightvalue)) { heightvalue = 0d; } var tileScalarX = 256f / mapbmp.Width; // Used to hack back in those constants that were hand-tuned to 256px tiles. var tileScalarY = 256f / mapbmp.Height; // Used to hack back in those constants that were hand-tuned to 256px tiles. // add a bit noise for breaking up those flat colors: // - a large-scale noise, for the "patches" (using an doubled s-curve for sharper contrast) // - a small-scale noise, for bringing in some small scale variation //float bigNoise = (float)TerrainUtil.InterpolatedNoise(x / 8.0, y / 8.0) * .5f + .5f; // map to 0.0 - 1.0 //float smallNoise = (float)TerrainUtil.InterpolatedNoise(x + 33, y + 43) * .5f + .5f; //float hmod = heightvalue + smallNoise * 3f + S(S(bigNoise)) * 10f; var hmod = heightvalue + TerrainUtil.InterpolatedNoise(tileScalarX * (x + 33 + (int)region.Location?.X * mapbmp.Width), tileScalarY * (y + 43 + (int)region.Location?.Y * mapbmp.Height)) * 1.5f + 1.5f + // 0 - 3 MathUtilities.SCurve(MathUtilities.SCurve(TerrainUtil.InterpolatedNoise(tileScalarX * (x + (int)region.Location?.X * mapbmp.Width) / 8.0, tileScalarY * (y + (int)region.Location?.Y * mapbmp.Height) / 8.0) * .5f + .5f)) * 10f; // 0 - 10 // find the low/high values for this point (interpolated bilinearily) // (and remember, x=0,y=0 is SW) var low = terrain.ElevationSWLow * (1f - rowRatio) * (1f - columnRatio) + terrain.ElevationSELow * (1f - rowRatio) * columnRatio + terrain.ElevationNWLow * rowRatio * (1f - columnRatio) + terrain.ElevationNELow * rowRatio * columnRatio ; var high = terrain.ElevationSWHigh * (1f - rowRatio) * (1f - columnRatio) + terrain.ElevationSEHigh * (1f - rowRatio) * columnRatio + terrain.ElevationNWHigh * rowRatio * (1f - columnRatio) + terrain.ElevationNEHigh * rowRatio * columnRatio ; if (high < low) { // someone tried to fool us. High value should be higher than low every time var tmp = high; high = low; low = tmp; } Color result; if (heightvalue > terrain.WaterHeight) { HSV hsv; // Above water if (hmod <= low) { hsv = hsv1; // too low } else if (hmod >= high) { hsv = hsv4; // too high } else { // HSV-interpolate along the colors // first, rescale h to 0.0 - 1.0 hmod = (hmod - low) / (high - low); // now we have to split: 0.00 => color1, 0.33 => color2, 0.67 => color3, 1.00 => color4 if (hmod < 1d / 3d) { hsv = hsv1.InterpolateHSV(ref hsv2, (float)(hmod * 3d)); } else if (hmod < 2d / 3d) { hsv = hsv2.InterpolateHSV(ref hsv3, (float)((hmod * 3d) - 1d)); } else { hsv = hsv3.InterpolateHSV(ref hsv4, (float)((hmod * 3d) - 2d)); } } result = hsv.ToColor(); } else { // Under water. var deepwater = new HSV(_waterColor); var beachwater = new HSV(_beachColor); var water = deepwater.InterpolateHSV(ref beachwater, (float)MathUtilities.SCurve(heightvalue / terrain.WaterHeight)); if (_waterOverlay == null) { result = water.ToColor(); } else { // Overlay the water image var baseColor = water.ToColor(); var overlayColor = _waterOverlay[x, y]; var resultR = overlayColor.R + (baseColor.R * (255 - overlayColor.A) / 255); var resultG = overlayColor.G + (baseColor.G * (255 - overlayColor.A) / 255); var resultB = overlayColor.B + (baseColor.B * (255 - overlayColor.A) / 255); result = Color.FromArgb(resultR, resultG, resultB); } } // Shade the terrain for shadows //if (x < (mapbmp.Width - 1) && y < (mapbmp.Height - 1)) { // var hfvaluecompare = getHeight(region.heightmapData, (x + 1) / (mapbmp.Width - 1), (y + 1) / (mapbmp.Height - 1)); // light from north-east => look at land height there // if (Double.IsInfinity(hfvaluecompare) || Double.IsNaN(hfvaluecompare)) // hfvaluecompare = 0d; // // var hfdiff = heightvalue - hfvaluecompare; // => positive if NE is lower, negative if here is lower // hfdiff *= 0.06d; // some random factor so "it looks good" // if (hfdiff > 0.02d) { // var highlightfactor = 0.18d; // // NE is lower than here // // We have to desaturate and lighten the land at the same time // hsv.s = (hsv.s - (hfdiff * highlightfactor) > 0d) ? (float)(hsv.s - (hfdiff * highlightfactor)) : 0f; // hsv.v = (hsv.v + (hfdiff * highlightfactor) < 1d) ? (float)(hsv.v + (hfdiff * highlightfactor)) : 1f; // } // else if (hfdiff < -0.02f) { // var highlightfactor = 1.0d; // // here is lower than NE: // // We have to desaturate and blacken the land at the same time // hsv.s = (hsv.s + (hfdiff * highlightfactor) > 0f) ? (float)(hsv.s + (hfdiff * highlightfactor)) : 0f; // hsv.v = (hsv.v + (hfdiff * highlightfactor) > 0f) ? (float)(hsv.v + (hfdiff * highlightfactor)) : 0f; // } //} mapbmp.Bitmap.SetPixel(x, yr, result); } } return(terrain); }