/*
* This file is part of the Illarion project.
*
* Copyright © 2015 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion 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.
*/
package org.illarion.engine.graphic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
/**
* A ray node is one node on the path of a light to a destination. It knows all its child nodes around.
*
* @author Nop
* @author Martin Karing <nitram@illarion.org>
*/
final class RayNode {
/**
* The error and debug logger of the client.
*/
@Nonnull
private static final Logger log = LoggerFactory.getLogger(RayNode.class);
/**
* The list of children this node has.
*/
@Nonnull
private final Collection<RayNode> children;
/**
* The light intensity value of this node.
*/
private final double intensity;
/**
* The level of the node, means how many steps the node is away from the center of the light. The root ray node
* has the level 0.
*/
private final int level;
/**
* The x coordinate of this node.
*/
private final int posX;
/**
* The y coordinate of this node.
*/
private final int posY;
/**
* Create a ray node. A node created with this constructor is placed at the origin of the light as the root node.
*
* @param size the length of the rays of the light this node is a part of, its used to calculate the intensity of
* the light
*/
public RayNode(float size) {
this(0, 0, 0, size);
}
/**
* Create a ray node at a location. The location is relative to the origin of the light.
*
* @param posX the relative x coordinate to the origin of the light
* @param posY the relative y coordinate to the origin of the light
* @param nodeLevel the level of the node, means how many steps the node is away from the origin of the light or
* how many nodes are in front of this ray node. The root ray node is level 0
* @param size the length of the rays of the light this node is a part of,
*/
RayNode(int posX, int posY, int nodeLevel, double size) {
level = nodeLevel;
this.posX = posX;
this.posY = posY;
float distance = (float) Math.sqrt((this.posX * this.posX) + (this.posY * this.posY));
intensity = 1.0 - (distance / (size + 0.5));
children = new ArrayList<>();
}
/**
* Create a ray to a existing ray. The path is generated by Bresenham with a defined length. In case the node
* added in this step exists already, the there is no node added in this step and if the node is still not created
* its created now. This function is called recursively up until the length of the path.
*
* @param xPath the array of relative x coordinates to the parent node
* @param yPath the array of relative y coordinates to the parent node
* @param len the length of the ray, means the coordinate values in the arrays for this ray
* @param index the current position on the ray
* @param size real length of the ray
*/
void addRay(@Nonnull int[] xPath, @Nonnull int[] yPath, int len, int index, double size) {
if (index < 0) {
throw new IllegalArgumentException("The index has to be positive.");
}
if (len < index) {
throw new IllegalArgumentException("The index has to be smaller then the length.");
}
if ((len > xPath.length) || (len > yPath.length)) {
throw new IllegalArgumentException("The length argument must not be larger then the length of the arrays.");
}
// there are more points on the path
if (index < len) {
int nx = xPath[index];
int ny = yPath[index];
// search matching point
RayNode next = null;
for (RayNode node : children) {
if (node == null) {
break;
}
if ((node.posX == nx) && (node.posY == ny)) {
next = node;
break;
}
}
// create it if not found
if (next == null) {
next = new RayNode(nx, ny, index, size);
// only inside falloff circle
if (next.intensity > 0) {
children.add(next);
}
}
// recursively add rest of path
next.addRay(xPath, yPath, len, index + 1, size);
}
}
/**
* Apply this ray node and all its children to a light map. The light intensity is set for the tile and the
* intensity is reduced by the light blocking level of the tile. In case the tile blocks the light entirely the
* ray stops at this location and the following ray nodes are not rendered applies anymore,
* that leads to the point that the light has no influence anymore.
*
* @param shadowMap the map that is the target of the apply operation and gives the data how much light is
* blocked out by the tiles
* @param globalIntensity global intensity modificator that reduces the default intensity of the light by the
* glowing intensity of the light in order to make the light generally weaker
*/
public void apply(@Nonnull LightSource shadowMap, float globalIntensity) {
int blocked = shadowMap.setIntensity(posX, posY, globalIntensity * intensity);
float newIntensity = globalIntensity;
// never block light source itself, remove when blocking is variable
if (level == 0) {
blocked = 0;
}
if (blocked < LightingMap.BLOCKED_VIEW) {
if (blocked > 0) {
newIntensity -= blocked / (float) LightingMap.BLOCKED_VIEW;
}
if (newIntensity > 0.05) {
for (RayNode node : children) {
if (node == null) {
break;
}
node.apply(shadowMap, newIntensity);
}
}
}
}
/**
* Create a string representation of this ray node.
*
* @return the string representation of this ray node
*/
@Nonnull
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < level; i++) {
builder.append("--");
}
builder.append('(');
builder.append(posX);
builder.append(',');
builder.append(posY);
builder.append(')');
return builder.toString();
}
}