aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/addon-modules/OpenSim.Modules.Warp3DCachedImageModule/src/TerrainSplat.cs
diff options
context:
space:
mode:
authoronefang2020-09-10 21:20:23 +1000
committeronefang2020-09-10 21:20:23 +1000
commit2940f325436f5b07dcfbac03545b3eb96e20ffcf (patch)
tree71babe10d710dac41431d20c8a22387d48f5130b /addon-modules/OpenSim.Modules.Warp3DCachedImageModule/src/TerrainSplat.cs
parentAnother ini file I forgot in the last commit. (diff)
downloadopensim-SC-2940f325436f5b07dcfbac03545b3eb96e20ffcf.zip
opensim-SC-2940f325436f5b07dcfbac03545b3eb96e20ffcf.tar.gz
opensim-SC-2940f325436f5b07dcfbac03545b3eb96e20ffcf.tar.bz2
opensim-SC-2940f325436f5b07dcfbac03545b3eb96e20ffcf.tar.xz
Warp3DCachedImageModule from Christopher Latza.
From - https://clatza.dev/OpenSim/OpenSim.Modules.Warp3DCachedImageModule.git Commit ad0aa59f53ae77c85a7c745d9af5aa70187568ba on 17/5/20 1:37 AM
Diffstat (limited to 'addon-modules/OpenSim.Modules.Warp3DCachedImageModule/src/TerrainSplat.cs')
-rw-r--r--addon-modules/OpenSim.Modules.Warp3DCachedImageModule/src/TerrainSplat.cs470
1 files changed, 470 insertions, 0 deletions
diff --git a/addon-modules/OpenSim.Modules.Warp3DCachedImageModule/src/TerrainSplat.cs b/addon-modules/OpenSim.Modules.Warp3DCachedImageModule/src/TerrainSplat.cs
new file mode 100644
index 0000000..f60beaf
--- /dev/null
+++ b/addon-modules/OpenSim.Modules.Warp3DCachedImageModule/src/TerrainSplat.cs
@@ -0,0 +1,470 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Diagnostics;
30using System.Drawing;
31using System.Drawing.Imaging;
32using log4net;
33using OpenMetaverse;
34using OpenSim.Framework;
35using OpenSim.Region.Framework.Interfaces;
36using OpenSim.Services.Interfaces;
37
38namespace OpenSim.Region.CoreModules.World.Warp3DMap
39{
40 public static class TerrainSplat
41 {
42 #region Constants
43
44 private static readonly UUID DIRT_DETAIL = new UUID("0bc58228-74a0-7e83-89bc-5c23464bcec5");
45 private static readonly UUID GRASS_DETAIL = new UUID("63338ede-0037-c4fd-855b-015d77112fc8");
46 private static readonly UUID MOUNTAIN_DETAIL = new UUID("303cd381-8560-7579-23f1-f0a880799740");
47 private static readonly UUID ROCK_DETAIL = new UUID("53a2f406-4895-1d13-d541-d2e3b86bc19c");
48
49 private static readonly UUID[] DEFAULT_TERRAIN_DETAIL = new UUID[]
50 {
51 DIRT_DETAIL,
52 GRASS_DETAIL,
53 MOUNTAIN_DETAIL,
54 ROCK_DETAIL
55 };
56
57 private static readonly Color[] DEFAULT_TERRAIN_COLOR = new Color[]
58 {
59 Color.FromArgb(255, 164, 136, 117),
60 Color.FromArgb(255, 65, 87, 47),
61 Color.FromArgb(255, 157, 145, 131),
62 Color.FromArgb(255, 125, 128, 130)
63 };
64
65 private static readonly UUID TERRAIN_CACHE_MAGIC = new UUID("2c0c7ef2-56be-4eb8-aacb-76712c535b4b");
66
67 #endregion Constants
68
69 private static readonly ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
70 private static string LogHeader = "[WARP3D TERRAIN SPLAT]";
71
72 /// <summary>
73 /// Builds a composited terrain texture given the region texture
74 /// and heightmap settings
75 /// </summary>
76 /// <param name="terrain">Terrain heightmap</param>
77 /// <param name="regionInfo">Region information including terrain texture parameters</param>
78 /// <returns>A 256x256 square RGB texture ready for rendering</returns>
79 /// <remarks>Based on the algorithm described at http://opensimulator.org/wiki/Terrain_Splatting
80 /// Note we create a 256x256 dimension texture even if the actual terrain is larger.
81 /// </remarks>
82
83 public static Bitmap Splat(ITerrainChannel terrain, UUID[] textureIDs,
84 float[] startHeights, float[] heightRanges,
85 uint regionPositionX, uint regionPositionY,
86 IAssetService assetService, IJ2KDecoder decoder,
87 bool textureTerrain, bool averagetextureTerrain,
88 int twidth, int theight)
89 {
90 Debug.Assert(textureIDs.Length == 4);
91 Debug.Assert(startHeights.Length == 4);
92 Debug.Assert(heightRanges.Length == 4);
93
94 Bitmap[] detailTexture = new Bitmap[4];
95
96 byte[] mapColorsRed = new byte[4];
97 byte[] mapColorsGreen = new byte[4];
98 byte[] mapColorsBlue = new byte[4];
99
100 bool usecolors = false;
101
102 if (textureTerrain)
103 {
104 // Swap empty terrain textureIDs with default IDs
105 for(int i = 0; i < textureIDs.Length; i++)
106 {
107 if(textureIDs[i] == UUID.Zero)
108 textureIDs[i] = DEFAULT_TERRAIN_DETAIL[i];
109 }
110
111 #region Texture Fetching
112
113 if(assetService != null)
114 {
115 for(int i = 0; i < 4; i++)
116 {
117 AssetBase asset = null;
118
119 // asset cache indexes are strings
120 string cacheName ="MAP-Patch" + textureIDs[i].ToString();
121
122 // Try to fetch a cached copy of the decoded/resized version of this texture
123 asset = assetService.GetCached(cacheName);
124 if(asset != null)
125 {
126 try
127 {
128 using(System.IO.MemoryStream stream = new System.IO.MemoryStream(asset.Data))
129 detailTexture[i] = (Bitmap)Image.FromStream(stream);
130
131 if(detailTexture[i].PixelFormat != PixelFormat.Format24bppRgb ||
132 detailTexture[i].Width != 16 || detailTexture[i].Height != 16)
133 {
134 detailTexture[i].Dispose();
135 detailTexture[i] = null;
136 }
137 }
138 catch(Exception ex)
139 {
140 m_log.Warn("Failed to decode cached terrain patch texture" + textureIDs[i] + "): " + ex.Message);
141 }
142 }
143
144 if(detailTexture[i] == null)
145 {
146 // Try to fetch the original JPEG2000 texture, resize if needed, and cache as PNG
147 asset = assetService.Get(textureIDs[i].ToString());
148 if(asset != null)
149 {
150 try
151 {
152 detailTexture[i] = (Bitmap)decoder.DecodeToImage(asset.Data);
153 }
154 catch(Exception ex)
155 {
156 m_log.Warn("Failed to decode terrain texture " + asset.ID + ": " + ex.Message);
157 }
158 }
159
160 if(detailTexture[i] != null)
161 {
162 if(detailTexture[i].PixelFormat != PixelFormat.Format24bppRgb ||
163 detailTexture[i].Width != 16 || detailTexture[i].Height != 16)
164 using(Bitmap origBitmap = detailTexture[i])
165 detailTexture[i] = Util.ResizeImageSolid(origBitmap, 16, 16);
166
167 // Save the decoded and resized texture to the cache
168 byte[] data;
169 using(System.IO.MemoryStream stream = new System.IO.MemoryStream())
170 {
171 detailTexture[i].Save(stream, ImageFormat.Png);
172 data = stream.ToArray();
173 }
174
175 // Cache a PNG copy of this terrain texture
176 AssetBase newAsset = new AssetBase
177 {
178 Data = data,
179 Description = "PNG",
180 Flags = AssetFlags.Collectable,
181 FullID = UUID.Zero,
182 ID = cacheName,
183 Local = true,
184 Name = String.Empty,
185 Temporary = true,
186 Type = (sbyte)AssetType.Unknown
187 };
188 newAsset.Metadata.ContentType = "image/png";
189 assetService.Store(newAsset);
190 }
191 }
192 }
193 }
194
195 #endregion Texture Fetching
196 if(averagetextureTerrain)
197 {
198 for(int t = 0; t < 4; t++)
199 {
200 usecolors = true;
201 if(detailTexture[t] == null)
202 {
203 mapColorsRed[t] = DEFAULT_TERRAIN_COLOR[t].R;
204 mapColorsGreen[t] = DEFAULT_TERRAIN_COLOR[t].G;
205 mapColorsBlue[t] = DEFAULT_TERRAIN_COLOR[t].B;
206 continue;
207 }
208
209 int npixeis = 0;
210 int cR = 0;
211 int cG = 0;
212 int cB = 0;
213
214 BitmapData bmdata = detailTexture[t].LockBits(new Rectangle(0, 0, 16, 16),
215 ImageLockMode.ReadOnly, detailTexture[t].PixelFormat);
216
217 npixeis = bmdata.Height * bmdata.Width;
218 int ylen = bmdata.Height * bmdata.Stride;
219
220 unsafe
221 {
222 for(int y = 0; y < ylen; y += bmdata.Stride)
223 {
224 byte* ptrc = (byte*)bmdata.Scan0 + y;
225 for(int x = 0 ; x < bmdata.Width; ++x)
226 {
227 cR += *(ptrc++);
228 cG += *(ptrc++);
229 cB += *(ptrc++);
230 }
231 }
232
233 }
234 detailTexture[t].UnlockBits(bmdata);
235 detailTexture[t].Dispose();
236
237 mapColorsRed[t] = (byte)Util.Clamp(cR / npixeis, 0 , 255);
238 mapColorsGreen[t] = (byte)Util.Clamp(cG / npixeis, 0 , 255);
239 mapColorsBlue[t] = (byte)Util.Clamp(cB / npixeis, 0 , 255);
240 }
241 }
242 else
243 {
244 // Fill in any missing textures with a solid color
245 for(int i = 0; i < 4; i++)
246 {
247 if(detailTexture[i] == null)
248 {
249 m_log.DebugFormat("{0} Missing terrain texture for layer {1}. Filling with solid default color", LogHeader, i);
250
251 // Create a solid color texture for this layer
252 detailTexture[i] = new Bitmap(16, 16, PixelFormat.Format24bppRgb);
253 using(Graphics gfx = Graphics.FromImage(detailTexture[i]))
254 {
255 using(SolidBrush brush = new SolidBrush(DEFAULT_TERRAIN_COLOR[i]))
256 gfx.FillRectangle(brush, 0, 0, 16, 16);
257 }
258 }
259 else
260 {
261 if(detailTexture[i].Width != 16 || detailTexture[i].Height != 16)
262 {
263 using(Bitmap origBitmap = detailTexture[i])
264 detailTexture[i] = Util.ResizeImageSolid(origBitmap, 16, 16);
265 }
266 }
267 }
268 }
269 }
270 else
271 {
272 usecolors = true;
273 for(int t = 0; t < 4; t++)
274 {
275 mapColorsRed[t] = DEFAULT_TERRAIN_COLOR[t].R;
276 mapColorsGreen[t] = DEFAULT_TERRAIN_COLOR[t].G;
277 mapColorsBlue[t] = DEFAULT_TERRAIN_COLOR[t].B;
278 }
279 }
280
281 #region Layer Map
282
283 float xFactor = terrain.Width / twidth;
284 float yFactor = terrain.Height / theight;
285
286 #endregion Layer Map
287
288 #region Texture Compositing
289
290 Bitmap output = new Bitmap(twidth, theight, PixelFormat.Format24bppRgb);
291 BitmapData outputData = output.LockBits(new Rectangle(0, 0, twidth, theight), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
292
293 // Unsafe work as we lock down the source textures for quicker access and access the
294 // pixel data directly
295 float invtwitdthMinus1 = 1.0f / (twidth - 1);
296 float invtheightMinus1 = 1.0f / (theight - 1);
297 int ty;
298 int tx;
299 float pctx;
300 float pcty;
301 float height;
302 float layer;
303 float layerDiff;
304 int l0;
305 int l1;
306 uint yglobalpos;
307
308 if(usecolors)
309 {
310 float a;
311 float b;
312 unsafe
313 {
314 byte* ptrO;
315 for(int y = 0; y < theight; ++y)
316 {
317 pcty = y * invtheightMinus1;
318 ptrO = (byte*)outputData.Scan0 + y * outputData.Stride;
319 ty = (int)(y * yFactor);
320 yglobalpos = (uint)ty + regionPositionY;
321
322 for(int x = 0; x < twidth; ++x)
323 {
324 tx = (int)(x * xFactor);
325 pctx = x * invtwitdthMinus1;
326 height = (float)terrain[tx, ty];
327 layer = getLayerTex(height, pctx, pcty,
328 (uint)tx + regionPositionX, yglobalpos,
329 startHeights, heightRanges);
330
331 // Select two textures
332 l0 = (int)layer;
333 l1 = Math.Min(l0 + 1, 3);
334
335 layerDiff = layer - l0;
336
337 a = mapColorsRed[l0];
338 b = mapColorsRed[l1];
339 *(ptrO++) = (byte)(a + layerDiff * (b - a));
340
341 a = mapColorsGreen[l0];
342 b = mapColorsGreen[l1];
343 *(ptrO++) = (byte)(a + layerDiff * (b - a));
344
345 a = mapColorsBlue[l0];
346 b = mapColorsBlue[l1];
347 *(ptrO++) = (byte)(a + layerDiff * (b - a));
348 }
349 }
350 }
351 }
352 else
353 {
354 float aB;
355 float aG;
356 float aR;
357 float bB;
358 float bG;
359 float bR;
360
361 unsafe
362 {
363 // Get handles to all of the texture data arrays
364 BitmapData[] datas = new BitmapData[]
365 {
366 detailTexture[0].LockBits(new Rectangle(0, 0, 16, 16), ImageLockMode.ReadOnly, detailTexture[0].PixelFormat),
367 detailTexture[1].LockBits(new Rectangle(0, 0, 16, 16), ImageLockMode.ReadOnly, detailTexture[1].PixelFormat),
368 detailTexture[2].LockBits(new Rectangle(0, 0, 16, 16), ImageLockMode.ReadOnly, detailTexture[2].PixelFormat),
369 detailTexture[3].LockBits(new Rectangle(0, 0, 16, 16), ImageLockMode.ReadOnly, detailTexture[3].PixelFormat)
370 };
371
372 byte* ptr;
373 byte* ptrO;
374 for(int y = 0; y < theight; y++)
375 {
376 pcty = y * invtheightMinus1;
377 int ypatch = ((int)(y * yFactor) & 0x0f) * datas[0].Stride;
378 ptrO = (byte*)outputData.Scan0 + y * outputData.Stride;
379 ty = (int)(y * yFactor);
380 yglobalpos = (uint)ty + regionPositionY;
381
382 for(int x = 0; x < twidth; x++)
383 {
384 tx = (int)(x * xFactor);
385 pctx = x * invtwitdthMinus1;
386 height = (float)terrain[tx, ty];
387 layer = getLayerTex(height, pctx, pcty,
388 (uint)tx + regionPositionX, yglobalpos,
389 startHeights, heightRanges);
390
391 // Select two textures
392 l0 = (int)layer;
393 layerDiff = layer - l0;
394
395 int patchOffset = (tx & 0x0f) * 3 + ypatch;
396
397 ptr = (byte*)datas[l0].Scan0 + patchOffset;
398 aB = *(ptr++);
399 aG = *(ptr++);
400 aR = *(ptr);
401
402 l1 = Math.Min(l0 + 1, 3);
403 ptr = (byte*)datas[l1].Scan0 + patchOffset;
404 bB = *(ptr++);
405 bG = *(ptr++);
406 bR = *(ptr);
407
408
409 // Interpolate between the two selected textures
410 *(ptrO++) = (byte)(aB + layerDiff * (bB - aB));
411 *(ptrO++) = (byte)(aG + layerDiff * (bG - aG));
412 *(ptrO++) = (byte)(aR + layerDiff * (bR - aR));
413 }
414 }
415
416 for(int i = 0; i < detailTexture.Length; i++)
417 detailTexture[i].UnlockBits(datas[i]);
418 }
419
420 for(int i = 0; i < detailTexture.Length; i++)
421 if(detailTexture[i] != null)
422 detailTexture[i].Dispose();
423 }
424
425 output.UnlockBits(outputData);
426
427//output.Save("terr.png",ImageFormat.Png);
428
429 #endregion Texture Compositing
430
431 return output;
432 }
433
434 [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
435 private static float getLayerTex(float height, float pctX, float pctY, uint X, uint Y,
436 float[] startHeights, float[] heightRanges)
437 {
438 // Use bilinear interpolation between the four corners of start height and
439 // height range to select the current values at this position
440 float startHeight = ImageUtils.Bilinear(
441 startHeights[0], startHeights[2],
442 startHeights[1], startHeights[3],
443 pctX, pctY);
444 if (float.IsNaN(startHeight))
445 return 0;
446
447 startHeight = Utils.Clamp(startHeight, 0f, 255f);
448
449 float heightRange = ImageUtils.Bilinear(
450 heightRanges[0], heightRanges[2],
451 heightRanges[1], heightRanges[3],
452 pctX, pctY);
453 heightRange = Utils.Clamp(heightRange, 0f, 255f);
454 if(heightRange == 0f || float.IsNaN(heightRange))
455 return 0;
456
457 // Generate two frequencies of perlin noise based on our global position
458 // The magic values were taken from http://opensimulator.org/wiki/Terrain_Splatting
459 float sX = X * 0.20319f;
460 float sY = Y * 0.20319f;
461
462 float noise = Perlin.noise2(sX * 0.222222f, sY * 0.222222f) * 13.0f;
463 noise += Perlin.turbulence2(sX, sY, 2f) * 4.5f;
464
465 // Combine the current height, generated noise, start height, and height range parameters, then scale all of it
466 float layer = ((height + noise - startHeight) / heightRange) * 4f;
467 return Utils.Clamp(layer, 0f, 3f);
468 }
469 }
470}