nova.core.util.RayTracer.java Source code

Java tutorial

Introduction

Here is the source code for nova.core.util.RayTracer.java

Source

/*
 * Copyright (c) 2015 NOVA, All rights reserved.
 * This library is free software, licensed under GNU Lesser General Public License version 3
 *
 * This file is part of NOVA.
 *
 * NOVA 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.
 *
 * NOVA 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 NOVA.  If not, see <http://www.gnu.org/licenses/>.
 */package nova.core.util;

import nova.core.block.Block;
import nova.core.component.ComponentProvider;
import nova.core.component.misc.Collider;
import nova.core.component.transform.WorldTransform;
import nova.core.entity.Entity;
import nova.core.entity.component.Living;
import nova.core.util.math.Vector3DUtil;
import nova.core.util.shape.Cuboid;
import nova.core.world.World;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * Ray tracing for cuboids.
 * @author Calclavia
 */
//TODO: Add ray trace masks
public class RayTracer {

    public final Ray ray;
    public double distance;
    public int parallelThreshold = 1000;

    public RayTracer(Ray ray) {
        this.ray = ray;
    }

    /**
     * Does an entity look ray trace to see which block the entity is looking at.
     * @param entity The entity
     */
    public RayTracer(Entity entity) {
        this(new Ray(entity.position()
                .add(entity.components.has(Living.class)
                        ? entity.components.get(Living.class).faceDisplacement.get()
                        : Vector3D.ZERO),
                entity.rotation().applyTo(Vector3DUtil.FORWARD)));
    }

    /**
     * Sets the distance of the ray
     * @param distance Distance in meters
     * @return This
     */
    public RayTracer setDistance(double distance) {
        this.distance = distance;
        return this;
    }

    public RayTracer setParallelThreshold(int parallelThreshold) {
        this.parallelThreshold = parallelThreshold;
        return this;
    }

    public boolean doParallel() {
        return distance > parallelThreshold;
    }

    public Stream<RayTraceResult> rayTraceAll(World world) {
        return Stream.concat(rayTraceBlocks(world), rayTraceEntities(world)).sorted();
    }

    /**
     * Check all blocks that are in a line
     * @return The blocks ray traced in the order from closest to furthest.
     */
    public Stream<RayTraceBlockResult> rayTraceBlocks(World world) {
        //All relevant blocks
        return rayTraceBlocks(
                IntStream.range(0, (int) distance + 1).mapToObj(i -> ray.origin.add(ray.dir.scalarMultiply(i)))
                        .flatMap(vec -> Arrays.stream(Direction.VALID_DIRECTIONS)
                                .map(direction -> Vector3DUtil.floor(vec.add(direction.toVector())))) //Cover a larger area to be safe
                        .distinct().map(world::getBlock).filter(Optional::isPresent).map(Optional::get));
    }

    /**
     * Ray traces a set of blocks
     * @param blocks Set of blocks
     * @return A list of cuboids that intersect with the line segment in the order from closest to furthest.
     */
    public Stream<RayTraceBlockResult> rayTraceBlocks(Set<Block> blocks) {
        return rayTraceBlocks(blocks.stream());
    }

    public Stream<RayTraceBlockResult> rayTraceBlocks(Stream<Block> blockStream) {
        return (doParallel() ? blockStream.parallel() : blockStream)
                .filter(block -> block.components.has(Collider.class))
                .flatMap(block -> rayTraceCollider(block, (pos, cuboid) -> new RayTraceBlockResult(pos,
                        ray.origin.distance(pos), cuboid.sideOf(pos), cuboid, block)))
                .sorted();
    }

    public Stream<RayTraceEntityResult> rayTraceEntities(World world) {
        //TODO: Consider smaller check space
        return rayTraceEntities(world.getEntities(Cuboid.ZERO.expand(distance).add(ray.origin)).stream()
                .filter(entity -> entity.components.has(Collider.class)));
    }

    public Stream<RayTraceEntityResult> rayTraceEntities(Stream<Entity> entityStream) {
        return (doParallel() ? entityStream.parallel() : entityStream)
                .filter(entity -> entity.components.has(Collider.class))
                .flatMap(entity -> rayTraceCollider(entity, (pos, cuboid) -> new RayTraceEntityResult(pos,
                        ray.origin.distance(pos), cuboid.sideOf(pos), cuboid, entity)))
                .sorted();
    }

    public <R extends RayTraceResult> Stream<R> rayTraceCollider(ComponentProvider colliderProvider,
            BiFunction<Vector3D, Cuboid, R> resultMapper) {
        return colliderProvider.components.get(Collider.class).occlusionBoxes.apply(Optional.empty()).stream()
                .map(cuboid -> cuboid
                        .add((Vector3D) colliderProvider.components.get(WorldTransform.class).position()))
                .map(cuboid -> rayTrace(cuboid, resultMapper)).filter(Optional::isPresent).map(Optional::get);
    }

    /**
     * Ray traces a set of cuboids
     * @param stream A stream of cuboids
     * @return A list of cuboids that intersect with the line segment in the order from closest to furthest.
     */
    public List<RayTraceResult> rayTrace(Stream<Cuboid> stream) {
        return stream.map(this::rayTrace).filter(Optional::isPresent).map(Optional::get).sorted()
                .collect(Collectors.toList());
    }

    public Optional<RayTraceResult> rayTrace(Cuboid cuboid) {
        return rayTrace(cuboid,
                (pos, cuboid2) -> new RayTraceResult(pos, ray.origin.distance(pos), cuboid.sideOf(pos), cuboid));
    }

    /**
     * Ray traces a cuboid
     * @param cuboid The cuboid in absolute world coordinates
     * @return The ray trace result if the ray intersects the cuboid
     */
    public <R extends RayTraceResult> Optional<R> rayTrace(Cuboid cuboid,
            BiFunction<Vector3D, Cuboid, R> resultMapper) {
        return rayTrace(cuboid, 0, distance).map(vec -> resultMapper.apply(vec, cuboid));
    }

    /**
     * Calculates intersection with the given ray between a certain distance
     * interval.
     * <p>
     * Ray-box intersection is using IEEE numerical properties to ensure the
     * test is both robust and efficient, as described in:
     * <br>
     * <code>Amy Williams, Steve Barrus, R. Keith Morley, and Peter Shirley: "An
     * Efficient and Robust Ray-Box Intersection Algorithm" Journal of graphics
     * tools, 10(1):49-54, 2005</code>
     * @param cuboid The cuboid to trace
     * @param minDist The minimum distance
     * @param maxDist The maximum distance
     * @return intersection point on the bounding box (only the first is
     * returned) or null if no intersection
     */
    public Optional<Vector3D> rayTrace(Cuboid cuboid, double minDist, double maxDist) {
        Vector3D bbox;

        double tMin;
        double tMax;

        bbox = ray.signDirX ? cuboid.max : cuboid.min;
        tMin = (bbox.getX() - ray.origin.getX()) * ray.invDir.getX();
        bbox = ray.signDirX ? cuboid.min : cuboid.max;
        tMax = (bbox.getX() - ray.origin.getX()) * ray.invDir.getX();

        //Y
        bbox = ray.signDirY ? cuboid.max : cuboid.min;
        double tyMin = (bbox.getY() - ray.origin.getY()) * ray.invDir.getY();
        bbox = ray.signDirY ? cuboid.min : cuboid.max;
        double tyMax = (bbox.getY() - ray.origin.getY()) * ray.invDir.getY();

        //Check with the current tMin and tMax to see if the clipping is out of bounds
        if ((tMin > tyMax) || (tyMin > tMax)) {
            return Optional.empty();
        }

        //Reset tMin and tMax
        if (tyMin > tMin) {
            tMin = tyMin;
        }
        if (tyMax < tMax) {
            tMax = tyMax;
        }
        bbox = ray.signDirZ ? cuboid.max : cuboid.min;
        double tzMin = (bbox.getZ() - ray.origin.getZ()) * ray.invDir.getZ();
        bbox = ray.signDirZ ? cuboid.min : cuboid.max;
        double tzMax = (bbox.getZ() - ray.origin.getZ()) * ray.invDir.getZ();

        //Check with the current tMin and tMax to see if the clipping is out of bounds
        if ((tMin > tzMax) || (tzMin > tMax)) {
            return Optional.empty();
        }

        //Reset tMin and tMax
        if (tzMin > tMin) {
            tMin = tzMin;
        }
        if (tzMax < tMax) {
            tMax = tzMax;
        }

        if ((tMin < maxDist) && (tMax > minDist)) {
            return Optional.of(ray.origin.add(ray.dir.scalarMultiply(tMin)));
        }

        return Optional.empty();
    }

    public static class RayTraceResult implements Comparable<RayTraceResult> {
        public final Vector3D hit;
        public final double distance;
        public final Direction side;
        public final Cuboid hitCuboid;

        public RayTraceResult(Vector3D hit, double distance, Direction side, Cuboid hitCuboid) {
            this.hit = hit;
            this.distance = distance;
            this.side = side;
            this.hitCuboid = hitCuboid;
        }

        @Override
        public int compareTo(RayTraceResult o) {
            return distance == o.distance ? 0 : distance < o.distance ? -1 : 1;
        }

    }

    public static class RayTraceBlockResult extends RayTraceResult {
        public final Block block;

        public RayTraceBlockResult(Vector3D hit, double distance, Direction side, Cuboid hitCuboid, Block block) {
            super(hit, distance, side, hitCuboid);
            this.block = block;
        }
    }

    public static class RayTraceEntityResult extends RayTraceResult {
        public final Entity entity;

        public RayTraceEntityResult(Vector3D hit, double distance, Direction side, Cuboid hitCuboid,
                Entity entity) {
            super(hit, distance, side, hitCuboid);
            this.entity = entity;
        }
    }
}