Skip to content

Commit 8facd84

Browse files
authored
Import model (#18)
* Asset mod helper for creating custom 3D objects * New sample mod which spawns rubber ducks
1 parent 24690b7 commit 8facd84

File tree

24 files changed

+664
-9
lines changed

24 files changed

+664
-9
lines changed

OWML.Assets/Mod3DObject.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using UnityEngine;
2+
3+
namespace OWML.Assets
4+
{
5+
public class Mod3DObject : MonoBehaviour
6+
{
7+
private void Start()
8+
{
9+
DontDestroyOnLoad(gameObject);
10+
}
11+
12+
}
13+
}

OWML.Assets/ModAssets.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System.Collections;
2+
using OWML.Common;
3+
using UnityEngine;
4+
5+
namespace OWML.Assets
6+
{
7+
public class ModAssets : IModAssets
8+
{
9+
private readonly IModConsole _console;
10+
private readonly ObjImporter _objImporter;
11+
12+
public ModAssets(IModConsole console)
13+
{
14+
_console = console;
15+
_objImporter = new ObjImporter();
16+
}
17+
18+
public GameObject Create3DObject(ModBehaviour modBehaviour, string objectFilename, string imageFilename)
19+
{
20+
var objectPath = modBehaviour.ModManifest.FolderPath + objectFilename;
21+
var imagePath = modBehaviour.ModManifest.FolderPath + imageFilename;
22+
23+
_console.WriteLine("Creating object from " + objectPath);
24+
25+
var go = new GameObject();
26+
go.AddComponent<Mod3DObject>();
27+
28+
var loadObject = LoadObject(go, objectPath);
29+
modBehaviour.StartCoroutine(loadObject);
30+
31+
var loadTexture = LoadTexture(go, imagePath);
32+
modBehaviour.StartCoroutine(loadTexture);
33+
34+
return go;
35+
}
36+
37+
private IEnumerator LoadObject(GameObject go, string objectPath)
38+
{
39+
var mesh = _objImporter.ImportFile(objectPath);
40+
var meshFilter = go.AddComponent<MeshFilter>();
41+
meshFilter.mesh = mesh;
42+
yield return null;
43+
}
44+
45+
private IEnumerator LoadTexture(GameObject go, string imagePath)
46+
{
47+
var texture = new Texture2D(4, 4, TextureFormat.DXT1, false);
48+
var url = "file://" + imagePath;
49+
using (var www = new WWW(url))
50+
{
51+
yield return www;
52+
www.LoadImageIntoTexture(texture);
53+
}
54+
var meshRenderer = go.AddComponent<MeshRenderer>();
55+
meshRenderer.material.mainTexture = texture;
56+
}
57+
58+
}
59+
}

OWML.Assets/OWML.Assets.csproj

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<ProjectGuid>{A62856BD-D06C-4F2C-86E8-91C6FDF8F8D5}</ProjectGuid>
8+
<OutputType>Library</OutputType>
9+
<AppDesignerFolder>Properties</AppDesignerFolder>
10+
<RootNamespace>OWML.Assets</RootNamespace>
11+
<AssemblyName>OWML.Assets</AssemblyName>
12+
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
13+
<FileAlignment>512</FileAlignment>
14+
<Deterministic>true</Deterministic>
15+
</PropertyGroup>
16+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
17+
<DebugSymbols>true</DebugSymbols>
18+
<DebugType>full</DebugType>
19+
<Optimize>false</Optimize>
20+
<OutputPath>bin\Debug\</OutputPath>
21+
<DefineConstants>DEBUG;TRACE</DefineConstants>
22+
<ErrorReport>prompt</ErrorReport>
23+
<WarningLevel>4</WarningLevel>
24+
</PropertyGroup>
25+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
26+
<DebugType>pdbonly</DebugType>
27+
<Optimize>true</Optimize>
28+
<OutputPath>bin\Release\</OutputPath>
29+
<DefineConstants>TRACE</DefineConstants>
30+
<ErrorReport>prompt</ErrorReport>
31+
<WarningLevel>4</WarningLevel>
32+
</PropertyGroup>
33+
<ItemGroup>
34+
<Reference Include="Assembly-CSharp">
35+
<HintPath>..\..\..\..\..\..\Program Files (x86)\Outer Wilds\OuterWilds_Data\Managed\Assembly-CSharp.dll</HintPath>
36+
</Reference>
37+
<Reference Include="System.Core" />
38+
<Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
39+
<SpecificVersion>False</SpecificVersion>
40+
<HintPath>..\..\..\..\..\..\Program Files (x86)\Outer Wilds\OuterWilds_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
41+
</Reference>
42+
<Reference Include="UnityEngine.PhysicsModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
43+
<SpecificVersion>False</SpecificVersion>
44+
<HintPath>..\..\..\..\..\..\Program Files (x86)\Outer Wilds\OuterWilds_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
45+
</Reference>
46+
<Reference Include="UnityEngine.UnityWebRequestWWWModule">
47+
<HintPath>..\..\..\..\..\..\Program Files (x86)\Outer Wilds\OuterWilds_Data\Managed\UnityEngine.UnityWebRequestWWWModule.dll</HintPath>
48+
</Reference>
49+
</ItemGroup>
50+
<ItemGroup>
51+
<Compile Include="Mod3DObject.cs" />
52+
<Compile Include="ModAssets.cs" />
53+
<Compile Include="ObjImporter.cs" />
54+
<Compile Include="Properties\AssemblyInfo.cs" />
55+
</ItemGroup>
56+
<ItemGroup>
57+
<ProjectReference Include="..\OWML.Common\OWML.Common.csproj">
58+
<Project>{3C00626F-B688-4F32-B493-5B7EC1C879A0}</Project>
59+
<Name>OWML.Common</Name>
60+
</ProjectReference>
61+
</ItemGroup>
62+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
63+
</Project>

OWML.Assets/ObjImporter.cs

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
using UnityEngine;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
5+
namespace OWML.Assets
6+
{
7+
// Source: https://wiki.unity3d.com/index.php?title=ObjImporter&oldid=13033
8+
9+
/* This version of ObjImporter first reads through the entire file, getting a count of how large
10+
* the final arrays will be, and then uses standard arrays for everything (as opposed to ArrayLists
11+
* or any other fancy things).
12+
*/
13+
14+
public class ObjImporter
15+
{
16+
17+
private struct meshStruct
18+
{
19+
public Vector3[] vertices;
20+
public Vector3[] normals;
21+
public Vector2[] uv;
22+
public Vector2[] uv1;
23+
public Vector2[] uv2;
24+
public int[] triangles;
25+
public int[] faceVerts;
26+
public int[] faceUVs;
27+
public Vector3[] faceData;
28+
public string name;
29+
public string fileName;
30+
}
31+
32+
// Use this for initialization
33+
public Mesh ImportFile(string filePath)
34+
{
35+
meshStruct newMesh = createMeshStruct(filePath);
36+
populateMeshStruct(ref newMesh);
37+
38+
Vector3[] newVerts = new Vector3[newMesh.faceData.Length];
39+
Vector2[] newUVs = new Vector2[newMesh.faceData.Length];
40+
Vector3[] newNormals = new Vector3[newMesh.faceData.Length];
41+
int i = 0;
42+
/* The following foreach loops through the facedata and assigns the appropriate vertex, uv, or normal
43+
* for the appropriate Unity mesh array.
44+
*/
45+
foreach (Vector3 v in newMesh.faceData)
46+
{
47+
newVerts[i] = newMesh.vertices[(int)v.x - 1];
48+
if (v.y >= 1)
49+
newUVs[i] = newMesh.uv[(int)v.y - 1];
50+
51+
if (v.z >= 1)
52+
newNormals[i] = newMesh.normals[(int)v.z - 1];
53+
i++;
54+
}
55+
56+
Mesh mesh = new Mesh();
57+
58+
mesh.vertices = newVerts;
59+
mesh.uv = newUVs;
60+
mesh.normals = newNormals;
61+
mesh.triangles = newMesh.triangles;
62+
63+
mesh.RecalculateBounds();
64+
//mesh.Optimize();
65+
66+
return mesh;
67+
}
68+
69+
private static meshStruct createMeshStruct(string filename)
70+
{
71+
int triangles = 0;
72+
int vertices = 0;
73+
int vt = 0;
74+
int vn = 0;
75+
int face = 0;
76+
meshStruct mesh = new meshStruct();
77+
mesh.fileName = filename;
78+
StreamReader stream = File.OpenText(filename);
79+
string entireText = stream.ReadToEnd();
80+
stream.Close();
81+
using (StringReader reader = new StringReader(entireText))
82+
{
83+
string currentText = reader.ReadLine();
84+
char[] splitIdentifier = { ' ' };
85+
string[] brokenString;
86+
while (currentText != null)
87+
{
88+
if (!currentText.StartsWith("f ") && !currentText.StartsWith("v ") && !currentText.StartsWith("vt ")
89+
&& !currentText.StartsWith("vn "))
90+
{
91+
currentText = reader.ReadLine();
92+
if (currentText != null)
93+
{
94+
currentText = currentText.Replace(" ", " ");
95+
}
96+
}
97+
else
98+
{
99+
currentText = currentText.Trim(); //Trim the current line
100+
brokenString = currentText.Split(splitIdentifier, 50); //Split the line into an array, separating the original line by blank spaces
101+
switch (brokenString[0])
102+
{
103+
case "v":
104+
vertices++;
105+
break;
106+
case "vt":
107+
vt++;
108+
break;
109+
case "vn":
110+
vn++;
111+
break;
112+
case "f":
113+
face = face + brokenString.Length - 1;
114+
triangles = triangles + 3 * (brokenString.Length - 2); /*brokenString.Length is 3 or greater since a face must have at least
115+
3 vertices. For each additional vertice, there is an additional
116+
triangle in the mesh (hence this formula).*/
117+
break;
118+
}
119+
currentText = reader.ReadLine();
120+
if (currentText != null)
121+
{
122+
currentText = currentText.Replace(" ", " ");
123+
}
124+
}
125+
}
126+
}
127+
mesh.triangles = new int[triangles];
128+
mesh.vertices = new Vector3[vertices];
129+
mesh.uv = new Vector2[vt];
130+
mesh.normals = new Vector3[vn];
131+
mesh.faceData = new Vector3[face];
132+
return mesh;
133+
}
134+
135+
private static void populateMeshStruct(ref meshStruct mesh)
136+
{
137+
StreamReader stream = File.OpenText(mesh.fileName);
138+
string entireText = stream.ReadToEnd();
139+
stream.Close();
140+
using (StringReader reader = new StringReader(entireText))
141+
{
142+
string currentText = reader.ReadLine();
143+
144+
char[] splitIdentifier = { ' ' };
145+
char[] splitIdentifier2 = { '/' };
146+
string[] brokenString;
147+
string[] brokenBrokenString;
148+
int f = 0;
149+
int f2 = 0;
150+
int v = 0;
151+
int vn = 0;
152+
int vt = 0;
153+
int vt1 = 0;
154+
int vt2 = 0;
155+
while (currentText != null)
156+
{
157+
if (!currentText.StartsWith("f ") && !currentText.StartsWith("v ") && !currentText.StartsWith("vt ") &&
158+
!currentText.StartsWith("vn ") && !currentText.StartsWith("g ") && !currentText.StartsWith("usemtl ") &&
159+
!currentText.StartsWith("mtllib ") && !currentText.StartsWith("vt1 ") && !currentText.StartsWith("vt2 ") &&
160+
!currentText.StartsWith("vc ") && !currentText.StartsWith("usemap "))
161+
{
162+
currentText = reader.ReadLine();
163+
if (currentText != null)
164+
{
165+
currentText = currentText.Replace(" ", " ");
166+
}
167+
}
168+
else
169+
{
170+
currentText = currentText.Trim();
171+
brokenString = currentText.Split(splitIdentifier, 50);
172+
switch (brokenString[0])
173+
{
174+
case "g":
175+
break;
176+
case "usemtl":
177+
break;
178+
case "usemap":
179+
break;
180+
case "mtllib":
181+
break;
182+
case "v":
183+
mesh.vertices[v] = new Vector3(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]),
184+
System.Convert.ToSingle(brokenString[3]));
185+
v++;
186+
break;
187+
case "vt":
188+
mesh.uv[vt] = new Vector2(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]));
189+
vt++;
190+
break;
191+
case "vt1":
192+
mesh.uv[vt1] = new Vector2(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]));
193+
vt1++;
194+
break;
195+
case "vt2":
196+
mesh.uv[vt2] = new Vector2(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]));
197+
vt2++;
198+
break;
199+
case "vn":
200+
mesh.normals[vn] = new Vector3(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]),
201+
System.Convert.ToSingle(brokenString[3]));
202+
vn++;
203+
break;
204+
case "vc":
205+
break;
206+
case "f":
207+
208+
int j = 1;
209+
List<int> intArray = new List<int>();
210+
while (j < brokenString.Length && ("" + brokenString[j]).Length > 0)
211+
{
212+
Vector3 temp = new Vector3();
213+
brokenBrokenString = brokenString[j].Split(splitIdentifier2, 3); //Separate the face into individual components (vert, uv, normal)
214+
temp.x = System.Convert.ToInt32(brokenBrokenString[0]);
215+
if (brokenBrokenString.Length > 1) //Some .obj files skip UV and normal
216+
{
217+
if (brokenBrokenString[1] != "") //Some .obj files skip the uv and not the normal
218+
{
219+
temp.y = System.Convert.ToInt32(brokenBrokenString[1]);
220+
}
221+
temp.z = System.Convert.ToInt32(brokenBrokenString[2]);
222+
}
223+
j++;
224+
225+
mesh.faceData[f2] = temp;
226+
intArray.Add(f2);
227+
f2++;
228+
}
229+
j = 1;
230+
while (j + 2 < brokenString.Length) //Create triangles out of the face data. There will generally be more than 1 triangle per face.
231+
{
232+
mesh.triangles[f] = intArray[0];
233+
f++;
234+
mesh.triangles[f] = intArray[j];
235+
f++;
236+
mesh.triangles[f] = intArray[j + 1];
237+
f++;
238+
239+
j++;
240+
}
241+
break;
242+
}
243+
currentText = reader.ReadLine();
244+
if (currentText != null)
245+
{
246+
currentText = currentText.Replace(" ", " "); //Some .obj files insert double spaces, this removes them.
247+
}
248+
}
249+
}
250+
}
251+
}
252+
}
253+
}

0 commit comments

Comments
 (0)