com.quadbits.gdxhelper.actors.CloudsActor.java Source code

Java tutorial

Introduction

Here is the source code for com.quadbits.gdxhelper.actors.CloudsActor.java

Source

/*
 * Copyright (c) 2015 Quadbits SLU
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.quadbits.gdxhelper.actors;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Pool;
import com.quadbits.gdxhelper.utils.DrawUtils;
import com.quadbits.gdxhelper.utils.Recyclable;
import com.quadbits.gdxhelper.utils.SpriteGrid;

import javax.inject.Inject;

/**
 *
 */
public class CloudsActor extends ControllableActor
        implements DrawUtils.BatchDrawableSprite, Recyclable<CloudsActor> {
    protected Pool<CloudsActor> cloudsActorPool;
    protected Array<SpriteGrid> spriteGrids;
    protected Array<Cloud> clouds;
    protected Array<Float> spritesMinScale;
    protected Array<Float> spritesMaxScale;
    protected float cloudsPerc; // [0, 1]
    protected float availableArea;
    protected float coveredArea;
    protected float fadeAnimDurationSeconds;
    protected float fadeAnimMaxDeltaSeconds;
    protected boolean fading;

    protected boolean tileableX;
    protected boolean tileableY;
    protected float minTiledX;
    protected float maxTiledX;
    protected float minTiledY;
    protected float maxTiledY;

    @Inject
    protected Pool<Cloud> cloudPool;

    @Inject
    protected Pool<SpriteGrid> spriteGridPool;

    public static final float DEFAULT_FADE_ANIM_DURATION_SECONDS = 1f;
    public static final float DEFAULT_FADE_ANIM_MAX_DELTA_SECONDS = 1.f / 30.f;

    @Inject
    public CloudsActor() {
        super();
        spriteGrids = new Array<SpriteGrid>();
        spritesMinScale = new Array<Float>();
        spritesMaxScale = new Array<Float>();
        clouds = new Array<Cloud>();
        init();
    }

    private void init() {
        cloudsPerc = 0;
        availableArea = 0;
        coveredArea = 0;
        fadeAnimDurationSeconds = DEFAULT_FADE_ANIM_DURATION_SECONDS;
        fadeAnimMaxDeltaSeconds = DEFAULT_FADE_ANIM_MAX_DELTA_SECONDS;
        fading = false;
    }

    @Override
    public void reset() {
        super.reset();

        for (SpriteGrid spriteGrid : spriteGrids) {
            spriteGrid.free();
        }
        spriteGrids.clear();
        spritesMinScale.clear();
        spritesMaxScale.clear();

        for (Cloud cloud : clouds) {
            cloud.free();
        }
        clouds.clear();
        init();
    }

    @Override
    public void free() {
        cloudsActorPool.free(this);
    }

    @Override
    public void setPool(Pool<CloudsActor> cloudsActorPool) {
        this.cloudsActorPool = cloudsActorPool;
    }

    public void addTexture(String textureName, float minScale, float maxScale) {
        SpriteGrid spriteGrid = spriteGridPool.obtain();
        spriteGrid.setTexture(textureName);
        spriteGrids.add(spriteGrid);
        spritesMinScale.add(minScale);
        spritesMaxScale.add(maxScale);
    }

    public void updateClouds() {
        recalculateAvailableArea();
        recalculateCoveredArea();

        // If already present clouds cover a greater area than available,
        // mark some clouds as removed
        for (Cloud cloud : clouds) {
            if (coveredArea <= availableArea) {
                break;
            }

            if (cloud.targetAlpha == 0) {
                continue;
            }

            cloud.targetAlpha = 0;
            coveredArea -= cloud.width * cloud.height;
        }

        // Add clouds until we fill up the available area
        while (coveredArea < availableArea) {
            Cloud cloud = createCloud(availableArea - coveredArea);
            if (cloud == null) {
                break;
            }

            clouds.add(cloud);
            coveredArea += cloud.width * cloud.height;
        }

        // Randomize positions of the clouds using a Halton sequence with bases 2 and 3,
        // discarding the first r numbers (r = random[1, 60])
        int base2 = 2;
        int base3 = 3;
        int index = MathUtils.random(1, 60) + 1;
        for (Cloud cloud : clouds) {
            if (cloud.targetAlpha == 1 && cloud.targetAlpha != cloud.alpha) {
                cloud.x = DrawUtils.haltonSequence(index, base2) * getWidth();
                cloud.x -= Math.max(0, cloud.x + cloud.width - getWidth());
                cloud.y = DrawUtils.haltonSequence(index, base3) * getHeight();
                index++;
            }
        }
    }

    protected void recalculateAvailableArea() {
        availableArea = getWidth() * getHeight() * cloudsPerc;
    }

    protected void recalculateCoveredArea() {
        coveredArea = 0;
        for (Cloud cloud : clouds) {
            if (cloud.targetAlpha == 1) {
                coveredArea += cloud.width * cloud.height;
            }
        }
    }

    protected Cloud createCloud(float remainingArea) {
        // Create the cloud object
        Cloud cloud = cloudPool.obtain();

        // Randomly choose the cloud sprite to use
        cloud.spriteGridIndex = MathUtils.random(spriteGrids.size - 1);
        SpriteGrid spriteGrid = spriteGrids.get(cloud.spriteGridIndex);

        // Find out the area occupied by the original sprite
        float originalArea = spriteGrid.getOriginalWidth() * spriteGrid.getOriginalHeight();

        // Calculate the maximum scale that we can use to create a cloud that would fit
        // in the remaining area
        float remainingAreaMaxScale = (float) Math.sqrt(remainingArea / originalArea);
        float minScale = spritesMinScale.get(cloud.spriteGridIndex);
        float maxScale = spritesMaxScale.get(cloud.spriteGridIndex);

        // If the scale is less than the minimum scale, return
        if (remainingAreaMaxScale < minScale) {
            cloud.free();
            return null;
        }

        // If the scale is less than the maximum scale, adjust max-scale
        if (remainingAreaMaxScale < maxScale) {
            maxScale = remainingAreaMaxScale;
        }

        // Set the cloud's properties
        cloud.scale = MathUtils.random(minScale, maxScale);
        cloud.width = spriteGrid.getOriginalWidth() * cloud.scale;
        cloud.height = spriteGrid.getOriginalHeight() * cloud.scale;
        //cloud.x = MathUtils.random(0, getWidth() - cloud.width);
        //cloud.y = MathUtils.random(0, getHeight() - cloud.height);
        cloud.flipX = MathUtils.randomBoolean();
        cloud.alpha = 0;
        cloud.targetAlpha = 1;

        return cloud;
    }

    protected void setSpriteGridPropertiesFromCloud(SpriteGrid spriteGrid, Cloud cloud) {
        spriteGrid.setPosition(getX() + cloud.x, getY() + cloud.y);
        spriteGrid.setSize(cloud.width, cloud.height);
        spriteGrid.setFlipX(cloud.flipX);
        spriteGrid.setFlipY(cloud.flipY);
        Color spriteGridColor = spriteGrid.getColor();
        Color actorColor = getColor();
        spriteGridColor.set(actorColor.r, actorColor.g, actorColor.b, cloud.alpha);
        spriteGrid.setColor(spriteGridColor);
    }

    public void setTextureFilter(Texture.TextureFilter minFilter, Texture.TextureFilter maxFilter) {
        for (SpriteGrid spriteGrid : spriteGrids) {
            spriteGrid.setTextureFilter(minFilter, maxFilter);
        }
    }

    @Override
    public void act(float deltaSeconds) {
        super.act(deltaSeconds);

        float deltaAlpha = deltaSeconds / fadeAnimDurationSeconds;

        // Update fade alphas
        for (Cloud cloud : clouds) {
            if (cloud.alpha != cloud.targetAlpha) {
                cloud.alpha += (cloud.targetAlpha == 1) ? deltaAlpha : -deltaAlpha;
                if (cloud.alpha < 0) {
                    cloud.alpha = 0;
                }
                if (cloud.alpha > 1) {
                    cloud.alpha = 1;
                }
            }
        }

        // If fade animations are completed, remove deleted clouds
        int i = 0;
        while (i < clouds.size) {
            Cloud cloud = clouds.get(i);
            if ((cloud.targetAlpha == 0) && (cloud.alpha == 0)) {
                clouds.removeIndex(i);
                cloud.free();
            } else {
                i++;
            }
        }
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        // Special case: not tileable
        if (!tileableX && !tileableY) {
            drawSprite(batch, parentAlpha);
        }

        // General case: tileable in at least one dimension
        else {
            DrawUtils.drawTileableSprite(batch, parentAlpha, this, tileableX, tileableY, minTiledX, maxTiledX,
                    minTiledY, maxTiledY);
        }
    }

    @Override
    public void drawSprite(Batch batch, float parentAlpha) {
        fading = false;
        float actorX = getX();
        float screenWidth = Gdx.graphics.getWidth();

        for (Cloud cloud : clouds) {
            // Do not draw out-of-screen clouds
            float cloudX = actorX + cloud.x;
            if (cloudX > screenWidth) {
                continue;
            }
            if (cloudX + cloud.width < 0) {
                continue;
            }

            // Set sprite grid properties from the cloud and draw it
            setSpriteGridPropertiesFromCloud(spriteGrids.get(cloud.spriteGridIndex), cloud);
            spriteGrids.get(cloud.spriteGridIndex).draw(batch, parentAlpha);
            if (cloud.alpha != cloud.targetAlpha) {
                fading = true;
            }
        }
    }

    @Override
    public long getMaxSleepTime() {
        if (fading) {
            return 0;
        }

        return super.getMaxSleepTime();
    }

    @Override
    public void scaleBy(float scale) {
        for (Cloud cloud : clouds) {
            SpriteGrid spriteGrid = spriteGrids.get(cloud.spriteGridIndex);
            float originalWidth = spriteGrid.getOriginalWidth();
            float originalHeight = spriteGrid.getOriginalHeight();
            cloud.width = originalWidth * cloud.scale * scale;
            cloud.height = originalHeight * cloud.scale * scale;
        }
    }

    public float getCloudsPerc() {
        return cloudsPerc;
    }

    public void setCloudsPerc(float cloudsPerc) {
        this.cloudsPerc = cloudsPerc;
    }

    public boolean isTileableX() {
        return tileableX;
    }

    public void setTileableX(boolean tileableX) {
        this.tileableX = tileableX;
    }

    public boolean isTileableY() {
        return tileableY;
    }

    public void setTileableY(boolean tileableY) {
        this.tileableY = tileableY;
    }

    public float getMinTiledX() {
        return minTiledX;
    }

    public void setMinTiledX(float minTiledX) {
        this.minTiledX = minTiledX;
    }

    public float getMaxTiledX() {
        return maxTiledX;
    }

    public void setMaxTiledX(float maxTiledX) {
        this.maxTiledX = maxTiledX;
    }

    public float getMinTiledY() {
        return minTiledY;
    }

    public void setMinTiledY(float minTiledY) {
        this.minTiledY = minTiledY;
    }

    public float getMaxTiledY() {
        return maxTiledY;
    }

    public void setMaxTiledY(float maxTiledY) {
        this.maxTiledY = maxTiledY;
    }

    public void setTiledBoundsX(float minTiledX, float maxTiledX) {
        this.minTiledX = minTiledX;
        this.maxTiledX = maxTiledX;
    }

    public void setTiledBoundsY(float minTiledY, float maxTiledY) {
        this.minTiledY = minTiledY;
        this.maxTiledY = maxTiledY;
    }

    public float getFadeAnimDurationSeconds() {
        return fadeAnimDurationSeconds;
    }

    public void setFadeAnimDurationSeconds(float fadeAnimDurationSeconds) {
        this.fadeAnimDurationSeconds = fadeAnimDurationSeconds;
    }

    public float getFadeAnimMaxDeltaSeconds() {
        return fadeAnimMaxDeltaSeconds;
    }

    public void setFadeAnimMaxDeltaSeconds(float fadeAnimMaxDeltaSeconds) {
        this.fadeAnimMaxDeltaSeconds = fadeAnimMaxDeltaSeconds;
    }

    public static class Cloud implements Pool.Poolable {
        protected Pool<Cloud> cloudPool;
        float x, y;
        float width, height;
        float scale;
        float alpha, targetAlpha;
        boolean flipX, flipY;
        int spriteGridIndex;

        @Inject
        public Cloud() {
            reset();
        }

        @Override
        public void reset() {
            x = y = width = height = 0;
            scale = 1;
            alpha = 0;
            targetAlpha = 1;
            flipX = flipY = false;
            spriteGridIndex = -1;
        }

        public void free() {
            cloudPool.free(this);
        }

        public void setPool(Pool<Cloud> cloudPool) {
            this.cloudPool = cloudPool;
        }
    }

}