private static Material FindMaterial(MaterialSearcher materialSearcher, HashSet <string> materialSearcherWarnings, string name) { // find the material in the unity project automatically. Material material; // try finding the fully qualified texture name with '/' replaced by '.' so 'BRICK.BRICKWALL052D'. string materialName = name.Replace("/", "."); if (materialName.Contains(".")) { // try finding both 'BRICK.BRICKWALL052D' and 'BRICKWALL052D'. string tiny = materialName.Substring(materialName.LastIndexOf('.') + 1); material = materialSearcher.FindMaterial(new string[] { materialName, tiny }); if (material == null && materialSearcherWarnings.Add(materialName)) { Debug.Log("Chisel: Tried to find material '" + materialName + "' and also as '" + tiny + "' but it couldn't be found in the project."); } } else { // only try finding 'BRICKWALL052D'. material = materialSearcher.FindMaterial(new string[] { materialName }); if (material == null && materialSearcherWarnings.Add(materialName)) { Debug.Log("Chisel: Tried to find material '" + materialName + "' but it couldn't be found in the project."); } } return(material); }
/// <summary> /// Imports the specified map into the SabreCSG model. /// </summary> /// <param name="model">The model to import into.</param> /// <param name="map">The map to be imported.</param> /// <param name="scale">The scale modifier.</param> public static void Import(CSGModelBase model, T3dMap map, int scale = 64) { try { model.BeginUpdate(); // create a material searcher to associate materials automatically. MaterialSearcher materialSearcher = new MaterialSearcher(); List <T3dActor> brushes = map.Brushes; Brush[] sabreBrushes = new Brush[brushes.Count]; // iterate through all brush actors. for (int k = 0; k < brushes.Count; k++) { // get the underlying brush data. T3dActor tactor = brushes[k]; T3dBrush tbrush = tactor.Brush; #if UNITY_EDITOR UnityEditor.EditorUtility.DisplayProgressBar("SabreCSG: Importing Unreal Gold Map", "Converting Unreal Brushes To SabreCSG Brushes (" + (k + 1) + " / " + brushes.Count + ")...", k / (float)brushes.Count); #endif // iterate through the brush polygons. Polygon[] polygons = new Polygon[tbrush.Polygons.Count]; for (int i = 0; i < tbrush.Polygons.Count; i++) { T3dPolygon tpolygon = tbrush.Polygons[i]; // find the material in the unity project automatically. Material material = null; if (!string.IsNullOrEmpty(tpolygon.Texture)) { if (tpolygon.Texture.Contains('.')) { // try finding both 'PlayrShp.Ceiling.Hullwk' and 'Hullwk'. string tiny = tpolygon.Texture.Substring(tpolygon.Texture.LastIndexOf('.') + 1); material = materialSearcher.FindMaterial(new string[] { tpolygon.Texture, tiny }); if (material == null) { Debug.Log("SabreCSG: Tried to find material '" + tpolygon.Texture + "' and also as '" + tiny + "' but it couldn't be found in the project."); } } else { // only try finding 'Hullwk'. material = materialSearcher.FindMaterial(new string[] { tpolygon.Texture }); if (material == null) { Debug.Log("SabreCSG: Tried to find material '" + tpolygon.Texture + "' but it couldn't be found in the project."); } } } Vertex[] vertices = new Vertex[tpolygon.Vertices.Count]; for (int j = 0; j < tpolygon.Vertices.Count; j++) { // main-scale // scale around pivot point. Vector3 vertexPosition = ToVector3(tpolygon.Vertices[j]); Vector3 pivot = ToVector3(tactor.PrePivot); Vector3 difference = vertexPosition - pivot; vertexPosition = difference.Multiply(ToVector3Raw(tactor.MainScale)) + pivot; // post-scale vertices[j] = new Vertex(vertexPosition.Multiply(ToVector3Raw(tactor.PostScale)) / (float)scale, ToVector3(tpolygon.Normal), GenerateUV(tpolygon, j, material)); } // detect the polygon flags. bool userExcludeFromFinal = false; if ((tpolygon.Flags & T3dPolygonFlags.Invisible) > 0) { userExcludeFromFinal = true; } polygons[i] = new Polygon(vertices, material, false, userExcludeFromFinal); } // position and rotate the brushes around their pivot point. Transform transform = model.CreateCustomBrush(polygons).transform; transform.position = (ToVector3(tactor.Location) / (float)scale) - (ToVector3(tactor.PrePivot) / (float)scale); Vector3 axis; float angle; T3dRotatorToQuaternion(tactor.Rotation).ToAngleAxis(out angle, out axis); transform.RotateAround(transform.position + (ToVector3(tactor.PrePivot) / (float)scale), axis, angle); PrimitiveBrush brush = transform.GetComponent <PrimitiveBrush>(); sabreBrushes[k] = brush; object value; // detect the brush mode (additive, subtractive). if (tactor.Properties.TryGetValue("CsgOper", out value)) { brush.Mode = (string)value == "CSG_Add" ? CSGMode.Add : CSGMode.Subtract; } // detect special brush flags. if (tactor.Properties.TryGetValue("PolyFlags", out value)) { T3dBrushFlags flags = (T3dBrushFlags)value; if ((flags & T3dBrushFlags.Invisible) > 0) { brush.IsVisible = false; } if ((flags & T3dBrushFlags.NonSolid) > 0) { brush.HasCollision = false; } if ((flags & T3dBrushFlags.SemiSolid) > 0) { brush.IsNoCSG = true; } } // detect single polygons. if (polygons.Length == 1) { brush.IsNoCSG = true; } } // add all new brushes to a group. string title = "Unreal Gold Map"; if (map.Title != "") { title += " '" + map.Title + "'"; } if (map.Author != "") { title += " (" + map.Author + ")"; } GroupBrush groupBrush = new GameObject(title).AddComponent <GroupBrush>(); groupBrush.transform.SetParent(model.transform); for (int i = 0; i < sabreBrushes.Length; i++) { sabreBrushes[i].transform.SetParent(groupBrush.transform); } #if UNITY_EDITOR UnityEditor.EditorUtility.ClearProgressBar(); #endif } catch (Exception) { throw; } finally { model.EndUpdate(); } }
private const float inchesInMeters = 0.03125f; // 1/32 /// <summary> /// Imports the specified world into the SabreCSG model. /// </summary> /// <param name="model">The model to import into.</param> /// <param name="world">The world to be imported.</param> /// <param name="scale">The scale modifier.</param> public static void Import(CSGModelBase model, VmfWorld world) { try { model.BeginUpdate(); // create a material searcher to associate materials automatically. MaterialSearcher materialSearcher = new MaterialSearcher(); // group all the brushes together. GroupBrush groupBrush = new GameObject("Source Engine Map").AddComponent <GroupBrush>(); groupBrush.transform.SetParent(model.transform); // iterate through all world solids. for (int i = 0; i < world.Solids.Count; i++) { #if UNITY_EDITOR UnityEditor.EditorUtility.DisplayProgressBar("SabreCSG: Importing Source Engine Map", "Converting Hammer Solids To SabreCSG Brushes (" + (i + 1) + " / " + world.Solids.Count + ")...", i / (float)world.Solids.Count); #endif VmfSolid solid = world.Solids[i]; // don't add triggers to the scene. if (solid.Sides.Count > 0 && IsSpecialMaterial(solid.Sides[0].Material)) { continue; } // build a very large cube brush. var go = model.CreateBrush(PrimitiveBrushType.Cube, Vector3.zero); var pr = go.GetComponent <PrimitiveBrush>(); BrushUtility.Resize(pr, new Vector3(8192, 8192, 8192)); // clip all the sides out of the brush. for (int j = solid.Sides.Count; j-- > 0;) { VmfSolidSide side = solid.Sides[j]; Plane clip = new Plane(pr.transform.InverseTransformPoint(new Vector3(side.Plane.P1.X, side.Plane.P1.Z, side.Plane.P1.Y) * inchesInMeters), pr.transform.InverseTransformPoint(new Vector3(side.Plane.P2.X, side.Plane.P2.Z, side.Plane.P2.Y) * inchesInMeters), pr.transform.InverseTransformPoint(new Vector3(side.Plane.P3.X, side.Plane.P3.Z, side.Plane.P3.Y) * inchesInMeters)); ClipUtility.ApplyClipPlane(pr, clip, false); // find the polygons associated with the clipping plane. // the normal is unique and can never occur twice as that wouldn't allow the solid to be convex. var polygons = pr.GetPolygons().Where(p => p.Plane.normal == clip.normal); foreach (var polygon in polygons) { // detect excluded polygons. if (IsExcludedMaterial(side.Material)) { polygon.UserExcludeFromFinal = true; } // detect collision-only brushes. if (IsInvisibleMaterial(side.Material)) { pr.IsVisible = false; } // find the material in the unity project automatically. Material material; // try finding the fully qualified texture name with '/' replaced by '.' so 'BRICK.BRICKWALL052D'. string materialName = side.Material.Replace("/", "."); if (materialName.Contains('.')) { // try finding both 'BRICK.BRICKWALL052D' and 'BRICKWALL052D'. string tiny = materialName.Substring(materialName.LastIndexOf('.') + 1); material = materialSearcher.FindMaterial(new string[] { materialName, tiny }); if (material == null) { Debug.Log("SabreCSG: Tried to find material '" + materialName + "' and also as '" + tiny + "' but it couldn't be found in the project."); } } else { // only try finding 'BRICKWALL052D'. material = materialSearcher.FindMaterial(new string[] { materialName }); if (material == null) { Debug.Log("SabreCSG: Tried to find material '" + materialName + "' but it couldn't be found in the project."); } } polygon.Material = material; // calculate the texture coordinates. int w = 256; int h = 256; if (polygon.Material != null && polygon.Material.mainTexture != null) { w = polygon.Material.mainTexture.width; h = polygon.Material.mainTexture.height; } CalculateTextureCoordinates(pr, polygon, w, h, side.UAxis, side.VAxis); } } // add the brush to the group. pr.transform.SetParent(groupBrush.transform); } // iterate through all entities. for (int e = 0; e < world.Entities.Count; e++) { #if UNITY_EDITOR UnityEditor.EditorUtility.DisplayProgressBar("SabreCSG: Importing Source Engine Map", "Converting Hammer Entities To SabreCSG Brushes (" + (e + 1) + " / " + world.Entities.Count + ")...", e / (float)world.Entities.Count); #endif VmfEntity entity = world.Entities[e]; // skip entities that sabrecsg can't handle. switch (entity.ClassName) { case "func_areaportal": case "func_areaportalwindow": case "func_capturezone": case "func_changeclass": case "func_combine_ball_spawner": case "func_dustcloud": case "func_dustmotes": case "func_nobuild": case "func_nogrenades": case "func_occluder": case "func_precipitation": case "func_proprespawnzone": case "func_regenerate": case "func_respawnroom": case "func_smokevolume": case "func_viscluster": continue; } // iterate through all entity solids. for (int i = 0; i < entity.Solids.Count; i++) { VmfSolid solid = entity.Solids[i]; // don't add triggers to the scene. if (solid.Sides.Count > 0 && IsSpecialMaterial(solid.Sides[0].Material)) { continue; } // build a very large cube brush. var go = model.CreateBrush(PrimitiveBrushType.Cube, Vector3.zero); var pr = go.GetComponent <PrimitiveBrush>(); BrushUtility.Resize(pr, new Vector3(8192, 8192, 8192)); // clip all the sides out of the brush. for (int j = solid.Sides.Count; j-- > 0;) { VmfSolidSide side = solid.Sides[j]; Plane clip = new Plane(pr.transform.InverseTransformPoint(new Vector3(side.Plane.P1.X, side.Plane.P1.Z, side.Plane.P1.Y) * inchesInMeters), pr.transform.InverseTransformPoint(new Vector3(side.Plane.P2.X, side.Plane.P2.Z, side.Plane.P2.Y) * inchesInMeters), pr.transform.InverseTransformPoint(new Vector3(side.Plane.P3.X, side.Plane.P3.Z, side.Plane.P3.Y) * inchesInMeters)); ClipUtility.ApplyClipPlane(pr, clip, false); // find the polygons associated with the clipping plane. // the normal is unique and can never occur twice as that wouldn't allow the solid to be convex. var polygons = pr.GetPolygons().Where(p => p.Plane.normal == clip.normal); foreach (var polygon in polygons) { // detect excluded polygons. if (IsExcludedMaterial(side.Material)) { polygon.UserExcludeFromFinal = true; } // detect collision-only brushes. if (IsInvisibleMaterial(side.Material)) { pr.IsVisible = false; } // find the material in the unity project automatically. Material material; // try finding the fully qualified texture name with '/' replaced by '.' so 'BRICK.BRICKWALL052D'. string materialName = side.Material.Replace("/", "."); if (materialName.Contains('.')) { // try finding both 'BRICK.BRICKWALL052D' and 'BRICKWALL052D'. string tiny = materialName.Substring(materialName.LastIndexOf('.') + 1); material = materialSearcher.FindMaterial(new string[] { materialName, tiny }); if (material == null) { Debug.Log("SabreCSG: Tried to find material '" + materialName + "' and also as '" + tiny + "' but it couldn't be found in the project."); } } else { // only try finding 'BRICKWALL052D'. material = materialSearcher.FindMaterial(new string[] { materialName }); if (material == null) { Debug.Log("SabreCSG: Tried to find material '" + materialName + "' but it couldn't be found in the project."); } } polygon.Material = material; // calculate the texture coordinates. int w = 256; int h = 256; if (polygon.Material != null && polygon.Material.mainTexture != null) { w = polygon.Material.mainTexture.width; h = polygon.Material.mainTexture.height; } CalculateTextureCoordinates(pr, polygon, w, h, side.UAxis, side.VAxis); } } // detail brushes that do not affect the CSG world. if (entity.ClassName == "func_detail") { pr.IsNoCSG = true; } // collision only brushes. if (entity.ClassName == "func_vehicleclip") { pr.IsVisible = false; } // add the brush to the group. pr.transform.SetParent(groupBrush.transform); } } #if UNITY_EDITOR UnityEditor.EditorUtility.ClearProgressBar(); #endif } catch (Exception) { throw; } finally { model.EndUpdate(); } }
/// <summary> /// Imports the specified world into the SabreCSG model. /// </summary> /// <param name="model">The model to import into.</param> /// <param name="world">The world to be imported.</param> /// <param name="scale">The scale modifier.</param> public static void Import(CSGModelBase model, MapWorld world) { try { model.BeginUpdate(); // create a material searcher to associate materials automatically. MaterialSearcher materialSearcher = new MaterialSearcher(); // group all the brushes together. GroupBrush groupBrush = new GameObject("Quake 1 Map").AddComponent <GroupBrush>(); groupBrush.transform.SetParent(model.transform); // iterate through all entities. for (int e = 0; e < world.Entities.Count; e++) { #if UNITY_EDITOR UnityEditor.EditorUtility.DisplayProgressBar("SabreCSG: Importing Quake 1 Map", "Converting Quake 1 Entities To SabreCSG Brushes (" + (e + 1) + " / " + world.Entities.Count + ")...", e / (float)world.Entities.Count); #endif MapEntity entity = world.Entities[e]; // iterate through all entity solids. for (int i = 0; i < entity.Brushes.Count; i++) { MapBrush brush = entity.Brushes[i]; #if UNITY_EDITOR if (world.Entities[e].ClassName == "worldspawn") { UnityEditor.EditorUtility.DisplayProgressBar("SabreCSG: Importing Quake 1 Map", "Converting Quake 1 Brushes To SabreCSG Brushes (" + (i + 1) + " / " + entity.Brushes.Count + ")...", i / (float)entity.Brushes.Count); } #endif // don't add triggers to the scene. if (brush.Sides.Count > 0 && IsSpecialMaterial(brush.Sides[0].Material)) { continue; } // build a very large cube brush. var go = model.CreateBrush(PrimitiveBrushType.Cube, Vector3.zero); var pr = go.GetComponent <PrimitiveBrush>(); BrushUtility.Resize(pr, new Vector3(8192, 8192, 8192)); // clip all the sides out of the brush. for (int j = brush.Sides.Count; j-- > 0;) { MapBrushSide side = brush.Sides[j]; Plane clip = new Plane(pr.transform.InverseTransformPoint(new Vector3(side.Plane.P1.X, side.Plane.P1.Z, side.Plane.P1.Y) / (float)s_Scale), pr.transform.InverseTransformPoint(new Vector3(side.Plane.P2.X, side.Plane.P2.Z, side.Plane.P2.Y) / (float)s_Scale), pr.transform.InverseTransformPoint(new Vector3(side.Plane.P3.X, side.Plane.P3.Z, side.Plane.P3.Y) / (float)s_Scale)); ClipUtility.ApplyClipPlane(pr, clip, false); // find the polygons associated with the clipping plane. // the normal is unique and can never occur twice as that wouldn't allow the solid to be convex. var polygons = pr.GetPolygons().Where(p => p.Plane.normal.EqualsWithEpsilonLower3(clip.normal)); foreach (var polygon in polygons) { // detect excluded polygons. if (IsExcludedMaterial(side.Material)) { polygon.UserExcludeFromFinal = true; } // detect collision-only brushes. if (IsInvisibleMaterial(side.Material)) { pr.IsVisible = false; } // find the material in the unity project automatically. Material material; // try finding the texture name with '*' replaced by '#' so '#teleport'. string materialName = side.Material.Replace("*", "#"); material = materialSearcher.FindMaterial(new string[] { materialName }); if (material == null) { Debug.Log("SabreCSG: Tried to find material '" + materialName + "' but it couldn't be found in the project."); } polygon.Material = material; // calculate the texture coordinates. int w = 256; int h = 256; if (polygon.Material != null && polygon.Material.mainTexture != null) { w = polygon.Material.mainTexture.width; h = polygon.Material.mainTexture.height; } CalculateTextureCoordinates(pr, polygon, w, h, new Vector2(side.Offset.X, -side.Offset.Y), new Vector2(side.Scale.X, side.Scale.Y), side.Rotation); } } // add the brush to the group. pr.transform.SetParent(groupBrush.transform); } } #if UNITY_EDITOR UnityEditor.EditorUtility.ClearProgressBar(); #endif } catch (Exception) { throw; } finally { model.EndUpdate(); } }
/// <summary> /// Imports the specified world into the Chisel model. /// </summary> /// <param name="model">The model to import into.</param> /// <param name="world">The world to be imported.</param> public static void Import(ChiselModel model, VmfWorld world) { // create a material searcher to associate materials automatically. MaterialSearcher materialSearcher = new MaterialSearcher(); HashSet <string> materialSearcherWarnings = new HashSet <string>(); // iterate through all world solids. for (int i = 0; i < world.Solids.Count; i++) { #if UNITY_EDITOR UnityEditor.EditorUtility.DisplayProgressBar("Chisel: Importing Source Engine Map (1/3)", "Converting Hammer Solids To Chisel Brushes (" + (i + 1) + " / " + world.Solids.Count + ")...", i / (float)world.Solids.Count); #endif VmfSolid solid = world.Solids[i]; // don't add triggers to the scene. if (solid.Sides.Count > 0 && IsSpecialMaterial(solid.Sides[0].Material)) { continue; } // HACK: Fix me in the future! // HACK: Chisel doesn't support collision brushes yet- skip them completely! if (solid.Sides.Count > 0 && IsInvisibleMaterial(solid.Sides[0].Material)) { continue; } // HACK: Fix me in the future! // build a very large cube brush. ChiselBrush go = ChiselComponentFactory.Create <ChiselBrush>(model); go.definition.surfaceDefinition = new ChiselSurfaceDefinition(); go.definition.surfaceDefinition.EnsureSize(6); BrushMesh brushMesh = new BrushMesh(); go.definition.brushOutline = brushMesh; BrushMeshFactory.CreateBox(ref brushMesh, new Vector3(-4096, -4096, -4096), new Vector3(4096, 4096, 4096), in go.definition.surfaceDefinition); // prepare for any displacements. List <DisplacementSide> DisplacementSurfaces = new List <DisplacementSide>(); // prepare for uv calculations of clip planes after cutting. var planes = new float4[solid.Sides.Count]; var planeSurfaces = new ChiselSurface[solid.Sides.Count]; // compute all the sides of the brush that will be clipped. for (int j = solid.Sides.Count; j-- > 0;) { VmfSolidSide side = solid.Sides[j]; // detect excluded polygons. //if (IsExcludedMaterial(side.Material)) //polygon.UserExcludeFromFinal = true; // detect collision-only brushes. //if (IsInvisibleMaterial(side.Material)) //pr.IsVisible = false; // find the material in the unity project automatically. Material material; // try finding the fully qualified texture name with '/' replaced by '.' so 'BRICK.BRICKWALL052D'. string materialName = side.Material.Replace("/", "."); if (materialName.Contains(".")) { // try finding both 'BRICK.BRICKWALL052D' and 'BRICKWALL052D'. string tiny = materialName.Substring(materialName.LastIndexOf('.') + 1); material = materialSearcher.FindMaterial(new string[] { materialName, tiny }); if (material == null && materialSearcherWarnings.Add(materialName)) { Debug.Log("Chisel: Tried to find material '" + materialName + "' and also as '" + tiny + "' but it couldn't be found in the project."); } } else { // only try finding 'BRICKWALL052D'. material = materialSearcher.FindMaterial(new string[] { materialName }); if (material == null && materialSearcherWarnings.Add(materialName)) { Debug.Log("Chisel: Tried to find material '" + materialName + "' but it couldn't be found in the project."); } } // fallback to default material. if (material == null) { material = ChiselMaterialManager.DefaultFloorMaterial; } // create chisel surface for the clip. ChiselSurface surface = new ChiselSurface(); surface.brushMaterial = ChiselBrushMaterial.CreateInstance(material, ChiselMaterialManager.DefaultPhysicsMaterial); surface.surfaceDescription = SurfaceDescription.Default; // detect collision-only polygons. if (IsInvisibleMaterial(side.Material)) { surface.brushMaterial.LayerUsage &= ~LayerUsageFlags.RenderReceiveCastShadows; } // detect excluded polygons. if (IsExcludedMaterial(side.Material)) { surface.brushMaterial.LayerUsage &= LayerUsageFlags.CastShadows; surface.brushMaterial.LayerUsage |= LayerUsageFlags.Collidable; } // calculate the clipping planes. Plane clip = new Plane(go.transform.InverseTransformPoint(new Vector3(side.Plane.P1.X, side.Plane.P1.Z, side.Plane.P1.Y) * inchesInMeters), go.transform.InverseTransformPoint(new Vector3(side.Plane.P2.X, side.Plane.P2.Z, side.Plane.P2.Y) * inchesInMeters), go.transform.InverseTransformPoint(new Vector3(side.Plane.P3.X, side.Plane.P3.Z, side.Plane.P3.Y) * inchesInMeters)); planes[j] = new float4(clip.normal, clip.distance); planeSurfaces[j] = surface; // check whether this surface is a displacement. if (side.Displacement != null) { // disable the brush. go.gameObject.GetComponent <ChiselBrush>().enabled = false; // keep track of the surface used to cut the mesh. DisplacementSurfaces.Add(new DisplacementSide { side = side, surface = surface }); } } // cut all the clipping planes out of the brush in one go. brushMesh.Cut(planes, planeSurfaces); // now iterate over the planes to calculate UV coordinates. int[] indices = new int[solid.Sides.Count]; for (int k = 0; k < planes.Length; k++) { var plane = planes[k]; int closestIndex = 0; float closestDistance = math.lengthsq(plane - brushMesh.planes[0]); for (int j = 1; j < brushMesh.planes.Length; j++) { float testDistance = math.lengthsq(plane - brushMesh.planes[j]); if (testDistance < closestDistance) { closestIndex = j; closestDistance = testDistance; } } indices[k] = closestIndex; } for (int j = 0; j < indices.Length; j++) { brushMesh.planes[indices[j]] = planes[j]; } for (int j = solid.Sides.Count; j-- > 0;) { VmfSolidSide side = solid.Sides[j]; var surface = brushMesh.polygons[indices[j]].surface; var material = surface.brushMaterial.RenderMaterial; // calculate the texture coordinates. int w = 256; int h = 256; if (material.mainTexture != null) { w = material.mainTexture.width; h = material.mainTexture.height; } var clip = new Plane(planes[j].xyz, planes[j].w); CalculateTextureCoordinates(go, surface, clip, w, h, side.UAxis, side.VAxis); } // build displacements. foreach (DisplacementSide displacement in DisplacementSurfaces) { // find the brush mesh polygon: for (int polyidx = 0; polyidx < brushMesh.polygons.Length; polyidx++) { if (brushMesh.polygons[polyidx].surface == displacement.surface) { // find the polygon plane. Plane plane = new Plane(brushMesh.planes[polyidx].xyz, brushMesh.planes[polyidx].w); // find all vertices that belong to this polygon: List <Vector3> vertices = new List <Vector3>(); { var polygon = brushMesh.polygons[polyidx]; var firstEdge = polygon.firstEdge; var edgeCount = polygon.edgeCount; var lastEdge = firstEdge + edgeCount; for (int e = firstEdge; e < lastEdge; e++) { vertices.Add(brushMesh.vertices[brushMesh.halfEdges[e].vertexIndex]); } } // reverse the winding order. vertices.Reverse(); var first = vertices[0]; vertices.RemoveAt(0); vertices.Add(first); // build displacement: BuildDisplacementSurface(go, displacement.side, displacement.surface, vertices, plane); } } } // finalize the brush by snapping planes and centering the pivot point. go.transform.position += brushMesh.CenterAndSnapPlanes(); foreach (Transform child in go.transform) { child.position -= go.transform.position; } } // iterate through all entities. for (int e = 0; e < world.Entities.Count; e++) { #if UNITY_EDITOR UnityEditor.EditorUtility.DisplayProgressBar("Chisel: Importing Source Engine Map (2/3)", "Converting Hammer Entities To Chisel Brushes (" + (e + 1) + " / " + world.Entities.Count + ")...", e / (float)world.Entities.Count); #endif VmfEntity entity = world.Entities[e]; // skip entities that chisel can't handle. switch (entity.ClassName) { case "func_areaportal": case "func_areaportalwindow": case "func_capturezone": case "func_changeclass": case "func_combine_ball_spawner": case "func_dustcloud": case "func_dustmotes": case "func_nobuild": case "func_nogrenades": case "func_occluder": case "func_precipitation": case "func_proprespawnzone": case "func_regenerate": case "func_respawnroom": case "func_smokevolume": case "func_viscluster": continue; } // iterate through all entity solids. for (int i = 0; i < entity.Solids.Count; i++) { VmfSolid solid = entity.Solids[i]; // don't add triggers to the scene. if (solid.Sides.Count > 0 && IsSpecialMaterial(solid.Sides[0].Material)) { continue; } // HACK: Fix me in the future! // HACK: Chisel doesn't support collision brushes yet- skip them completely! if (solid.Sides.Count > 0 && IsInvisibleMaterial(solid.Sides[0].Material)) { continue; } // HACK: Fix me in the future! // build a very large cube brush. ChiselBrush go = ChiselComponentFactory.Create <ChiselBrush>(model); go.definition.surfaceDefinition = new ChiselSurfaceDefinition(); go.definition.surfaceDefinition.EnsureSize(6); BrushMesh brushMesh = new BrushMesh(); go.definition.brushOutline = brushMesh; BrushMeshFactory.CreateBox(ref brushMesh, new Vector3(-4096, -4096, -4096), new Vector3(4096, 4096, 4096), in go.definition.surfaceDefinition); // prepare for uv calculations of clip planes after cutting. var planes = new float4[solid.Sides.Count]; var planeSurfaces = new ChiselSurface[solid.Sides.Count]; // clip all the sides out of the brush. for (int j = solid.Sides.Count; j-- > 0;) { VmfSolidSide side = solid.Sides[j]; // detect excluded polygons. //if (IsExcludedMaterial(side.Material)) // polygon.UserExcludeFromFinal = true; // detect collision-only brushes. //if (IsInvisibleMaterial(side.Material)) // pr.IsVisible = false; // find the material in the unity project automatically. Material material; // try finding the fully qualified texture name with '/' replaced by '.' so 'BRICK.BRICKWALL052D'. string materialName = side.Material.Replace("/", "."); if (materialName.Contains(".")) { // try finding both 'BRICK.BRICKWALL052D' and 'BRICKWALL052D'. string tiny = materialName.Substring(materialName.LastIndexOf('.') + 1); material = materialSearcher.FindMaterial(new string[] { materialName, tiny }); if (material == null && materialSearcherWarnings.Add(materialName)) { Debug.Log("Chisel: Tried to find material '" + materialName + "' and also as '" + tiny + "' but it couldn't be found in the project."); } } else { // only try finding 'BRICKWALL052D'. material = materialSearcher.FindMaterial(new string[] { materialName }); if (material == null && materialSearcherWarnings.Add(materialName)) { Debug.Log("Chisel: Tried to find material '" + materialName + "' but it couldn't be found in the project."); } } // fallback to default material. if (material == null) { material = ChiselMaterialManager.DefaultFloorMaterial; } // create chisel surface for the clip. ChiselSurface surface = new ChiselSurface(); surface.brushMaterial = ChiselBrushMaterial.CreateInstance(material, ChiselMaterialManager.DefaultPhysicsMaterial); surface.surfaceDescription = SurfaceDescription.Default; // detect collision-only polygons. if (IsInvisibleMaterial(side.Material)) { surface.brushMaterial.LayerUsage &= ~LayerUsageFlags.RenderReceiveCastShadows; } // detect excluded polygons. if (IsExcludedMaterial(side.Material)) { surface.brushMaterial.LayerUsage &= LayerUsageFlags.CastShadows; surface.brushMaterial.LayerUsage |= LayerUsageFlags.Collidable; } // calculate the clipping planes. Plane clip = new Plane(go.transform.InverseTransformPoint(new Vector3(side.Plane.P1.X, side.Plane.P1.Z, side.Plane.P1.Y) * inchesInMeters), go.transform.InverseTransformPoint(new Vector3(side.Plane.P2.X, side.Plane.P2.Z, side.Plane.P2.Y) * inchesInMeters), go.transform.InverseTransformPoint(new Vector3(side.Plane.P3.X, side.Plane.P3.Z, side.Plane.P3.Y) * inchesInMeters)); planes[j] = new float4(clip.normal, clip.distance); planeSurfaces[j] = surface; } // cut all the clipping planes out of the brush in one go. brushMesh.Cut(planes, planeSurfaces); // now iterate over the planes to calculate UV coordinates. int[] indices = new int[solid.Sides.Count]; for (int k = 0; k < planes.Length; k++) { var plane = planes[k]; int closestIndex = 0; float closestDistance = math.lengthsq(plane - brushMesh.planes[0]); for (int j = 1; j < brushMesh.planes.Length; j++) { float testDistance = math.lengthsq(plane - brushMesh.planes[j]); if (testDistance < closestDistance) { closestIndex = j; closestDistance = testDistance; } } indices[k] = closestIndex; } for (int j = 0; j < indices.Length; j++) { brushMesh.planes[indices[j]] = planes[j]; } for (int j = solid.Sides.Count; j-- > 0;) { VmfSolidSide side = solid.Sides[j]; var surface = brushMesh.polygons[indices[j]].surface; var material = surface.brushMaterial.RenderMaterial; // calculate the texture coordinates. int w = 256; int h = 256; if (material.mainTexture != null) { w = material.mainTexture.width; h = material.mainTexture.height; } var clip = new Plane(planes[j].xyz, planes[j].w); CalculateTextureCoordinates(go, surface, clip, w, h, side.UAxis, side.VAxis); } // finalize the brush by snapping planes and centering the pivot point. go.transform.position += brushMesh.CenterAndSnapPlanes(); // detail brushes that do not affect the CSG world. //if (entity.ClassName == "func_detail") //pr.IsNoCSG = true; // collision only brushes. //if (entity.ClassName == "func_vehicleclip") //pr.IsVisible = false; } } }
/// <summary> /// Imports the entities and attaches them to the specified parent. /// </summary> /// <param name="parent">The parent to attach entities to.</param> /// <param name="world">The world to be imported.</param> public static void Import(Transform parent, VmfWorld world) { #if COM_AETERNUMGAMES_CHISEL_DECALS // optional decals package: https://github.com/Henry00IS/Chisel.Decals // create a material searcher to associate materials automatically. MaterialSearcher materialSearcher = new MaterialSearcher(); HashSet <string> materialSearcherWarnings = new HashSet <string>(); #endif // iterate through all entities. for (int e = 0; e < world.Entities.Count; e++) { #if UNITY_EDITOR UnityEditor.EditorUtility.DisplayProgressBar("Chisel: Importing Source Engine Map (3/3)", "Converting Hammer Entities To Unity Objects (" + (e + 1) + " / " + world.Entities.Count + ")...", e / (float)world.Entities.Count); #endif VmfEntity entity = world.Entities[e]; switch (entity.ClassName) { // https://developer.valvesoftware.com/wiki/Light // light is a point entity available in all Source games. it creates an invisible, static light source that shines in all directions. case "light": { // create a new light object: GameObject go = new GameObject("Light"); go.transform.parent = GetLightingGroupOrCreate(parent); // set the object position: if (TryGetEntityOrigin(entity, out Vector3 origin)) { go.transform.position = origin; } // add a light component: Light light = go.AddComponent <Light>(); light.type = LightType.Point; #if UNITY_EDITOR light.lightmapBakeType = LightmapBakeType.Baked; #endif light.range = 25.0f; // set the light color: if (entity.TryGetProperty("_light", out VmfVector4 color)) { light.intensity = color.W * lightBrightnessScalar; light.color = new Color(color.X / 255.0f, color.Y / 255.0f, color.Z / 255.0f); } break; } // https://developer.valvesoftware.com/wiki/Light_spot // light_spot is a point entity available in all Source games. it is a cone-shaped, invisible light source. case "light_spot": { // create a new light object: GameObject go = new GameObject("Spot Light"); go.transform.parent = GetLightingGroupOrCreate(parent); // set the object position: if (TryGetEntityOrigin(entity, out Vector3 origin)) { go.transform.position = origin; } // set the object rotation: if (TryGetEntityRotation(entity, out Quaternion rotation)) { go.transform.rotation = rotation; } // add a light component: Light light = go.AddComponent <Light>(); light.type = LightType.Spot; #if UNITY_EDITOR light.lightmapBakeType = LightmapBakeType.Mixed; #endif light.range = 10.0f; // set the light color: if (entity.TryGetProperty("_light", out VmfVector4 color)) { light.intensity = color.W * lightBrightnessScalar; light.color = new Color(color.X / 255.0f, color.Y / 255.0f, color.Z / 255.0f); } // approximate the light cookie cone shape and the spot angle. if (entity.TryGetProperty("_inner_cone", out int inner_cone) && entity.TryGetProperty("_cone", out int cone)) { float lightInnerCone = Mathf.Min(inner_cone * 2, 175); float lightCone = Mathf.Min(cone * 2, 175); if (lightInnerCone > lightCone) { float t = lightCone; lightInnerCone = lightCone; lightCone = t; } // set the spot angle: light.spotAngle = lightCone; // generate and set the light cookie: float coneFactor = Mathf.Max(0, lightInnerCone / lightCone); light.cookie = BuildLightCookieTexture(coneFactor); } // backup approach for the spot angle. else if (entity.TryGetProperty("_cone", out int cone2)) { // set the spot angle: float lightCone = Mathf.Min(cone2 * 2, 175); light.spotAngle = lightCone; } break; } #if COM_AETERNUMGAMES_CHISEL_DECALS // optional decals package: https://github.com/Henry00IS/Chisel.Decals case "infodecal": { // create a new decal object: GameObject go = new GameObject("Decal"); go.transform.parent = GetDecalsGroupOrCreate(parent); // set the object position: if (TryGetEntityOrigin(entity, out Vector3 origin)) { go.transform.position = origin; } // add the decal component: ChiselDecal decal = go.AddComponent <ChiselDecal>(); // assign the material: if (entity.TryGetProperty("texture", out string texture)) { Material material = FindMaterial(materialSearcher, materialSearcherWarnings, texture); if (material != null) { go.GetComponent <MeshRenderer>().sharedMaterial = material; var mainTexture = material.mainTexture; if (mainTexture != null) { // use the texture size to determine the size of the decal. go.transform.localScale = new Vector3(mainTexture.width * 0.008f, mainTexture.height * 0.008f, 0.1f); } } } // it should be snug against a surface- so we try to find it. RaycastHit raycastHit = default; bool hit = false; Vector3 r = Vector3.right * 0.1f; Vector3 f = Vector3.forward * 0.1f; Vector3 u = Vector3.up * 0.1f; // try a ray cast in all world axis to find a hit. if (hit = Physics.Raycast(go.transform.position - r, r, out RaycastHit hitInfo1, 0.2f)) { raycastHit = hitInfo1; } if (!hit && (hit = Physics.Raycast(go.transform.position + r, -r, out RaycastHit hitInfo2, 0.2f))) { raycastHit = hitInfo2; } if (!hit && (hit = Physics.Raycast(go.transform.position - f, f, out RaycastHit hitInfo3, 0.2f))) { raycastHit = hitInfo3; } if (!hit && (hit = Physics.Raycast(go.transform.position + f, -f, out RaycastHit hitInfo4, 0.2f))) { raycastHit = hitInfo4; } if (!hit && (hit = Physics.Raycast(go.transform.position - u, u, out RaycastHit hitInfo5, 0.2f))) { raycastHit = hitInfo5; } if (!hit && (hit = Physics.Raycast(go.transform.position + u, -u, out RaycastHit hitInfo6, 0.2f))) { raycastHit = hitInfo6; } // shouldn't not hit unless the level designer actually messed up. if (hit) { // now we have the normal of the surface to "face align" the decal. go.transform.rotation = Quaternion.LookRotation(-raycastHit.normal); } break; } #endif } } }
/// <summary> /// Imports the specified world into the RealtimeCSG. /// </summary> /// <param name="rootTransform">Transform to be parent of RealtimeCSG brushes</param> /// <param name="world">The world to be imported.</param> public static void Import(Transform rootTransform, MapWorld world) { try { //model.BeginUpdate(); // create a material searcher to associate materials automatically. MaterialSearcher materialSearcher = new MaterialSearcher(); // group all the brushes together. //GroupBrush groupBrush = new GameObject("Quake 1 Map").AddComponent<GroupBrush>(); //groupBrush.transform.SetParent(model.transform); var mapTransform = CreateGameObjectWithUniqueName(world.mapName, rootTransform); mapTransform.position = Vector3.zero; // Index of entities by trenchbroom id var entitiesById = new Dictionary <int, EntityContainer>(); var layers = new List <EntityContainer>(); for (int e = 0; e < world.Entities.Count; e++) { var entity = world.Entities[e]; //EntityContainer eContainer = null; if (entity.tbId >= 0) { var name = String.IsNullOrEmpty(entity.tbName) ? "Unnamed" : entity.tbName; var t = CreateGameObjectWithUniqueName(name, mapTransform); var eContainer = new EntityContainer(t, entity); entitiesById.Add(entity.tbId, eContainer); if (entity.tbType == "_tb_layer") { layers.Add(eContainer); eContainer.transform.SetParent(null); // unparent until layers are sorted by sort index } } } var defaultLayer = CreateGameObjectWithUniqueName("Default Layer", mapTransform); //var worldSpawnModel = OperationsUtility.CreateModelInstanceInScene(defaultLayer); //worldSpawnModel.name = "WorldSpawn"; //worldSpawnModel.transform.SetParent(mapTransform); layers = layers.OrderBy(l => l.entity.tbLayerSortIndex).ToList(); // sort layers by layer sort index foreach (var l in layers) { l.transform.SetParent(mapTransform); // parent layers to map in order } bool valveFormat = world.valveFormat; // iterate through all entities. for (int e = 0; e < world.Entities.Count; e++) { #if UNITY_EDITOR UnityEditor.EditorUtility.DisplayProgressBar("Importing Quake 1 Map", "Converting Quake 1 Entities To Brushes (" + (e + 1) + " / " + world.Entities.Count + ")...", e / (float)world.Entities.Count); #endif MapEntity entity = world.Entities[e]; Transform brushParent = mapTransform; bool isLayer = false; bool isTrigger = false; if (entity.ClassName == "worldspawn") { brushParent = defaultLayer; } else if (entity.tbType == "_tb_layer") { isLayer = true; if (entitiesById.TryGetValue(entity.tbId, out EntityContainer eContainer)) { brushParent = eContainer.transform; } } else if (entity.tbType == "_tb_group") { if (entitiesById.TryGetValue(entity.tbId, out EntityContainer eContainer)) { brushParent = eContainer.transform; } } else { if (entity.ClassName.Contains("trigger")) { isTrigger = true; } brushParent = CreateGameObjectWithUniqueName(entity.ClassName, mapTransform); } if (brushParent != mapTransform && brushParent != defaultLayer) { if (entity.tbGroup > 0) { if (entitiesById.TryGetValue(entity.tbGroup, out EntityContainer eContainer)) { brushParent.SetParent(eContainer.transform); } } else if (entity.tbLayer > 0) { if (entitiesById.TryGetValue(entity.tbLayer, out EntityContainer eContainer)) { brushParent.SetParent(eContainer.transform); } } else if (!isLayer) { brushParent.SetParent(defaultLayer); } } if (entity.Brushes.Count == 0) { continue; } var model = OperationsUtility.CreateModelInstanceInScene(brushParent); var parent = model.transform; if (isTrigger) { model.Settings = (model.Settings | ModelSettingsFlags.IsTrigger | ModelSettingsFlags.SetColliderConvex | ModelSettingsFlags.DoNotRender); } //GroupBrush entityGroup = new GameObject(entity.ClassName).AddComponent<GroupBrush>(); //entityGroup.transform.SetParent(groupBrush.transform); // iterate through all entity solids. for (int i = 0; i < entity.Brushes.Count; i++) { MapBrush brush = entity.Brushes[i]; #if UNITY_EDITOR if (world.Entities[e].ClassName == "worldspawn") { UnityEditor.EditorUtility.DisplayProgressBar("RealtimeCSG: Importing Quake 1 Map", "Converting Quake 1 Brushes To RealtimeCSG Brushes (" + (i + 1) + " / " + entity.Brushes.Count + ")...", i / (float)entity.Brushes.Count); } #endif // don't add triggers to the scene. // Triggers will get placed in entity model now //if (brush.Sides.Count > 0 && IsSpecialMaterial(brush.Sides[0].Material)) // continue; var name = UnityEditor.GameObjectUtility.GetUniqueNameForSibling(parent, "Brush"); var gameObject = new GameObject(name); var rcsgBrush = gameObject.AddComponent <CSGBrush>(); var t = gameObject.transform; gameObject.transform.SetParent(parent, true); gameObject.transform.position = new Vector3(0.5f, 0.5f, 0.5f); // this aligns it's vertices to the grid // //BrushFactory.CreateCubeControlMesh(out brush.ControlMesh, out brush.Shape, Vector3.one); var planes = new Plane[brush.Sides.Count]; var textureMatrices = new Matrix4x4[brush.Sides.Count]; var materials = new Material[brush.Sides.Count]; Debug.Log($"Brush sides {brush.Sides.Count}"); // Get planes for all sides of the brush for (int j = 0; j < brush.Sides.Count; j++) { MapBrushSide side = brush.Sides[j]; var pa = t.transform.InverseTransformPoint(new Vector3(side.Plane.P1.X, side.Plane.P1.Z, side.Plane.P1.Y) / (float)s_Scale); var pb = t.transform.InverseTransformPoint(new Vector3(side.Plane.P2.X, side.Plane.P2.Z, side.Plane.P2.Y) / (float)s_Scale); var pc = t.transform.InverseTransformPoint(new Vector3(side.Plane.P3.X, side.Plane.P3.Z, side.Plane.P3.Y) / (float)s_Scale); planes[j] = new Plane(pa, pb, pc); if (IsExcludedMaterial(side.Material)) { // polygon.UserExcludeFromFinal = true; } // detect collision-only brushes. if (IsInvisibleMaterial(side.Material)) { // pr.IsVisible = false; } // find the material in the unity project automatically. //Material material; // try finding the texture name with '*' replaced by '#' so '#teleport'. string materialName = side.Material.Replace("*", "#"); materials[j] = materialSearcher.FindMaterial(new string[] { materialName }); if (materials[j] == null) { materials[j] = CSGSettings.DefaultMaterial; // Debug.Log("RealtimeCSG: Tried to find material '" + materialName + "' but it couldn't be found in the project."); } if (valveFormat) { // calculate the texture coordinates. int w = 256; int h = 256; if (materials[j].mainTexture != null) { w = materials[j].mainTexture.width; h = materials[j].mainTexture.height; } var uAxis = new VmfAxis(side.t1, side.Offset.X, side.Scale.X); var vAxis = new VmfAxis(side.t2, side.Offset.Y, side.Scale.Y); textureMatrices[j] = CalculateTextureCoordinates(planes[j], w, h, uAxis, vAxis); } } bool controlMeshSuccess = true; if (valveFormat) { controlMeshSuccess = BrushFactory.CreateControlMeshFromPlanes(out rcsgBrush.ControlMesh, out rcsgBrush.Shape, planes, null, null, materials, textureMatrices, TextureMatrixSpace.WorldSpace); } else { controlMeshSuccess = BrushFactory.CreateControlMeshFromPlanes(out rcsgBrush.ControlMesh, out rcsgBrush.Shape, planes, null, null, materials); } if (controlMeshSuccess) { for (int j = 0; j < brush.Sides.Count; j++) { MapBrushSide side = brush.Sides[j]; // calculate the texture coordinates. int w = 32; int h = 32; if (materials[j].mainTexture != null) { w = materials[j].mainTexture.width; h = materials[j].mainTexture.height; } var tScale = new Vector2( SafeDivision((32.0f / w), side.Scale.X), SafeDivision((32.0f / h), side.Scale.Y)); if (valveFormat) { // This shouldn't be needed due to setting texture matrix rcsgBrush.Shape.TexGens[j].Scale = tScale; rcsgBrush.Shape.TexGens[j].Translation.x = SafeDivision(side.Offset.X, w); rcsgBrush.Shape.TexGens[j].Translation.y = SafeDivision(-side.Offset.Y, h); //rcsgBrush.Shape.TexGens[j].RotationAngle += 180 + side.Rotation; // Textures often need to be flipped or rotated 180 to match } else { rcsgBrush.Shape.TexGens[j].Scale = tScale; if (side.Offset.X != 0) { rcsgBrush.Shape.TexGens[j].Translation.x = side.Offset.X / Mathf.Max(w, float.Epsilon); } else { rcsgBrush.Shape.TexGens[j].Translation.x = 0; } if (side.Offset.Y != 0) { rcsgBrush.Shape.TexGens[j].Translation.y = side.Offset.Y / Mathf.Max(h, float.Epsilon); } else { rcsgBrush.Shape.TexGens[j].Translation.y = 0; } rcsgBrush.Shape.TexGens[j].RotationAngle = 180 + side.Rotation; } } } else { GameObject.DestroyImmediate(rcsgBrush.gameObject); } } } } catch (Exception) { throw; } finally { InternalCSGModelManager.CheckForChanges(); InternalCSGModelManager.UpdateMeshes(); //model.EndUpdate(); } #if UNITY_EDITOR UnityEditor.EditorUtility.ClearProgressBar(); #endif }