├── LICENSE ├── README.md └── SourceTools ├── Materials.meta ├── Materials ├── Black.mat └── Black.mat.meta ├── Prefabs.meta ├── Prefabs ├── Decal.prefab ├── Decal.prefab.meta ├── Rope.prefab └── Rope.prefab.meta ├── Scripts.meta └── Scripts ├── Decals.meta ├── Decals ├── BSPTree.cs ├── BSPTree.cs.meta ├── Decal.cs ├── Decal.cs.meta ├── DecalBuilder.cs ├── DecalBuilder.cs.meta ├── DecalClipper.cs ├── DecalClipper.cs.meta ├── Editor.meta ├── Editor │ ├── DecalEditor.cs │ └── DecalEditor.cs.meta ├── Follower.cs └── Follower.cs.meta ├── Rope.meta ├── Rope ├── RopeMeshRenderer.cs ├── RopeMeshRenderer.cs.meta ├── RopeSim.cs └── RopeSim.cs.meta ├── SourceVMF.cs ├── SourceVMF.cs.meta ├── Utility.meta └── Utility ├── ThreadedJob.cs └── ThreadedJob.cs.meta /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Dalton "Naelstrof" Nell 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Source Tools 2 | 3 | TODO polish this with pictures and videos and stuff. 4 | 5 | Here's a video I made a while back: 6 | https://www.youtube.com/watch?v=0a26cHPS-7g 7 | 8 | ## How to use 9 | 10 | 1. Convert bsp into vmf with bspsrc. 11 | 2. Open vmf with hammer, export fbx/textures with zfbx. 12 | 3. Convert bsp with crafty into obj, in order to get the compiled bsp mesh. 13 | 4. Use vim macros to fix up material names in the .mtl 14 | 5. Open obj with blender in order to load textures properly, then export compiled map as fbx. 15 | 6. Set import scale to 0.01 (to match original import's size) 16 | 6. Convert and import entire hl2 decals folder (zfbx won't do it for you, and we do spawn decals!) 17 | 7. Import vmf as txt 18 | 8. Run SourceTools->LoadVMF 19 | 9. Place vmf text into vmf text slot 20 | 10. Place compiled bsp fbx from crafty into MapFBX slot 21 | 11. Set shader to "Standard" (should already be set) 22 | 12. Set Rope and Decal prefabs to corresponding prefabs from SourceTools/Prefabs folder. (should already be set) 23 | 13. Set Root model folder to where your model folder is located (so if your models folder is `Assets/maps/trainstation01/models/`, then you'd put `Assets/maps/trainstation01/`) 24 | 14. Press generate, done! 25 | -------------------------------------------------------------------------------- /SourceTools/Materials.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5ab93e991f71cd042ac3ed2bfc6c7bd9 3 | folderAsset: yes 4 | timeCreated: 1517528897 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /SourceTools/Materials/Black.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naelstrof/UnitySourceTools/2865a942d25f6571f67a846f7d5e7282d3cac41d/SourceTools/Materials/Black.mat -------------------------------------------------------------------------------- /SourceTools/Materials/Black.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0c64b68d1d180c54bb939f2f56326c78 3 | timeCreated: 1517528814 4 | licenseType: Free 5 | NativeFormatImporter: 6 | mainObjectFileID: 2100000 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /SourceTools/Prefabs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 057b0e443291748499cc376089480e76 3 | folderAsset: yes 4 | timeCreated: 1517528891 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /SourceTools/Prefabs/Decal.prefab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naelstrof/UnitySourceTools/2865a942d25f6571f67a846f7d5e7282d3cac41d/SourceTools/Prefabs/Decal.prefab -------------------------------------------------------------------------------- /SourceTools/Prefabs/Decal.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7cb274ee30d347a468f8f469083fe30c 3 | timeCreated: 1517514608 4 | licenseType: Free 5 | NativeFormatImporter: 6 | mainObjectFileID: 100100000 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /SourceTools/Prefabs/Rope.prefab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naelstrof/UnitySourceTools/2865a942d25f6571f67a846f7d5e7282d3cac41d/SourceTools/Prefabs/Rope.prefab -------------------------------------------------------------------------------- /SourceTools/Prefabs/Rope.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d0aee46be7601014897c0addf109c234 3 | timeCreated: 1517528883 4 | licenseType: Free 5 | NativeFormatImporter: 6 | mainObjectFileID: 100100000 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /SourceTools/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b78a80dc812e7524ba0b883d699d7cbe 3 | folderAsset: yes 4 | timeCreated: 1517528902 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c360155a4af0a4447acea63cf0fe8d11 3 | folderAsset: yes 4 | timeCreated: 1515618129 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/BSPTree.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | /// 6 | /// Recursively paritions a mesh's vertices to allow to more quickly 7 | /// narrow down the search for a nearest point on it's surface with respect to another 8 | /// point 9 | /// 10 | [ExecuteInEditMode] 11 | public class BSPTree : MonoBehaviour { 12 | 13 | public class Node 14 | { 15 | public Vector3 partitionPoint; 16 | public Vector3 partitionNormal; 17 | 18 | public Node positiveChild; 19 | public Node negativeChild; 20 | 21 | public int[] triangles; 22 | }; 23 | 24 | private int triangleCount; 25 | private int vertexCount; 26 | private Vector3[] vertices; 27 | private Vector3[] normals; 28 | private int[] tris; 29 | private Vector3[] triangleNormals; 30 | 31 | private Mesh mesh; 32 | 33 | private Node tree; 34 | 35 | void Awake() 36 | { 37 | MeshCollider mc = GetComponent(); 38 | if (!mc) { 39 | MeshFilter mf = GetComponent (); 40 | if (!mf) { 41 | throw new UnityException ("BSPTree can only be applied to things that have a mesh!"); 42 | } 43 | mesh = mf.sharedMesh; 44 | } else { 45 | mesh = mc.sharedMesh; 46 | } 47 | 48 | tris = mesh.triangles; 49 | vertices = mesh.vertices; 50 | normals = mesh.normals; 51 | 52 | vertexCount = mesh.vertices.Length; 53 | triangleCount = mesh.triangles.Length / 3; 54 | 55 | triangleNormals = new Vector3[triangleCount]; 56 | 57 | for (int i = 0; i < tris.Length; i += 3) 58 | { 59 | Vector3 normal = Vector3.Cross((vertices[tris[i + 1]] - vertices[tris[i]]).normalized, (vertices[tris[i + 2]] - vertices[tris[i]]).normalized).normalized; 60 | 61 | triangleNormals[i / 3] = normal; 62 | } 63 | BuildTriangleTree(); 64 | } 65 | 66 | void Start() 67 | { 68 | //BuildTriangleTree(); 69 | } 70 | 71 | /// 72 | /// Returns the closest point on the mesh with respect to Vector3 point to 73 | /// 74 | public Vector3 ClosestPointOn(Vector3 to, float radius) 75 | { 76 | to = transform.InverseTransformPoint(to); 77 | 78 | List triangles = new List(); 79 | 80 | FindClosestTriangles(tree, to, radius, triangles); 81 | 82 | Vector3 closest = ClosestPointOnTriangle(triangles.ToArray(), to); 83 | 84 | return transform.TransformPoint(closest); 85 | } 86 | 87 | public void FindClosestTriangles(Vector3 to, float radius, List triangles) { 88 | //to = transform.InverseTransformPoint(to); 89 | if (tree == null) { 90 | BuildTriangleTree (); 91 | } 92 | // TODO: Maybe just use a hash set initially instead of doing this dumb shit lmaoo. 93 | List tempTriangles = new List(); 94 | FindClosestTriangles (tree, to, radius, tempTriangles); 95 | HashSet noDupSet = new HashSet (); 96 | foreach (int tri in tempTriangles) { 97 | noDupSet.Add (tri); 98 | } 99 | foreach (int tri in noDupSet) { 100 | triangles.Add (tri); 101 | } 102 | tempTriangles.Clear(); 103 | } 104 | 105 | public void GetIndices( int triangleIndex, out int i1, out int i2, out int i3 ) { 106 | i1 = tris[triangleIndex]; 107 | i2 = tris[triangleIndex + 1]; 108 | i3 = tris[triangleIndex + 2]; 109 | } 110 | 111 | public void GetVertices( int triangleIndex, out Vector3 v1, out Vector3 v2, out Vector3 v3 ) { 112 | v1 = vertices[tris[triangleIndex]]; 113 | v2 = vertices[tris[triangleIndex + 1]]; 114 | v3 = vertices[tris[triangleIndex + 2]]; 115 | } 116 | 117 | public void GetNormals( int triangleIndex, out Vector3 n1, out Vector3 n2, out Vector3 n3 ) { 118 | n1 = normals[tris[triangleIndex]]; 119 | n2 = normals[tris[triangleIndex + 1]]; 120 | n3 = normals[tris[triangleIndex + 2]]; 121 | } 122 | 123 | void FindClosestTriangles(Node node, Vector3 to, float radius, List triangles) 124 | { 125 | if (node.triangles == null) 126 | { 127 | if (PointDistanceFromPlane(node.partitionPoint, node.partitionNormal, to) <= radius) 128 | { 129 | FindClosestTriangles(node.positiveChild, to, radius, triangles); 130 | FindClosestTriangles(node.negativeChild, to, radius, triangles); 131 | } 132 | else if (PointAbovePlane(node.partitionPoint, node.partitionNormal, to)) 133 | { 134 | FindClosestTriangles(node.positiveChild, to, radius, triangles); 135 | } 136 | else 137 | { 138 | FindClosestTriangles(node.negativeChild, to, radius, triangles); 139 | } 140 | } 141 | else 142 | { 143 | triangles.AddRange(node.triangles); 144 | } 145 | } 146 | 147 | Vector3 ClosestPointOnTriangle(int[] triangles, Vector3 to) 148 | { 149 | float shortestDistance = float.MaxValue; 150 | 151 | Vector3 shortestPoint = Vector3.zero; 152 | 153 | // Iterate through all triangles 154 | foreach (int triangle in triangles) 155 | { 156 | Vector3 p1 = vertices[tris[triangle]]; 157 | Vector3 p2 = vertices[tris[triangle + 1]]; 158 | Vector3 p3 = vertices[tris[triangle + 2]]; 159 | 160 | Vector3 nearest; 161 | 162 | ClosestPointOnTriangleToPoint(ref p1, ref p2, ref p3, ref to, out nearest); 163 | 164 | float distance = (to - nearest).sqrMagnitude; 165 | 166 | if (distance <= shortestDistance) 167 | { 168 | shortestDistance = distance; 169 | shortestPoint = nearest; 170 | } 171 | } 172 | 173 | return shortestPoint; 174 | } 175 | 176 | void BuildTriangleTree() 177 | { 178 | List rootTriangles = new List(); 179 | 180 | for (int i = 0; i < tris.Length; i += 3) 181 | { 182 | rootTriangles.Add(i); 183 | } 184 | 185 | tree = new Node(); 186 | 187 | RecursivePartition(rootTriangles, 0, tree); 188 | } 189 | 190 | void RecursivePartition(List triangles, int depth, Node parent) 191 | { 192 | Vector3 partitionPoint = Vector3.zero; 193 | 194 | Vector3 maxExtents = new Vector3(-float.MaxValue, -float.MaxValue, -float.MaxValue); 195 | Vector3 minExtents = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); 196 | 197 | foreach (int triangle in triangles) 198 | { 199 | partitionPoint += vertices[tris[triangle]] + vertices[tris[triangle + 1]] + vertices[tris[triangle + 2]]; 200 | 201 | minExtents.x = Mathf.Min(minExtents.x, vertices[tris[triangle]].x, vertices[tris[triangle + 1]].x, vertices[tris[triangle + 2]].x); 202 | minExtents.y = Mathf.Min(minExtents.y, vertices[tris[triangle]].y, vertices[tris[triangle + 1]].y, vertices[tris[triangle + 2]].y); 203 | minExtents.z = Mathf.Min(minExtents.z, vertices[tris[triangle]].z, vertices[tris[triangle + 1]].z, vertices[tris[triangle + 2]].z); 204 | 205 | maxExtents.x = Mathf.Max(maxExtents.x, vertices[tris[triangle]].x, vertices[tris[triangle + 1]].x, vertices[tris[triangle + 2]].x); 206 | maxExtents.y = Mathf.Max(maxExtents.y, vertices[tris[triangle]].y, vertices[tris[triangle + 1]].y, vertices[tris[triangle + 2]].y); 207 | maxExtents.z = Mathf.Max(maxExtents.z, vertices[tris[triangle]].z, vertices[tris[triangle + 1]].z, vertices[tris[triangle + 2]].z); 208 | } 209 | 210 | // Centroid of all vertices 211 | partitionPoint /= vertexCount; 212 | 213 | // Better idea? Center of bounding box 214 | //partitionPoint = minExtents + Math3d.SetVectorLength((maxExtents - minExtents), (maxExtents - minExtents).magnitude * 0.5f); 215 | partitionPoint = minExtents + (Vector3.Normalize(maxExtents - minExtents)*(maxExtents - minExtents).magnitude*0.5f); 216 | 217 | Vector3 extentsMagnitude = new Vector3(Mathf.Abs(maxExtents.x - minExtents.x), Mathf.Abs(maxExtents.y - minExtents.y), Mathf.Abs(maxExtents.z - minExtents.z)); 218 | 219 | Vector3 partitionNormal; 220 | 221 | if (extentsMagnitude.x >= extentsMagnitude.y && extentsMagnitude.x >= extentsMagnitude.z) 222 | partitionNormal = Vector3.right; 223 | else if (extentsMagnitude.y >= extentsMagnitude.x && extentsMagnitude.y >= extentsMagnitude.z) 224 | partitionNormal = Vector3.up; 225 | else 226 | partitionNormal = Vector3.forward; 227 | 228 | List positiveTriangles; 229 | List negativeTriangles; 230 | 231 | Split(triangles, partitionPoint, partitionNormal, out positiveTriangles, out negativeTriangles); 232 | 233 | parent.partitionNormal = partitionNormal; 234 | parent.partitionPoint = partitionPoint; 235 | 236 | Node posNode = new Node(); 237 | parent.positiveChild = posNode; 238 | 239 | Node negNode = new Node(); 240 | parent.negativeChild = negNode; 241 | 242 | if (positiveTriangles.Count < triangles.Count && positiveTriangles.Count > 3) 243 | { 244 | RecursivePartition(positiveTriangles, depth + 1, posNode); 245 | } 246 | else 247 | { 248 | posNode.triangles = positiveTriangles.ToArray(); 249 | 250 | // if (drawMeshTreeOnStart) 251 | //DrawTriangleSet(posNode.triangles, DebugDraw.RandomColor()); 252 | } 253 | 254 | if (negativeTriangles.Count < triangles.Count && negativeTriangles.Count > 3) 255 | { 256 | RecursivePartition(negativeTriangles, depth + 1, negNode); 257 | } 258 | else 259 | { 260 | negNode.triangles = negativeTriangles.ToArray(); 261 | 262 | // if (drawMeshTreeOnStart) 263 | //DrawTriangleSet(negNode.triangles, DebugDraw.RandomColor()); 264 | } 265 | 266 | } 267 | 268 | /// 269 | /// Splits a a set of input triangles by a partition plane into positive and negative sets, with triangles 270 | /// that are intersected by the partition plane being placed in both sets 271 | /// 272 | void Split(List triangles, Vector3 partitionPoint, Vector3 partitionNormal, out List positiveTriangles, out List negativeTriangles) 273 | { 274 | positiveTriangles = new List(); 275 | negativeTriangles = new List(); 276 | 277 | foreach (int triangle in triangles) 278 | { 279 | bool firstPointAbove = PointAbovePlane(partitionPoint, partitionNormal, vertices[tris[triangle]]); 280 | bool secondPointAbove = PointAbovePlane(partitionPoint, partitionNormal, vertices[tris[triangle + 1]]); 281 | bool thirdPointAbove = PointAbovePlane(partitionPoint, partitionNormal, vertices[tris[triangle + 2]]); 282 | 283 | if (firstPointAbove && secondPointAbove && thirdPointAbove) 284 | { 285 | positiveTriangles.Add(triangle); 286 | } 287 | else if (!firstPointAbove && !secondPointAbove && !thirdPointAbove) 288 | { 289 | negativeTriangles.Add(triangle); 290 | } 291 | else 292 | { 293 | positiveTriangles.Add(triangle); 294 | negativeTriangles.Add(triangle); 295 | } 296 | } 297 | } 298 | 299 | bool PointAbovePlane(Vector3 planeOrigin, Vector3 planeNormal, Vector3 point) 300 | { 301 | return Vector3.Dot(point - planeOrigin, planeNormal) >= 0; 302 | } 303 | 304 | float PointDistanceFromPlane(Vector3 planeOrigin, Vector3 planeNormal, Vector3 point) 305 | { 306 | return Mathf.Abs(Vector3.Dot((point - planeOrigin), planeNormal)); 307 | } 308 | 309 | /// 310 | /// Determines the closest point between a point and a triangle. 311 | /// Borrowed from RPGMesh class of the RPGController package for Unity, by fholm 312 | /// The code in this method is copyrighted by the SlimDX Group under the MIT license: 313 | /// 314 | /// Copyright (c) 2007-2010 SlimDX Group 315 | /// 316 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 317 | /// of this software and associated documentation files (the "Software"), to deal 318 | /// in the Software without restriction, including without limitation the rights 319 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 320 | /// copies of the Software, and to permit persons to whom the Software is 321 | /// furnished to do so, subject to the following conditions: 322 | /// 323 | /// The above copyright notice and this permission notice shall be included in 324 | /// all copies or substantial portions of the Software. 325 | /// 326 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 327 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 328 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 329 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 330 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 331 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 332 | /// THE SOFTWARE. 333 | /// 334 | /// 335 | /// The point to test. 336 | /// The first vertex to test. 337 | /// The second vertex to test. 338 | /// The third vertex to test. 339 | /// When the method completes, contains the closest point between the two objects. 340 | public static void ClosestPointOnTriangleToPoint(ref Vector3 vertex1, ref Vector3 vertex2, ref Vector3 vertex3, ref Vector3 point, out Vector3 result) 341 | { 342 | //Source: Real-Time Collision Detection by Christer Ericson 343 | //Reference: Page 136 344 | 345 | //Check if P in vertex region outside A 346 | Vector3 ab = vertex2 - vertex1; 347 | Vector3 ac = vertex3 - vertex1; 348 | Vector3 ap = point - vertex1; 349 | 350 | float d1 = Vector3.Dot(ab, ap); 351 | float d2 = Vector3.Dot(ac, ap); 352 | if (d1 <= 0.0f && d2 <= 0.0f) 353 | { 354 | result = vertex1; //Barycentric coordinates (1,0,0) 355 | return; 356 | } 357 | 358 | //Check if P in vertex region outside B 359 | Vector3 bp = point - vertex2; 360 | float d3 = Vector3.Dot(ab, bp); 361 | float d4 = Vector3.Dot(ac, bp); 362 | if (d3 >= 0.0f && d4 <= d3) 363 | { 364 | result = vertex2; // barycentric coordinates (0,1,0) 365 | return; 366 | } 367 | 368 | //Check if P in edge region of AB, if so return projection of P onto AB 369 | float vc = d1 * d4 - d3 * d2; 370 | if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) 371 | { 372 | float v = d1 / (d1 - d3); 373 | result = vertex1 + v * ab; //Barycentric coordinates (1-v,v,0) 374 | return; 375 | } 376 | 377 | //Check if P in vertex region outside C 378 | Vector3 cp = point - vertex3; 379 | float d5 = Vector3.Dot(ab, cp); 380 | float d6 = Vector3.Dot(ac, cp); 381 | if (d6 >= 0.0f && d5 <= d6) 382 | { 383 | result = vertex3; //Barycentric coordinates (0,0,1) 384 | return; 385 | } 386 | 387 | //Check if P in edge region of AC, if so return projection of P onto AC 388 | float vb = d5 * d2 - d1 * d6; 389 | if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) 390 | { 391 | float w = d2 / (d2 - d6); 392 | result = vertex1 + w * ac; //Barycentric coordinates (1-w,0,w) 393 | return; 394 | } 395 | 396 | //Check if P in edge region of BC, if so return projection of P onto BC 397 | float va = d3 * d6 - d5 * d4; 398 | if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) 399 | { 400 | float w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); 401 | result = vertex2 + w * (vertex3 - vertex2); //Barycentric coordinates (0,1-w,w) 402 | return; 403 | } 404 | 405 | //P inside face region. Compute Q through its barycentric coordinates (u,v,w) 406 | float denom = 1.0f / (va + vb + vc); 407 | float v2 = vb * denom; 408 | float w2 = vc * denom; 409 | result = vertex1 + ab * v2 + ac * w2; //= u*vertex1 + v*vertex2 + w*vertex3, u = va * denom = 1.0f - v - w 410 | } 411 | 412 | void DrawTriangleSet(int[] triangles, Color color) 413 | { 414 | foreach (int triangle in triangles) 415 | { 416 | //DebugDraw.DrawTriangle(vertices[tris[triangle]], vertices[tris[triangle + 1]], vertices[tris[triangle + 2]], color, transform); 417 | } 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/BSPTree.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9e815f3a3f0a832439e3913e04c6369f 3 | timeCreated: 1515618129 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/Decal.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using System.Threading; 5 | 6 | [RequireComponent( typeof(MeshFilter) )] 7 | [RequireComponent( typeof(MeshRenderer) )] 8 | [ExecuteInEditMode] 9 | public class Decal : MonoBehaviour { 10 | private Material decal; 11 | public bool deleteIfEmpty = false; 12 | public float offset = 0.005f; 13 | public bool randomRotateOnSpawn = false; 14 | public LayerMask layerMask; 15 | public List affectedObjects; 16 | private List newVerts = new List (); 17 | private List newNorms = new List (); 18 | private List newUV = new List(); 19 | private List newTri = new List (); 20 | [HideInInspector] 21 | public List subDecals = new List (); 22 | private List jobs = new List(); 23 | private bool buildingMesh = false; 24 | 25 | void Start() { 26 | // Randomly rotate the decal around its up axis. 27 | if (randomRotateOnSpawn) { 28 | transform.RotateAround (transform.position, transform.up, Random.Range (0f, 360f)); 29 | } 30 | // Set the texture of our decal. 31 | decal = GetComponent ().sharedMaterial; 32 | BuildDecal (); 33 | // Make sure we don't re-build the mesh by saying our transform hasn't changed. 34 | transform.hasChanged = false; 35 | } 36 | 37 | // This shows a pretty little box when we're in the editor. 38 | void OnDrawGizmosSelected() { 39 | Gizmos.matrix = transform.localToWorldMatrix; 40 | Gizmos.DrawWireCube( Vector3.zero, Vector3.one ); 41 | } 42 | 43 | public void Update() { 44 | // Check if our transform has changed while editing, and rebuld the mesh if so. 45 | // This doesn't run if we're playing since the decal would be rebuilt whenever 46 | // its parent moves, and we don't need that. 47 | if (transform.hasChanged && !Application.isPlaying && Application.isEditor ) { 48 | BuildDecal (); 49 | transform.hasChanged = false; 50 | } 51 | for (int i = 0; i < jobs.Count; i++) { 52 | if (jobs[i].Update()) { 53 | jobs.RemoveAt (i); 54 | i--; 55 | } 56 | } 57 | if (jobs.Count <= 0 && buildingMesh) { 58 | buildingMesh = false; 59 | Mesh newMesh = new Mesh (); 60 | newMesh.name = "DecalMesh"; 61 | GetComponent ().mesh = newMesh; 62 | newMesh.vertices = newVerts.ToArray (); 63 | newMesh.uv = newUV.ToArray (); 64 | newMesh.normals = newNorms.ToArray (); 65 | newMesh.triangles = newTri.ToArray(); 66 | if (deleteIfEmpty && !Application.isPlaying && newTri.Count == 0) { 67 | DestroyImmediate (gameObject); 68 | } else { 69 | deleteIfEmpty = false; 70 | } 71 | } 72 | } 73 | 74 | private MeshFilter InstanciateDecalForMovable( GameObject obj, GameObject target ) { 75 | if (target == null) { 76 | Debug.LogError ("We shouldn't ever get a null target for moving decals..."); 77 | } 78 | GameObject subDecal = new GameObject("SubDecalMesh"); 79 | subDecal.transform.position = transform.position; 80 | subDecal.transform.rotation = transform.rotation; 81 | subDecal.transform.localScale = transform.localScale; 82 | Follower subMeshFollower = subDecal.AddComponent (); 83 | subMeshFollower.target = target.transform; 84 | MeshFilter subMeshFilter = subDecal.AddComponent (); 85 | MeshRenderer subRenderer = subDecal.AddComponent (); 86 | subRenderer.material = decal; 87 | subDecals.Add (subDecal); 88 | return subMeshFilter; 89 | } 90 | 91 | void OnDestroy() { 92 | foreach (GameObject obj in subDecals) { 93 | Destroy (obj); 94 | } 95 | subDecals.Clear (); 96 | } 97 | 98 | // Radius 99 | private float GetScale(GameObject obj ) { 100 | Vector3 scale = transform.lossyScale; 101 | Vector3 otherScale = obj.transform.lossyScale; 102 | float maxScale = Mathf.Max(Mathf.Max(scale.x,scale.y),scale.z)/2f; 103 | float minScale = Mathf.Min(Mathf.Min(otherScale.x,otherScale.y),otherScale.z); 104 | return maxScale / minScale; 105 | } 106 | 107 | // Try our best to determine how to apply our mesh. 108 | public void BuildDecal() { 109 | buildingMesh = true; 110 | // Clear whatever mesh we might have generated already. 111 | StartBuildMesh (); 112 | // Separate our affected objects into moving and static objects. 113 | affectedObjects = GetAffectedObjects (); 114 | List movingObjects = new List (); 115 | List staticObjects = new List (); 116 | foreach (GameObject obj in affectedObjects) { 117 | if (obj.GetComponentInParent () != null ) { 118 | movingObjects.Add (obj); 119 | } else { 120 | staticObjects.Add (obj); 121 | } 122 | } 123 | foreach (GameObject obj in movingObjects) { 124 | GameObject subDecalTarget = new GameObject("SubDecalTarget"); 125 | subDecalTarget.transform.position = transform.position; 126 | subDecalTarget.transform.rotation = transform.rotation; 127 | subDecalTarget.transform.SetParent (obj.transform); 128 | subDecals.Add (subDecalTarget); 129 | 130 | BSPTree affectedMesh = obj.GetComponent (); 131 | DecalBuilder builder = new DecalBuilder (); 132 | builder.mat = transform.worldToLocalMatrix * obj.transform.localToWorldMatrix; 133 | builder.position = affectedMesh.transform.InverseTransformPoint(transform.position); 134 | builder.rotation = GetRotation (builder.mat); 135 | builder.scale = GetScale (obj); 136 | builder.tree = affectedMesh; 137 | builder.offset = offset*Random.Range(0.1f,1f); 138 | builder.decal = this; 139 | builder.target = obj; 140 | builder.isStatic = false; 141 | builder.targetPos = subDecalTarget; 142 | builder.Start (); 143 | jobs.Add (builder); 144 | } 145 | // Try building a mesh for each static object. 146 | foreach (GameObject obj in staticObjects) { 147 | BSPTree affectedMesh = obj.GetComponent (); 148 | DecalBuilder builder = new DecalBuilder (); 149 | builder.mat = transform.worldToLocalMatrix * obj.transform.localToWorldMatrix; 150 | builder.position = affectedMesh.transform.InverseTransformPoint(transform.position); 151 | builder.rotation = GetRotation (builder.mat); 152 | builder.scale = GetScale (obj); 153 | builder.tree = affectedMesh; 154 | builder.offset = offset*Random.Range(0.1f,1f); 155 | builder.decal = this; 156 | builder.target = obj; 157 | builder.isStatic = true; 158 | builder.Start (); 159 | jobs.Add (builder); 160 | } 161 | } 162 | 163 | public Quaternion GetRotation(Matrix4x4 matrix) { 164 | return Quaternion.LookRotation(matrix.GetColumn(2), matrix.GetColumn(1)); 165 | } 166 | 167 | private void StartBuildMesh() { 168 | foreach (GameObject obj in subDecals) { 169 | if (obj == null) { 170 | continue; 171 | } 172 | for (int i = 0; i < obj.transform.childCount; i++) { 173 | if (obj.transform.GetChild (i) == null) { 174 | continue; 175 | } 176 | DestroyImmediate (obj.transform.GetChild (i).gameObject, true); 177 | } 178 | DestroyImmediate (obj, true); 179 | } 180 | foreach (DecalBuilder job in jobs) { 181 | job.Abort (); 182 | } 183 | jobs.Clear (); 184 | subDecals.Clear (); 185 | newVerts.Clear (); 186 | newUV.Clear (); 187 | newNorms.Clear (); 188 | newTri.Clear (); 189 | } 190 | 191 | public void FinishMesh( bool isStatic, GameObject obj, List verts, List norms, List uvs, LinkedList tris, GameObject target = null) { 192 | if (tris.Count <= 0) { 193 | return; 194 | } 195 | // For moving objects, we create a new object to parent to it. 196 | if (!isStatic) { 197 | MeshFilter mf = InstanciateDecalForMovable (obj, target); 198 | Mesh newMesh = new Mesh (); 199 | newMesh.name = "DecalMesh"; 200 | mf.mesh = newMesh; 201 | newMesh.vertices = verts.ToArray (); 202 | newMesh.normals = norms.ToArray (); 203 | newMesh.uv = uvs.ToArray (); 204 | int[] triangles = new int[tris.Count]; 205 | tris.CopyTo (triangles, 0); 206 | newMesh.triangles = triangles; 207 | return; 208 | } 209 | // For everything else, we collect all of the mesh data from our jobs, then finally collapse them into a singleton mesh on Update(); 210 | int offset = newVerts.Count; 211 | newVerts.AddRange (verts); 212 | newUV.AddRange (uvs); 213 | newNorms.AddRange (norms); 214 | int[] triangles1 = new int[tris.Count]; 215 | tris.CopyTo (triangles1, 0); 216 | for (int i = 0; i < triangles1.Length; i++) { 217 | triangles1 [i] += offset; 218 | } 219 | newTri.AddRange (triangles1); 220 | } 221 | 222 | // Use the physics engine to get nearby affected objects. 223 | private List GetAffectedObjects() { 224 | List objects = new List(); 225 | foreach( Collider col in Physics.OverlapBox(transform.position, transform.lossyScale/2f, transform.rotation, layerMask, QueryTriggerInteraction.Collide) ) { 226 | if (objects.Contains (col.gameObject)) continue; 227 | if ( col.gameObject.GetComponent() == null ) continue; 228 | // If we are trying to apply ourselves to another decal, ignore. 229 | if( col.gameObject.GetComponent() != null ) continue; 230 | // If we're a trigger, ignore. 231 | if( col.isTrigger ) continue; 232 | objects.Add(col.gameObject); 233 | } 234 | return objects; 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/Decal.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8e08498d2c918ef44bb5cb49b0a82ec8 3 | timeCreated: 1509911074 4 | licenseType: Free 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/DecalBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using System.Threading; 5 | using System.Diagnostics; 6 | 7 | public class DecalBuilder : ThreadedJob { 8 | // Indata 9 | public Matrix4x4 mat; 10 | public Vector3 position; 11 | public float scale; 12 | public BSPTree tree; 13 | public float offset; 14 | public Decal decal; 15 | public GameObject target; 16 | public Quaternion rotation; 17 | public bool isStatic; 18 | public GameObject targetPos = null; 19 | 20 | // Outdata 21 | private List verts = new List (); 22 | private List normals = new List(); 23 | private List uvs = new List(); 24 | private LinkedList tri = new LinkedList (); 25 | private Dictionary indexLookup;//= new Dictionary(); 26 | private void BuildMeshForObject() { 27 | List triangles = new List(); 28 | // Use a BSP tree to find nearby triangles at log(n) speeds. 29 | tree.FindClosestTriangles (position, scale, triangles); 30 | // Calculate the matrix needed to transform a point from the obj's local mesh, to our local mesh. 31 | // Matrix4x4 mat = transform.worldToLocalMatrix * obj.transform.localToWorldMatrix; 32 | // Clear the index lookup, we use it to check which verticies are shared. 33 | // This keeps us from having to rebuild the mesh so intricately. 34 | indexLookup = new Dictionary(triangles.Count); 35 | // TODO: split this up into more jobs. 36 | for (int i = 0; i < triangles.Count; i++) { 37 | // We use the indices of the original mesh's triangles to determine which 38 | // verticies are shared. So we don't have to calculate that ourselves. 39 | // we grab them here. 40 | int i1, i2, i3; 41 | tree.GetIndices (triangles [i], out i1, out i2, out i3); 42 | 43 | // Here we get the points of the obj's mesh in our local space. 44 | Vector3 v1, v2, v3; 45 | tree.GetVertices (triangles [i], out v1, out v2, out v3); 46 | v1 = mat.MultiplyPoint (v1); 47 | v2 = mat.MultiplyPoint (v2); 48 | v3 = mat.MultiplyPoint (v3); 49 | 50 | Vector3 n1, n2, n3; 51 | tree.GetNormals (triangles [i], out n1, out n2, out n3); 52 | 53 | // We do a quick normal calculation to see if the triangle is mostly facing us. 54 | // we have to recalculate it since the normal is different in our local space 55 | // (maybe we could just transform the original bsptree's precomputed normals with the matrix ?) 56 | Vector3 side1 = v2 - v1; 57 | Vector3 side2 = v3 - v1; 58 | Vector3 normal = Vector3.Cross(side1, side2).normalized; 59 | 60 | if (normal.y <= 0.2f) { 61 | continue; 62 | } 63 | 64 | // To prevent z-fighting, I randomly offset each vertex by the normal. 65 | v1 += normal * offset; 66 | v2 += normal * offset; 67 | v3 += normal * offset; 68 | 69 | // First we check to see if a vertex has already been grabbed and calculated. 70 | // If it has been, we just use that as the index for the triangle. 71 | // Otherwise we create a new vertex, with coorisponding UV mapping. 72 | // Since we're in a local space where the decal spans from -0.5f to 0.5f, 73 | // Our UV is just the x and z values in the space offset by 0.5f 74 | int ni1, ni2, ni3; 75 | if (!indexLookup.TryGetValue (i1, out ni1)) { 76 | verts.Add (v1); 77 | uvs.Add (new Vector2 (v1.x + 0.5f, v1.z + 0.5f)); 78 | normals.Add (rotation*n1); 79 | ni1 = verts.Count - 1; 80 | indexLookup [i1] = ni1; 81 | } 82 | if (!indexLookup.TryGetValue (i2, out ni2)) { 83 | verts.Add (v2); 84 | uvs.Add (new Vector2 (v2.x + 0.5f, v2.z + 0.5f)); 85 | normals.Add (rotation*n2); 86 | ni2 = verts.Count - 1; 87 | indexLookup [i2] = ni2; 88 | } 89 | if (!indexLookup.TryGetValue (i3, out ni3)) { 90 | verts.Add (v3); 91 | uvs.Add (new Vector2 (v3.x + 0.5f, v3.z + 0.5f)); 92 | normals.Add (rotation*n3); 93 | ni3 = verts.Count - 1; 94 | indexLookup [i3] = ni3; 95 | } 96 | 97 | // Finally we add the triangle to the triangle list. 98 | tri.AddLast (ni1); 99 | tri.AddLast (ni2); 100 | tri.AddLast (ni3); 101 | } 102 | } 103 | protected override void ThreadFunction() 104 | { 105 | BuildMeshForObject (); 106 | // TODO: Split this into more jobs. 107 | DecalClipper clipper = new DecalClipper (); 108 | clipper.newTri = tri; 109 | clipper.newUV = uvs; 110 | clipper.newVerts = verts; 111 | clipper.newNormals = normals; 112 | clipper.Start (); 113 | while (!clipper.IsDone) { 114 | Thread.Sleep (1); 115 | // we wait for our jobs to be done. 116 | } 117 | } 118 | protected override void OnFinished() 119 | { 120 | decal.FinishMesh (isStatic, target, verts, normals, uvs, tri, targetPos); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/DecalBuilder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9cd2158ab7eee1448adab7f244cd37ec 3 | timeCreated: 1510169087 4 | licenseType: Free 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/DecalClipper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using UnityEngine; 5 | 6 | public class DecalClipper : ThreadedJob { 7 | // Indata 8 | public List newVerts; 9 | public List newUV; 10 | public List newNormals; 11 | public LinkedList newTri; 12 | 13 | protected override void ThreadFunction() 14 | { 15 | ClipPlane( newVerts, newNormals, newUV, newTri, new Plane( Vector3.right, Vector3.right/2f )); 16 | ClipPlane( newVerts, newNormals, newUV, newTri, new Plane( -Vector3.right, -Vector3.right/2f )); 17 | ClipPlane( newVerts, newNormals, newUV, newTri, new Plane( Vector3.forward, Vector3.forward/2f )); 18 | ClipPlane( newVerts, newNormals, newUV, newTri, new Plane( -Vector3.forward, -Vector3.forward/2f )); 19 | ClipPlane( newVerts, newNormals, newUV, newTri, new Plane( Vector3.up, Vector3.up/2f )); 20 | ClipPlane( newVerts, newNormals, newUV, newTri, new Plane( -Vector3.up, -Vector3.up/2f )); 21 | } 22 | 23 | protected override void OnFinished() 24 | { 25 | } 26 | 27 | // This function takes a mesh, a triangle index, and a plane 28 | // Then it tries to clip the triangle by the plane, creating new 29 | // triangles if needed. It returns how many triangle indices have 30 | // been removed (and thus, if iterating, would use that value to offset the current index pointer.) 31 | private LinkedListNode ClipTriangle( List verts, List norms, List uvs, LinkedList tris, LinkedListNode triangle, Plane plane) { 32 | // Detect violating vertices. 33 | bool[] violating = new bool[3]; 34 | int violationCount = 0; 35 | int[] tids = new int[3]; 36 | tids[0] = triangle.Value; 37 | tids[1] = triangle.Next.Value; 38 | tids[2] = triangle.Next.Next.Value; 39 | LinkedListNode nextnode = triangle.Next.Next.Next; 40 | for (int i = 0; i < 3; i++) { 41 | violating[i] = plane.GetSide (verts[tids[i]]); 42 | if (violating[i]) { 43 | violationCount++; 44 | } 45 | } 46 | // I couldn't think of the general case of generating new triangles. 47 | // So i break the problem into each of its pieces. 48 | switch( violationCount ) { 49 | // If no vertices were outside the plane, we do nothing. 50 | case 0: 51 | return nextnode; 52 | // If one vertex is outside the plane.. 53 | case 1: 54 | // It was difficult for me to think of a general solution 55 | // So I first find which of the three vertices are the violating one. 56 | Vector3 v1 = Vector3.zero, v2 = Vector3.zero, v3 = Vector3.zero; 57 | Vector3 n1 = Vector3.zero, n2 = Vector3.zero, n3 = Vector3.zero; 58 | int i1 = -1, i2 = -1, i3 = -1; 59 | for (int i = 0; i < 3; i++) { 60 | // Once found, I record their indices, and vertex locations while keeping 61 | // their order intact. 62 | if (violating [i]) { 63 | i1 = tids [i]; 64 | i2 = tids [(i + 1) % 3]; 65 | i3 = tids [(i + 2) % 3]; 66 | v1 = verts [i1]; 67 | v2 = verts [i2]; 68 | v3 = verts [i3]; 69 | n1 = norms [i1]; 70 | n2 = norms [i2]; 71 | n3 = norms [i3]; 72 | break; 73 | } 74 | } 75 | // Create triangle number one 76 | Vector3 nv = LineCast (plane, v1, v2); 77 | verts.Add (nv); 78 | norms.Add (n1); 79 | uvs.Add (new Vector2 (nv.x + 0.5f, nv.z + 0.5f)); 80 | int i4 = verts.Count - 1; 81 | tris.AddLast (i4); 82 | tris.AddLast (i2); 83 | tris.AddLast (i3); 84 | 85 | // Create triangle number two 86 | tris.AddLast (i3); 87 | nv = LineCast (plane, v3, v1); 88 | verts.Add (nv); 89 | norms.Add (n1); 90 | uvs.Add (new Vector2 (nv.x + 0.5f, nv.z + 0.5f)); 91 | tris.AddLast (verts.Count - 1); 92 | tris.AddLast (i4); 93 | // Delete the old triangle. 94 | tris.Remove(triangle.Next.Next); 95 | tris.Remove(triangle.Next); 96 | tris.Remove(triangle); 97 | return nextnode; 98 | // If two vertices are outside the plane... 99 | case 2: 100 | for (int i = 0; i < 3; i++) { 101 | // If we're a vertex thats not outside the plane, we are going to be part of the new triangle. 102 | if (!violating [i]) { 103 | tris.AddLast (tids[i]); 104 | } 105 | // We use XOR to check if we cross the plane while going to the next vertex. 106 | if (violating [i] ^ violating [(i + 1) % 3]) { 107 | // If we did cross a plane, we create a new vertex and use that as part of our triangle. 108 | Vector3 v = LineCast (plane, verts [tids[i]], verts [tids[(i + 1) % 3]]); 109 | verts.Add (v); 110 | norms.Add (norms [tids[(i + 1) % 3]]); 111 | uvs.Add (new Vector2 (v.x + 0.5f, v.z + 0.5f)); 112 | tris.AddLast (verts.Count - 1); 113 | } 114 | } 115 | // Delete the old triangle. 116 | tris.Remove(triangle.Next.Next); 117 | tris.Remove(triangle.Next); 118 | tris.Remove(triangle); 119 | return nextnode; 120 | // If all of our vertices are outside the plane, we just delete the whole triangle and exit. 121 | case 3: 122 | // Delete the old triangle. 123 | tris.Remove(triangle.Next.Next); 124 | tris.Remove(triangle.Next); 125 | tris.Remove(triangle); 126 | return nextnode; 127 | } 128 | return nextnode; 129 | } 130 | // Clips the given mesh to fit entirely on one side of the plane. 131 | private void ClipPlane( List verts, List norms, List uvs, LinkedList tris, Plane plane ) { 132 | int triCount = tris.Count/3; 133 | int i = 0; 134 | LinkedListNode node = tris.First; 135 | while (i < triCount) { 136 | node = ClipTriangle (verts, norms, uvs, tris, node, plane); 137 | i++; 138 | } 139 | } 140 | // Find the point on a plane where a line intersects. 141 | private static Vector3 LineCast(Plane plane, Vector3 a, Vector3 b) { 142 | float dis; 143 | Ray ray = new Ray(a, b-a); 144 | plane.Raycast( ray, out dis ); 145 | return ray.GetPoint(dis); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/DecalClipper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1d551a7023b1ef44d8ddb5dcf2cf9b22 3 | timeCreated: 1510169553 4 | licenseType: Free 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ed361db64bcd01541bc9b0c3583c1b1a 3 | folderAsset: yes 4 | timeCreated: 1509918403 5 | licenseType: Free 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/Editor/DecalEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | 6 | [CustomEditor(typeof(Decal))] 7 | public class DecalEditor : Editor { 8 | public override void OnInspectorGUI() { 9 | Decal decal = (Decal)target; 10 | //decal.decal = AssetField("Material", decal.decal); 11 | decal.deleteIfEmpty = EditorGUILayout.Toggle("Destroy if empty", decal.deleteIfEmpty ); 12 | decal.offset = EditorGUILayout.FloatField("Offset", decal.offset); 13 | decal.randomRotateOnSpawn = EditorGUILayout.Toggle("Spawn with random rotation", decal.randomRotateOnSpawn); 14 | decal.layerMask = LayerMaskField("Affected Layers", decal.layerMask); 15 | EditorGUILayout.Separator(); 16 | if (GUI.changed) { 17 | decal.BuildDecal (); 18 | } 19 | } 20 | void OnSceneGUI() { 21 | Decal decal = (Decal)target; 22 | if (decal.transform.hasChanged) { 23 | foreach (GameObject obj in decal.subDecals) { 24 | if (obj == null) { 25 | continue; 26 | } 27 | for (int i = 0; i < obj.transform.childCount; i++) { 28 | if (obj.transform.GetChild (i) == null) { 29 | continue; 30 | } 31 | DestroyImmediate (obj.transform.GetChild (i).gameObject, true); 32 | } 33 | DestroyImmediate (obj, true); 34 | } 35 | decal.subDecals.Clear (); 36 | decal.BuildDecal (); 37 | decal.transform.hasChanged = false; 38 | } 39 | decal.Update (); 40 | } 41 | private static LayerMask LayerMaskField(string label, LayerMask mask) { 42 | List layers = new List(); 43 | for(int i=0; i<32; i++) { 44 | string name = LayerMask.LayerToName(i); 45 | if(name != "") layers.Add( name ); 46 | } 47 | return EditorGUILayout.MaskField( label, mask, layers.ToArray() ); 48 | } 49 | private static T AssetField(string label, T obj) where T : Object { 50 | return (T) EditorGUILayout.ObjectField(label, (T)obj, typeof(T), false); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/Editor/DecalEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d2fa18901573a7e4a91736169bdc7808 3 | timeCreated: 1509918410 4 | licenseType: Free 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/Follower.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class Follower : MonoBehaviour { 6 | public Transform target; 7 | void Update() { 8 | transform.position = target.position; 9 | transform.rotation = target.rotation; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Decals/Follower.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 86946f4668ab94f419294f3254519f0d 3 | timeCreated: 1515618190 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Rope.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 352117f2a57172945a52cf09f8cb6aab 3 | folderAsset: yes 4 | timeCreated: 1517528762 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Rope/RopeMeshRenderer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using System.Text.RegularExpressions; 5 | using System.IO; 6 | #if UNITY_EDITOR 7 | using UnityEditor; 8 | #endif 9 | 10 | [RequireComponent(typeof(RopeSim))] 11 | [RequireComponent(typeof(SkinnedMeshRenderer))] 12 | [ExecuteInEditMode] 13 | public class RopeMeshRenderer : MonoBehaviour { 14 | public float radius = 0.05f; 15 | private RopeSim ropesim; 16 | private Bounds biggestBounds; 17 | private float biggestBoundsVolume; 18 | private bool wroteBounds; 19 | void Start () { 20 | ropesim = GetComponent(); 21 | SkinnedMeshRenderer renderer = GetComponent(); 22 | if ( !ropesim.sane ) { 23 | if ( renderer.sharedMesh != null ) { 24 | renderer.sharedMesh.Clear(); 25 | renderer.sharedMesh = null; 26 | } 27 | return; 28 | } 29 | Mesh mesh = new Mesh(); 30 | int parts = (int)(Vector3.Distance(ropesim.start.position, ropesim.end.position)*ropesim.boneDensity); 31 | List verts = new List(); 32 | List uvs = new List(); 33 | List tris = new List(); 34 | List weights = new List(); 35 | float distMultiplier = Vector3.Distance(ropesim.start.position, ropesim.end.position)/(float)parts; 36 | 37 | // End cap 38 | BoneWeight capweight = new BoneWeight(); 39 | capweight.boneIndex0 = ropesim.bones.Count-1; 40 | capweight.weight0 = 1f; 41 | verts.Add( new Vector3(-radius, ((float)parts)*distMultiplier, -radius ) ); 42 | weights.Add( capweight ); 43 | uvs.Add( new Vector2( 0, 0 ) ); 44 | verts.Add( new Vector3(-radius, ((float)parts)*distMultiplier, radius ) ); 45 | weights.Add( capweight ); 46 | uvs.Add( new Vector2( 0, 1 ) ); 47 | verts.Add( new Vector3(radius, ((float)parts)*distMultiplier, radius ) ); 48 | weights.Add( capweight ); 49 | uvs.Add( new Vector2( 1, 1 ) ); 50 | 51 | verts.Add( new Vector3(-radius, ((float)parts)*distMultiplier, -radius ) ); 52 | weights.Add( capweight ); 53 | uvs.Add( new Vector2( 0, 0 ) ); 54 | verts.Add( new Vector3(radius, ((float)parts)*distMultiplier, radius ) ); 55 | weights.Add( capweight ); 56 | uvs.Add( new Vector2( 1, 1 ) ); 57 | verts.Add( new Vector3(radius, ((float)parts)*distMultiplier, -radius ) ); 58 | weights.Add( capweight ); 59 | uvs.Add( new Vector2( 1, 0 ) ); 60 | 61 | capweight.boneIndex0 = 0; 62 | capweight.weight0 = 1f; 63 | 64 | verts.Add( new Vector3(-radius, 0, -radius ) ); 65 | weights.Add( capweight ); 66 | uvs.Add( new Vector2( 0, 0 ) ); 67 | verts.Add( new Vector3(radius, 0, radius ) ); 68 | weights.Add( capweight ); 69 | uvs.Add( new Vector2( 1, 1 ) ); 70 | verts.Add( new Vector3(-radius, 0, radius ) ); 71 | weights.Add( capweight ); 72 | uvs.Add( new Vector2( 0, 1 ) ); 73 | 74 | verts.Add( new Vector3(-radius, 0, -radius ) ); 75 | weights.Add( capweight ); 76 | uvs.Add( new Vector2( 0, 0 ) ); 77 | verts.Add( new Vector3(radius, 0, -radius ) ); 78 | weights.Add( capweight ); 79 | uvs.Add( new Vector2( 1, 0 ) ); 80 | verts.Add( new Vector3(radius, 0, radius ) ); 81 | weights.Add( capweight ); 82 | uvs.Add( new Vector2( 1, 1 ) ); 83 | 84 | for( int i=0;i bindPoses = new List(); 203 | 204 | foreach( Transform bone in ropesim.bones ) { 205 | bindPoses.Add( bone.worldToLocalMatrix * transform.localToWorldMatrix ); 206 | } 207 | 208 | mesh.bindposes = bindPoses.ToArray(); 209 | 210 | renderer.bones = ropesim.bones.ToArray(); 211 | if ( renderer.sharedMesh != null ) { 212 | renderer.sharedMesh.Clear(); 213 | } 214 | renderer.sharedMesh = mesh; 215 | TextAsset t = AssetDatabase.LoadAssetAtPath("Assets/" + transform.parent.gameObject.name + ".txt") as TextAsset; 216 | if (t) { 217 | MatchCollection matches = Regex.Matches(t.text, @"-?\d+\.?\d*"); 218 | Vector3 center = new Vector3 (float.Parse (matches [0].Value), float.Parse (matches [1].Value), float.Parse (matches [2].Value)); 219 | Vector3 extents = new Vector3 (float.Parse (matches [3].Value), float.Parse (matches [4].Value), float.Parse (matches [5].Value)); 220 | renderer.localBounds = new Bounds (center, extents); 221 | AssetDatabase.DeleteAsset ("Assets/" + transform.parent.gameObject.name + ".txt"); 222 | } 223 | } 224 | 225 | void Update() { 226 | SkinnedMeshRenderer renderer = GetComponent(); 227 | #if UNITY_EDITOR 228 | if (!EditorApplication.isPlaying ) { 229 | #endif 230 | if ( renderer.sharedMesh == null || 231 | renderer.sharedMesh.triangles.Length <= 0 || 232 | ropesim.transform.hasChanged || 233 | renderer.bones[0] != ropesim.bones[0]) { 234 | Start(); 235 | return; 236 | } 237 | #if UNITY_EDITOR 238 | } else { 239 | #endif 240 | Vector3 max = ropesim.bones[0].localPosition; 241 | Vector3 min = ropesim.bones[0].localPosition; 242 | foreach( Transform bone in ropesim.bones ) { 243 | Vector3 pos = bone.localPosition; 244 | max.x = Mathf.Max(pos.x, max.x); 245 | max.y = Mathf.Max(pos.y, max.y); 246 | max.z = Mathf.Max(pos.z, max.z); 247 | 248 | min.x = Mathf.Min(pos.x, min.x); 249 | min.y = Mathf.Min(pos.y, min.y); 250 | min.z = Mathf.Min(pos.z, min.z); 251 | } 252 | max += new Vector3 (radius, radius, radius); 253 | min -= new Vector3 (radius, radius, radius); 254 | Vector3 center = min + (Vector3.Normalize (max - min) * Vector3.Distance(min,max)/2f); 255 | renderer.localBounds = new Bounds(center, max-min); 256 | float volume = (max - min).x*(max-min).y*(max-min).z; 257 | if (volume > biggestBoundsVolume) { 258 | biggestBounds = renderer.localBounds; 259 | biggestBoundsVolume = volume; 260 | } 261 | if (ropesim.recordAnimation && Time.time > ropesim.animationLength+ropesim.settleTime && !wroteBounds) { 262 | wroteBounds = true; 263 | StreamWriter writer = new StreamWriter("Assets/"+gameObject.transform.parent.gameObject.name+".txt", true); 264 | writer.WriteLine(biggestBounds.center + " " + biggestBounds.size); 265 | writer.Close(); 266 | } 267 | #if UNITY_EDITOR 268 | } 269 | #endif 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Rope/RopeMeshRenderer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 396189d9400de06429b6de52d8c98ced 3 | timeCreated: 1517528766 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Rope/RopeSim.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | #if UNITY_EDITOR 5 | using UnityEditor; 6 | #endif 7 | 8 | [ExecuteInEditMode] 9 | [RequireComponent(typeof(Animation))] 10 | public class RopeSim : MonoBehaviour { 11 | public Transform start; 12 | public Transform end; 13 | public float boneDensity = 1f; 14 | public float timeStep = 1 / 60f; 15 | private float timeStepTimer = 0f; 16 | [HideInInspector] 17 | public List bones = new List(); 18 | private List accel; 19 | private List vel; 20 | private List stuck; 21 | public bool sticky = false; 22 | [HideInInspector] 23 | public float distanceBetweenBones; 24 | public float strength = 100f; 25 | public bool staticStart = true; 26 | public bool staticEnd = true; 27 | public Vector3 gravity = new Vector3(0,-9.81f,0); 28 | public float damping = 0.05f; 29 | public float noise = 0.08f; 30 | public Vector3 windDirection = Vector3.forward; 31 | public float windAmount = 0.3f; 32 | public float maxVel = 100f; 33 | public float slack = -0.5f; 34 | public bool recordAnimation = false; 35 | public float settleTime = 4f; 36 | public float animationLength = 10f; 37 | public float animationFPS = 30f; 38 | public LayerMask collidesWith; 39 | private bool generated = false; 40 | public bool sane { 41 | get { return generated && !transform.hasChanged; } 42 | } 43 | private float stretchDistance; 44 | private float animationTimer; 45 | private bool savedAnimation = false; 46 | private AnimationClip clip; 47 | private List> cx; 48 | private List> cy; 49 | private List> cz; 50 | void Awake() { 51 | generated = false; 52 | if ( start == null || end == null || start.position == end.position ) { 53 | return; 54 | } 55 | if ( recordAnimation ) { 56 | animationTimer = 0f; 57 | savedAnimation = false; 58 | clip = new AnimationClip(); 59 | cx = new List>(); 60 | cy = new List>(); 61 | cz = new List>(); 62 | } 63 | transform.localScale = new Vector3( 1f, 1f, 1f ); 64 | transform.position = Vector3.zero; 65 | foreach( Transform bone in bones ) { 66 | if ( bone != null && bone.gameObject != null ) { 67 | DestroyImmediate( bone.gameObject, true ); 68 | } 69 | } 70 | List children = new List (); 71 | for ( int i=0;i(); 80 | vel = new List(); 81 | stuck = new List(); 82 | int parts = (int)(Vector3.Distance(start.position, end.position)*boneDensity); 83 | if (parts == 0) { 84 | return; 85 | } 86 | distanceBetweenBones = Vector3.Distance(start.position, end.position)/parts; 87 | for( int i=0;i<=parts;i++ ) { 88 | Transform bone = new GameObject("Bone" + i).transform; 89 | bone.parent = transform; 90 | bone.localRotation = Quaternion.identity; 91 | bone.localPosition = new Vector3( 0, distanceBetweenBones*i, 0 ); 92 | bones.Add( bone ); 93 | accel.Add( Vector3.zero ); 94 | vel.Add( Vector3.zero ); 95 | stuck.Add (false); 96 | if ( recordAnimation ) { 97 | cx.Add( new List() ); 98 | cy.Add( new List() ); 99 | cz.Add( new List() ); 100 | } 101 | } 102 | transform.position = start.position; 103 | transform.up = Vector3.Normalize(end.position-start.position); 104 | transform.hasChanged = false; 105 | start.hasChanged = false; 106 | end.hasChanged = false; 107 | distanceBetweenBones += distanceBetweenBones * slack; 108 | generated = true; 109 | if ( recordAnimation ) { 110 | AnimationClip clip = AssetDatabase.LoadAssetAtPath("Assets/" + transform.parent.gameObject.name + ".anim") as AnimationClip; 111 | if (clip) { 112 | recordAnimation = false; 113 | GetComponent ().clip = clip; 114 | } 115 | } 116 | } 117 | #if UNITY_EDITOR 118 | void OnDrawGizmosSelected() { 119 | if ( !sane ) { 120 | return; 121 | } 122 | for( int i=1;i 0f) { 175 | timeStepTimer += Time.deltaTime; 176 | return; 177 | } 178 | timeStepTimer -= timeStep; 179 | #if UNITY_EDITOR 180 | if ( !EditorApplication.isPlaying ) { 181 | if ( transform.hasChanged || start.hasChanged || end.hasChanged ) { 182 | generated = false; 183 | transform.hasChanged = true; 184 | Awake(); 185 | return; 186 | } 187 | if ( !generated ) { 188 | Awake(); 189 | return; 190 | } 191 | return; 192 | } 193 | #endif 194 | if (bones.Count <= 0) { 195 | return; 196 | } 197 | bones[0].position = start.position; 198 | bones[bones.Count-1].position = end.position; 199 | for( int i=0;i maxVel ) { 231 | vel[i] = Vector3.Normalize(vel[i])*maxVel; 232 | } 233 | } 234 | 235 | for (int i = 0; i < bones.Count; i++) { 236 | foreach (Collider col in Physics.OverlapSphere(bones[i].position,0.5f,collidesWith,QueryTriggerInteraction.Ignore)) { 237 | if (sticky) { 238 | stuck [i] = true; 239 | } 240 | if ( vel[i].y < 0 ) { 241 | vel[i] = new Vector3(vel[i].x, 0, vel[i].z); 242 | } 243 | break; 244 | } 245 | } 246 | 247 | for( int i=0;i 1f/animationFPS && Time.time > settleTime && Time.time < animationLength+settleTime ) { 252 | animationTimer = 0f; 253 | cx[i].Add(new Keyframe(Time.time, bones[i].localPosition.x)); 254 | cy[i].Add(new Keyframe(Time.time, bones[i].localPosition.y)); 255 | cz[i].Add(new Keyframe(Time.time, bones[i].localPosition.z)); 256 | } 257 | #endif 258 | if (stuck [i] && sticky) { 259 | continue; 260 | } 261 | if (i == 0 && staticStart || i == bones.Count-1 && staticEnd) { 262 | continue; 263 | } 264 | bones [i].position += vel [i] * timeStep; 265 | } 266 | start.position = bones [0].position; 267 | end.position = bones [bones.Count - 1].position; 268 | 269 | #if UNITY_EDITOR 270 | if ( recordAnimation && Time.time > animationLength+settleTime && !savedAnimation ) { 271 | savedAnimation = true; 272 | clip.legacy = true; 273 | for ( int i=0;i (); 284 | ani.clip = clip; 285 | } 286 | #endif 287 | transform.hasChanged = false; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Rope/RopeSim.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 68bf2b718cebbf345b0e2233ea70832a 3 | timeCreated: 1517528766 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /SourceTools/Scripts/SourceVMF.cs: -------------------------------------------------------------------------------- 1 | // README: 2 | // 1. Convert bsp to vmf with bspsrc 3 | // 2. Use HL2's hammer to open vmf, and compile it using zfbx (effectively extracting textures and models) 4 | // 3. Change .vmf to .txt, and drag into unity with compiled fbx 5 | // 4. Configure crafty (nem's tools), usually requires extracting .paks, and setting custom mount options. Make sure it can open bsp files properly. 6 | // 5. Export .obj of compiled bsp map with crafty (without the models). 7 | // 6. Use vim macros to fix the path names in the .mtl file. 8 | // 7. Use blender to open obj (make sure the textures load properly), then export as fbx. 9 | // 8. Import this new compiled bsp mesh into unity, set its import scale to 0.01 (to match the vmf mesh scale exported from zfbx) 10 | 11 | // At this point you have compiled all the required data for this script to function... not so bad, huh? :v 12 | 13 | // 9. Run from the menu bar Source Tools/LoadVMF 14 | // 10. Drag and drop the compiled bsp mesh fbx onto the MapFBX spot 15 | // 11. Drag and drop the .vmf (which should be a .txt file) into the VMF spot. 16 | // 12. Specify the folder that contains your "models" folder. Should start with "Assets" and end with a "/", an example is already put there. 17 | // 13. Press create! 18 | 19 | 20 | using System.Collections; 21 | using System.Collections.Generic; 22 | using UnityEngine; 23 | using UnityEditor; 24 | using System.Text.RegularExpressions; 25 | 26 | public class SourceVMF : ScriptableWizard { 27 | public TextAsset vmfText; 28 | public GameObject mapFBX; 29 | public Shader decalShader; 30 | public GameObject ropePrefab; 31 | public GameObject decalPrefab; 32 | private float sourceToUnityScale = 2.54f; // This is the scale that converts zfbx's output units to unity's units. 33 | public string modelParentFolder = "Assets/maps/d2_coast_03_d/"; 34 | 35 | private GameObject world; 36 | private List entities; 37 | [MenuItem("Source Tools/LoadVMF")] 38 | static void CreateWizard() { 39 | ScriptableWizard.DisplayWizard("Load VMF", "Create", "Cancel"); 40 | } 41 | void OnWizardUpdate() { 42 | helpString = "Feed me vmf"; 43 | } 44 | void Read() { 45 | entities = new List (); 46 | int depth = 0; 47 | string header = ""; 48 | foreach (string line in vmfText.text.Split('\n')) { 49 | if (line.Trim () == "{") { 50 | depth++; 51 | } else if ( depth == 0 ) { 52 | header = line.Trim (); 53 | entities.Add (new SourceEntity (header)); 54 | } 55 | if (line.Trim () == "}") { 56 | depth--; 57 | } 58 | if (depth != 1) { 59 | continue; 60 | } 61 | MatchCollection matches = Regex.Matches(line, "\\\"(.*?)\\\" \\\"(.*?)\\\""); 62 | if (matches.Count > 0) { 63 | entities [entities.Count - 1].SetKey (matches [0].Groups [1].Value, matches [0].Groups [2].Value); 64 | } 65 | } 66 | } 67 | void PlaceMap() { 68 | if (mapFBX == null) { 69 | throw new UnityException ("Didn't specify map."); 70 | } 71 | GameObject map = Instantiate (mapFBX, Vector3.zero, Quaternion.identity); 72 | if (map == null) { 73 | throw new UnityException ("Failed to make map."); 74 | } 75 | foreach (MeshFilter mf in map.GetComponentsInChildren()) { 76 | mf.gameObject.AddComponent ().sharedMesh = mf.sharedMesh; 77 | mf.gameObject.AddComponent (); 78 | } 79 | map.transform.localScale = new Vector3 (sourceToUnityScale, sourceToUnityScale, sourceToUnityScale); 80 | map.transform.eulerAngles = new Vector3 (-90f, 180f, 0f); 81 | map.transform.SetParent (world.transform); 82 | } 83 | T GetAsset( string path, string ext) where T : UnityEngine.Object { 84 | // separate the model name from the path. 85 | List paths = new List (path.Split ('/')); 86 | string texturename = paths [paths.Count - 1]; 87 | paths.Remove (texturename); 88 | 89 | string[] guids = AssetDatabase.FindAssets(texturename, new string[] {modelParentFolder + string.Join("/",paths.ToArray())}); 90 | if (guids.Length <= 0) { 91 | Debug.LogError("Couldn't find " + path); 92 | return default(T); 93 | } 94 | string assetPath = ""; 95 | for ( int i=0;i(assetPath) as T; 110 | } 111 | GameObject CreateModel( string path ) { 112 | GameObject mdl = GetAsset(path, "fbx"); 113 | if (mdl == null) { 114 | return null; 115 | } 116 | return PrefabUtility.InstantiatePrefab(mdl) as GameObject; 117 | } 118 | void PlaceStaticProps() { 119 | GameObject staticContainer = new GameObject (); 120 | staticContainer.name = "StaticEntities"; 121 | staticContainer.transform.SetParent (world.transform); 122 | foreach( SourceEntity ent in entities ) { 123 | if (ent.GetStringValue ("classname") != "prop_static") { 124 | continue; 125 | } 126 | string modelname = ent.GetStringValue ("model"); 127 | // malformed prop?? 128 | if (modelname == "") { 129 | continue; 130 | } 131 | // lop off the .mdl part 132 | modelname = modelname.Substring (0, modelname.Length - 4); 133 | GameObject model = CreateModel(modelname); 134 | // Failed to find model, it's ok though just skip it. 135 | if (model == null) { 136 | continue; 137 | } 138 | foreach (MeshFilter mf in model.GetComponentsInChildren()) { 139 | MeshCollider mc = mf.gameObject.AddComponent (); 140 | mc.sharedMesh = mf.sharedMesh; 141 | mf.gameObject.AddComponent (); 142 | } 143 | model.transform.position = SourceToUnityPosition(ent.GetVectorValue ("origin")); 144 | model.transform.eulerAngles = SourceToUnityRotation (ent.GetVectorValue ("angles")); 145 | //model.transform.RotateAround (model.transform.position, model.transform.up, 90f); 146 | model.transform.localScale = new Vector3 (sourceToUnityScale, sourceToUnityScale, sourceToUnityScale); 147 | model.transform.SetParent (staticContainer.transform); 148 | } 149 | } 150 | void PlaceDynamicProps() { 151 | GameObject staticContainer = new GameObject (); 152 | staticContainer.name = "DynamicEntities"; 153 | staticContainer.transform.SetParent (world.transform); 154 | foreach( SourceEntity ent in entities ) { 155 | if (ent.GetStringValue ("classname") != "prop_dynamic" && ent.GetStringValue ("classname") != "prop_physics") { 156 | continue; 157 | } 158 | string modelname = ent.GetStringValue ("model"); 159 | // malformed prop?? 160 | if (modelname == "") { 161 | continue; 162 | } 163 | // lop off the .mdl part 164 | modelname = modelname.Substring (0, modelname.Length - 4); 165 | GameObject model = CreateModel(modelname); 166 | // Failed to find model, it's ok though just skip it. 167 | if (model == null) { 168 | continue; 169 | } 170 | foreach (MeshFilter mf in model.GetComponentsInChildren()) { 171 | MeshCollider mc = mf.gameObject.AddComponent (); 172 | mc.sharedMesh = mf.sharedMesh; 173 | mc.convex = true; 174 | } 175 | Rigidbody rb = model.AddComponent (); 176 | rb.mass = model.GetComponentInChildren ().bounds.size.magnitude*10f; 177 | model.transform.position = SourceToUnityPosition(ent.GetVectorValue ("origin")); 178 | model.transform.eulerAngles = SourceToUnityRotation (ent.GetVectorValue ("angles")); 179 | model.transform.localScale = new Vector3 (sourceToUnityScale, sourceToUnityScale, sourceToUnityScale); 180 | model.transform.SetParent (staticContainer.transform); 181 | SpecialExceptions (model); 182 | } 183 | } 184 | void CreateDecal( Material mat, Texture tex, Vector3 pos, Vector3 facing, Transform parent ) { 185 | GameObject decal = Instantiate(decalPrefab); 186 | decal.name = tex.name; 187 | decal.GetComponentInChildren ().sharedMaterial = mat; 188 | decal.transform.localScale = new Vector3 ((float)tex.width * 0.0025f * sourceToUnityScale, 0.5f, (float)tex.height * 0.0025f * sourceToUnityScale); 189 | decal.transform.position = pos; 190 | decal.transform.rotation = Quaternion.LookRotation (facing, Vector3.up) * Quaternion.AngleAxis (-90f, Vector3.right); 191 | decal.transform.position -= decal.transform.up*0.22f; 192 | //decal.transform.eulerAngles = new Vector3 (decal.transform.eulerAngles.x, 0, decal.transform.eulerAngles.z); 193 | decal.transform.SetParent (parent); 194 | } 195 | void SpecialExceptions( GameObject obj ) { 196 | if (obj.name == "wood_fence01a" || obj.name == "wood_fence01b" || obj.name == "plasterwall029c_window01a_bars" || obj.name =="wood_fence01c" || obj.name == "plasterwall029g_window01a_bars" || obj.name == "woodwall030b_window02a_bars") { 197 | obj.transform.Rotate (Vector3.up, 90f); 198 | } 199 | } 200 | void PlaceDecals() { 201 | GameObject staticContainer = new GameObject (); 202 | staticContainer.name = "Decals"; 203 | staticContainer.transform.SetParent (world.transform); 204 | foreach( SourceEntity ent in entities ) { 205 | if (ent.GetStringValue ("classname") != "infodecal") { 206 | continue; 207 | } 208 | string texture = ent.GetStringValue ("texture"); 209 | // malformed decal?? 210 | if (texture == "") { 211 | continue; 212 | } 213 | Material mat = new Material (decalShader); 214 | Texture tex = GetAsset ("textures/"+texture, "tga"); 215 | // Failed to find texture for decal :/ 216 | if (tex == null) { 217 | continue; 218 | } 219 | mat.SetTexture ("_MainTex", tex); 220 | mat.SetFloat ("_Smoothness", 0); 221 | mat.SetFloat ("_Mode", 2); 222 | mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); 223 | mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); 224 | mat.SetInt("_ZWrite", 0); 225 | mat.DisableKeyword("_ALPHATEST_ON"); 226 | mat.EnableKeyword("_ALPHABLEND_ON"); 227 | mat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); 228 | mat.renderQueue = 3000; 229 | RaycastHit hit; 230 | Vector3 pos = SourceToUnityPosition (ent.GetVectorValue ("origin")); 231 | CreateDecal (mat, tex, pos, Vector3.up, staticContainer.transform); 232 | CreateDecal (mat, tex, pos, Vector3.down, staticContainer.transform); 233 | CreateDecal (mat, tex, pos, Vector3.left, staticContainer.transform); 234 | CreateDecal (mat, tex, pos, Vector3.right, staticContainer.transform); 235 | CreateDecal (mat, tex, pos, Vector3.back, staticContainer.transform); 236 | CreateDecal (mat, tex, pos, Vector3.forward, staticContainer.transform); 237 | } 238 | } 239 | void PlaceRopes() { 240 | GameObject staticContainer = new GameObject (); 241 | staticContainer.name = "Ropes"; 242 | staticContainer.transform.SetParent (world.transform); 243 | int ropenum = 1; 244 | foreach (SourceEntity ent in entities) { 245 | if (ent.GetStringValue ("classname") != "keyframe_rope" && ent.GetStringValue ("classname") != "move_rope") { 246 | continue; 247 | } 248 | SourceEntity next = GetSourceEntityByName (ent.GetStringValue ("NextKey")); 249 | if (next == null) { 250 | continue; 251 | } 252 | GameObject ropeRoot = Instantiate (ropePrefab); 253 | RopeSim rope = ropeRoot.GetComponentInChildren (); 254 | rope.start.position = SourceToUnityPosition(ent.GetVectorValue ("origin")); 255 | rope.end.position = SourceToUnityPosition(next.GetVectorValue ("origin")); 256 | ropeRoot.transform.SetParent (staticContainer.transform); 257 | ropeRoot.name = "Rope" + ropenum; 258 | ropenum++; 259 | } 260 | } 261 | void PlaceLights() { 262 | GameObject staticContainer = new GameObject (); 263 | staticContainer.name = "Lights"; 264 | staticContainer.transform.SetParent (world.transform); 265 | foreach (SourceEntity ent in entities) { 266 | if (ent.GetStringValue ("classname") == "point_spotlight") { 267 | GameObject spotlightroot = new GameObject (); 268 | spotlightroot.name = "PointSpotLight"; 269 | Light spotlight = spotlightroot.AddComponent (); 270 | spotlight.lightmapBakeType = LightmapBakeType.Baked; 271 | spotlight.type = LightType.Point; 272 | spotlight.range = float.Parse (ent.GetStringValue ("spotlightlength"))*0.01f*sourceToUnityScale; 273 | //spotlight.spotAngle = float.Parse (ent.GetStringValue ("spotlightwidth")); 274 | Vector3 col = ent.GetVectorValue ("rendercolor").normalized; 275 | spotlight.color = new Color (col.x, col.y, col.z); 276 | spotlightroot.transform.position = SourceToUnityPosition(ent.GetVectorValue ("origin")); 277 | //spotlightroot.transform.eulerAngles = SourceToUnityRotation (ent.GetVectorValue ("angles")); 278 | spotlightroot.transform.SetParent (staticContainer.transform); 279 | continue; 280 | } 281 | if (ent.GetStringValue ("classname") == "light_spot") { 282 | GameObject spotlightroot = new GameObject (); 283 | spotlightroot.name = "LightSpot"; 284 | Light spotlight = spotlightroot.AddComponent (); 285 | spotlight.lightmapBakeType = LightmapBakeType.Mixed; 286 | spotlight.type = LightType.Spot; 287 | spotlight.range = 10f; 288 | spotlight.spotAngle = float.Parse (ent.GetStringValue ("_cone")); 289 | Vector3 col = ent.GetVectorValue ("_light").normalized; 290 | spotlight.color = new Color (col.x, col.y, col.z); 291 | spotlightroot.transform.position = SourceToUnityPosition(ent.GetVectorValue ("origin")); 292 | Vector3 ang = SourceToUnityRotation (ent.GetVectorValue ("angles")); 293 | ang.x *= -1f; 294 | spotlightroot.transform.eulerAngles = ang; 295 | spotlightroot.transform.SetParent (staticContainer.transform); 296 | continue; 297 | } 298 | if (ent.GetStringValue ("classname") == "light") { 299 | GameObject spotlightroot = new GameObject (); 300 | spotlightroot.name = "Light"; 301 | Light spotlight = spotlightroot.AddComponent (); 302 | spotlight.lightmapBakeType = LightmapBakeType.Baked; 303 | spotlight.type = LightType.Point; 304 | spotlight.range = 10f; 305 | Vector3 col = ent.GetVectorValue ("_light").normalized; 306 | spotlight.color = new Color (col.x, col.y, col.z); 307 | spotlightroot.transform.position = SourceToUnityPosition (ent.GetVectorValue ("origin")); 308 | spotlightroot.transform.SetParent (staticContainer.transform); 309 | continue; 310 | } 311 | if (ent.GetStringValue ("classname") == "light_environment") { 312 | GameObject spotlightroot = new GameObject (); 313 | spotlightroot.name = "LightEnvironment"; 314 | Light spotlight = spotlightroot.AddComponent (); 315 | spotlight.lightmapBakeType = LightmapBakeType.Mixed; 316 | spotlight.type = LightType.Directional; 317 | Vector3 col = ent.GetVectorValue ("_light").normalized; 318 | spotlight.color = new Color (col.x, col.y, col.z); 319 | spotlightroot.transform.position = SourceToUnityPosition (ent.GetVectorValue ("origin")); 320 | Vector3 ang = SourceToUnityRotation (ent.GetVectorValue ("angles")); 321 | ang.x = -float.Parse (ent.GetStringValue ("pitch")); 322 | spotlightroot.transform.eulerAngles = ang; 323 | spotlightroot.transform.SetParent (staticContainer.transform); 324 | continue; 325 | } 326 | } 327 | } 328 | void Abort() { 329 | if (world != null) { 330 | DestroyImmediate (world); 331 | } 332 | } 333 | Vector3 SourceToUnityPosition(Vector3 source) { 334 | return (new Vector3 (source.x, source.z, source.y))*sourceToUnityScale*0.01f; 335 | } 336 | Vector3 SourceToUnityRotation( Vector3 source ) { 337 | return new Vector3 (source.x, -source.y+90f, -source.z); 338 | } 339 | void OnWizardCreate() { 340 | Read (); 341 | 342 | world = new GameObject (); 343 | world.name = "World"; 344 | world.transform.position = Vector3.zero; 345 | try { 346 | PlaceMap (); 347 | PlaceStaticProps(); 348 | PlaceDynamicProps(); 349 | PlaceDecals(); 350 | PlaceRopes(); 351 | PlaceLights(); 352 | } catch( UnityException e ) { 353 | Abort (); 354 | throw e; 355 | } 356 | } 357 | SourceEntity GetSourceEntityByName( string name ) { 358 | if (name == "") { 359 | return null; 360 | } 361 | foreach (SourceEntity ent in entities) { 362 | if (ent.GetStringValue ("targetname") == name) { 363 | return ent; 364 | } 365 | } 366 | return null; 367 | } 368 | } 369 | 370 | public class SourceEntity { 371 | public Dictionary data; 372 | public string header; 373 | public SourceEntity( string header ) { 374 | this.header = header; 375 | data = new Dictionary(); 376 | } 377 | public void SetKey( string key, string value ) { 378 | data [key] = value; 379 | } 380 | public string GetStringValue( string key ) { 381 | if (!data.ContainsKey (key)) { 382 | return ""; 383 | } 384 | return data [key]; 385 | } 386 | public Vector3 GetVectorValue( string key ) { 387 | MatchCollection matches = Regex.Matches(data[key], @"-?\d+\.?\d*"); 388 | return new Vector3 (float.Parse (matches [0].Value), float.Parse (matches [1].Value), float.Parse (matches [2].Value)); 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /SourceTools/Scripts/SourceVMF.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 27f59b5d3fb38bd4d844203008e7c17a 3 | timeCreated: 1517569675 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: 8 | - vmfText: {fileID: 4900000, guid: 9d7e5b8aba2cfc74d960fc12870fb7d8, type: 3} 9 | - mapFBX: {fileID: 100000, guid: 77b0887d274db2847a97685c9dc675c8, type: 3} 10 | - decalShader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} 11 | - ropePrefab: {fileID: 1242466808652464, guid: d0aee46be7601014897c0addf109c234, 12 | type: 2} 13 | - decalPrefab: {fileID: 1905463203030152, guid: 7cb274ee30d347a468f8f469083fe30c, 14 | type: 2} 15 | executionOrder: 0 16 | icon: {instanceID: 0} 17 | userData: 18 | assetBundleName: 19 | assetBundleVariant: 20 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Utility.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1e24b1d1e4f111247a6d6cd7c2e934c9 3 | folderAsset: yes 4 | timeCreated: 1515618169 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /SourceTools/Scripts/Utility/ThreadedJob.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using UnityEngine; 5 | 6 | public class ThreadedJob { 7 | private bool m_IsDone = false; 8 | private object m_Handle = new object(); 9 | private System.Threading.Thread m_Thread = null; 10 | public bool IsDone 11 | { 12 | get 13 | { 14 | return m_IsDone; 15 | } 16 | set 17 | { 18 | lock (m_Handle) 19 | { 20 | m_IsDone = value; 21 | } 22 | } 23 | } 24 | 25 | public virtual void Start() 26 | { 27 | m_Thread = new System.Threading.Thread(Run); 28 | m_Thread.IsBackground = true; 29 | m_Thread.Priority = System.Threading.ThreadPriority.BelowNormal; 30 | m_Thread.Start (); 31 | } 32 | public virtual void Abort() 33 | { 34 | m_Thread.Abort(); 35 | } 36 | 37 | protected virtual void ThreadFunction() { } 38 | 39 | protected virtual void OnFinished() { } 40 | 41 | public virtual bool Update() 42 | { 43 | if (IsDone) 44 | { 45 | OnFinished(); 46 | return true; 47 | } 48 | return false; 49 | } 50 | public IEnumerator WaitFor() 51 | { 52 | while(!Update()) 53 | { 54 | yield return null; 55 | } 56 | } 57 | private void Run() 58 | { 59 | ThreadFunction(); 60 | IsDone = true; 61 | } 62 | } -------------------------------------------------------------------------------- /SourceTools/Scripts/Utility/ThreadedJob.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c1f73dabc1b26d74798ef52e1d3d3f64 3 | timeCreated: 1515618169 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | --------------------------------------------------------------------------------