/* * 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(); } }