Ejemplo n.º 1
0
 void ChunkRender(VoxelChunk chunk)
 {
     if (FastVector.SqrMinDistanceXZ(chunk.position, transform.position) < 32 * 32)
     {
         requireUpdateLighting = true;
     }
 }
Ejemplo n.º 2
0
        void LateUpdate()
        {
            if (env == null || !env.applicationIsPlaying || !crosshairEnabled)
            {
                return;
            }

            UpdateCrosshairScreenPosition();

            Ray          ray = m_Camera.ScreenPointToRay(input.screenPos);
            VoxelHitInfo hitInfo;

            // Check if there's a voxel in range
            crosshairOnBlock = env.RayCast(ray, out hitInfo) && hitInfo.voxelIndex >= 0;
            if (!input.GetButton(InputButtonNames.Button1) || crosshairHitInfo.GetVoxelNow().isEmpty)
            {
                crosshairHitInfo = hitInfo;
            }
            if (crosshairOnBlock)
            {
                crosshairOnBlock = FastVector.SqrDistance(ref crosshairHitInfo.voxelCenter, ref curPos) < crosshairMaxDistance * crosshairMaxDistance;
                if (!crosshairOnBlock)
                {
                    crosshairHitInfo.Clear();
                }
            }
            if (changeOnBlock)
            {
                if (!crosshairOnBlock)
                {
                    ResetCrosshairPosition();
                    return;
                }
                // Puts crosshair over the voxel but do it only if crosshair won't disappear because of the angle or it's switching from orbit to free mode (or viceversa)
                float d = Vector3.Dot(ray.direction, crosshairHitInfo.normal);
                if (d < -0.2f)
                {
                    crosshair.position = hitInfo.point;
                    crosshair.LookAt(hitInfo.point + crosshairHitInfo.normal);
                }
                else
                {
                    crosshair.localRotation = Misc.quaternionZero;
                }
                crosshairMat.color = crosshairOnTargetColor;
            }
            if (crosshairOnBlock)
            {
                crosshair.localScale = Misc.vector3one * (crosshairScale * (1f - targetAnimationScale * 0.5f + Mathf.PingPong(Time.time * targetAnimationSpeed, targetAnimationScale)));
                env.VoxelHighlight(crosshairHitInfo, voxelHighlightColor, voxelHighlightEdge);
            }
        }
        int DamageAreaFast(Vector3 origin, int damage, int damageRadius = 1, bool distanceAttenuation = true, bool addParticles = true, List <VoxelIndex> results = null)
        {
            bool hasResults = results != null;

            if (hasResults)
            {
                results.Clear();
            }
            if (damageRadius < 0 || damage < 1)
            {
                return(0);
            }

            int          damagedCount = 0;
            Vector3      direction    = Misc.vector3zero;
            VoxelHitInfo hitInfo;

            GetVoxelIndices(origin, damageRadius, tempVoxelIndices);
            int count = tempVoxelIndices.Count;

            for (int k = 0; k < count; k++)
            {
                VoxelIndex vi         = tempVoxelIndices [k];
                VoxelChunk otherChunk = vi.chunk;
                int        otherIndex = vi.voxelIndex;
                int        dam        = damage;
                if (distanceAttenuation && vi.sqrDistance > 1)
                {
                    dam = (int)(damage * damageRadius * damageRadius / vi.sqrDistance);
                }
                if (dam > 0 && GetVoxelVisibility(otherChunk, otherIndex))
                {
                    FastVector.NormalizedDirection(ref origin, ref vi.position, ref direction);
                    if (RayCastFast(origin, direction, out hitInfo, damageRadius, false, 5))
                    {
                        int damageTaken = DamageVoxelFast(ref hitInfo, dam, addParticles, false);
                        if (hasResults)
                        {
                            VoxelIndex di = vi;
                            di.damageTaken = damageTaken;
                            results.Add(di);
                            damagedCount++;
                        }
                    }
                }
            }

            return(damagedCount);
        }
Ejemplo n.º 4
0
        IEnumerator Consolidate()
        {
            if (gameObject == null)
            {
                yield break;
            }
            WaitForSeconds       w = new WaitForSeconds(1f);
            VoxelChunk           targetChunk;
            VoxelPlayEnvironment env = VoxelPlayEnvironment.instance;

            if (env.GetChunk(transform.position, out targetChunk, false))
            {
                const float maxDist = 100 * 100;
                while (FastVector.SqrDistanceByValue(targetChunk.position, env.cameraMain.transform.position) < maxDist && env.ChunkIsInFrustum(targetChunk))
                {
                    yield return(w);
                }
                env.VoxelCancelDynamic(this);
            }
        }
        bool HitVoxelFast(Vector3 origin, Vector3 direction, int damage, out VoxelHitInfo hitInfo, float maxDistance = 0, int damageRadius = 1, bool addParticles = true, bool playSound = true, bool allowDamageEvent = true)
        {
            RayCastFast(origin, direction, out hitInfo, maxDistance, false, 0, ColliderTypes.IgnorePlayer);
            VoxelChunk chunk = hitInfo.chunk;

            if (chunk == null || hitInfo.voxelIndex < 0)
            {
                lastHitInfo.chunk      = null;
                lastHitInfo.voxelIndex = -1;
                return(false);
            }

            lastHitInfo = hitInfo;
            DamageVoxelFast(ref hitInfo, damage, addParticles, playSound, allowDamageEvent);

            bool button1Pressed = input.GetButton(InputButtonNames.Button1);
            bool button2Pressed = input.GetButton(InputButtonNames.Button2);

            if ((button1Pressed || button2Pressed) && OnVoxelClick != null)
            {
                OnVoxelClick(chunk, hitInfo.voxelIndex, button1Pressed ? 0 : 1, hitInfo);
            }

            if (damageRadius > 1)
            {
                Vector3 otherPos;
                Vector3 explosionPosition = hitInfo.voxelCenter + hitInfo.normal * damageRadius;
                damageRadius--;

                for (int y = -damageRadius; y <= damageRadius; y++)
                {
                    otherPos.y = lastHitInfo.voxelCenter.y + y;
                    for (int z = -damageRadius; z <= damageRadius; z++)
                    {
                        otherPos.z = lastHitInfo.voxelCenter.z + z;
                        for (int x = -damageRadius; x <= damageRadius; x++)
                        {
                            if (x == 0 && z == 0 && y == 0)
                            {
                                continue;
                            }
                            VoxelChunk otherChunk;
                            int        otherIndex;
                            otherPos.x = lastHitInfo.voxelCenter.x + x;
                            if (GetVoxelIndex(otherPos, out otherChunk, out otherIndex, false))
                            {
                                if (GetVoxelVisibility(otherChunk, otherIndex))
                                {
                                    FastVector.NormalizedDirection(ref explosionPosition, ref otherPos, ref direction);
                                    if (RayCast(explosionPosition, direction, out hitInfo))
                                    {
                                        DamageVoxelFast(ref hitInfo, damage, addParticles, playSound, allowDamageEvent);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return(true);
        }
        private void FixedUpdate()
        {
            if (!hasCharacterController)
            {
                return;
            }

            float speed;

            GetInput(out speed);

            Vector3 pos = transform.position;

            if (isFlying || isInWater)
            {
                m_MoveDir  = m_Camera.transform.forward * m_Input.y + m_Camera.transform.right * m_Input.x + m_Camera.transform.up * m_Input.z;
                m_MoveDir *= speed;
                if (!isFlying)
                {
                    if (m_MoveDir.y < 0)
                    {
                        m_MoveDir.y += 0.1f * Time.fixedDeltaTime;
                    }
                    if (m_Jump)
                    {
                        // Check if player is next to terrain
                        if (env.CheckCollision(new Vector3(pos.x + m_Camera.transform.forward.x, pos.y, pos.z + m_Camera.transform.forward.z)))
                        {
                            m_MoveDir.y = jumpSpeed * 0.5f;
                            m_Jumping   = true;
                        }
                        m_Jump = false;
                    }
                    else
                    {
                        m_MoveDir += Physics.gravity * gravityMultiplier * Time.fixedDeltaTime * 0.5f;
                    }
                    if (pos.y > waterLevelTop && m_MoveDir.y > 0)
                    {
                        m_MoveDir.y = 0;                         // do not exit water
                    }
                    ProgressSwimCycle(m_CharacterController.velocity, swimSpeed);
                }
            }
            else
            {
                // always move along the camera forward as it is the direction that it being aimed at
                Vector3 desiredMove = transform.forward * m_Input.y + transform.right * m_Input.x;

                // get a normal for the surface that is being touched to move along it
                RaycastHit hitInfo;
                Physics.SphereCast(pos, m_CharacterController.radius, Misc.vector3down, out hitInfo,
                                   characterHeight / 2f, Physics.AllLayers, QueryTriggerInteraction.Ignore);
                desiredMove = Vector3.ProjectOnPlane(desiredMove, hitInfo.normal).normalized;

                m_MoveDir.x = desiredMove.x * speed;
                m_MoveDir.z = desiredMove.z * speed;
                if (m_CharacterController.isGrounded)
                {
                    m_MoveDir.y = -stickToGroundForce;

                    if (m_Jump)
                    {
                        m_MoveDir.y = jumpSpeed;
                        PlayJumpSound();
                        m_Jump    = false;
                        m_Jumping = true;
                    }
                }
                else
                {
                    m_MoveDir += Physics.gravity * gravityMultiplier * Time.fixedDeltaTime;
                }

                UpdateCameraPosition(speed);
                ProgressStepCycle(m_CharacterController.velocity, speed);
            }


            Vector3 finalMove = m_MoveDir * Time.fixedDeltaTime;
            Vector3 newPos    = pos + finalMove;
            bool    canMove   = !limitBoundsEnabled || limitBounds.Contains(newPos);

            if (m_PreviouslyGrounded && !isFlying && isCrouched)
            {
                // check if player is beyond the edge
                Ray ray = new Ray(newPos, Misc.vector3down);
                canMove = Physics.SphereCast(ray, 0.3f, 1f);
                // if player can't move, clamp movement along the edge and check again
                if (!canMove)
                {
                    if (Mathf.Abs(m_MoveDir.z) > Mathf.Abs(m_MoveDir.x))
                    {
                        m_MoveDir.x = 0;
                    }
                    else
                    {
                        m_MoveDir.z = 0;
                    }
                    finalMove  = m_MoveDir * Time.fixedDeltaTime;
                    newPos     = pos + finalMove;
                    ray.origin = newPos;
                    canMove    = Physics.SphereCast(ray, 0.3f, 1f);
                }
            }

            // if constructor is enabled, disable any movement if control key is pressed (reserved for special constructor actions)
            if (env.constructorMode && input.GetButton(InputButtonNames.LeftControl))
            {
                canMove = false;
            }
            if (canMove)
            {
                // autoclimb
                Vector3 dir     = new Vector3(m_MoveDir.x, 0, m_MoveDir.z);
                Vector3 basePos = new Vector3(pos.x, pos.y - characterHeight * 0.25f, pos.z);
                Ray     ray     = new Ray(basePos, dir);
                if (Physics.SphereCast(ray, 0.3f, 1f))
                {
                    m_CharacterController.stepOffset = 1.1f;
                }
                else
                {
                    m_CharacterController.stepOffset = 0.2f;
                }
                m_CollisionFlags = m_CharacterController.Move(finalMove);
            }
            isGrounded = m_CharacterController.isGrounded;

            // Check limits
            if (orbitMode)
            {
                if (FastVector.ClampDistance(ref lookAt, ref pos, minDistance, maxDistance))
                {
                    m_CharacterController.transform.position = pos;
                }
            }

            mouseLook.UpdateCursorLock();

            if (!isGrounded && !isFlying)
            {
                // Check current chunk
                VoxelChunk chunk = env.GetCurrentChunk();
                if (chunk != null && !chunk.isRendered)
                {
                    WaitForCurrentChunk();
                    return;
                }
            }
        }
Ejemplo n.º 7
0
 void CheckChunksVisibleDistanceLoop(long maxFrameTime)
 {
     try {
         bool eventOut = OnChunkExitVisibleDistance != null;
         bool eventIn  = OnChunkEnterVisibleDistance != null;
         int  max      = checkChunksVisibleDistanceIndex + 200;
         if (max >= chunksPoolLoadIndex)
         {
             max = chunksPoolLoadIndex;
         }
         float visibleDistanceSqr = (_visibleChunksDistance + 1) * CHUNK_SIZE;
         visibleDistanceSqr *= visibleDistanceSqr;
         while (checkChunksVisibleDistanceIndex < max)
         {
             VoxelChunk chunk = chunksPool[checkChunksVisibleDistanceIndex];
             if (chunk.isRendered && !chunk.isCloud)
             {
                 float dist = FastVector.SqrMaxDistanceXorZ(ref chunk.position, ref currentAnchorPos);
                 if (dist > visibleDistanceSqr)
                 {
                     if (chunk.visibleDistanceStatus != ChunkVisibleDistanceStatus.OutOfVisibleDistance)
                     {
                         chunk.visibleDistanceStatus = ChunkVisibleDistanceStatus.OutOfVisibleDistance;
                         if (unloadFarChunks || eventOut)
                         {
                             if (unloadFarChunks)
                             {
                                 chunk.gameObject.SetActive(false);
                             }
                             if (eventOut)
                             {
                                 OnChunkExitVisibleDistance(chunk);
                             }
                             if (stopWatch.ElapsedMilliseconds >= maxFrameTime)
                             {
                                 break;
                             }
                         }
                     }
                 }
                 else if (chunk.visibleDistanceStatus != ChunkVisibleDistanceStatus.WithinVisibleDistance)
                 {
                     chunk.visibleDistanceStatus = ChunkVisibleDistanceStatus.WithinVisibleDistance;
                     if (unloadFarChunks || eventOut)
                     {
                         if (unloadFarChunks)
                         {
                             chunk.gameObject.SetActive(true);
                         }
                         if (eventIn)
                         {
                             OnChunkEnterVisibleDistance(chunk);
                         }
                         if (stopWatch.ElapsedMilliseconds >= maxFrameTime)
                         {
                             break;
                         }
                     }
                 }
             }
             checkChunksVisibleDistanceIndex++;
         }
         if (checkChunksVisibleDistanceIndex >= chunksPoolLoadIndex)
         {
             checkChunksVisibleDistanceIndex = -1;
         }
     } catch (Exception ex) {
         ShowExceptionMessage(ex);
     }
 }
Ejemplo n.º 8
0
        void LateUpdate()
        {
            Collider collider = null;

            if (env != null && env.characterController != null)
            {
                // Check object on the crosshair
                collider = env.characterController.crosshairHitInfo.collider;
                if (env.input.GetButtonDown(InputButtonNames.Action))
                {
                    if (collider != null)
                    {
                        VoxelPlayInteractiveObject obj = collider.GetComponentInChildren <VoxelPlayInteractiveObject> ();
                        if (obj != null)
                        {
                            if (obj.triggerNearbyObjects)
                            {
                                for (int k = 0; k < nearCount; k++)
                                {
                                    obj = nearObjs [k];
                                    if (obj != null && obj.playerIsNear)
                                    {
                                        nearObjs [k].OnPlayerAction();
                                    }
                                }
                            }
                            else if (obj.playerIsNear)
                            {
                                obj.OnPlayerAction();
                            }
                        }
                    }
                }
            }

            // Check nearby objects

            // Get player position
            Vector3 playerPos = env.currentAnchorPos;

            // Check if player has moved since last frame
            int playerPosX = (int)playerPos.x;
            int playerPosY = (int)playerPos.y;
            int playerPosZ = (int)playerPos.z;

            if (playerPosX == lastPlayerPosX && playerPosY == lastPlayerPosY && playerPosZ == lastPlayerPosZ && collider == lastCollider)
            {
                return;
            }
            lastCollider   = collider;
            lastPlayerPosX = playerPosX;
            lastPlayerPosY = playerPosY;
            lastPlayerPosZ = playerPosZ;

            // Check if player enters/exits the interaction area per object
            for (int k = 0; k < count; k++)
            {
                VoxelPlayInteractiveObject o = objs [k];
                if (o != null && o.enabled)
                {
                    Vector3 objPos = o.transform.position;
                    float   interactionDistanceSqr = o.interactionDistance * o.interactionDistance;
                    float   dist   = FastVector.SqrDistance(ref playerPos, ref objPos);
                    bool    isNear = dist <= interactionDistanceSqr;
                    if (o.playerIsNear && !isNear)
                    {
                        o.playerIsNear = false;
                        // Remove from near list
                        nearObjs [o.nearIndex] = null;
                        if (o.nearIndex == nearCount - 1)
                        {
                            nearCount--;
                        }
                        o.nearIndex = 0;
                        // Call event
                        o.OnPlayerGoesAway();
                    }
                    else if (isNear && !o.playerIsNear)
                    {
                        o.playerIsNear = true;
                        o.nearIndex    = AddToDynamicList(o, ref nearObjs, ref nearCount);
                        // Call event
                        o.OnPlayerApproach();
                    }
                }
            }
        }
        void ModelPlace(Vector3 position, ModelDefinition model, ref Bounds bounds, int rotationDegrees = 0, float colorBrightness = 1f, bool fitTerrain = false, List <VoxelIndex> indices = null, int indexStart = -1, int indexEnd = -1)
        {
            if (model == null)
            {
                return;
            }
            if (indexStart < 0)
            {
                indexStart = 0;
            }
            if (indexEnd < 0)
            {
                indexEnd = model.bits.Length - 1;
            }

            Vector3 pos;
            int     modelOneYRow = model.sizeZ * model.sizeX;
            int     modelOneZRow = model.sizeX;
            int     halfSizeX    = model.sizeX / 2;
            int     halfSizeZ    = model.sizeZ / 2;

            if (rotationDegrees == 360)
            {
                switch (UnityEngine.Random.Range(0, 4))
                {
                case 0:
                    rotationDegrees = 90;
                    break;

                case 1:
                    rotationDegrees = 180;
                    break;

                case 2:
                    rotationDegrees = 270;
                    break;
                }
            }

            bool indicesProvided = indices != null;

            if (indicesProvided && indexStart < 0 && indexEnd < 0)
            {
                indices.Clear();
            }
            VoxelIndex index     = new VoxelIndex();
            VoxelChunk lastChunk = null;
            int        tmp;
            Vector3    min = bounds.min;
            Vector3    max = bounds.max;

            for (int b = indexStart; b <= indexEnd; b++)
            {
                int bitIndex = model.bits [b].voxelIndex;
                int py       = bitIndex / modelOneYRow;
                int remy     = bitIndex - py * modelOneYRow;
                int pz       = remy / modelOneZRow;
                int px       = remy - pz * modelOneZRow;
                switch (rotationDegrees)
                {
                case 90:
                    tmp = px;
                    px  = halfSizeZ - pz;
                    pz  = halfSizeX - tmp;
                    break;

                case 180:
                    px = halfSizeX - px;
                    pz = halfSizeZ - pz;
                    break;

                case 270:
                    tmp = px;
                    px  = pz - halfSizeZ;
                    pz  = tmp - halfSizeX;
                    break;

                default:
                    px -= halfSizeX;
                    pz -= halfSizeZ;
                    break;
                }

                pos.x = position.x + model.offsetX + px;
                pos.y = position.y + model.offsetY + py;
                pos.z = position.z + model.offsetZ + pz;

                VoxelChunk chunk;
                int        voxelIndex;
                if (GetVoxelIndex(pos, out chunk, out voxelIndex))
                {
                    Color32         color      = model.bits [b].finalColor;
                    VoxelDefinition vd         = model.bits [b].voxelDefinition ?? defaultVoxel;
                    bool            emptyVoxel = model.bits [b].isEmpty;
                    if (emptyVoxel)
                    {
                        chunk.voxels [voxelIndex] = Voxel.Empty;
                    }
                    else
                    {
                        if (colorBrightness != 1f)
                        {
                            color.r = (byte)(color.r * colorBrightness);
                            color.g = (byte)(color.g * colorBrightness);
                            color.b = (byte)(color.b * colorBrightness);
                        }
                        chunk.voxels [voxelIndex].Set(vd, color);
                        // Add index
                        if (indicesProvided)
                        {
                            index.chunk      = chunk;
                            index.voxelIndex = voxelIndex;
                            index.position   = pos;
                            indices.Add(index);
                        }
                        if (pos.x < min.x)
                        {
                            min.x = pos.x;
                        }
                        if (pos.y < min.y)
                        {
                            min.y = pos.y;
                        }
                        if (pos.z < min.z)
                        {
                            min.z = pos.z;
                        }
                        if (pos.x > max.x)
                        {
                            max.x = pos.x;
                        }
                        if (pos.y > max.y)
                        {
                            max.y = pos.y;
                        }
                        if (pos.z > max.z)
                        {
                            max.z = pos.z;
                        }
                    }

                    // Prevent tree population
                    chunk.allowTrees = false;
                    chunk.modified   = true;

                    if (fitTerrain && !emptyVoxel)
                    {
                        // Fill beneath row 1
                        if (py == 0)
                        {
                            Vector3 under = pos;
                            under.y -= 1;
                            for (int k = 0; k < 100; k++, under.y--)
                            {
                                VoxelChunk lowChunk;
                                int        vindex;
                                GetVoxelIndex(under, out lowChunk, out vindex, false);
                                if (lowChunk != null && lowChunk.voxels [vindex].opaque < FULL_OPAQUE)
                                {
                                    lowChunk.voxels [vindex].Set(vd, color);
                                    if (lowChunk != lastChunk)
                                    {
                                        lastChunk = lowChunk;
                                        if (!lastChunk.inqueue)
                                        {
                                            ChunkRequestRefresh(lastChunk, true, true);
                                        }
                                    }
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }
                    }

                    if (chunk != lastChunk)
                    {
                        lastChunk = chunk;
                        if (!lastChunk.inqueue)
                        {
                            lastChunk.MarkAsInconclusive();
                            ChunkRequestRefresh(lastChunk, true, true);
                        }
                    }
                }
            }

            FastVector.Floor(ref min);
            FastVector.Ceiling(ref max);
            bounds.center = (max + min) * 0.5f;
            bounds.size   = max - min;
        }
        void ModelPlace(Vector3 position, ModelDefinition model, ref Bounds bounds, int rotationDegrees = 0, float colorBrightness = 1f, bool fitTerrain = false, List <VoxelIndex> indices = null, int indexStart = -1, int indexEnd = -1, bool useUnpopulatedChunks = false, bool refreshChunks = true)
        {
            if (model == null)
            {
                return;
            }
            if (indexStart < 0)
            {
                indexStart = 0;
            }
            if (indexEnd < 0)
            {
                indexEnd = model.bits.Length - 1;
            }

            Vector3 pos;
            int     modelOneYRow = model.sizeZ * model.sizeX;
            int     modelOneZRow = model.sizeX;

            if (rotationDegrees == 360)
            {
                switch (WorldRand.Range(0, 4))
                {
                case 1:
                    rotationDegrees = 90;
                    break;

                case 2:
                    rotationDegrees = 180;
                    break;

                case 3:
                    rotationDegrees = 270;
                    break;
                }
            }

            int     halfSizeX = model.sizeX / 2;
            int     halfSizeZ = model.sizeZ / 2;
            Vector3 zeroPos   = Quaternion.Euler(0, rotationDegrees, 0) * new Vector3(-halfSizeX, 0, -halfSizeZ);

            // ensure all voxel definitions are present
            //bool reloadTextures = false;
            for (int b = indexStart; b <= indexEnd; b++)
            {
                VoxelDefinition vd = model.bits [b].voxelDefinition;
                if (vd != null && vd.index == 0)
                {
                    AddVoxelDefinition(vd);
                    //reloadTextures = true;
                }
            }
            //if (reloadTextures) { // TODO: RML (AddVoxelDefinition already marks reload world textures flag)
            //    LoadWorldTextures();
            //}

            bool indicesProvided = indices != null;

            if (indicesProvided && indexStart < 0 && indexEnd < 0)
            {
                indices.Clear();
            }
            VoxelIndex index = new VoxelIndex();
            Vector3    min   = bounds.min;
            Vector3    max   = bounds.max;

            for (int b = indexStart; b <= indexEnd; b++)
            {
                int bitIndex = model.bits [b].voxelIndex;
                int py       = bitIndex / modelOneYRow;
                int remy     = bitIndex - py * modelOneYRow;
                int pz       = remy / modelOneZRow;
                int px       = remy - pz * modelOneZRow;

                float wx = zeroPos.x, wz = zeroPos.z;
                switch (rotationDegrees)
                {
                case 90:
                    wx += pz;
                    wz -= px;
                    break;

                case 180:
                    wx -= px;
                    wz -= pz;
                    break;

                case 270:
                    wx -= pz;
                    wz += px;
                    break;

                default:
                    wx += px;
                    wz += pz;
                    break;
                }

                pos.x = position.x + model.offsetX + wx;
                pos.y = position.y + model.offsetY + py;
                pos.z = position.z + model.offsetZ + wz;

                VoxelChunk chunk;
                int        voxelIndex;
                if (useUnpopulatedChunks)
                {
                    chunk = GetChunkUnpopulated(pos);
                }
                if (GetVoxelIndex(pos, out chunk, out voxelIndex))
                {
                    bool emptyVoxel = model.bits [b].isEmpty;
                    if (emptyVoxel)
                    {
                        chunk.voxels [voxelIndex] = Voxel.Hole;
                    }
                    else
                    {
                        Color32 color = model.bits [b].finalColor;
                        if (colorBrightness != 1f)
                        {
                            color.r = (byte)(color.r * colorBrightness);
                            color.g = (byte)(color.g * colorBrightness);
                            color.b = (byte)(color.b * colorBrightness);
                        }
                        VoxelDefinition vd = model.bits [b].voxelDefinition ?? defaultVoxel;
                        chunk.voxels [voxelIndex].Set(vd, color);
                        float rotation = (model.bits [b].rotation + 360 + rotationDegrees) % 360;
                        if (rotation != 0)
                        {
                            chunk.voxels [voxelIndex].SetTextureRotation(Voxel.GetTextureRotationFromDegrees(rotation));
                        }

                        // Add index
                        if (indicesProvided)
                        {
                            index.chunk      = chunk;
                            index.voxelIndex = voxelIndex;
                            index.position   = pos;
                            indices.Add(index);
                        }
                        if (pos.x < min.x)
                        {
                            min.x = pos.x;
                        }
                        if (pos.y < min.y)
                        {
                            min.y = pos.y;
                        }
                        if (pos.z < min.z)
                        {
                            min.z = pos.z;
                        }
                        if (pos.x > max.x)
                        {
                            max.x = pos.x;
                        }
                        if (pos.y > max.y)
                        {
                            max.y = pos.y;
                        }
                        if (pos.z > max.z)
                        {
                            max.z = pos.z;
                        }

                        if (fitTerrain)
                        {
                            // Fill beneath row 1
                            if (py == 0)
                            {
                                Vector3 under = pos;
                                under.y -= 1;
                                for (int k = 0; k < 100; k++, under.y--)
                                {
                                    VoxelChunk lowChunk;
                                    int        vindex;
                                    GetVoxelIndex(under, out lowChunk, out vindex, false);
                                    if (lowChunk != null && lowChunk.voxels [vindex].opaque < FULL_OPAQUE)
                                    {
                                        lowChunk.voxels [vindex].Set(vd, color);
                                        lowChunk.modified = true;
                                        if (!lowChunk.inqueue && !useUnpopulatedChunks)
                                        {
                                            ChunkRequestRefresh(lowChunk, true, true);
                                        }
                                    }
                                    else
                                    {
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    // Prevent tree population
                    chunk.allowTrees = false;
                    chunk.modified   = true;

                    if (!chunk.inqueue && !useUnpopulatedChunks)
                    {
                        chunk.MarkAsInconclusive();
                        if (refreshChunks)
                        {
                            ChunkRequestRefresh(chunk, true, true);
                        }
                    }
                }
            }

            FastVector.Floor(ref min);
            FastVector.Ceiling(ref max);
            bounds.center = (max + min) * 0.5f;
            bounds.size   = max - min;
        }
        void ModelPlaceTorches(Vector3 position, ModelDefinition model, int rotationDegrees = 0)
        {
            if (model == null || model.torches == null)
            {
                return;
            }
            FastVector.Floor(ref position);
            Vector3 pos;
            int     modelOneYRow = model.sizeZ * model.sizeX;
            int     modelOneZRow = model.sizeX;
            int     halfSizeX    = model.sizeX / 2;
            int     halfSizeZ    = model.sizeZ / 2;

            if (rotationDegrees == 360)
            {
                switch (UnityEngine.Random.Range(0, 4))
                {
                case 0:
                    rotationDegrees = 90;
                    break;

                case 1:
                    rotationDegrees = 180;
                    break;

                case 2:
                    rotationDegrees = 270;
                    break;
                }
            }

            // ensure all voxel definitions are present
            int tmp;

            for (int b = 0; b < model.torches.Length; b++)
            {
                int     bitIndex = model.torches [b].voxelIndex;
                int     py       = bitIndex / modelOneYRow;
                int     remy     = bitIndex - py * modelOneYRow;
                int     pz       = remy / modelOneZRow;
                int     px       = remy - pz * modelOneZRow;
                Vector3 normal   = model.torches [b].normal;
                switch (rotationDegrees)
                {
                case 90:
                    tmp      = px;
                    px       = halfSizeZ - pz;
                    pz       = halfSizeX - tmp;
                    tmp      = (int)normal.x;
                    normal.x = normal.z;
                    normal.z = -tmp;
                    break;

                case 180:
                    px       = halfSizeX - px;
                    pz       = halfSizeZ - pz;
                    normal.x = -normal.x;
                    normal.z = -normal.z;
                    break;

                case 270:
                    tmp      = px;
                    px       = pz - halfSizeZ;
                    pz       = tmp - halfSizeX;
                    tmp      = (int)normal.x;
                    normal.x = normal.z;
                    normal.z = tmp;
                    break;

                default:
                    px -= halfSizeX;
                    pz -= halfSizeZ;
                    break;
                }

                pos.x = position.x + model.offsetX + px;
                pos.y = position.y + model.offsetY + py;
                pos.z = position.z + model.offsetZ + pz;
                pos  -= normal;

                VoxelChunk   chunk;
                int          voxelIndex;
                VoxelHitInfo hitInfo = new VoxelHitInfo();
                if (GetVoxelIndex(pos, out chunk, out voxelIndex))
                {
                    hitInfo.chunk       = chunk;
                    hitInfo.voxelIndex  = voxelIndex;
                    hitInfo.normal      = normal;
                    hitInfo.voxelCenter = pos + Misc.vector3half;
                    TorchAttach(hitInfo, model.torches [b].itemDefinition, false);
                }
            }
        }