/// <summary> /// Imports the specified Quake 1 Map Format file. /// </summary> /// <param name="path">The file path.</param> /// <returns>A <see cref="MapWorld"/> containing the imported world data.</returns> public MapWorld Import(string path) { // create a new world. MapWorld world = new MapWorld(); world.mapName = Path.GetFileNameWithoutExtension(path); // 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. int depth = 0; string line; bool justEnteredClosure = false; bool valveFormat = false; string key; object value; MapBrush brush = null; MapEntity entity = null; while (!reader.EndOfStream) { line = reader.ReadLine().Trim(); //UnityEngine.Debug.Log("Line = " + line); if (line.Length == 0) { continue; } // skip comments. if (line[0] == '/') { continue; } // parse closures and keep track of them. if (line[0] == '{') { depth++; justEnteredClosure = true; //UnityEngine.Debug.Log($"Entered Closure Depth = {depth}"); continue; } if (line[0] == '}') { depth--; //UnityEngine.Debug.Log($"Exited Closure Depth = {depth}"); continue; } // parse entity. if (depth == 1) { // create a new entity and add it to the world. if (justEnteredClosure) { entity = new MapEntity(); world.Entities.Add(entity); } // parse entity properties. if (TryParsekeyValue(line, out key, out value)) { switch (key) { case "mapversion": var version = (int)value; if (version == 220) { valveFormat = true; world.valveFormat = adjustTexturesForValve; } //UnityEngine.Debug.Log($"mapversion = {version}"); break; case "classname": entity.ClassName = (string)value; //UnityEngine.Debug.Log($"Classname = {entity.ClassName}"); break; case "_tb_type": entity.tbType = (string)value; break; case "_tb_name": entity.tbName = (string)value; break; case "_tb_id": entity.tbId = (int)value; break; case "_tb_layer": entity.tbLayer = (int)value; break; case "_tb_layer_sort_index": entity.tbLayerSortIndex = (int)value; break; case "_tb_group": entity.tbGroup = (int)value; break; } } } // parse entity brush. if (depth == 2) { // create a new brush and add it to the entity. if (justEnteredClosure) { brush = new MapBrush(); entity.Brushes.Add(brush); } // parse brush sides. MapBrushSide mapBrushSide; if (TryParseBrushSide(line, out mapBrushSide, valveFormat)) { brush.Sides.Add(mapBrushSide); } } justEnteredClosure = false; } } return(world); }
/// <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 }