From f43d4b16de9edb0965f28c9afed09c530eadcbb5 Mon Sep 17 00:00:00 2001 From: IsaacLic <40670945+IsaacLic@users.noreply.github.com> Date: Mon, 24 Aug 2020 23:09:53 -0400 Subject: [PATCH] improvement: textures scale to size (#551) * fix: textures scale to size * fix: added EmptyComponent to whitelist * improvement: graphics are more closely aligned with meshes * docs: added documentation to the alignment code * refactor: made Invisibility a field in Renderable component * docs: cleaned up and added more documentation --- .../org/destinationsol/SolApplication.java | 13 ++--- .../systems/AsteroidBodyCreationSystem.java | 21 +++++++- .../destinationsol/modules/ModuleManager.java | 1 + .../rendering/RenderableElement.java | 48 +++++++++++++++++-- .../rendering/components/Invisible.java | 25 ---------- .../rendering/components/Renderable.java | 3 ++ .../rendering/systems/RenderingSystem.java | 25 +++++++--- 7 files changed, 93 insertions(+), 43 deletions(-) delete mode 100644 engine/src/main/java/org/destinationsol/rendering/components/Invisible.java diff --git a/engine/src/main/java/org/destinationsol/SolApplication.java b/engine/src/main/java/org/destinationsol/SolApplication.java index a85e3052a..a33d65c22 100644 --- a/engine/src/main/java/org/destinationsol/SolApplication.java +++ b/engine/src/main/java/org/destinationsol/SolApplication.java @@ -260,13 +260,17 @@ private void draw() { //TODO remove this block - it is for debugging purposes if (!entityCreated) { + + Size size = new Size(); + size.size = 2; + RenderableElement element = new RenderableElement(); element.texture = SolRandom.randomElement(Assets.listTexturesMatching("engine:asteroid_.*")); - element.relativePosition = new Vector2(0, 0); + element.relativePosition = new Vector2(); element.drawableLevel = DrawableLevel.BODIES; - element.width = 2; - element.height = 2; element.tint = Color.YELLOW; + element.setSize(size.size); + element.graphicsOffset = new Vector2(); Renderable graphicsComponent = new Renderable(); graphicsComponent.elements.add(element); @@ -274,9 +278,6 @@ private void draw() { position.position = solGame.getHero().getShip().getPosition().cpy(); position.position.y += 3; - Size size = new Size(); - size.size = 2; - Health health = new Health(); health.currentHealth = 1; diff --git a/engine/src/main/java/org/destinationsol/asteroids/systems/AsteroidBodyCreationSystem.java b/engine/src/main/java/org/destinationsol/asteroids/systems/AsteroidBodyCreationSystem.java index f0b7a1d89..7b8c5b18c 100644 --- a/engine/src/main/java/org/destinationsol/asteroids/systems/AsteroidBodyCreationSystem.java +++ b/engine/src/main/java/org/destinationsol/asteroids/systems/AsteroidBodyCreationSystem.java @@ -66,7 +66,6 @@ public class AsteroidBodyCreationSystem implements EventReceiver { @ReceiveEvent(components = {AsteroidMesh.class, Size.class, Position.class, Angle.class, Renderable.class}) public EventResult onGenerateBody(GenerateBodyEvent event, EntityRef entity) { - float size = entity.getComponent(Size.class).get().size; Vector2 position = entity.getComponent(Position.class).get().position; float angle = entity.getComponent(Angle.class).get().getAngle(); @@ -90,9 +89,29 @@ public EventResult onGenerateBody(GenerateBodyEvent event, EntityRef entity) { fixtureDef.density = DENSITY; fixtureDef.friction = Const.FRICTION; collisionMeshLoader.attachFixture(body, element.texture.name, fixtureDef, size); + + calculateGraphicsOffset(element); } entitySystemManager.sendEvent(new BodyCreatedEvent(body), entity); return EventResult.CONTINUE; } + + /** + * This calculates the offset of the renderable element from "the origin" (as defined in the JSON that the + * CollisionMeshLoader reads from). + * The origin is where the center of the object should be, which is relevant for physics handling. The + * CollisionMeshLoader creates Fixtures (collision meshes) using that information, so the sprites need to be + * adjusted to overlay the mesh properly. + * LibGDX draws sprites from the bottom left corner. Since the position information is from the center, it + * needs to be adjusted to be at the bottom left of the sprite. To do so, (.5, .5) is subtracted from the origin. + * (The coordinates are scaled to range from zero to one, so (.5, .5) represents the center.) + * The originInformation is the information that was read from the JSON, which is used to calculate the graphics + * offset information. + */ + //TODO separate this method into a separate system once CollisionMeshLoader is modular + private void calculateGraphicsOffset(RenderableElement element) { + Vector2 originInformation = collisionMeshLoader.getOrigin(element.texture.name, 1); + element.graphicsOffset = new Vector2(originInformation.x - .5f, originInformation.y - .5f); + } } diff --git a/engine/src/main/java/org/destinationsol/modules/ModuleManager.java b/engine/src/main/java/org/destinationsol/modules/ModuleManager.java index c27861b0e..2e7f8a4e5 100644 --- a/engine/src/main/java/org/destinationsol/modules/ModuleManager.java +++ b/engine/src/main/java/org/destinationsol/modules/ModuleManager.java @@ -157,6 +157,7 @@ public class ModuleManager { java.io.PipedOutputStream.class, /* Gestalt classes */ org.terasology.gestalt.entitysystem.component.Component.class, + org.terasology.gestalt.entitysystem.component.EmptyComponent.class, org.terasology.gestalt.entitysystem.event.Event.class, org.terasology.gestalt.entitysystem.entity.EntityRef.class, org.terasology.gestalt.entitysystem.entity.EntityIterator.class, diff --git a/engine/src/main/java/org/destinationsol/rendering/RenderableElement.java b/engine/src/main/java/org/destinationsol/rendering/RenderableElement.java index dded2dade..e0df1d201 100644 --- a/engine/src/main/java/org/destinationsol/rendering/RenderableElement.java +++ b/engine/src/main/java/org/destinationsol/rendering/RenderableElement.java @@ -18,7 +18,9 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.math.Vector2; +import org.destinationsol.common.SolMath; import org.destinationsol.game.drawables.DrawableLevel; +import org.destinationsol.size.components.Size; /** * Contains a {@link TextureAtlas.AtlasRegion} (an image), along with information about how it should be drawn. @@ -29,10 +31,10 @@ public class RenderableElement { public TextureAtlas.AtlasRegion texture; /** The width of the texture when drawn. */ - public float width; + private float width; /** The height of the texture when drawn. */ - public float height; + private float height; /** Represents the depth at which this element renders, as well as its logical grouping. */ public DrawableLevel drawableLevel; @@ -46,14 +48,52 @@ public class RenderableElement { /** The tint that the texture should be given. */ public Color tint; + /** + * The amount that the sprite should be moved to line up accurately with the mesh. This should be scaled according + * to the {@link Size}. This is different from the relativePosition, because this is a practical adjustment to align + * the mesh with the sprite, as opposed to moving the sprite relative to the base entity. To draw the sprite + * accurately, it needs to be drawn from the bottom-left of the actual image, not the bottom left of the .png file, + * so this contains the information for calculating the actual start of the sprite. + *

+ * Modification of this can create mesh misalignments, so only change this if you know what you're doing. + */ + public Vector2 graphicsOffset; + + + //TODO this should be automatically called when the Size component is changed, e.g. the entity shrinks or grows + /** + * Resizes the renderable element to the given size. The larger dimension of the texture is set to the size, and the + * smaller one is scaled down proportionally. + */ + public void setSize(float size) { + size /= drawableLevel.depth; // Scales the texture size for objects that are in the background + float dimensionsRatio = (float) texture.getRegionWidth() / texture.getRegionHeight(); + if (dimensionsRatio > 1) { + width = size; + height = size / dimensionsRatio; + } else { + width = size / dimensionsRatio; + height = size; + } + } + public void copy(RenderableElement other) { this.texture = new TextureAtlas.AtlasRegion(other.texture); this.drawableLevel = other.drawableLevel; this.relativePosition = other.relativePosition.cpy(); this.relativeAngle = other.relativeAngle; - this.width = other.width; - this.height = other.height; + this.width = other.getWidth(); + this.height = other.getHeight(); this.tint = other.tint.cpy(); + this.graphicsOffset = other.graphicsOffset.cpy(); + } + + public float getWidth() { + return width; + } + + public float getHeight() { + return height; } } diff --git a/engine/src/main/java/org/destinationsol/rendering/components/Invisible.java b/engine/src/main/java/org/destinationsol/rendering/components/Invisible.java deleted file mode 100644 index 03005163e..000000000 --- a/engine/src/main/java/org/destinationsol/rendering/components/Invisible.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2020 The Terasology Foundation - * - * 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.destinationsol.rendering.components; - -import org.destinationsol.rendering.systems.RenderingSystem; -import org.terasology.gestalt.entitysystem.component.EmptyComponent; - -/** - * Denotes that the object should not be drawn by the {@link RenderingSystem}. - */ -public class Invisible extends EmptyComponent { -} diff --git a/engine/src/main/java/org/destinationsol/rendering/components/Renderable.java b/engine/src/main/java/org/destinationsol/rendering/components/Renderable.java index 9df0dc447..b459c4e2d 100644 --- a/engine/src/main/java/org/destinationsol/rendering/components/Renderable.java +++ b/engine/src/main/java/org/destinationsol/rendering/components/Renderable.java @@ -27,6 +27,8 @@ public final class Renderable implements Component { public ArrayList elements = new ArrayList<>(); + public boolean isInvisible; + @Override public void copy(Renderable other) { ArrayList newElements = new ArrayList<>(); @@ -37,5 +39,6 @@ public void copy(Renderable other) { } this.elements = newElements; + this.isInvisible = other.isInvisible; } } diff --git a/engine/src/main/java/org/destinationsol/rendering/systems/RenderingSystem.java b/engine/src/main/java/org/destinationsol/rendering/systems/RenderingSystem.java index 4399ac2fb..c2746fe73 100644 --- a/engine/src/main/java/org/destinationsol/rendering/systems/RenderingSystem.java +++ b/engine/src/main/java/org/destinationsol/rendering/systems/RenderingSystem.java @@ -19,13 +19,13 @@ import org.destinationsol.common.In; import org.destinationsol.rendering.RenderableElement; import org.destinationsol.rendering.components.Renderable; -import org.destinationsol.rendering.components.Invisible; import org.destinationsol.rendering.events.RenderEvent; import org.destinationsol.entitysystem.EntitySystemManager; import org.destinationsol.entitysystem.EventReceiver; import org.destinationsol.game.GameDrawer; import org.destinationsol.location.components.Angle; import org.destinationsol.location.components.Position; +import org.destinationsol.size.components.Size; import org.terasology.gestalt.entitysystem.entity.EntityRef; import org.terasology.gestalt.entitysystem.event.EventResult; import org.terasology.gestalt.entitysystem.event.ReceiveEvent; @@ -41,13 +41,14 @@ public class RenderingSystem implements EventReceiver { @In private GameDrawer drawer; - @ReceiveEvent(components = {Renderable.class, Position.class}) + @ReceiveEvent(components = {Renderable.class, Position.class, Size.class}) public EventResult onRender(RenderEvent event, EntityRef entity) { - if (!entity.hasComponent(Invisible.class)) { + Renderable renderable = entity.getComponent(Renderable.class).get(); + if (!renderable.isInvisible) { - Renderable renderable = entity.getComponent(Renderable.class).get(); Vector2 basePosition = entity.getComponent(Position.class).get().position; + float size = entity.getComponent(Size.class).get().size; float baseAngle = 0; if (entity.hasComponent(Position.class)) { @@ -56,10 +57,20 @@ public EventResult onRender(RenderEvent event, EntityRef entity) { for (RenderableElement renderableElement : renderable.elements) { float angle = renderableElement.relativeAngle + baseAngle; + Vector2 renderableElementPosition = basePosition.add(renderableElement.relativePosition); - drawer.draw(renderableElement.texture, renderableElement.width, - renderableElement.height, renderableElement.width / 2, renderableElement.height / 2, - basePosition.x, basePosition.y, angle, renderableElement.tint); + /* + This calculates how much the position of the drawable should be shifted horizontally or vertically to + line up with the collision mesh. + */ + float horizontalShift = renderableElement.getWidth() / 2; + float verticalShift = renderableElement.getHeight() / 2; + horizontalShift += renderableElement.graphicsOffset.x * size; + verticalShift += renderableElement.graphicsOffset.y * size; + + drawer.draw(renderableElement.texture, renderableElement.getWidth(), renderableElement.getHeight(), + horizontalShift, verticalShift, renderableElementPosition.x, renderableElementPosition.y, angle, + renderableElement.tint); } }