Compare commits
13 Commits
dda12589a7
...
9fe8ab7fbe
| Author | SHA1 | Date | |
|---|---|---|---|
|
9fe8ab7fbe
|
|||
|
4fede6a0fc
|
|||
|
2c238ead7b
|
|||
|
1ce0810cc2
|
|||
|
8a2a5511f4
|
|||
|
39a91ad4ac
|
|||
|
9f463cf553
|
|||
|
4cb710a1b5
|
|||
|
1d1730f97b
|
|||
|
f131f9ef7f
|
|||
|
380d2cd254
|
|||
|
ef5ea98981
|
|||
|
c89a1f1d2e
|
@@ -2,20 +2,141 @@ package com.bartlomiejpluta.base.api.animation;
|
||||
|
||||
import com.bartlomiejpluta.base.internal.program.Updatable;
|
||||
|
||||
/**
|
||||
* Represents an object that can be animated by cycling through multiple frames of a texture.
|
||||
*
|
||||
* <p>This interface provides a complete animation system for 2D sprites and other visual objects
|
||||
* in the game engine. Animated objects automatically cycle through their available frames at a
|
||||
* specified speed when animation is enabled.
|
||||
*
|
||||
* <p>The animation system works by:
|
||||
* <ul>
|
||||
* <li>Maintaining an internal timer that tracks elapsed time</li>
|
||||
* <li>Automatically advancing to the next frame when enough time has passed</li>
|
||||
* <li>Cycling through available frames in sequence</li>
|
||||
* <li>Supporting both full texture animation and subset-based animation</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Animation can be controlled in real-time through enable/disable methods and speed adjustments.
|
||||
* The animation automatically resets and pauses when disabled.
|
||||
*
|
||||
* <p>This interface extends {@link Updatable}, meaning animated objects are automatically
|
||||
* updated each frame by the game engine's update loop.
|
||||
*/
|
||||
public interface Animated extends Updatable {
|
||||
|
||||
/**
|
||||
* Returns whether animation is currently enabled for this object.
|
||||
*
|
||||
* <p>When animation is disabled, the object remains on its current frame
|
||||
* and the internal animation timer is reset to zero.
|
||||
*
|
||||
* @return {@code true} if animation is enabled, {@code false} otherwise
|
||||
*/
|
||||
boolean isAnimationEnabled();
|
||||
|
||||
/**
|
||||
* Enables or disables animation for this object.
|
||||
*
|
||||
* <p>When animation is disabled:
|
||||
* <ul>
|
||||
* <li>The object stops cycling through frames</li>
|
||||
* <li>The current frame remains displayed</li>
|
||||
* <li>The internal animation timer is reset to zero</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>When animation is enabled, the object resumes cycling through frames
|
||||
* at the configured animation speed.
|
||||
*
|
||||
* @param enabled {@code true} to enable animation, {@code false} to disable
|
||||
*/
|
||||
void setAnimationEnabled(boolean enabled);
|
||||
|
||||
void enableAnimation();
|
||||
/**
|
||||
* Enables animation for this object.
|
||||
*
|
||||
* <p>This is a convenience method equivalent to calling
|
||||
* {@code setAnimationEnabled(true)}.
|
||||
*
|
||||
* @see #setAnimationEnabled(boolean)
|
||||
*/
|
||||
default void enableAnimation() {
|
||||
setAnimationEnabled(true);
|
||||
}
|
||||
|
||||
void disableAnimation();
|
||||
/**
|
||||
* Disables animation for this object.
|
||||
*
|
||||
* <p>This is a convenience method equivalent to calling
|
||||
* {@code setAnimationEnabled(false)}. The object will remain on its
|
||||
* current frame and the animation timer will be reset.
|
||||
*
|
||||
* @see #setAnimationEnabled(boolean)
|
||||
*/
|
||||
default void disableAnimation() {
|
||||
setAnimationEnabled(false);
|
||||
}
|
||||
|
||||
void toggleAnimationEnabled();
|
||||
/**
|
||||
* Toggles the animation enabled state.
|
||||
*
|
||||
* <p>If animation is currently enabled, it will be disabled.
|
||||
* If animation is currently disabled, it will be enabled.
|
||||
*
|
||||
* @see #isAnimationEnabled()
|
||||
* @see #setAnimationEnabled(boolean)
|
||||
*/
|
||||
default void toggleAnimationEnabled() {
|
||||
setAnimationEnabled(!isAnimationEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current animation speed.
|
||||
*
|
||||
* <p>Animation speed determines how quickly the object cycles through its frames.
|
||||
* Higher values result in faster animation, while lower values create slower animation.
|
||||
*
|
||||
* <p>The speed is typically measured in relation to the game engine's target
|
||||
* update rate, where a speed of 1.0 represents normal timing.
|
||||
* If 60PFS (target update rate) is maintained, it should be measured in frames per second.
|
||||
*
|
||||
* @return the current animation speed as a positive float value
|
||||
*/
|
||||
float getAnimationSpeed();
|
||||
|
||||
/**
|
||||
* Sets the animation speed for this object.
|
||||
*
|
||||
* <p>Animation speed controls how quickly frames are cycled during animation.
|
||||
* The speed value is typically:
|
||||
* <ul>
|
||||
* <li>1.0 for normal speed animation</li>
|
||||
* <li>Greater than 1.0 for faster animation</li>
|
||||
* <li>Between 0.0 and 1.0 for slower animation</li>
|
||||
* <li>Values are automatically clamped to prevent invalid speeds</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The actual frame timing is calculated based on the game engine's target
|
||||
* update rate and the specified speed multiplier.
|
||||
* If 60PFS (target update rate) is maintained, it should be measured in frames per second.
|
||||
*
|
||||
* @param speed the new animation speed as a positive float value
|
||||
* @throws IllegalArgumentException if speed is negative or zero
|
||||
*/
|
||||
void setAnimationSpeed(float speed);
|
||||
|
||||
/**
|
||||
* Manually sets the current animation frame.
|
||||
*
|
||||
* <p>This method allows direct control over which frame is currently displayed,
|
||||
* bypassing the automatic animation timing. The frame index is automatically
|
||||
* wrapped if it exceeds the available frame count.
|
||||
*
|
||||
*
|
||||
* <p>Frame indices are zero-based, and values that exceed the available frame
|
||||
* count are automatically wrapped using modulo operation.
|
||||
*
|
||||
* @param frame the zero-based index of the frame to display
|
||||
*/
|
||||
void setAnimationFrame(int frame);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package com.bartlomiejpluta.base.api.camera;
|
||||
|
||||
import com.bartlomiejpluta.base.api.context.Context;
|
||||
import com.bartlomiejpluta.base.api.move.Movable;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.internal.object.Placeable;
|
||||
import com.bartlomiejpluta.base.internal.render.BoundingBox;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
public interface Camera extends Placeable {
|
||||
public interface Camera extends Placeable, BoundingBox {
|
||||
Matrix4fc computeViewModelMatrix(Matrix4fc modelMatrix);
|
||||
|
||||
Matrix4fc getProjectionMatrix();
|
||||
|
||||
boolean insideFrustum(float x, float y, float radius);
|
||||
|
||||
boolean insideFrustum(Context context, float x, float y);
|
||||
|
||||
@@ -8,15 +8,103 @@ import com.bartlomiejpluta.base.api.move.Movement;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Represents a game world object that can face four cardinal directions and uses
|
||||
* a special texture format called a character set (charset).
|
||||
*
|
||||
* <p>A character set is a sprite sheet organized in a 4-row, multi-column format where:
|
||||
* <ul>
|
||||
* <li>Row 1: Graphics for facing DOWN</li>
|
||||
* <li>Row 2: Graphics for facing LEFT</li>
|
||||
* <li>Row 3: Graphics for facing RIGHT</li>
|
||||
* <li>Row 4: Graphics for facing UP</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Each column represents a different animation frame or object variant. For animated
|
||||
* characters, columns contain sequential animation frames. For static objects (like boxes),
|
||||
* each column may represent different object types or states (e.g., normal box, colored box,
|
||||
* damaged box).
|
||||
*
|
||||
* <p>This interface combines movement capabilities ({@link Movable}), animation features
|
||||
* ({@link Animated}), and entity properties ({@link Entity}) to create a complete
|
||||
* character system for 2D games.
|
||||
*/
|
||||
public interface Character extends Movable, Animated, Entity {
|
||||
|
||||
/**
|
||||
* Initiates movement in the specified direction.
|
||||
*
|
||||
* <p>This method handles the character's movement mechanics and automatically
|
||||
* updates the facing direction to match the movement direction.
|
||||
*
|
||||
* @param direction the direction to move towards
|
||||
* @return a {@link Movement} object representing the movement operation
|
||||
* @throws IllegalArgumentException if direction is null
|
||||
*/
|
||||
Movement move(Direction direction);
|
||||
|
||||
/**
|
||||
* Returns the current facing direction of the character.
|
||||
*
|
||||
* <p>The facing direction determines which row of the character set
|
||||
* is used for rendering and animation.
|
||||
*
|
||||
* @return the current facing direction
|
||||
*/
|
||||
Direction getFaceDirection();
|
||||
|
||||
/**
|
||||
* Sets the default sprite column to use from the character set.
|
||||
*
|
||||
* <p>This method is particularly useful for character sets that contain
|
||||
* multiple object variants in different columns. For example, if a character
|
||||
* set contains different box types in each column, this method selects
|
||||
* which box type to display.
|
||||
*
|
||||
* <p>The column index is zero-based and must be within the bounds of the
|
||||
* current character set.
|
||||
*
|
||||
* @param column the zero-based column index to use as default
|
||||
* @throws IndexOutOfBoundsException if the column index is invalid
|
||||
*/
|
||||
void setDefaultSpriteColumn(int column);
|
||||
|
||||
/**
|
||||
* Changes the character's facing direction without initiating movement.
|
||||
*
|
||||
* <p>This method updates which row of the character set is used for rendering.
|
||||
* It can be used to create custom animations by cycling through different
|
||||
* facing directions, or to orient the character without moving.
|
||||
*
|
||||
* @param direction the new facing direction
|
||||
* @throws IllegalArgumentException if direction is null
|
||||
*/
|
||||
void setFaceDirection(Direction direction);
|
||||
|
||||
/**
|
||||
* Changes the character set (sprite sheet) used by this character.
|
||||
*
|
||||
* <p>This allows dynamic switching between different visual representations
|
||||
* of the character, such as different costumes, character states, or
|
||||
* completely different character types.
|
||||
*
|
||||
* @param characterSetUid the unique identifier of the new character set
|
||||
* @throws IllegalArgumentException if characterSetUid is null or empty
|
||||
*/
|
||||
void changeCharacterSet(String characterSetUid);
|
||||
|
||||
/**
|
||||
* Performs an instant animation using all frames from the current facing direction.
|
||||
*
|
||||
* <p>This method executes an animation sequence by cycling through all columns
|
||||
* of the row corresponding to the character's current facing direction. For example,
|
||||
* if the character is facing UP, the animation will use all frames from the fourth
|
||||
* row of the character set.
|
||||
*
|
||||
* <p>The animation runs asynchronously and the returned {@link CompletableFuture}
|
||||
* completes when the animation finishes.
|
||||
*
|
||||
* @return a {@link CompletableFuture} that completes when the animation finishes
|
||||
*/
|
||||
CompletableFuture<Void> performInstantAnimation();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,5 @@ import com.bartlomiejpluta.base.api.map.layer.base.Layer;
|
||||
public interface TileLayer extends Layer {
|
||||
void setTile(int row, int column, int tileId);
|
||||
|
||||
void setTile(int row, int column, int tileSetRow, int tileSetColumn);
|
||||
|
||||
void clearTile(int row, int column);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.bartlomiejpluta.base.internal.render;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public interface BoundingBox {
|
||||
float getMinX();
|
||||
|
||||
float getMaxX();
|
||||
|
||||
float getMinY();
|
||||
|
||||
float getMaxY();
|
||||
|
||||
default boolean containsBox(float minX, float maxX, float minY, float maxY) {
|
||||
return !(this.getMaxX() < minX ||
|
||||
this.getMinX() > maxX ||
|
||||
this.getMaxY() < minY ||
|
||||
this.getMinY() > maxY);
|
||||
}
|
||||
|
||||
default boolean containsBox(BoundingBox box) {
|
||||
return !(this.getMaxX() < box.getMinX() ||
|
||||
this.getMinX() > box.getMaxX() ||
|
||||
this.getMaxY() < box.getMinY() ||
|
||||
this.getMinY() > box.getMaxY());
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,9 @@ import org.joml.*;
|
||||
public interface ShaderManager extends Cleanable {
|
||||
ShaderManager createShader(String programName, String vertexShaderFilename, String fragmentShaderFilename);
|
||||
|
||||
ShaderManager selectShader(String programName);
|
||||
ShaderManager activateShader(String programName);
|
||||
|
||||
ShaderManager useSelectedShader();
|
||||
|
||||
ShaderManager detachCurrentShader();
|
||||
ShaderManager deactivateShader();
|
||||
|
||||
ShaderManager createUniform(String uniformName);
|
||||
|
||||
|
||||
@@ -293,6 +293,11 @@ public abstract class CharacterDelegate implements Character {
|
||||
character.setZIndex(zIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultSpriteColumn(int column) {
|
||||
character.setDefaultSpriteColumn(column);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Event> void handleEvent(E event) {
|
||||
character.handleEvent(event);
|
||||
|
||||
@@ -19,10 +19,12 @@ public class CompletableFutureSegment<T extends Movable> implements PathSegment<
|
||||
}
|
||||
|
||||
if (future.isCancelled() || future.isCompletedExceptionally()) {
|
||||
this.future = null;
|
||||
return PathProgress.SEGMENT_FAILED;
|
||||
}
|
||||
|
||||
if (future.isDone()) {
|
||||
this.future = null;
|
||||
return PathProgress.SEGMENT_DONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ public abstract class MapObject extends CharacterDelegate {
|
||||
this.frame = frame;
|
||||
setBlocking(true);
|
||||
disableAnimation();
|
||||
setAnimationFrame(frame);
|
||||
setDefaultSpriteColumn(frame);
|
||||
setFaceDirection(DOWN);
|
||||
pathExecutor.setRepeat(1);
|
||||
|
||||
initPath();
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.object.color;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
import org.joml.*;
|
||||
|
||||
public class Color implements Material {
|
||||
private static final float[][] TEXTURE_COORDINATES = {{
|
||||
0, 1,
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1
|
||||
}};
|
||||
|
||||
protected final Vector4f color = new Vector4f();
|
||||
|
||||
public Color() {
|
||||
this(1f, 1f, 1f, 1f);
|
||||
}
|
||||
|
||||
public Color(float red, float green, float blue, float alpha) {
|
||||
setColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector4fc getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(Vector4fc color) {
|
||||
this.color.x = color.x();
|
||||
this.color.y = color.y();
|
||||
this.color.z = color.z();
|
||||
this.color.w = color.w();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(Vector3fc color) {
|
||||
this.color.x = color.x();
|
||||
this.color.y = color.y();
|
||||
this.color.z = color.z();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(float red, float green, float blue, float alpha) {
|
||||
this.color.x = red;
|
||||
this.color.y = green;
|
||||
this.color.z = blue;
|
||||
this.color.w = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(float red, float green, float blue) {
|
||||
this.color.x = red;
|
||||
this.color.y = green;
|
||||
this.color.z = blue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRed(float red) {
|
||||
this.color.x = red;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGreen(float green) {
|
||||
this.color.y = green;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlue(float blue) {
|
||||
this.color.z = blue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(float alpha) {
|
||||
this.color.w = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRows() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumns() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[][] getTextureCoordinatesForAllFrames() {
|
||||
return TEXTURE_COORDINATES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
shaderManager.setUniform(UniformName.UNI_HAS_OBJECT_TEXTURE, false);
|
||||
shaderManager.setUniform(UniformName.UNI_OBJECT_COLOR, color);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +1,48 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.object.material;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.color.Color;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.TexturedMaterial;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
import org.joml.*;
|
||||
import org.joml.Vector3fc;
|
||||
import org.joml.Vector4fc;
|
||||
|
||||
public class Material implements Renderable {
|
||||
private final Vector4f color = new Vector4f();
|
||||
private final Vector2f spritePosition = new Vector2f(0, 0);
|
||||
public interface Material extends Renderable {
|
||||
Vector4fc getColor();
|
||||
|
||||
@Getter
|
||||
private final Texture texture;
|
||||
void setColor(Vector4fc color);
|
||||
|
||||
private Material(Texture texture, float red, float green, float blue, float alpha) {
|
||||
this.texture = texture;
|
||||
setColor(red, green, blue, alpha);
|
||||
void setColor(Vector3fc color);
|
||||
|
||||
void setColor(float red, float green, float blue, float alpha);
|
||||
|
||||
void setColor(float red, float green, float blue);
|
||||
|
||||
void setRed(float red);
|
||||
|
||||
void setGreen(float green);
|
||||
|
||||
void setBlue(float blue);
|
||||
|
||||
void setAlpha(float alpha);
|
||||
|
||||
int getColumns();
|
||||
|
||||
int getRows();
|
||||
|
||||
float[][] getTextureCoordinatesForAllFrames();
|
||||
|
||||
/**
|
||||
* Creates a material with single, uniform color.
|
||||
*/
|
||||
static Material unicolor(float red, float green, float blue, float alpha) {
|
||||
return new Color(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
shaderManager.setUniform(UniformName.UNI_OBJECT_COLOR, color);
|
||||
|
||||
if (texture != null) {
|
||||
shaderManager.setUniform(UniformName.UNI_HAS_OBJECT_TEXTURE, true);
|
||||
shaderManager.setUniform(UniformName.UNI_SPRITE_SIZE, texture.getSpriteFragment());
|
||||
shaderManager.setUniform(UniformName.UNI_SPRITE_POSITION, spritePosition);
|
||||
shaderManager.setUniform(UniformName.UNI_TEXTURE_SAMPLER, 0);
|
||||
texture.activate();
|
||||
} else {
|
||||
shaderManager.setUniform(UniformName.UNI_HAS_OBJECT_TEXTURE, false);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector4fc getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(Vector4fc color) {
|
||||
this.color.x = color.x();
|
||||
this.color.y = color.y();
|
||||
this.color.z = color.z();
|
||||
this.color.w = color.w();
|
||||
}
|
||||
|
||||
public void setColor(Vector3fc color) {
|
||||
this.color.x = color.x();
|
||||
this.color.y = color.y();
|
||||
this.color.z = color.z();
|
||||
}
|
||||
|
||||
public void setColor(float red, float green, float blue, float alpha) {
|
||||
this.color.x = red;
|
||||
this.color.y = green;
|
||||
this.color.z = blue;
|
||||
this.color.w = alpha;
|
||||
}
|
||||
|
||||
public void setColor(float red, float green, float blue) {
|
||||
this.color.x = red;
|
||||
this.color.y = green;
|
||||
this.color.z = blue;
|
||||
}
|
||||
|
||||
public void setRed(float red) {
|
||||
this.color.x = red;
|
||||
}
|
||||
|
||||
public void setGreen(float green) {
|
||||
this.color.y = green;
|
||||
}
|
||||
|
||||
public void setBlue(float blue) {
|
||||
this.color.z = blue;
|
||||
}
|
||||
|
||||
public void setAlpha(float alpha) {
|
||||
this.color.w = alpha;
|
||||
}
|
||||
|
||||
public Vector2fc getSpritePosition() {
|
||||
return spritePosition;
|
||||
}
|
||||
|
||||
public void setSpritePosition(Vector2fc spritePosition) {
|
||||
if (texture != null) {
|
||||
var size = texture.getSpriteFragment();
|
||||
this.spritePosition.x = size.x() * spritePosition.x();
|
||||
this.spritePosition.y = size.y() * spritePosition.y();
|
||||
}
|
||||
}
|
||||
|
||||
public void setSpritePosition(float x, float y) {
|
||||
if (texture != null) {
|
||||
var size = texture.getSpriteFragment();
|
||||
this.spritePosition.x = size.x() * x;
|
||||
this.spritePosition.y = size.y() * y;
|
||||
}
|
||||
}
|
||||
|
||||
public static Material colored(float red, float green, float blue, float alpha) {
|
||||
return new Material(null, red, green, blue, alpha);
|
||||
}
|
||||
|
||||
public static Material textured(Texture texture) {
|
||||
return new Material(texture, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
public static Material textured(Texture texture, float red, float green, float blue, float alpha) {
|
||||
return new Material(texture, red, green, blue, alpha);
|
||||
}
|
||||
|
||||
public static Material textured(Texture texture, float alpha) {
|
||||
return new Material(texture, 1, 1, 1, alpha);
|
||||
/**
|
||||
* Creates a textured material
|
||||
*/
|
||||
static TexturedMaterial textured(Texture texture) {
|
||||
return new TexturedMaterial(texture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.object.mesh;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DefaultMeshManager implements MeshManager {
|
||||
private final List<Mesh> meshes = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public Mesh createMesh(int maxQuads) {
|
||||
log.debug("Creating [maxQuads: {}] mesh", maxQuads);
|
||||
var mesh = new Mesh(maxQuads);
|
||||
|
||||
meshes.add(mesh);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuadMesh createQuad(float width, float height, float originX, float originY) {
|
||||
log.debug("Creating [w:{}, h:{} | O:{}, {}] mesh", width, height, originX, originY);
|
||||
var quad = new QuadMesh(width, height, originX, originY);
|
||||
|
||||
meshes.add(quad);
|
||||
return quad;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
log.info("Disposing meshes");
|
||||
meshes.forEach(Mesh::dispose);
|
||||
log.info("Disposed {} meshes", meshes.size());
|
||||
}
|
||||
}
|
||||
@@ -6,100 +6,274 @@ import com.bartlomiejpluta.base.internal.gc.Disposable;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector2fc;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static java.lang.Math.abs;
|
||||
import static org.lwjgl.opengl.GL15.*;
|
||||
import static org.lwjgl.opengl.GL20.*;
|
||||
import static org.lwjgl.opengl.GL30.*;
|
||||
|
||||
public class Mesh implements Renderable, Disposable {
|
||||
// Texture: Quad:
|
||||
// (0,1) ---- (1,1) 1 ----------- 2
|
||||
// | | | |
|
||||
// | | => | TEXTURE |
|
||||
// | | | |
|
||||
// (0,0) ---- (1,0) 0 ----------- 3
|
||||
private static final float[] DEFAULT_TEXTURE_COORDINATES = new float[] {
|
||||
0, 0, // 0 - bottom left
|
||||
0, 1, // 1 - top left
|
||||
1, 1, // 2 - top right
|
||||
1, 0 // 3 - bottom right
|
||||
};
|
||||
|
||||
private static final int POSITION_VECTOR_LAYOUT_INDEX = 0;
|
||||
private static final int POSITION_VECTOR_LENGTH = 2;
|
||||
private static final int TEXTURE_COORDINATES_VECTOR_LAYOUT_INDEX = 1;
|
||||
private static final int TEXTURE_COORDINATES_VECTOR_LENGTH = 2;
|
||||
|
||||
private static final int VERTICES_PER_QUAD = 4;
|
||||
private static final int INDICES_PER_QUAD = 6;
|
||||
|
||||
private final int maxQuads;
|
||||
private final int vaoId;
|
||||
private final List<Integer> vboIds = new ArrayList<>(2);
|
||||
private final int elementsCount;
|
||||
private final List<Integer> vboIds = new ArrayList<>(3);
|
||||
private final int maxVertices;
|
||||
private final int maxIndices;
|
||||
|
||||
private final float[] vertexBuffer;
|
||||
private final float[] texCoordBuffer;
|
||||
private final int[] indexBuffer;
|
||||
|
||||
private boolean dirty;
|
||||
|
||||
@Getter
|
||||
private final Vector2fc farthestVertex;
|
||||
private int currentQuadCount = 0;
|
||||
|
||||
@Getter
|
||||
private final Vector2fc primarySize;
|
||||
Mesh(int maxQuads) {
|
||||
this.maxQuads = maxQuads;
|
||||
this.maxVertices = maxQuads * VERTICES_PER_QUAD * 2;
|
||||
this.maxIndices = maxQuads * INDICES_PER_QUAD;
|
||||
|
||||
public Mesh(float[] vertices, float[] texCoords, int[] elements) {
|
||||
this.elementsCount = elements.length;
|
||||
this.vertexBuffer = new float[maxVertices];
|
||||
this.texCoordBuffer = new float[maxVertices];
|
||||
this.indexBuffer = new int[maxIndices];
|
||||
|
||||
var vboId = 0;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
vaoId = glGenVertexArrays();
|
||||
glBindVertexArray(vaoId);
|
||||
|
||||
// Vertices VBO
|
||||
vboId = glGenBuffers();
|
||||
vboIds.add(vboId);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
||||
glBufferData(GL_ARRAY_BUFFER, stack.mallocFloat(vertices.length).put(vertices).flip(), GL_STATIC_DRAW);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0);
|
||||
// Vertex buffer
|
||||
var vertexVboId = glGenBuffers();
|
||||
vboIds.add(vertexVboId);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertexVboId);
|
||||
glBufferData(GL_ARRAY_BUFFER, maxVertices * Float.BYTES, GL_DYNAMIC_DRAW);
|
||||
glVertexAttribPointer(POSITION_VECTOR_LAYOUT_INDEX, POSITION_VECTOR_LENGTH, GL_FLOAT, false, 0, 0);
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
// Texture Coordinates VBO
|
||||
vboId = glGenBuffers();
|
||||
vboIds.add(vboId);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
||||
glBufferData(GL_ARRAY_BUFFER, stack.mallocFloat(texCoords.length).put(texCoords).flip(), GL_STATIC_DRAW);
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);
|
||||
// Texture coordinates buffer
|
||||
var texCoordVboId = glGenBuffers();
|
||||
vboIds.add(texCoordVboId);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, texCoordVboId);
|
||||
glBufferData(GL_ARRAY_BUFFER, maxVertices * Float.BYTES, GL_DYNAMIC_DRAW);
|
||||
glVertexAttribPointer(TEXTURE_COORDINATES_VECTOR_LAYOUT_INDEX, TEXTURE_COORDINATES_VECTOR_LENGTH, GL_FLOAT, false, 0, 0);
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
// Elements VBO
|
||||
vboId = glGenBuffers();
|
||||
vboIds.add(vboId);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, stack.mallocInt(elementsCount).put(elements).flip(), GL_STATIC_DRAW);
|
||||
// Index buffer
|
||||
var indexVboId = glGenBuffers();
|
||||
vboIds.add(indexVboId);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVboId);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, maxIndices * Integer.BYTES, GL_DYNAMIC_DRAW);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
var minX = Float.MAX_VALUE;
|
||||
var minY = Float.MAX_VALUE;
|
||||
var maxX = 0f;
|
||||
var maxY = 0f;
|
||||
public int addQuad(QuadTemplate template, float x, float y) {
|
||||
return addQuad(template, x, y, DEFAULT_TEXTURE_COORDINATES);
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertices.length / 2; ++i) {
|
||||
var x = vertices[2 * i];
|
||||
var y = vertices[2 * i + 1];
|
||||
|
||||
if (x < minX) {
|
||||
minX = x;
|
||||
}
|
||||
|
||||
if (x > maxX) {
|
||||
maxX = x;
|
||||
}
|
||||
|
||||
if (y < minY) {
|
||||
minY = y;
|
||||
}
|
||||
|
||||
if (y > maxY) {
|
||||
maxY = y;
|
||||
}
|
||||
public int addQuad(QuadTemplate template, float x, float y, float[] textureCoordinates) {
|
||||
if (currentQuadCount >= maxQuads) {
|
||||
throw new RuntimeException("Batch is full!");
|
||||
}
|
||||
|
||||
farthestVertex = new Vector2f(abs(maxX) > abs(minX) ? maxX : minX, abs(maxY) > abs(minY) ? maxY : minY);
|
||||
primarySize = new Vector2f(maxX - minX, maxY - minY);
|
||||
int quadId = currentQuadCount;
|
||||
|
||||
// Vertices
|
||||
var localVertices = template.getVertices();
|
||||
int vertexStartIndex = currentQuadCount * VERTICES_PER_QUAD * 2;
|
||||
for (int i = 0; i < VERTICES_PER_QUAD; i++) {
|
||||
float localX = localVertices[i * 2];
|
||||
float localY = localVertices[i * 2 + 1];
|
||||
|
||||
vertexBuffer[vertexStartIndex + i * 2] = x + localX;
|
||||
vertexBuffer[vertexStartIndex + i * 2 + 1] = y + localY;
|
||||
}
|
||||
|
||||
// Texture
|
||||
var texCoordStartIndex = currentQuadCount * VERTICES_PER_QUAD * 2;
|
||||
System.arraycopy(textureCoordinates, 0, texCoordBuffer, texCoordStartIndex, textureCoordinates.length);
|
||||
|
||||
// Indices
|
||||
var indexStartIndex = currentQuadCount * INDICES_PER_QUAD;
|
||||
var vertexOffset = currentQuadCount * VERTICES_PER_QUAD;
|
||||
|
||||
int[] quadIndices = {
|
||||
// First triangle: indices 0-1-2
|
||||
// 1 ----------- 2
|
||||
// |XXXXXXXXXXX╱ |
|
||||
// |XXXXXXXX╱ |
|
||||
// |XXXXX╱ |
|
||||
// |XX╱ |
|
||||
// 0 ----------- 3
|
||||
vertexOffset + 0, vertexOffset + 1, vertexOffset + 2,
|
||||
|
||||
// Second triangle: indices 2-3-0
|
||||
// 1 ----------- 2
|
||||
// | ╱X|
|
||||
// | ╱XXXX|
|
||||
// | ╱XXXXXXX|
|
||||
// | ╱XXXXXXXXXX|
|
||||
// 0 ----------- 3
|
||||
vertexOffset + 2, vertexOffset + 3, vertexOffset + 0
|
||||
};
|
||||
|
||||
System.arraycopy(quadIndices, 0, indexBuffer, indexStartIndex, quadIndices.length);
|
||||
currentQuadCount++;
|
||||
|
||||
dirty = true;
|
||||
|
||||
return quadId;
|
||||
}
|
||||
|
||||
public void setQuadTextureCoordinates(int quadId, float[] textureCoordinates) {
|
||||
if (quadId < 0 || quadId >= currentQuadCount) {
|
||||
throw new IllegalArgumentException("Invalid quad ID: " + quadId);
|
||||
}
|
||||
|
||||
var texCoordsIndex = quadId * VERTICES_PER_QUAD * 2;
|
||||
System.arraycopy(textureCoordinates, 0, texCoordBuffer, texCoordsIndex, textureCoordinates.length);
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
currentQuadCount = 0;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public void removeQuad(int quadId) {
|
||||
if (quadId < 0 || quadId >= currentQuadCount) {
|
||||
throw new IllegalArgumentException("Invalid quad ID: " + quadId);
|
||||
}
|
||||
|
||||
int lastQuadId = currentQuadCount - 1;
|
||||
|
||||
if (quadId != lastQuadId) {
|
||||
swapQuads(quadId, lastQuadId);
|
||||
}
|
||||
|
||||
currentQuadCount--;
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
private void swapQuads(int quad1, int quad2) {
|
||||
// Swap vertices
|
||||
swapQuadData(vertexBuffer, quad1, quad2, VERTICES_PER_QUAD * 2);
|
||||
|
||||
// Swap texture coordinates
|
||||
swapQuadData(texCoordBuffer, quad1, quad2, VERTICES_PER_QUAD * 2);
|
||||
|
||||
// Swap indices
|
||||
swapQuadIndices(quad1, quad2);
|
||||
}
|
||||
|
||||
private void swapQuadData(float[] buffer, int quad1, int quad2, int dataSize) {
|
||||
var start1 = quad1 * dataSize;
|
||||
var start2 = quad2 * dataSize;
|
||||
|
||||
for (int i = 0; i < dataSize; i++) {
|
||||
var temp = buffer[start1 + i];
|
||||
buffer[start1 + i] = buffer[start2 + i];
|
||||
buffer[start2 + i] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
private void swapQuadIndices(int quad1, int quad2) {
|
||||
var start1 = quad1 * INDICES_PER_QUAD;
|
||||
var start2 = quad2 * INDICES_PER_QUAD;
|
||||
|
||||
var vertexOffset1 = quad1 * VERTICES_PER_QUAD;
|
||||
int[] newIndices1 = {
|
||||
vertexOffset1 + 0, vertexOffset1 + 1, vertexOffset1 + 2,
|
||||
vertexOffset1 + 2, vertexOffset1 + 3, vertexOffset1 + 0
|
||||
};
|
||||
|
||||
var vertexOffset2 = quad2 * VERTICES_PER_QUAD;
|
||||
int[] newIndices2 = {
|
||||
vertexOffset2 + 0, vertexOffset2 + 1, vertexOffset2 + 2,
|
||||
vertexOffset2 + 2, vertexOffset2 + 3, vertexOffset2 + 0
|
||||
};
|
||||
|
||||
System.arraycopy(newIndices1, 0, indexBuffer, start1, INDICES_PER_QUAD);
|
||||
System.arraycopy(newIndices2, 0, indexBuffer, start2, INDICES_PER_QUAD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
if (currentQuadCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
glBindVertexArray(vaoId);
|
||||
|
||||
var allIndices = currentQuadCount * INDICES_PER_QUAD;
|
||||
|
||||
if (dirty) {
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
var allVertices = currentQuadCount * VERTICES_PER_QUAD * 2;
|
||||
|
||||
// Vertex buffer
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vboIds.get(0));
|
||||
var vertexFloatBuffer = stack.mallocFloat(allVertices);
|
||||
for (int i = 0; i < allVertices; i++) {
|
||||
vertexFloatBuffer.put(vertexBuffer[i]);
|
||||
}
|
||||
vertexFloatBuffer.flip();
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexFloatBuffer);
|
||||
|
||||
// Texture coordinate buffer
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vboIds.get(1));
|
||||
var texCoordFloatBuffer = stack.mallocFloat(allVertices);
|
||||
for (int i = 0; i < allVertices; i++) {
|
||||
texCoordFloatBuffer.put(texCoordBuffer[i]);
|
||||
}
|
||||
texCoordFloatBuffer.flip();
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, texCoordFloatBuffer);
|
||||
|
||||
// Index buffer
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds.get(2));
|
||||
var indexIntBuffer = stack.mallocInt(allIndices);
|
||||
for (int i = 0; i < allIndices; i++) {
|
||||
indexIntBuffer.put(indexBuffer[i]);
|
||||
}
|
||||
indexIntBuffer.flip();
|
||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indexIntBuffer);
|
||||
}
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
glDrawElements(GL_TRIANGLES, elementsCount, GL_UNSIGNED_INT, 0);
|
||||
glDrawElements(GL_TRIANGLES, allIndices, GL_UNSIGNED_INT, 0);
|
||||
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
@@ -121,18 +295,11 @@ public class Mesh implements Renderable, Disposable {
|
||||
glDeleteVertexArrays(vaoId);
|
||||
}
|
||||
|
||||
public static Mesh quad(float width, float height, float originX, float originY) {
|
||||
var vertices = new float[] {
|
||||
-originX, -originY,
|
||||
-originX, height - originY,
|
||||
width - originX, height - originY,
|
||||
width - originX, - originY
|
||||
};
|
||||
public boolean isFull() {
|
||||
return currentQuadCount >= maxQuads;
|
||||
}
|
||||
|
||||
var texCoords = new float[] { 0, 0, 0, 1, 1, 1, 1, 0 };
|
||||
|
||||
var elements = new int[] { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
return new Mesh(vertices, texCoords, elements);
|
||||
public boolean isEmpty() {
|
||||
return currentQuadCount == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.object.mesh;
|
||||
|
||||
import com.bartlomiejpluta.base.internal.gc.Cleanable;
|
||||
|
||||
public interface MeshManager extends Cleanable {
|
||||
Mesh createMesh(int maxQuads);
|
||||
|
||||
QuadMesh createQuad(float width, float height, float originX, float originY);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.object.mesh;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
public class QuadMesh extends Mesh {
|
||||
private final int quadId;
|
||||
|
||||
@Getter
|
||||
private final float width;
|
||||
|
||||
@Getter
|
||||
private final float height;
|
||||
|
||||
@Getter
|
||||
private final float originX;
|
||||
|
||||
@Getter
|
||||
private final float originY;
|
||||
|
||||
QuadMesh(float width, float height, float originX, float originY) {
|
||||
this(new QuadTemplate(width, height, originX, originY));
|
||||
}
|
||||
|
||||
QuadMesh(@NonNull QuadTemplate template) {
|
||||
super(1);
|
||||
|
||||
this.width = template.getWidth();
|
||||
this.height = template.getHeight();
|
||||
this.originX = template.getOriginX();
|
||||
this.originY = template.getOriginY();
|
||||
|
||||
this.quadId = addQuad(template, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeQuad(int quadId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void setTextureCoordinates(float[] textureCoordinates) {
|
||||
super.setQuadTextureCoordinates(quadId, textureCoordinates);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.object.mesh;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class QuadTemplate {
|
||||
private final float width;
|
||||
private final float height;
|
||||
private final float originX;
|
||||
private final float originY;
|
||||
private final float[] vertices;
|
||||
|
||||
public QuadTemplate(float width, float height, float originX, float originY) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.originX = originX;
|
||||
this.originY = originY;
|
||||
|
||||
// 1 ----------- 2
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// 0 ----------- 3
|
||||
this.vertices = new float[]{
|
||||
-originX, -originY, // 0 - bottom left
|
||||
-originX, height - originY, // 1 - top left
|
||||
width - originX, height - originY, // 2 - top right
|
||||
width - originX, -originY // 3 - bottom right
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.object.texture;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.internal.gc.Disposable;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector2fc;
|
||||
@@ -15,7 +20,7 @@ import static org.lwjgl.opengl.GL13.glActiveTexture;
|
||||
import static org.lwjgl.stb.STBImage.stbi_failure_reason;
|
||||
import static org.lwjgl.stb.STBImage.stbi_load_from_memory;
|
||||
|
||||
public class Texture implements Disposable {
|
||||
public class Texture implements Renderable, Disposable {
|
||||
private static final int DESIRED_CHANNELS = 4;
|
||||
|
||||
private final int textureId;
|
||||
@@ -36,11 +41,10 @@ public class Texture implements Disposable {
|
||||
private final int columns;
|
||||
|
||||
@Getter
|
||||
private final Vector2fc spriteFragment;
|
||||
private final Vector2fc frameFragment;
|
||||
|
||||
@Getter
|
||||
private final Vector2fc spriteSize;
|
||||
|
||||
private final Vector2fc frameSize;
|
||||
|
||||
Texture(String textureFilename, ByteBuffer buffer, int rows, int columns) {
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
@@ -69,12 +73,50 @@ public class Texture implements Disposable {
|
||||
|
||||
this.rows = rows;
|
||||
this.columns = columns;
|
||||
this.spriteFragment = new Vector2f(1 / (float) columns, 1 / (float) rows);
|
||||
this.spriteSize = new Vector2f(width, height).mul(spriteFragment);
|
||||
this.frameFragment = new Vector2f(1 / (float) columns, 1 / (float) rows);
|
||||
this.frameSize = new Vector2f(width, height).mul(frameFragment);
|
||||
}
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
public float[][] getTextureCoordinatesForAllFrames() {
|
||||
var array = new float[rows * columns][];
|
||||
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
array[i] = getTextureCoordinates(i);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public float[] getTextureCoordinates(int id) {
|
||||
return getTextureCoordinates(id % columns, id / columns);
|
||||
}
|
||||
|
||||
public float[] getTextureCoordinates(int x, int y) {
|
||||
return getTextureCoordinates(x * (int) frameSize.x(), y * (int) frameSize.y(), (int) frameSize.x(), (int) frameSize.y());
|
||||
}
|
||||
|
||||
public float[] getTextureCoordinates(int textureX, int textureY, int tileWidth, int tileHeight) {
|
||||
var normalizedX = (float) textureX / width;
|
||||
var normalizedY = (float) textureY / height;
|
||||
var normalizedWidth = (float) tileWidth / width;
|
||||
var normalizedHeight = (float) tileHeight / height;
|
||||
|
||||
var xEnd = (normalizedX + normalizedWidth);
|
||||
var yEnd = (normalizedY + normalizedHeight);
|
||||
|
||||
return new float[]{
|
||||
normalizedX, normalizedY, // bottom left
|
||||
normalizedX, yEnd, // top left
|
||||
xEnd, yEnd, // top right
|
||||
xEnd, normalizedY // bottom right
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
shaderManager.setUniform(UniformName.UNI_HAS_OBJECT_TEXTURE, true);
|
||||
shaderManager.setUniform(UniformName.UNI_TEXTURE_SAMPLER, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.object.texture;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.color.Color;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import org.joml.Vector2fc;
|
||||
|
||||
@Getter
|
||||
public class TexturedMaterial extends Color implements Material {
|
||||
private final Texture texture;
|
||||
|
||||
public TexturedMaterial(@NonNull Texture texture) {
|
||||
this.texture = texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[][] getTextureCoordinatesForAllFrames() {
|
||||
return texture.getTextureCoordinatesForAllFrames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumns() {
|
||||
return texture.getColumns();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRows() {
|
||||
return texture.getRows();
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return texture.getFileName();
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return texture.getHeight();
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return texture.getWidth();
|
||||
}
|
||||
|
||||
public Vector2fc getFrameFragment() {
|
||||
return texture.getFrameFragment();
|
||||
}
|
||||
|
||||
public Vector2fc getFrameSize() {
|
||||
return texture.getFrameSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
shaderManager.setUniform(UniformName.UNI_OBJECT_COLOR, color);
|
||||
texture.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,7 @@ package com.bartlomiejpluta.base.engine.core.gl.render;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.CounterName;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.RenderConstants;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.Shader;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -12,6 +10,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static org.lwjgl.opengl.GL15.*;
|
||||
|
||||
@Slf4j
|
||||
@@ -23,28 +22,9 @@ public class DefaultRenderer implements Renderer {
|
||||
@Override
|
||||
public void init() {
|
||||
log.info("Initializing renderer");
|
||||
shaderManager
|
||||
.createShader("default", "/shaders/default.vs", "/shaders/default.fs")
|
||||
.selectShader("default")
|
||||
.createUniform(UniformName.UNI_VIEW_MODEL_MATRIX)
|
||||
.createUniform(UniformName.UNI_MODEL_MATRIX)
|
||||
.createUniform(UniformName.UNI_PROJECTION_MATRIX)
|
||||
.createUniform(UniformName.UNI_OBJECT_COLOR)
|
||||
.createUniform(UniformName.UNI_HAS_OBJECT_TEXTURE)
|
||||
.createUniform(UniformName.UNI_TEXTURE_SAMPLER)
|
||||
.createUniform(UniformName.UNI_SPRITE_SIZE)
|
||||
.createUniform(UniformName.UNI_SPRITE_POSITION)
|
||||
.createUniform(UniformName.UNI_AMBIENT)
|
||||
.createUniform(UniformName.UNI_ACTIVE_LIGHTS)
|
||||
.createCounter(CounterName.LIGHT);
|
||||
|
||||
for(int i=0; i<RenderConstants.MAX_LIGHTS; ++i) {
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].position");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].intensity");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].constantAttenuation");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].linearAttenuation");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].quadraticAttenuation");
|
||||
}
|
||||
log.info("Registering shaders");
|
||||
stream(Shader.values()).forEach(shader -> shader.init(shaderManager));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,7 +32,7 @@ public class DefaultRenderer implements Renderer {
|
||||
clear();
|
||||
updateViewport(screen);
|
||||
|
||||
shaderManager.selectShader("default").useSelectedShader();
|
||||
shaderManager.activateShader(Shader.DEFAULT.name);
|
||||
shaderManager.resetCounters();
|
||||
|
||||
// Important note:
|
||||
@@ -60,9 +40,10 @@ public class DefaultRenderer implements Renderer {
|
||||
// due to the fact, that the method updates projection and view matrices, that
|
||||
// are used to compute proper vertex coordinates of rendered objects (renderables).
|
||||
camera.render(screen, shaderManager);
|
||||
|
||||
renderable.render(screen, camera, shaderManager);
|
||||
|
||||
shaderManager.detachCurrentShader();
|
||||
shaderManager.deactivateShader();
|
||||
|
||||
screen.restoreState();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.shader.constant;
|
||||
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum Shader {
|
||||
DEFAULT("default", "/shaders/default.vs", "/shaders/default.fs", shaderManager -> {
|
||||
shaderManager
|
||||
.createUniform(UniformName.UNI_MODEL_MATRIX)
|
||||
.createUniform(UniformName.UNI_VIEW_MODEL_MATRIX)
|
||||
.createUniform(UniformName.UNI_PROJECTION_MATRIX)
|
||||
.createUniform(UniformName.UNI_HAS_OBJECT_TEXTURE)
|
||||
.createUniform(UniformName.UNI_OBJECT_COLOR)
|
||||
.createUniform(UniformName.UNI_TEXTURE_SAMPLER)
|
||||
.createUniform(UniformName.UNI_AMBIENT)
|
||||
.createUniform(UniformName.UNI_ACTIVE_LIGHTS)
|
||||
.createCounter(CounterName.LIGHT);
|
||||
|
||||
for (int i = 0; i < RenderConstants.MAX_LIGHTS; ++i) {
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].position");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].intensity");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].constantAttenuation");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].linearAttenuation");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].quadraticAttenuation");
|
||||
}
|
||||
});
|
||||
|
||||
public final String name;
|
||||
private final String vertex;
|
||||
private final String fragment;
|
||||
private final Consumer<ShaderManager> activator;
|
||||
|
||||
public void init(ShaderManager manager) {
|
||||
manager.createShader(name, vertex, fragment);
|
||||
manager.activateShader(name);
|
||||
this.activator.accept(manager);
|
||||
manager.deactivateShader();
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ public interface UniformName {
|
||||
String UNI_OBJECT_COLOR = "objectColor";
|
||||
String UNI_HAS_OBJECT_TEXTURE = "hasTexture";
|
||||
String UNI_TEXTURE_SAMPLER = "sampler";
|
||||
String UNI_SPRITE_SIZE = "spriteSize";
|
||||
String UNI_SPRITE_POSITION = "spritePosition";
|
||||
String UNI_LIGHTS = "lights";
|
||||
String UNI_ACTIVE_LIGHTS = "activeLights";
|
||||
String UNI_AMBIENT = "ambient";
|
||||
|
||||
@@ -12,7 +12,9 @@ import org.joml.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@@ -25,6 +27,7 @@ public class DefaultShaderManager implements ShaderManager {
|
||||
private final ResourcesManager resourcesManager;
|
||||
private final Map<String, ShaderProgram> shaders = new HashMap<>();
|
||||
private final Map<String, AtomicInteger> counters = new HashMap<>();
|
||||
private final Deque<ShaderProgram> stack = new LinkedList<>();
|
||||
private ShaderProgram current;
|
||||
|
||||
@Override
|
||||
@@ -40,20 +43,23 @@ public class DefaultShaderManager implements ShaderManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShaderManager selectShader(String programName) {
|
||||
public ShaderManager activateShader(String programName) {
|
||||
current = shaders.get(programName);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShaderManager useSelectedShader() {
|
||||
stack.push(current);
|
||||
current.use();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShaderManager detachCurrentShader() {
|
||||
public ShaderManager deactivateShader() {
|
||||
current.detach();
|
||||
stack.pop();
|
||||
current = stack.peek();
|
||||
|
||||
if (current != null) {
|
||||
current.use();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -150,7 +156,7 @@ public class DefaultShaderManager implements ShaderManager {
|
||||
@Override
|
||||
public ShaderManager createCounter(String counterName) {
|
||||
if (counters.containsKey(counterName)) {
|
||||
throw new AppException(format("The [%s] counter already exists", counterName));
|
||||
return this;
|
||||
}
|
||||
|
||||
log.info("Creating {} uniform counter", counterName);
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.bartlomiejpluta.base.engine.util.mesh;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.joml.Vector2f;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DefaultMeshManager implements MeshManager {
|
||||
private final Map<QuadDimension, Mesh> quads = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Mesh createQuad(float width, float height, float originX, float originY) {
|
||||
var dim = new QuadDimension(new Vector2f(width, height), new Vector2f(originX, originY));
|
||||
var mesh = quads.get(dim);
|
||||
|
||||
if(mesh == null) {
|
||||
log.info("Creating [w:{}, h:{} | O:{}, {}] mesh and putting it into the cache", width, height, originX, originY);
|
||||
mesh = Mesh.quad(width, height, originX, originY);
|
||||
quads.put(dim, mesh);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
log.info("Disposing meshes");
|
||||
quads.forEach((dim, mesh) -> mesh.dispose());
|
||||
log.info("{} meshes have been disposed", quads.size());
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class QuadDimension {
|
||||
private final Vector2f size;
|
||||
private final Vector2f origin;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.bartlomiejpluta.base.engine.util.mesh;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.internal.gc.Cleanable;
|
||||
|
||||
public interface MeshManager extends Cleanable {
|
||||
Mesh createQuad(float width, float height, float originX, float originY);
|
||||
}
|
||||
@@ -5,5 +5,5 @@ import com.bartlomiejpluta.base.engine.common.init.Initializable;
|
||||
import com.bartlomiejpluta.base.engine.common.manager.AssetManager;
|
||||
import com.bartlomiejpluta.base.engine.world.animation.asset.AnimationAsset;
|
||||
|
||||
public interface AnimationManager extends Initializable, AssetManager<AnimationAsset, Animation> {
|
||||
public interface AnimationManager extends AssetManager<AnimationAsset, Animation> {
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package com.bartlomiejpluta.base.engine.world.animation.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.api.animation.Animation;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadMesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.TextureManager;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration;
|
||||
import com.bartlomiejpluta.base.engine.util.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.world.animation.asset.AnimationAsset;
|
||||
import com.bartlomiejpluta.base.engine.world.animation.model.DefaultAnimation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -23,17 +22,16 @@ import java.util.Map;
|
||||
@Component
|
||||
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
|
||||
public class DefaultAnimationManager implements AnimationManager {
|
||||
private static final float QUAD_WIDTH = 1;
|
||||
private static final float QUAD_HEIGHT = 1;
|
||||
private static final float QUAD_ORIGIN_X = .5f;
|
||||
private static final float QUAD_ORIGIN_Y = .5f;
|
||||
|
||||
private final MeshManager meshManager;
|
||||
private final TextureManager textureManager;
|
||||
private final Map<String, AnimationAsset> assets = new HashMap<>();
|
||||
private final Map<String, Vector2fc[]> frames = new HashMap<>();
|
||||
private final ProjectConfiguration configuration;
|
||||
private Mesh mesh;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
mesh = meshManager.createQuad(1, 1, 0.5f, 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAsset(AnimationAsset asset) {
|
||||
@@ -60,9 +58,8 @@ public class DefaultAnimationManager implements AnimationManager {
|
||||
|
||||
var source = configuration.projectFile("animations", asset.getSource());
|
||||
var texture = textureManager.loadTexture(source, asset.getRows(), asset.getColumns());
|
||||
var material = Material.textured(texture);
|
||||
|
||||
return new DefaultAnimation(mesh, material, animationFrames);
|
||||
return new DefaultAnimation(meshManager.createQuad(QUAD_WIDTH, QUAD_HEIGHT, QUAD_ORIGIN_X, QUAD_ORIGIN_Y), texture, animationFrames);
|
||||
}
|
||||
|
||||
private Vector2fc[] createFrames(int rows, int columns) {
|
||||
|
||||
@@ -3,11 +3,10 @@ package com.bartlomiejpluta.base.engine.world.animation.model;
|
||||
import com.bartlomiejpluta.base.api.animation.Animated;
|
||||
import com.bartlomiejpluta.base.engine.core.engine.DefaultGameEngine;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadMesh;
|
||||
import com.bartlomiejpluta.base.engine.world.object.Sprite;
|
||||
import com.bartlomiejpluta.base.util.math.MathUtil;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.joml.Vector2fc;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class AnimatedSprite extends Sprite implements Animated {
|
||||
@@ -17,13 +16,59 @@ public abstract class AnimatedSprite extends Sprite implements Animated {
|
||||
private int intervalInMilliseconds = 100;
|
||||
protected int currentAnimationFrame;
|
||||
|
||||
public AnimatedSprite(Mesh mesh, Material material) {
|
||||
public AnimatedSprite(QuadMesh mesh, Material material) {
|
||||
super(mesh, material);
|
||||
}
|
||||
|
||||
protected abstract boolean shouldAnimate();
|
||||
|
||||
protected abstract Vector2fc[] getSpriteAnimationFramesPositions();
|
||||
/**
|
||||
* Allows subclasses to restrict animation to a specific subset of frames.
|
||||
*
|
||||
* <p>By default, this method returns {@code null}, which means all frames in the texture
|
||||
* are available for animation. In this case, the frame IDs used by {@link #setAnimationFrame(int)}
|
||||
* correspond directly to the global frame indices in the texture, as shown below:
|
||||
*
|
||||
* <pre>
|
||||
* +----+----+----+----+
|
||||
* | 00 | 01 | 02 | 03 |
|
||||
* +----+----+----+----+
|
||||
* | 04 | 05 | 06 | 07 |
|
||||
* +----+----+----+----+
|
||||
* | 08 | 09 | 10 | 11 |
|
||||
* +----+----+----+----+
|
||||
* | 12 | 13 | 14 | 15 |
|
||||
* +----+----+----+----+
|
||||
* </pre>
|
||||
*
|
||||
* <p>However, if this method returns a specific array of frame indices (e.g., {@code {8, 9, 10, 11, 13, 15}}),
|
||||
* only those frames will be used for animation. The frame IDs are then remapped, where index 0
|
||||
* corresponds to the first frame in the subset, index 1 to the second, and so on:
|
||||
*
|
||||
* <pre>
|
||||
* +----+----+----+----+
|
||||
* | | | | |
|
||||
* +----+----+----+----+
|
||||
* | | | | |
|
||||
* +----+----+----+----+
|
||||
* | 00 | 01 | 02 | 03 |
|
||||
* +----+----+----+----+
|
||||
* | | 04 | | 05 |
|
||||
* +----+----+----+----+
|
||||
* </pre>
|
||||
*
|
||||
* <p><strong>Important:</strong> When this method returns {@code null}, calling
|
||||
* {@link #setAnimationFrame(int)} has the same effect as calling {@code setFrame(int)}
|
||||
* directly, since the frame IDs map directly to the global texture frame indices.
|
||||
* When a subset is defined, {@link #setAnimationFrame(int)} uses local indices
|
||||
* within that subset.
|
||||
*
|
||||
* @return an array of global frame IDs to use for animation, or {@code null} to use all frames
|
||||
*/
|
||||
|
||||
protected int[] getAvailableFramesSubset() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnimationSpeed(float speed) {
|
||||
@@ -37,22 +82,24 @@ public abstract class AnimatedSprite extends Sprite implements Animated {
|
||||
|
||||
@Override
|
||||
public void setAnimationFrame(int frame) {
|
||||
var positions = getSpriteAnimationFramesPositions();
|
||||
currentAnimationFrame = frame % positions.length;
|
||||
var current = positions[currentAnimationFrame];
|
||||
material.setSpritePosition(current);
|
||||
var availableFrames = getAvailableFramesSubset();
|
||||
|
||||
if (availableFrames == null) {
|
||||
setFrame(frame % getTextureCoordinates().length);
|
||||
return;
|
||||
}
|
||||
|
||||
setFrame(availableFrames[frame % availableFrames.length]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float dt) {
|
||||
if (shouldAnimate()) {
|
||||
time += dt * 1000;
|
||||
var positions = getSpriteAnimationFramesPositions();
|
||||
currentAnimationFrame = ((time % (positions.length * intervalInMilliseconds)) / intervalInMilliseconds);
|
||||
var current = positions[currentAnimationFrame];
|
||||
material.setSpritePosition(current);
|
||||
} else {
|
||||
time = 0;
|
||||
setAnimationFrame(time / intervalInMilliseconds * intervalInMilliseconds);
|
||||
return;
|
||||
}
|
||||
|
||||
time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import com.bartlomiejpluta.base.api.move.AnimationMovement;
|
||||
import com.bartlomiejpluta.base.api.move.Direction;
|
||||
import com.bartlomiejpluta.base.api.move.Movement;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadMesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.world.movement.MovableSprite;
|
||||
import com.bartlomiejpluta.base.util.path.Path;
|
||||
import com.bartlomiejpluta.base.util.path.PathExecutor;
|
||||
@@ -19,6 +20,7 @@ import org.joml.Vector2fc;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static com.bartlomiejpluta.base.engine.core.gl.object.material.Material.textured;
|
||||
import static com.bartlomiejpluta.base.util.path.PathProgress.DONE;
|
||||
import static com.bartlomiejpluta.base.util.path.PathProgress.SEGMENT_FAILED;
|
||||
|
||||
@@ -40,7 +42,7 @@ public class DefaultAnimation extends MovableSprite implements Animation {
|
||||
private boolean enabled = true;
|
||||
|
||||
@Getter
|
||||
private PathExecutor<Animation> pathExecutor = new PathExecutor<>(this);
|
||||
private final PathExecutor<Animation> pathExecutor = new PathExecutor<>(this);
|
||||
private boolean finishOnEnd;
|
||||
private boolean finishOnFail;
|
||||
|
||||
@@ -51,12 +53,12 @@ public class DefaultAnimation extends MovableSprite implements Animation {
|
||||
@Getter
|
||||
private final CompletableFuture<Animation> future = new CompletableFuture<>();
|
||||
|
||||
public DefaultAnimation(Mesh mesh, Material material, @NonNull Vector2fc[] frames) {
|
||||
super(mesh, material);
|
||||
public DefaultAnimation(QuadMesh mesh, Texture texture, @NonNull Vector2fc[] frames) {
|
||||
super(mesh, textured(texture));
|
||||
this.frames = frames;
|
||||
this.lastFrameIndex = frames.length - 1;
|
||||
|
||||
this.animationSpriteSize = material.getTexture().getSpriteSize();
|
||||
this.animationSpriteSize = texture.getFrameSize();
|
||||
super.setScale(animationSpriteSize.x() * animationScale.x, animationSpriteSize.y() * animationScale.y);
|
||||
}
|
||||
|
||||
@@ -90,11 +92,6 @@ public class DefaultAnimation extends MovableSprite implements Animation {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Vector2fc[] getSpriteAnimationFramesPositions() {
|
||||
return frames;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDefaultAnimationFrame() {
|
||||
// do nothing
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.bartlomiejpluta.base.engine.world.autotile.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.common.init.Initializable;
|
||||
import com.bartlomiejpluta.base.engine.common.manager.AssetManager;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.asset.AutoTileSetAsset;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet;
|
||||
|
||||
public interface AutoTileManager extends Initializable, AssetManager<AutoTileSetAsset, AutoTileSet> {
|
||||
public interface AutoTileManager extends AssetManager<AutoTileSetAsset, AutoTileSet> {
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package com.bartlomiejpluta.base.engine.world.autotile.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.TextureManager;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration;
|
||||
import com.bartlomiejpluta.base.engine.util.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.asset.AutoTileSetAsset;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -20,16 +19,9 @@ import java.util.Map;
|
||||
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
|
||||
public class DefaultAutoTileSetManager implements AutoTileManager {
|
||||
private final TextureManager textureManager;
|
||||
private final MeshManager meshManager;
|
||||
private final Map<String, AutoTileSet> autoTiles = new HashMap<>();
|
||||
private final Map<String, AutoTileSet> tileSets = new HashMap<>();
|
||||
private final Map<String, AutoTileSetAsset> assets = new HashMap<>();
|
||||
private final ProjectConfiguration configuration;
|
||||
private Mesh mesh;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
this.mesh = meshManager.createQuad(1, 1, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAsset(AutoTileSetAsset asset) {
|
||||
@@ -44,9 +36,9 @@ public class DefaultAutoTileSetManager implements AutoTileManager {
|
||||
|
||||
@Override
|
||||
public AutoTileSet loadObject(String uid) {
|
||||
var autoTile = autoTiles.get(uid);
|
||||
var tileSet = tileSets.get(uid);
|
||||
|
||||
if (autoTile == null) {
|
||||
if (tileSet == null) {
|
||||
var asset = assets.get(uid);
|
||||
|
||||
if (asset == null) {
|
||||
@@ -55,11 +47,12 @@ public class DefaultAutoTileSetManager implements AutoTileManager {
|
||||
|
||||
var source = configuration.projectFile("autotiles", asset.getSource());
|
||||
var texture = textureManager.loadTexture(source, asset.getRows() * asset.getLayout().getRows() * 2, asset.getColumns() * asset.getLayout().getColumns() * 2);
|
||||
autoTile = new AutoTileSet(texture, mesh, asset.getRows(), asset.getColumns(), asset.getLayout());
|
||||
tileSet = new AutoTileSet(texture, asset.getLayout());
|
||||
|
||||
log.info("Loading auto tile set from assets to cache under the key: [{}]", uid);
|
||||
autoTiles.put(uid, autoTile);
|
||||
tileSets.put(uid, tileSet);
|
||||
}
|
||||
|
||||
return autoTile;
|
||||
return tileSet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.bartlomiejpluta.base.engine.world.autotile.model;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.world.object.Sprite;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector2fc;
|
||||
import org.joml.Vector2ic;
|
||||
|
||||
@Getter
|
||||
public class AutoSubTile extends Sprite {
|
||||
private final Vector2fc tileSpriteSize;
|
||||
private final Vector2f tileScale = new Vector2f(1, 1);
|
||||
|
||||
public AutoSubTile(Mesh mesh, AutoTileSet autoTileSet) {
|
||||
super(mesh, Material.textured(autoTileSet.getTexture()));
|
||||
|
||||
tileSpriteSize = material.getTexture().getSpriteSize();
|
||||
|
||||
super.setScale(tileSpriteSize.x() * tileScale.x, tileSpriteSize.y() * tileScale.y);
|
||||
}
|
||||
|
||||
public void recalculate(@NonNull Vector2ic spritePosition) {
|
||||
material.setSpritePosition(spritePosition.y(), spritePosition.x());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScale(float scale) {
|
||||
this.tileScale.x = scale;
|
||||
this.tileScale.y = scale;
|
||||
super.setScale(tileSpriteSize.x() * scale, tileSpriteSize.y() * scale);
|
||||
}
|
||||
|
||||
public void setScale(float scaleX, float scaleY) {
|
||||
this.tileScale.x = scaleX;
|
||||
this.tileScale.y = scaleY;
|
||||
super.setScale(tileSpriteSize.x() * scaleX, tileSpriteSize.y() * scaleY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getScaleX() {
|
||||
return tileScale.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleX(float scaleX) {
|
||||
this.tileScale.x = scaleX;
|
||||
super.setScaleX(tileSpriteSize.x() * scaleX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getScaleY() {
|
||||
return tileScale.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleY(float scaleY) {
|
||||
this.tileScale.y = scaleY;
|
||||
super.setScaleY(tileSpriteSize.x() * scaleY);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package com.bartlomiejpluta.base.engine.world.autotile.model;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
public class AutoTile implements Renderable {
|
||||
private final AutoTileSet autoTileSet;
|
||||
private final AutoSubTile topLeftSubTile;
|
||||
private final AutoSubTile topRightSubTile;
|
||||
private final AutoSubTile bottomLeftSubTile;
|
||||
private final AutoSubTile bottomRightSubTile;
|
||||
|
||||
@Getter
|
||||
private int setId;
|
||||
|
||||
public AutoTile(@NonNull Mesh mesh, @NonNull AutoTileSet autoTileSet, int setId) {
|
||||
this.topLeftSubTile = new AutoSubTile(mesh, autoTileSet);
|
||||
this.topRightSubTile = new AutoSubTile(mesh, autoTileSet);
|
||||
this.bottomLeftSubTile = new AutoSubTile(mesh, autoTileSet);
|
||||
this.bottomRightSubTile = new AutoSubTile(mesh, autoTileSet);
|
||||
this.autoTileSet = autoTileSet;
|
||||
this.setId = setId;
|
||||
}
|
||||
|
||||
public void setCoordinates(int column, int row) {
|
||||
var x = column * autoTileSet.getTileSize().x() * 2;
|
||||
var y = row * autoTileSet.getTileSize().y() * 2;
|
||||
|
||||
topLeftSubTile.setPosition(x, y);
|
||||
topRightSubTile.setPosition(x + autoTileSet.getTileSize().x(), y);
|
||||
bottomLeftSubTile.setPosition(x, y + autoTileSet.getTileSize().y());
|
||||
bottomRightSubTile.setPosition(x + autoTileSet.getTileSize().x(), y + autoTileSet.getTileSize().y());
|
||||
}
|
||||
|
||||
public void regularTile(Integer setId, int topLeft, int topRight, int bottomLeft, int bottomRight) {
|
||||
if (setId == null) {
|
||||
setId = this.setId;
|
||||
}
|
||||
|
||||
topLeftSubTile.recalculate(autoTileSet.getTopLeftSubTiles()[setId][topLeft]);
|
||||
topRightSubTile.recalculate(autoTileSet.getTopRightSubTiles()[setId][topRight]);
|
||||
bottomLeftSubTile.recalculate(autoTileSet.getBottomLeftSubTiles()[setId][bottomLeft]);
|
||||
bottomRightSubTile.recalculate(autoTileSet.getBottomRightSubTiles()[setId][bottomRight]);
|
||||
}
|
||||
|
||||
public void islandTile(Integer setId) {
|
||||
if (setId == null) {
|
||||
setId = this.setId;
|
||||
}
|
||||
|
||||
topLeftSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][0]);
|
||||
topRightSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][1]);
|
||||
bottomLeftSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][2]);
|
||||
bottomRightSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][3]);
|
||||
}
|
||||
|
||||
public void shiftTileSet() {
|
||||
this.setId = (setId + 1) % autoTileSet.getSetsCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
topLeftSubTile.render(screen, camera, shaderManager);
|
||||
topRightSubTile.render(screen, camera, shaderManager);
|
||||
bottomLeftSubTile.render(screen, camera, shaderManager);
|
||||
bottomRightSubTile.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
@@ -1,207 +1,25 @@
|
||||
package com.bartlomiejpluta.base.engine.world.autotile.model;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import org.joml.Vector2fc;
|
||||
import org.joml.Vector2i;
|
||||
import org.joml.Vector2ic;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
// Algorithm source: https://love2d.org/forums/viewtopic.php?t=7826
|
||||
@Getter
|
||||
public class AutoTileSet {
|
||||
private static final Vector2ic ISLAND_TILE_2x3 = new Vector2i(0, 0);
|
||||
private static final Vector2ic CROSS_TILE_2x3 = new Vector2i(1, 0);
|
||||
private static final Vector2ic TOP_LEFT_TILE_2x3 = new Vector2i(0, 1);
|
||||
private static final Vector2ic TOP_RIGHT_TILE_2x3 = new Vector2i(1, 1);
|
||||
private static final Vector2ic BOTTOM_LEFT_TILE_2x3 = new Vector2i(0, 2);
|
||||
private static final Vector2ic BOTTOM_RIGHT_TILE_2x3 = new Vector2i(1, 2);
|
||||
|
||||
private static final Vector2ic TOP_LEFT_TILE_2x2 = new Vector2i(0, 0);
|
||||
private static final Vector2ic TOP_RIGHT_TILE_2x2 = new Vector2i(1, 0);
|
||||
private static final Vector2ic BOTTOM_LEFT_TILE_2x2 = new Vector2i(0, 1);
|
||||
private static final Vector2ic BOTTOM_RIGHT_TILE_2x2 = new Vector2i(1, 1);
|
||||
|
||||
private final int textureSubTilesRows;
|
||||
private final int textureSubTilesColumns;
|
||||
|
||||
|
||||
@Getter
|
||||
private Vector2ic[][] islandSubTiles;
|
||||
|
||||
@Getter
|
||||
private Vector2ic[][] topLeftSubTiles;
|
||||
|
||||
@Getter
|
||||
private Vector2ic[][] topRightSubTiles;
|
||||
|
||||
@Getter
|
||||
private Vector2ic[][] bottomLeftSubTiles;
|
||||
|
||||
@Getter
|
||||
private Vector2ic[][] bottomRightSubTiles;
|
||||
|
||||
@Getter
|
||||
private final Texture texture;
|
||||
private final Mesh mesh;
|
||||
|
||||
@Getter
|
||||
private final Vector2fc tileSize;
|
||||
|
||||
@Getter
|
||||
private final AutoTileLayout layout;
|
||||
private final int rows;
|
||||
private final int columns;
|
||||
|
||||
@Getter
|
||||
private final int setsColumns;
|
||||
private final int setsRows;
|
||||
private final int setsCount;
|
||||
|
||||
public AutoTileSet(@NonNull Texture texture, @NonNull Mesh mesh, int rows, int columns, @NonNull AutoTileLayout layout) {
|
||||
|
||||
public AutoTileSet(@NonNull Texture texture, @NonNull AutoTileLayout layout) {
|
||||
this.texture = texture;
|
||||
this.mesh = mesh;
|
||||
this.rows = rows;
|
||||
this.columns = columns;
|
||||
this.tileSize = texture.getSpriteSize();
|
||||
this.layout = layout;
|
||||
this.setsCount = rows * columns;
|
||||
this.textureSubTilesRows = layout.getRows() * 2;
|
||||
this.textureSubTilesColumns = layout.getColumns() * 2;
|
||||
|
||||
switch (layout) {
|
||||
case LAYOUT_2X2 -> init2x2();
|
||||
case LAYOUT_2X3 -> init2x3();
|
||||
}
|
||||
}
|
||||
|
||||
private void init2x3() {
|
||||
var islandSubTiles = new LinkedList<Vector2ic[]>();
|
||||
var topLeftSubTiles = new LinkedList<Vector2ic[]>();
|
||||
var topRightSubTiles = new LinkedList<Vector2ic[]>();
|
||||
var bottomLeftSubTiles = new LinkedList<Vector2ic[]>();
|
||||
var bottomRightSubTiles = new LinkedList<Vector2ic[]>();
|
||||
|
||||
for (int setId = 0; setId < setsCount; ++setId) {
|
||||
var crossSubTiles = cutSubTiles(setId, CROSS_TILE_2x3);
|
||||
var topLeftTileSubTiles = cutSubTiles(setId, TOP_LEFT_TILE_2x3);
|
||||
var topRightTileSubTiles = cutSubTiles(setId, TOP_RIGHT_TILE_2x3);
|
||||
var bottomLeftTileSubTiles = cutSubTiles(setId, BOTTOM_LEFT_TILE_2x3);
|
||||
var bottomRightTileSubTiles = cutSubTiles(setId, BOTTOM_RIGHT_TILE_2x3);
|
||||
|
||||
/*
|
||||
* Indexes:
|
||||
* 0 - No connected tiles
|
||||
* 1 - Left tile is connected
|
||||
* 2 - Right tile is connected
|
||||
* 3 - Left and Right tiles are connected
|
||||
* 4 - Left, Right, and Center tiles are connected.
|
||||
*/
|
||||
var tl3 = crossSubTiles[0];
|
||||
var tr3 = crossSubTiles[1];
|
||||
var bl3 = crossSubTiles[2];
|
||||
var br3 = crossSubTiles[3];
|
||||
|
||||
var tl0 = topLeftTileSubTiles[0];
|
||||
var tr2 = topLeftTileSubTiles[1];
|
||||
var bl1 = topLeftTileSubTiles[2];
|
||||
var br4 = topLeftTileSubTiles[3];
|
||||
|
||||
var tl1 = topRightTileSubTiles[0];
|
||||
var tr0 = topRightTileSubTiles[1];
|
||||
var bl4 = topRightTileSubTiles[2];
|
||||
var br2 = topRightTileSubTiles[3];
|
||||
|
||||
var tl2 = bottomLeftTileSubTiles[0];
|
||||
var tr4 = bottomLeftTileSubTiles[1];
|
||||
var bl0 = bottomLeftTileSubTiles[2];
|
||||
var br1 = bottomLeftTileSubTiles[3];
|
||||
|
||||
var tl4 = bottomRightTileSubTiles[0];
|
||||
var tr1 = bottomRightTileSubTiles[1];
|
||||
var bl2 = bottomRightTileSubTiles[2];
|
||||
var br0 = bottomRightTileSubTiles[3];
|
||||
|
||||
islandSubTiles.add(cutSubTiles(setId, ISLAND_TILE_2x3));
|
||||
topLeftSubTiles.add(new Vector2ic[]{tl0, tl1, tl2, tl3, tl4});
|
||||
topRightSubTiles.add(new Vector2ic[]{tr0, tr1, tr2, tr3, tr4});
|
||||
bottomLeftSubTiles.add(new Vector2ic[]{bl0, bl1, bl2, bl3, bl4});
|
||||
bottomRightSubTiles.add(new Vector2ic[]{br0, br1, br2, br3, br4});
|
||||
}
|
||||
|
||||
this.islandSubTiles = islandSubTiles.toArray(new Vector2ic[islandSubTiles.size()][]);
|
||||
this.topLeftSubTiles = topLeftSubTiles.toArray(new Vector2ic[topLeftSubTiles.size()][]);
|
||||
this.topRightSubTiles = topRightSubTiles.toArray(new Vector2ic[topRightSubTiles.size()][]);
|
||||
this.bottomLeftSubTiles = bottomLeftSubTiles.toArray(new Vector2ic[bottomLeftSubTiles.size()][]);
|
||||
this.bottomRightSubTiles = bottomRightSubTiles.toArray(new Vector2ic[bottomRightSubTiles.size()][]);
|
||||
}
|
||||
|
||||
private void init2x2() {
|
||||
var topLeftSubTiles = new LinkedList<Vector2ic[]>();
|
||||
var topRightSubTiles = new LinkedList<Vector2ic[]>();
|
||||
var bottomLeftSubTiles = new LinkedList<Vector2ic[]>();
|
||||
var bottomRightSubTiles = new LinkedList<Vector2ic[]>();
|
||||
|
||||
for (int setId = 0; setId < setsCount; ++setId) {
|
||||
var topLeftTileSubTiles = cutSubTiles(setId, TOP_LEFT_TILE_2x2);
|
||||
var topRightTileSubTiles = cutSubTiles(setId, TOP_RIGHT_TILE_2x2);
|
||||
var bottomLeftTileSubTiles = cutSubTiles(setId, BOTTOM_LEFT_TILE_2x2);
|
||||
var bottomRightTileSubTiles = cutSubTiles(setId, BOTTOM_RIGHT_TILE_2x2);
|
||||
|
||||
/*
|
||||
* Indexes:
|
||||
* 0 - No connected tiles
|
||||
* 1 - Left tile is connected
|
||||
* 2 - Right tile is connected
|
||||
* 3 - Left, Right, and Center tiles are connected.
|
||||
*/
|
||||
|
||||
var tl0 = topLeftTileSubTiles[0];
|
||||
var tr2 = topLeftTileSubTiles[1];
|
||||
var bl1 = topLeftTileSubTiles[2];
|
||||
var br3 = topLeftTileSubTiles[3];
|
||||
|
||||
var tl1 = topRightTileSubTiles[0];
|
||||
var tr0 = topRightTileSubTiles[1];
|
||||
var bl3 = topRightTileSubTiles[2];
|
||||
var br2 = topRightTileSubTiles[3];
|
||||
|
||||
var tl2 = bottomLeftTileSubTiles[0];
|
||||
var tr3 = bottomLeftTileSubTiles[1];
|
||||
var bl0 = bottomLeftTileSubTiles[2];
|
||||
var br1 = bottomLeftTileSubTiles[3];
|
||||
|
||||
var tl3 = bottomRightTileSubTiles[0];
|
||||
var tr1 = bottomRightTileSubTiles[1];
|
||||
var bl2 = bottomRightTileSubTiles[2];
|
||||
var br0 = bottomRightTileSubTiles[3];
|
||||
|
||||
topLeftSubTiles.add(new Vector2ic[]{tl0, tl1, tl2, tl3});
|
||||
topRightSubTiles.add(new Vector2ic[]{tr0, tr1, tr2, tr3});
|
||||
bottomLeftSubTiles.add(new Vector2ic[]{bl0, bl1, bl2, bl3});
|
||||
bottomRightSubTiles.add(new Vector2ic[]{br0, br1, br2, br3});
|
||||
}
|
||||
|
||||
this.topLeftSubTiles = topLeftSubTiles.toArray(new Vector2ic[topLeftSubTiles.size()][]);
|
||||
this.topRightSubTiles = topRightSubTiles.toArray(new Vector2ic[topRightSubTiles.size()][]);
|
||||
this.bottomLeftSubTiles = bottomLeftSubTiles.toArray(new Vector2ic[bottomLeftSubTiles.size()][]);
|
||||
this.bottomRightSubTiles = bottomRightSubTiles.toArray(new Vector2ic[bottomRightSubTiles.size()][]);
|
||||
}
|
||||
|
||||
private Vector2ic[] cutSubTiles(int setId, Vector2ic tile) {
|
||||
var topLeft = getTile(setId, tile.y() * 2, tile.x() * 2);
|
||||
var topRight = getTile(setId, tile.y() * 2, tile.x() * 2 + 1);
|
||||
var bottomLeft = getTile(setId, tile.y() * 2 + 1, tile.x() * 2);
|
||||
var bottomRight = getTile(setId, tile.y() * 2 + 1, tile.x() * 2 + 1);
|
||||
|
||||
return new Vector2ic[]{topLeft, topRight, bottomLeft, bottomRight};
|
||||
}
|
||||
|
||||
private Vector2ic getTile(int setId, int row, int column) {
|
||||
return new Vector2i(((setId / columns) * textureSubTilesRows) + row, ((setId % columns) * textureSubTilesColumns) + column);
|
||||
}
|
||||
|
||||
public AutoTile createTile(int setId) {
|
||||
return new AutoTile(mesh, this, setId);
|
||||
this.setsColumns = texture.getColumns() / layout.getColumns() / 2;
|
||||
this.setsRows = texture.getRows() / layout.getRows() / 2;
|
||||
this.setsCount = setsRows * setsColumns;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,32 @@ package com.bartlomiejpluta.base.engine.world.camera;
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.context.Context;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.engine.world.object.Model;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
import org.joml.FrustumIntersection;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
public class DefaultCamera extends Model implements Camera {
|
||||
@Getter
|
||||
private final Matrix4f projectionMatrix = new Matrix4f();
|
||||
private final Matrix4f viewMatrix = new Matrix4f();
|
||||
private final Matrix4f projectionViewMatrix = new Matrix4f();
|
||||
private final FrustumIntersection frustum = new FrustumIntersection();
|
||||
|
||||
@Getter
|
||||
private float minX = 0f;
|
||||
|
||||
@Getter
|
||||
private float maxX = 0f;
|
||||
|
||||
@Getter
|
||||
private float minY = 0f;
|
||||
|
||||
@Getter
|
||||
private float maxY = 0f;
|
||||
|
||||
@Override
|
||||
public Matrix4fc computeViewModelMatrix(Matrix4fc modelMatrix) {
|
||||
return new Matrix4f(viewMatrix).mul(modelMatrix);
|
||||
@@ -35,10 +48,13 @@ public class DefaultCamera extends Model implements Camera {
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, ShaderManager shaderManager) {
|
||||
var screenWidth = screen.getWidth();
|
||||
var screenHeight = screen.getHeight();
|
||||
|
||||
// Update matrices
|
||||
projectionMatrix
|
||||
.identity()
|
||||
.setOrtho2D(0, screen.getWidth(), screen.getHeight(), 0);
|
||||
.setOrtho2D(0, screenWidth, screenHeight, 0);
|
||||
|
||||
viewMatrix
|
||||
.identity()
|
||||
@@ -51,6 +67,9 @@ public class DefaultCamera extends Model implements Camera {
|
||||
|
||||
frustum.set(projectionViewMatrix);
|
||||
|
||||
shaderManager.setUniform(UniformName.UNI_PROJECTION_MATRIX, projectionMatrix);
|
||||
this.minX = position.x;
|
||||
this.maxX = position.x + screenWidth / scaleX;
|
||||
this.minY = position.y;
|
||||
this.maxY = position.y + screenHeight / scaleY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ import com.bartlomiejpluta.base.api.character.Character;
|
||||
import com.bartlomiejpluta.base.engine.common.init.Initializable;
|
||||
import com.bartlomiejpluta.base.internal.gc.Cleanable;
|
||||
|
||||
public interface CharacterManager extends Initializable, Cleanable {
|
||||
public interface CharacterManager extends Cleanable {
|
||||
Character createCharacter(String characterSetUid);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.bartlomiejpluta.base.engine.world.character.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.common.manager.AssetManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.world.character.asset.CharacterSetAsset;
|
||||
import com.bartlomiejpluta.base.engine.world.character.model.CharacterSet;
|
||||
|
||||
public interface CharacterSetManager extends AssetManager<CharacterSetAsset, Material> {
|
||||
public interface CharacterSetManager extends AssetManager<CharacterSetAsset, CharacterSet> {
|
||||
}
|
||||
|
||||
@@ -1,56 +1,31 @@
|
||||
package com.bartlomiejpluta.base.engine.world.character.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.api.character.Character;
|
||||
import com.bartlomiejpluta.base.api.move.Direction;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.util.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadMesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.world.character.config.CharacterSpriteConfiguration;
|
||||
import com.bartlomiejpluta.base.engine.world.character.model.DefaultCharacter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector2fc;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static java.util.stream.Collectors.toUnmodifiableMap;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor(onConstructor_ = @__(@Autowired))
|
||||
public class DefaultCharacterManager implements CharacterManager {
|
||||
private static final float QUAD_WIDTH = 1;
|
||||
private static final float QUAD_HEIGHT = 1;
|
||||
private static final float QUAD_ORIGIN_X = .5f;
|
||||
private static final float QUAD_ORIGIN_Y = 1;
|
||||
|
||||
private final MeshManager meshManager;
|
||||
private final CharacterSetManager characterSetManager;
|
||||
|
||||
private final int defaultSpriteColumn;
|
||||
private final Map<Direction, Integer> spriteDirectionRows;
|
||||
private final Map<Direction, Vector2fc> spriteDefaultRows;
|
||||
|
||||
private Mesh mesh;
|
||||
|
||||
@Autowired
|
||||
public DefaultCharacterManager(MeshManager meshManager, CharacterSetManager characterSetManager, CharacterSpriteConfiguration configuration) {
|
||||
this.meshManager = meshManager;
|
||||
this.characterSetManager = characterSetManager;
|
||||
|
||||
this.spriteDirectionRows = configuration.getSpriteDirectionRows();
|
||||
|
||||
defaultSpriteColumn = configuration.getDefaultSpriteColumn();
|
||||
this.spriteDefaultRows = spriteDirectionRows
|
||||
.entrySet()
|
||||
.stream()
|
||||
.collect(toUnmodifiableMap(Entry::getKey, entry -> new Vector2f(defaultSpriteColumn, entry.getValue())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
mesh = meshManager.createQuad(1, 1, 0.5f, 1);
|
||||
}
|
||||
private final CharacterSpriteConfiguration configuration;
|
||||
|
||||
@Override
|
||||
public Character createCharacter(String characterSetUid) {
|
||||
return new DefaultCharacter(mesh, characterSetManager, defaultSpriteColumn, spriteDirectionRows, spriteDefaultRows, characterSetUid);
|
||||
return new DefaultCharacter(characterSetManager, meshManager.createQuad(QUAD_WIDTH, QUAD_HEIGHT, QUAD_ORIGIN_X, QUAD_ORIGIN_Y), characterSetUid, configuration.getDefaultSpriteColumn());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package com.bartlomiejpluta.base.engine.world.character.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.TextureManager;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration;
|
||||
import com.bartlomiejpluta.base.engine.world.character.asset.CharacterSetAsset;
|
||||
import com.bartlomiejpluta.base.engine.world.character.config.CharacterSpriteConfiguration;
|
||||
import com.bartlomiejpluta.base.engine.world.character.model.CharacterSet;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -19,6 +20,8 @@ import java.util.Map;
|
||||
public class DefaultCharacterSetManager implements CharacterSetManager {
|
||||
private final TextureManager textureManager;
|
||||
private final Map<String, CharacterSetAsset> assets = new HashMap<>();
|
||||
private final Map<String, CharacterSet> charSets = new HashMap<>();
|
||||
private final CharacterSpriteConfiguration charsetConfiguration;
|
||||
private final ProjectConfiguration configuration;
|
||||
|
||||
@Override
|
||||
@@ -33,16 +36,24 @@ public class DefaultCharacterSetManager implements CharacterSetManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Material loadObject(String uid) {
|
||||
var asset = assets.get(uid);
|
||||
public CharacterSet loadObject(String uid) {
|
||||
var charSet = charSets.get(uid);
|
||||
|
||||
if (asset == null) {
|
||||
throw new AppException("The character set asset with UID: [%s] does not exist", uid);
|
||||
if (charSet == null) {
|
||||
var asset = assets.get(uid);
|
||||
|
||||
if (asset == null) {
|
||||
throw new AppException("The character set asset with UID: [%s] does not exist", uid);
|
||||
}
|
||||
|
||||
var source = configuration.projectFile("charsets", asset.getSource());
|
||||
|
||||
var texture = textureManager.loadTexture(source, asset.getRows(), asset.getColumns());
|
||||
charSet = CharacterSet.from(texture, charsetConfiguration.getSpriteDirectionRows());
|
||||
log.info("Loading character set from assets to cache under the key: [{}]", uid);
|
||||
charSets.put(uid, charSet);
|
||||
}
|
||||
|
||||
var source = configuration.projectFile("charsets", asset.getSource());
|
||||
var texture = textureManager.loadTexture(source, asset.getRows(), asset.getColumns());
|
||||
|
||||
return Material.textured(texture);
|
||||
return charSet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public class CharacterInstantAnimation {
|
||||
CharacterInstantAnimation(DefaultCharacter character, int firstFrame) {
|
||||
this.character = character;
|
||||
this.firstFrame = firstFrame;
|
||||
this.lastFrame = character.getMaterial().getTexture().getColumns() - 1;
|
||||
this.lastFrame = character.getMaterial().getColumns() - 1;
|
||||
}
|
||||
|
||||
public State update() {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.bartlomiejpluta.base.engine.world.character.model;
|
||||
|
||||
import com.bartlomiejpluta.base.api.move.Direction;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.bartlomiejpluta.base.api.move.Direction.values;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.function.Function.identity;
|
||||
import static java.util.stream.Collectors.toUnmodifiableMap;
|
||||
import static java.util.stream.IntStream.range;
|
||||
|
||||
@Getter
|
||||
public class CharacterSet {
|
||||
private final Texture texture;
|
||||
private final Map<Direction, int[]> frames;
|
||||
|
||||
private CharacterSet(@NonNull Texture texture, @NonNull Map<Direction, Integer> charsetRowsByDirections) {
|
||||
this.texture = texture;
|
||||
this.frames = stream(values())
|
||||
.collect(toUnmodifiableMap(identity(), calculateFrames(charsetRowsByDirections)));
|
||||
}
|
||||
|
||||
private Function<Direction, int[]> calculateFrames(Map<Direction, Integer> charsetRowsByDirections) {
|
||||
return d -> framesForRow(charsetRowsByDirections.get(d));
|
||||
}
|
||||
|
||||
private int[] framesForRow(int row) {
|
||||
var cols = texture.getColumns();
|
||||
return range(row * cols, (row + 1) * cols).toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CharacterSet instance from the specified texture and direction-to-row mapping.
|
||||
*
|
||||
* <p>This factory method constructs a CharacterSet by associating each facing direction
|
||||
* with its corresponding row in the character set texture. The method automatically
|
||||
* calculates frame indices for each direction based on the texture dimensions and
|
||||
* row mappings.
|
||||
*
|
||||
* <p>The character set texture should be organized as a grid where:
|
||||
* <ul>
|
||||
* <li>Each row represents graphics for a specific facing direction</li>
|
||||
* <li>Each column represents either an animation frame or object variant</li>
|
||||
* <li>Row indices are zero-based (0 = first row, 1 = second row, etc.)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Example mapping:
|
||||
* <pre>{@code
|
||||
* Map<Direction, Integer> mapping = Map.of(
|
||||
* Direction.DOWN, 0, // First row (index 0) for DOWN direction
|
||||
* Direction.LEFT, 1, // Second row (index 1) for LEFT direction
|
||||
* Direction.RIGHT, 2, // Third row (index 2) for RIGHT direction
|
||||
* Direction.UP, 3 // Fourth row (index 3) for UP direction
|
||||
* );
|
||||
* CharacterSet charset = CharacterSet.from(texture, mapping);
|
||||
* }</pre>
|
||||
*
|
||||
* @param texture the character set texture containing sprite graphics organized in rows and columns
|
||||
* @param charsetRowsByDirections a mapping that defines which texture row corresponds to each facing direction,
|
||||
* where the key is the direction and the value is the zero-based row index
|
||||
* @return a new CharacterSet instance configured with the specified texture and direction mappings
|
||||
* @throws NullPointerException if either texture or charsetRowsByDirections is null
|
||||
* @throws IllegalArgumentException if the charsetRowsByDirections map is empty or contains invalid row indices
|
||||
* @throws IndexOutOfBoundsException if any row index in the mapping exceeds the texture's row count
|
||||
*/
|
||||
public static CharacterSet from(Texture texture, Map<Direction, Integer> charsetRowsByDirections) {
|
||||
return new CharacterSet(texture, charsetRowsByDirections);
|
||||
}
|
||||
}
|
||||
@@ -8,35 +8,37 @@ import com.bartlomiejpluta.base.api.move.CharacterMovement;
|
||||
import com.bartlomiejpluta.base.api.move.Direction;
|
||||
import com.bartlomiejpluta.base.api.move.Movement;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadMesh;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.engine.world.character.manager.CharacterSetManager;
|
||||
import com.bartlomiejpluta.base.engine.world.movement.MovableSprite;
|
||||
import com.bartlomiejpluta.base.lib.event.EventHandler;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector2fc;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.bartlomiejpluta.base.engine.core.gl.object.material.Material.textured;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DefaultCharacter extends MovableSprite implements Character {
|
||||
private final int defaultSpriteColumn;
|
||||
private final CharacterSetManager characterSetManager;
|
||||
private final Map<Direction, Integer> spriteDirectionRows;
|
||||
private final Map<Direction, Vector2fc> spriteDefaultRows;
|
||||
private final Vector2f characterScale = new Vector2f(1, 1);
|
||||
private Vector2fc characterSetSize;
|
||||
|
||||
private final EventHandler eventHandler = new EventHandler();
|
||||
private final Queue<CharacterInstantAnimation> instantAnimations = new LinkedList<>();
|
||||
|
||||
@Setter
|
||||
private int defaultSpriteColumn;
|
||||
private CharacterSet characterSet;
|
||||
private Vector2fc characterSetSize;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -54,25 +56,22 @@ public class DefaultCharacter extends MovableSprite implements Character {
|
||||
|
||||
private boolean animationEnabled = true;
|
||||
|
||||
private final Queue<CharacterInstantAnimation> instantAnimations = new LinkedList<>();
|
||||
|
||||
public DefaultCharacter(Mesh mesh, CharacterSetManager characterSetManager, int defaultSpriteColumn, Map<Direction, Integer> spriteDirectionRows, Map<Direction, Vector2fc> spriteDefaultRows, String characterSetUid) {
|
||||
super(mesh, createMaterial(characterSetManager, characterSetUid));
|
||||
this.defaultSpriteColumn = defaultSpriteColumn;
|
||||
this.characterSetManager = characterSetManager;
|
||||
this.spriteDirectionRows = spriteDirectionRows;
|
||||
this.faceDirection = Direction.DOWN;
|
||||
this.spriteDefaultRows = spriteDefaultRows;
|
||||
|
||||
var texture = material.getTexture();
|
||||
if (texture != null) {
|
||||
this.characterSetSize = texture.getSpriteSize();
|
||||
super.setScale(characterSetSize.x() * characterScale.x, characterSetSize.y() * characterScale.y);
|
||||
}
|
||||
public DefaultCharacter(CharacterSetManager characterSetManager, QuadMesh mesh, @NonNull String characterSetUid, int defaultSpriteColumn) {
|
||||
this(mesh, characterSetManager.loadObject(characterSetUid), characterSetManager, defaultSpriteColumn);
|
||||
}
|
||||
|
||||
private static Material createMaterial(CharacterSetManager characterSetManager, String characterSetUid) {
|
||||
return characterSetUid != null ? characterSetManager.loadObject(requireNonNull(characterSetUid)) : Material.colored(0, 0, 0, 0);
|
||||
private DefaultCharacter(QuadMesh mesh, @NonNull CharacterSet characterSet, @NonNull CharacterSetManager characterSetManager, int defaultSpriteColumn) {
|
||||
super(mesh, textured(characterSet.getTexture()));
|
||||
|
||||
this.defaultSpriteColumn = defaultSpriteColumn;
|
||||
this.characterSetManager = characterSetManager;
|
||||
this.faceDirection = Direction.DOWN;
|
||||
this.characterSet = characterSet;
|
||||
this.characterSetSize = characterSet.getTexture().getFrameSize();
|
||||
|
||||
super.setScale(characterSetSize.x() * characterScale.x, characterSetSize.y() * characterScale.y);
|
||||
|
||||
setDefaultAnimationFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,38 +101,25 @@ public class DefaultCharacter extends MovableSprite implements Character {
|
||||
|
||||
@Override
|
||||
protected boolean shouldAnimate() {
|
||||
return animationEnabled && material.getTexture() != null && (isMoving() || !instantAnimations.isEmpty());
|
||||
return animationEnabled && (isMoving() || !instantAnimations.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Vector2fc[] getSpriteAnimationFramesPositions() {
|
||||
var row = spriteDirectionRows.get(faceDirection);
|
||||
var frames = material.getTexture().getColumns();
|
||||
var array = new Vector2f[frames];
|
||||
|
||||
for (int column = 0; column < frames; ++column) {
|
||||
array[column] = new Vector2f(column, row);
|
||||
}
|
||||
|
||||
return array;
|
||||
protected int[] getAvailableFramesSubset() {
|
||||
return characterSet.getFrames().get(faceDirection);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDefaultAnimationFrame() {
|
||||
material.setSpritePosition(spriteDefaultRows.get(faceDirection));
|
||||
setAnimationFrame(characterSet.getFrames().get(faceDirection)[defaultSpriteColumn]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeCharacterSet(String characterSetUid) {
|
||||
this.material = createMaterial(characterSetManager, characterSetUid);
|
||||
var texture = this.material.getTexture();
|
||||
|
||||
if (texture != null) {
|
||||
this.characterSetSize = texture.getSpriteSize();
|
||||
super.setScale(characterSetSize.x() * characterScale.x, characterSetSize.y() * characterScale.y);
|
||||
} else {
|
||||
this.characterSetSize = null;
|
||||
}
|
||||
this.characterSet = characterSetManager.loadObject(characterSetUid);
|
||||
this.characterSetSize = characterSet.getTexture().getFrameSize();
|
||||
super.setScale(characterSetSize.x() * characterScale.x, characterSetSize.y() * characterScale.y);
|
||||
applyTextureAndPreserveCurrentColor(characterSet.getTexture());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.bartlomiejpluta.base.engine.world.icon.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.api.icon.Icon;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.util.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadMesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.world.icon.model.DefaultIcon;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -13,18 +13,17 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
|
||||
public class DefaultIconManager implements IconManager {
|
||||
private static final float QUAD_WIDTH = 1;
|
||||
private static final float QUAD_HEIGHT = 1;
|
||||
private static final float QUAD_ORIGIN_X = .5f;
|
||||
private static final float QUAD_ORIGIN_Y = 1;
|
||||
|
||||
private final MeshManager meshManager;
|
||||
private final IconSetManager iconSetManager;
|
||||
private Mesh mesh;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
this.mesh = meshManager.createQuad(1, 1, 0.5f, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon createIcon(String iconSetUid, int row, int column) {
|
||||
return new DefaultIcon(mesh, iconSetManager, iconSetUid, row, column);
|
||||
return new DefaultIcon(iconSetManager, meshManager.createQuad(QUAD_WIDTH, QUAD_HEIGHT, QUAD_ORIGIN_X, QUAD_ORIGIN_Y), iconSetUid, row, column);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bartlomiejpluta.base.engine.world.icon.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.TextureManager;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration;
|
||||
@@ -38,7 +39,7 @@ public class DefaultIconSetManager implements IconSetManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Material loadObject(String uid) {
|
||||
public Texture loadObject(String uid) {
|
||||
var asset = assets.get(uid);
|
||||
|
||||
if (asset == null) {
|
||||
@@ -46,9 +47,7 @@ public class DefaultIconSetManager implements IconSetManager {
|
||||
}
|
||||
|
||||
var source = configuration.projectFile("iconsets", asset.getSource());
|
||||
var texture = textureManager.loadTexture(source, asset.getRows(), asset.getColumns());
|
||||
|
||||
return Material.textured(texture);
|
||||
return textureManager.loadTexture(source, asset.getRows(), asset.getColumns());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,6 +4,6 @@ import com.bartlomiejpluta.base.api.icon.Icon;
|
||||
import com.bartlomiejpluta.base.engine.common.init.Initializable;
|
||||
import com.bartlomiejpluta.base.internal.gc.Cleanable;
|
||||
|
||||
public interface IconManager extends Initializable, Cleanable {
|
||||
public interface IconManager extends Cleanable {
|
||||
Icon createIcon(String iconSetUid, int row, int column);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ package com.bartlomiejpluta.base.engine.world.icon.manager;
|
||||
import com.bartlomiejpluta.base.engine.common.manager.AssetManager;
|
||||
import com.bartlomiejpluta.base.engine.common.manager.ByteBufferAssetManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.world.icon.asset.IconSetAsset;
|
||||
|
||||
public interface IconSetManager extends AssetManager<IconSetAsset, Material>, ByteBufferAssetManager<IconSetAsset> {
|
||||
public interface IconSetManager extends AssetManager<IconSetAsset, Texture>, ByteBufferAssetManager<IconSetAsset> {
|
||||
}
|
||||
|
||||
@@ -4,18 +4,22 @@ import com.bartlomiejpluta.base.api.event.Event;
|
||||
import com.bartlomiejpluta.base.api.event.EventType;
|
||||
import com.bartlomiejpluta.base.api.icon.Icon;
|
||||
import com.bartlomiejpluta.base.api.map.layer.object.ObjectLayer;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadMesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.engine.world.icon.manager.IconSetManager;
|
||||
import com.bartlomiejpluta.base.engine.world.object.Sprite;
|
||||
import com.bartlomiejpluta.base.lib.event.EventHandler;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector2fc;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.bartlomiejpluta.base.engine.core.gl.object.material.Material.textured;
|
||||
|
||||
public class DefaultIcon extends Sprite implements Icon {
|
||||
private final EventHandler eventHandler = new EventHandler();
|
||||
private final IconSetManager iconSetManager;
|
||||
@@ -42,16 +46,17 @@ public class DefaultIcon extends Sprite implements Icon {
|
||||
@Getter
|
||||
private int zIndex;
|
||||
|
||||
public DefaultIcon(Mesh mesh, IconSetManager iconSetManager, String iconSetUid, int row, int column) {
|
||||
super(mesh, iconSetManager.loadObject(iconSetUid));
|
||||
this.iconSetManager = iconSetManager;
|
||||
material.setSpritePosition(column, row);
|
||||
public DefaultIcon(IconSetManager iconSetManager, QuadMesh mesh, String iconSetUid, int row, int column) {
|
||||
this(iconSetManager, mesh, iconSetManager.loadObject(iconSetUid), iconSetUid, row, column);
|
||||
}
|
||||
|
||||
var texture = material.getTexture();
|
||||
if (texture != null) {
|
||||
this.iconSetSize = texture.getSpriteSize();
|
||||
super.setScale(iconSetSize.x() * iconScale.x, iconSetSize.y() * iconScale.y);
|
||||
}
|
||||
private DefaultIcon(@NonNull IconSetManager iconSetManager, QuadMesh mesh, @NonNull Texture texture, @NonNull String iconSetUid, int row, int column) {
|
||||
super(mesh, textured(texture));
|
||||
this.iconSetManager = iconSetManager;
|
||||
setFrame(row, column);
|
||||
|
||||
this.iconSetSize = texture.getFrameSize();
|
||||
super.setScale(iconSetSize.x() * iconScale.x, iconSetSize.y() * iconScale.y);
|
||||
|
||||
this.iconSetUid = iconSetUid;
|
||||
this.iconSetRow = row;
|
||||
@@ -70,27 +75,23 @@ public class DefaultIcon extends Sprite implements Icon {
|
||||
|
||||
@Override
|
||||
public void changeIcon(int row, int column) {
|
||||
material.setSpritePosition(column, row);
|
||||
setFrame(row, column);
|
||||
this.iconSetRow = row;
|
||||
this.iconSetColumn = column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeIcon(String iconSetUid, int row, int column) {
|
||||
this.material = iconSetManager.loadObject(iconSetUid);
|
||||
material.setSpritePosition(column, row);
|
||||
var texture = iconSetManager.loadObject(iconSetUid);
|
||||
applyTextureAndPreserveCurrentColor(texture);
|
||||
setFrame(row, column);
|
||||
|
||||
this.iconSetUid = iconSetUid;
|
||||
this.iconSetRow = row;
|
||||
this.iconSetColumn = column;
|
||||
|
||||
var texture = material.getTexture();
|
||||
if (texture != null) {
|
||||
this.iconSetSize = texture.getSpriteSize();
|
||||
super.setScale(iconSetSize.x() * iconScale.x, iconSetSize.y() * iconScale.y);
|
||||
} else {
|
||||
this.iconSetSize = null;
|
||||
}
|
||||
this.iconSetSize = texture.getFrameSize();
|
||||
super.setScale(iconSetSize.x() * iconScale.x, iconSetSize.y() * iconScale.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package com.bartlomiejpluta.base.engine.world.image.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.api.image.Image;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadMesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.TextureManager;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration;
|
||||
import com.bartlomiejpluta.base.engine.util.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.util.res.ResourcesManager;
|
||||
import com.bartlomiejpluta.base.engine.world.image.asset.ImageAsset;
|
||||
import com.bartlomiejpluta.base.engine.world.image.model.DefaultImage;
|
||||
@@ -24,18 +23,17 @@ import java.util.Map;
|
||||
@Component
|
||||
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
|
||||
public class DefaultImageManager implements ImageManager {
|
||||
private static final float QUAD_WIDTH = 1;
|
||||
private static final float QUAD_HEIGHT = 1;
|
||||
private static final float QUAD_ORIGIN_X = 0;
|
||||
private static final float QUAD_ORIGIN_Y = 0;
|
||||
|
||||
private final MeshManager meshManager;
|
||||
private final TextureManager textureManager;
|
||||
private final ResourcesManager resourcesManager;
|
||||
private final Map<String, ImageAsset> assets = new HashMap<>();
|
||||
private final Map<String, ByteBuffer> imageBuffers = new HashMap<>();
|
||||
private final ProjectConfiguration configuration;
|
||||
private Mesh mesh;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
mesh = meshManager.createQuad(1, 1, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAsset(ImageAsset asset) {
|
||||
@@ -63,10 +61,9 @@ public class DefaultImageManager implements ImageManager {
|
||||
var gcd = MathUtil.gcdEuclidean(width, height);
|
||||
var initialWidth = width / gcd;
|
||||
var initialHeight = height / gcd;
|
||||
var material = Material.textured(texture);
|
||||
log.info("Creating new image on asset with UID: [{}]", uid);
|
||||
|
||||
return new DefaultImage(mesh, material, initialWidth, initialHeight, gcd);
|
||||
return new DefaultImage(meshManager.createQuad(QUAD_WIDTH, QUAD_HEIGHT, QUAD_ORIGIN_X, QUAD_ORIGIN_Y), texture, initialWidth, initialHeight, gcd);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,5 +7,5 @@ import com.bartlomiejpluta.base.engine.common.manager.ByteBufferAssetManager;
|
||||
import com.bartlomiejpluta.base.engine.world.image.asset.ImageAsset;
|
||||
import com.bartlomiejpluta.base.internal.gc.Cleanable;
|
||||
|
||||
public interface ImageManager extends Initializable, AssetManager<ImageAsset, Image>, ByteBufferAssetManager<ImageAsset>, Cleanable {
|
||||
public interface ImageManager extends AssetManager<ImageAsset, Image>, ByteBufferAssetManager<ImageAsset>, Cleanable {
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@ package com.bartlomiejpluta.base.engine.world.image.model;
|
||||
|
||||
import com.bartlomiejpluta.base.api.image.Image;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadMesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.world.object.Sprite;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import org.joml.Vector2f;
|
||||
|
||||
import static com.bartlomiejpluta.base.engine.core.gl.object.material.Material.textured;
|
||||
|
||||
@Getter
|
||||
public class DefaultImage extends Sprite implements Image {
|
||||
private final Vector2f imageScale = new Vector2f(1, 1);
|
||||
@@ -17,8 +20,8 @@ public class DefaultImage extends Sprite implements Image {
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
public DefaultImage(@NonNull Mesh mesh, @NonNull Material texture, int primaryWidth, int primaryHeight, int factor) {
|
||||
super(mesh, texture);
|
||||
public DefaultImage(QuadMesh mesh, @NonNull Texture texture, int primaryWidth, int primaryHeight, int factor) {
|
||||
super(mesh, textured(texture));
|
||||
this.primaryWidth = primaryWidth;
|
||||
this.primaryHeight = primaryHeight;
|
||||
this.factor = factor;
|
||||
@@ -31,12 +34,12 @@ public class DefaultImage extends Sprite implements Image {
|
||||
|
||||
@Override
|
||||
public void setOpacity(float opacity) {
|
||||
material.setAlpha(opacity);
|
||||
getMaterial().setAlpha(opacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getOpacity() {
|
||||
return material.getColor().w();
|
||||
return getMaterial().getColor().w();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,7 +9,6 @@ import com.bartlomiejpluta.base.engine.world.location.LocationableModel;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector3fc;
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.bartlomiejpluta.base.engine.world.map.layer.autotile;
|
||||
|
||||
import com.bartlomiejpluta.base.api.map.model.GameMap;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
/*
|
||||
* 0 1 2 3
|
||||
*
|
||||
* +-------------------+---------+---------+
|
||||
* | . | . |
|
||||
* 0 | TL0 . TR2 | TL1 . TR0 |
|
||||
* | . | . |
|
||||
* + . . . . + . . . . + . . . . + . . . . +
|
||||
* | . | . |
|
||||
* 1 | BL1 . BR3 | BL3 . BR2 |
|
||||
* | . | . |
|
||||
* +---------+---------+---------+---------+
|
||||
* | . | . |
|
||||
* 2 | TL2 . TR3 | TL3 . TR1 |
|
||||
* | . | . |
|
||||
* + . . . . + . . . . + . . . . + . . . . +
|
||||
* | . | . |
|
||||
* 3 | BL0 . BR1 | BL2 . BR0 |
|
||||
* | . | . |
|
||||
* +---------+---------+---------+---------+
|
||||
*
|
||||
* Legend:
|
||||
* TL - top left
|
||||
* TR - top right
|
||||
* BL - bottom left
|
||||
* BR - bottom right
|
||||
* 0 - Not connected
|
||||
* 1 - Left tile connected
|
||||
* 2 - Right tile connected
|
||||
* 3 - Left and right tiles connected
|
||||
* 4 - Left, right and center tiles connected
|
||||
*/
|
||||
public class AutoTileLayer2x2 extends BaseAutoTileLayer {
|
||||
private static final int ROWS = 2;
|
||||
private static final int COLUMNS = 2;
|
||||
private static final int SUB_ROWS = ROWS * 2;
|
||||
private static final int SUB_COLUMNS = COLUMNS * 2;
|
||||
|
||||
// 0 1 2 3
|
||||
private static final int[][] TOP_LEFT = {{0, 0}, {2, 0}, {0, 2}, {2, 2}};
|
||||
private static final int[][] TOP_RIGHT = {{3, 0}, {3, 2}, {1, 0}, {1, 2}};
|
||||
private static final int[][] BOTTOM_LEFT = {{0, 3}, {0, 1}, {2, 3}, {2, 1}};
|
||||
private static final int[][] BOTTOM_RIGHT = {{3, 3}, {1, 3}, {3, 1}, {1, 3}};
|
||||
|
||||
private final int tileSetColumns;
|
||||
|
||||
|
||||
public AutoTileLayer2x2(MeshManager meshManager, @NonNull GameMap map, @NonNull AutoTileSet tileSet, int rows, int columns, boolean connect, boolean animated, double animationDuration) {
|
||||
super(meshManager, map, tileSet, rows, columns, connect, animated, animationDuration);
|
||||
|
||||
this.tileSetColumns = tileSet.getSetsColumns();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void recalculateTile(int row, int column) {
|
||||
if (layer[row][column] == EMPTY_TILE) {
|
||||
return;
|
||||
}
|
||||
|
||||
var topLeft = 0;
|
||||
var topRight = 0;
|
||||
var bottomLeft = 0;
|
||||
var bottomRight = 0;
|
||||
|
||||
var tile = layer[row][column];
|
||||
|
||||
// Top
|
||||
if (row > 0 && (layer[row - 1][column] != EMPTY_TILE && (connect || layer[row - 1][column] == tile))) {
|
||||
topLeft += 2;
|
||||
topRight += 1;
|
||||
}
|
||||
|
||||
// Bottom
|
||||
if (row < map.getRows() - 1 && (layer[row + 1][column] != EMPTY_TILE && (connect || layer[row + 1][column] == tile))) {
|
||||
bottomLeft += 1;
|
||||
bottomRight += 2;
|
||||
}
|
||||
|
||||
// Left
|
||||
if (column > 0 && (layer[row][column - 1] != EMPTY_TILE && (connect || layer[row][column - 1] == tile))) {
|
||||
topLeft += 1;
|
||||
bottomLeft += 2;
|
||||
}
|
||||
|
||||
// Right
|
||||
if (column < map.getColumns() - 1 && (layer[row][column + 1] != EMPTY_TILE && (connect || layer[row][column + 1] == tile))) {
|
||||
topRight += 2;
|
||||
bottomRight += 1;
|
||||
}
|
||||
|
||||
setSubTiles(
|
||||
tile % tileSetColumns * SUB_COLUMNS,
|
||||
tile / tileSetColumns * SUB_ROWS,
|
||||
row,
|
||||
column,
|
||||
TOP_LEFT[topLeft],
|
||||
TOP_RIGHT[topRight],
|
||||
BOTTOM_LEFT[bottomLeft],
|
||||
BOTTOM_RIGHT[bottomRight]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.bartlomiejpluta.base.engine.world.map.layer.autotile;
|
||||
|
||||
import com.bartlomiejpluta.base.api.map.layer.autotile.AutoTileLayer;
|
||||
import com.bartlomiejpluta.base.api.map.model.GameMap;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
/*
|
||||
* 0 1 2 3
|
||||
*
|
||||
* +-------------------+---------+---------+
|
||||
* | | . |
|
||||
* 0 | | TL3 . TR3 |
|
||||
* | | . |
|
||||
* | ISLAND + . . . . + . . . . +
|
||||
* | | . |
|
||||
* 1 | | BL3 . BR3 |
|
||||
* | | . |
|
||||
* +-------------------+---------+---------+
|
||||
* | . | . |
|
||||
* 2 | TL0 . TR2 | TL1 . TR0 |
|
||||
* | . | . |
|
||||
* + . . . . + . . . . + . . . . + . . . . +
|
||||
* | . | . |
|
||||
* 3 | BL1 . BR4 | BL4 . BR2 |
|
||||
* | . | . |
|
||||
* +---------+---------+---------+---------+
|
||||
* | . | . |
|
||||
* 4 | TL2 . TR4 | TL4 . TR1 |
|
||||
* | . | . |
|
||||
* + . . . . + . . . . + . . . . + . . . . +
|
||||
* | . | . |
|
||||
* 5 | BL0 . BR1 | BL2 . BR0 |
|
||||
* | . | . |
|
||||
* +---------+---------+---------+---------+
|
||||
*
|
||||
* Legend:
|
||||
* TL - top left
|
||||
* TR - top right
|
||||
* BL - bottom left
|
||||
* BR - bottom right
|
||||
* 0 - Not connected
|
||||
* 1 - Left tile connected
|
||||
* 2 - Right tile connected
|
||||
* 3 - Left and right tiles connected
|
||||
* 4 - Left, right and center tiles connected
|
||||
*/
|
||||
public class AutoTileLayer3x2 extends BaseAutoTileLayer implements AutoTileLayer {
|
||||
private static final int ROWS = 3;
|
||||
private static final int COLUMNS = 2;
|
||||
private static final int SUB_ROWS = ROWS * 2;
|
||||
private static final int SUB_COLUMNS = COLUMNS * 2;
|
||||
|
||||
// 0 1 2 3 4
|
||||
private static final int[][] TOP_LEFT = {{0, 2}, {2, 2}, {0, 4}, {2, 0}, {2, 4}};
|
||||
private static final int[][] TOP_RIGHT = {{3, 2}, {3, 4}, {1, 2}, {3, 0}, {1, 4}};
|
||||
private static final int[][] BOTTOM_LEFT = {{0, 5}, {0, 3}, {2, 5}, {2, 1}, {2, 3}};
|
||||
private static final int[][] BOTTOM_RIGHT = {{3, 5}, {1, 5}, {3, 3}, {3, 1}, {1, 3}};
|
||||
|
||||
private final int tileSetColumns;
|
||||
|
||||
public AutoTileLayer3x2(MeshManager meshManager, @NonNull GameMap map, @NonNull AutoTileSet tileSet, int rows, int columns, boolean connect, boolean animated, double animationDuration) {
|
||||
super(meshManager, map, tileSet, rows, columns, connect, animated, animationDuration);
|
||||
|
||||
this.tileSetColumns = tileSet.getSetsColumns();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void recalculateTile(int row, int column) {
|
||||
if (layer[row][column] == EMPTY_TILE) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte topLeft = 0;
|
||||
byte topRight = 0;
|
||||
byte bottomLeft = 0;
|
||||
byte bottomRight = 0;
|
||||
|
||||
var tile = layer[row][column];
|
||||
|
||||
// Top
|
||||
if (row > 0 && (layer[row - 1][column] != EMPTY_TILE && (connect || layer[row - 1][column] == tile))) {
|
||||
topLeft += 2;
|
||||
topRight += 1;
|
||||
}
|
||||
|
||||
// Bottom
|
||||
if (row < map.getRows() - 1 && (layer[row + 1][column] != EMPTY_TILE && (connect || layer[row + 1][column] == tile))) {
|
||||
bottomLeft += 1;
|
||||
bottomRight += 2;
|
||||
}
|
||||
|
||||
// Left
|
||||
if (column > 0 && (layer[row][column - 1] != EMPTY_TILE && (connect || layer[row][column - 1] == tile))) {
|
||||
topLeft += 1;
|
||||
bottomLeft += 2;
|
||||
}
|
||||
|
||||
// Right
|
||||
if (column < map.getColumns() - 1 && (layer[row][column + 1] != EMPTY_TILE && (connect || layer[row][column + 1] == tile))) {
|
||||
topRight += 2;
|
||||
bottomRight += 1;
|
||||
}
|
||||
|
||||
// Top left
|
||||
if (row > 0 && column > 0 && (layer[row - 1][column - 1] != EMPTY_TILE && (connect || layer[row - 1][column - 1] == tile)) && topLeft == 3) {
|
||||
topLeft = 4;
|
||||
}
|
||||
|
||||
// Top right
|
||||
if (row > 0 && column < map.getColumns() - 1 && (layer[row - 1][column + 1] != EMPTY_TILE && (connect || layer[row - 1][column + 1] == tile)) && topRight == 3) {
|
||||
topRight = 4;
|
||||
}
|
||||
|
||||
// Bottom left
|
||||
if (row < map.getRows() - 1 && column > 0 && (layer[row + 1][column - 1] != EMPTY_TILE && (connect || layer[row + 1][column - 1] == tile)) && bottomLeft == 3) {
|
||||
bottomLeft = 4;
|
||||
}
|
||||
|
||||
// Bottom right
|
||||
if (row < map.getRows() - 1 && column < map.getColumns() - 1 && (layer[row + 1][column + 1] != EMPTY_TILE && (connect || layer[row + 1][column + 1] == tile)) && bottomRight == 3) {
|
||||
bottomRight = 4;
|
||||
}
|
||||
|
||||
setSubTiles(
|
||||
tile % tileSetColumns * SUB_COLUMNS,
|
||||
tile / tileSetColumns * SUB_ROWS,
|
||||
row,
|
||||
column,
|
||||
TOP_LEFT[topLeft],
|
||||
TOP_RIGHT[topRight],
|
||||
BOTTOM_LEFT[bottomLeft],
|
||||
BOTTOM_RIGHT[bottomRight]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.bartlomiejpluta.base.engine.world.map.layer.autotile;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.map.model.GameMap;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.base.BaseLayer;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.util.ChunkManager;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.NonNull;
|
||||
import org.joml.Vector2f;
|
||||
|
||||
import static java.util.Arrays.fill;
|
||||
import static java.util.Arrays.stream;
|
||||
|
||||
// Algorithm source: https://love2d.org/forums/viewtopic.php?t=7826
|
||||
public abstract class BaseAutoTileLayer extends BaseLayer {
|
||||
protected static final int EMPTY_TILE = -1;
|
||||
|
||||
protected final int[][] layer;
|
||||
protected final ChunkManager chunkManager;
|
||||
protected final boolean connect;
|
||||
protected final boolean animated;
|
||||
protected final double animationDuration;
|
||||
private final int setsCount;
|
||||
|
||||
private float accumulator = 0f;
|
||||
private boolean dirty = true;
|
||||
|
||||
public BaseAutoTileLayer(MeshManager meshManager, @NonNull GameMap map, @NonNull AutoTileSet tileSet, int rows, int columns, boolean connect, boolean animated, double animationDuration) {
|
||||
super(map);
|
||||
this.layer = new int[rows][columns];
|
||||
this.connect = connect;
|
||||
this.animated = animated;
|
||||
this.animationDuration = animationDuration;
|
||||
this.setsCount = tileSet.getSetsCount();
|
||||
|
||||
this.chunkManager = new ChunkManager(
|
||||
meshManager,
|
||||
tileSet.getTexture(),
|
||||
rows * 4,
|
||||
columns * 4,
|
||||
new Vector2f(tileSet.getTexture().getFrameSize()).div(2)
|
||||
);
|
||||
|
||||
stream(layer).forEach(tiles -> fill(tiles, EMPTY_TILE));
|
||||
}
|
||||
|
||||
public void setTile(int row, int column, int setId) {
|
||||
layer[row][column] = setId;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public void clearTile(int row, int column) {
|
||||
layer[row][column] = EMPTY_TILE;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public void recalculate() {
|
||||
for (int row = 0; row < map.getRows(); ++row) {
|
||||
for (int column = 0; column < map.getColumns(); ++column) {
|
||||
recalculateTile(row, column);
|
||||
}
|
||||
}
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
protected abstract void recalculateTile(int row, int column);
|
||||
|
||||
protected void setSubTiles(int tileSetColumnOffset, int tileSetRowOffset, int row, int column, int[] topLeft, int[] topRight, int[] bottomLeft, int[] bottomRight) {
|
||||
var y = row * 4;
|
||||
var x = column * 4;
|
||||
|
||||
chunkManager.upsertTile(y, x, topLeft[0] + tileSetColumnOffset, topLeft[1] + tileSetRowOffset);
|
||||
chunkManager.upsertTile(y, x + 2, topRight[0] + tileSetColumnOffset, topRight[1] + tileSetRowOffset);
|
||||
chunkManager.upsertTile(y + 2, x, bottomLeft[0] + tileSetColumnOffset, bottomLeft[1] + tileSetRowOffset);
|
||||
chunkManager.upsertTile(y + 2, x + 2, bottomRight[0] + tileSetColumnOffset, bottomRight[1] + tileSetRowOffset);
|
||||
}
|
||||
|
||||
private void nextAnimationFrame() {
|
||||
for (int row = 0; row < map.getRows(); ++row) {
|
||||
for (int column = 0; column < map.getColumns(); ++column) {
|
||||
var tile = layer[row][column];
|
||||
if (tile != EMPTY_TILE) {
|
||||
layer[row][column] = (tile + 1) % setsCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float dt) {
|
||||
super.update(dt);
|
||||
|
||||
if (animated) {
|
||||
accumulator += dt;
|
||||
|
||||
if (accumulator > animationDuration) {
|
||||
nextAnimationFrame();
|
||||
accumulator = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
if (dirty) {
|
||||
recalculate();
|
||||
}
|
||||
|
||||
chunkManager.render(screen, camera, shaderManager);
|
||||
|
||||
super.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
package com.bartlomiejpluta.base.engine.world.map.layer.autotile;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.map.layer.autotile.AutoTileLayer;
|
||||
import com.bartlomiejpluta.base.api.map.model.GameMap;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTile;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.base.BaseLayer;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
// Algorithm source: https://love2d.org/forums/viewtopic.php?t=7826
|
||||
public class DefaultAutoTileLayer extends BaseLayer implements AutoTileLayer {
|
||||
private final AutoTileSet autoTileSet;
|
||||
private final AutoTile[][] layer;
|
||||
private final boolean animated;
|
||||
private final double animationDuration;
|
||||
private final boolean connect;
|
||||
private double accumulator;
|
||||
|
||||
public DefaultAutoTileLayer(@NonNull GameMap map, @NonNull AutoTileSet autoTileSet, int rows, int columns, boolean animated, double animationDuration, boolean connect) {
|
||||
super(map);
|
||||
this.autoTileSet = autoTileSet;
|
||||
this.animated = animated;
|
||||
this.animationDuration = animationDuration;
|
||||
this.connect = connect;
|
||||
layer = new AutoTile[rows][columns];
|
||||
Arrays.stream(layer).forEach(tiles -> Arrays.fill(tiles, null));
|
||||
}
|
||||
|
||||
public void setTile(int row, int column, int setId) {
|
||||
var tile = autoTileSet.createTile(setId);
|
||||
tile.setCoordinates(column, row);
|
||||
layer[row][column] = tile;
|
||||
}
|
||||
|
||||
public void clearTile(int row, int column) {
|
||||
layer[row][column] = null;
|
||||
}
|
||||
|
||||
public void recalculate(Integer setId) {
|
||||
for (int row = 0; row < map.getRows(); ++row) {
|
||||
for (int column = 0; column < map.getColumns(); ++column) {
|
||||
recalculateTile(setId, row, column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void recalculateTile(Integer setId, int row, int column) {
|
||||
switch(autoTileSet.getLayout()) {
|
||||
case LAYOUT_2X2 -> recalculateTile2x2(setId, row, column);
|
||||
case LAYOUT_2X3 -> recalculateTile2x3(setId, row, column);
|
||||
}
|
||||
}
|
||||
|
||||
private void recalculateTile2x2(Integer setId, int row, int column) {
|
||||
if (layer[row][column] == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var topLeft = 0;
|
||||
var topRight = 0;
|
||||
var bottomLeft = 0;
|
||||
var bottomRight = 0;
|
||||
|
||||
var tile = layer[row][column];
|
||||
var centerSetId = tile.getSetId();
|
||||
|
||||
if (animated) {
|
||||
tile.shiftTileSet();
|
||||
}
|
||||
|
||||
// Top
|
||||
if (row > 0 && (layer[row - 1][column] != null && (connect || layer[row - 1][column].getSetId() == centerSetId))) {
|
||||
topLeft += 2;
|
||||
topRight += 1;
|
||||
}
|
||||
|
||||
// Bottom
|
||||
if (row < map.getRows() - 1 && (layer[row + 1][column] != null && (connect || layer[row + 1][column].getSetId() == centerSetId))) {
|
||||
bottomLeft += 1;
|
||||
bottomRight += 2;
|
||||
}
|
||||
|
||||
// Left
|
||||
if (column > 0 && (layer[row][column - 1] != null && (connect || layer[row][column - 1].getSetId() == centerSetId))) {
|
||||
topLeft += 1;
|
||||
bottomLeft += 2;
|
||||
}
|
||||
|
||||
// Right
|
||||
if (column < map.getColumns() - 1 && (layer[row][column + 1] != null && (connect || layer[row][column + 1].getSetId() == centerSetId))) {
|
||||
topRight += 2;
|
||||
bottomRight += 1;
|
||||
}
|
||||
|
||||
tile.regularTile(setId, topLeft, topRight, bottomLeft, bottomRight);
|
||||
}
|
||||
|
||||
private void recalculateTile2x3(Integer setId, int row, int column) {
|
||||
if (layer[row][column] == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var topLeft = 0;
|
||||
var topRight = 0;
|
||||
var bottomLeft = 0;
|
||||
var bottomRight = 0;
|
||||
|
||||
var tile = layer[row][column];
|
||||
var centerSetId = tile.getSetId();
|
||||
|
||||
if (animated) {
|
||||
tile.shiftTileSet();
|
||||
}
|
||||
|
||||
// Top
|
||||
if (row > 0 && (layer[row - 1][column] != null && (connect || layer[row - 1][column].getSetId() == centerSetId))) {
|
||||
topLeft += 2;
|
||||
topRight += 1;
|
||||
}
|
||||
|
||||
// Bottom
|
||||
if (row < map.getRows() - 1 && (layer[row + 1][column] != null && (connect || layer[row + 1][column].getSetId() == centerSetId))) {
|
||||
bottomLeft += 1;
|
||||
bottomRight += 2;
|
||||
}
|
||||
|
||||
// Left
|
||||
if (column > 0 && (layer[row][column - 1] != null && (connect || layer[row][column - 1].getSetId() == centerSetId))) {
|
||||
topLeft += 1;
|
||||
bottomLeft += 2;
|
||||
}
|
||||
|
||||
// Right
|
||||
if (column < map.getColumns() - 1 && (layer[row][column + 1] != null && (connect || layer[row][column + 1].getSetId() == centerSetId))) {
|
||||
topRight += 2;
|
||||
bottomRight += 1;
|
||||
}
|
||||
|
||||
// Top left
|
||||
if (row > 0 && column > 0 && (layer[row - 1][column - 1] != null && (connect || layer[row - 1][column - 1].getSetId() == centerSetId)) && topLeft == 3) {
|
||||
topLeft = 4;
|
||||
}
|
||||
|
||||
// Top right
|
||||
if (row > 0 && column < map.getColumns() - 1 && (layer[row - 1][column + 1] != null && (connect || layer[row - 1][column + 1].getSetId() == centerSetId)) && topRight == 3) {
|
||||
topRight = 4;
|
||||
}
|
||||
|
||||
// Bottom left
|
||||
if (row < map.getRows() - 1 && column > 0 && (layer[row + 1][column - 1] != null && (connect || layer[row + 1][column - 1].getSetId() == centerSetId)) && bottomLeft == 3) {
|
||||
bottomLeft = 4;
|
||||
}
|
||||
|
||||
// Bottom right
|
||||
if (row < map.getRows() - 1 && column < map.getColumns() - 1 && (layer[row + 1][column + 1] != null && (connect || layer[row + 1][column + 1].getSetId() == centerSetId)) && bottomRight == 3) {
|
||||
bottomRight = 4;
|
||||
}
|
||||
|
||||
if (topLeft == 0 && topRight == 0 && bottomLeft == 0 && bottomRight == 0) {
|
||||
tile.islandTile(setId);
|
||||
return;
|
||||
}
|
||||
|
||||
tile.regularTile(setId, topLeft, topRight, bottomLeft, bottomRight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float dt) {
|
||||
super.update(dt);
|
||||
|
||||
if (animated) {
|
||||
accumulator += dt;
|
||||
|
||||
if (accumulator > animationDuration) {
|
||||
accumulator = 0;
|
||||
recalculate(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
for (var row : layer) {
|
||||
for (var tile : row) {
|
||||
if (tile != null) {
|
||||
tile.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import com.bartlomiejpluta.base.api.map.layer.color.ColorLayer;
|
||||
import com.bartlomiejpluta.base.api.map.model.GameMap;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.util.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.base.BaseLayer;
|
||||
import com.bartlomiejpluta.base.engine.world.object.Sprite;
|
||||
import com.bartlomiejpluta.base.internal.object.Placeable;
|
||||
@@ -14,14 +14,21 @@ import lombok.NonNull;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.joml.Vector2fc;
|
||||
|
||||
import static com.bartlomiejpluta.base.engine.core.gl.object.material.Material.unicolor;
|
||||
|
||||
public class DefaultColorLayer extends BaseLayer implements ColorLayer {
|
||||
private final Color color;
|
||||
private static final float QUAD_WIDTH = 1;
|
||||
private static final float QUAD_HEIGHT = 1;
|
||||
private static final float QUAD_ORIGIN_X = 0;
|
||||
private static final float QUAD_ORIGIN_Y = 0;
|
||||
|
||||
private final ColorPlane colorPlane;
|
||||
private final Material material;
|
||||
|
||||
public DefaultColorLayer(@NonNull GameMap map, @NonNull MeshManager meshManager, float red, float green, float blue, float alpha) {
|
||||
super(map);
|
||||
this.color = new Color(meshManager, red, green, blue, alpha);
|
||||
this.material = color.getMaterial();
|
||||
this.colorPlane = new ColorPlane(meshManager, red, green, blue, alpha);
|
||||
this.material = colorPlane.getMaterial();
|
||||
|
||||
setScale(map.getWidth(), map.getHeight());
|
||||
}
|
||||
@@ -58,93 +65,93 @@ public class DefaultColorLayer extends BaseLayer implements ColorLayer {
|
||||
|
||||
@Override
|
||||
public Vector2fc getPosition() {
|
||||
return color.getPosition();
|
||||
return colorPlane.getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPosition(float x, float y) {
|
||||
color.setPosition(x, y);
|
||||
colorPlane.setPosition(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPosition(Vector2fc position) {
|
||||
color.setPosition(position);
|
||||
colorPlane.setPosition(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void movePosition(float x, float y) {
|
||||
color.movePosition(x, y);
|
||||
colorPlane.movePosition(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void movePosition(Vector2fc position) {
|
||||
color.movePosition(position);
|
||||
colorPlane.movePosition(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRotation() {
|
||||
return color.getRotation();
|
||||
return colorPlane.getRotation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRotation(float rotation) {
|
||||
color.setRotation(rotation);
|
||||
colorPlane.setRotation(rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveRotation(float rotation) {
|
||||
color.moveRotation(rotation);
|
||||
colorPlane.moveRotation(rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getScaleX() {
|
||||
return color.getScaleX();
|
||||
return colorPlane.getScaleX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleX(float scale) {
|
||||
color.setScaleX(scale);
|
||||
colorPlane.setScaleX(scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getScaleY() {
|
||||
return color.getScaleY();
|
||||
return colorPlane.getScaleY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleY(float scale) {
|
||||
color.setScaleY(scale);
|
||||
colorPlane.setScaleY(scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScale(float scale) {
|
||||
color.setScale(scale);
|
||||
colorPlane.setScale(scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScale(float scaleX, float scaleY) {
|
||||
color.setScale(scaleX, scaleY);
|
||||
colorPlane.setScale(scaleX, scaleY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float euclideanDistance(Placeable other) {
|
||||
return color.euclideanDistance(other);
|
||||
return colorPlane.euclideanDistance(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matrix4fc getModelMatrix() {
|
||||
return color.getModelMatrix();
|
||||
return colorPlane.getModelMatrix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
color.render(screen, camera, shaderManager);
|
||||
colorPlane.render(screen, camera, shaderManager);
|
||||
super.render(screen, camera, shaderManager);
|
||||
}
|
||||
|
||||
private static class Color extends Sprite {
|
||||
public Color(@NonNull MeshManager meshManager, float red, float green, float blue, float alpha) {
|
||||
super(meshManager.createQuad(1, 1, 0, 0), Material.colored(red, green, blue, alpha));
|
||||
private static class ColorPlane extends Sprite {
|
||||
public ColorPlane(@NonNull MeshManager meshManager, float red, float green, float blue, float alpha) {
|
||||
super(meshManager.createQuad(QUAD_WIDTH, QUAD_HEIGHT, QUAD_ORIGIN_X, QUAD_ORIGIN_Y), unicolor(red, green, blue, alpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,19 +106,17 @@ public class DefaultImageLayer extends BaseLayer implements ImageLayer {
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
if (image != null) {
|
||||
if (parallax) {
|
||||
var cameraPosition = camera.getPosition();
|
||||
image.setPosition(cameraPosition.x() + x, cameraPosition.y() + y);
|
||||
}
|
||||
|
||||
if (mode == ImageLayerMode.FIT_SCREEN) {
|
||||
image.setScale(screen.getWidth() / imageWidth, screen.getHeight() / imageHeight);
|
||||
}
|
||||
|
||||
image.render(screen, camera, shaderManager);
|
||||
if (parallax) {
|
||||
var cameraPosition = camera.getPosition();
|
||||
image.setPosition(cameraPosition.x() + x, cameraPosition.y() + y);
|
||||
}
|
||||
|
||||
if (mode == ImageLayerMode.FIT_SCREEN) {
|
||||
image.setScale(screen.getWidth() / imageWidth, screen.getHeight() / imageHeight);
|
||||
}
|
||||
|
||||
image.render(screen, camera, shaderManager);
|
||||
|
||||
super.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,55 +4,35 @@ import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.map.layer.tile.TileLayer;
|
||||
import com.bartlomiejpluta.base.api.map.model.GameMap;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.base.BaseLayer;
|
||||
import com.bartlomiejpluta.base.engine.world.tileset.model.Tile;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.util.ChunkManager;
|
||||
import com.bartlomiejpluta.base.engine.world.tileset.model.TileSet;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class DefaultTileLayer extends BaseLayer implements TileLayer {
|
||||
private final TileSet tileSet;
|
||||
private final Tile[][] layer;
|
||||
private final ChunkManager chunkManager;
|
||||
|
||||
public DefaultTileLayer(@NonNull GameMap map, @NonNull TileSet tileSet, int rows, int columns) {
|
||||
public DefaultTileLayer(MeshManager meshManager, @NonNull GameMap map, @NonNull TileSet tileSet, int rows, int columns) {
|
||||
super(map);
|
||||
this.tileSet = tileSet;
|
||||
layer = new Tile[rows][columns];
|
||||
Arrays.stream(layer).forEach(tiles -> Arrays.fill(tiles, null));
|
||||
|
||||
this.chunkManager = new ChunkManager(meshManager, tileSet.texture(), rows, columns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTile(int row, int column, int tileId) {
|
||||
var tile = tileSet.tileById(tileId);
|
||||
tile.setLocation(row, column);
|
||||
layer[row][column] = tile;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTile(int row, int column, int tileSetRow, int tileSetColumn) {
|
||||
var tile = tileSet.tileAt(tileSetRow, tileSetColumn);
|
||||
tile.setLocation(row, column);
|
||||
layer[row][column] = tile;
|
||||
chunkManager.setTile(row, column, tileId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTile(int row, int column) {
|
||||
layer[row][column] = null;
|
||||
chunkManager.clearTile(row, column);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
for (var row : layer) {
|
||||
for (var tile : row) {
|
||||
if (tile != null) {
|
||||
tile.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chunkManager.render(screen, camera, shaderManager);
|
||||
super.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.bartlomiejpluta.base.engine.world.map.layer.util;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.internal.gc.Disposable;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.NonNull;
|
||||
import org.joml.Vector2fc;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChunkManager implements Renderable, Disposable {
|
||||
private static final int DEFAULT_CHUNK_SIZE = 10;
|
||||
private final MeshManager meshManager;
|
||||
|
||||
private final Map<Long, TileChunk> chunks = new HashMap<>();
|
||||
private final Map<Long, Map<Integer, Integer>> chunkTileIds = new HashMap<>();
|
||||
|
||||
private final Texture tileSet;
|
||||
private final int chunkSize;
|
||||
private final float tileWidth, tileHeight;
|
||||
private final int mapRows, mapColumns;
|
||||
|
||||
public ChunkManager(MeshManager meshManager, Texture tileSet, int rows, int columns) {
|
||||
this(meshManager, tileSet, rows, columns, DEFAULT_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
public ChunkManager(MeshManager meshManager, Texture tileSet, int rows, int columns, Vector2fc tileSize) {
|
||||
this(meshManager, tileSet, rows, columns, tileSize, DEFAULT_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
public ChunkManager(MeshManager meshManager, Texture tileSet, int rows, int columns, int chunkSize) {
|
||||
this(meshManager, tileSet, rows, columns, tileSet.getFrameSize(), chunkSize);
|
||||
}
|
||||
|
||||
public ChunkManager(MeshManager meshManager, @NonNull Texture tileSet, int rows, int columns, @NonNull Vector2fc tileSize, int chunkSize) {
|
||||
this.meshManager = meshManager;
|
||||
this.tileSet = tileSet;
|
||||
this.mapRows = rows;
|
||||
this.mapColumns = columns;
|
||||
this.tileWidth = tileSize.x();
|
||||
this.tileHeight = tileSize.y();
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
private long getChunkKey(int row, int column) {
|
||||
var chunkX = column / chunkSize;
|
||||
var chunkY = row / chunkSize;
|
||||
return ((long) chunkX << 32) | (chunkY & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
private int getLocalTileKey(int row, int column) {
|
||||
var localRow = row % chunkSize;
|
||||
var localColumn = column % chunkSize;
|
||||
return localRow * chunkSize + localColumn;
|
||||
}
|
||||
|
||||
private TileChunk getOrCreateChunk(int row, int column) {
|
||||
var chunkKey = getChunkKey(row, column);
|
||||
|
||||
var chunk = chunks.get(chunkKey);
|
||||
if (chunk == null) {
|
||||
int chunkX = (column / chunkSize) * (int) tileWidth * chunkSize;
|
||||
int chunkY = (row / chunkSize) * (int) tileHeight * chunkSize;
|
||||
|
||||
chunk = new TileChunk(meshManager, tileSet, chunkSize);
|
||||
chunk.setPosition(chunkX, chunkY);
|
||||
chunks.put(chunkKey, chunk);
|
||||
chunkTileIds.put(chunkKey, new HashMap<>());
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public void setTile(int row, int column, int tileX, int tileY) {
|
||||
if (row < 0 || row >= mapRows || column < 0 || column >= mapColumns) {
|
||||
throw new IllegalArgumentException("Tile coordinates out of bounds");
|
||||
}
|
||||
|
||||
clearTile(row, column);
|
||||
|
||||
var chunk = getOrCreateChunk(row, column);
|
||||
var chunkKey = getChunkKey(row, column);
|
||||
var localTileKey = getLocalTileKey(row, column);
|
||||
|
||||
var quadId = chunk.addTile(column % chunkSize * (int) this.tileHeight, row % chunkSize * (int) this.tileWidth, tileX, tileY);
|
||||
|
||||
chunkTileIds.get(chunkKey).put(localTileKey, quadId);
|
||||
}
|
||||
|
||||
public void setTile(int row, int column, int tileId) {
|
||||
if (row < 0 || row >= mapRows || column < 0 || column >= mapColumns) {
|
||||
throw new IllegalArgumentException("Tile coordinates out of bounds");
|
||||
}
|
||||
|
||||
clearTile(row, column);
|
||||
|
||||
var chunk = getOrCreateChunk(row, column);
|
||||
var chunkKey = getChunkKey(row, column);
|
||||
var localTileKey = getLocalTileKey(row, column);
|
||||
|
||||
var quadId = chunk.addTile(column % chunkSize * (int) this.tileHeight, row % chunkSize * (int) this.tileWidth, tileId);
|
||||
|
||||
chunkTileIds.get(chunkKey).put(localTileKey, quadId);
|
||||
}
|
||||
|
||||
public void updateTile(int row, int column, int tileId) {
|
||||
var chunk = getOrCreateChunk(row, column);
|
||||
var chunkKey = getChunkKey(row, column);
|
||||
var localTileKey = getLocalTileKey(row, column);
|
||||
var quadId = chunkTileIds.get(chunkKey).get(localTileKey);
|
||||
chunk.updateTile(quadId, tileId);
|
||||
}
|
||||
|
||||
public void upsertTile(int row, int column, int tileX, int tileY) {
|
||||
if (row < 0 || row >= mapRows || column < 0 || column >= mapColumns) {
|
||||
throw new IllegalArgumentException("Tile coordinates out of bounds");
|
||||
}
|
||||
|
||||
var chunk = getOrCreateChunk(row, column);
|
||||
var chunkKey = getChunkKey(row, column);
|
||||
var localTileKey = getLocalTileKey(row, column);
|
||||
var tileMap = chunkTileIds.get(chunkKey);
|
||||
var quadId = tileMap.get(localTileKey);
|
||||
|
||||
if (quadId == null) {
|
||||
quadId = chunk.addTile(column % chunkSize * (int) this.tileHeight, row % chunkSize * (int) this.tileWidth, tileX, tileY);
|
||||
chunkTileIds.get(chunkKey).put(localTileKey, quadId);
|
||||
} else {
|
||||
chunk.updateTile(quadId, tileX, tileY);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearTile(int row, int column) {
|
||||
if (row < 0 || row >= mapRows || column < 0 || column >= mapColumns) {
|
||||
return;
|
||||
}
|
||||
|
||||
var chunkKey = getChunkKey(row, column);
|
||||
var localTileKey = getLocalTileKey(row, column);
|
||||
|
||||
var chunk = chunks.get(chunkKey);
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tileMap = chunkTileIds.get(chunkKey);
|
||||
var quadId = tileMap.remove(localTileKey);
|
||||
|
||||
if (quadId != null) {
|
||||
chunk.removeTile(quadId);
|
||||
|
||||
if (chunk.isEmpty()) {
|
||||
chunks.remove(chunkKey);
|
||||
chunkTileIds.remove(chunkKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
for (TileChunk chunk : chunks.values()) {
|
||||
if (camera.containsBox(chunk)) {
|
||||
chunk.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
chunks.values().forEach(TileChunk::dispose);
|
||||
chunks.clear();
|
||||
chunkTileIds.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.bartlomiejpluta.base.engine.world.map.layer.util;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadTemplate;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.engine.world.object.Model;
|
||||
import com.bartlomiejpluta.base.internal.gc.Disposable;
|
||||
import com.bartlomiejpluta.base.internal.object.Placeable;
|
||||
import com.bartlomiejpluta.base.internal.render.BoundingBox;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.NonNull;
|
||||
|
||||
public class TileChunk extends Model implements Placeable, Renderable, Disposable, BoundingBox {
|
||||
private final Texture tileSet;
|
||||
private final Mesh mesh;
|
||||
private final int chunkSize;
|
||||
private final QuadTemplate template;
|
||||
private final float originX;
|
||||
private final float originY;
|
||||
|
||||
public TileChunk(MeshManager meshManager, Texture tileSet, int chunkSize) {
|
||||
this(meshManager, tileSet, chunkSize, 0, 0);
|
||||
}
|
||||
|
||||
public TileChunk(@NonNull MeshManager meshManager, @NonNull Texture tileSet, int chunkSize, float originX, float originY) {
|
||||
this.tileSet = tileSet;
|
||||
this.chunkSize = chunkSize;
|
||||
this.mesh = meshManager.createMesh(chunkSize * chunkSize);
|
||||
this.template = new QuadTemplate(tileSet.getFrameSize().x(), tileSet.getFrameSize().y(), originX, originY);
|
||||
this.originX = originX;
|
||||
this.originY = originY;
|
||||
}
|
||||
|
||||
public int addTile(int x, int y, int tileX, int tileY) {
|
||||
return mesh.addQuad(template, x, y, tileSet.getTextureCoordinates(tileX, tileY));
|
||||
}
|
||||
|
||||
public int addTile(int x, int y, int tileId) {
|
||||
return mesh.addQuad(template, x, y, tileSet.getTextureCoordinates(tileId));
|
||||
}
|
||||
|
||||
public void updateTile(int quadId, int tileX, int tileY) {
|
||||
mesh.setQuadTextureCoordinates(quadId, tileSet.getTextureCoordinates(tileX, tileY));
|
||||
}
|
||||
|
||||
public void updateTile(int quadId, int tileId) {
|
||||
mesh.setQuadTextureCoordinates(quadId, tileSet.getTextureCoordinates(tileId));
|
||||
}
|
||||
|
||||
public void removeTile(int quadId) {
|
||||
mesh.removeQuad(quadId);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mesh.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
tileSet.render(screen, camera, shaderManager);
|
||||
|
||||
shaderManager.setUniform(UniformName.UNI_MODEL_MATRIX, getModelMatrix());
|
||||
shaderManager.setUniform(UniformName.UNI_PROJECTION_MATRIX, camera.getProjectionMatrix());
|
||||
shaderManager.setUniform(UniformName.UNI_VIEW_MODEL_MATRIX, camera.computeViewModelMatrix(getModelMatrix()));
|
||||
|
||||
mesh.render(screen, camera, shaderManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinX() {
|
||||
float scaledOriginX = originX * scaleX;
|
||||
return getPosition().x() - scaledOriginX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxX() {
|
||||
float scaledOriginX = originX * scaleX;
|
||||
float scaledChunkWidth = chunkSize * tileSet.getFrameSize().x() * scaleX;
|
||||
return getPosition().x() + scaledChunkWidth - scaledOriginX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinY() {
|
||||
float scaledOriginY = originY * scaleY;
|
||||
return getPosition().y() - scaledOriginY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxY() {
|
||||
float scaledOriginY = originY * scaleY;
|
||||
float scaledChunkHeight = chunkSize * tileSet.getFrameSize().y() * scaleY;
|
||||
return getPosition().y() + scaledChunkHeight - scaledOriginY;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
mesh.dispose();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package com.bartlomiejpluta.base.engine.world.map.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.api.context.Context;
|
||||
import com.bartlomiejpluta.base.api.map.handler.MapHandler;
|
||||
import com.bartlomiejpluta.base.api.map.layer.object.ObjectLayer;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration;
|
||||
import com.bartlomiejpluta.base.engine.util.reflection.ClassLoader;
|
||||
|
||||
@@ -13,9 +13,10 @@ import com.bartlomiejpluta.base.api.map.layer.tile.TileLayer;
|
||||
import com.bartlomiejpluta.base.api.map.model.GameMap;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.engine.util.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.autotile.DefaultAutoTileLayer;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.autotile.AutoTileLayer2x2;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.autotile.AutoTileLayer3x2;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.color.DefaultColorLayer;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.image.DefaultImageLayer;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.object.DefaultObjectLayer;
|
||||
@@ -35,6 +36,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class DefaultGameMap implements Renderable, Updatable, GameMap {
|
||||
private final MeshManager meshManager;
|
||||
|
||||
@Getter
|
||||
private final List<Layer> layers = new ArrayList<>();
|
||||
@@ -63,7 +65,8 @@ public class DefaultGameMap implements Renderable, Updatable, GameMap {
|
||||
@Getter
|
||||
private final Vector3f ambientColor = new Vector3f(1, 1, 1);
|
||||
|
||||
public DefaultGameMap(int tileWidth, int tileHeight, int rows, int columns, String handler) {
|
||||
public DefaultGameMap(MeshManager meshManager, int tileWidth, int tileHeight, int rows, int columns, String handler) {
|
||||
this.meshManager = meshManager;
|
||||
this.rows = rows;
|
||||
this.columns = columns;
|
||||
this.stepSize = new Vector2f(tileWidth, tileHeight);
|
||||
@@ -133,14 +136,21 @@ public class DefaultGameMap implements Renderable, Updatable, GameMap {
|
||||
}
|
||||
|
||||
public TileLayer createTileLayer(@NonNull TileSet tileSet) {
|
||||
var layer = new DefaultTileLayer(this, tileSet, rows, columns);
|
||||
var layer = new DefaultTileLayer(meshManager, this, tileSet, rows, columns);
|
||||
layers.add(layer);
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
public DefaultAutoTileLayer createAutoTileLayer(@NonNull AutoTileSet autoTileSet, boolean animated, double animationDuration, boolean connect) {
|
||||
var layer = new DefaultAutoTileLayer(this, autoTileSet, rows, columns, animated, animationDuration, connect);
|
||||
public AutoTileLayer3x2 createAutoTileLayer3x2(@NonNull AutoTileSet tileSet, boolean animated, double animationDuration, boolean connect) {
|
||||
var layer = new AutoTileLayer3x2(meshManager, this, tileSet, rows, columns, connect, animated, animationDuration);
|
||||
layers.add(layer);
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
public AutoTileLayer2x2 createAutoTileLayer2x2(@NonNull AutoTileSet tileSet, boolean animated, double animationDuration, boolean connect) {
|
||||
var layer = new AutoTileLayer2x2(meshManager, this, tileSet, rows, columns, connect, animated, animationDuration);
|
||||
layers.add(layer);
|
||||
|
||||
return layer;
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.bartlomiejpluta.base.engine.world.map.serial;
|
||||
import com.bartlomiejpluta.base.api.map.layer.image.ImageLayerMode;
|
||||
import com.bartlomiejpluta.base.api.map.layer.object.PassageAbility;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.engine.util.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.manager.AutoTileManager;
|
||||
import com.bartlomiejpluta.base.engine.world.image.manager.ImageManager;
|
||||
import com.bartlomiejpluta.base.engine.world.map.model.DefaultGameMap;
|
||||
@@ -28,7 +28,7 @@ public class ProtobufMapDeserializer extends MapDeserializer {
|
||||
@Override
|
||||
protected DefaultGameMap parse(InputStream input) throws Exception {
|
||||
var proto = GameMapProto.GameMap.parseFrom(input);
|
||||
var map = new DefaultGameMap(proto.getTileWidth(), proto.getTileHeight(), proto.getRows(), proto.getColumns(), proto.getHandler());
|
||||
var map = new DefaultGameMap(meshManager, proto.getTileWidth(), proto.getTileHeight(), proto.getRows(), proto.getColumns(), proto.getHandler());
|
||||
|
||||
proto.getLayersList().forEach(layer -> deserializeLayer(map, layer));
|
||||
|
||||
@@ -73,21 +73,19 @@ public class ProtobufMapDeserializer extends MapDeserializer {
|
||||
var animationDuration = proto.getAutoTileLayer().getAnimationDuration();
|
||||
var connect = proto.getAutoTileLayer().getConnect();
|
||||
|
||||
var layer = map.createAutoTileLayer(autoTileSet, animated, animationDuration, connect);
|
||||
var layer = switch(autoTileSet.getLayout()) {
|
||||
case LAYOUT_2X3 -> map.createAutoTileLayer3x2(autoTileSet, animated, animationDuration, connect);
|
||||
case LAYOUT_2X2 -> map.createAutoTileLayer2x2(autoTileSet, animated, animationDuration, connect);
|
||||
};
|
||||
|
||||
var columns = map.getColumns();
|
||||
var tiles = proto.getAutoTileLayer().getTilesList();
|
||||
|
||||
for (var i = 0; i < tiles.size(); ++i) {
|
||||
var tile = tiles.get(i);
|
||||
|
||||
if (tile == 0) {
|
||||
layer.clearTile(i / columns, i % columns);
|
||||
} else {
|
||||
layer.setTile(i / columns, i % columns, tile - 1);
|
||||
}
|
||||
layer.setTile(i / columns, i % columns, tile - 1);
|
||||
}
|
||||
|
||||
layer.recalculate(null);
|
||||
}
|
||||
|
||||
private void deserializeObjectLayer(DefaultGameMap map, GameMapProto.Layer proto) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.bartlomiejpluta.base.api.move.Movable;
|
||||
import com.bartlomiejpluta.base.api.move.Movement;
|
||||
import com.bartlomiejpluta.base.engine.core.engine.DefaultGameEngine;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadMesh;
|
||||
import com.bartlomiejpluta.base.engine.world.animation.model.AnimatedSprite;
|
||||
import com.bartlomiejpluta.base.internal.program.Updatable;
|
||||
import com.bartlomiejpluta.base.util.math.MathUtil;
|
||||
@@ -12,9 +12,6 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import org.joml.Vector2f;
|
||||
|
||||
import static java.lang.Math.abs;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class MovableSprite extends AnimatedSprite implements Movable, Updatable {
|
||||
private int moveTime = 0;
|
||||
@@ -24,7 +21,7 @@ public abstract class MovableSprite extends AnimatedSprite implements Movable, U
|
||||
@Getter
|
||||
private Movement movement;
|
||||
|
||||
public MovableSprite(Mesh mesh, Material material) {
|
||||
public MovableSprite(QuadMesh mesh, Material material) {
|
||||
super(mesh, material);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,43 +3,152 @@ package com.bartlomiejpluta.base.engine.world.object;
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.QuadMesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.engine.world.location.LocationableModel;
|
||||
import com.bartlomiejpluta.base.internal.render.BoundingBox;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import org.joml.Vector3fc;
|
||||
import org.joml.Vector4fc;
|
||||
|
||||
import static com.bartlomiejpluta.base.engine.core.gl.object.material.Material.textured;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class Sprite extends LocationableModel implements Renderable {
|
||||
private final float farthestVertexDistance;
|
||||
|
||||
protected final Mesh mesh;
|
||||
|
||||
public abstract class Sprite extends LocationableModel implements Renderable, BoundingBox {
|
||||
@NonNull
|
||||
@Setter
|
||||
@Getter
|
||||
protected Material material;
|
||||
private Material material;
|
||||
|
||||
public Sprite(Mesh mesh, Material material) {
|
||||
this.mesh = mesh;
|
||||
private final QuadMesh mesh;
|
||||
private final float width;
|
||||
private final float height;
|
||||
private final float originX;
|
||||
private final float originY;
|
||||
|
||||
@Getter
|
||||
private float[][] textureCoordinates;
|
||||
|
||||
public Sprite(@NonNull QuadMesh mesh, @NonNull Material material) {
|
||||
this.material = material;
|
||||
this.mesh = mesh;
|
||||
|
||||
this.farthestVertexDistance = this.mesh.getFarthestVertex().lengthSquared();
|
||||
this.width = mesh.getWidth();
|
||||
this.height = mesh.getHeight();
|
||||
this.originX = mesh.getOriginX();
|
||||
this.originY = mesh.getOriginY();
|
||||
|
||||
updateTextureCoordinates();
|
||||
}
|
||||
|
||||
private void updateTextureCoordinates() {
|
||||
this.textureCoordinates = material.getTextureCoordinatesForAllFrames();
|
||||
}
|
||||
|
||||
public void setMaterial(@NonNull Material material) {
|
||||
this.material = material;
|
||||
updateTextureCoordinates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces current material with textured one but preserves the color.
|
||||
* For example if some sprite is 0.4 opaque but needs to change its texture,
|
||||
* this function will apply new texture keeping the 0.4 opacity in opposite to {@link #setMaterial(Material)},
|
||||
* which completely replaces the material including its color.
|
||||
* @param texture - desired texture to be applied
|
||||
*/
|
||||
protected void applyTextureAndPreserveCurrentColor(@NonNull Texture texture) {
|
||||
var currentColor = material.getColor();
|
||||
setMaterial(textured(texture));
|
||||
material.setColor(currentColor);
|
||||
}
|
||||
|
||||
public void setFrame(int row, int column) {
|
||||
setFrame(row * material.getColumns() + column);
|
||||
}
|
||||
|
||||
public void setFrame(int id) {
|
||||
mesh.setTextureCoordinates(textureCoordinates[id]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinX() {
|
||||
float scaledOriginX = originX * scaleX;
|
||||
return getPosition().x() - scaledOriginX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxX() {
|
||||
float scaledOriginX = originX * scaleX;
|
||||
float scaledChunkWidth = width * scaleX;
|
||||
return getPosition().x() + scaledChunkWidth - scaledOriginX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinY() {
|
||||
float scaledOriginY = originY * scaleY;
|
||||
return getPosition().y() - scaledOriginY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxY() {
|
||||
float scaledOriginY = originY * scaleY;
|
||||
float scaledChunkHeight = height * scaleY;
|
||||
return getPosition().y() + scaledChunkHeight - scaledOriginY;
|
||||
}
|
||||
|
||||
public Vector4fc getColor() {
|
||||
return material.getColor();
|
||||
}
|
||||
|
||||
public void setColor(Vector4fc color) {
|
||||
material.setColor(color);
|
||||
}
|
||||
|
||||
public void setColor(Vector3fc color) {
|
||||
material.setColor(color);
|
||||
}
|
||||
|
||||
public void setColor(float red, float green, float blue, float alpha) {
|
||||
material.setColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
public void setColor(float red, float green, float blue) {
|
||||
material.setColor(red, green, blue);
|
||||
}
|
||||
|
||||
public void setRed(float red) {
|
||||
material.setRed(red);
|
||||
}
|
||||
|
||||
public void setGreen(float green) {
|
||||
material.setGreen(green);
|
||||
}
|
||||
|
||||
public void setBlue(float blue) {
|
||||
material.setBlue(blue);
|
||||
}
|
||||
|
||||
public void setAlpha(float alpha) {
|
||||
material.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
if (!camera.insideFrustum(position.x, position.y, farthestVertexDistance * (scaleX > scaleY ? scaleX : scaleY))) {
|
||||
if (!camera.containsBox(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
shaderManager.setUniform(UniformName.UNI_VIEW_MODEL_MATRIX, camera.computeViewModelMatrix(getModelMatrix()));
|
||||
shaderManager.setUniform(UniformName.UNI_MODEL_MATRIX, getModelMatrix());
|
||||
material.render(screen, camera, shaderManager);
|
||||
|
||||
shaderManager.setUniform(UniformName.UNI_MODEL_MATRIX, getModelMatrix());
|
||||
shaderManager.setUniform(UniformName.UNI_PROJECTION_MATRIX, camera.getProjectionMatrix());
|
||||
shaderManager.setUniform(UniformName.UNI_VIEW_MODEL_MATRIX, camera.computeViewModelMatrix(getModelMatrix()));
|
||||
|
||||
mesh.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package com.bartlomiejpluta.base.engine.world.tileset.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.TextureManager;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration;
|
||||
import com.bartlomiejpluta.base.engine.util.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.world.tileset.asset.TileSetAsset;
|
||||
import com.bartlomiejpluta.base.engine.world.tileset.model.TileSet;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -20,16 +18,9 @@ import java.util.Map;
|
||||
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
|
||||
public class DefaultTileSetManager implements TileSetManager {
|
||||
private final TextureManager textureManager;
|
||||
private final MeshManager meshManager;
|
||||
private final Map<String, TileSet> tileSets = new HashMap<>();
|
||||
private final Map<String, TileSetAsset> assets = new HashMap<>();
|
||||
private final ProjectConfiguration configuration;
|
||||
private Mesh mesh;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
this.mesh = meshManager.createQuad(1, 1, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAsset(TileSetAsset asset) {
|
||||
@@ -55,7 +46,7 @@ public class DefaultTileSetManager implements TileSetManager {
|
||||
|
||||
var source = configuration.projectFile("tilesets", asset.getSource());
|
||||
var texture = textureManager.loadTexture(source, asset.getRows(), asset.getColumns());
|
||||
tileset = new TileSet(texture, mesh);
|
||||
tileset = new TileSet(texture);
|
||||
log.info("Loading tile set from assets to cache under the key: [{}]", uid);
|
||||
tileSets.put(uid, tileset);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package com.bartlomiejpluta.base.engine.world.tileset.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.common.init.Initializable;
|
||||
import com.bartlomiejpluta.base.engine.common.manager.AssetManager;
|
||||
import com.bartlomiejpluta.base.engine.world.tileset.asset.TileSetAsset;
|
||||
import com.bartlomiejpluta.base.engine.world.tileset.model.TileSet;
|
||||
import com.bartlomiejpluta.base.internal.gc.Cleanable;
|
||||
|
||||
public interface TileSetManager extends Initializable, AssetManager<TileSetAsset, TileSet>, Cleanable {
|
||||
public interface TileSetManager extends AssetManager<TileSetAsset, TileSet>, Cleanable {
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
package com.bartlomiejpluta.base.engine.world.tileset.model;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.world.object.Sprite;
|
||||
import lombok.Getter;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector2fc;
|
||||
|
||||
@Getter
|
||||
public class Tile extends Sprite {
|
||||
private final int id;
|
||||
private final int tileSetRow;
|
||||
private final int tileSetColumn;
|
||||
private final Vector2f tileScale = new Vector2f(1, 1);
|
||||
private final Vector2fc tileSpriteSize;
|
||||
|
||||
public Tile setLocation(int row, int column) {
|
||||
var stepSize = material.getTexture().getSpriteSize();
|
||||
setPosition(column * stepSize.x(), row * stepSize.y());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Tile(Mesh mesh, Texture tileSet, int id) {
|
||||
super(mesh, Material.textured(tileSet));
|
||||
this.id = id;
|
||||
this.tileSetRow = id / tileSet.getColumns();
|
||||
this.tileSetColumn = id % tileSet.getColumns();
|
||||
material.setSpritePosition(tileSetColumn, tileSetRow);
|
||||
tileSpriteSize = tileSet.getSpriteSize();
|
||||
|
||||
super.setScale(tileSpriteSize.x() * tileScale.x, tileSpriteSize.y() * tileScale.y);
|
||||
}
|
||||
|
||||
public Tile(Mesh mesh, Texture tileSet, int row, int column) {
|
||||
super(mesh, Material.textured(tileSet));
|
||||
this.tileSetRow = row;
|
||||
this.tileSetColumn = column;
|
||||
this.id = row * tileSet.getColumns() + column;
|
||||
material.setSpritePosition(tileSetColumn, tileSetRow);
|
||||
tileSpriteSize = tileSet.getSpriteSize();
|
||||
|
||||
super.setScale(tileSpriteSize.x() * tileScale.x, tileSpriteSize.y() * tileScale.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleX(float scaleX) {
|
||||
this.tileScale.x = scaleX;
|
||||
super.setScaleX(tileSpriteSize.x() * scaleX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleY(float scaleY) {
|
||||
this.tileScale.y = scaleY;
|
||||
super.setScaleY(tileSpriteSize.x() * scaleY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScale(float scale) {
|
||||
this.tileScale.x = scale;
|
||||
this.tileScale.y = scale;
|
||||
super.setScale(tileSpriteSize.x() * scale, tileSpriteSize.y() * scale);
|
||||
}
|
||||
|
||||
public void setScale(float scaleX, float scaleY) {
|
||||
this.tileScale.x = scaleX;
|
||||
this.tileScale.y = scaleY;
|
||||
super.setScale(tileSpriteSize.x() * scaleX, tileSpriteSize.y() * scaleY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getScaleX() {
|
||||
return tileScale.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getScaleY() {
|
||||
return tileScale.y;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,7 @@
|
||||
package com.bartlomiejpluta.base.engine.world.tileset.model;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class TileSet {
|
||||
private final Texture tileSet;
|
||||
private final Mesh mesh;
|
||||
|
||||
public Tile tileById(int id) {
|
||||
return new Tile(mesh, tileSet, id);
|
||||
}
|
||||
|
||||
public Tile tileAt(int row, int column) {
|
||||
return new Tile(mesh, tileSet, row, column);
|
||||
}
|
||||
public record TileSet(@NonNull Texture texture) {
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = hasTexture == 1 ? objectColor * texture(sampler, fragmentTexCoord * spriteSize + spritePosition) : objectColor;
|
||||
vec4 color = hasTexture == 1 ? objectColor * texture(sampler, fragmentTexCoord) : objectColor;
|
||||
vec4 total = vec4(color.rgb * ambient, color.a);
|
||||
|
||||
for(int i=0; i<activeLights; ++i)
|
||||
|
||||
Reference in New Issue
Block a user