void RefreshNineChunks(VoxelChunk chunk, bool forceMeshRefresh = false) { if (chunk == null) { return; } int chunkX, chunkY, chunkZ; FastMath.FloorToInt(chunk.position.x / CHUNK_SIZE, chunk.position.y / CHUNK_SIZE, chunk.position.z / CHUNK_SIZE, out chunkX, out chunkY, out chunkZ); VoxelChunk neighbour; for (int y = -1; y <= 1; y++) { for (int z = -1; z <= 1; z++) { for (int x = -1; x <= 1; x++) { GetChunkFast(chunkX + x, chunkY + y, chunkZ + z, out neighbour); if (neighbour != null) { ChunkRequestRefresh(neighbour, true, forceMeshRefresh); } } } } }
void RefreshNineChunks(VoxelChunk chunk) { if (chunk == null) { return; } Vector3 position = chunk.position; int chunkX = FastMath.FloorToInt(position.x / 16f); int chunkY = FastMath.FloorToInt(position.y / 16f); int chunkZ = FastMath.FloorToInt(position.z / 16f); VoxelChunk neighbour; for (int y = -1; y <= 1; y++) { for (int z = -1; z <= 1; z++) { for (int x = -1; x <= 1; x++) { GetChunkFast(chunkX + x, chunkY + y, chunkZ + z, out neighbour); if (neighbour != null) { ChunkRequestRefresh(neighbour, true, false); } } } } }
/// <summary> /// Gets the octree from a position and clears the explored flag to ensure that area can be refreshed again. /// Optionally remove the chunk from cache. /// </summary> void SetChunkOctreeIsDirty(Vector3 position, bool removeFromCache) { int chunkX, chunkY, chunkZ; FastMath.FloorToInt(position.x / 16f, position.y / 16f, position.z / 16f, out chunkX, out chunkY, out chunkZ); int existingChunkHash = GetChunkHash(chunkX, chunkY, chunkZ); CachedChunk cachedChunk; if (cachedChunks.TryGetValue(existingChunkHash, out cachedChunk)) { Octree octree = cachedChunk.octree; while (octree != null) { octree.explored = false; if (octree.parent != null) { octree.parent.exploredChildren--; } octree = octree.parent; } if (removeFromCache) { cachedChunks.Remove(existingChunkHash); } } }
void RefreshNineChunksMeshes(VoxelChunk chunk) { Vector3 position = chunk.position; int chunkX, chunkY, chunkZ; FastMath.FloorToInt(position.x / CHUNK_SIZE, position.y / CHUNK_SIZE, position.z / CHUNK_SIZE, out chunkX, out chunkY, out chunkZ); VoxelChunk neighbour; for (int y = -1; y <= 1; y++) { for (int z = -1; z <= 1; z++) { for (int x = -1; x <= 1; x++) { if (y == 0 && x == 0 && z == 0) { continue; } GetChunkFast(chunkX + x, chunkY + y, chunkZ + z, out neighbour); if (neighbour != null && neighbour != chunk) { ChunkRequestRefresh(neighbour, false, true); } } } } ChunkRequestRefresh(chunk, false, true); }
void UpdateVoxelHighlight() { if (voxelHighlightBuilder == null) { voxelHighlightBuilder = Instantiate <GameObject> (Resources.Load <GameObject> ("VoxelPlay/Prefabs/VoxelHighlight")); } Vector3 rawPos; if (crosshairOnBlock) { rawPos = crosshairHitInfo.voxelCenter + crosshairHitInfo.normal; } else { rawPos = m_Camera.transform.position + m_Camera.transform.forward * 4f; } // Bound check for (int i = 0; i < 50; i++) { if (limitBounds.Contains(rawPos)) { break; } rawPos -= m_Camera.transform.forward * 0.1f; } rawPos.x = FastMath.FloorToInt(rawPos.x) + 0.5f; if (rawPos.x > limitBounds.max.x) { rawPos.x = limitBounds.max.x - 0.5f; } if (rawPos.x < limitBounds.min.x) { rawPos.x = limitBounds.min.x + 0.5f; } rawPos.y = FastMath.FloorToInt(rawPos.y) + 0.5f; if (rawPos.y > limitBounds.max.y) { rawPos.y = limitBounds.max.y - 0.5f; } if (rawPos.y < limitBounds.min.y) { rawPos.y = limitBounds.min.y + 0.5f; } rawPos.z = FastMath.FloorToInt(rawPos.z) + 0.5f; if (rawPos.z > limitBounds.max.z) { rawPos.z = limitBounds.max.z - 0.5f; } if (rawPos.z < limitBounds.min.z) { rawPos.z = limitBounds.min.z + 0.5f; } voxelHighlightBuilder.transform.position = rawPos; }
VoxelChunk GetChunkOrCreate(Vector3 position) { int x = FastMath.FloorToInt(position.x / 16); int y = FastMath.FloorToInt(position.y / 16); int z = FastMath.FloorToInt(position.z / 16); VoxelChunk chunk; GetChunkFast(x, y, z, out chunk, true); return(chunk); }
VoxelChunk GetChunkOrCreate(Vector3 position) { int x, y, z; FastMath.FloorToInt(position.x / 16, position.y / 16, position.z / 16, out x, out y, out z); VoxelChunk chunk; GetChunkFast(x, y, z, out chunk, true); return(chunk); }
VoxelChunk GetChunkOrCreate(Vector3 position) { int x, y, z; FastMath.FloorToInt(position.x / VoxelPlayEnvironment.CHUNK_SIZE, position.y / VoxelPlayEnvironment.CHUNK_SIZE, position.z / VoxelPlayEnvironment.CHUNK_SIZE, out x, out y, out z); VoxelChunk chunk; GetChunkFast(x, y, z, out chunk, true); return(chunk); }
void CheckNearChunks(Vector3 position) { if (!checkNearChunks) { return; } int chunkX, chunkY, chunkZ; FastMath.FloorToInt(position.x / 16f, position.y / 16f, position.z / 16f, out chunkX, out chunkY, out chunkZ); if (lastChunkX != chunkX || lastChunkY != chunkY || lastChunkZ != chunkZ) { lastChunkX = chunkX; lastChunkY = chunkY; lastChunkZ = chunkZ; env.ChunkCheckArea(position, chunkExtents, renderChunks); } }
/// <summary> /// Paints the terrain inside the chunk defined by its central "position" /// </summary> /// <returns><c>true</c>, if terrain was painted, <c>false</c> otherwise.</returns> public override bool PaintChunk(VoxelChunk chunk) { int chunkBottomPos = FastMath.FloorToInt(chunk.position.y - VoxelPlayEnvironment.CHUNK_HALF_SIZE); if (chunkBottomPos >= altitude) { return(false); // does not have contents } // Voxel Color (checker board style) Color32 voxelColor; uint chance = (((uint)chunk.position.x + (uint)chunk.position.z) / VoxelPlayEnvironment.CHUNK_SIZE) % 2; if (chance == 1) { voxelColor = voxelColor1; } else { voxelColor = voxelColor2; } // a chunk is made of 16x16x16 voxels - calculate the last voxel position in the array to be filled // constant ONE_Y_ROW equals to the number of voxels in an horizontal slice (ie. 16*16 voxels per row) int maxY = altitude - chunkBottomPos; int lastVoxel = maxY * ONE_Y_ROW; if (lastVoxel >= chunk.voxels.Length) { lastVoxel = chunk.voxels.Length; } // Fill the chunk.voxels 3D array with voxels for (int k = 0; k < lastVoxel; k++) { chunk.voxels [k].Set(terrainVoxel, voxelColor); } // For correct light illumination, specify if this chunk on surface level int chunkTopPos = (int)chunk.position.y + VoxelPlayEnvironment.CHUNK_HALF_SIZE; chunk.isAboveSurface = chunkTopPos >= altitude; return(true); // true = > this chunk has contents }
void CheckNearChunks(Vector3 position) { if (!checkNearChunks) { return; } int chunkX, chunkY, chunkZ; FastMath.FloorToInt(position.x / VoxelPlayEnvironment.CHUNK_SIZE, position.y / VoxelPlayEnvironment.CHUNK_SIZE, position.z / VoxelPlayEnvironment.CHUNK_SIZE, out chunkX, out chunkY, out chunkZ); if (lastChunkX != chunkX || lastChunkY != chunkY || lastChunkZ != chunkZ) { lastChunkX = chunkX; lastChunkY = chunkY; lastChunkZ = chunkZ; // Ensure area is rendered env.ChunkCheckArea(position, chunkExtents, renderChunks); } }
/// <summary> /// Checks if there's a voxel at given position. /// </summary> /// <returns><c>true</c>, if collision was checked, <c>false</c> otherwise.</returns> /// <param name="position">Position.</param> public bool CheckCollision(Vector3 position) { int x = FastMath.FloorToInt(position.x / 16); int y = FastMath.FloorToInt(position.y / 16); int z = FastMath.FloorToInt(position.z / 16); VoxelChunk chunk = GetChunkOrCreate(x, y, z); if (chunk != null) { Voxel[] voxels = chunk.voxels; int py = (int)(position.y - y * 16); // FastMath.FloorToInt (position.y) - y * 16; int pz = (int)(position.z - z * 16); // FastMath.FloorToInt (position.z) - z * 16; int px = (int)(position.x - x * 16); // FastMath.FloorToInt (position.x) - x * 16; int voxelIndex = py * ONE_Y_ROW + pz * ONE_Z_ROW + px; return(voxels [voxelIndex].hasContent == 1 && voxelDefinitions [voxels [voxelIndex].typeIndex].navigatable); } return(false); }
void LateUpdate() { if (!env.initialized) { return; } // Check if position has changed since previous Vector3 position = transform.position; int x, y, z; FastMath.FloorToInt(position.x, position.y, position.z, out x, out y, out z); if (lastX != x || lastY != y || lastZ != z) { requireUpdateLighting = true; lastPosition = position; lastX = x; lastY = y; lastZ = z; if (forceUnstuck) { Vector3 pos = transform.position; pos.y += 0.1f; if (env.CheckCollision(pos)) { float deltaY = FastMath.FloorToInt(pos.y) + 1f - pos.y; pos.y += deltaY + 0.01f; transform.position = pos; lastX--; } } CheckNearChunks(position); } if (requireUpdateLighting) { requireUpdateLighting = false; UpdateLightingNow(); } }
/// <summary> /// Gets heightmap info for 16x16 positions in a chunk /// </summary> /// <returns>The height map info.</returns> /// <param name="x">The x coordinate of the left-most position of the chunk.</param> /// <param name="z">The z coordinate of the back-most position of the chunk.</param> /// <param name="heightChunkData">An array of HeightMapInfo structs to be filled with data. The size of the array must be 16*16.</param> public void GetHeightMapInfoFast(float x, float z, HeightMapInfo[] heightChunkData) { int ix = FastMath.FloorToInt(x); int iz = FastMath.FloorToInt(z); VoxelPlayTerrainGenerator tg = world.terrainGenerator; HeightMapInfo[] heights; int heightsIndex; for (int zz = 0; zz < 16; zz++) { for (int xx = 0; xx < 16; xx++) { if (!heightMapCache.TryGetValue(ix + xx, iz + zz, out heights, out heightsIndex)) { float altitude, moisture; tg.GetHeightAndMoisture(ix + xx, iz + zz, out altitude, out moisture); if (altitude > 1f) { altitude = 1f; } else if (altitude < 0f) { altitude = 0f; } if (moisture > 1f) { moisture = 1f; } else if (moisture < 0f) { moisture = 0f; } int biomeIndex = (int)(altitude * 20) * 21 + (int)(moisture * 20f); heights [heightsIndex].groundLevel = (int)(altitude * tg.maxHeight); heights [heightsIndex].moisture = moisture; heights [heightsIndex].biome = biomeLookUp [biomeIndex]; } heightChunkData [zz * 16 + xx] = heights [heightsIndex]; } } }
void LateUpdate() { if (!env.initialized) { return; } // Check if position has changed since previous Vector3 position = transform.position; int x = FastMath.FloorToInt(position.x); int y = FastMath.FloorToInt(position.y); int z = FastMath.FloorToInt(position.z); if (lastX == x && lastY == y && lastZ == z) { return; } lastPosition = position; lastX = x; lastY = y; lastZ = z; UpdateLighting(); if (forceUnstuck) { Vector3 pos = transform.position; pos.y += 0.1f; if (env.CheckCollision(pos)) { float deltaY = FastMath.FloorToInt(pos.y) + 1f - pos.y; pos.y += deltaY + 0.01f; transform.position = pos; lastX--; } } CheckNearChunks(position); }
void ChunkRequestRefresh(Bounds bounds, bool clearLightmap, bool refreshMesh) { Vector3 position; Vector3 min = bounds.min; int xmin, ymin, zmin; FastMath.FloorToInt(min.x / CHUNK_SIZE, min.y / CHUNK_SIZE, min.z / CHUNK_SIZE, out xmin, out ymin, out zmin); xmin *= CHUNK_SIZE; ymin *= CHUNK_SIZE; zmin *= CHUNK_SIZE; Vector3 max = bounds.max; int xmax, ymax, zmax; FastMath.FloorToInt(max.x / CHUNK_SIZE, max.y / CHUNK_SIZE, max.z / CHUNK_SIZE, out xmax, out ymax, out zmax); xmax *= CHUNK_SIZE; ymax *= CHUNK_SIZE; zmax *= CHUNK_SIZE; for (int y = ymax; y >= ymin; y -= CHUNK_SIZE) { position.y = y; for (int z = zmin; z <= zmax; z += CHUNK_SIZE) { position.z = z; for (int x = xmin; x <= xmax; x += CHUNK_SIZE) { position.x = x; VoxelChunk chunk; if (GetChunk(position, out chunk, true)) { ChunkRequestRefresh(chunk, clearLightmap, refreshMesh); } } } } }
/// <summary> /// Gets heightmap info on a given position. This method only works at runtime. Use GetTerainInfo() in Editor. /// </summary> /// <returns>The height map info.</returns> /// <param name="x">The x coordinate.</param> /// <param name="z">The z coordinate.</param> public HeightMapInfo GetHeightMapInfoFast(float x, float z) { int ix = FastMath.FloorToInt(x); int iz = FastMath.FloorToInt(z); HeightMapInfo[] heights; int heightsIndex; if (!heightMapCache.TryGetValue(ix, iz, out heights, out heightsIndex)) { float altitude, moisture; VoxelPlayTerrainGenerator tg = world.terrainGenerator; tg.GetHeightAndMoisture(x, z, out altitude, out moisture); if (altitude > 1f) { altitude = 1f; } else if (altitude < 0f) { altitude = 0f; } if (moisture > 1f) { moisture = 1f; } else if (moisture < 0f) { moisture = 0f; } int biomeIndex = (int)(altitude * 20) * 21 + (int)(moisture * 20f); heights [heightsIndex].groundLevel = (int)(altitude * tg.maxHeight); heights [heightsIndex].moisture = moisture; heights [heightsIndex].biome = biomeLookUp [biomeIndex]; } return(heights [heightsIndex]); }
/// <summary> /// Move worms /// </summary> public override bool DoWork(long endTime) { int count = worms.Count; if (count == 0) { return(false); } for (int k = 0; k < count; k++) { env.STAGE = 3000; HoleWorm worm = worms [k]; uint xx = (uint)worm.head.x % texSize; worm.ax += noiseValues [xx]; if (worm.ax > 1f) { worm.ax = 1f; } else if (worm.ax < -1f) { worm.ax = -1f; } worm.head.x += worm.ax; uint yy = (uint)worm.head.y % texSize; worm.ay += noiseValues [yy]; if (worm.ay > 1f) { worm.ay = 1f; } else if (worm.ay < -1f) { worm.ay = -1f; } worm.head.y += worm.ay; uint zz = (uint)worm.head.z % texSize; worm.az += noiseValues [zz]; if (worm.az > 1f) { worm.az = 1f; } else if (worm.az < -1f) { worm.az = -1f; } worm.head.z += worm.az; int ix = (int)(worm.head.x); int iy = (int)(worm.head.y); int iz = (int)(worm.head.z); env.STAGE = 3001; if (ix != worm.lastX || iy != worm.lastY || iz != worm.lastZ) { worm.lastX = ix; worm.lastY = iy; worm.lastZ = iz; // keep this order of assignment to improve randomization int minx = ix - (caveBorder++ & 5); int miny = iy - (caveBorder++ & 5); int maxx = ix + (caveBorder++ & 5); int minz = iz - (caveBorder++ & 5); int maxy = iy + (caveBorder++ & 5); int maxz = iz + (caveBorder++ & 5); int mx = (maxx + minx) / 2; int my = (maxy + miny) / 2; int mz = (maxz + minz) / 2; VoxelChunk chunk = null; int lastChunkX = int.MinValue, lastChunkY = int.MinValue, lastChunkZ = int.MinValue; for (int y = miny; y < maxy; y++) { int chunkY = FastMath.FloorToInt(y / 16f); int py = (int)(y - chunkY * 16); int voxelIndexY = py * ONE_Y_ROW; for (int z = minz; z < maxz; z++) { int chunkZ = FastMath.FloorToInt(z / 16f); int pz = (int)(z - chunkZ * 16); int voxelIndexZ = voxelIndexY + pz * ONE_Z_ROW; for (int x = minx; x < maxx; x++) { int dx = x - mx; int dy = y - my; int dz = z - mz; if (dx * dx + dy * dy + dz * dz > 23) { continue; } int chunkX = FastMath.FloorToInt(x / 16f); if (chunkX != lastChunkX || chunkY != lastChunkY || chunkZ != lastChunkZ) { lastChunkX = chunkX; lastChunkY = chunkY; lastChunkZ = chunkZ; env.STAGE = 3004; chunk = env.GetChunkUnpopulated(chunkX, chunkY, chunkZ); env.STAGE = 3005; if (chunk.isPopulated) { worm.life = 0; y = maxy; z = maxz; break; } // mark the chunk as modified by this detail generator SetChunkIsDirty(chunk); } int px = (int)(x - chunkX * 16); int voxelIndex = voxelIndexZ + px; chunk.voxels [voxelIndex].hasContent = 2; } } } worm.life--; if (worm.life <= 0) { env.STAGE = 3007; worms.RemoveAt(k); env.STAGE = 0; return(true); } } worms [k] = worm; long elapsed = env.stopWatch.ElapsedMilliseconds; if (elapsed >= endTime) { break; } } env.STAGE = 0; return(true); }
/// <summary> /// Performs the voxel damage. /// </summary> /// <returns>The actual damage taken by the voxe.</returns> /// <param name="hitInfo">Hit info.</param> /// <param name="damage">Damage.</param> /// <param name="addParticles">If set to <c>true</c> add particles.</param> int DamageVoxelFast(ref VoxelHitInfo hitInfo, int damage, bool addParticles, bool playSound, bool allowDamageEvent = true) { VoxelChunk chunk = hitInfo.chunk; if (hitInfo.voxel.typeIndex == 0) { return(0); } VoxelDefinition voxelType = hitInfo.voxel.type; byte voxelTypeResistancePoints = voxelType.resistancePoints; if (buildMode) { if (damage > 0) { damage = 255; } } else { if (voxelTypeResistancePoints == (byte)0) { damage = 0; } else if (voxelTypeResistancePoints == (byte)255) { if (playSound) { PlayImpactSound(hitInfo.voxel.type.impactSound, hitInfo.voxelCenter); } damage = 0; } } if (allowDamageEvent && OnVoxelDamaged != null) { OnVoxelDamaged(chunk, hitInfo.voxelIndex, ref damage); } if (damage == 0) { return(0); } // Gets ambient light near surface float voxelLight = GetVoxelLight(hitInfo.point + hitInfo.normal * 0.5f); // Get voxel damage indicator GO's name bool destroyed = voxelType.renderType == RenderType.CutoutCross; int resistancePointsLeft = 0; VoxelPlaceholder placeholder = null; if (!destroyed) { placeholder = GetVoxelPlaceholder(chunk, hitInfo.voxelIndex, true); resistancePointsLeft = placeholder.resistancePointsLeft - damage; if (resistancePointsLeft < 0) { resistancePointsLeft = 0; destroyed = true; } placeholder.resistancePointsLeft = resistancePointsLeft; } if (voxelType.renderType == RenderType.Empty) { addParticles = false; } int particlesAmount; if (destroyed) { // Add recoverable voxel on the scene (not for vegetation) if (voxelType.renderType != RenderType.Empty && voxelType.renderType != RenderType.CutoutCross && voxelType.canBeCollected && !buildMode) { bool create = true; if (OnVoxelBeforeDropItem != null) { OnVoxelBeforeDropItem(chunk, hitInfo, out create); } if (create) { CreateRecoverableVoxel(hitInfo.voxelCenter, voxelDefinitions [hitInfo.voxel.typeIndex], hitInfo.voxel.color); } } // Destroy the voxel VoxelDestroyFast(chunk, hitInfo.voxelIndex); // Check if grass is on top and remove it as well VoxelChunk topChunk; int topIndex; if (GetVoxelIndex(hitInfo.voxelCenter + Misc.vector3up, out topChunk, out topIndex, false)) { if (topChunk.voxels [topIndex].typeIndex != 0 && voxelDefinitions [topChunk.voxels [topIndex].typeIndex].renderType == RenderType.CutoutCross) { byte light = topChunk.voxels [topIndex].lightMesh; topChunk.voxels [topIndex].Clear(light); topChunk.modified = true; } } // Max particles particlesAmount = 20; if (playSound) { PlayDestructionSound(voxelDefinitions [hitInfo.voxel.typeIndex].destructionSound, hitInfo.voxelCenter); } } else { // Add damage indicator if (placeholder == null) { placeholder = GetVoxelPlaceholder(chunk, hitInfo.voxelIndex, true); } if (placeholder.damageIndicator == null) { if (damagedVoxelPrefab == null) { damagedVoxelPrefab = Resources.Load <GameObject> ("VoxelPlay/Prefabs/DamagedVoxel"); } GameObject g = Instantiate <GameObject> (damagedVoxelPrefab); g.name = DAMAGE_INDICATOR; Transform tDamageIndicator = g.transform; placeholder.damageIndicator = tDamageIndicator.GetComponent <Renderer> (); tDamageIndicator.SetParent(placeholder.transform, false); tDamageIndicator.localPosition = placeholder.bounds.center; tDamageIndicator.localScale = placeholder.bounds.size * 1.001f; } int textureIndex = FastMath.FloorToInt((5f * resistancePointsLeft) / voxelTypeResistancePoints); if (world.voxelDamageTextures.Length > 0) { if (textureIndex >= world.voxelDamageTextures.Length) { textureIndex = world.voxelDamageTextures.Length - 1; } Material mi = placeholder.damageIndicatorMaterial; // gets a copy of material the first time it's used mi.mainTexture = world.voxelDamageTextures [textureIndex]; mi.SetFloat("_VoxelLight", voxelLight); placeholder.damageIndicator.enabled = true; } // Particle amount depending of damage particlesAmount = (6 - textureIndex) + 3; // Sets health recovery for the voxel placeholder.StartHealthRecovery(chunk, hitInfo.voxelIndex, world.damageDuration); if (playSound) { PlayImpactSound(voxelDefinitions [hitInfo.voxel.typeIndex].impactSound, hitInfo.voxelCenter); } } // Add random particles if (!addParticles) { particlesAmount = 0; } for (int k = 0; k < particlesAmount; k++) { int ppeIndex = GetParticleFromPool(); if (ppeIndex < 0) { continue; } // Scale of particle Renderer particleRenderer = particlePool [ppeIndex].renderer; if (destroyed) { if (voxelType.renderType == RenderType.CutoutCross) // smaller particles for vegetation { particleRenderer.transform.localScale = Misc.vector3one * Random.Range(0.03f, 0.04f); } else { particleRenderer.transform.localScale = Misc.vector3one * Random.Range(0.04f, 0.1f); } } else { particleRenderer.transform.localScale = Misc.vector3one * Random.Range(0.03f, 0.06f); } // Set particle texture Material instanceMat = particleRenderer.sharedMaterial; SetParticleMaterialTextures(instanceMat, voxelDefinitions [hitInfo.voxel.typeIndex], hitInfo.voxel.color); instanceMat.mainTextureOffset = new Vector2(Random.value, Random.value); instanceMat.mainTextureScale = Misc.vector2one * 0.05f; instanceMat.SetFloat("_VoxelLight", voxelLight); instanceMat.SetFloat("_FlashDelay", 0); // Set position Rigidbody rb = particlePool [ppeIndex].rigidBody; if (destroyed) { Vector3 expelDir = Random.insideUnitSphere; Vector3 pos = hitInfo.voxelCenter + expelDir; particleRenderer.transform.position = pos; rb.AddForce(expelDir * (Random.value * 125f)); } else { Vector3 pos = hitInfo.point; Vector3 v1 = new Vector3(-hitInfo.normal.y, hitInfo.normal.z, hitInfo.normal.x); Vector3 v2 = new Vector3(-hitInfo.normal.z, hitInfo.normal.x, hitInfo.normal.y); Vector3 dx = (Random.value - 0.5f) * 0.7f * v1; Vector3 dy = (Random.value - 0.5f) * 0.7f * v2; particleRenderer.transform.position = pos + hitInfo.normal * 0.001f + dx + dy; rb.AddForce(cameraMain.transform.forward * (Random.value * -125f)); } rb.AddForce(Misc.vector3up * 25f); rb.AddTorque(Random.onUnitSphere * 100f); rb.useGravity = true; // Self-destruct particlePool [ppeIndex].destructionTime = Time.time + 2.5f + Random.value; } return(damage); }
VoxelChunk RayCastFastVoxel(Vector3 origin, Vector3 direction, out VoxelHitInfo hitInfo, float maxDistance = 0, bool createChunksIfNeeded = false, byte minOpaque = 0) { #if DEBUG_RAYCAST GameObject o; #endif float maxDistanceSqr = maxDistance == 0 ? 1000 * 1000 : maxDistance * maxDistance; // Ray march throuch chunks until hit one loaded chunk Vector3 position = origin; VoxelChunk chunk = null; hitInfo = new VoxelHitInfo(); hitInfo.voxelIndex = -1; Vector3 viewDirSign = new Vector3(Mathf.Sign(direction.x), Mathf.Sign(direction.y), Mathf.Sign(direction.z)); Vector3 viewSign = (viewDirSign + Misc.vector3one) * 0.5f; // 0 = left, 1 = right float vxz, vzy, vxy; if (direction.y != 0) { float a = direction.x / direction.y; float b = direction.z / direction.y; vxz = Mathf.Sqrt(1f + a * a + b * b); } else { vxz = 1000000f; } if (direction.x != 0) { float a = direction.z / direction.x; float b = direction.y / direction.x; vzy = Mathf.Sqrt(1f + a * a + b * b); } else { vzy = 1000000f; } if (direction.z != 0) { float a = direction.x / direction.z; float b = direction.y / direction.z; vxy = Mathf.Sqrt(1f + a * a + b * b); } else { vxy = 1000000f; } Vector3 v3 = new Vector3(vzy, vxz, vxy); Vector3 viewSign16 = viewSign * 16f; Vector3 viewDirSignOffset = viewDirSign * 0.002f; int chunkX, chunkY, chunkZ; int chunkCount = 0; float t; Vector3 normal = Misc.vector3zero, db; // bool notFirstVoxel = false; while (chunkCount++ < 500) // safety counter to avoid any potential infinite loop // Check max distance { float distSqr = (position.x - origin.x) * (position.x - origin.x) + (position.y - origin.y) * (position.y - origin.y) + (position.z - origin.z) * (position.z - origin.z); if (distSqr > maxDistanceSqr) { return(null); } #if DEBUG_RAYCAST o = GameObject.CreatePrimitive(PrimitiveType.Cube); o.transform.localScale = Misc.Vector3one * 0.15f; o.transform.position = position; DestroyImmediate(o.GetComponent <Collider>()); o.GetComponent <Renderer>().material.color = Color.blue; #endif chunkX = FastMath.FloorToInt(position.x / 16f); chunkY = FastMath.FloorToInt(position.y / 16f); chunkZ = FastMath.FloorToInt(position.z / 16f); chunk = null; if (createChunksIfNeeded) { chunk = GetChunkOrCreate(chunkX, chunkY, chunkZ); } else { int x00 = WORLD_SIZE_DEPTH * WORLD_SIZE_HEIGHT * (chunkX + WORLD_SIZE_WIDTH); int y00 = WORLD_SIZE_DEPTH * (chunkY + WORLD_SIZE_HEIGHT); int hash = x00 + y00 + chunkZ; chunk = GetChunkIfExists(hash); } chunkX *= 16; chunkY *= 16; chunkZ *= 16; if (chunk) { // Ray-march through chunk Voxel[] voxels = chunk.voxels; Vector3 inPosition = position; for (int k = 0; k < 64; k++) { #if DEBUG_RAYCAST o = GameObject.CreatePrimitive(PrimitiveType.Sphere); o.transform.localScale = Misc.Vector3one * 0.1f; o.transform.position = inPosition; DestroyImmediate(o.GetComponent <Collider>()); o.GetComponent <Renderer>().material.color = Color.yellow; #endif // Check voxel content int fy = FastMath.FloorToInt(inPosition.y); int py = fy - chunkY; int fz = FastMath.FloorToInt(inPosition.z); int pz = fz - chunkZ; int fx = FastMath.FloorToInt(inPosition.x); int px = fx - chunkX; if (px < 0 || px > 15 || py < 0 || py > 15 || pz < 0 || pz > 15) { break; } int voxelIndex = py * ONE_Y_ROW + pz * ONE_Z_ROW + px; if (voxels [voxelIndex].hasContent == 1 && !voxels [voxelIndex].type.ignoresRayCast && (minOpaque == 255 || voxels [voxelIndex].opaque >= minOpaque)) { VoxelDefinition vd = voxelDefinitions [voxels [voxelIndex].typeIndex]; if (vd.renderType != RenderType.Custom || !vd.modelUsesCollider) { // Check max distance distSqr = (inPosition.x - origin.x) * (inPosition.x - origin.x) + (inPosition.y - origin.y) * (inPosition.y - origin.y) + (inPosition.z - origin.z) * (inPosition.z - origin.z); if (distSqr > maxDistanceSqr) { return(null); } // Check water level or grass height float voxelHeight = 0; if (vd.renderType == RenderType.Water) { voxelHeight = voxels [voxelIndex].GetWaterLevel() / 15f; } else if (vd.renderType == RenderType.CutoutCross) { voxelHeight = vd.scale.y; } bool hit = true; Vector3 voxelCenter = new Vector3(chunkX + px + 0.5f, chunkY + py + 0.5f, chunkZ + pz + 0.5f); Vector3 localHitPos = inPosition - voxelCenter; if (voxelHeight > 0 && voxelHeight < 1f && direction.y != 0) { t = localHitPos.y + 0.5f - voxelHeight; if (t > 0) { t = t * Mathf.Sqrt(1 + (direction.x * direction.x + direction.z * direction.z) / (direction.y * direction.y)); localHitPos += t * direction; hit = localHitPos.x >= -0.5f && localHitPos.x <= 0.5f && localHitPos.z >= -0.5f && localHitPos.z <= 0.5f; } } if (hit) { hitInfo = new VoxelHitInfo(); hitInfo.chunk = chunk; hitInfo.voxel = voxels [voxelIndex]; hitInfo.point = inPosition - normal; hitInfo.distance = Mathf.Sqrt(distSqr); hitInfo.voxelIndex = voxelIndex; hitInfo.voxelCenter = voxelCenter; if (localHitPos.y >= 0.495) { hitInfo.normal = Misc.vector3up; } else if (localHitPos.y <= -0.495) { hitInfo.normal = Misc.vector3down; } else if (localHitPos.x < -0.495) { hitInfo.normal = Misc.vector3left; } else if (localHitPos.x > 0.495) { hitInfo.normal = Misc.vector3right; } else if (localHitPos.z < -0.495) { hitInfo.normal = Misc.vector3back; } else if (localHitPos.z > 0.495) { hitInfo.normal = Misc.vector3forward; } #if DEBUG_RAYCAST o = GameObject.CreatePrimitive(PrimitiveType.Sphere); o.transform.localScale = Misc.Vector3one * 0.15f; o.transform.position = inPosition; DestroyImmediate(o.GetComponent <Collider>()); o.GetComponent <Renderer>().material.color = Color.red; #endif return(chunk); } } } db.x = (fx + viewSign.x - inPosition.x) * v3.x; db.y = (fy + viewSign.y - inPosition.y) * v3.y; db.z = (fz + viewSign.z - inPosition.z) * v3.z; db.x = db.x < 0 ? -db.x : db.x; db.y = db.y < 0 ? -db.y : db.y; db.z = db.z < 0 ? -db.z : db.z; t = db.x; normal.x = viewDirSignOffset.x; normal.y = 0; normal.z = 0; if (db.y < t) { t = db.y; normal.x = 0; normal.y = viewDirSignOffset.y; } if (db.z < t) { t = db.z; normal.x = 0; normal.y = 0; normal.z = viewDirSignOffset.z; } inPosition.x += direction.x * t + normal.x; inPosition.y += direction.y * t + normal.y; inPosition.z += direction.z * t + normal.z; // notFirstVoxel = true; } } db.x = (chunkX + viewSign16.x - position.x) * v3.x; db.y = (chunkY + viewSign16.y - position.y) * v3.y; db.z = (chunkZ + viewSign16.z - position.z) * v3.z; db.x = db.x < 0 ? -db.x : db.x; db.y = db.y < 0 ? -db.y : db.y; db.z = db.z < 0 ? -db.z : db.z; t = db.x; normal.x = viewDirSignOffset.x; normal.y = 0; normal.z = 0; if (db.y < t) { t = db.y; normal.x = 0; normal.y = viewDirSignOffset.y; } if (db.z < t) { t = db.z; normal.x = 0; normal.y = 0; normal.z = viewDirSignOffset.z; } position.x += direction.x * t + normal.x; position.y += direction.y * t + normal.y; position.z += direction.z * t + normal.z; // notFirstVoxel = true; } return(null); }
void GetChunkCoordinates(Vector3 position, out int chunkX, out int chunkY, out int chunkZ) { FastMath.FloorToInt(position.x / VoxelPlayEnvironment.CHUNK_SIZE, position.y / VoxelPlayEnvironment.CHUNK_SIZE, position.z / VoxelPlayEnvironment.CHUNK_SIZE, out chunkX, out chunkY, out chunkZ); }
void GetChunkCoordinates(Vector3 position, out int chunkX, out int chunkY, out int chunkZ) { chunkX = FastMath.FloorToInt(position.x / 16); chunkY = FastMath.FloorToInt(position.y / 16); chunkZ = FastMath.FloorToInt(position.z / 16); }
void CheckWaterStatus() { Vector3 nearClipPos = m_Camera.transform.position + m_Camera.transform.forward * (m_Camera.nearClipPlane + 0.001f); if (nearClipPos.x == lastNearClipPosX && nearClipPos.y == lastNearClipPosY && nearClipPos.z == lastNearClipPosZ) { return; } lastNearClipPosX = (int)nearClipPos.x; lastNearClipPosY = (int)nearClipPos.y; lastNearClipPosZ = (int)nearClipPos.z; bool wasInWater = isInWater; isInWater = false; isSwimming = false; isUnderwater = false; // Check water on character controller position VoxelChunk chunk; int voxelIndex; Voxel voxelCh; if (env.GetVoxelIndex(curPos, out chunk, out voxelIndex, false)) { voxelCh = chunk.voxels [voxelIndex]; } else { voxelCh = Voxel.Empty; } VoxelDefinition voxelChType = env.voxelDefinitions [voxelCh.typeIndex]; if (voxelCh.hasContent == 1) { CheckEnterTrigger(chunk, voxelIndex); CheckDamage(voxelChType); } // Safety check; if voxel at character position is solid, move character on top of terrain if (voxelCh.isSolid) { Unstuck(false); } else { // Check if water surrounds camera Voxel voxelCamera = env.GetVoxel(nearClipPos, false); VoxelDefinition voxelCameraType = env.voxelDefinitions [voxelCamera.typeIndex]; if (voxelCamera.hasContent == 1) { CheckEnterTrigger(chunk, voxelIndex); CheckDamage(voxelCameraType); } if (voxelCamera.GetWaterLevel() > 7) { // More water on top? Vector3 pos1Up = nearClipPos; pos1Up.y += 1f; Voxel voxel1Up = env.GetVoxel(pos1Up); if (voxel1Up.GetWaterLevel() > 0) { isUnderwater = true; waterLevelTop = nearClipPos.y + 1f; } else { waterLevelTop = FastMath.FloorToInt(nearClipPos.y) + 0.9f; isUnderwater = nearClipPos.y < waterLevelTop; isSwimming = !isUnderwater; } underWaterMat.color = voxelCameraType.diveColor; } else if (voxelCh.GetWaterLevel() > 7) // type == env.world.waterVoxel) { { isSwimming = true; waterLevelTop = FastMath.FloorToInt(curPos.y) + 0.9f; underWaterMat.color = voxelChType.diveColor; } underWaterMat.SetFloat("_WaterLevel", waterLevelTop); } isInWater = isSwimming || isUnderwater; if (crouch != null) { // move camera a bit down to simulate swimming position if (!wasInWater && isInWater) { PlayWaterSplashSound(); crouch.localPosition = Misc.vector3down * 0.6f; // crouch } else if (wasInWater && !isInWater) { crouch.localPosition = Misc.vector3zero; } } // Show/hide underwater panel if (isInWater && !underwaterPanel.activeSelf) { underwaterPanel.SetActive(true); } else if (!isInWater && underwaterPanel.activeSelf) { underwaterPanel.SetActive(false); } }
void CheckChunksInRange(long endTime) { if (cachedChunks == null) { return; } int chunkX, chunkY, chunkZ; FastMath.FloorToInt(currentAnchorPos.x / CHUNK_SIZE, currentAnchorPos.y / CHUNK_SIZE, currentAnchorPos.z / CHUNK_SIZE, out chunkX, out chunkY, out chunkZ); int chunkDeltaX = chunkX - lastChunkX; int chunkDeltaY = chunkY - lastChunkY; int chunkDeltaZ = chunkZ - lastChunkZ; bool checkOnlyBorders = true; if (chunkDeltaX < -1 || chunkDeltaX > 1 || chunkDeltaY < -1 || chunkDeltaY > 1 || chunkDeltaZ < -1 || chunkDeltaZ > 1) { checkOnlyBorders = false; } lastChunkX = chunkX; lastChunkY = chunkY; lastChunkZ = chunkZ; bool deltaXZChanged = chunkDeltaX != 0 || chunkDeltaZ != 0; if (!constructorMode && (deltaXZChanged || chunkDeltaY != 0)) { if (deltaXZChanged) { TriggerFarChunksUnloadCheck(); } // Anchor has entered a new chunk if (OnPlayerEnterChunk != null) { OnPlayerEnterChunk(); } // Inform detail generators if (worldHasDetailGenerators) { for (int k = 0; k < world.detailGenerators.Length; k++) { if (world.detailGenerators [k].enabled) { world.detailGenerators [k].ExploreArea(currentAnchorPos, checkOnlyBorders, endTime); } } } } // Check new chunks to render if (checkOnlyBorders) { int chunksXZDistance = _visibleChunksDistance; int chunksYDistance = Mathf.Min(_visibleChunksDistance, 8); visible_xmin = chunkX - chunksXZDistance; visible_xmax = chunkX + chunksXZDistance; visible_zmin = chunkZ - chunksXZDistance; visible_zmax = chunkZ + chunksXZDistance; visible_ymin = chunkY - chunksYDistance; visible_ymax = chunkY + chunksYDistance; CheckNewChunksInFrustum(endTime); } else { visible_xmin = chunkX - forceChunkDistance; visible_xmax = chunkX + forceChunkDistance; visible_zmin = chunkZ - forceChunkDistance; visible_zmax = chunkZ + forceChunkDistance; visible_ymin = chunkY - forceChunkDistance; visible_ymax = chunkY + forceChunkDistance; CheckNewNearChunks(); shouldCheckChunksInFrustum = true; } }
void GetChunkCoordinates(Vector3 position, out int chunkX, out int chunkY, out int chunkZ) { FastMath.FloorToInt(position.x / 16f, position.y / 16f, position.z / 16f, out chunkX, out chunkY, out chunkZ); }
void CheckChunksInRange(long endTime) { if (cachedChunks == null) { return; } int chunkX = FastMath.FloorToInt(currentCamPos.x / 16f); int chunkY = FastMath.FloorToInt(currentCamPos.y / 16f); int chunkZ = FastMath.FloorToInt(currentCamPos.z / 16f); int chunkDeltaX = chunkX - lastChunkX; int chunkDeltaY = chunkY - lastChunkY; int chunkDeltaZ = chunkZ - lastChunkZ; bool checkOnlyBorders = true; if (chunkDeltaX < -1 || chunkDeltaX > 1) { checkOnlyBorders = false; } else if (chunkDeltaY < -1 || chunkDeltaY > 1) { checkOnlyBorders = false; } else if (chunkDeltaZ < -1 || chunkDeltaZ > 1) { checkOnlyBorders = false; } lastChunkX = chunkX; lastChunkY = chunkY; lastChunkZ = chunkZ; if (!constructorMode && (chunkDeltaX != 0 || chunkDeltaY != 0 || chunkDeltaZ != 0)) { // Inform detail generators if (worldHasDetailGenerators) { for (int k = 0; k < world.detailGenerators.Length; k++) { if (world.detailGenerators [k].enabled) { world.detailGenerators [k].ExploreArea(currentCamPos, checkOnlyBorders); } } } } // Check new chunks to render if (checkOnlyBorders) { int chunksXZDistance = _visibleChunksDistance; int chunksYDistance = Mathf.Min(_visibleChunksDistance, 8); visible_xmin = chunkX - chunksXZDistance; visible_xmax = chunkX + chunksXZDistance; visible_zmin = chunkZ - chunksXZDistance; visible_zmax = chunkZ + chunksXZDistance; visible_ymin = chunkY - chunksYDistance; visible_ymax = chunkY + chunksYDistance; CheckNewChunksInFrustum(endTime); } else { visible_xmin = chunkX - forceChunkDistance; visible_xmax = chunkX + forceChunkDistance; visible_zmin = chunkZ - forceChunkDistance; visible_zmax = chunkZ + forceChunkDistance; visible_ymin = chunkY - forceChunkDistance; visible_ymax = chunkY + forceChunkDistance; CheckNewNearChunks(); shouldCheckChunksInFrustum = true; } }
void GenerateChunkMeshDataOneJob(MeshingThread thread) { lock (thread.indicesUpdating) { thread.meshJobMeshDataGenerationIndex++; if (thread.meshJobMeshDataGenerationIndex >= thread.meshJobs.Length) { thread.meshJobMeshDataGenerationIndex = 0; } } VoxelChunk chunk = thread.meshJobs [thread.meshJobMeshDataGenerationIndex].chunk; Voxel [] [] chunk9 = thread.chunk9; chunk9 [13] = chunk.voxels; Voxel [] emptyChunk = chunk.isAboveSurface ? emptyChunkAboveTerrain : emptyChunkUnderground; int chunkX, chunkY, chunkZ; FastMath.FloorToInt(chunk.position.x / CHUNK_SIZE, chunk.position.y / CHUNK_SIZE, chunk.position.z / CHUNK_SIZE, out chunkX, out chunkY, out chunkZ); // Reset bit field; inconclusive neighbours are those neighbours which are undefined when an adjacent chunk is rendered. We mark it so then it's finally rendered, we re-render the adjacent chunk. This is required if the new chunk can // break holes on the edge of the chunk while no lighting is entering the chunk or global illumination is disabled (yes, it's an edge case but must be addressed to avoid gaps in those cases). chunk.inconclusiveNeighbours = 0; //(byte)(chunk.inconclusiveNeighbours & ~CHUNK_IS_INCONCLUSIVE); VoxelChunk [] neighbourChunks = thread.neighbourChunks; neighbourChunks [13] = chunk; thread.allowAO = enableSmoothLighting && !draftModeActive; // AO is disabled on edges of world to reduce vertex count for (int c = 0, y = -1; y <= 1; y++) { int yy = chunkY + y; for (int z = -1; z <= 1; z++) { int zz = chunkZ + z; for (int x = -1; x <= 1; x++, c++) { if (y == 0 && z == 0 && x == 0) { continue; } int xx = chunkX + x; VoxelChunk neighbour; if (GetChunkFast(xx, yy, zz, out neighbour, false) && (neighbour.isPopulated || neighbour.isRendered)) { chunk9 [c] = neighbour.voxels; } else { chunk.inconclusiveNeighbours |= inconclusiveNeighbourTable [c]; chunk9 [c] = emptyChunk; if (y == 0 && !chunk.modified) { // if this chunk has no neighbours horizontally and is not modified, it's probably an edge chunk. // in this case, disable AO so the entire edge wall can be rendered using a single quad thanks to greedy meshing thread.allowAO = false; } } neighbourChunks [c] = neighbour; } } } #if USES_SEE_THROUGH lock (seeThroughLock) { // Hide voxels marked as hidden for (int c = 0; c < neighbourChunks.Length; c++) { ToggleHiddenVoxels(neighbourChunks [c], false); } #endif thread.GenerateMeshData(); #if USES_SEE_THROUGH // Reactivate hidden voxels for (int c = 0; c < neighbourChunks.Length; c++) { ToggleHiddenVoxels(neighbourChunks [c], true); } } #endif }
void GenerateChunkMeshDataOneJob() { meshJobMeshDataGenerationIndex++; if (meshJobMeshDataGenerationIndex >= meshJobs.Length) { meshJobMeshDataGenerationIndex = 0; } VoxelChunk chunk = meshJobs [meshJobMeshDataGenerationIndex].chunk; chunk9 [13] = chunk.voxels; Voxel[] emptyChunk = chunk.isAboveSurface ? emptyChunkAboveTerrain : emptyChunkUnderground; int chunkX = FastMath.FloorToInt(chunk.position.x / 16); int chunkY = FastMath.FloorToInt(chunk.position.y / 16); int chunkZ = FastMath.FloorToInt(chunk.position.z / 16); // Reset bit field; inconclusive neighbours are those neighbours which are undefined when an adjacent chunk is rendered. We mark it so then it's finally rendered, we re-render the adjacent chunk. This is required if the new chunk can // break holes on the edge of the chunk while no lighting is entering the chunk or global illumination is disabled (yes, it's an edge case but must be addressed to avoid gaps in those cases). chunk.inconclusiveNeighbours = 0; //(byte)(chunk.inconclusiveNeighbours & ~CHUNK_IS_INCONCLUSIVE); for (int c = 0, y = -1; y <= 1; y++) { int yy = chunkY + y; for (int z = -1; z <= 1; z++) { int zz = chunkZ + z; for (int x = -1; x <= 1; x++, c++) { if (y == 0 && z == 0 && x == 0) { continue; } int xx = chunkX + x; VoxelChunk neighbour; if (GetChunkFast(xx, yy, zz, out neighbour, false) && (neighbour.isPopulated || neighbour.isRendered)) { chunk9 [c] = neighbour.voxels; } else { chunk.inconclusiveNeighbours |= inconclusiveNeighbourTable [c]; chunk9 [c] = emptyChunk; } } } } // clear buffers if (effectiveUseGeometryShaders) { for (int j = 0; j < MAX_MATERIALS_PER_CHUNK; j++) { tempGeoIndices [j].Clear(); meshJobs [meshJobMeshDataGenerationIndex].buffers [j].indicesCount = 0; } GenerateMeshData_Geo(meshJobMeshDataGenerationIndex); } else { for (int j = 0; j < MAX_MATERIALS_PER_CHUNK; j++) { if (meshJobs [meshJobMeshDataGenerationIndex].buffers [j].indicesCount > 0) { meshJobs [meshJobMeshDataGenerationIndex].buffers [j].indices.Clear(); meshJobs [meshJobMeshDataGenerationIndex].buffers [j].indicesCount = 0; } } GenerateMeshData_Triangle(meshJobMeshDataGenerationIndex); } }