/// <summary> /// Imports the specified Valve Map Format file. /// </summary> /// <param name="path">The file path.</param> /// <returns>A <see cref="VmfWorld"/> containing the imported world data.</returns> public VmfWorld Import(string path) { // create a new world. VmfWorld world = new VmfWorld(); // open the file for reading. we use streams for additional performance. // it's faster than File.ReadAllLines() as that requires two iterations. using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read)) using (StreamReader reader = new StreamReader(stream)) { // read all the lines from the file. //bool inActor = false; T3dActor actor = null; //bool inBrush = false; T3dBrush brush = null; //bool inPolygon = false; T3dPolygon polygon = null; string[] closures = new string[64]; int depth = 0; string line; string previousLine = ""; bool justEnteredClosure = false; string key; object value; VmfSolid solid = null; VmfSolidSide solidSide = null; VmfSolidSideDisplacement displacement = null; VmfEntity entity = null; while (!reader.EndOfStream) { line = reader.ReadLine().Trim(); if (line.Length == 0) { continue; } // parse closures and keep track of them. if (line[0] == '{') { closures[depth] = previousLine; depth++; justEnteredClosure = true; continue; } if (line[0] == '}') { depth--; closures[depth] = null; continue; } // parse version info. if (closures[0] == "versioninfo") { if (TryParsekeyValue(line, out key, out value)) { switch (key) { case "editorversion": world.VersionInfoEditorVersion = (int)value; break; case "editorbuild": world.VersionInfoEditorBuild = (int)value; break; case "mapversion": world.VersionInfoMapVersion = (int)value; break; case "formatversion": world.VersionInfoFormatVersion = (int)value; break; case "prefab": world.VersionInfoPrefab = (int)value; break; } } } // parse view settings. if (closures[0] == "viewsettings") { if (TryParsekeyValue(line, out key, out value)) { switch (key) { case "bSnapToGrid": world.ViewSettingsSnapToGrid = (int)value; break; case "bShowGrid": world.ViewSettingsShowGrid = (int)value; break; case "bShowLogicalGrid": world.ViewSettingsShowLogicalGrid = (int)value; break; case "nGridSpacing": world.ViewSettingsGridSpacing = (int)value; break; case "bShow3DGrid": world.ViewSettingsShow3DGrid = (int)value; break; } } } // parse world properties. if (closures[0] == "world" && closures[1] == null) { if (TryParsekeyValue(line, out key, out value)) { switch (key) { case "id": world.Id = (int)value; break; case "mapversion": world.MapVersion = (int)value; break; case "classname": world.ClassName = (string)value; break; case "detailmaterial": world.DetailMaterial = (string)value; break; case "detailvbsp": world.DetailVBsp = (string)value; break; case "maxpropscreenwidth": world.MaxPropScreenWidth = (int)value; break; case "skyname": world.SkyName = (string)value; break; } } } // parse world solid. if (closures[0] == "world" && closures[1] == "solid" && closures[2] == null) { // create a new solid and add it to the world. if (justEnteredClosure) { solid = new VmfSolid(); world.Solids.Add(solid); } // parse solid properties. if (TryParsekeyValue(line, out key, out value)) { switch (key) { case "id": solid.Id = (int)value; break; } } } // parse world solid side. if (closures[0] == "world" && closures[1] == "solid" && closures[2] == "side" && closures[3] == null) { // create a new solid side and add it to the solid. if (justEnteredClosure) { solidSide = new VmfSolidSide(); solid.Sides.Add(solidSide); } // parse solid side properties. if (TryParsekeyValue(line, out key, out value)) { switch (key) { case "id": solidSide.Id = (int)value; break; case "plane": solidSide.Plane = (VmfPlane)value; break; case "material": solidSide.Material = (string)value; break; //case "rotation": solidSide.Rotation = (float)value; break; case "uaxis": solidSide.UAxis = (VmfAxis)value; break; case "vaxis": solidSide.VAxis = (VmfAxis)value; break; case "lightmapscale": solidSide.LightmapScale = (int)value; break; case "smoothing_groups": solidSide.SmoothingGroups = (int)value; break; } } } // parse world solid side displacement. if (closures[0] == "world" && closures[1] == "solid" && closures[2] == "side" && closures[3] == "dispinfo" && closures[4] == null) { // create a new solid side displacement and add it to the solid side. if (justEnteredClosure) { displacement = new VmfSolidSideDisplacement(); solidSide.Displacement = displacement; } // parse displacement properties. if (TryParsekeyValue(line, out key, out value)) { switch (key) { case "power": displacement.Power = (int)value; break; case "startposition": displacement.StartPosition = (VmfVector3)value; break; case "elevation": displacement.Elevation = Convert.ToSingle(value); break; case "subdiv": displacement.Subdivide = (int)value; break; } } } // parse world solid side displacement normals. if (closures[0] == "world" && closures[1] == "solid" && closures[2] == "side" && closures[3] == "dispinfo" && closures[4] == "normals" && closures[5] == null) { // parse displacement vector rows. if (TryParseVectorRow(line, out key, out List <VmfVector3> normals)) { displacement.Normals.Add(normals); } } // parse world solid side displacement distances. if (closures[0] == "world" && closures[1] == "solid" && closures[2] == "side" && closures[3] == "dispinfo" && closures[4] == "distances" && closures[5] == null) { // parse displacement float rows. if (TryParseFloatRow(line, out key, out List <float> distances)) { displacement.Distances.Add(distances); } } // parse world solid side displacement offsets. if (closures[0] == "world" && closures[1] == "solid" && closures[2] == "side" && closures[3] == "dispinfo" && closures[4] == "offsets" && closures[5] == null) { // parse displacement vector rows. if (TryParseVectorRow(line, out key, out List <VmfVector3> offsets)) { displacement.Offsets.Add(offsets); } } // parse world solid side displacement offset normals. if (closures[0] == "world" && closures[1] == "solid" && closures[2] == "side" && closures[3] == "dispinfo" && closures[4] == "offset_normals" && closures[5] == null) { // parse displacement vector rows. if (TryParseVectorRow(line, out key, out List <VmfVector3> offsetnormals)) { displacement.OffsetNormals.Add(offsetnormals); } } // parse entity. if (closures[0] == "entity" && closures[1] == null) { // create a new entity and add it to the world. if (justEnteredClosure) { entity = new VmfEntity(); world.Entities.Add(entity); } // parse entity properties. if (TryParsekeyValue(line, out key, out value)) { switch (key) { case "id": entity.Id = (int)value; break; case "classname": entity.ClassName = (string)value; break; default: entity.Properties[key] = value; break; } } } // parse entity solid. if (closures[0] == "entity" && closures[1] == "solid" && closures[2] == null) { // create a new solid and add it to the entity. if (justEnteredClosure) { solid = new VmfSolid(); entity.Solids.Add(solid); } // parse solid properties. if (TryParsekeyValue(line, out key, out value)) { switch (key) { case "id": solid.Id = (int)value; break; } } } // parse entity solid side. if (closures[0] == "entity" && closures[1] == "solid" && closures[2] == "side" && closures[3] == null) { // create a new solid side and add it to the solid. if (justEnteredClosure) { solidSide = new VmfSolidSide(); solid.Sides.Add(solidSide); } // parse solid side properties. if (TryParsekeyValue(line, out key, out value)) { switch (key) { case "id": solidSide.Id = (int)value; break; case "plane": solidSide.Plane = (VmfPlane)value; break; case "material": solidSide.Material = (string)value; break; //case "rotation": solidSide.Rotation = (float)value; break; case "uaxis": solidSide.UAxis = (VmfAxis)value; break; case "vaxis": solidSide.VAxis = (VmfAxis)value; break; case "lightmapscale": solidSide.LightmapScale = (int)value; break; case "smoothing_groups": solidSide.SmoothingGroups = (int)value; break; } } } previousLine = line; justEnteredClosure = false; } } return(world); }
/// <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; } } }