"Varregion" is a feature of OpenSimulator that enables region sizes larger than 256x256. The region is just larger so it acts like a regular region but with borders farther apart.
This is different from the older Megaregions large region feature which does not require extra viewer support (above that already implemented by Linden Lab) but which relies on a number of fragile hacks to make a viewer work in a way that Linden Lab never supported. Megaregions also contain a number of extant simulator-side bugs.
A growing list of protocol changes to implement "varregion" is at Varregion Protocol.
Some restrictions apply:
- The dimensions must be a multiple of 256 and less than or equal to 8192.
- The dimensions must be square (as of 20131104)
- Adjacent regions must be the same size. For instance, you can have multiple 512x512 regions adjacent (and see into the other region and border cross). There seems to be a viewer problem where, if regions of different size are within view distance, crashes can happen. Consider having regions of only the same size 'in view'. Remember that the region coordinates are specified in 256m region count so a group of four 512x512 regions would be specified at 8000/8000, 8000/8002, 8002/8000, and 8002/8002, for instance.
- You must use BulletSim (as of 20131104, ODE has not been modified for varregions).
- You must use BulletSim's height map terrain implementation. As of 20140128, BulletSim has been modified to force heightmap terrain implementation if region size is greater than 256 on any side. The setting can be forced by adding to your INI files:
[BulletSim] TerrainImplementation = 0
- Because there is a lot of terrain in a varregion, it can take a while for the terrain to load into the viewer. You should set the following parameter so terrain is only loaded within an avatar's view distance and so the terrain is loaded from the avatar's position outward (rather than the 'lawn mower' pattern from the outside of the region:
[Terrain] SendTerrainUpdatesByViewDistance = true
- If you were previously using megaregions you must ensure that CombineContiguousRegions is now set to false.
[Startup] CombineContiguousRegions = false
The size is be specified in the Region.ini file:
[MyRegionName] RegionUUID = 95ec77ec-58c5-4ce2-9ff3-b6d1900d78a2 Location = 1000,1000 SizeX = 1024 SizeY = 1024 InternalAddress = 0.0.0.0 InternalPort = 9200 AllowAlternatePorts = False ExternalHostName = SYSTEMIP
If size is not specified it will default to the legacy size of 256.
If the given dimensions do not fit the restrictions, acceptable values are computed and warning and error messages are output into the log.
If converting from a mega-region, remember to set "CombineContiguousRegions = false".
For llRezObject to work beyond 256m set "ScriptDistanceLimitFactor = 512.0" (for 512mx512m) in the [Xengine] section of OpenSim.ini.
The terrain heightmap is stored in the database as an array of 'shorts' (two bytes) so, if you have a very large region you might need to set the database maximum size to a larger value. For instance, a 2816x2816 region (11x11 legacy regions) turns into a 56 megabyte heightmap and, for MySQL, you would need to set max_allowed_packet to something like 64M.
The usual terrain console commands work with varregions. If you have set up a larger region, you can load BMP/RAW/PNG heightmap files of the dimensions of the region and fill the whole region. For instance, a 1024x1024 region terrain would be completely initialized by "terrain load 1024.bmp" if '1024.bmp is a 1024x1024 bitmap.
Varregions and OAR Files
The objects and terrain are stored in an OAR file as if it was one region. That is, if you save a 1024x1024 region in an OAR file, you can later restore the objects and terrain into a 1024x1024 sized region. If you load an OAR file from a smaller region into a larger region, the unspecified terrain space will default to 25m.
To make conversion to varregions easier, load oar now has a '--displacement "<x,y,z>"' parameter. This displaces all of the objects and the terrain from the oar file when loading them into the new region. For instance, say you have four OAR files from four adjacent 256x256 regions (oar00.oar, oar01.oar, oar10.oar, and oar11.oar). You create a new 512x512 varregion named 'bigregion'. The following commands place the four regions of objects, terrains and parcels into the new larger region:
change region bigregion load oar oar00.oar load oar oar01.oar --displacement "<0,256,0>" --merge --force-terrain --force-parcels load oar oar10.oar --displacement "<256,0,0>" --merge --force-terrain --force-parcels load oar oar11.oar --displacement "<256,256,0>" --merge --force-terrain --force-parcels
Note the new "--force-terrain" and "--force-parcels" parameters. "--merge", used by itself, is for merging together the objects from multiple OARs. Merging also suppresses the loading of terrain and parcel data which is just what you want when merging objects. But, if loading multiple OARs to create a new, larger region, the terrain and parcel information must be loaded. Thus the new parameters.
Things Known to be Broken
- There is some problem with teleporting in and out of larger region. It works for some configurations and not others.
- While adjacent regions are usually viewable, sometimes they are not. They appear when you cross over into the region.
Since this will be a major change to OpenSimulator that touches a lot of different parts, subsequent posts, will discuss the changes I'm making.
One major problem is passing the terrain data from the region to the protocol stack. The existing implementation passed an array of floats that were presumed to be a 256x256 array of region terrain heights. The TerrainChannel class is an attempt to hide the terrain implementation from TerrainModule . TerrainChannel can't be passed into the protocol stack (LLClientView) because TerrainChannel is defined as part of OpenSim.Region.Framework which is not visible to the protocol code.
My solution is to create the TerrainData class in OpenSim.Framework. TerrainData just wraps the data structure for the terrain and additionally has the attributes giving X and Y size.
I didn't want to change the signature of IClientAPI since so many external modules rely on it. It should be changed to pass TerrainData rather than a float. I decided to not change IClientAPI but rather have LLClientView ignore the passed array and instead reach back into the associated scene and fetch the TerrainData instance.
There is one subclass of TerrainData: HeightmapTerrainData which keeps the terrain as a compressed heightmap. The height of each point is stored as a short which is height * compressionFactor where compressionFactor is usually "100". This creates a compact storage of the terrain heights with two decimal points of resolution.
The LLLP ("Linden Lab Legacy Protocol") sends terrain height data in compressed "patches" of 16x16 areas of the terrain height. This is a protocol feature that is implemented in TerrainChannel. I feel that underlying protocol optimizations shouldn't appear up the stack so, in creating TerrainData, I tried to hide terrain patches. I mean, someday terrain will be generalized meshes. Right?
Terrain in the Database
Previously, terrain height maps were saved in the database as a blob of 256x256 doubles. To have different region sizes, that format had to change. There is an existing database field revision that stored the time the terrain was saved. This revision information wasn't used for anything so this field was co-opted to contain a revision code for the height field blob.
There are three forms for the height map blob: legacy, compressed2D and regular2D. "legacy" is, of course, the previous 256x256 collections of doubles. "regular2D" contains the X, Y dimensions and enough floats for that area's heights. "compressed2D" contains the X,Y and compressionFactor followed by enough shorts for the height map.
The database readers and writers default to the legacy format and, if the region happens to have the dimensions 256x256, it is stored using the legacy format. This is an attempt to keep downward compatibility.
All of the database modules (MySQL, SQLite, MSSQL and PSQL) have been modified to store terrain this new way.
Sensing Border Crossing
Most of the ‘move to new region’ code is based on checking boundaries. There is much code related to computing if an object or avatar has crossed a region boundary and then computing the address of the next region from same. Introducing variable sized regions messes a lot of this computation up. That is, the code doing the arithmetic usually assumes it knows the address of the next region based on a known region size and then can compute the location of the next region based on that. With varregions those assumptions no longer hold. Varregion implementation means that the computation of region base locations and border locations moves to the GridService who is the entity who really knows the size of all the regions and what is adjacent to what.
The realization that location to region computation is really a GridService operation lead me to totally rip apart the grid boundary checking code and replace it with two functions: Scene.PositionIsInCurrentRegion(Vector3 pos) and then EntityTransferModule.GetRegionContainingWorldLocation(double X, double Y). The former function tests to see if the object/avatar has moved out of the current region and the latter gets the region moved into. (Side note: GetRegionContainingWorldLocation should really be a function on IGridService but that exercise is left for future hacking).
These changes leave all the 'border' code in limbo -- the generation of the border lists is still there but it is not being used. This should eventually be cleaned up. Also, the computation of neighbor regions is scattered around with routines on IGridService, in EntityTransferModule and a bunch of bookkeeping in Scene. This too should be cleaned up.
As of January 27, 2014, varregion is a feature of the 'master' repository branch.
Stuff to Work On
What follows are notes I am making as things that might need work are found in OpenSimulator.
- ITerrainLoader implementations (mostly done)
- How to handle tiles.
- How to handle large regions sizes.
- Terrain/FileLoaders/*.cs all return an ITerrainChannel
- Need to fix all the file reader/writers
- FileLoaders/LLRAW.cs has several "256"s rather than constant references
- Teleporting: should be able to teleport to anywhere in a large region
- HG code needs to allow addresses anywhere into large regions
- Need to look through RegionCombinerModule.cs and see what safety checks are needed
- Maybe just prevent combination if not legacy region size
- In LSL_Api.cs, llEdgeOfWorld() does some neighbor computation. Check for ok'ness.
- LandManagementModule.SendParcelOverlay sends land sale/ownership info for 4x4m areas
- Looks like it expects to send exactly one 1024 byte block (for the 256x256 legacy region
- Check viewer code for what will happen for larger regions
- New overlay types added
- LLClientView.SendMapBlockSplit() needs to have region size to send with the map info
- Region coords are sometimes 'int' and sometimes 'uint' with conversions EVERYWHERE
- pass over everything and convert region coordinates it 'uint's
- consider creating structures for world and region coordinates so there can be compile time checking
- LLSurface is wired to expect adjacent regions of same size
- Terrain surface image is sized with a 'static": " static S32 sTextureSize; // Size of the surface texture"
- From FreeNode:#SingularityViewer 20131125:
<Aleric> radams1, SianaGearz, frnic : I couldn't help myself doing a profile anyway.. The reason the viewer is slow is because it's busy "drawing" the terrain. "drawing" between quotes because my guess is that it is trying to draw ALL terrain and then clip that afterwards to the frustrum... <Aleric> So, this could be greatly improved by disregarding terrain way way earlier when it is beyond the drawing range anyway. <SianaGearz> OK <Aleric> I disabled all rendering, even added a 'return' in terrain render <Aleric> but.. no improvement of FPS! <frnic> oops <Aleric> LLSurfacePatch::updateVisibility() is still eating a lot of cpu. <Aleric> LLSurface::updatePatchVisibilities does 97% of the calls to LLSurfacePatch::updateVisibility <Aleric> which then spends most of it's time calling LLCamera::AABBInFrustumNoFarClip
- Singularity commit for varregion: https://github.com/singularity-viewer/SingularityViewer/commit/ad8ea07a
- Terrain/parcel operations
- Default 256 standalone region
- Clean database. Start single standalone region. Verify 'pimple' region. Verify one parcel. Flatten region.
- Create parcels adjacent to each edge and standalone in middle. Change names of each parcel and verify. Restart simulator and verify parcels exist and work correctly.
- Save five parcel region as OAR. Clear database. Restart region. Load oar. Verify 5 parcels of correct location, name and features.
- Adjacent 256 standalone regions
- 768x768 standalone region
- Changing land from 256 to 768
- Default 256 standalone region