/* * Copyright 2013 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.world.chunks.internal; import com.google.common.base.Objects; import com.google.common.collect.Sets; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.logic.location.LocationComponent; import org.terasology.math.ChunkMath; import org.terasology.math.Region3i; import org.terasology.math.geom.Vector3i; import org.terasology.world.chunks.Chunk; import org.terasology.world.chunks.ChunkRegionListener; import java.util.Iterator; import java.util.Set; /** */ public class ChunkRelevanceRegion { private EntityRef entity; private Vector3i relevanceDistance = new Vector3i(); private boolean dirty; private Vector3i center = new Vector3i(); private Region3i currentRegion = Region3i.EMPTY; private Region3i previousRegion = Region3i.EMPTY; private ChunkRegionListener listener; private Set<Vector3i> relevantChunks = Sets.newLinkedHashSet(); public ChunkRelevanceRegion(EntityRef entity, Vector3i relevanceDistance) { this.entity = entity; this.relevanceDistance.set(relevanceDistance); LocationComponent loc = entity.getComponent(LocationComponent.class); if (loc == null) { dirty = false; } else { center.set(ChunkMath.calcChunkPos(loc.getWorldPosition())); currentRegion = calculateRegion(); dirty = true; } } public Vector3i getCenter() { return new Vector3i(center); } public void setRelevanceDistance(Vector3i distance) { if (!distance.equals(this.relevanceDistance)) { reviewRelevantChunks(distance); this.relevanceDistance.set(distance); this.currentRegion = calculateRegion(); dirty = true; } } private void reviewRelevantChunks(Vector3i distance) { Vector3i extents = new Vector3i(distance.x / 2, distance.y / 2, distance.z / 2); Region3i retainRegion = Region3i.createFromCenterExtents(center, extents); Iterator<Vector3i> iter = relevantChunks.iterator(); while (iter.hasNext()) { Vector3i pos = iter.next(); if (!retainRegion.encompasses(pos)) { sendChunkIrrelevant(pos); iter.remove(); } } } public boolean isValid() { return entity.hasComponent(LocationComponent.class); } public boolean isDirty() { return dirty; } public void setUpToDate() { dirty = false; previousRegion = currentRegion; } public Region3i getCurrentRegion() { return currentRegion; } public Region3i getPreviousRegion() { return previousRegion; } public void update() { if (!isValid()) { dirty = false; } else { Vector3i newCenter = calculateCenter(); if (!newCenter.equals(center)) { dirty = true; center.set(newCenter); currentRegion = calculateRegion(); reviewRelevantChunks(relevanceDistance); } } } private Region3i calculateRegion() { LocationComponent loc = entity.getComponent(LocationComponent.class); if (loc != null) { Vector3i extents = new Vector3i(relevanceDistance.x / 2, relevanceDistance.y / 2, relevanceDistance.z / 2); return Region3i.createFromCenterExtents(ChunkMath.calcChunkPos(loc.getWorldPosition()), extents); } return Region3i.EMPTY; } private Vector3i calculateCenter() { LocationComponent loc = entity.getComponent(LocationComponent.class); if (loc != null) { return ChunkMath.calcChunkPos(loc.getWorldPosition()); } return new Vector3i(); } public void setListener(ChunkRegionListener listener) { this.listener = listener; } private void sendChunkRelevant(Chunk chunk) { if (listener != null) { listener.onChunkRelevant(chunk.getPosition(), chunk); } } private void sendChunkIrrelevant(Vector3i pos) { if (listener != null) { listener.onChunkIrrelevant(pos); } } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o instanceof ChunkRelevanceRegion) { ChunkRelevanceRegion other = (ChunkRelevanceRegion) o; return Objects.equal(other.entity, entity); } return false; } @Override public int hashCode() { return Objects.hashCode(entity); } /** * Checks if the chunk belongs to this relevance region and adds it to it if it is relevant. * * This method does explictly not care for the readyness of the chunk (light calcualted) or not: The light * calculation gets only performed once the adjacent chunks got loaded. So if wait for the light calculation * before we mark a chunk as relevant for a client then we would transfer less chunks to the client then the * relevance region is large. the client would then again perform the light calculation too based on that * reduced chunk count and would reduce the view distance again. That is why it makes sense to detect * chunks as relevant even when no light calculation has been performed yet. */ public void checkIfChunkIsRelevant(Chunk chunk) { if (currentRegion.encompasses(chunk.getPosition()) && !relevantChunks.contains(chunk.getPosition())) { relevantChunks.add(chunk.getPosition()); sendChunkRelevant(chunk); } } public Iterable<Vector3i> getNeededChunks() { return NeededChunksIterator::new; } public void chunkUnloaded(Vector3i pos) { if (relevantChunks.contains(pos)) { relevantChunks.remove(pos); sendChunkIrrelevant(pos); } } private class NeededChunksIterator implements Iterator<Vector3i> { Vector3i nextChunkPos; Iterator<Vector3i> regionPositions = currentRegion.iterator(); NeededChunksIterator() { calculateNext(); } @Override public boolean hasNext() { return nextChunkPos != null; } @Override public Vector3i next() { Vector3i result = nextChunkPos; calculateNext(); return result; } @Override public void remove() { throw new UnsupportedOperationException(); } private void calculateNext() { nextChunkPos = null; while (regionPositions.hasNext() && nextChunkPos == null) { Vector3i candidate = regionPositions.next(); if (!relevantChunks.contains(candidate)) { nextChunkPos = candidate; } } } } }