001/**
002 * Copyright (c) 2011-2012 Ardor Labs AB.
003 *
004 * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
005 */
006
007package com.ardorcraft.collision;
008
009import com.ardor3d.math.MathUtils;
010import com.ardor3d.math.Vector3;
011import com.ardor3d.math.type.ReadOnlyVector3;
012import com.ardorcraft.world.BlockProvider;
013
014/**
015 * Ray tracing for a block world.
016 */
017public class Tracer {
018    private double mult = 1;
019    private final Vector3 tmax = new Vector3();
020    private final Vector3 tdelta = new Vector3();
021    private final Vector3 newPos = new Vector3();
022
023    private static final class DefaultHitTester implements HitTester {
024        @Override
025        public boolean isHit(final int blockId) {
026            return blockId != 0;
027        }
028    }
029
030    private final int maxHeight;
031    private final HitTester hitTester;
032    private final BlockProvider provider;
033
034    public Tracer(final BlockProvider provider) {
035        this.provider = provider;
036        hitTester = new DefaultHitTester();
037        maxHeight = -1;
038    }
039
040    public Tracer(final BlockProvider provider, final HitTester hitTester) {
041        this.provider = provider;
042        this.hitTester = hitTester;
043        maxHeight = -1;
044    }
045
046    public Tracer(final BlockProvider provider, final HitTester hitTester, final int maxHeight) {
047        this.provider = provider;
048        this.hitTester = hitTester;
049        this.maxHeight = maxHeight;
050    }
051
052    public void traceCollision(ReadOnlyVector3 curpos, ReadOnlyVector3 raydir, final int iterations,
053            final IntersectionResult result) {
054        tmax.set(0, 0, 0);
055        tdelta.set(0, 0, 0);
056
057        mult = 1;
058        boolean back = false;
059
060        result.hit = false;
061        result.length = 0;
062
063        if (curpos.getY() < 0) {
064            return;
065        }
066        if (maxHeight > 0 && curpos.getY() >= maxHeight - 1 && raydir.getY() >= 0) {
067            return;
068        }
069
070        if (maxHeight > 0 && curpos.getY() >= maxHeight - 1) {
071            final double diff = maxHeight - 1 - curpos.getY();
072            final double t = diff / raydir.getY();
073            newPos.set(raydir).multiplyLocal(t).addLocal(curpos);
074            curpos = newPos;
075        }
076
077        int X = (int) MathUtils.floor(curpos.getX());
078        int Y = (int) MathUtils.floor(curpos.getY());
079        int Z = (int) MathUtils.floor(curpos.getZ());
080
081        final int block1 = provider.getBlock(X, Y, Z);
082        if (block1 != 0 && hitTester.isHit(block1)) {
083            raydir = new Vector3(raydir).negateLocal();
084            mult = -1;
085            back = true;
086        }
087
088        int stepX, stepY, stepZ;
089        double cbx, cby, cbz;
090
091        if (raydir.getX() > 0.0) {
092            stepX = 1;
093            cbx = X + 1;
094        } else {
095            stepX = -1;
096            cbx = X;
097        }
098        if (raydir.getY() > 0.0) {
099            stepY = 1;
100            cby = Y + 1;
101        } else {
102            stepY = -1;
103            cby = Y;
104        }
105        if (raydir.getZ() > 0.0) {
106            stepZ = 1;
107            cbz = Z + 1;
108        } else {
109            stepZ = -1;
110            cbz = Z;
111        }
112
113        if (raydir.getX() != 0) {
114            final double rxr = 1.0 / raydir.getX();
115            tmax.setX((cbx - curpos.getX()) * rxr);
116            tdelta.setX(stepX * rxr);
117        } else {
118            tmax.setX(1000000);
119        }
120        if (raydir.getY() != 0) {
121            final double ryr = 1.0 / raydir.getY();
122            tmax.setY((cby - curpos.getY()) * ryr);
123            tdelta.setY(stepY * ryr);
124        } else {
125            tmax.setY(1000000);
126        }
127        if (raydir.getZ() != 0) {
128            final double rzr = 1.0 / raydir.getZ();
129            tmax.setZ((cbz - curpos.getZ()) * rzr);
130            tdelta.setZ(stepZ * rzr);
131        } else {
132            tmax.setZ(1000000);
133        }
134
135        int oldX = X, oldY = Y, oldZ = Z;
136
137        for (int i = 0; i < iterations; i++) {
138            if (tmax.getX() < tmax.getY()) {
139                if (tmax.getX() < tmax.getZ()) {
140                    X = X + stepX;
141                    final int block = provider.getBlock(X, Y, Z);
142                    final boolean isHit = block != 0 && hitTester.isHit(block);
143                    if (back && !isHit || !back && isHit) {
144                        gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ);
145                        result.hit = true;
146                        return;
147                    }
148                    tmax.setX(tmax.getX() + tdelta.getX());
149                } else {
150                    Z = Z + stepZ;
151                    final int block = provider.getBlock(X, Y, Z);
152                    final boolean isHit = block != 0 && hitTester.isHit(block);
153                    if (back && !isHit || !back && isHit) {
154                        gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ);
155                        result.hit = true;
156                        return;
157                    }
158                    tmax.setZ(tmax.getZ() + tdelta.getZ());
159                }
160            } else {
161                if (tmax.getY() < tmax.getZ()) {
162                    Y = Y + stepY;
163                    if (maxHeight > 0 && Y >= maxHeight - 1) {
164                        return;
165                    }
166                    final int block = provider.getBlock(X, Y, Z);
167                    final boolean isHit = block != 0 && hitTester.isHit(block);
168                    if (back && !isHit || !back && isHit) {
169                        gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ);
170                        result.hit = true;
171                        return;
172                    }
173                    tmax.setY(tmax.getY() + tdelta.getY());
174                } else {
175                    Z = Z + stepZ;
176                    final int block = provider.getBlock(X, Y, Z);
177                    final boolean isHit = block != 0 && hitTester.isHit(block);
178                    if (back && !isHit || !back && isHit) {
179                        gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ);
180                        result.hit = true;
181                        return;
182                    }
183                    tmax.setZ(tmax.getZ() + tdelta.getZ());
184                }
185            }
186
187            oldX = X;
188            oldY = Y;
189            oldZ = Z;
190        }
191    }
192
193    private void gatherMin(final IntersectionResult result, final Vector3 tmax, final int X, final int Y, final int Z,
194            final int oldX, final int oldY, final int oldZ) {
195        result.oldPos.set(oldX, oldY, oldZ);
196        result.pos.set(X, Y, Z);
197
198        double min = tmax.getX();
199        if (tmax.getY() < min) {
200            min = tmax.getY();
201        }
202        if (tmax.getZ() < min) {
203            min = tmax.getZ();
204        }
205
206        final double epsilon = 0.0001;
207
208        double length = min * mult;
209        if (length > 0) {
210            length = Math.max(length - epsilon, 0.0);
211        } else if (length < 0) {
212            length = Math.min(length - epsilon, 0.0);
213        }
214
215        result.length = length;
216    }
217}