001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. 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 package org.apache.commons.math3.analysis.interpolation; 018 019 import java.util.ArrayList; 020 import java.util.HashMap; 021 import java.util.List; 022 import java.util.Map; 023 024 import org.apache.commons.math3.analysis.MultivariateFunction; 025 import org.apache.commons.math3.exception.DimensionMismatchException; 026 import org.apache.commons.math3.exception.NoDataException; 027 import org.apache.commons.math3.exception.NullArgumentException; 028 import org.apache.commons.math3.linear.ArrayRealVector; 029 import org.apache.commons.math3.linear.RealVector; 030 import org.apache.commons.math3.random.UnitSphereRandomVectorGenerator; 031 import org.apache.commons.math3.util.FastMath; 032 033 /** 034 * Interpolating function that implements the 035 * <a href="http://www.dudziak.com/microsphere.php">Microsphere Projection</a>. 036 * 037 * @version $Id: MicrosphereInterpolatingFunction.java 1379904 2012-09-01 23:54:52Z erans $ 038 */ 039 public class MicrosphereInterpolatingFunction 040 implements MultivariateFunction { 041 /** 042 * Space dimension. 043 */ 044 private final int dimension; 045 /** 046 * Internal accounting data for the interpolation algorithm. 047 * Each element of the list corresponds to one surface element of 048 * the microsphere. 049 */ 050 private final List<MicrosphereSurfaceElement> microsphere; 051 /** 052 * Exponent used in the power law that computes the weights of the 053 * sample data. 054 */ 055 private final double brightnessExponent; 056 /** 057 * Sample data. 058 */ 059 private final Map<RealVector, Double> samples; 060 061 /** 062 * Class for storing the accounting data needed to perform the 063 * microsphere projection. 064 */ 065 private static class MicrosphereSurfaceElement { 066 /** Normal vector characterizing a surface element. */ 067 private final RealVector normal; 068 /** Illumination received from the brightest sample. */ 069 private double brightestIllumination; 070 /** Brightest sample. */ 071 private Map.Entry<RealVector, Double> brightestSample; 072 073 /** 074 * @param n Normal vector characterizing a surface element 075 * of the microsphere. 076 */ 077 MicrosphereSurfaceElement(double[] n) { 078 normal = new ArrayRealVector(n); 079 } 080 081 /** 082 * Return the normal vector. 083 * @return the normal vector 084 */ 085 RealVector normal() { 086 return normal; 087 } 088 089 /** 090 * Reset "illumination" and "sampleIndex". 091 */ 092 void reset() { 093 brightestIllumination = 0; 094 brightestSample = null; 095 } 096 097 /** 098 * Store the illumination and index of the brightest sample. 099 * @param illuminationFromSample illumination received from sample 100 * @param sample current sample illuminating the element 101 */ 102 void store(final double illuminationFromSample, 103 final Map.Entry<RealVector, Double> sample) { 104 if (illuminationFromSample > this.brightestIllumination) { 105 this.brightestIllumination = illuminationFromSample; 106 this.brightestSample = sample; 107 } 108 } 109 110 /** 111 * Get the illumination of the element. 112 * @return the illumination. 113 */ 114 double illumination() { 115 return brightestIllumination; 116 } 117 118 /** 119 * Get the sample illuminating the element the most. 120 * @return the sample. 121 */ 122 Map.Entry<RealVector, Double> sample() { 123 return brightestSample; 124 } 125 } 126 127 /** 128 * @param xval Arguments for the interpolation points. 129 * {@code xval[i][0]} is the first component of interpolation point 130 * {@code i}, {@code xval[i][1]} is the second component, and so on 131 * until {@code xval[i][d-1]}, the last component of that interpolation 132 * point (where {@code dimension} is thus the dimension of the sampled 133 * space). 134 * @param yval Values for the interpolation points. 135 * @param brightnessExponent Brightness dimming factor. 136 * @param microsphereElements Number of surface elements of the 137 * microsphere. 138 * @param rand Unit vector generator for creating the microsphere. 139 * @throws DimensionMismatchException if the lengths of {@code yval} and 140 * {@code xval} (equal to {@code n}, the number of interpolation points) 141 * do not match, or the the arrays {@code xval[0]} ... {@code xval[n]}, 142 * have lengths different from {@code dimension}. 143 * @throws NoDataException if there an array has zero-length. 144 * @throws NullArgumentException if an argument is {@code null}. 145 */ 146 public MicrosphereInterpolatingFunction(double[][] xval, 147 double[] yval, 148 int brightnessExponent, 149 int microsphereElements, 150 UnitSphereRandomVectorGenerator rand) 151 throws DimensionMismatchException, 152 NoDataException, 153 NullArgumentException { 154 if (xval == null || 155 yval == null) { 156 throw new NullArgumentException(); 157 } 158 if (xval.length == 0) { 159 throw new NoDataException(); 160 } 161 if (xval.length != yval.length) { 162 throw new DimensionMismatchException(xval.length, yval.length); 163 } 164 if (xval[0] == null) { 165 throw new NullArgumentException(); 166 } 167 168 dimension = xval[0].length; 169 this.brightnessExponent = brightnessExponent; 170 171 // Copy data samples. 172 samples = new HashMap<RealVector, Double>(yval.length); 173 for (int i = 0; i < xval.length; ++i) { 174 final double[] xvalI = xval[i]; 175 if (xvalI == null) { 176 throw new NullArgumentException(); 177 } 178 if (xvalI.length != dimension) { 179 throw new DimensionMismatchException(xvalI.length, dimension); 180 } 181 182 samples.put(new ArrayRealVector(xvalI), yval[i]); 183 } 184 185 microsphere = new ArrayList<MicrosphereSurfaceElement>(microsphereElements); 186 // Generate the microsphere, assuming that a fairly large number of 187 // randomly generated normals will represent a sphere. 188 for (int i = 0; i < microsphereElements; i++) { 189 microsphere.add(new MicrosphereSurfaceElement(rand.nextVector())); 190 } 191 } 192 193 /** 194 * @param point Interpolation point. 195 * @return the interpolated value. 196 */ 197 public double value(double[] point) { 198 final RealVector p = new ArrayRealVector(point); 199 200 // Reset. 201 for (MicrosphereSurfaceElement md : microsphere) { 202 md.reset(); 203 } 204 205 // Compute contribution of each sample points to the microsphere elements illumination 206 for (Map.Entry<RealVector, Double> sd : samples.entrySet()) { 207 208 // Vector between interpolation point and current sample point. 209 final RealVector diff = sd.getKey().subtract(p); 210 final double diffNorm = diff.getNorm(); 211 212 if (FastMath.abs(diffNorm) < FastMath.ulp(1d)) { 213 // No need to interpolate, as the interpolation point is 214 // actually (very close to) one of the sampled points. 215 return sd.getValue(); 216 } 217 218 for (MicrosphereSurfaceElement md : microsphere) { 219 final double w = FastMath.pow(diffNorm, -brightnessExponent); 220 md.store(cosAngle(diff, md.normal()) * w, sd); 221 } 222 223 } 224 225 // Interpolation calculation. 226 double value = 0; 227 double totalWeight = 0; 228 for (MicrosphereSurfaceElement md : microsphere) { 229 final double iV = md.illumination(); 230 final Map.Entry<RealVector, Double> sd = md.sample(); 231 if (sd != null) { 232 value += iV * sd.getValue(); 233 totalWeight += iV; 234 } 235 } 236 237 return value / totalWeight; 238 } 239 240 /** 241 * Compute the cosine of the angle between 2 vectors. 242 * 243 * @param v Vector. 244 * @param w Vector. 245 * @return the cosine of the angle between {@code v} and {@code w}. 246 */ 247 private double cosAngle(final RealVector v, final RealVector w) { 248 return v.dotProduct(w) / (v.getNorm() * w.getNorm()); 249 } 250 }