diff options
author | Kevin Cozens | 2011-01-26 20:53:21 -0500 |
---|---|---|
committer | Justin Clark-Casey (justincc) | 2011-01-28 21:19:53 +0000 |
commit | 9798b044fe92df6bd1b3f18c04937bd99a4d98cb (patch) | |
tree | 0fad6c8fccf80e395f150f776330bd29f7c1ac7f | |
parent | Make the new style stuff compatible with the older revision (diff) | |
download | opensim-SC_OLD-9798b044fe92df6bd1b3f18c04937bd99a4d98cb.zip opensim-SC_OLD-9798b044fe92df6bd1b3f18c04937bd99a4d98cb.tar.gz opensim-SC_OLD-9798b044fe92df6bd1b3f18c04937bd99a4d98cb.tar.bz2 opensim-SC_OLD-9798b044fe92df6bd1b3f18c04937bd99a4d98cb.tar.xz |
Added loading and saving of terrain files using Terragen format (Mantis #1564)
Terrain files can now be loaded and saved using the Terragen (.ter) format.
Selection of the terrain file loader to use is now based on the extension
of the filename being loaded and the data is loaded using a memory stream
instead of writing it to a file and then loading it from the file.
-rw-r--r-- | OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs | 35 | ||||
-rw-r--r-- | OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs | 239 |
2 files changed, 224 insertions, 50 deletions
diff --git a/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs b/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs index b0563c5..01f04d9 100644 --- a/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs +++ b/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs | |||
@@ -555,37 +555,12 @@ namespace OpenSim.Region.CoreModules.World.Estate | |||
555 | 555 | ||
556 | try | 556 | try |
557 | { | 557 | { |
558 | MemoryStream terrainStream = new MemoryStream(terrainData); | ||
559 | terr.LoadFromStream(filename, terrainStream); | ||
560 | terrainStream.Close(); | ||
558 | 561 | ||
559 | string localfilename = "terrain.raw"; | 562 | FileInfo x = new FileInfo(filename); |
560 | 563 | remoteClient.SendAlertMessage("Your terrain was loaded as a " + x.Extension + " file. It may take a few moments to appear."); | |
561 | if (terrainData.Length == 851968) | ||
562 | { | ||
563 | localfilename = Path.Combine(Util.dataDir(),"terrain.raw"); // It's a .LLRAW | ||
564 | } | ||
565 | |||
566 | if (terrainData.Length == 196662) // 24-bit 256x256 Bitmap | ||
567 | localfilename = Path.Combine(Util.dataDir(), "terrain.bmp"); | ||
568 | |||
569 | if (terrainData.Length == 256 * 256 * 4) // It's a .R32 | ||
570 | localfilename = Path.Combine(Util.dataDir(), "terrain.r32"); | ||
571 | |||
572 | if (terrainData.Length == 256 * 256 * 8) // It's a .R64 | ||
573 | localfilename = Path.Combine(Util.dataDir(), "terrain.r64"); | ||
574 | |||
575 | if (File.Exists(localfilename)) | ||
576 | { | ||
577 | File.Delete(localfilename); | ||
578 | } | ||
579 | |||
580 | FileStream input = new FileStream(localfilename, FileMode.CreateNew); | ||
581 | input.Write(terrainData, 0, terrainData.Length); | ||
582 | input.Close(); | ||
583 | |||
584 | FileInfo x = new FileInfo(localfilename); | ||
585 | |||
586 | terr.LoadFromFile(localfilename); | ||
587 | remoteClient.SendAlertMessage("Your terrain was loaded as a ." + x.Extension + " file. It may take a few moments to appear."); | ||
588 | |||
589 | } | 564 | } |
590 | catch (IOException e) | 565 | catch (IOException e) |
591 | { | 566 | { |
diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs index 3ee20ae..2919897 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs | |||
@@ -30,6 +30,7 @@ using System.IO; | |||
30 | using System.Text; | 30 | using System.Text; |
31 | using OpenSim.Region.Framework.Interfaces; | 31 | using OpenSim.Region.Framework.Interfaces; |
32 | using OpenSim.Region.Framework.Scenes; | 32 | using OpenSim.Region.Framework.Scenes; |
33 | using OpenSim.Framework; | ||
33 | 34 | ||
34 | namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders | 35 | namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders |
35 | { | 36 | { |
@@ -53,17 +54,120 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders | |||
53 | return retval; | 54 | return retval; |
54 | } | 55 | } |
55 | 56 | ||
57 | public ITerrainChannel LoadFile(string filename, int offsetX, int offsetY, int fileWidth, int fileHeight, int sectionWidth, int sectionHeight) | ||
58 | { | ||
59 | TerrainChannel retval = new TerrainChannel(sectionWidth, sectionHeight); | ||
60 | |||
61 | FileInfo file = new FileInfo(filename); | ||
62 | FileStream s = file.Open(FileMode.Open, FileAccess.Read); | ||
63 | BinaryReader bs = new BinaryReader(s); | ||
64 | |||
65 | bool eof = false; | ||
66 | |||
67 | int fileXPoints = 0; | ||
68 | int fileYPoints = 0; | ||
69 | |||
70 | // Terragen file | ||
71 | while (eof == false) | ||
72 | { | ||
73 | string tmp = Encoding.ASCII.GetString(bs.ReadBytes(4)); | ||
74 | switch (tmp) | ||
75 | { | ||
76 | case "SIZE": | ||
77 | fileXPoints = bs.ReadInt16() + 1; | ||
78 | fileYPoints = fileXPoints; | ||
79 | bs.ReadInt16(); | ||
80 | break; | ||
81 | case "XPTS": | ||
82 | fileXPoints = bs.ReadInt16(); | ||
83 | bs.ReadInt16(); | ||
84 | break; | ||
85 | case "YPTS": | ||
86 | fileYPoints = bs.ReadInt16(); | ||
87 | bs.ReadInt16(); | ||
88 | break; | ||
89 | case "ALTW": | ||
90 | eof = true; | ||
91 | Int16 heightScale = bs.ReadInt16(); | ||
92 | Int16 baseHeight = bs.ReadInt16(); | ||
93 | |||
94 | int currFileYOffset = 0; | ||
95 | |||
96 | // if our region isn't on the first X section of the areas to be landscaped, then | ||
97 | // advance to our section of the file | ||
98 | while (currFileYOffset < offsetY) | ||
99 | { | ||
100 | // read a whole strip of regions | ||
101 | int heightsToRead = sectionHeight * fileXPoints; | ||
102 | bs.ReadBytes(heightsToRead * 2); // because the shorts are 2 bytes in the file | ||
103 | currFileYOffset++; | ||
104 | } | ||
105 | |||
106 | for (int y = 0; y < sectionHeight; y++) | ||
107 | { | ||
108 | int currFileXOffset = 0; | ||
109 | |||
110 | // if our region isn't the first X section of the areas to be landscaped, then | ||
111 | // advance the stream to the X start pos of our section in the file | ||
112 | // i.e. eat X upto where we start | ||
113 | while (currFileXOffset < offsetX) | ||
114 | { | ||
115 | bs.ReadBytes(sectionWidth * 2); // 2 bytes = short | ||
116 | currFileXOffset++; | ||
117 | } | ||
118 | |||
119 | // got to our X offset, so write our regions X line | ||
120 | for (int x = 0; x < sectionWidth; x++) | ||
121 | { | ||
122 | // Read a strip and continue | ||
123 | retval[x, y] = baseHeight + bs.ReadInt16() * (double)heightScale / 65536.0; | ||
124 | } | ||
125 | // record that we wrote it | ||
126 | currFileXOffset++; | ||
127 | |||
128 | // if our region isn't the last X section of the areas to be landscaped, then | ||
129 | // advance the stream to the end of this Y column | ||
130 | while (currFileXOffset < fileWidth) | ||
131 | { | ||
132 | // eat the next regions x line | ||
133 | bs.ReadBytes(sectionWidth * 2); // 2 bytes = short | ||
134 | currFileXOffset++; | ||
135 | } | ||
136 | //eat the last additional point | ||
137 | bs.ReadInt16(); | ||
138 | } | ||
139 | |||
140 | |||
141 | break; | ||
142 | default: | ||
143 | bs.ReadInt32(); | ||
144 | break; | ||
145 | } | ||
146 | } | ||
147 | |||
148 | bs.Close(); | ||
149 | s.Close(); | ||
150 | |||
151 | return retval; | ||
152 | } | ||
153 | |||
56 | public ITerrainChannel LoadStream(Stream s) | 154 | public ITerrainChannel LoadStream(Stream s) |
57 | { | 155 | { |
58 | TerrainChannel retval = new TerrainChannel(); | 156 | |
157 | int w = (int)Constants.RegionSize; | ||
158 | int h = (int)Constants.RegionSize; | ||
159 | |||
160 | TerrainChannel retval = new TerrainChannel(w, h); | ||
59 | 161 | ||
60 | BinaryReader bs = new BinaryReader(s); | 162 | BinaryReader bs = new BinaryReader(s); |
61 | 163 | ||
62 | bool eof = false; | 164 | bool eof = false; |
63 | if (Encoding.ASCII.GetString(bs.ReadBytes(16)) == "TERRAGENTERRAIN ") | 165 | if (Encoding.ASCII.GetString(bs.ReadBytes(16)) == "TERRAGENTERRAIN ") |
64 | { | 166 | { |
65 | int w = 256; | 167 | |
66 | int h = 256; | 168 | int fileWidth = w; |
169 | int fileHeight = h; | ||
170 | |||
67 | 171 | ||
68 | // Terragen file | 172 | // Terragen file |
69 | while (eof == false) | 173 | while (eof == false) |
@@ -73,30 +177,27 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders | |||
73 | { | 177 | { |
74 | case "SIZE": | 178 | case "SIZE": |
75 | int sztmp = bs.ReadInt16() + 1; | 179 | int sztmp = bs.ReadInt16() + 1; |
76 | w = sztmp; | 180 | fileWidth = sztmp; |
77 | h = sztmp; | 181 | fileHeight = sztmp; |
78 | bs.ReadInt16(); | 182 | bs.ReadInt16(); |
79 | break; | 183 | break; |
80 | case "XPTS": | 184 | case "XPTS": |
81 | w = bs.ReadInt16(); | 185 | fileWidth = bs.ReadInt16(); |
82 | bs.ReadInt16(); | 186 | bs.ReadInt16(); |
83 | break; | 187 | break; |
84 | case "YPTS": | 188 | case "YPTS": |
85 | h = bs.ReadInt16(); | 189 | fileHeight = bs.ReadInt16(); |
86 | bs.ReadInt16(); | 190 | bs.ReadInt16(); |
87 | break; | 191 | break; |
88 | case "ALTW": | 192 | case "ALTW": |
89 | eof = true; | 193 | eof = true; |
90 | Int16 heightScale = bs.ReadInt16(); | 194 | Int16 heightScale = bs.ReadInt16(); |
91 | Int16 baseHeight = bs.ReadInt16(); | 195 | Int16 baseHeight = bs.ReadInt16(); |
92 | retval = new TerrainChannel(w, h); | 196 | for (int y = 0; y < h; y++) |
93 | int x; | ||
94 | for (x = 0; x < w; x++) | ||
95 | { | 197 | { |
96 | int y; | 198 | for (int x = 0; x < w; x++) |
97 | for (y = 0; y < h; y++) | ||
98 | { | 199 | { |
99 | retval[x, y] = baseHeight + bs.ReadInt16() * (double) heightScale / 65536.0; | 200 | retval[x, y] = baseHeight + bs.ReadInt16() * (double)heightScale / 65536.0; |
100 | } | 201 | } |
101 | } | 202 | } |
102 | break; | 203 | break; |
@@ -114,12 +215,92 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders | |||
114 | 215 | ||
115 | public void SaveFile(string filename, ITerrainChannel map) | 216 | public void SaveFile(string filename, ITerrainChannel map) |
116 | { | 217 | { |
117 | throw new NotImplementedException(); | 218 | FileInfo file = new FileInfo(filename); |
219 | FileStream s = file.Open(FileMode.Create, FileAccess.Write); | ||
220 | SaveStream(s, map); | ||
221 | |||
222 | s.Close(); | ||
118 | } | 223 | } |
119 | 224 | ||
120 | public void SaveStream(Stream stream, ITerrainChannel map) | 225 | public void SaveStream(Stream stream, ITerrainChannel map) |
121 | { | 226 | { |
122 | throw new NotImplementedException(); | 227 | BinaryWriter bs = new BinaryWriter(stream); |
228 | |||
229 | //find the max and min heights on the map | ||
230 | double heightMax = map[0,0]; | ||
231 | double heightMin = map[0,0]; | ||
232 | |||
233 | for (int y = 0; y < map.Height; y++) | ||
234 | { | ||
235 | for (int x = 0; x < map.Width; x++) | ||
236 | { | ||
237 | double current = map[x,y]; | ||
238 | if (heightMax < current) | ||
239 | heightMax = current; | ||
240 | if (heightMin > current) | ||
241 | heightMin = current; | ||
242 | } | ||
243 | } | ||
244 | |||
245 | double baseHeight = Math.Floor( (heightMax + heightMin) / 2d ); | ||
246 | |||
247 | double horizontalScale = Math.Ceiling((heightMax - heightMin)); | ||
248 | |||
249 | // if we are completely flat add 1cm range to avoid NaN divisions | ||
250 | if (horizontalScale < 0.01d) | ||
251 | horizontalScale = 0.01d; | ||
252 | |||
253 | System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding(); | ||
254 | |||
255 | bs.Write(enc.GetBytes("TERRAGENTERRAIN ")); | ||
256 | |||
257 | bs.Write(enc.GetBytes("SIZE")); | ||
258 | bs.Write(Convert.ToInt16(Constants.RegionSize)); | ||
259 | bs.Write(Convert.ToInt16(0)); // necessary padding | ||
260 | |||
261 | //The XPTS and YPTS chunks are not needed for square regions | ||
262 | //but L3DT won't load the terrain file properly without them. | ||
263 | bs.Write(enc.GetBytes("XPTS")); | ||
264 | bs.Write(Convert.ToInt16(Constants.RegionSize)); | ||
265 | bs.Write(Convert.ToInt16(0)); // necessary padding | ||
266 | |||
267 | bs.Write(enc.GetBytes("YPTS")); | ||
268 | bs.Write(Convert.ToInt16(Constants.RegionSize)); | ||
269 | bs.Write(Convert.ToInt16(0)); // necessary padding | ||
270 | |||
271 | bs.Write(enc.GetBytes("SCAL")); | ||
272 | bs.Write(ToLittleEndian(1f)); //we're going to say that 1 terrain unit is 1 metre | ||
273 | bs.Write(ToLittleEndian(1f)); | ||
274 | bs.Write(ToLittleEndian(1f)); | ||
275 | |||
276 | // as we are square and not projected on a sphere then the other | ||
277 | // header blocks are not required | ||
278 | |||
279 | // now write the elevation data | ||
280 | bs.Write(enc.GetBytes("ALTW")); | ||
281 | bs.Write(Convert.ToInt16(horizontalScale)); // range between max and min | ||
282 | bs.Write(Convert.ToInt16(baseHeight)); // base height or mid point | ||
283 | |||
284 | for (int y = 0; y < map.Height; y++) | ||
285 | { | ||
286 | for (int x = 0; x < map.Width; x++) | ||
287 | { | ||
288 | float elevation = (float)((map[x,y] - baseHeight) * 65536 ) / (float)horizontalScale; // see LoadStream for inverse | ||
289 | |||
290 | // clamp rounding issues | ||
291 | if (elevation > Int16.MaxValue) | ||
292 | elevation = Int16.MaxValue; | ||
293 | else if (elevation < Int16.MinValue) | ||
294 | elevation = Int16.MinValue; | ||
295 | |||
296 | bs.Write(Convert.ToInt16(elevation)); | ||
297 | } | ||
298 | } | ||
299 | |||
300 | //This is only necessary for older versions of Terragen. | ||
301 | bs.Write(enc.GetBytes("EOF ")); | ||
302 | |||
303 | bs.Close(); | ||
123 | } | 304 | } |
124 | 305 | ||
125 | public string FileExtension | 306 | public string FileExtension |
@@ -127,16 +308,34 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders | |||
127 | get { return ".ter"; } | 308 | get { return ".ter"; } |
128 | } | 309 | } |
129 | 310 | ||
130 | public ITerrainChannel LoadFile(string filename, int x, int y, int fileWidth, int fileHeight, int w, int h) | ||
131 | { | ||
132 | throw new NotImplementedException(); | ||
133 | } | ||
134 | |||
135 | #endregion | 311 | #endregion |
136 | 312 | ||
137 | public override string ToString() | 313 | public override string ToString() |
138 | { | 314 | { |
139 | return "Terragen"; | 315 | return "Terragen"; |
140 | } | 316 | } |
317 | |||
318 | /// <summary> | ||
319 | /// terragen SCAL floats need to be written intel ordered regardless of | ||
320 | /// big or little endian system | ||
321 | /// </summary> | ||
322 | /// <param name="number"></param> | ||
323 | /// <returns></returns> | ||
324 | private byte[] ToLittleEndian( float number) | ||
325 | { | ||
326 | byte[] retVal = BitConverter.GetBytes(number); | ||
327 | if (BitConverter.IsLittleEndian == false) | ||
328 | { | ||
329 | byte[] tmp = new byte[4]; | ||
330 | for (int i = 0; i < 4; i++) | ||
331 | { | ||
332 | tmp[i] = retVal[3 - i]; | ||
333 | } | ||
334 | retVal = tmp; | ||
335 | |||
336 | } | ||
337 | return retVal ; | ||
338 | } | ||
339 | |||
141 | } | 340 | } |
142 | } | 341 | } |