/**
* Copyright 2010 The ForPlay Authors
*
* 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 forplay.sample.cute.core;
import static forplay.core.ForPlay.*;
import static java.lang.Math.max;
import forplay.core.Image;
import forplay.core.Json;
import forplay.core.Surface;
import java.util.ArrayList;
import java.util.List;
public class CuteWorld {
static class Stack {
int[] tiles;
List<CuteObject> objects = new ArrayList<CuteObject>();
int height() {
return tiles.length;
}
}
private static final String[] tileNames = new String[] { "block_brown",
"block_dirt", "block_grass", "block_plain", "block_stone", "block_wall",
"block_water", "block_wood",
"ramp_north", "ramp_east", "ramp_south", "ramp_west",
"roof_north", "roof_northeast", "roof_east", "roof_southeast",
"roof_south", "roof_southwest", "roof_west", "roof_northwest", };
private static final String[] shadowNames = new String[] { "shadow_east",
"shadow_northeast", "shadow_north", "shadow_northwest", "shadow_west",
"shadow_southwest", "shadow_south", "shadow_southeast",
"shadow_side_west" };
private static final int SHADOW_EAST = 0;
private static final int SHADOW_NORTHEAST = 1;
private static final int SHADOW_NORTH = 2;
private static final int SHADOW_NORTHWEST = 3;
private static final int SHADOW_WEST = 4;
private static final int SHADOW_SOUTHWEST = 5;
private static final int SHADOW_SOUTH = 6;
private static final int SHADOW_SOUTHEAST = 7;
private static final int SHADOW_SIDE_WEST = 8;
private static final int TILE_WIDTH = 100;
private static final int TILE_HEIGHT = 80;
private static final int TILE_DEPTH = 40;
private static final int TILE_BASE = 90;
private static final int TILE_IMAGE_HEIGHT = 170;
private static final int OBJECT_BASE = 30;
private static final double GRAVITY = -10.0;
private static final double RESTITUTION = 0.4;
private static final double FRICTION = 10.0;
private static final int MAX_STACK_HEIGHT = 8;
private static final Stack EMPTY_STACK;
static {
EMPTY_STACK = new Stack();
EMPTY_STACK.tiles = new int[0];
}
private final Image[] tiles = new Image[tileNames.length];
private final Image[] shadows = new Image[shadowNames.length];
private Stack[] world;
private int worldWidth, worldHeight;
private double viewOriginX, viewOriginY, viewOriginZ;
private int updateCounter = -1;
public CuteWorld(Json.Object data) {
loadImages();
initWorld(data);
}
public CuteWorld(int width, int height) {
worldWidth = width;
worldHeight = height;
loadImages();
this.world = new Stack[worldWidth * worldHeight];
int i = 0;
for (int ty = 0; ty < worldHeight; ++ty) {
for (int tx = 0; tx < worldWidth; ++tx) {
this.world[i] = new Stack();
world[i].tiles = new int[0];
++i;
}
}
}
public void addObject(CuteObject o) {
Stack stack = stackForObject(o);
stack.objects.add(o);
o.stack = stack;
}
public void addTile(int tx, int ty, int type) {
Stack stack = stack(tx, ty);
int len = stack.tiles.length;
if (len == MAX_STACK_HEIGHT) {
return;
}
int[] newTiles = new int[len + 1];
System.arraycopy(stack.tiles, 0, newTiles, 0, len);
stack.tiles = newTiles;
stack.tiles[len] = type;
}
public void removeTopTile(int tx, int ty) {
Stack stack = stack(tx, ty);
int len = stack.tiles.length;
if (len == 0) {
return;
}
int[] newTiles = new int[len - 1];
System.arraycopy(stack.tiles, 0, newTiles, 0, len - 1);
stack.tiles = newTiles;
}
public void paint(Surface surf, float alpha) {
int startX = (int) pixelToWorldX(surf, 0);
int endX = (int) pixelToWorldX(surf, surf.width());
if (startX < 0)
startX = 0;
if (endX < 0)
endX = 0;
if (startX >= worldWidth)
startX = worldWidth - 1;
if (endX >= worldWidth)
endX = worldWidth - 1;
int startY = (int) pixelToWorldY(surf, 0, 0);
int endY = (int) pixelToWorldY(surf, surf.height(), MAX_STACK_HEIGHT);
if (startY < 0)
startY = 0;
if (endY < 0)
endY = 0;
if (startY >= worldHeight)
startY = worldHeight - 1;
if (endY >= worldHeight)
endY = worldHeight - 1;
// Paint all the tiles from back to front.
for (int tz = 0; tz < MAX_STACK_HEIGHT; ++tz) {
for (int ty = startY; ty <= endY; ++ty) {
for (int tx = startX; tx <= endX; ++tx) {
Stack stack = world[ty * worldWidth + tx];
if (tz < stack.height()) {
// Draw the tile and its shadows.
// Skip obviously hidden tiles.
if ((tz < stack.height() - 1) && (height(tx, ty + 1) > tz)) {
continue;
}
// Figure out where the tile goes. If it's out of screen bounds,
// skip it (paintShadow() is relatively expensive).
int px = worldToPixelX(surf, tx);
int py = worldToPixelY(surf, ty, tz) - TILE_BASE;
if ((px > surf.width()) || (py > surf.height())
|| (px + TILE_WIDTH < 0) || (py + TILE_IMAGE_HEIGHT < 0)) {
continue;
}
surf.drawImage(tiles[stack.tiles[tz]], px, py);
paintShadow(surf, tx, ty, px, py);
} else if (tz >= stack.height()) {
// Paint the objects in this stack.
paintObjects(surf, stack, tz, alpha);
}
}
}
}
}
public void setViewOrigin(double x, double y, double z) {
viewOriginX = x;
viewOriginY = y;
viewOriginZ = z;
}
public void updatePhysics(double delta) {
for (int ty = 0; ty < worldHeight; ++ty) {
for (int tx = 0; tx < worldWidth; ++tx) {
updatePhysics(stack(tx, ty), delta);
}
}
updatePhysics(EMPTY_STACK, delta);
++updateCounter;
}
public void write(Json.Writer w) {
w.object();
{
w.key("width");
w.value(worldWidth);
w.key("height");
w.value(worldHeight);
w.key("stacks");
w.array();
for (int y = 0; y < worldHeight; ++y) {
for (int x = 0; x < worldWidth; ++x) {
Stack stack = stack(x, y);
w.array();
for (int z = 0; z < stack.height(); ++z) {
w.value(stack.tiles[z]);
}
w.endArray();
}
}
w.endArray();
}
w.endObject();
}
private int height(int tx, int ty) {
return stack(tx, ty).height();
}
private String imageRes(String name) {
return "images/" + name + ".png";
}
private void initWorld(Json.Object data) {
worldWidth = data.getInt("width");
worldHeight = data.getInt("height");
this.world = new Stack[worldWidth * worldHeight];
Json.Array stacksData = data.getArray("stacks");
int i = 0;
for (int ty = 0; ty < worldHeight; ++ty) {
for (int tx = 0; tx < worldWidth; ++tx) {
Json.Array stackData = stacksData.getArray(i);
world[i] = new Stack();
world[i].tiles = new int[stackData.length()];
for (int tz = 0; tz < stackData.length(); ++tz) {
world[i].tiles[tz] = stackData.getInt(tz);
}
++i;
}
}
viewOriginX = 0;
viewOriginY = 2.5;
}
private void loadImages() {
// Load tiles.
for (int i = 0; i < tiles.length; ++i) {
tiles[i] = assetManager().getImage(imageRes(tileNames[i]));
}
// Load shadows.
for (int i = 0; i < shadows.length; ++i) {
shadows[i] = assetManager().getImage(imageRes(shadowNames[i]));
}
}
/**
* Moves an object by the given vector, handling all collisions.
*/
private void moveBy(CuteObject o, double dx, double dy, double dz) {
// Walls - start by getting relative heights of neighbors
int tx = (int) o.x, ty = (int) o.y;
int hc = (int) o.z;
int hn = height(tx, ty - 1);
int hs = height(tx, ty + 1);
int hw = height(tx - 1, ty);
int he = height(tx + 1, ty);
int hse = height(tx + 1, ty + 1);
int hne = height(tx + 1, ty - 1);
int hsw = height(tx - 1, ty + 1);
int hnw = height(tx - 1, ty - 1);
double left = o.x + dx - o.r, right = o.x + dx + o.r;
double top = o.y + dy - o.r, bottom = o.y + dy + o.r;
boolean pastLeft = left < tx, pastTop = top < ty;
boolean pastRight = right > tx + 1, pastBottom = bottom > ty + 1;
// Collisions: north, east, west, south.
if (pastLeft) {
if (hw > hc) {
dx = tx + o.r - o.x;
o.vx = -o.vx * RESTITUTION;
}
} else if (pastRight) {
if (he > hc) {
dx = tx + 1 - o.r - o.x;
o.vx = -o.vx * RESTITUTION;
}
}
if (pastTop) {
if (hn > hc) {
dy = ty + o.r - o.y;
o.vy = -o.vy * RESTITUTION;
}
} else if (pastBottom) {
if (hs > hc) {
dy = ty + 1 - o.r - o.y;
o.vy = -o.vy * RESTITUTION;
}
}
// Collisions: nw, ne, se, sw.
if (pastLeft && pastTop) {
if (hnw > hc) {
if (tx - left > ty - top) {
dy = ty - (o.y - o.r);
o.vy = -o.vy * RESTITUTION;
} else {
dx = tx - (o.x - o.r);
o.vx = -o.vx * RESTITUTION;
}
}
}
if (pastRight && pastTop) {
if (hne > hc) {
if (right - (tx + 1) > ty - top) {
dy = ty - (o.y - o.r);
o.vy = -o.vy * RESTITUTION;
} else {
dx = (tx + 1) - (o.r + o.x);
o.vx = -o.vx * RESTITUTION;
}
}
}
if (pastRight && pastBottom) {
if (hse > hc) {
if (right - (tx + 1) > bottom - (ty + 1)) {
dy = (ty + 1) - (o.r + o.y);
o.vy = -o.vy * RESTITUTION;
} else {
dx = (tx + 1) - (o.r + o.x);
o.vx = -o.vx * RESTITUTION;
}
}
}
if (pastLeft && pastBottom) {
if (hsw > hc) {
if (tx - left > bottom - (ty + 1)) {
dy = (ty + 1) - (o.r + o.y);
o.vy = -o.vy * RESTITUTION;
} else {
dx = tx - (o.x - o.r);
o.vx = -o.vx * RESTITUTION;
}
}
}
// Update x/y position.
o.x = o.x + dx;
o.y = o.y + dy;
// Clamp to world bounds.
if (o.x < o.r) {
o.x = o.r;
}
if (o.y < o.r) {
o.y = o.r;
}
if (o.x > worldWidth - o.r) {
o.x = worldWidth - o.r;
}
if (o.y > worldHeight - o.r) {
o.y = worldHeight - o.r;
}
// Collisions: floors.
left = o.x + dx - o.r;
right = o.x + dx + o.r;
top = o.y + dy - o.r;
bottom = o.y + dy + o.r;
pastLeft = left < tx - 0.01;
pastTop = top < ty - 0.01;
pastRight = right > tx + 1.01;
pastBottom = bottom > ty + 1.01;
double floor = height(tx, ty);
if (pastLeft && hw - o.z < 0.5) {
floor = max(floor, hw);
}
if (pastTop && hn - o.z < 0.5) {
floor = max(floor, hn);
}
if (pastRight && he - o.z < 0.5) {
floor = max(floor, he);
}
if (pastBottom && hs - o.z < 0.5) {
floor = max(floor, hs);
}
if (o.z + dz < floor) {
dz = floor - o.z;
o.vz = -o.vz * RESTITUTION;
if (o.vz < 0.01) {
o.vz = 0;
}
o.resting = true;
} else {
o.resting = o.vz == 0;
}
o.z = o.z + dz;
// Clamp to world bounds.
if (o.z < 0) {
o.z = 0;
}
if (o.z > MAX_STACK_HEIGHT - 0.01) {
o.z = MAX_STACK_HEIGHT - 0.01;
}
}
private void paintObjects(Surface surf, Stack stack, int tz, float alpha) {
for (CuteObject o : stack.objects) {
if ((int) o.z == tz) {
int px = worldToPixelX(surf, o.x(alpha));
int py = worldToPixelY(surf, o.y(alpha), o.z(alpha));
int baseX = o.img.width() / 2;
int baseY = o.img.height() - OBJECT_BASE;
surf.drawImage(o.img, px - baseX, py - baseY);
}
}
}
private void paintShadow(Surface surf, int tx, int ty, int px, int py) {
int hc = height(tx, ty);
int hn = height(tx, ty - 1);
int hs = height(tx, ty + 1);
int hw = height(tx - 1, ty);
int he = height(tx + 1, ty);
int hse = height(tx + 1, ty + 1);
int hne = height(tx + 1, ty - 1);
int hsw = height(tx - 1, ty + 1);
int hnw = height(tx - 1, ty - 1);
if (hn > hc) {
surf.drawImage(shadows[SHADOW_NORTH], px, py);
}
if (hs > hc) {
surf.drawImage(shadows[SHADOW_SOUTH], px, py);
}
if (he > hc) {
surf.drawImage(shadows[SHADOW_EAST], px, py);
}
if (hw > hc) {
surf.drawImage(shadows[SHADOW_WEST], px, py);
}
if ((hse > hc) && (he <= hc)) {
surf.drawImage(shadows[SHADOW_SOUTHEAST], px, py);
}
if ((hsw > hc) && (hw <= hc)) {
surf.drawImage(shadows[SHADOW_SOUTHWEST], px, py);
}
if ((hne > hc) && (he <= hc) && (hn <= hc)) {
surf.drawImage(shadows[SHADOW_NORTHEAST], px, py);
}
if ((hnw > hc) && (hw <= hc) && (hn <= hc)) {
surf.drawImage(shadows[SHADOW_NORTHWEST], px, py);
}
// Special case: the side shadow has to be drawn potentially multiple
// times, because it's rendered on the front face of the stack.
while (hc > 0) {
if ((hsw >= hc) && (hs < hc)) {
surf.drawImage(shadows[SHADOW_SIDE_WEST], px, py);
}
py += TILE_DEPTH;
if (hs >= hc) {
break;
}
--hc;
}
}
private double pixelToWorldX(Surface surf, int x) {
double center = surf.width() * 0.5;
return (int) (((viewOriginX * TILE_WIDTH) + x - center) / TILE_WIDTH);
}
private double pixelToWorldY(Surface surf, int y, double z) {
double center = surf.height() * 0.5;
return (y + (viewOriginY * TILE_HEIGHT - viewOriginZ * TILE_DEPTH)
+ (z * TILE_DEPTH) - center)
/ TILE_HEIGHT;
}
private Stack stack(int tx, int ty) {
if ((tx < 0) || (tx >= worldWidth) || (ty < 0) || (ty >= worldHeight)) {
return EMPTY_STACK;
}
return world[ty * worldWidth + tx];
}
private Stack stackForObject(CuteObject o) {
if ((o.x < 0) || (o.y < 0) || (o.x >= worldWidth) || (o.y >= worldHeight)) {
return EMPTY_STACK;
}
return stack((int) o.x, (int) o.y);
}
private void updatePhysics(CuteObject o, double delta) {
// Avoid double-updates.
if (o.lastUpdated == updateCounter) {
return;
}
o.lastUpdated = updateCounter;
o.saveOldPos();
// Gravity & friction.
if (o.z > (double) o.stack.height()) {
o.az += delta * GRAVITY;
}
if (o.resting) {
o.vx -= o.vx * FRICTION * delta;
o.vy -= o.vy * FRICTION * delta;
if (o.vz < 0) {
o.vz = 0;
}
}
// Update velocity
o.vx += o.ax * delta;
o.vy += o.ay * delta;
o.vz += o.az * delta;
// Update position and handle collisions.
moveBy(o, o.vx, o.vy, o.vz);
}
private void updatePhysics(Stack stack, double delta) {
for (int i = 0; i < stack.objects.size(); ++i) {
// Run physics.
CuteObject o = stack.objects.get(i);
updatePhysics(o, delta);
// Re-sort.
Stack newStack = stackForObject(o);
if (stack != newStack) {
stack.objects.remove(i--);
newStack.objects.add(o);
o.stack = newStack;
}
}
}
private int worldToPixelX(Surface surf, double x) {
double center = surf.width() * 0.5;
return (int) (center - (viewOriginX * TILE_WIDTH) + x * TILE_WIDTH);
}
private int worldToPixelY(Surface surf, double y, double z) {
double center = surf.height() * 0.5;
return (int) (center
- (viewOriginY * TILE_HEIGHT - viewOriginZ * TILE_DEPTH) + y
* TILE_HEIGHT - z * TILE_DEPTH);
}
public double worldWidth() {
return worldWidth;
}
public double worldHeight() {
return worldHeight;
}
}