852dc97104e917ab7bc96ae60e75f628f4c23939
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / gui / Avatar.java
1 /*
2  * Sixth 3D engine. Copyright ©2012-2018, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of version 3 of the GNU Lesser General Public License
6  * or later as published by the Free Software Foundation.
7  *
8  */
9
10 package eu.svjatoslav.sixth.e3d.gui;
11
12 import eu.svjatoslav.sixth.e3d.geometry.Point3D;
13
14 import static java.lang.Math.cos;
15 import static java.lang.Math.sin;
16
17 public class Avatar implements ViewRenderListener {
18
19     public static final double SPEED_LIMIT = 30;
20     /**
21      * Just in case we want to adjust global speed for some reason.
22      */
23     private static final double SPEED_MULTIPLIER = .02d;
24     /**
25      * Determines amount of friction user experiences every millisecond while moving around in space.
26      */
27     private static final double MILLISECOND_FRICTION = 1.005;
28     /**
29      * Avatar movement speed, relative to avatar itself. When avatar coordinates
30      * are updated within the world, avatar orientation relative to the world is
31      * taken into account.
32      */
33     private final Point3D movementVector = new Point3D();
34     public double avatarAcceleration = 0.1;
35     /**
36      * Avatar location within the 3D world.
37      */
38     private Point3D location = new Point3D();
39     /**
40      * Avatar orientation on the X-Z plane. It changes when turning left or
41      * right.
42      */
43     private double orientationXZ;
44     /**
45      * Avatar orientation on the Y-Z plane. It changes when looking up or down.
46      */
47     private double orientationYZ;
48
49     public Avatar() {
50     }
51
52     public Avatar(final Avatar sourceView) {
53         setLocation(new Point3D(sourceView.location));
54         setAngleXZ(sourceView.getAngleXZ());
55         setAngleYZ(sourceView.getAngleYZ());
56     }
57
58     public Avatar(final Point3D location) {
59         setLocation(location);
60     }
61
62     public Avatar(final Point3D location, final float angleXZ,
63                   final float angleYZ) {
64         setLocation(location);
65         setAngleXZ(angleXZ);
66         setAngleYZ(angleYZ);
67     }
68
69     @Override
70     public boolean beforeRender(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
71
72         final Point3D locationBeforeUpdate = new Point3D(location);
73         translateAvatarLocationBasedOnMovementVector(millisecondsSinceLastFrame);
74         applyFrictionToUserMovement(millisecondsSinceLastFrame);
75         return isFrameRepaintNeeded(locationBeforeUpdate);
76     }
77
78     private boolean isFrameRepaintNeeded(Point3D locationBeforeUpdate) {
79         final double distanceMoved = location.getDistanceTo(locationBeforeUpdate);
80         return distanceMoved > 0.03;
81     }
82
83     public void enforceSpeedLimit() {
84         final double currentSpeed = movementVector.getVectorLength();
85
86         if (currentSpeed <= SPEED_LIMIT)
87             return;
88
89         movementVector.scaleDown(currentSpeed / SPEED_LIMIT);
90     }
91
92     public double getAngleXZ() {
93         return orientationXZ;
94     }
95
96     public void setAngleXZ(final double angleXZ) {
97         orientationXZ = angleXZ;
98     }
99
100     public double getAngleYZ() {
101         return orientationYZ;
102     }
103
104     public void setAngleYZ(final double angleYZ) {
105         orientationYZ = angleYZ;
106     }
107
108     public Point3D getLocation() {
109         return location;
110     }
111
112     public void setLocation(final Point3D location) {
113         this.location = location;
114     }
115
116     public Point3D getMovementVector() {
117         return movementVector;
118     }
119
120     public double getMovementSpeed() {
121         return movementVector.getVectorLength();
122     }
123
124     private void applyFrictionToUserMovement(int millisecondsPassedSinceLastFrame) {
125         for (int i = 0; i < millisecondsPassedSinceLastFrame; i++)
126             applyMillisecondFrictionToUserMovementVector();
127     }
128
129     private void applyMillisecondFrictionToUserMovementVector() {
130         getMovementVector().x /= MILLISECOND_FRICTION;
131         getMovementVector().y /= MILLISECOND_FRICTION;
132         getMovementVector().z /= MILLISECOND_FRICTION;
133     }
134
135     /**
136      * Translate coordinates based on avatar movement vector and avatar orientation in the world.
137      *
138      * @param millisecondsPassedSinceLastFrame We want avatar movement to be independent of framerate.
139      *                                         Therefore we take frame rendering time into account when translating
140      *                                         avatar between consecutive frames.
141      */
142     private void translateAvatarLocationBasedOnMovementVector(int millisecondsPassedSinceLastFrame) {
143         location.x -= (float) sin(getAngleXZ())
144                 * getMovementVector().z * SPEED_MULTIPLIER
145                 * millisecondsPassedSinceLastFrame;
146         location.z += (float) cos(getAngleXZ())
147                 * getMovementVector().z * SPEED_MULTIPLIER
148                 * millisecondsPassedSinceLastFrame;
149
150         location.x += (float) cos(getAngleXZ())
151                 * getMovementVector().x * SPEED_MULTIPLIER
152                 * millisecondsPassedSinceLastFrame;
153         location.z += (float) sin(getAngleXZ())
154                 * getMovementVector().x * SPEED_MULTIPLIER
155                 * millisecondsPassedSinceLastFrame;
156
157         location.y += getMovementVector().y * SPEED_MULTIPLIER
158                 * millisecondsPassedSinceLastFrame;
159     }
160 }