Billboarded Cutaway Visualization for Subsurface Urban Sewer Networks
Flood Simulation embedded in Unity3D
SewerManager.cs
/* Copyright (C) Manuel Matusich * Unauthorized copying of this file, via any medium is strictly prohibited * Written by Manuel Matusich, Juli 2017 */ using UnityEngine; using System; using System.IO; using System.Xml; using System.Collections.Generic; public class SewerManager: MonoBehaviour { public Material terrainMaterial; public Material groundZeroMaterial; public Material manholeMaterial; public Material shaftMaterial; public Material shaftInsideMaterial; public Material shaftPipeCancelMaterial; public Material shaftWaterMaterial; public Material buildingsMaterial; public Texture DTglobalTex; //for more details look into the method refineNodes private static int removedNodes = 0; private static int addedNodes = 0; private static int pipeNodesCounter = 0; private static int maxThreads = 20; //Timestep for simulation data public static int currenTimestep = 0; private int UpdateCounterPipeNodeID = 0; private bool computeDTtex = true; private data[] dataForPipeNodeBuffer; private GameObject mainCam; private Texture2D DTglobalTex_dt; private ComputeBuffer pipeNodesVec3ReadBuffer; private ComputeBuffer RWpipeNodesUVBuffer; private SurroundingsCommands surroundingsCommands; private Transform initialCamPos; struct data { public Vector3 pos; public int pipeID; public int nodeID; }; struct uvData { public Vector2 uvPos; public int pipeID; public int nodeID; }; //Awake will be called first void Awake() { //while parsing, all data will be added to the static class SewerSystem string sewerDataPath = Application.dataPath + "/Worringen Visdom Exports/sewer network/21_global/Sewer Network"; //get the number of lines from the xml description document int nOflinkLines = getNumOfLines(sewerDataPath + "/dataDescription.xml", "links"); int nOfnodeLines = getNumOfLines(sewerDataPath + "/dataDescription.xml", "nodes"); //pass number of lines and read binary files parseShafts(sewerDataPath + "/nodes/lines.bin", nOfnodeLines); parseLinks(sewerDataPath + "/links/lines.bin", nOflinkLines); //needs to be called after parsing shafts and pipes and sets references between pipes and shafts parseNodeIndicesPerLink(sewerDataPath + "/nodeIndicesPerLink/data.bin", nOflinkLines); //pipe radius will be broadcastet to connected pipes as well parselinkRadii(sewerDataPath + "/linkRadii/data.bin", nOflinkLines); float parseStartTime = Time.realtimeSinceStartup; // Parsing simulation data is threaded since this would take pretty long otherwise /************ NODE DATA ************/ List < ParseJob > parseJobs = new List < ParseJob > (); //create a job for each timestep for (int timestep = 0; timestep <= SewerSystem.maxSimulationTimeStep; timestep += 10) { ParseJob myJob = new ParseJob(nOfnodeLines, timestep, "Node"); parseJobs.Add(myJob); } ParseJobManager parseJobManager = new ParseJobManager(maxThreads, parseJobs); while (!parseJobManager.update()) { /*wait until all jobs are finished*/ } //assign data from parse jobs foreach(ParseJob job in parseJobs) { int timestep = job.timestep / 10; for (int i = 0; i < job.numLines; i++) { SewerSystem.getShaftbyIndex(i).setDepthForTimestep(job.depthDataOut[i], timestep); SewerSystem.getShaftbyIndex(i).setDepthColorForTimestep(job.depthColorDataOut[i], timestep); SewerSystem.getShaftbyIndex(i).setDischargeColorForTimestep(job.dischargeColorDataOut[i], timestep); } } /************ LINK DATA COLORS ************/ parseJobs.Clear(); //create a job for each timestep for (int timestep = 0; timestep <= SewerSystem.maxSimulationTimeStep; timestep += 10) { ParseJob myJob = new ParseJob(nOflinkLines, timestep, "Link"); parseJobs.Add(myJob); } parseJobManager = new ParseJobManager(maxThreads, parseJobs); while (!parseJobManager.update()) { /*wait until all jobs are finished*/ } //assign data from parse jobs foreach(ParseJob job in parseJobs) { int timestep = job.timestep / 10; for (int i = 0; i < job.numLines; i++) { SewerSystem.getPipebyIndex(i).setDepthForTimestep(job.depthDataOut[i], timestep); SewerSystem.getPipebyIndex(i).setDepthColorForTimestep(job.depthColorDataOut[i], timestep); SewerSystem.getPipebyIndex(i).setDischargeColorForTimestep(job.dischargeColorDataOut[i], timestep); } } parseJobs.Clear(); Debug.Log("ParseTime: " + (Time.realtimeSinceStartup - parseStartTime)); foreach(Pipe pipe in SewerSystem.getPipes()) { List < Vector3 > refinedPipeNodes = refineNodes(pipe.getNodes()); pipe.addAllNodes(refinedPipeNodes); } Debug.Log("removed PipeNodes: " + removedNodes); Debug.Log("added PipeNodes: " + addedNodes); Debug.Log("PipeNodes: " + pipeNodesCounter); SewerSystem.intializeAfterParsing(); } //Start will be called after Awake or when the GameObject, //this script is attached to, is activated void Start() { //keyboard input commands for surroundings GameObject surroundings = GameObject.FindGameObjectWithTag("surroundings"); surroundingsCommands = surroundings.GetComponent < SurroundingsCommands > (); mainCam = Camera.main.gameObject; initialCamPos = mainCam.transform; setRenderQueues(); Texture2D textureDT = LoadPNG(Application.dataPath + "/../AfterDT.png"); // in case there is no .png file with the distance to pipe information stored it has to be computed and stored if (textureDT == null) { // 12 Bytes for float3, 8 for float2 .... plus 4 byte per int (two integers) computePipeNodesForBuffer(); pipeNodesVec3ReadBuffer = new ComputeBuffer(SewerSystem.getNumberOfPipeNodes(), sizeof(float) * 3 + sizeof(int) * 2); pipeNodesVec3ReadBuffer.SetData(dataForPipeNodeBuffer); Shader.SetGlobalBuffer("pipeNodesVec3ReadBuffer", pipeNodesVec3ReadBuffer); RWpipeNodesUVBuffer = new ComputeBuffer(1, sizeof(float) * 2 + sizeof(int) * 2); Shader.SetGlobalBuffer("RWpipeNodesUVBuffer", RWpipeNodesUVBuffer); Graphics.SetRandomWriteTarget(1, RWpipeNodesUVBuffer, true); Shader.SetGlobalInt("scanTexCoords", 1); //set to true Shader.SetGlobalTexture("DTglobalTex", DTglobalTex); surroundingsCommands.setOnlyTerrainActive(); SewerSystem.setActive(false); } else { computeDTtex = false; Shader.SetGlobalTexture("DTglobalTex", textureDT); } GameObject[] groundZero = GameObject.FindGameObjectsWithTag("terrainzero"); foreach(GameObject go in groundZero) { go.AddComponent < FlattenTerrain > (); go.BroadcastMessage("flatten"); } } public Texture2D LoadPNG(string filePath) { Texture2D tex = null; byte[] fileData; if (File.Exists(filePath)) { fileData = File.ReadAllBytes(filePath); tex = new Texture2D(2, 2); //will auto-resize the texture dimensions. tex.LoadImage(fileData); } return tex; } private void distanceTransform() { DTglobalTex_dt = DistanceTransform.transform(DTglobalTex as Texture2D); byte[] bytes = DTglobalTex_dt.EncodeToPNG(); File.WriteAllBytes(Application.dataPath + "/../AfterDT.png", bytes); Shader.SetGlobalTexture("DTglobalTex", DTglobalTex_dt); } private void computePipeNodesForBuffer() { List < Pipe > allPipes = SewerSystem.getPipes(); List < data > nodesData = new List < data > (); foreach(Pipe pipe in allPipes) { Vector3[] nodes = pipe.getNodes().ToArray(); for (int i = 0; i < (nodes.Length); i++) { data data = new data(); data.pos = nodes[i]; data.pipeID = pipe.id; data.nodeID = i; nodesData.Add(data); } } dataForPipeNodeBuffer = nodesData.ToArray(); } private void setRenderQueues() { GameObject[] terrain = GameObject.FindGameObjectsWithTag("terrain"); GameObject[] terrainZero = GameObject.FindGameObjectsWithTag("terrainzero"); GameObject[] buildings = GameObject.FindGameObjectsWithTag("buildings"); GameObject[] trees = GameObject.FindGameObjectsWithTag("tree"); GameObject water = GameObject.FindGameObjectWithTag("water"); water.GetComponent < Renderer > ().sharedMaterial.renderQueue = 3001; foreach(GameObject go in terrainZero) { groundZeroMaterial.renderQueue = 2005; go.GetComponent < Renderer > ().sharedMaterial = groundZeroMaterial; go.GetComponent < Renderer > ().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } foreach(GameObject go in terrain) { terrainMaterial.renderQueue = 3000; go.GetComponent < Renderer > ().sharedMaterial = terrainMaterial; go.GetComponent < Renderer > ().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } shaftMaterial.renderQueue = 2013; shaftInsideMaterial.renderQueue = 2013; manholeMaterial.renderQueue = 2014; shaftPipeCancelMaterial.renderQueue = 2016; shaftWaterMaterial.renderQueue = 2015; foreach(GameObject go in buildings) { go.GetComponent < Renderer > ().sharedMaterial = buildingsMaterial; go.GetComponent < Renderer > ().sharedMaterial.renderQueue = 2018; go.GetComponent < Renderer > ().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } foreach(GameObject go in trees) { go.GetComponent < Renderer > ().sharedMaterial.renderQueue = 2020; } } // Update is called once per frame void Update() { if (computeDTtex) { if (UpdateCounterPipeNodeID < dataForPipeNodeBuffer.Length) { // SETUP FOR SNAPSHOT Shader.SetGlobalInt("UpdateCounterPipeNodeID", UpdateCounterPipeNodeID); Vector3 vec = dataForPipeNodeBuffer[UpdateCounterPipeNodeID++].pos; vec.y = 40; mainCam.transform.position = vec; mainCam.transform.LookAt(vec + Vector3.down); // READ uvData[] uvDatas = new uvData[1]; RWpipeNodesUVBuffer.GetData(uvDatas); uvData uv = uvDatas[0]; // SET if (uv.pipeID != 0) { //Debug.Log("looking for pipeID: " + uv.pipeID); Pipe pipe = SewerSystem.getPipeByID(uv.pipeID); if (pipe != null) pipe.setUV(uv.uvPos, uv.nodeID); } } else if (UpdateCounterPipeNodeID == dataForPipeNodeBuffer.Length) { Shader.SetGlobalInt("scanTexCoords", 0); //set to false distanceTransform(); surroundingsCommands.setAllActive(); SewerSystem.setActive(true); UpdateCounterPipeNodeID++; computeDTtex = false; mainCam.transform.position = initialCamPos.position; } } } // Fixed timestep of 0.05 seconds void FixedUpdate() { currenTimestep++; if (currenTimestep >= SewerSystem.simulationSteps) { currenTimestep = 0; } foreach(Shaft shaft in SewerSystem.getShafts()) { // used to update water levels in shaft shaft.setTimeStep(currenTimestep); } } // returns how many lines the binary files have // which is defined in xml static int getNumOfLines(string path, string dataType) { // Create an XML reader for this file. using(XmlReader reader = XmlReader.Create(path)) { bool match = false; while (reader.Read()) { // Only detect start elements. if (reader.IsStartElement()) { if (reader.Name == "name") { // check if this is the element we are looking for if (reader.ReadInnerXml() == dataType) { match = true; } } // we found a match if (reader.Name == "numLines" && match) { return Int32.Parse(reader.ReadInnerXml()); } } } //in case nothing was found -1 will skip the parsing algorithm Debug.LogError("Failed to read the desired string in XML, the file may be missing."); return - 1; } } void parseShafts(string path, int numLines) { // Read the data. using(BinaryReader bReader = new BinaryReader(File.Open(path, FileMode.Open))) { //Parse for each single Line, always 2 positions for (int index = 0; index < numLines; index++) { // The first element is the id of the line int id = bReader.ReadInt32(); // The second element is the number of positions in the line, shoudl be 2 here int numPositions = bReader.ReadInt32(); if (numPositions != 2) Debug.LogError("SHAFT numPositions != 2 ERROR"); //first position is the lower one Shaft shaft = new Shaft(id, this.gameObject, shaftMaterial, shaftInsideMaterial, manholeMaterial, shaftWaterMaterial, shaftPipeCancelMaterial); Transform dummyTransform = new GameObject().transform; // Three doubles per position (x,y,z) times number of positions to read float x = (float) bReader.ReadDouble(); float y = (float) bReader.ReadDouble(); float z = (float) bReader.ReadDouble(); //for some reason y needs to be inverted to make it fit properly in unity y *= -1; //and some rotations are needed to make it fit to the terrain dummyTransform.position = new Vector3(x, y, z); dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(1f, 0f, 0f), -90f); dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(0f, 1f, 0f), 180f); shaft.setLower(dummyTransform.position); // Three doubles per position (x,y,z) times number of positions to read x = (float) bReader.ReadDouble(); y = (float) bReader.ReadDouble(); z = (float) bReader.ReadDouble(); //for some reason y needs to be inverted to make it fit properly in unity y *= -1; //and some rotations are needed to make it fit to the terrain dummyTransform.position = new Vector3(x, y, z); dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(1f, 0f, 0f), -90f); dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(0f, 1f, 0f), 180f); shaft.setUpper(dummyTransform.position); Destroy(dummyTransform.gameObject); SewerSystem.addShaft(shaft); } } } void parseLinks(string path, int numLines) { // Read the data. using(BinaryReader bReader = new BinaryReader(File.Open(path, FileMode.Open))) { //Parse for each single Line, there might be >= 2 positions per line for (int i = 0; i < numLines; i++) { // The first element is the id of the line int id = bReader.ReadInt32(); // The second element is the number of positions in the line int numPositions = bReader.ReadInt32(); Pipe pipe = new Pipe(id, this.gameObject); List < Vector3 > pipeNodes = new List < Vector3 > (); // Three doubles per position (x,y,z) times number of positions to read for (int j = 0; j < numPositions; j++) { float x = (float) bReader.ReadDouble(); float y = (float) bReader.ReadDouble(); float z = (float) bReader.ReadDouble(); //for some reason y needs to be inverted to make it fit properly in unity y *= -1; //and some rotations are needed to make it fit to the terrain Transform dummyTransform = new GameObject().transform; dummyTransform.position = new Vector3(x, y, z); dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(1f, 0f, 0f), -90f); dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(0f, 1f, 0f), 180f); //finally pass the node positions to pipe class pipeNodes.Add(dummyTransform.position); Destroy(dummyTransform.gameObject); } //remove or add points without interfering with semantic structure pipe.addAllNodes(pipeNodes); // and add the pipe to the sewer system class SewerSystem.addPipe(pipe); } } } // this method will remove unnecessary nodes // and add those wich are needed for the cutaways of the pipes private List < Vector3 > refineNodes(List < Vector3 > nodes) { if (nodes.Count > 2) { int prev = 0; int current = 1; int next = 2; //remove nodes while (next < nodes.Count && prev < current && current < next) { Vector3 directionBig = nodes[next] - nodes[prev]; Vector3 directionSmall = nodes[current] - nodes[prev]; float dot = Vector3.Dot(directionBig.normalized, directionSmall.normalized); if (dot > 0.999f || dot < 0.6f) { nodes.RemoveAt(current); removedNodes++; } else { next++; current++; prev++; } } } //add nodes for cut if ((nodes[nodes.Count - 1] - nodes[0]).magnitude > 7) { if (nodes.Count == 2) { Vector3 direction = nodes[1] - nodes[0]; Vector3 tempLast = nodes[1]; //save last nodes.Insert(1, nodes[0] + direction.normalized * direction.magnitude / 4); nodes.Insert(2, tempLast - direction.normalized * direction.magnitude / 4); addedNodes += 2; } else if (nodes.Count == 3) { Vector3 firstDirection = (nodes[1] - nodes[0]); Vector3 secondDirection = (nodes[2] - nodes[1]); nodes.Insert(1, nodes[0] + firstDirection.normalized * firstDirection.magnitude / 2); nodes.Insert(3, nodes[2] + secondDirection.normalized * secondDirection.magnitude / 2); } else if (nodes.Count >= 4) { Vector3 firstDirection = (nodes[1] - nodes[0]); if (firstDirection.magnitude > 3) { nodes.Insert(1, nodes[0] + firstDirection.normalized * firstDirection.magnitude / 2); } Vector3 secondDirection = (nodes[nodes.Count - 1] - nodes[nodes.Count - 2]); if (secondDirection.magnitude > 3) { nodes.Insert(nodes.Count - 1, nodes[nodes.Count - 1] - secondDirection.normalized * secondDirection.magnitude / 2); } } } pipeNodesCounter += nodes.Count; return nodes; } void parseNodeIndicesPerLink(string path, int numLines) { // Read the data. using(BinaryReader bReader = new BinaryReader(File.Open(path, FileMode.Open))) { //Parse for each single Line // add startShaft and endShaft to its Pipes and add Pipes connected to a shaft to this shaft as well for (int i = 0; i < numLines; i++) { //indices are in the same order as the pipes where parsed with (hopefully), same for shafts Pipe pipe = SewerSystem.getPipebyIndex(i); //index of the first shaft the pipe is connected to int startIndex = bReader.ReadInt32(); Shaft shaft = SewerSystem.getShaftbyIndex(startIndex); pipe.setStartShaft(shaft); shaft.addConnectedPipe(pipe); //index of the second shaft the pipe is connected to int endIndex = bReader.ReadInt32(); shaft = SewerSystem.getShaftbyIndex(endIndex); pipe.setEndShaft(shaft); shaft.addConnectedPipe(pipe); } } } void parselinkRadii(string path, int numLines) { // Read the data. using(BinaryReader bReader = new BinaryReader(File.Open(path, FileMode.Open))) { // parse for each single for a pipes radius for (int i = 0; i < numLines; i++) { Pipe pipe = SewerSystem.getPipebyIndex(i); //indices are in the same order as the pipes where parsed with (hopefully) pipe.radius = bReader.ReadSingle(); pipe.broadcastRadiusToShafts(); } } } void OnDestroy() { if (pipeNodesVec3ReadBuffer != null) pipeNodesVec3ReadBuffer.Release(); pipeNodesVec3ReadBuffer = null; if (RWpipeNodesUVBuffer != null) RWpipeNodesUVBuffer.Release(); RWpipeNodesUVBuffer = null; } }
SewerSystem.cs
/* Copyright (C) Manuel Matusich * Unauthorized copying of this file, via any medium is strictly prohibited * Written by Manuel Matusich, Juli 2017 */ using System.Collections.Generic; using UnityEngine; public static class SewerSystem { private static List < Pipe > pipes = new List < Pipe > (); private static List < Shaft > shafts = new List < Shaft > (); private static int numberOfPipeNodes = 0; private static int minNumberOfNodesPerPipe = int.MaxValue; public static int maxSimulationTimeStep = 7200; public static int simulationSteps = 721; public static void setActive(bool active) { foreach(Pipe pipe in pipes) { pipe.setActive(active); } foreach(Shaft shaft in shafts) { shaft.setActive(active); } } public static int getNumberOfPipeNodes() { if (numberOfPipeNodes == 0) { foreach(Pipe pipe in pipes) { numberOfPipeNodes += pipe.getNodeCount(); if (pipe.getNodeCount() < minNumberOfNodesPerPipe) minNumberOfNodesPerPipe = pipe.getNodeCount(); } } return numberOfPipeNodes; } public static void addShaft(Shaft shaft) { shafts.Add(shaft); } public static void addPipe(Pipe pipe) { pipes.Add(pipe); } public static List < Pipe > getPipes() { return pipes; } public static List < Shaft > getShafts() { return shafts; } public static Shaft getShaftbyIndex(int index) { return shafts[index]; } public static Pipe getPipebyIndex(int index) { return pipes[index]; } public static Pipe getPipeByID(int id) { foreach(Pipe pipe in pipes) { if (pipe.id == id) { return pipe; } } return null; } //sort pipes by height, to ensure the are drawn in this order by geometryshader private static void sortPipesByHeight() { pipes.Sort(delegate(Pipe a, Pipe b) { return (b.position.y).CompareTo(a.position.y); }); } public static void intializeAfterParsing() { foreach(Pipe pipe in pipes) { pipe.initializAfterParsing(); } sortPipesByHeight(); foreach(Shaft shaft in shafts) { shaft.createShaft(); shaft.createWater(); } } }
Pipe.cs
/* Copyright (C) Manuel Matusich * Unauthorized copying of this file, via any medium is strictly prohibited * Written by Manuel Matusich, Juli 2017 */ using UnityEngine; using System.Collections.Generic; using System.Collections; public class Pipe { private GameObject pipeGameObject; private List < Vector3 > pipeNodes; private Vector2[] UVs = null; private Shaft startShaft; private Shaft endShaft; public Vector3 position = Vector3.one; public float radius = 0; public int id; public float[] depthTimeline; public Color[] depthColorTimeline; public Color[] dischargeColorTimeline; public Pipe(int id, GameObject parent) { this.id = id; pipeNodes = new List < Vector3 > (); depthTimeline = new float[SewerSystem.simulationSteps]; depthColorTimeline = new Color[SewerSystem.simulationSteps]; dischargeColorTimeline = new Color[SewerSystem.simulationSteps]; pipeGameObject = new GameObject(); pipeGameObject.name = "Pipe ID: " + this.id; pipeGameObject.transform.SetParent(parent.transform); pipeGameObject.tag = "pipe"; } public void setDepthForTimestep(float depth, int timestep) { depthTimeline[timestep] = depth; } public void setDepthColorForTimestep(Color color, int timestep) { depthColorTimeline[timestep] = color; } public void setDischargeColorForTimestep(Color color, int timestep) { dischargeColorTimeline[timestep] = color; } public void broadcastRadiusToShafts() { startShaft.increaseWidth(radius); endShaft.increaseWidth(radius); } private void initiateUVs() { if (UVs == null) UVs = new Vector2[pipeNodes.Count]; } public void setUV(Vector2 uv, int nodeID) { initiateUVs(); this.UVs[nodeID] = uv; } public Vector2[] getUVs() { return this.UVs; } public void setActive(bool active) { pipeGameObject.SetActive(active); } public void initializAfterParsing() { swapShaftsIfNeeded(); position.x = (pipeNodes[0].x + pipeNodes[pipeNodes.Count - 1].x) / 2; position.y = (pipeNodes[0].y + pipeNodes[pipeNodes.Count - 1].y) / 2; position.z = (pipeNodes[0].z + pipeNodes[pipeNodes.Count - 1].z) / 2; } public void setStartShaft(Shaft shaft) { startShaft = shaft; } public void setEndShaft(Shaft shaft) { endShaft = shaft; } public float getHeightOfPipeEndToConnectedShaft(Shaft shaft) { if (shaft.getID() == startShaft.getID()) { return pipeNodes[0].y; } else if (shaft.getID() == endShaft.getID()) { return pipeNodes[pipeNodes.Count - 1].y; } else { Debug.Log("shaft error: given shaft is neither endshaft or startshaft"); return - 1000000; } } //Make shure that start shaft is closer to index 0 of pipeNodes public void swapShaftsIfNeeded() { float distStart = Vector3.Distance(startShaft.getPos(), pipeNodes[0]); float distEnd = Vector3.Distance(endShaft.getPos(), pipeNodes[0]); if (distStart > distEnd) { Shaft temp = startShaft; startShaft = endShaft; endShaft = temp; } } public List < Vector3 > getNodes() { return pipeNodes; } public void addNode(Vector3 node) { pipeNodes.Add(node); } public void addAllNodes(List < Vector3 > nodes) { pipeNodes = nodes; } public int getNodeCount() { return pipeNodes.Count; } }
Shaft.cs
/* Copyright (C) Manuel Matusich * Unauthorized copying of this file, via any medium is strictly prohibited * Written by Manuel Matusich, Juli 2017 */ using UnityEngine; using System.Collections.Generic; public class Shaft { static private float wallThickness = 0.1f; static private float waterOffset = 0.01f; private GameObject shaft; private GameObject water; private GameObject cylinder; private GameObject cylinderInside; private GameObject manhole; private GameObject pipeCancel; private Material shaftMaterial; private Material manholeMaterial; private Material waterMaterial; private Material shaftInsideMaterial; private Material pipeCancelMaterial; private Vector3 upperPoint; private Vector3 lowerPoint; private float[] depthTimeline; private Color[] depthColorTimeline; private Color[] dischargeColorTimeline; private List < Pipe > connectedPipes; private int id; private float width; private float waterFillPercent; public Shaft(int id, GameObject parent, Material shaftMaterial, Material shaftInsideMaterial, Material manholeMaterial, Material waterMaterial, Material pipeCancelMaterial) { this.id = id; depthTimeline = new float[SewerSystem.simulationSteps]; depthColorTimeline = new Color[SewerSystem.simulationSteps]; dischargeColorTimeline = new Color[SewerSystem.simulationSteps]; shaft = new GameObject(); shaft.transform.SetParent(parent.transform); shaft.name = "Shaft ID: " + this.id; this.shaftMaterial = shaftMaterial; this.manholeMaterial = manholeMaterial; this.waterMaterial = waterMaterial; this.shaftInsideMaterial = shaftInsideMaterial; this.pipeCancelMaterial = pipeCancelMaterial; this.width = 0.7f; upperPoint = new Vector3(); lowerPoint = new Vector3(); connectedPipes = new List < Pipe > (); cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder); cylinder.transform.SetParent(shaft.transform); cylinder.name = "Shaft Cylinder"; cylinder.tag = "shaft"; pipeCancel = GameObject.CreatePrimitive(PrimitiveType.Cylinder); pipeCancel.transform.SetParent(shaft.transform); pipeCancel.name = "Shaft Pipe Cancel"; pipeCancel.tag = "shaft"; cylinderInside = GameObject.CreatePrimitive(PrimitiveType.Cylinder); cylinderInside.transform.SetParent(shaft.transform); cylinderInside.name = "Shaft Cylinder Inside"; cylinderInside.tag = "shaft"; water = GameObject.CreatePrimitive(PrimitiveType.Cylinder); water.transform.SetParent(shaft.transform); water.name = "Water"; waterFillPercent = Random.Range(0.0f, 1.0f); manhole = GameObject.CreatePrimitive(PrimitiveType.Cylinder); manhole.transform.SetParent(cylinder.transform); manhole.name = "Manhole"; } public int getID() { return this.id; } public void setDepthForTimestep(float depth, int timestep) { depthTimeline[timestep] = depth; } public void setDepthColorForTimestep(Color color, int timestep) { depthColorTimeline[timestep] = color; } public void setDischargeColorForTimestep(Color color, int timestep) { dischargeColorTimeline[timestep] = color; } public void increaseWidth(float radius) { if (radius * 2.5f > width) width = radius * 2.5f; } // called at the end of start when all is set up public void createShaft() { Vector3 offset = lowerPoint - upperPoint; Vector3 scale = new Vector3(width, offset.magnitude / 2.0f, width); cylinder.transform.position = upperPoint + new Vector3(offset.x / 2.0f, offset.y / 2.0f, offset.z / 2.0f); cylinder.GetComponent < MeshRenderer > ().sharedMaterial = shaftMaterial; cylinder.transform.up = offset; cylinder.transform.localScale = scale; cylinder.isStatic = true; pipeCancel.transform.position = upperPoint + new Vector3(offset.x / 2.0f, offset.y / 2.0f, offset.z / 2.0f); pipeCancel.GetComponent < MeshRenderer > ().sharedMaterial = pipeCancelMaterial; pipeCancel.transform.up = offset; pipeCancel.transform.localScale = scale; pipeCancel.isStatic = true; manhole.transform.position = upperPoint + new Vector3(0, 0.05f, 0); manhole.GetComponent < MeshRenderer > ().sharedMaterial = manholeMaterial; manhole.transform.localScale = new Vector3(0.9f, 0.02f, 0.9f); manhole.isStatic = true; float widthModifier = 1 - wallThickness; Vector3 upperPointInner = upperPoint + Vector3.down * waterOffset / 2; Vector3 lowerPointInner = lowerPoint + Vector3.up * wallThickness / 2; offset = lowerPointInner - upperPointInner; scale = new Vector3(width * widthModifier, offset.magnitude / 2.0f, width * widthModifier); cylinderInside.transform.position = upperPointInner + new Vector3(offset.x / 2.0f, offset.y / 2.0f, offset.z / 2.0f); cylinderInside.GetComponent < MeshRenderer > ().sharedMaterial = shaftInsideMaterial; cylinderInside.transform.up = offset; cylinderInside.transform.localScale = scale; cylinderInside.isStatic = true; } public void createWater() { Vector3 waterMax = upperPoint + Vector3.down * (wallThickness / 2 + waterOffset); Vector3 waterMin = lowerPoint + Vector3.up * (wallThickness / 2 + waterOffset); float widthModifier = 1 - wallThickness - waterOffset * 10; //float maxDist = Vector3.Distance(waterMin, waterMax); Vector3 offset = (waterMin - waterMax); Vector3 scale = new Vector3(width * widthModifier, offset.magnitude / 2.0f, width * widthModifier); water.transform.position = waterMax + new Vector3(offset.x / 2.0f, offset.y / 2.0f, offset.z / 2.0f); //GameObject cylinder = (GameObject)UnityEngine.Object.Instantiate(cylinderPrefab, position, Quaternion.identity); water.GetComponent < MeshRenderer > ().sharedMaterial = waterMaterial; water.transform.up = offset; water.transform.localScale = scale; } public void setTimeStep(int timestep) { float waterHeight = depthTimeline[timestep]; float minHeight = 100000000.0f; //move this to initialzation foreach(Pipe pipe in connectedPipes) { float height = pipe.getHeightOfPipeEndToConnectedShaft(this); if (height < minHeight) minHeight = height; } if (minHeight < lowerPoint.y) minHeight = lowerPoint.y; waterFillPercent = ((minHeight - lowerPoint.y) + waterHeight) / Vector3.Distance(upperPoint, lowerPoint); if (waterFillPercent > 1) waterFillPercent = 1.0f; waterMaterial.SetColor("_Color", depthColorTimeline[timestep]); rescaleWater(); } public void rescaleWater() { Vector3 waterMax = upperPoint + Vector3.down * (wallThickness / 2 + waterOffset); Vector3 waterMin = lowerPoint + Vector3.up * (wallThickness / 2 + waterOffset); float widthModifier = 1 - wallThickness - waterOffset * 10; float maxDist = Vector3.Distance(waterMin, waterMax); Vector3 waterLevel = waterMin + Vector3.up * maxDist * waterFillPercent; Vector3 offset = (waterMin - waterLevel); Vector3 scale = new Vector3(width * widthModifier, offset.magnitude / 2.0f, width * widthModifier); water.transform.position = waterLevel + new Vector3(offset.x / 2.0f, offset.y / 2.0f, offset.z / 2.0f); water.transform.up = offset; water.transform.localScale = scale; } public void setActive(bool active) { shaft.SetActive(active); } public void addConnectedPipe(Pipe pipe) { if (!connectedPipes.Contains(pipe)) connectedPipes.Add(pipe); } public void setLower(Vector3 lower) { lowerPoint = lower + Vector3.down; } //setUpper must be called after setLower for each shaft respectively public void setUpper(Vector3 upper) { if (upper.y < lowerPoint.y && lowerPoint.y != 0) { upperPoint = lowerPoint; lowerPoint = upper; } else { upperPoint = upper; } } public Vector3 getPos() { Vector3 position = new Vector3(0f, 0f, 0f); position.x = (lowerPoint.x + upperPoint.x) / 2; position.y = (lowerPoint.y + upperPoint.y) / 2; position.z = (lowerPoint.z + upperPoint.z) / 2; return position; } }