opensim-development – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 eva 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  
28 using System;
29 using System.Diagnostics;
30 using System.Drawing;
31 using System.Drawing.Imaging;
32 using log4net;
33 using OpenMetaverse;
34 using OpenSim.Framework;
35 using OpenSim.Region.Framework.Interfaces;
36 using OpenSim.Services.Interfaces;
37  
38 namespace 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 public static Bitmap Splat(ITerrainChannel terrain,
83 UUID[] textureIDs, float[] startHeights, float[] heightRanges,
84 Vector3d regionPosition, IAssetService assetService, bool textureTerrain)
85 {
86 Debug.Assert(textureIDs.Length == 4);
87 Debug.Assert(startHeights.Length == 4);
88 Debug.Assert(heightRanges.Length == 4);
89  
90 Bitmap[] detailTexture = new Bitmap[4];
91  
92 if (textureTerrain)
93 {
94 // Swap empty terrain textureIDs with default IDs
95 for (int i = 0; i < textureIDs.Length; i++)
96 {
97 if (textureIDs[i] == UUID.Zero)
98 textureIDs[i] = DEFAULT_TERRAIN_DETAIL[i];
99 }
100  
101 #region Texture Fetching
102  
103 if (assetService != null)
104 {
105 for (int i = 0; i < 4; i++)
106 {
107 AssetBase asset;
108 UUID cacheID = UUID.Combine(TERRAIN_CACHE_MAGIC, textureIDs[i]);
109  
110 // Try to fetch a cached copy of the decoded/resized version of this texture
111 asset = assetService.GetCached(cacheID.ToString());
112 if (asset != null)
113 {
114 try
115 {
116 using (System.IO.MemoryStream stream = new System.IO.MemoryStream(asset.Data))
117 detailTexture[i] = (Bitmap)Image.FromStream(stream);
118 }
119 catch (Exception ex)
120 {
121 m_log.Warn("Failed to decode cached terrain texture " + cacheID +
122 " (textureID: " + textureIDs[i] + "): " + ex.Message);
123 }
124 }
125  
126 if (detailTexture[i] == null)
127 {
128 // Try to fetch the original JPEG2000 texture, resize if needed, and cache as PNG
129 asset = assetService.Get(textureIDs[i].ToString());
130 if (asset != null)
131 {
132 // m_log.DebugFormat(
133 // "[TERRAIN SPLAT]: Got cached original JPEG2000 terrain texture {0} {1}", i, asset.ID);
134  
135 try { detailTexture[i] = (Bitmap)CSJ2K.J2kImage.FromBytes(asset.Data); }
136 catch (Exception ex)
137 {
138 m_log.Warn("Failed to decode terrain texture " + asset.ID + ": " + ex.Message);
139 }
140 }
141  
142 if (detailTexture[i] != null)
143 {
144 // Make sure this texture is the correct size, otherwise resize
145 if (detailTexture[i].Width != 256 || detailTexture[i].Height != 256)
146 {
147 using (Bitmap origBitmap = detailTexture[i])
148 {
149 detailTexture[i] = ImageUtils.ResizeImage(origBitmap, 256, 256);
150 }
151 }
152  
153 // Save the decoded and resized texture to the cache
154 byte[] data;
155 using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
156 {
157 detailTexture[i].Save(stream, ImageFormat.Png);
158 data = stream.ToArray();
159 }
160  
161 // Cache a PNG copy of this terrain texture
162 AssetBase newAsset = new AssetBase
163 {
164 Data = data,
165 Description = "PNG",
166 Flags = AssetFlags.Collectable,
167 FullID = cacheID,
168 ID = cacheID.ToString(),
169 Local = true,
170 Name = String.Empty,
171 Temporary = true,
172 Type = (sbyte)AssetType.Unknown
173 };
174 newAsset.Metadata.ContentType = "image/png";
175 assetService.Store(newAsset);
176 }
177 }
178 }
179 }
180  
181 #endregion Texture Fetching
182 }
183  
184 // Fill in any missing textures with a solid color
185 for (int i = 0; i < 4; i++)
186 {
187 if (detailTexture[i] == null)
188 {
189 m_log.DebugFormat("{0} Missing terrain texture for layer {1}. Filling with solid default color",
190 LogHeader, i);
191 // Create a solid color texture for this layer
192 detailTexture[i] = new Bitmap(256, 256, PixelFormat.Format24bppRgb);
193 using (Graphics gfx = Graphics.FromImage(detailTexture[i]))
194 {
195 using (SolidBrush brush = new SolidBrush(DEFAULT_TERRAIN_COLOR[i]))
196 gfx.FillRectangle(brush, 0, 0, 256, 256);
197 }
198 }
199 else
200 {
201 if (detailTexture[i].Width != 256 || detailTexture[i].Height != 256)
202 {
203 detailTexture[i] = ResizeBitmap(detailTexture[i], 256, 256);
204 }
205 }
206 }
207  
208 #region Layer Map
209  
210 float[,] layermap = new float[256, 256];
211  
212 // Scale difference between actual region size and the 256 texture being created
213 int xFactor = terrain.Width / 256;
214 int yFactor = terrain.Height / 256;
215  
216 // Create 'layermap' where each value is the fractional layer number to place
217 // at that point. For instance, a value of 1.345 gives the blending of
218 // layer 1 and layer 2 for that point.
219 for (int y = 0; y < 256; y++)
220 {
221 for (int x = 0; x < 256; x++)
222 {
223 float height = (float)terrain[x * xFactor, y * yFactor];
224  
225 float pctX = (float)x / 255f;
226 float pctY = (float)y / 255f;
227  
228 // Use bilinear interpolation between the four corners of start height and
229 // height range to select the current values at this position
230 float startHeight = ImageUtils.Bilinear(
231 startHeights[0],
232 startHeights[2],
233 startHeights[1],
234 startHeights[3],
235 pctX, pctY);
236 startHeight = Utils.Clamp(startHeight, 0f, 255f);
237  
238 float heightRange = ImageUtils.Bilinear(
239 heightRanges[0],
240 heightRanges[2],
241 heightRanges[1],
242 heightRanges[3],
243 pctX, pctY);
244 heightRange = Utils.Clamp(heightRange, 0f, 255f);
245  
246 // Generate two frequencies of perlin noise based on our global position
247 // The magic values were taken from http://opensimulator.org/wiki/Terrain_Splatting
248 Vector3 vec = new Vector3
249 (
250 ((float)regionPosition.X + (x * xFactor)) * 0.20319f,
251 ((float)regionPosition.Y + (y * yFactor)) * 0.20319f,
252 height * 0.25f
253 );
254  
255 float lowFreq = Perlin.noise2(vec.X * 0.222222f, vec.Y * 0.222222f) * 6.5f;
256 float highFreq = Perlin.turbulence2(vec.X, vec.Y, 2f) * 2.25f;
257 float noise = (lowFreq + highFreq) * 2f;
258  
259 // Combine the current height, generated noise, start height, and height range parameters, then scale all of it
260 float layer = ((height + noise - startHeight) / heightRange) * 4f;
261 if (Single.IsNaN(layer))
262 layer = 0f;
263 layermap[x, y] = Utils.Clamp(layer, 0f, 3f);
264 }
265 }
266  
267 #endregion Layer Map
268  
269 #region Texture Compositing
270  
271 Bitmap output = new Bitmap(256, 256, PixelFormat.Format24bppRgb);
272 BitmapData outputData = output.LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
273  
274 // Unsafe work as we lock down the source textures for quicker access and access the
275 // pixel data directly
276 unsafe
277 {
278 // Get handles to all of the texture data arrays
279 BitmapData[] datas = new BitmapData[]
280 {
281 detailTexture[0].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[0].PixelFormat),
282 detailTexture[1].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[1].PixelFormat),
283 detailTexture[2].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[2].PixelFormat),
284 detailTexture[3].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[3].PixelFormat)
285 };
286  
287 // Compute size of each pixel data (used to address into the pixel data array)
288 int[] comps = new int[]
289 {
290 (datas[0].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3,
291 (datas[1].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3,
292 (datas[2].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3,
293 (datas[3].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3
294 };
295  
296 for (int y = 0; y < 256; y++)
297 {
298 for (int x = 0; x < 256; x++)
299 {
300 float layer = layermap[x, y];
301  
302 // Select two textures
303 int l0 = (int)Math.Floor(layer);
304 int l1 = Math.Min(l0 + 1, 3);
305  
306 byte* ptrA = (byte*)datas[l0].Scan0 + y * datas[l0].Stride + x * comps[l0];
307 byte* ptrB = (byte*)datas[l1].Scan0 + y * datas[l1].Stride + x * comps[l1];
308 byte* ptrO = (byte*)outputData.Scan0 + y * outputData.Stride + x * 3;
309  
310 float aB = *(ptrA + 0);
311 float aG = *(ptrA + 1);
312 float aR = *(ptrA + 2);
313  
314 float bB = *(ptrB + 0);
315 float bG = *(ptrB + 1);
316 float bR = *(ptrB + 2);
317  
318 float layerDiff = layer - l0;
319  
320 // Interpolate between the two selected textures
321 *(ptrO + 0) = (byte)Math.Floor(aB + layerDiff * (bB - aB));
322 *(ptrO + 1) = (byte)Math.Floor(aG + layerDiff * (bG - aG));
323 *(ptrO + 2) = (byte)Math.Floor(aR + layerDiff * (bR - aR));
324 }
325 }
326  
327 for (int i = 0; i < detailTexture.Length; i++)
328 detailTexture[i].UnlockBits(datas[i]);
329 }
330  
331 for (int i = 0; i < detailTexture.Length; i++)
332 if (detailTexture[i] != null)
333 detailTexture[i].Dispose();
334  
335 output.UnlockBits(outputData);
336  
337 // We generated the texture upside down, so flip it
338 output.RotateFlip(RotateFlipType.RotateNoneFlipY);
339  
340 #endregion Texture Compositing
341  
342 return output;
343 }
344  
345 public static Bitmap ResizeBitmap(Bitmap b, int nWidth, int nHeight)
346 {
347 m_log.DebugFormat("{0} ResizeBitmap. From <{1},{2}> to <{3},{4}>",
348 LogHeader, b.Width, b.Height, nWidth, nHeight);
349 Bitmap result = new Bitmap(nWidth, nHeight);
350 using (Graphics g = Graphics.FromImage(result))
351 g.DrawImage(b, 0, 0, nWidth, nHeight);
352 b.Dispose();
353 return result;
354 }
355  
356 public static Bitmap SplatSimple(float[] heightmap)
357 {
358 const float BASE_HSV_H = 93f / 360f;
359 const float BASE_HSV_S = 44f / 100f;
360 const float BASE_HSV_V = 34f / 100f;
361  
362 Bitmap img = new Bitmap(256, 256);
363 BitmapData bitmapData = img.LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
364  
365 unsafe
366 {
367 for (int y = 255; y >= 0; y--)
368 {
369 for (int x = 0; x < 256; x++)
370 {
371 float normHeight = heightmap[y * 256 + x] / 255f;
372 normHeight = Utils.Clamp(normHeight, BASE_HSV_V, 1.0f);
373  
374 Color4 color = Color4.FromHSV(BASE_HSV_H, BASE_HSV_S, normHeight);
375  
376 byte* ptr = (byte*)bitmapData.Scan0 + y * bitmapData.Stride + x * 3;
377 *(ptr + 0) = (byte)(color.B * 255f);
378 *(ptr + 1) = (byte)(color.G * 255f);
379 *(ptr + 2) = (byte)(color.R * 255f);
380 }
381 }
382 }
383  
384 img.UnlockBits(bitmapData);
385 return img;
386 }
387 }
388 }