private void attemptPropagate(Vector3i target, ushort sourceLight) { if (sourceLight <= 1) { return; } if (target.x >= 0 && target.x < 16 && target.y >= 0 && target.y < 16 && target.z >= 0 && target.z < 16) { if (_data[target.x, target.y, target.z] < sourceLight - 1) { if (_chunk.GetBlock(target).isTransparent()) { write(target.x, target.y, target.z, (ushort)(sourceLight - 1)); _work_propagate.Enqueue(target); } } } else { // pass into the neighbor chunk Chunk chunk = _chunk.Owner.GetChunkAt(_chunk.Key * 16 + target); if (chunk != null) { ChunkLightmap lightmap = _chunk.Owner.GetLightmap(chunk.Key); lightmap.attemptPropagate(_chunk.Key * 16 + target - chunk.Key * 16, sourceLight); } } }
// FIXME if lighting affects gameplay, it is unacceptable to delay it!!! // mesh rebuilds can be delayed, it's just representation IEnumerator CheckLighting() { // first wait for previous work to finish, then check once per second do { yield return(new WaitForSeconds(1.0f)); } while (_foofoo.Count > 0); // queue new work foreach (Chunk chunk in _world.Chunks.Values) { ChunkLightmap lightmap = _world.GetLightmapFor(chunk); if (lightmap.NeedsPropagate()) { _foofoo.Enqueue(chunk); } } foreach (VisualChunk vis in _vis.Values) { if (vis.Dirty) { _foofoo.Enqueue(vis.Chunk); } } }
public ChunkLightmap GetLightmapFor(Chunk chunk) { ChunkLightmap lightmap; if (!_lightmaps.TryGetValue(chunk.Key, out lightmap)) { lightmap = new ChunkLightmap(chunk); _lightmaps.Add(chunk.Key, lightmap); } return(lightmap); }
public ushort GetLight(Vector3i worldPosition) { // FIXME basically, this shouldn't be used because repeated lookups are bad, but it's easier to have for prototyping Chunk chunk = GetChunkAt(worldPosition); // TODO refactor - returning darkness for out of bounds if (chunk == null) { return(0); } ChunkLightmap lightmap = GetLightmapFor(chunk); Vector3i localpos = worldPosition % 16; return(lightmap.GetLight(localpos)); }
public void RebuildMesh() { ChunkLightmap lightmap = _world.GetLightmap(_chunk.Key); var appearances = new System.Collections.Generic.Dictionary <BlockType, BlockAppearance>(); // air appearances.Add(BlockType.Empty, new BlockAppearance()); // stone BlockAppearance tmp = new BlockAppearance(); for (int i = 0; i < 6; i++) { tmp.UVRects[i] = _atlas.Rects[0]; tmp.Colors[i] = new Color(0.4f, 0.4f, 0.4f); } appearances.Add(new BlockType(2), tmp); // dirt tmp = new BlockAppearance(); for (int i = 0; i < 6; i++) { if (i != (int)BlockFace.Top) { tmp.UVRects[i] = _atlas.Rects[1]; } else { tmp.UVRects[i] = _atlas.Rects[3]; } } appearances.Add(new BlockType(1), tmp); // grass tmp = new BlockAppearance(); for (int i = 0; i < 6; i++) { tmp.UVRects[i] = _atlas.Rects[2]; } tmp.UVRects[(int)BlockFace.Top] = _atlas.Rects[4]; tmp.UVRects[(int)BlockFace.Bottom] = _atlas.Rects[1]; appearances.Add(new BlockType(3), tmp); Profiler.BeginSample("RebuildMesh"); for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { BlockType block = _chunk.GetBlock(x, y, z); if (block.isTransparent() == false && _chunk.IsBlockVisible(x, y, z)) { BlockAppearance appearance; if (!appearances.TryGetValue(block, out appearance)) { appearance = new BlockAppearance(); } CubeBuilder.buildCube(_meshBuilder, new Vector3i(x, y, z), _chunk, lightmap, appearance); } } } } _mesh.Clear(); _meshBuilder.BuildMesh(_mesh); _meshBuilder.Clear(); _dirty = false; Profiler.EndSample(); }
public static void buildCube(MeshBuilder mb, Vector3i pos, Chunk chunk, ChunkLightmap lightmap, BlockAppearance appearance) { // FIXME foo... Color brown = new Color(0.65f, 0.4f, 0.2f); //Color[] foo = {brown*0.8f, brown*0.8f, Color.green, brown*0.5f, brown, brown}; //Color[] foo = {brown, brown, Color.green, brown, brown, brown}; BlockType block = chunk.GetBlock(pos); for (int i = 0; i < 6; i++) { if (!chunk.IsBlockFaceVisible(pos, (BlockFace)i)) { continue; } Rect UVs = appearance.UVRects[i]; Color tint = appearance.Colors[i]; Vector3i normal = cubeNormals[i]; mb.UVs.Add(new Vector2(UVs.xMin, UVs.yMin)); mb.UVs.Add(new Vector2(UVs.xMax, UVs.yMin)); mb.UVs.Add(new Vector2(UVs.xMin, UVs.yMax)); mb.UVs.Add(new Vector2(UVs.xMax, UVs.yMax)); float[] ao = new float[4]; for (int v = 0; v < 4; v++) { Vector3i vertex = cubeVertices[cubeFaces[i, v]]; // for sake of simplicity, get diagonal light value too even though it could be blocked by the two adjacent blocks if (i == 0 || i == 1) { // x ao[v] = lightmap.GetLightValue(pos + new Vector3i(-1 + normal.x + 1, -1 + vertex.y, -1 + vertex.z)) + lightmap.GetLightValue(pos + new Vector3i(-1 + normal.x + 1, -1 + vertex.y + 1, -1 + vertex.z)) + lightmap.GetLightValue(pos + new Vector3i(-1 + normal.x + 1, -1 + vertex.y, -1 + vertex.z + 1)) + lightmap.GetLightValue(pos + new Vector3i(-1 + normal.x + 1, -1 + vertex.y + 1, -1 + vertex.z + 1)); } else if (i == 2 || i == 3) { // y ao[v] = lightmap.GetLightValue(pos + new Vector3i(-1 + vertex.x, -1 + normal.y + 1, -1 + vertex.z)) + lightmap.GetLightValue(pos + new Vector3i(-1 + vertex.x + 1, -1 + normal.y + 1, -1 + vertex.z)) + lightmap.GetLightValue(pos + new Vector3i(-1 + vertex.x, -1 + normal.y + 1, -1 + vertex.z + 1)) + lightmap.GetLightValue(pos + new Vector3i(-1 + vertex.x + 1, -1 + normal.y + 1, -1 + vertex.z + 1)); } else { // z ao[v] = lightmap.GetLightValue(pos + new Vector3i(-1 + vertex.x, -1 + vertex.y, -1 + normal.z + 1)) + lightmap.GetLightValue(pos + new Vector3i(-1 + vertex.x + 1, -1 + vertex.y, -1 + normal.z + 1)) + lightmap.GetLightValue(pos + new Vector3i(-1 + vertex.x, -1 + vertex.y + 1, -1 + normal.z + 1)) + lightmap.GetLightValue(pos + new Vector3i(-1 + vertex.x + 1, -1 + vertex.y + 1, -1 + normal.z + 1)); } vertex += pos; mb.Vertices.Add((Vector3)vertex); mb.Normals.Add((Vector3)normal); float aoFactor = Mathf.Clamp(ao[v], 0, 4); aoFactor /= 4; Color color = tint * aoFactor; mb.Colors32.Add(color); } // prefer the bright edge in the middle, suitable for outdoors but bad for drastic light level transitions into darkness if (ao[0] + ao[3] < ao[1] + ao[2]) { mb.AddTriangle(-2, -3, -4); mb.AddTriangle(-3, -2, -1); } else { mb.AddTriangle(-2, -1, -4); mb.AddTriangle(-1, -3, -4); } } }
void Start() { // DEBUG foofoo testing... _targeting = GameObject.Find("Targeting").transform; ChunkGenerator chunkgen = new ChunkGenerator(); var sw = new System.Diagnostics.Stopwatch(); sw.Start(); for (int cx = 0; cx < _world.SizeX; cx++) { for (int cy = 0; cy < _world.SizeY; cy++) { for (int cz = 0; cz < _world.SizeZ; cz++) { var chunk = _world.CreateChunk(new Vector3i(cx, cy, cz), chunkgen); } } } Debug.Log("World generation took " + sw.ElapsedMilliseconds + " ms"); sw.Reset(); sw.Start(); // make some dummy content _world.BeginBatchUpdate(); for (int x = 0; x < _world.SizeX * 16; x++) { int z = 91; _world.SetBlock(new Vector3i(x, 85, z - 2), new BlockType(2)); _world.SetBlock(new Vector3i(x, 85, z - 1), new BlockType(2)); _world.SetBlock(new Vector3i(x, 85, z), new BlockType(2)); _world.SetBlock(new Vector3i(x, 85, z + 1), new BlockType(2)); _world.SetBlock(new Vector3i(x, 85, z + 2), new BlockType(2)); _world.SetBlock(new Vector3i(x, 86, z), new BlockType(0)); _world.SetBlock(new Vector3i(x, 87, z), new BlockType(0)); _world.SetBlock(new Vector3i(x, 88, z), new BlockType(0)); _world.SetBlock(new Vector3i(x, 86, z - 1), new BlockType(0)); _world.SetBlock(new Vector3i(x, 87, z - 1), new BlockType(0)); _world.SetBlock(new Vector3i(x, 86, z + 1), new BlockType(0)); _world.SetBlock(new Vector3i(x, 87, z + 1), new BlockType(0)); // pillars if (x % 6 == 3) { for (int y = 84; y > 0 && _world.GetBlock(new Vector3i(x, y, z)).isTransparent(); y--) { _world.SetBlock(new Vector3i(x, y, z), new BlockType(2)); } } } _world.EndBatchUpdate(); _world.Sunlight.CalculateAll(); // Do initial direct lighting for (int cy = _world.SizeY - 1; cy >= 0; cy--) { for (int cx = 0; cx < _world.SizeX; cx++) { for (int cz = 0; cz < _world.SizeZ; cz++) { ChunkLightmap lightmap = _world.GetLightmap(new Vector3i(cx, cy, cz)); lightmap.ApplyDirectSunlight(); } } } // spread the sunlight all around the place for (int cy = _world.SizeY - 1; cy >= 0; cy--) { for (int cx = 0; cx < _world.SizeX; cx++) { for (int cz = 0; cz < _world.SizeZ; cz++) { ChunkLightmap lightmap = _world.GetLightmap(new Vector3i(cx, cy, cz)); lightmap.PropagateSunlight(); } } } Debug.Log("Sunlight initialization took " + sw.ElapsedMilliseconds + " ms"); sw.Reset(); sw.Start(); foreach (Chunk chunk in _world.Chunks.Values) { _foofoo.Enqueue(chunk); } StartCoroutine(CheckLighting()); }
void Update() { if (Input.GetKeyDown(KeyCode.Escape)) { Application.Quit(); } float wheel = Input.GetAxis("Mouse ScrollWheel"); _cut += wheel * Time.deltaTime * wheelspeed; //_cut += Mathf.Sign(Input.GetAxisRaw("Mouse ScrollWheel"))*1; // snap to level afterwards if (Input.GetButtonDown("Depth Scroll") && Input.GetAxisRaw("Depth Scroll") > 0) { _cut = Mathf.Round(_cut + 1); } else if (Input.GetButtonDown("Depth Scroll") && Input.GetAxisRaw("Depth Scroll") < 0) { _cut = Mathf.Round(_cut - 1); } else if (Input.GetButton("Depth Scroll")) { // slowly scroll after snap if key held down _cut += Input.GetAxis("Depth Scroll") * Time.deltaTime * keyspeed; } _cut = Mathf.Clamp(_cut, 0, _world.SizeY * 16); int levelDisplayed = Mathf.RoundToInt(_cut); Shader.SetGlobalFloat("_VoxelCutY", levelDisplayed + 0.5f); // ------ raycast ---- Ray ray = Camera.main.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0)); RaycastHit hit; //Debug.DrawLine(ray.origin, ray.direction*100); //Plane cutPlane = new Plane(new Vector3(0,1,0), levelDisplayed + 0.5f); // FIXME rewrite, we need custom voxel raytracer due to cutplane nonsense, // other objects can be collided in some other way (based on their visibility) while (Physics.Raycast(ray, out hit)) { Vector3 p = hit.point - hit.normal * 0.5f; Vector3i targetCoord = new Vector3i(Mathf.FloorToInt(p.x), Mathf.FloorToInt(p.y), Mathf.FloorToInt(p.z)); if (targetCoord.y > levelDisplayed) { ray.origin = hit.point; continue; } BlockType block = _world.GetBlock(targetCoord); Chunk chunk = _world.GetChunkAt(targetCoord); string infotext; if (chunk == null) { infotext = "null chunk!? " + targetCoord.ToString() + " ... " + _world.Chunks.Count; } else { infotext = string.Format("Chunk {0} @ {1} blocktype {2}", chunk.Key.ToString(), targetCoord.ToString(), block.Raw); } // FIXME performance GameObject.Find("TargetingInfo").GetComponent <UnityEngine.UI.Text>().text = infotext; GameObject.Find("Targeting").transform.localPosition = (Vector3)targetCoord + new Vector3(0.5f, 0.5f, 0.5f); break; } // ---------- magic rebuild ----------- // TODO any better way to not waste time? threads? :P var sw = new System.Diagnostics.Stopwatch(); sw.Start(); // process visual chunks (rebuild meshes if world has changed etc) while (_foofoo.Count > 0) { // spend a maximum of 10ms on this nonsense if (sw.ElapsedMilliseconds > 10) { break; } Chunk chunk = _foofoo.Dequeue(); VisualChunk vis; _vis.TryGetValue(chunk.Key, out vis); if (vis != null) { ChunkLightmap lightmap = _world.GetLightmapFor(chunk); lightmap.Propagate(); vis.RebuildMesh(); } else { vis = new VisualChunk(chunk, _world, gameObject); _vis.Add(chunk.Key, vis); } } }