/*
* This file is part of the Illarion project.
*
* Copyright © 2015 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package illarion.client.graphics;
import illarion.common.types.Direction;
import illarion.common.types.DisplayCoordinate;
import org.illarion.engine.GameContainer;
import org.illarion.engine.graphic.Color;
import org.illarion.engine.graphic.Graphics;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.EnumMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* This class is able to trigger the rendering of the clothes of a avatar. The
* render action is invoked in the order that is defined for the direction the
* parent avatar is looking at.
*
* @author Martin Karing <nitram@illarion.org>
*/
final class AvatarClothRenderer {
/**
* The definition of the orders that are used to render the clothes a
* character wears. Each direction has a separated order that is stored in
* this list.
*/
@Nonnull
private static final EnumMap<Direction, int[]> RENDER_DIR;
static {
RENDER_DIR = new EnumMap<>(Direction.class);
int cnt = 0;
int[] groupArray = new int[AvatarClothManager.GROUP_COUNT];
RENDER_DIR.put(Direction.North, groupArray);
groupArray[cnt++] = AvatarClothManager.GROUP_FIRST_HAND;
groupArray[cnt++] = AvatarClothManager.GROUP_TROUSERS;
groupArray[cnt++] = AvatarClothManager.GROUP_SHOES;
groupArray[cnt++] = AvatarClothManager.GROUP_CHEST;
groupArray[cnt++] = AvatarClothManager.GROUP_COAT;
groupArray[cnt++] = AvatarClothManager.GROUP_HAIR;
groupArray[cnt++] = AvatarClothManager.GROUP_BEARD;
groupArray[cnt++] = AvatarClothManager.GROUP_HAT;
groupArray[cnt++] = AvatarClothManager.GROUP_SECOND_HAND;
cnt = 0;
groupArray = new int[AvatarClothManager.GROUP_COUNT];
RENDER_DIR.put(Direction.NorthEast, groupArray);
groupArray[cnt++] = AvatarClothManager.GROUP_FIRST_HAND;
groupArray[cnt++] = AvatarClothManager.GROUP_SECOND_HAND;
groupArray[cnt++] = AvatarClothManager.GROUP_TROUSERS;
groupArray[cnt++] = AvatarClothManager.GROUP_SHOES;
groupArray[cnt++] = AvatarClothManager.GROUP_CHEST;
groupArray[cnt++] = AvatarClothManager.GROUP_COAT;
groupArray[cnt++] = AvatarClothManager.GROUP_HAIR;
groupArray[cnt++] = AvatarClothManager.GROUP_BEARD;
groupArray[cnt++] = AvatarClothManager.GROUP_HAT;
cnt = 0;
groupArray = new int[AvatarClothManager.GROUP_COUNT];
RENDER_DIR.put(Direction.East, groupArray);
groupArray[cnt++] = AvatarClothManager.GROUP_FIRST_HAND;
groupArray[cnt++] = AvatarClothManager.GROUP_TROUSERS;
groupArray[cnt++] = AvatarClothManager.GROUP_SHOES;
groupArray[cnt++] = AvatarClothManager.GROUP_CHEST;
groupArray[cnt++] = AvatarClothManager.GROUP_COAT;
groupArray[cnt++] = AvatarClothManager.GROUP_HAIR;
groupArray[cnt++] = AvatarClothManager.GROUP_BEARD;
groupArray[cnt++] = AvatarClothManager.GROUP_HAT;
groupArray[cnt++] = AvatarClothManager.GROUP_SECOND_HAND;
cnt = 0;
groupArray = new int[AvatarClothManager.GROUP_COUNT];
RENDER_DIR.put(Direction.SouthEast, groupArray);
groupArray[cnt++] = AvatarClothManager.GROUP_FIRST_HAND;
groupArray[cnt++] = AvatarClothManager.GROUP_TROUSERS;
groupArray[cnt++] = AvatarClothManager.GROUP_SHOES;
groupArray[cnt++] = AvatarClothManager.GROUP_CHEST;
groupArray[cnt++] = AvatarClothManager.GROUP_COAT;
groupArray[cnt++] = AvatarClothManager.GROUP_HAIR;
groupArray[cnt++] = AvatarClothManager.GROUP_BEARD;
groupArray[cnt++] = AvatarClothManager.GROUP_HAT;
groupArray[cnt++] = AvatarClothManager.GROUP_SECOND_HAND;
cnt = 0;
groupArray = new int[AvatarClothManager.GROUP_COUNT];
RENDER_DIR.put(Direction.South, groupArray);
groupArray[cnt++] = AvatarClothManager.GROUP_FIRST_HAND;
groupArray[cnt++] = AvatarClothManager.GROUP_COAT;
groupArray[cnt++] = AvatarClothManager.GROUP_TROUSERS;
groupArray[cnt++] = AvatarClothManager.GROUP_SHOES;
groupArray[cnt++] = AvatarClothManager.GROUP_CHEST;
groupArray[cnt++] = AvatarClothManager.GROUP_HAIR;
groupArray[cnt++] = AvatarClothManager.GROUP_BEARD;
groupArray[cnt++] = AvatarClothManager.GROUP_HAT;
groupArray[cnt++] = AvatarClothManager.GROUP_SECOND_HAND;
cnt = 0;
groupArray = new int[AvatarClothManager.GROUP_COUNT];
RENDER_DIR.put(Direction.SouthWest, groupArray);
groupArray[cnt++] = AvatarClothManager.GROUP_COAT;
groupArray[cnt++] = AvatarClothManager.GROUP_TROUSERS;
groupArray[cnt++] = AvatarClothManager.GROUP_SHOES;
groupArray[cnt++] = AvatarClothManager.GROUP_CHEST;
groupArray[cnt++] = AvatarClothManager.GROUP_FIRST_HAND;
groupArray[cnt++] = AvatarClothManager.GROUP_SECOND_HAND;
groupArray[cnt++] = AvatarClothManager.GROUP_HAIR;
groupArray[cnt++] = AvatarClothManager.GROUP_BEARD;
groupArray[cnt++] = AvatarClothManager.GROUP_HAT;
cnt = 0;
groupArray = new int[AvatarClothManager.GROUP_COUNT];
RENDER_DIR.put(Direction.West, groupArray);
groupArray[cnt++] = AvatarClothManager.GROUP_FIRST_HAND;
groupArray[cnt++] = AvatarClothManager.GROUP_COAT;
groupArray[cnt++] = AvatarClothManager.GROUP_TROUSERS;
groupArray[cnt++] = AvatarClothManager.GROUP_SHOES;
groupArray[cnt++] = AvatarClothManager.GROUP_CHEST;
groupArray[cnt++] = AvatarClothManager.GROUP_HAIR;
groupArray[cnt++] = AvatarClothManager.GROUP_BEARD;
groupArray[cnt++] = AvatarClothManager.GROUP_HAT;
groupArray[cnt++] = AvatarClothManager.GROUP_SECOND_HAND;
cnt = 0;
groupArray = new int[AvatarClothManager.GROUP_COUNT];
RENDER_DIR.put(Direction.NorthWest, groupArray);
groupArray[cnt++] = AvatarClothManager.GROUP_FIRST_HAND;
groupArray[cnt++] = AvatarClothManager.GROUP_TROUSERS;
groupArray[cnt++] = AvatarClothManager.GROUP_SHOES;
groupArray[cnt++] = AvatarClothManager.GROUP_CHEST;
groupArray[cnt++] = AvatarClothManager.GROUP_COAT;
groupArray[cnt++] = AvatarClothManager.GROUP_HAIR;
groupArray[cnt++] = AvatarClothManager.GROUP_BEARD;
groupArray[cnt++] = AvatarClothManager.GROUP_HAT;
groupArray[cnt++] = AvatarClothManager.GROUP_SECOND_HAND;
}
/**
* The current x coordinate of the avatar on the screen.
*/
@Nullable
private DisplayCoordinate avatarPos;
/**
* The list of clothes the avatar currently wears. This clothes are rendered
* one by one when its requested.
*/
@Nonnull
private final AvatarCloth[] currentClothes;
/**
* The frame that is currently rendered.
*/
private int currentFrame;
/**
* The light that is currently set to the clothes.
*/
@Nullable
private Color currentLight;
/**
* The direction if the parent that defines the order that is used to render the parts of the clothes.
*/
@Nonnull
private final Direction direction;
/**
* The amount of frames the parent animation stores.
*/
private final int parentFrames;
/**
* The scaling value that applies to all cloth graphics.
*/
private float scale;
/**
* The alpha value applied to the clothes.
*/
private int clothAlpha;
/**
* This is the lock used to ensure the proper access on the cloth objects.
*/
@Nonnull
private final ReadWriteLock clothLock;
/**
* The copy constructor that is used to create a duplicate of this class in
* order to get separated instances for each avatar that is needed.
*
* @param org the instance of AvatarClothRenderer that shall be copied into
* a new instance
*/
AvatarClothRenderer(@Nonnull AvatarClothRenderer org) {
this(org.direction, org.parentFrames);
}
/**
* Create a cloth renderer for a avatar that looks into a defined direction.
*
* @param dir the direction this character is looking at.
* @param frames the amount of frames the parent avatar animation contains
*/
AvatarClothRenderer(@Nonnull Direction dir, int frames) {
clothLock = new ReentrantReadWriteLock();
scale = 1.f;
currentClothes = new AvatarCloth[AvatarClothManager.GROUP_COUNT];
parentFrames = frames;
direction = dir;
clothAlpha = -1;
}
/**
* Set the alpha value of all clothes. This is used to perform a proper
* fading out effect on all clothes.
*
* @param newAlpha the new alpha value
*/
public void setAlpha(int newAlpha) {
if (newAlpha == clothAlpha) {
return;
}
clothAlpha = newAlpha;
clothLock.readLock().lock();
try {
for (int i = 0; i < AvatarClothManager.GROUP_COUNT; ++i) {
if (currentClothes[i] != null) {
currentClothes[i].setAlpha(newAlpha);
currentClothes[i].setAlphaTarget(newAlpha);
}
}
} finally {
clothLock.readLock().unlock();
}
}
/**
* Set the frame that is currently rendered to all clothes.
*
* @param frame the index of the frame that shall be rendered
*/
public void setFrame(int frame) {
currentFrame = frame;
clothLock.readLock().lock();
try {
for (int i = 0; i < AvatarClothManager.GROUP_COUNT; ++i) {
AvatarCloth currentCloth = currentClothes[i];
if (currentCloth != null) {
int currentFrames = currentCloth.getTemplate().getFrames();
if (currentFrames == parentFrames) {
currentCloth.setFrame(frame);
} else if (currentFrames > 1) {
currentCloth.setFrame((int) (((float) currentFrames * frame) / parentFrames));
}
}
}
} finally {
clothLock.readLock().unlock();
}
}
/**
* Set the light that effects the clothes. This sets the instance of the
* light directly, so any change to the instance will be send to the clothes
* as well. How ever in case the used instance changes, its needed to report
* this to the clothes.
*
* @param light the light object that is send to all currently set clothes
*/
public void setLight(@Nonnull Color light) {
currentLight = light;
clothLock.readLock().lock();
try {
for (int i = 0; i < AvatarClothManager.GROUP_COUNT; ++i) {
if (currentClothes[i] != null) {
currentClothes[i].setLight(light);
}
}
} finally {
clothLock.readLock().unlock();
}
}
/**
* Set the scaling value for all clothes so everything is rendered at the
* proper size.
*
* @param newScale the new scaling value to ensure that everything is
* rendered at the proper size
*/
public void setScale(float newScale) {
scale = newScale;
clothLock.readLock().lock();
try {
for (int i = 0; i < AvatarClothManager.GROUP_COUNT; ++i) {
if (currentClothes[i] != null) {
currentClothes[i].setScale(newScale);
}
}
} finally {
clothLock.readLock().unlock();
}
}
/**
* Change the base color of one cloth.
*
* @param slot the slot that shall be changed
* @param color the new color that shall be used as base color
*/
void changeBaseColor(int slot, Color color) {
clothLock.readLock().lock();
try {
if (currentClothes[slot] != null) {
currentClothes[slot].changeBaseColor(color);
}
} finally {
clothLock.readLock().unlock();
}
}
/**
* Render all clothes in the correct order.
*/
void render(@Nonnull Graphics g) {
clothLock.readLock().lock();
try {
for (int i = 0; i < AvatarClothManager.GROUP_COUNT; ++i) {
int currentIndex = RENDER_DIR.get(direction)[i];
if (currentClothes[currentIndex] != null) {
currentClothes[currentIndex].render(g);
}
}
} finally {
clothLock.readLock().unlock();
}
}
/**
* Update all clothes
*/
void update(@Nonnull GameContainer c, int delta) {
clothLock.readLock().lock();
try {
for (int i = 0; i < AvatarClothManager.GROUP_COUNT; ++i) {
if (currentClothes[i] != null) {
currentClothes[i].update(c, delta);
}
}
} finally {
clothLock.readLock().unlock();
}
}
/**
* Set on part of the clothes with a new cloth to wear. This cloth will be
* rendered at the next run. The current cloth, if any is put back into its
* factory.
*
* @param group the group the item is a part of. So the location its shown
* at
* @param item the item that shall be shown itself or {@code null} to
* remove the item
*/
void setCloth(int group, @Nullable AvatarCloth item) {
clothLock.writeLock().lock();
try {
if (currentClothes[group] != null) {
if ((item != null) && (currentClothes[group].getTemplate().getId() == item.getTemplate().getId())) {
return;
}
}
currentClothes[group] = item;
if (item != null) {
if (currentLight != null) {
item.setLight(currentLight);
}
if (avatarPos != null) {
item.setScreenPos(avatarPos);
}
item.setFrame(currentFrame);
item.setScale(scale);
if (clothAlpha > -1) {
item.setAlpha(clothAlpha);
item.setAlphaTarget(clothAlpha);
}
}
} finally {
clothLock.writeLock().unlock();
}
}
/**
* Set the screen position of all clothes that are currently defined in this
* class. Its needed to call this function when ever the location changes or
* a cloth is added.
*
* @param coordinate the display coordinate of the parent avatar
*/
void setScreenPos(@Nonnull DisplayCoordinate coordinate) {
avatarPos = coordinate;
clothLock.readLock().lock();
try {
for (int i = 0; i < AvatarClothManager.GROUP_COUNT; ++i) {
if (currentClothes[i] != null) {
currentClothes[i].setScreenPos(coordinate);
}
}
} finally {
clothLock.readLock().unlock();
}
}
}