Skip to content

Commit

Permalink
Import model (#18)
Browse files Browse the repository at this point in the history
* Asset mod helper for creating custom 3D objects
* New sample mod which spawns rubber ducks
  • Loading branch information
amazingalek authored Dec 23, 2019
1 parent 24690b7 commit 8facd84
Show file tree
Hide file tree
Showing 24 changed files with 664 additions and 9 deletions.
13 changes: 13 additions & 0 deletions OWML.Assets/Mod3DObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using UnityEngine;

namespace OWML.Assets
{
public class Mod3DObject : MonoBehaviour
{
private void Start()
{
DontDestroyOnLoad(gameObject);
}

}
}
59 changes: 59 additions & 0 deletions OWML.Assets/ModAssets.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Collections;
using OWML.Common;
using UnityEngine;

namespace OWML.Assets
{
public class ModAssets : IModAssets
{
private readonly IModConsole _console;
private readonly ObjImporter _objImporter;

public ModAssets(IModConsole console)
{
_console = console;
_objImporter = new ObjImporter();
}

public GameObject Create3DObject(ModBehaviour modBehaviour, string objectFilename, string imageFilename)
{
var objectPath = modBehaviour.ModManifest.FolderPath + objectFilename;
var imagePath = modBehaviour.ModManifest.FolderPath + imageFilename;

_console.WriteLine("Creating object from " + objectPath);

var go = new GameObject();
go.AddComponent<Mod3DObject>();

var loadObject = LoadObject(go, objectPath);
modBehaviour.StartCoroutine(loadObject);

var loadTexture = LoadTexture(go, imagePath);
modBehaviour.StartCoroutine(loadTexture);

return go;
}

private IEnumerator LoadObject(GameObject go, string objectPath)
{
var mesh = _objImporter.ImportFile(objectPath);
var meshFilter = go.AddComponent<MeshFilter>();
meshFilter.mesh = mesh;
yield return null;
}

private IEnumerator LoadTexture(GameObject go, string imagePath)
{
var texture = new Texture2D(4, 4, TextureFormat.DXT1, false);
var url = "file://" + imagePath;
using (var www = new WWW(url))
{
yield return www;
www.LoadImageIntoTexture(texture);
}
var meshRenderer = go.AddComponent<MeshRenderer>();
meshRenderer.material.mainTexture = texture;
}

}
}
63 changes: 63 additions & 0 deletions OWML.Assets/OWML.Assets.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A62856BD-D06C-4F2C-86E8-91C6FDF8F8D5}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>OWML.Assets</RootNamespace>
<AssemblyName>OWML.Assets</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Outer Wilds\OuterWilds_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Outer Wilds\OuterWilds_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.PhysicsModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Outer Wilds\OuterWilds_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestWWWModule">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Outer Wilds\OuterWilds_Data\Managed\UnityEngine.UnityWebRequestWWWModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Mod3DObject.cs" />
<Compile Include="ModAssets.cs" />
<Compile Include="ObjImporter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OWML.Common\OWML.Common.csproj">
<Project>{3C00626F-B688-4F32-B493-5B7EC1C879A0}</Project>
<Name>OWML.Common</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
253 changes: 253 additions & 0 deletions OWML.Assets/ObjImporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
using UnityEngine;
using System.Collections.Generic;
using System.IO;

namespace OWML.Assets
{
// Source: https://wiki.unity3d.com/index.php?title=ObjImporter&oldid=13033

/* This version of ObjImporter first reads through the entire file, getting a count of how large
* the final arrays will be, and then uses standard arrays for everything (as opposed to ArrayLists
* or any other fancy things).
*/

public class ObjImporter
{

private struct meshStruct
{
public Vector3[] vertices;
public Vector3[] normals;
public Vector2[] uv;
public Vector2[] uv1;
public Vector2[] uv2;
public int[] triangles;
public int[] faceVerts;
public int[] faceUVs;
public Vector3[] faceData;
public string name;
public string fileName;
}

// Use this for initialization
public Mesh ImportFile(string filePath)
{
meshStruct newMesh = createMeshStruct(filePath);
populateMeshStruct(ref newMesh);

Vector3[] newVerts = new Vector3[newMesh.faceData.Length];
Vector2[] newUVs = new Vector2[newMesh.faceData.Length];
Vector3[] newNormals = new Vector3[newMesh.faceData.Length];
int i = 0;
/* The following foreach loops through the facedata and assigns the appropriate vertex, uv, or normal
* for the appropriate Unity mesh array.
*/
foreach (Vector3 v in newMesh.faceData)
{
newVerts[i] = newMesh.vertices[(int)v.x - 1];
if (v.y >= 1)
newUVs[i] = newMesh.uv[(int)v.y - 1];

if (v.z >= 1)
newNormals[i] = newMesh.normals[(int)v.z - 1];
i++;
}

Mesh mesh = new Mesh();

mesh.vertices = newVerts;
mesh.uv = newUVs;
mesh.normals = newNormals;
mesh.triangles = newMesh.triangles;

mesh.RecalculateBounds();
//mesh.Optimize();

return mesh;
}

private static meshStruct createMeshStruct(string filename)
{
int triangles = 0;
int vertices = 0;
int vt = 0;
int vn = 0;
int face = 0;
meshStruct mesh = new meshStruct();
mesh.fileName = filename;
StreamReader stream = File.OpenText(filename);
string entireText = stream.ReadToEnd();
stream.Close();
using (StringReader reader = new StringReader(entireText))
{
string currentText = reader.ReadLine();
char[] splitIdentifier = { ' ' };
string[] brokenString;
while (currentText != null)
{
if (!currentText.StartsWith("f ") && !currentText.StartsWith("v ") && !currentText.StartsWith("vt ")
&& !currentText.StartsWith("vn "))
{
currentText = reader.ReadLine();
if (currentText != null)
{
currentText = currentText.Replace(" ", " ");
}
}
else
{
currentText = currentText.Trim(); //Trim the current line
brokenString = currentText.Split(splitIdentifier, 50); //Split the line into an array, separating the original line by blank spaces
switch (brokenString[0])
{
case "v":
vertices++;
break;
case "vt":
vt++;
break;
case "vn":
vn++;
break;
case "f":
face = face + brokenString.Length - 1;
triangles = triangles + 3 * (brokenString.Length - 2); /*brokenString.Length is 3 or greater since a face must have at least
3 vertices. For each additional vertice, there is an additional
triangle in the mesh (hence this formula).*/
break;
}
currentText = reader.ReadLine();
if (currentText != null)
{
currentText = currentText.Replace(" ", " ");
}
}
}
}
mesh.triangles = new int[triangles];
mesh.vertices = new Vector3[vertices];
mesh.uv = new Vector2[vt];
mesh.normals = new Vector3[vn];
mesh.faceData = new Vector3[face];
return mesh;
}

private static void populateMeshStruct(ref meshStruct mesh)
{
StreamReader stream = File.OpenText(mesh.fileName);
string entireText = stream.ReadToEnd();
stream.Close();
using (StringReader reader = new StringReader(entireText))
{
string currentText = reader.ReadLine();

char[] splitIdentifier = { ' ' };
char[] splitIdentifier2 = { '/' };
string[] brokenString;
string[] brokenBrokenString;
int f = 0;
int f2 = 0;
int v = 0;
int vn = 0;
int vt = 0;
int vt1 = 0;
int vt2 = 0;
while (currentText != null)
{
if (!currentText.StartsWith("f ") && !currentText.StartsWith("v ") && !currentText.StartsWith("vt ") &&
!currentText.StartsWith("vn ") && !currentText.StartsWith("g ") && !currentText.StartsWith("usemtl ") &&
!currentText.StartsWith("mtllib ") && !currentText.StartsWith("vt1 ") && !currentText.StartsWith("vt2 ") &&
!currentText.StartsWith("vc ") && !currentText.StartsWith("usemap "))
{
currentText = reader.ReadLine();
if (currentText != null)
{
currentText = currentText.Replace(" ", " ");
}
}
else
{
currentText = currentText.Trim();
brokenString = currentText.Split(splitIdentifier, 50);
switch (brokenString[0])
{
case "g":
break;
case "usemtl":
break;
case "usemap":
break;
case "mtllib":
break;
case "v":
mesh.vertices[v] = new Vector3(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]),
System.Convert.ToSingle(brokenString[3]));
v++;
break;
case "vt":
mesh.uv[vt] = new Vector2(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]));
vt++;
break;
case "vt1":
mesh.uv[vt1] = new Vector2(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]));
vt1++;
break;
case "vt2":
mesh.uv[vt2] = new Vector2(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]));
vt2++;
break;
case "vn":
mesh.normals[vn] = new Vector3(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]),
System.Convert.ToSingle(brokenString[3]));
vn++;
break;
case "vc":
break;
case "f":

int j = 1;
List<int> intArray = new List<int>();
while (j < brokenString.Length && ("" + brokenString[j]).Length > 0)
{
Vector3 temp = new Vector3();
brokenBrokenString = brokenString[j].Split(splitIdentifier2, 3); //Separate the face into individual components (vert, uv, normal)
temp.x = System.Convert.ToInt32(brokenBrokenString[0]);
if (brokenBrokenString.Length > 1) //Some .obj files skip UV and normal
{
if (brokenBrokenString[1] != "") //Some .obj files skip the uv and not the normal
{
temp.y = System.Convert.ToInt32(brokenBrokenString[1]);
}
temp.z = System.Convert.ToInt32(brokenBrokenString[2]);
}
j++;

mesh.faceData[f2] = temp;
intArray.Add(f2);
f2++;
}
j = 1;
while (j + 2 < brokenString.Length) //Create triangles out of the face data. There will generally be more than 1 triangle per face.
{
mesh.triangles[f] = intArray[0];
f++;
mesh.triangles[f] = intArray[j];
f++;
mesh.triangles[f] = intArray[j + 1];
f++;

j++;
}
break;
}
currentText = reader.ReadLine();
if (currentText != null)
{
currentText = currentText.Replace(" ", " "); //Some .obj files insert double spaces, this removes them.
}
}
}
}
}
}
}
Loading

0 comments on commit 8facd84

Please sign in to comment.