/* * 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.rendering.logic; import com.bulletphysics.linearmath.Transform; import com.google.common.collect.HashMultimap; import com.google.common.collect.SetMultimap; import java.nio.FloatBuffer; import java.util.Arrays; import java.util.Set; import org.lwjgl.BufferUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.config.Config; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.entitySystem.entity.lifecycleEvents.BeforeDeactivateComponent; import org.terasology.entitySystem.entity.lifecycleEvents.OnActivatedComponent; import org.terasology.entitySystem.entity.lifecycleEvents.OnChangedComponent; import org.terasology.entitySystem.event.ReceiveEvent; import org.terasology.entitySystem.systems.BaseComponentSystem; import org.terasology.entitySystem.systems.RegisterMode; import org.terasology.entitySystem.systems.RegisterSystem; import org.terasology.entitySystem.systems.RenderSystem; import org.terasology.logic.location.LocationComponent; import org.terasology.logic.players.LocalPlayer; import org.terasology.math.AABB; import org.terasology.math.MatrixUtils; import org.terasology.math.VecMath; import org.terasology.math.geom.Matrix4f; import org.terasology.math.geom.Quat4f; import org.terasology.math.geom.Vector3f; import org.terasology.network.ClientComponent; import org.terasology.network.NetworkSystem; import org.terasology.registry.In; import org.terasology.rendering.assets.material.Material; import org.terasology.rendering.opengl.OpenGLMesh; import org.terasology.rendering.world.WorldRenderer; import org.terasology.world.WorldProvider; /** * TODO: This should be made generic (no explicit shader or mesh) and ported directly into WorldRenderer? Later note: some GelCube functionality moved to a module */ @RegisterSystem(RegisterMode.CLIENT) public class MeshRenderer extends BaseComponentSystem implements RenderSystem { private static final Logger logger = LoggerFactory.getLogger(MeshRenderer.class); @In private NetworkSystem network; @In private LocalPlayer localPlayer; @In private Config config; @In private WorldRenderer worldRenderer; @In private WorldProvider worldProvider; private NearestSortingList opaqueMeshSorter = new NearestSortingList(); private NearestSortingList translucentMeshSorter = new NearestSortingList(); private int lastRendered; @Override public void initialise() { opaqueMeshSorter.initialise(worldRenderer.getActiveCamera()); translucentMeshSorter.initialise(worldRenderer.getActiveCamera()); } @Override public void shutdown() { opaqueMeshSorter.stop(); translucentMeshSorter.stop(); } @ReceiveEvent(components = {MeshComponent.class, LocationComponent.class}) public void onNewMesh(OnActivatedComponent event, EntityRef entity) { addMesh(entity); } private boolean isHidden(EntityRef entity, MeshComponent mesh) { if (!mesh.hideFromOwner) { return false; } ClientComponent owner = network.getOwnerEntity(entity).getComponent(ClientComponent.class); return (owner != null && owner.local); } private void addMesh(EntityRef entity) { MeshComponent meshComponent = entity.getComponent(MeshComponent.class); if (meshComponent != null && meshComponent.material != null) { if (meshComponent.translucent) { translucentMeshSorter.add(entity); } else { opaqueMeshSorter.add(entity); } } } @ReceiveEvent(components = MeshComponent.class) public void onChangeMesh(OnChangedComponent event, EntityRef entity) { removeMesh(entity); if (entity.hasComponent(LocationComponent.class)) { addMesh(entity); } } private void removeMesh(EntityRef entity) { MeshComponent meshComponent = entity.getComponent(MeshComponent.class); if (meshComponent != null && meshComponent.material != null) { if (meshComponent.translucent) { translucentMeshSorter.remove(entity); } else { opaqueMeshSorter.remove(entity); } } } @ReceiveEvent(components = {MeshComponent.class, LocationComponent.class}) public void onDestroyMesh(BeforeDeactivateComponent event, EntityRef entity) { removeMesh(entity); } @Override public void renderAlphaBlend() { if (config.getRendering().isRenderNearest()) { renderEntities(Arrays.asList(translucentMeshSorter.getNearest(config.getRendering().getMeshLimit()))); } else { renderEntities(translucentMeshSorter.getEntities()); } } public void renderOpaque() { if (config.getRendering().isRenderNearest()) { renderEntities(Arrays.asList(opaqueMeshSorter.getNearest(config.getRendering().getMeshLimit()))); } else { renderEntities(opaqueMeshSorter.getEntities()); } } private void renderEntities(Iterable<EntityRef> entityRefs) { SetMultimap<Material, EntityRef> entitiesToRender = HashMultimap.create(); for (EntityRef entity : entityRefs) { MeshComponent meshComponent = entity.getComponent(MeshComponent.class); if (meshComponent != null && meshComponent.material != null) { entitiesToRender.put(meshComponent.material, entity); } } renderEntitiesByMaterial(entitiesToRender); } private void renderEntitiesByMaterial(SetMultimap<Material, EntityRef> meshByMaterial) { Vector3f cameraPosition = worldRenderer.getActiveCamera().getPosition(); Quat4f worldRot = new Quat4f(); Vector3f worldPos = new Vector3f(); Transform transWorldSpace = new Transform(); FloatBuffer tempMatrixBuffer44 = BufferUtils.createFloatBuffer(16); FloatBuffer tempMatrixBuffer33 = BufferUtils.createFloatBuffer(12); for (Material material : meshByMaterial.keySet()) { if (material.isRenderable()) { OpenGLMesh lastMesh = null; material.enable(); material.setFloat("sunlight", 1.0f, true); material.setFloat("blockLight", 1.0f, true); material.setMatrix4("projectionMatrix", worldRenderer.getActiveCamera().getProjectionMatrix(), true); material.bindTextures(); Set<EntityRef> entities = meshByMaterial.get(material); lastRendered = entities.size(); for (EntityRef entity : entities) { MeshComponent meshComp = entity.getComponent(MeshComponent.class); LocationComponent location = entity.getComponent(LocationComponent.class); if (isHidden(entity, meshComp) || location == null || meshComp.mesh == null || !isRelevant(entity, location.getWorldPosition())) { continue; } if (meshComp.mesh.isDisposed()) { logger.error("Attempted to render disposed mesh"); continue; } location.getWorldRotation(worldRot); location.getWorldPosition(worldPos); float worldScale = location.getWorldScale(); javax.vecmath.Matrix4f matrixWorldSpace = new javax.vecmath.Matrix4f(VecMath.to(worldRot), VecMath.to(worldPos), worldScale); transWorldSpace.set(matrixWorldSpace); Vector3f worldPositionCameraSpace = new Vector3f(); worldPositionCameraSpace.sub(worldPos, cameraPosition); Matrix4f matrixCameraSpace = new Matrix4f(worldRot, worldPositionCameraSpace, worldScale); AABB aabb = meshComp.mesh.getAABB().transform(transWorldSpace); if (worldRenderer.getActiveCamera().hasInSight(aabb)) { if (meshComp.mesh != lastMesh) { if (lastMesh != null) { lastMesh.postRender(); } lastMesh = (OpenGLMesh) meshComp.mesh; lastMesh.preRender(); } Matrix4f modelViewMatrix = MatrixUtils.calcModelViewMatrix(worldRenderer.getActiveCamera().getViewMatrix(), matrixCameraSpace); MatrixUtils.matrixToFloatBuffer(modelViewMatrix, tempMatrixBuffer44); MatrixUtils.matrixToFloatBuffer(MatrixUtils.calcNormalMatrix(modelViewMatrix), tempMatrixBuffer33); material.setMatrix4("projectionMatrix", worldRenderer.getActiveCamera().getProjectionMatrix(), true); material.setMatrix4("worldViewMatrix", tempMatrixBuffer44, true); material.setMatrix3("normalMatrix", tempMatrixBuffer33, true); material.setFloat3("colorOffset", meshComp.color.rf(), meshComp.color.gf(), meshComp.color.bf(), true); material.setFloat("sunlight", worldRenderer.getMainLightIntensityAt(worldPos), true); material.setFloat("blockLight", Math.max(worldRenderer.getBlockLightIntensityAt(worldPos), meshComp.selfLuminance), true); lastMesh.doRender(); } } if (lastMesh != null) { lastMesh.postRender(); } } } } /** * Checks whether the entity at the given position is relevant. * <p> * The entity at the given position is relevant if * a) the entity itself is always relevant, or * b) the block at the position is relevant. * * @param entity the entity to check for relevance * @param position the world position the entity is located * @return true if the entity itself or the block at the given position are relevant, false otherwise. */ private boolean isRelevant(EntityRef entity, Vector3f position) { return worldProvider.isBlockRelevant(position) || entity.isAlwaysRelevant(); } @Override public void renderOverlay() { } @Override public void renderFirstPerson() { } @Override public void renderShadows() { } public int getLastRendered() { return lastRendered; } }