/* * Copyright 2016 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.google.common.collect.Maps; import org.terasology.assets.management.AssetManager; import org.terasology.entitySystem.entity.EntityManager; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.entitySystem.entity.lifecycleEvents.BeforeDeactivateComponent; 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.math.geom.Vector3f; import org.terasology.registry.In; import org.terasology.rendering.assets.font.Font; import org.terasology.rendering.assets.font.FontMeshBuilder; import org.terasology.rendering.assets.material.Material; import org.terasology.rendering.assets.mesh.Mesh; import org.terasology.rendering.cameras.Camera; import org.terasology.rendering.nui.Color; import org.terasology.rendering.nui.HorizontalAlign; import org.terasology.rendering.opengl.OpenGLUtils; import org.terasology.world.WorldProvider; import java.util.Arrays; import java.util.Map; import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST; import static org.lwjgl.opengl.GL11.glDisable; import static org.lwjgl.opengl.GL11.glEnable; import static org.lwjgl.opengl.GL11.glPopMatrix; import static org.lwjgl.opengl.GL11.glPushMatrix; import static org.lwjgl.opengl.GL11.glScaled; import static org.lwjgl.opengl.GL11.glTranslated; @RegisterSystem(RegisterMode.CLIENT) public class FloatingTextRenderer extends BaseComponentSystem implements RenderSystem { private static final int PIXEL_PER_METER = 250; private static final float METER_PER_PIXEL = 1.0f / PIXEL_PER_METER; @In private EntityManager entityManager; @In private AssetManager assetManager; @In private WorldProvider worldProvider; @In private Camera camera; private Font font; private Material underlineMaterial; private Map<EntityRef, Map<Material, Mesh>> entityMeshCache = Maps.newHashMap(); @Override public void initialise() { this.font = assetManager.getAsset("engine:NotoSans-Regular-Large", Font.class).get(); this.underlineMaterial = assetManager.getAsset("engine:UIUnderline", Material.class).get(); } private void render(Iterable<EntityRef> floatingTextEntities) { glDisable(GL_DEPTH_TEST); Vector3f cameraPosition = camera.getPosition(); for (EntityRef entity : floatingTextEntities) { LocationComponent location = entity.getComponent(LocationComponent.class); if (location == null) { continue; } Vector3f worldPos = location.getWorldPosition(); if (!worldProvider.isBlockRelevant(worldPos)) { continue; } FloatingTextComponent floatingText = entity.getComponent(FloatingTextComponent.class); String text = floatingText.text; Color baseColor = floatingText.textColor; Color shadowColor = floatingText.textShadowColor; boolean underline = false; int textWidth = font.getWidth(text); FontMeshBuilder meshBuilder = new FontMeshBuilder(underlineMaterial); Map<Material, Mesh> meshMap = entityMeshCache.get(entity); if (meshMap == null) { meshMap = meshBuilder .createTextMesh(font, Arrays.asList(text), textWidth, HorizontalAlign.CENTER, baseColor, shadowColor, underline); entityMeshCache.put(entity, meshMap); } glPushMatrix(); float scale = METER_PER_PIXEL * floatingText.scale; glTranslated(worldPos.x - cameraPosition.x, worldPos.y - cameraPosition.y, worldPos.z - cameraPosition.z); OpenGLUtils.applyBillboardOrientation(); glScaled(scale, -scale, scale); glTranslated(-textWidth / 2.0, 0.0, 0.0); for (Map.Entry<Material, Mesh> meshMapEntry : meshMap.entrySet()) { Mesh mesh = meshMapEntry.getValue(); Material material = meshMapEntry.getKey(); material.enable(); material.bindTextures(); material.setFloat4("croppingBoundaries", Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_VALUE, Float.MAX_VALUE); material.setFloat2("offset", 0.0f, 0.0f); material.setFloat("alpha", 1.0f); mesh.render(); } glPopMatrix(); } glEnable(GL_DEPTH_TEST); } private void diposeMeshMap(Map<Material, Mesh> meshMap) { for (Map.Entry<Material, Mesh> meshMapEntry : meshMap.entrySet()) { Mesh mesh = meshMapEntry.getValue(); mesh.dispose(); // Note: Material belongs to font and must not be disposed } } @Override public void renderOpaque() { } @Override public void renderAlphaBlend() { render(entityManager.getEntitiesWith(FloatingTextComponent.class, LocationComponent.class)); } @Override public void renderFirstPerson() { } @Override public void renderOverlay() { } @Override public void renderShadows() { } @ReceiveEvent(components = {FloatingTextComponent.class }) public void onDisplayNameChange(OnChangedComponent event, EntityRef entity) { disposeCachedMeshOfEntity(entity); } @ReceiveEvent(components = {FloatingTextComponent.class }) public void onNameTagOwnerRemoved(BeforeDeactivateComponent event, EntityRef entity) { disposeCachedMeshOfEntity(entity); } private void disposeCachedMeshOfEntity(EntityRef entity) { Map<Material, Mesh> meshMap = entityMeshCache.remove(entity); if (meshMap != null) { diposeMeshMap(meshMap); } } @Override public void shutdown() { entityMeshCache.values().forEach(this::diposeMeshMap); entityMeshCache.clear(); } }