import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.util.Pair;
import processing.core.PApplet;
import processing.core.PVector;
import saito.objloader.Face;
import saito.objloader.OBJModel;
import saito.objloader.Segment;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.Random;
import java.util.TreeMap;
public class Model<T extends Double> extends OBJModel {
// should be done somewhere else
public static double speedOfSound = 343.2; // could be altered?
public static double epsilon = 1.0 / 0.0037;// should be frequency dependent
public static double airAbsoption(double distance, double sample) {
//System.out.println("sdfsdf "+(float)(Math.exp(-distance/epsilon))+" s "+sample);
//double ret = (((Double) sample) * Math.exp((-1.0) * distance / epsilon));
double ret = (((Double) sample) * Math.exp((-1.0) * distance / epsilon));
//float ret = 0;
//ret = 0.3f* sample*(float)(1.0/distance);
return ret;
//return (float)(decibel(sample)-20.0*Math.log(distance));
//return sample*(float)(1.0/distance);
}
private final static double minDB = 1.e-10;
public static double decibel(double a) {
double y;
double abs_a = Math.abs(a);
if (abs_a < minDB) {
y = minDB;
} else {
y = abs_a;
}
return 20 * Math.log(abs_a) / Math.log(10);
}
private static float[] azimuthAndElevation(PVector p1, PVector p2) {
PVector diff = PVector.sub(p2, p1);
//PVector ret = cartesianToPolar(diff);
float elev = PApplet.atan2(diff.y, diff.x);
float azimuth = PApplet.atan2(diff.z, diff.x);
return new float[]{azimuth, elev};
}
// x = len; y = angleY -> elevation, z = angleZ -> azimuth
public static PVector cartesianToPolar(PVector theVector) {
PVector res = new PVector();
res.x = theVector.mag();
if (res.x > 0) {
res.y = -PApplet.atan2(theVector.z, theVector.x);
res.z = PApplet.asin(theVector.y / res.x);
} else {
res.y = 0f;
res.z = 0f;
}
return res;
}
/////
public class GraphModelData<X extends Double> {
public LinkedList<Node> listenerNodes;
public LinkedList<Node> sourceNodes;
public LinkedList<Double> azimuth;
public LinkedList<Double> elevation;
public DirectedSparseGraph<Node, Edge<X>> graph;
public GraphModelData(DirectedSparseGraph<Node, Edge<X>> g, LinkedList<Node> s, LinkedList<Node> l) {
this.listenerNodes = l;
this.sourceNodes = s;
this.graph = g;
}
}
private LinkedList<Face> faceList = new LinkedList<Face>();
private PVector minBounds;
private PVector maxBounds;
private double size;
//private DirectedSparseGraph<Node, Edge<T>> graph = new DirectedSparseGraph<Node, Edge<T>>();
private DirectedSparseGraph<Node, Edge<T>> graph = new DirectedSparseGraph<Node, Edge<T>>();
/**
*
* Model variables set by the RendererConfig-File
*
*
**/
private int numVolNodes = HypertronVelodyne.getRendererConfig().getNumVolNodes();
private int numWallNodes = HypertronVelodyne.getRendererConfig().getNumWallNodes();
private int numWallNodesRandom = HypertronVelodyne.getRendererConfig().getNumWallNodesRandom();
private double stretchFactor = HypertronVelodyne.getRendererConfig().getStretchFactor();
private int numSourceNodes = HypertronVelodyne.getSourceListenerConfig().getNumSources();
private int numListenerNodes = HypertronVelodyne.getSourceListenerConfig().getNumListeners();
private LinkedList<Node> listenerNodes = new LinkedList<Node>();
private LinkedList<Node> sourceNodes = new LinkedList<Node>();
public Model(PApplet parent, String fileName, String texturePathMode, int shapeMode) {
super(parent, fileName, texturePathMode, shapeMode);
faceList = new LinkedList<Face>();
float xmin = Float.MAX_VALUE;
float ymin = Float.MAX_VALUE;
float zmin = Float.MAX_VALUE;
float xmax = Float.MIN_VALUE;
float ymax = Float.MIN_VALUE;
float zmax = Float.MIN_VALUE;
for (int j = 0; j < this.getSegmentCount(); j++) {
Segment segment = this.getSegment(j);
Face[] faces = segment.getFaces();
for (Face f : faces) {
faceList.add(f);
// bounds
PVector center = f.getCenter();
if (center.x < xmin) {
xmin = center.x;
}
if (center.y < ymin) {
ymin = center.y;
}
if (center.z < zmin) {
zmin = center.z;
}
if (center.x >= xmax) {
xmax = center.x;
}
if (center.y >= ymax) {
ymax = center.y;
}
if (center.z >= zmax) {
zmax = center.z;
}
}
minBounds = new PVector(xmin, ymin, zmin);
maxBounds = new PVector(xmax, ymax, zmax);
size = Math.abs(minBounds.dist(maxBounds));
}
System.out.println("Bounds: min " + minBounds + " " + maxBounds);
this.createGraph();
}
public DirectedSparseGraph<Node, Edge<T>> getGraph() {
return this.graph;
}
public GraphModelData<T> getModelData() {
return new GraphModelData<T>(this.graph, this.sourceNodes, this.listenerNodes);
}
private void createGraph() {
System.out.println("Creating mesh with " + numWallNodes + " wall Nodes and " + numVolNodes + " volume Nodes");
createListenerAndSources();
createVolumeMesh();
createWallToVolumeScoreMap();
createWallMesh(numWallNodes);
createWallMeshRandomized(2*numWallNodes);
/*for (Node w1 : this.getGraph().getVertices()) {
if (w1.type == Node.NodeType.wall) {
for (Node w2 : this.getGraph().getVertices()) {
if (w2.type == Node.NodeType.wall && w1 != w2) {
double dist = w2.pos.dist(w2.pos);
this.getGraph().addEdge(new Edge(dist), w1, w2);
this.getGraph().addEdge(new Edge(dist), w2, w1);
}
}
}
} */
// set pos & buffers for rendering
for (Node n : this.listenerNodes) {
LinkedList<float[]> azAndEl = new LinkedList<float[]>();
Collection<Edge<T>> inEdges = this.graph.getInEdges(n);
for (Edge e : inEdges) {
Node dst = n;
Node src = this.graph.getSource(e);
azAndEl.add(azimuthAndElevation(dst.pos, src.pos));
}
n.createInputChannels(inEdges.size());
n.azimuthAndElevation = azAndEl;
}
double delayLineLength = 0.0;
for (Edge e : graph.getEdges()) {
delayLineLength = delayLineLength + e.dist;
}
for (Node n: this.graph.getVertices()) {
Collection<Edge<T>> ins = this.graph.getInEdges(n);
Collection<Edge<T>> outs = this.graph.getOutEdges(n);
if(ins.size() >= 1 && outs.size() >= 1) {
for(Edge<T> eout: outs) {
float[] gains = new float[ins.size()];
float gsum = 0.0f;
Node outSrcNode = n;
Node outDstNode = this.graph.getDest(eout);
PVector outDir = PVector.sub(outSrcNode.pos, outDstNode.pos);
int inEdgeCntr = 0;
for(Edge<T> ein: ins) {
Node inSrcNode = this.graph.getSource(ein);
Node inDstNode = this.graph.getDest(ein);
PVector inDir = PVector.sub(inSrcNode.pos, inDstNode.pos);
//cosine similarity
float g = (PVector.dot(outDir, inDir))/ (outDir.mag()*inDir.mag());
g = (g +1)/2;
//System.err.println(g);
gains[inEdgeCntr] = g;
gsum = gsum + g;
inEdgeCntr++;
}
for(int j=0; j < gains.length; j++) {
gains[j] = 5*gains[j]/gsum;
}
n.edgeGainMap.put((Object) eout, gains);
}
}
}
System.out.println("Overall delay line length: " + delayLineLength + " = " + (delayLineLength / this.speedOfSound) + " seconds\nNum. of Lines: " + graph.getEdgeCount());
//System.out.println(graph);
}
private void createListenerAndSources() {
Listener[] listeners = HypertronVelodyne.getSourceListenerConfig().getListeners();
Source[] sources = HypertronVelodyne.getSourceListenerConfig().getSources();
for(Listener l: listeners) {
PVector position = new PVector(l.x, l.y, l.z);
Node listener = new Node(position, Node.NodeType.listener);
listener.name = l.name;
graph.addVertex(listener);
listenerNodes.add(listener);
}
for(Source s: sources) {
PVector position = new PVector(s.x, s.y, s.z);
Node source = new Node(position, Node.NodeType.source);
source.name = s.name;
//source.wavFileReader = new WavFileReader(s.fileName);
graph.addVertex(source);
listenerNodes.add(source);
}
}
private void createVolumeMesh() {
for (int i = 0; i < numVolNodes; i++) {
graph.addVertex(new Node(this.getRandomPointInVolume(), Node.NodeType.volume));
}
double dist;
// fully connected graph for the volume mesh, snd-source is feedforward, listner is feedback
// not correct since listener->source is also possible?
for (Node src : graph.getVertices()) {
for (Node dst : graph.getVertices()) {
if (src != dst) {
dist = distanceFromVectors(src.pos,dst.pos);
if (src.type == Node.NodeType.source) {
//if(dst.type == Node.NodeType.listener) continue;
graph.addEdge(new Edge(dist), src, dst);
} else if (src.type == Node.NodeType.listener) {
graph.addEdge(new Edge(dist), dst, src);
} else {
// feedforward
Edge e = new Edge(dist);
e.feedforward = true;
graph.addEdge(e, src, dst);
//TODO fixme???
// feedback
/*e = new Edge(dist);
e.feedforward = false;
graph.addEdge(e, dst, src);*/
}
}
}
}
}
// Use something serious here...
// and take care of similar scores...
TreeMap<Double, Pair<Node>> wallToVolumeMap = new TreeMap<Double, Pair<Node>>();
private void createWallToVolumeScoreMap() {
for (Node volNode : graph.getVertices()) {
if (volNode.type != Node.NodeType.wall) {
for (Face face : faceList) {
// not correct, should overwrite only if score for specific node is better
wallToVolumeMap.put(directionalDistanceScore(face, volNode.pos),
new Pair(new Node(face.getCenter(), Node.NodeType.wall), volNode));
}
}
}
}
private double distanceFromVectors(PVector a, PVector b) {
return (double) Math.abs(PVector.dist(a,b)) * stretchFactor;
}
private void createWallMesh(int numFaces) {
// use k-best faces
int cntrAddedFaces = 0;
double distance = 0;
for (Entry<Double, Pair<Node>> e : wallToVolumeMap.entrySet()) {
if (numFaces == cntrAddedFaces) {
break;
}
Pair<Node> nodes = e.getValue();
Node wall = nodes.getFirst();
graph.addVertex(wall);
Node vol = nodes.getSecond();
//graph.addVertex(vol);
System.err.println("www "+graph.containsVertex(vol)+ " --- wall "+graph.containsVertex(wall));
distance = distanceFromVectors(wall.pos, vol.pos);
System.err.println("wall node"+distance+" ");
//graph.addEdge(new Edge(distance), wall, vol);
graph.addEdge(new Edge(distance), vol, wall);
for(Node nv: graph.getVertices()) {
if(nv != wall && nv != vol && nv.type != Node.NodeType.wall && nv.type != Node.NodeType.source) {
double nvd = distanceFromVectors(wall.pos, nv.pos);
graph.addEdge(new Edge(nvd), wall, nv);
}
}
cntrAddedFaces++;
}
}
private void createWallMeshRandomized(int numFaces) {
int nodeCounter = 0;
while(nodeCounter != numFaces) {
int index = (int)Math.floor(random(0, numFaces));
if(index < 0 || index >= faceList.size()) {
continue;
}
Face face = faceList.get(index);
//TODO: possible bug, lookup if wall node already exists
PVector center = face.getCenter();
Node wall = null;
for (Node n: graph.getVertices()) {
if(n.pos.x == center.x && n.pos.y == center.y && n.pos.z == center.z) {
wall = n;
}
}
if(wall == null) {
wall = new Node(face.getCenter(), Node.NodeType.wall);
graph.addVertex(wall);
}
Collection<Node> c = graph.getVertices();
for(Node nv: c) {
if(nv != wall && nv.type != Node.NodeType.wall && nv.type != Node.NodeType.source) {
double nvd = distanceFromVectors(wall.pos, nv.pos);
graph.addEdge(new Edge(nvd), wall, nv);
}
}
nodeCounter++;
}
}
private double directionalDistanceScore(Face f, PVector pos) {
// score = normalized distance + inv cos alpha
PVector fc = f.getCenter();
double distance = Math.abs(fc.dist(pos)) / this.size;
PVector direction = fc.sub(fc, pos);
PVector fNorm = f.getNormal().get();
fNorm.normalize();
direction.normalize();
double invCosA = 1.0 - fNorm.dot(direction);
return distance + invCosA;
}
public LinkedList<Face> getFaceList() {
return this.faceList;
}
private Random rndm = new Random();
private float random(float lo, float hi) {
float dist = Math.abs(hi - lo);
return (dist * rndm.nextFloat()) + lo;
}
public PVector getRandomPointInsideBounds() {
float xrnd = random(minBounds.x, maxBounds.x);
float yrnd = random(minBounds.y, maxBounds.y);
float zrnd = random(minBounds.z, maxBounds.z);
return new PVector(xrnd, yrnd, zrnd);
}
public PVector getRandomPointInVolume() {
boolean insideVolume = false;
PVector pos = null;
while (!insideVolume) {
insideVolume = true;
pos = this.getRandomPointInsideBounds();
//System.out.println(pos);
for (Face f : faceList) {
if (!f.isFacingPosition(pos)) {
insideVolume = false;
}
}
}
return pos;
}
}