I would say the pony is the easiest requirement.
The terrain asset is quite limited in what you can do with it in runtime. However... there are some undocumented API's you can use to basically construct/modify anything in a terrain. A simple google gave me the following code, don't know where it's from but it seems to use about anything terrain related to construct one dynamically. As the TerrainData API is undocumented it's unsupported and not guaranteed to work now or in the future...
I don't know who's the owner of the code below and haven't tried it myself but should be helpful to see how you can use the API.
Good luck with your project and pony.
//Download terrain assets referenced in headers
i = 1;
for(var v : String in trnArr) {
//v = "name;r:width,height,length,heightmapResolution //,detailResolution,controlResolution,textureResolution;h:heightMapUrl";
var vS2 : String[] = v.Split(";"[0]);
var tName = vS2[0];
for(i2 = 1; i2<vS2.length; i2++) {
var str : String[] = vS2[i2].Split(":"[0]);
if(str[0] == "r") var tRes : String[] = str[1].Split(","[0]);
else if(str[0] == "h") var tHtmp : String = whirld.getURL(str[1]);
else if(str[0] == "l") var tLtmp : String = whirld.getURL(str[1]);
else if(str[0] == "s") var tSpmp : String = whirld.getURL(str[1]);
else if(str[0] == "s2") var tSpmp2 : String = whirld.getURL(str[1]);
else if(str[0] == "t") var tTxts : String[] = str[1].Split(","[0]);
else if(str[0] == "d") var tDtmp : String = whirld.getURL(str[1]);
}
whirld.statusTxt = "Downloading Terrain " + i + " of " + trnArr.length + " (" + tName + "): " + tHtmp;
www = new WWW(tHtmp);
while(!www.isDone) {
whirld.progress = www.progress;
yield new WaitForSeconds(.1);
}
if (www.error != null) whirld.info += "\nTerrain Undownloadable: " + tName + " " + tHtmp + " (" + www.error + ")";
else {
whirld.statusTxt = "Initializing " + tName + "...";
whirld.progress = 0;
if(Application.isPlaying) yield;
var tWidth : int = parseInt(tRes[0]);
var tHeight : int = parseInt(tRes[1]);
var tLength : int = parseInt(tRes[2]);
var tHRes : int = parseInt(tRes[3]);
//var tDRes : int = parseInt(tRes[4]);
//var tCRes : int = parseInt(tRes[5]);
//var tBRes : int = parseInt(tRes[6]);
var trnDat : TerrainData = new TerrainData();
//Heights
trnDat.heightmapResolution = tHRes;
//trnDat.Init(tCRes, tDRes, tBRes);
var hmap = trnDat.GetHeights(0, 0, tHRes, tHRes);
var br : BinaryReader;
if(true) { //Terrain RAW file is compressed
/*var stream = new DeflateStream(new MemoryStream(www.bytes), CompressionMode.Decompress);
var buffer : byte[] = new byte[4096];
var ms : MemoryStream = new MemoryStream();
var bytesRead : int = 0;
while (bytesRead > 0) {
bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead > 0) ms.Write(buffer, 0, bytesRead);
}
br = new BinaryReader(ms);*/
br = new BinaryReader(new MemoryStream(new Ionic.Zlib.GZipStream(new MemoryStream(), Ionic.Zlib.CompressionMode.Decompress).UncompressBuffer(www.bytes)));
}
else br = new BinaryReader(new MemoryStream(www.bytes));
for (var x : int = 0; x < tHRes; x++) for (var y : int = 0; y < tHRes; y++) hmap[x, y] = br.ReadUInt16() / 65535.00000000;
trnDat.SetHeights(0, 0, hmap);
trnDat.size = Vector3(tWidth, tHeight, tLength);
//Textures
if(tTxts) {
var splatPrototypes : SplatPrototype[] = new SplatPrototype[tTxts.length];
for(i=0; i < tTxts.length; i++) {
var splatTxt : String[] = tTxts[i].Split("="[0]);
var splatTxtSize : String[] = splatTxt[1].Split("x"[0]);
whirld.statusTxt = "Downloading Terrain Texture " + (i + 1) + " of " + tTxts.length + " (" + splatTxt[0] + ")";
www = new WWW(whirld.getURL(splatTxt[0]));
while(!www.isDone) {
whirld.progress = www.progress;
yield new WaitForSeconds(.1);
}
if (www.error != null) whirld.info += "\nTerrain Texture Undownloadable: #" + (i + 1) + " (" + splatTxt[0] + ")";
else {
whirld.statusTxt = "Initializing Terrain Texture " + (i + 1) + " of " + tTxts.length + "...";
yield;
splatPrototypes[i] = new SplatPrototype();
splatPrototypes[i].texture = new Texture2D(4, 4, TextureFormat.DXT1, true);
www.LoadImageIntoTexture(splatPrototypes[i].texture);
splatPrototypes[i].texture.Apply(true);
splatPrototypes[i].texture.Compress(true);
splatPrototypes[i].tileSize = Vector2(parseInt(splatTxtSize[0]), parseInt(splatTxtSize[1]));
}
}
}
else {
splatPrototypes = new SplatPrototype[whirld.worldTerrainTextures.length];
for(i=0; i < whirld.worldTerrainTextures.length; i++) {
splatPrototypes[i] = new SplatPrototype();
splatPrototypes[i].texture = whirld.worldTerrainTextures[i];
splatPrototypes[i].tileSize = Vector2(15, 15);
}
}
trnDat.splatPrototypes = splatPrototypes;
//Lightmap
if(tLtmp) {
whirld.statusTxt = "Downloading Terrain Lightmap (" + tName + ")";
www = new WWW(tLtmp);
while(!www.isDone) {
whirld.progress = www.progress;
yield new WaitForSeconds(.1);
}
if (www.error != null) whirld.info += "\nTerrain Lightmap Undownloadable: " + tName + " " + tLtmp + " (" + www.error + ")";
else {
trnDat.lightmap = www.texture;
}
}
//Splatmap
if(tSpmp) {
if(tSpmp2) {
whirld.statusTxt = "Downloading Augmentative Terrain Texturemap (" + tName + ")";
www = new WWW(tSpmp2);
while(!www.isDone) {
whirld.progress = www.progress;
yield new WaitForSeconds(.1);
}
var mapColors2 = www.texture.GetPixels();
}
whirld.statusTxt = "Downloading Terrain Texturemap (" + tName + ")";
www = new WWW(tSpmp);
while(!www.isDone) {
whirld.progress = www.progress;
yield new WaitForSeconds(.1);
}
whirld.progress = 0;
whirld.statusTxt = "Mapping Terrain Textures...";
yield;
if (www.error != null) whirld.info += "\nTerrain Texturemap Undownloadable: " + tName + " " + tLtmp + " (" + www.error + ")";
else {
if (www.texture.format != TextureFormat.ARGB32 || www.texture.width != www.texture.height || Mathf.ClosestPowerOfTwo(www.texture.width) != www.texture.width) {
whirld.info += "\nTerrain Splatmap Unusable: Splatmap must be in RGBA 32 bit format, square, and it's size a power of 2";
}
else {
trnDat.alphamapResolution = www.texture.width;
var splatmapData = trnDat.GetAlphamaps(0, 0, www.texture.width, www.texture.width);
var mapColors = www.texture.GetPixels();
var ht : int = www.texture.height;
var wd : int = www.texture.width;
for (y = 0; y < ht; y++) for (x = 0; x < wd; x++) for (z = 0; z < trnDat.alphamapLayers; z++) {
if(z < 4) splatmapData[x,y,z] = mapColors[x * wd + y][z];
else splatmapData[x,y,z] = mapColors2[x * wd + y][z-4];
}
trnDat.SetAlphamaps(0, 0, splatmapData);
}
}
}
//Details (rocks, trees, grass, etc)
if(tDtmp) {
whirld.statusTxt = "Downloading Terrain Details (" + tDtmp + ")";
www = new WWW(tDtmp);
while(!www.isDone) {
whirld.progress = www.progress;
yield new WaitForSeconds(.1);
}
if (www.error != null) whirld.info += "\nTerrain Details Undownloadable: " + tName + " " + tDtmp + " (" + www.error + ")";
else {
var treePrototypes : Array = new Array();
var treeInstances : Array = new Array();
var treeProto : TreePrototype;
var detailProto : DetailPrototype;
var detailPrototypes : Array = new Array();
file = new Ionic.Zlib.GZipStream(new MemoryStream(), Ionic.Zlib.CompressionMode.Decompress).UncompressString(www.bytes).Split("\n"[0]);
for (i=0; i < file.length; i++) {
if(file[i] == "" || i == file.length - 1) { //Apply Existing Trees
if(treeProto) {
treePrototypes.Add(treeProto);
treeProto = null;
//continue;
}
if(detailProto) {
detailPrototypes.Add(detailProto);
detailProto = null;
//continue;
}
}
if(file[i].length > 10 && file[i].Substring(0, 10) == "detailmap2") {
whirld.statusTxt = "Downloading Augmentative Terrain Detail Map (" + whirld.getURL(file[i].Substring(11)) + ")";
www = new WWW(whirld.getURL(file[i].Substring(11)));
while(!www.isDone) {
whirld.progress = www.progress;
yield new WaitForSeconds(.1);
}
if (www.error != null) whirld.info += "\nAugmentative Terrain Detail Map Undownloadable: " + tName + " " + file[i].Substring(11) + " (" + www.error + ")";
else {
tex = www.texture;
pixels = www.texture.GetPixels();
if(detailPrototypes.length > 4) var detLayer4 = trnDat.GetDetailLayer(0, 0, trnDat.detailResolution, trnDat.detailResolution, 4);
if(detailPrototypes.length > 5) var detLayer5 = trnDat.GetDetailLayer(0, 0, trnDat.detailResolution, trnDat.detailResolution, 5);
if(detailPrototypes.length > 6) var detLayer6 = trnDat.GetDetailLayer(0, 0, trnDat.detailResolution, trnDat.detailResolution, 6);
if(detailPrototypes.length > 7) var detLayer7 = trnDat.GetDetailLayer(0, 0, trnDat.detailResolution, trnDat.detailResolution, 7);
i2 = 0;
for(iY = 0; iY < trnDat.detailResolution; iY++) {
for(iX = 0; iX < trnDat.detailResolution; iX++) {
if(detailPrototypes.length > 4) detLayer4[iX, iY] = Mathf.RoundToInt(pixels[i2].r * 16);
if(detailPrototypes.length > 5) detLayer5[iX, iY] = Mathf.RoundToInt(pixels[i2].g * 16);
if(detailPrototypes.length > 6) detLayer6[iX, iY] = Mathf.RoundToInt(pixels[i2].b * 16);
if(detailPrototypes.length > 7) detLayer7[iX, iY] = Mathf.RoundToInt(pixels[i2].a * 16);
i2 += 1;
}
}
}
}
else if(file[i].length > 10 && file[i].Substring(0, 9) == "detailmap") {
whirld.statusTxt = "Downloading Terrain Detail Map (" + whirld.getURL(file[i].Substring(10)) + ")";
www = new WWW(whirld.getURL(file[i].Substring(10)));
while(!www.isDone) {
whirld.progress = www.progress;
yield new WaitForSeconds(.1);
}
if (www.error != null) whirld.info += "\nTerrain Detail Map Undownloadable: " + tName + " " + file[i].Substring(10) + " (" + www.error + ")";
else {
tex = www.texture;
pixels = www.texture.GetPixels();
trnDat.detailResolution = www.texture.width;
if(detailPrototypes.length > 0) var detLayer0 = trnDat.GetDetailLayer(0, 0, trnDat.detailResolution, trnDat.detailResolution, 0);
if(detailPrototypes.length > 1) var detLayer1 = trnDat.GetDetailLayer(0, 0, trnDat.detailResolution, trnDat.detailResolution, 1);
if(detailPrototypes.length > 2) var detLayer2 = trnDat.GetDetailLayer(0, 0, trnDat.detailResolution, trnDat.detailResolution, 2);
if(detailPrototypes.length > 3) var detLayer3 = trnDat.GetDetailLayer(0, 0, trnDat.detailResolution, trnDat.detailResolution, 3);
i2 = 0;
for(iY = 0; iY < trnDat.detailResolution; iY++) {
for(iX = 0; iX < trnDat.detailResolution; iX++) {
if(detailPrototypes.length > 0) detLayer0[iX, iY] = Mathf.RoundToInt(pixels[i2].r * 16);
if(detailPrototypes.length > 1) detLayer1[iX, iY] = Mathf.RoundToInt(pixels[i2].g * 16);
if(detailPrototypes.length > 2) detLayer2[iX, iY] = Mathf.RoundToInt(pixels[i2].b * 16);
if(detailPrototypes.length > 3) detLayer3[iX, iY] = Mathf.RoundToInt(pixels[i2].a * 16);
i2 += 1;
}
}
}
}
else if(file[i] == "tree") { //Create new tree prototype
treeProto = new TreePrototype();
}
else if(file[i] == "detail") {
detailProto = new DetailPrototype();
}
else if(treeProto) {
if(file[i].Substring(0, 1) == "m") {
//treeProto.prefab = Resources.Load(file[i].Substring(2));
//treeProto.prefab = Game.Controller.prefab;
//prefab.AddComponent(MeshFilter);
//prefab.AddComponent(MeshRenderer);
if(objects.ContainsKey(file[i].Substring(2))) {
treeProto.prefab = prefabs.Pop();
treeProto.prefab.name = file[i].Substring(2);
if(!treeProto.prefab.GetComponent(MeshFilter)) treeProto.prefab.AddComponent(MeshFilter);
treeProto.prefab.GetComponent(MeshFilter).mesh = objects[file[i].Substring(2)].GetComponent(MeshFilter).mesh;
if(!treeProto.prefab.GetComponent(MeshRenderer)) treeProto.prefab.AddComponent(MeshRenderer);
treeProto.prefab.GetComponent(MeshRenderer).materials = objects[file[i].Substring(2)].GetComponent(MeshRenderer).materials;
//treeProto.prefab = objects[file[i].Substring(2)];
}
else whirld.info += "\nTerrain Detail Mesh not found: " + file[i].Substring(2);
}
else if(file[i].Substring(0, 1) == "b") treeProto.bendFactor = parseFloat(file[i].Substring(2));
else if(file[i+1] == "") { //Read detail objects
var treedat : String[] = file[i].Split(";"[0]);
for(var tr : String in treedat) {
if(!tr) continue;
var tree = tr.Split(","[0]);
if(!tree[0] || tree[0] == "") continue;
var treeInstance = new TreeInstance();
treeInstance.prototypeIndex = treePrototypes.length;
treeInstance.position = Vector3(parseFloat(tree[0]), parseFloat(tree[1]), parseFloat(tree[2]));
treeInstance.widthScale = parseFloat(tree[3]);
treeInstance.heightScale = parseFloat(tree[4]);
var c : float = Random.Range(.6, .7);
treeInstance.color = Color(c - .15, c - .15, c - .15, 1);
treeInstance.lightmapColor = Color(c + .15, c + .15, c + .15, 0);
treeInstances.Add(treeInstance);
}
}
}
else if(detailProto) {
l = file[i].Split(" "[0]);
if(l[0] == "pO") {
if(objects.ContainsKey(l[1])) {
//detailProto.prototype = objects[l[1]];
detailProto.prototype = prefabs.Pop();
detailProto.prototype.name = l[1];
if(!detailProto.prototype.GetComponent(MeshFilter)) detailProto.prototype.AddComponent(MeshFilter);
detailProto.prototype.GetComponent(MeshFilter).mesh = objects[l[1]].GetComponent(MeshFilter).mesh;
if(!detailProto.prototype.GetComponent(MeshRenderer)) detailProto.prototype.AddComponent(MeshRenderer);
detailProto.prototype.GetComponent(MeshRenderer).material = objects[l[1]].GetComponent(MeshRenderer).material;
}
else whirld.info += "\nTerrain Detail Mesh not found: " + l[1];
}
else if(l[0] == "pT") {
l[1] = whirld.getURL(l[1]);
whirld.statusTxt = "Downloading Terrain Detail Texture (" + l[1] + ")";
www = new WWW(l[1]);
while(!www.isDone) {
whirld.progress = www.progress;
yield new WaitForSeconds(.1);
}
if (www.error != null) whirld.info += "\nTerrain Detail Texture Undownloadable: " + l[1];
else {
whirld.statusTxt = "Initializing " + vS[0] + "...";
whirld.progress = 0;
yield;
mshTxt = new Texture2D(4, 4, TextureFormat.DXT5, true);
www.LoadImageIntoTexture(mshTxt);
mshTxt.Apply(true);
mshTxt.Compress(true);
mshTxt.wrapMode = TextureWrapMode.Clamp;
detailProto.prototypeTexture = mshTxt;
}
}
else if(l[0] == "minW") detailProto.minWidth = parseFloat(l[1]);
else if(l[0] == "maxW") detailProto.maxWidth = parseFloat(l[1]);
else if(l[0] == "minH") detailProto.minHeight = parseFloat(l[1]);
else if(l[0] == "maxH") detailProto.maxHeight = parseFloat(l[1]);
else if(l[0] == "nS") detailProto.noiseSpread = parseFloat(l[1]);
else if(l[0] == "bF") detailProto.bendFactor = parseFloat(l[1]);
else if(l[0] == "hC") detailProto.healthyColor = Color(parseFloat(l[1]), parseFloat(l[2]), parseFloat(l[3]));
else if(l[0] == "dC") detailProto.dryColor = Color(parseFloat(l[1]), parseFloat(l[2]), parseFloat(l[3]));
else if(l[0] == "lF") detailProto.lightmapFactor = parseFloat(l[1]);
else if(l[0] == "gL") detailProto.grayscaleLighting = (l[1] == "1" ? true : false);
else if(l[0] == "rM") {
if(l[1] == "GrassBillboard") detailProto.renderMode = DetailRenderMode.GrassBillboard;
else if(l[1] == "VertexLit") detailProto.renderMode = DetailRenderMode.VertexLit;
else detailProto.renderMode = DetailRenderMode.Grass;
}
else if(l[0] == "uM") detailProto.usePrototypeMesh = (l[1] == "1" ? true : false);
}
}
trnDat.treePrototypes = treePrototypes.ToBuiltin(TreePrototype);
trnDat.treeInstances = treeInstances.ToBuiltin(TreeInstance);
trnDat.detailPrototypes = detailPrototypes.ToBuiltin(DetailPrototype);
if(detailPrototypes.length > 0) trnDat.SetDetailLayer(0, 0, 0, detLayer0);
if(detailPrototypes.length > 1) trnDat.SetDetailLayer(0, 0, 1, detLayer1);
if(detailPrototypes.length > 2) trnDat.SetDetailLayer(0, 0, 2, detLayer2);
if(detailPrototypes.length > 3) trnDat.SetDetailLayer(0, 0, 3, detLayer3);
if(detailPrototypes.length > 4) trnDat.SetDetailLayer(0, 0, 4, detLayer4);
if(detailPrototypes.length > 5) trnDat.SetDetailLayer(0, 0, 5, detLayer5);
if(detailPrototypes.length > 6) trnDat.SetDetailLayer(0, 0, 6, detLayer6);
if(detailPrototypes.length > 7) trnDat.SetDetailLayer(0, 0, 7, detLayer7);
//trnDat.RefreshPrototypes();
//trnDat.RecalculateTreePositions();
}
/*for(i=0; i<trnDat.treePrototypes.length; i++) {
trnDat.treePrototypes[i].prefab.name = trnDat.treePrototypes[i].prefab.name.Replace(" ", "_");
MeshWriteObj(trnDat.treePrototypes[i].prefab.GetComponent(MeshFilter), trnDat.treePrototypes[i].prefab.name);
data = "[msh:" + trnDat.treePrototypes[i].prefab.name + "," + trnDat.treePrototypes[i].prefab.name + ".obj.gz]" + data;
trnV += (trnV != "" ? "\n\n" : "") + "tree\nm:" + trnDat.treePrototypes[i].prefab.name + "\nb:" + trnDat.treePrototypes[i].bendFactor + "\n";
for(var tree : TreeInstance in trnDat.treeInstances) {
if(tree.prototypeIndex != i) continue;
trnV += tree.position.x.ToString("F1", System.Globalization.CultureInfo.InvariantCulture) + "," + tree.position.y.ToString("F1", System.Globalization.CultureInfo.InvariantCulture) + "," + tree.position.z.ToString("F1", System.Globalization.CultureInfo.InvariantCulture) + "," + tree.widthScale.ToString("F1", System.Globalization.CultureInfo.InvariantCulture) + "," + tree.heightScale.ToString("F1", System.Globalization.CultureInfo.InvariantCulture) + ";";
}
}*/
}
//Go!
var trnObj : GameObject = new GameObject(tName);
trnObj.AddComponent(Terrain);
trnObj.GetComponent(Terrain).terrainData = trnDat;
trnObj.AddComponent(TerrainCollider);
trnObj.GetComponent(TerrainCollider).terrainData = trnDat;
objects.Add(tName, trnObj);
GameObject.Destroy(trnObj);
whirld.statusTxt = "";
}
i++;
}