/* Copyright (c) 2015 Jesper Öqvist <jesper@llbit.se> * * This file is part of Chunky. * * Chunky is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chunky 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. * You should have received a copy of the GNU General Public License * along with Chunky. If not, see <http://www.gnu.org/licenses/>. */ package se.llbit.chunky.world.entity; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.LinkedList; import java.util.Random; import org.apache.commons.math3.util.FastMath; import se.llbit.chunky.renderer.scene.PlayerModel; import se.llbit.chunky.resources.EntityTexture; import se.llbit.chunky.resources.Texture; import se.llbit.chunky.resources.texturepack.EntityTextureLoader; import se.llbit.chunky.resources.texturepack.TextureFormatError; import se.llbit.json.JsonObject; import se.llbit.json.JsonValue; import se.llbit.log.Log; import se.llbit.math.QuickMath; import se.llbit.math.Transform; import se.llbit.math.Vector3; import se.llbit.math.primitive.Box; import se.llbit.math.primitive.Primitive; public class PlayerEntity extends Entity { public final String uuid; public double yaw; public double pitch; public double headYaw = 0.0; public double leftLegPose; public double rightLegPose; public double leftArmPose; public double rightArmPose; public double scale = 1.0; public PlayerModel model; public String skin = ""; public PlayerEntity(String uuid, Vector3 position, double yawDegrees, double pitchDegrees) { this(uuid, position, QuickMath.degToRad(180 - yawDegrees), -QuickMath.degToRad(pitchDegrees), 0.4, -0.4, 0.4, -0.4, PlayerModel.STEVE); } public PlayerEntity(String uuid, Vector3 position, double yaw, double pitch, double leftLegPose, double rightLegPose, double leftArmPose, double rightArmPose, PlayerModel model) { super(position); this.uuid = uuid; this.yaw = yaw; this.pitch = pitch; this.leftLegPose = leftLegPose; this.rightLegPose = rightLegPose; this.leftArmPose = leftArmPose; this.rightArmPose = rightArmPose; this.model = model; } private void poseLimb(Box part, Transform transform, Transform offset) { part.transform(transform); part.transform(Transform.NONE.rotateY(yaw)); part.transform(offset); } private void poseHead(Box part, Transform transform, Transform offset) { part.transform(Transform.NONE.rotateX(pitch)); part.transform(transform); part.transform(Transform.NONE.rotateY(yaw)); part.transform(offset); } @Override public Collection<Primitive> primitives(Vector3 offset) { EntityTexture texture = Texture.steve; double armWidth = model == PlayerModel.ALEX ? 1.5 : 2; if (skin.isEmpty()) { switch (model) { case ALEX: texture = Texture.alex; break; case STEVE: texture = Texture.steve; break; } } else { texture = new EntityTexture(); EntityTextureLoader loader = new EntityTextureLoader(skin, texture); try { loader.load(new File(skin)); } catch (IOException | TextureFormatError e) { Log.warn("Failed to load skin", e); texture = Texture.steve; } } Collection<Primitive> faces = new LinkedList<>(); Transform offsetTransform = Transform.NONE .translate(position.x + offset.x, position.y + offset.y, position.z + offset.z); Box head = new Box(-4 / 16., 4 / 16., -4 / 16., 4 / 16., -4 / 16., 4 / 16.); poseHead(head, withScale(Transform.NONE.rotateY(headYaw).translate(0, 28 / 16., 0)), offsetTransform); head.addFrontFaces(faces, texture, texture.headFront); head.addBackFaces(faces, texture, texture.headBack); head.addLeftFaces(faces, texture, texture.headLeft); head.addRightFaces(faces, texture, texture.headRight); head.addTopFaces(faces, texture, texture.headTop); head.addBottomFaces(faces, texture, texture.headBottom); Box hat = new Box(-4.2 / 16., 4.2 / 16., -4.2 / 16., 4.2 / 16., -4.2 / 16., 4.2 / 16.); poseHead(hat, withScale(Transform.NONE.rotateY(headYaw).translate(0, 28.2 / 16., 0)), offsetTransform); hat.addFrontFaces(faces, texture, texture.hatFront); hat.addBackFaces(faces, texture, texture.hatBack); hat.addLeftFaces(faces, texture, texture.hatLeft); hat.addRightFaces(faces, texture, texture.hatRight); hat.addTopFaces(faces, texture, texture.hatTop); hat.addBottomFaces(faces, texture, texture.hatBottom); Box chest = new Box(-4 / 16., 4 / 16., -6 / 16., 6 / 16., -2 / 16., 2 / 16.); poseLimb(chest, withScale(Transform.NONE.translate(0, 18 / 16., 0)), offsetTransform); chest.addFrontFaces(faces, texture, texture.chestFront); chest.addBackFaces(faces, texture, texture.chestBack); chest.addLeftFaces(faces, texture, texture.chestLeft); chest.addRightFaces(faces, texture, texture.chestRight); chest.addTopFaces(faces, texture, texture.chestTop); chest.addBottomFaces(faces, texture, texture.chestBottom); Box leftLeg = new Box(-2 / 16., 2 / 16., -6 / 16., 6 / 16., -2 / 16., 2 / 16.); poseLimb(leftLeg, withScale(Transform.NONE.translate(0, -6 / 16., 0).rotateX(leftLegPose) .translate(-2 / 16., 12 / 16., 0)), offsetTransform); leftLeg.addFrontFaces(faces, texture, texture.leftLegFront); leftLeg.addBackFaces(faces, texture, texture.leftLegBack); leftLeg.addLeftFaces(faces, texture, texture.leftLegLeft); leftLeg.addRightFaces(faces, texture, texture.leftLegRight); leftLeg.addTopFaces(faces, texture, texture.leftLegTop); leftLeg.addBottomFaces(faces, texture, texture.leftLegBottom); Box rightLeg = new Box(-2 / 16., 2 / 16., -6 / 16., 6 / 16., -2 / 16., 2 / 16.); poseLimb(rightLeg, withScale(Transform.NONE.translate(0, -6 / 16., 0).rotateX(rightLegPose) .translate(2 / 16., 12 / 16., 0)), offsetTransform); rightLeg.addFrontFaces(faces, texture, texture.rightLegFront); rightLeg.addBackFaces(faces, texture, texture.rightLegBack); rightLeg.addLeftFaces(faces, texture, texture.rightLegLeft); rightLeg.addRightFaces(faces, texture, texture.rightLegRight); rightLeg.addTopFaces(faces, texture, texture.rightLegTop); rightLeg.addBottomFaces(faces, texture, texture.rightLegBottom); Box leftArm = new Box(-armWidth / 16., armWidth / 16., -6 / 16., 6 / 16., -2 / 16., 2 / 16.); poseLimb(leftArm, withScale(Transform.NONE.translate(0, -5 / 16., 0).rotateX(leftArmPose) .translate(-(4 + armWidth) / 16., 23 / 16., 0)), offsetTransform); leftArm.addFrontFaces(faces, texture, texture.leftArmFront); leftArm.addBackFaces(faces, texture, texture.leftArmBack); leftArm.addLeftFaces(faces, texture, texture.leftArmLeft); leftArm.addRightFaces(faces, texture, texture.leftArmRight); leftArm.addTopFaces(faces, texture, texture.leftArmTop); leftArm.addBottomFaces(faces, texture, texture.leftArmBottom); Box rightArm = new Box(-armWidth / 16., armWidth / 16., -6 / 16., 6 / 16., -2 / 16., 2 / 16.); poseLimb(rightArm, withScale(Transform.NONE.translate(0, -5 / 16., 0).rotateX(rightArmPose) .translate((4 + armWidth) / 16., 23 / 16., 0)), offsetTransform); rightArm.addFrontFaces(faces, texture, texture.rightArmFront); rightArm.addBackFaces(faces, texture, texture.rightArmBack); rightArm.addLeftFaces(faces, texture, texture.rightArmLeft); rightArm.addRightFaces(faces, texture, texture.rightArmRight); rightArm.addTopFaces(faces, texture, texture.rightArmTop); rightArm.addBottomFaces(faces, texture, texture.rightArmBottom); return faces; } private Transform withScale(Transform transform) { if (scale == 1.0) { return transform; } else { return transform.scale(scale); } } @Override public JsonValue toJson() { JsonObject json = new JsonObject(); json.add("kind", "player"); json.add("uuid", uuid); json.add("position", position.toJson()); json.add("model", model.name()); json.add("skin", skin); json.add("pitch", pitch); json.add("yaw", yaw); json.add("leftLegPose", leftLegPose); json.add("rightLegPose", rightLegPose); json.add("leftArmPose", leftArmPose); json.add("rightArmPose", rightArmPose); json.add("headYaw", headYaw); if (scale != 1.0) { json.add("scale", scale); } return json; } public static PlayerEntity fromJson(JsonObject json) { Vector3 position = new Vector3(); position.fromJson(json.get("position").object()); PlayerModel model = PlayerModel.get(json.get("model").stringValue("STEVE")); double pitch = json.get("pitch").doubleValue(0.0); double yaw = json.get("yaw").doubleValue(0.0); String uuid = json.get("uuid").stringValue("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); String skin = json.get("skin").stringValue(""); double leftLegPose = json.get("leftLegPose").doubleValue(0.0); double rightLegPose = json.get("rightLegPose").doubleValue(0.0); double leftArmPose = json.get("leftArmPose").doubleValue(0.0); double rightArmPose = json.get("rightArmPose").doubleValue(0.0); double scale = json.get("scale").doubleValue(1.0); PlayerEntity entity = new PlayerEntity(uuid, position, yaw, pitch, leftLegPose, rightLegPose, leftArmPose, rightArmPose, model); entity.headYaw = json.get("headYaw").doubleValue(0.0); entity.skin = skin; entity.scale = scale; return entity; } @Override public String toString() { return "player: " + uuid; } @Override public int hashCode() { return uuid.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof PlayerEntity) { return ((PlayerEntity) obj).uuid.equals(uuid); } return false; } public void setTexture(String path) { skin = path; } public void randomPoseAndLook() { Random random = new Random(System.currentTimeMillis()); randomPose(random); randomLook(random); } public void randomPose() { Random random = new Random(System.currentTimeMillis()); randomPose(random); } private void randomPose(Random random) { leftLegPose = (random.nextFloat() - 0.5) * QuickMath.HALF_PI; rightLegPose = -leftLegPose; leftArmPose = (random.nextFloat() - 0.5) * QuickMath.HALF_PI; rightArmPose = -leftArmPose; } private void randomLook(Random random) { yaw = (random.nextFloat() - 0.5) * QuickMath.TAU; headYaw = 0.4 * (random.nextFloat() - 0.5) * QuickMath.HALF_PI; pitch = (random.nextFloat() - 0.5) * QuickMath.HALF_PI; } public void lookAt(Vector3 target) { Vector3 dir = new Vector3(target); Vector3 face = new Vector3(position); face.add(0, 28 / 16., 0); dir.sub(face); dir.normalize(); yaw = FastMath.atan2(dir.x, dir.z) + Math.PI - headYaw; pitch = Math.asin(dir.y); } }