001/*
002 * Trident - A Multithreaded Server Alternative
003 * Copyright 2014 The TridentSDK Team
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package net.tridentsdk.base;
019
020import com.google.common.base.Objects;
021import com.google.common.base.Preconditions;
022import net.tridentsdk.util.Vector;
023import net.tridentsdk.world.Chunk;
024import net.tridentsdk.world.World;
025
026import javax.annotation.concurrent.ThreadSafe;
027
028/**
029 * Represents a point on the coordinate grid of the world, including pitch and yaw
030 *
031 * @author The TridentSDK Team
032 * @since 0.3-alpha-DP
033 */
034@ThreadSafe
035public final class Position extends Vector implements Cloneable {
036    private volatile World world;
037    private volatile float yaw;
038    private volatile float pitch;
039
040    private Position(World world, double x, double y, double z, float yaw, float pitch) {
041        this.world = world;
042
043        this.x = x;
044        this.y = y;
045        this.z = z;
046
047        this.yaw = yaw;
048        this.pitch = pitch;
049    }
050
051    public Position(World world, double x, double y, double z) {
052        this(world, x, y, z, 0.0F, 0.0F);
053    }
054
055    private static double square(double d) {
056        return d * d;
057    }
058
059    /**
060     * References the point on the world as a position that wraps the coordinates
061     *
062     * @param world the world which the point resides in
063     * @param x     the x coordinate
064     * @param y     the y coordinate
065     * @param z     the z coordinate
066     * @param yaw   goes side to side, in degrees
067     * @param pitch goes up and down, in degrees
068     */
069    public static Position create(World world, double x, double y, double z, float yaw, float pitch) {
070        return new Position(world, x, y, z, yaw, pitch);
071    }
072
073    /**
074     * Wraps the point without specific yaw and pitch (set to 0)
075     *
076     * @param world the world which the point resides in
077     * @param x     the x coordinate
078     * @param y     the y coordinate
079     * @param z     the z coordinate
080     */
081    public static Position create(World world, double x, double y, double z) {
082        return new Position(world, x, y, z);
083    }
084
085    /**
086     * The world the position is in
087     *
088     * @return the world where the position is
089     */
090    public World world() {
091        return this.world;
092    }
093
094    /**
095     * Chunk of the current position
096     *
097     * @return Chunk of the position
098     */
099    public Chunk chunk() {
100        return world.chunkAt((int) x >> 4, (int) z >> 4, true);
101    }
102
103    /**
104     * Sets the position's world
105     *
106     * @param world the world to set the position to
107     */
108    public void setWorld(World world) {
109        this.world = world;
110    }
111
112    /**
113     * The yaw of the position
114     *
115     * @return the yaw of this position
116     */
117    public float yaw() {
118        return this.yaw;
119    }
120
121    /**
122     * Sets the yaw of the position
123     *
124     * @param yaw the yaw of the position to set
125     */
126    public void setYaw(float yaw) {
127        this.yaw = yaw;
128    }
129
130    /**
131     * The pitch of the position
132     *
133     * @return the pitch value of this position
134     */
135    public float pitch() {
136        return this.pitch;
137    }
138
139    /**
140     * Sets the pitch of the position
141     *
142     * @param pitch the pitch of the position to set
143     */
144    public void setPitch(float pitch) {
145        this.pitch = pitch;
146    }
147
148    /**
149     * Adds the x, y, and z from the vector to the coordinates of this position
150     *
151     * @param vector the vector containing the relative data
152     * @return the relative position
153     */
154    public Position add(Vector vector) {
155        this.setX(x() + vector.x());
156        this.setY(y() + vector.y());
157        this.setZ(z() + vector.z());
158
159        return this;
160    }
161
162    /**
163     * Acquires the relative position to this set of coordinates
164     *
165     * @param vector the vector that has the x, y, and z of the position relative to this
166     * @return the relative position
167     */
168    public Position relative(Vector vector) {
169        return new Position(this.world(), vector.x() + this.x(), vector.y() + this.y(),
170                vector.z() + this.z(), this.yaw(), this.pitch());
171    }
172
173    /**
174     * Acquires the tile at this position
175     *
176     * @return the tile occupying the coordinates of this position
177     */
178    public Block block() {
179        return world().blockAt(this);
180    }
181
182    /**
183     * Creates new Vector with Location's coordinates
184     *
185     * @return New Vector containing this Location's coordinates
186     */
187    public Vector asVector() {
188        return new Vector(this.x(), this.y(), this.z());
189    }
190
191    /**
192     * Obtains the unit vector pointing in the direction of the pitch and yaw
193     *
194     * @return the direction vector
195     */
196    public Vector asUnitVector() {
197        // queerest calculations ever, apparently x goes towards 3pi/2 and z to 0...?
198        double x = -Math.cos(pitch) * Math.sin(yaw);
199        double y = -Math.sin(pitch);
200        double z = Math.cos(pitch) * Math.cos(yaw);
201
202        return new Vector(x, y, z);
203    }
204
205    /**
206     * The distance this from position to another. Math.sqrt is costly, ergo calling this method a lot is not advised.
207     *
208     * @param position the position to measure distance with
209     * @return distance from this position to another
210     */
211    public double distance(Position position) {
212        return Math.sqrt(this.distanceSquared(position));
213    }
214
215    /**
216     * The distance squared from this position to another
217     *
218     * @param position the position to measure distance with
219     * @return distance squared from this position to another
220     */
221    public double distanceSquared(Position position) {
222        Preconditions.checkNotNull(position, "Location cannot be null.");
223        if (!this.world().equals(position.world()))
224            return 0.0;
225        return square(this.x() - position.x()) + square(this.y() - position.y()) +
226                square(this.z() - position.z());
227    }
228
229    /**
230     * Convert the Pitch and Yaw to a Vectorized direction
231     *
232     * @return Vector of the direction of the position
233     */
234    public Vector toDirection(){
235        Vector vector = new Vector();
236        double rotX = yaw;
237        double rotY = pitch;
238        double xz = Math.cos(Math.toRadians(rotY));
239        vector.setY(-Math.sin(Math.toRadians(rotY)));
240        vector.setX(-xz * Math.sin(Math.toRadians(rotX)));
241        vector.setZ(xz * Math.cos(Math.toRadians(rotX)));
242        return vector;
243    }
244
245    @Override
246    public Position clone() {
247        return new Position(world, x, y, z(), yaw, pitch);
248    }
249
250    @Override
251    public boolean equals(Object obj) {
252        if (!(obj instanceof Position))
253            return false;
254        if (obj.hashCode() != this.hashCode())
255            return false;
256        if (x != ((Position) obj).x) {
257            return false;
258        } else if (y != ((Position) obj).y) {
259            return false;
260        } else if (z != ((Position) obj).z) {
261            return false;
262        } else if (world != ((Position) obj).world) {
263            return false;
264        } else if (pitch != ((Position) obj).pitch) {
265            return false;
266        } else if (yaw != ((Position) obj).yaw) {
267            return false;
268        }
269
270        return true;
271    }
272
273    @Override
274    public int hashCode() {
275        return Objects.hashCode(world, x, y, z, pitch, yaw);
276    }
277
278    @Override
279    public String toString(){
280        return "Position{" +
281                "world=" + world +
282                ", x=" + x +
283                ", y=" + y +
284                ", z=" + z +
285                ", yaw=" + yaw +
286                ", pitch=" + pitch +
287                '}';
288    }
289}