use of com.ardor3d.scenegraph.Spatial in project energy3d by concord-consortium.
the class SolarRadiation method computeOnParabolicDish.
// Unlike PV solar panels, no indirect (ambient or diffuse) radiation should be included in reflection calculation. The mesh is a parabolic surface.
private void computeOnParabolicDish(final int minute, final ReadOnlyVector3 directionTowardSun, final ParabolicDish dish) {
final int n = Scene.getInstance().getParabolicDishN();
final Calendar calendar = Heliodon.getInstance().getCalendar();
calendar.set(Calendar.HOUR_OF_DAY, (int) ((double) minute / (double) SolarRadiation.MINUTES_OF_DAY * 24.0));
calendar.set(Calendar.MINUTE, minute % 60);
dish.draw();
final ReadOnlyVector3 normal = dish.getNormal();
if (normal == null) {
throw new RuntimeException("Normal is null");
}
// n*n*60: n*n is to get the unit cell area of the nxn grid; 60 is to convert the unit of timeStep from minute to kWh
final double a = 4 * dish.getRimRadius() * dish.getRimRadius() * Scene.getInstance().getTimeStep() / (n * n * 60.0);
final Mesh mesh = dish.getRadiationMesh();
MeshDataStore data = onMesh.get(mesh);
if (data == null) {
data = initMeshTextureDataOnRectangle(mesh, n, n);
}
final double dot = normal.dot(directionTowardSun);
double directRadiation = 0;
if (dot > 0) {
directRadiation += calculateDirectRadiation(directionTowardSun, normal);
}
final double radius = dish.getRimRadius() / Scene.getInstance().getAnnotationScale();
final double depth = dish.getRimRadius() * dish.getRimRadius() / (4 * dish.getFocalLength() * Scene.getInstance().getAnnotationScale());
// center of the rim circle
final Vector3 center = new Vector3(0, 0, depth);
final Vector3 q = center.clone();
final double spacing = 2 * radius / n;
// as the parabolic dish always faces the sun, we only have to deal with its aperture plane (rim circle)
final int iMinute = minute / Scene.getInstance().getTimeStep();
for (int x = 0; x < n; x++) {
for (int y = 0; y < n; y++) {
if (EnergyPanel.getInstance().isCancelled()) {
throw new CancellationException();
}
q.setX(spacing * (x + 0.5 * (1 - n)));
q.setY(spacing * (y + 0.5 * (1 - n)));
final ReadOnlyVector3 p = mesh.localToWorld(q, null);
final Ray3 pickRay = new Ray3(p, directionTowardSun);
if (dot > 0) {
final PickResults pickResults = new PrimitivePickResults();
for (final Spatial spatial : collidables) {
if (spatial != mesh) {
PickingUtil.findPick(spatial, pickRay, pickResults, false);
if (pickResults.getNumber() != 0) {
break;
}
}
}
if (pickResults.getNumber() == 0) {
// for heat map generation (for now, just use a square image for texture)
data.dailySolarIntensity[y][x] += directRadiation;
if (q.distanceSquared(center) < radius * radius) {
// sum all the solar energy up over all meshes and store in the foundation's solar potential array
dish.getSolarPotential()[iMinute] += directRadiation * a;
}
}
}
}
}
}
use of com.ardor3d.scenegraph.Spatial in project energy3d by concord-consortium.
the class SolarRadiation method computeToday.
private void computeToday() {
// save current calendar for restoring at the end of this calculation
final Calendar today = (Calendar) Heliodon.getInstance().getCalendar().clone();
hourOfDay = today.get(Calendar.HOUR_OF_DAY);
minuteOfHour = today.get(Calendar.MINUTE);
today.set(Calendar.SECOND, 0);
today.set(Calendar.MINUTE, 0);
today.set(Calendar.HOUR_OF_DAY, 0);
final String city = (String) EnergyPanel.getInstance().getCityComboBox().getSelectedItem();
dailyAirTemperatures = Weather.computeOutsideTemperature(today, city);
final int timeStep = Scene.getInstance().getTimeStep();
final ReadOnlyVector3[] sunLocations = new ReadOnlyVector3[SolarRadiation.MINUTES_OF_DAY / timeStep];
int totalSteps = 0;
for (int minute = 0; minute < SolarRadiation.MINUTES_OF_DAY; minute += timeStep) {
final ReadOnlyVector3 sunLocation = Heliodon.getInstance().computeSunLocation(today).normalizeLocal();
sunLocations[minute / timeStep] = sunLocation;
if (sunLocation.getZ() > 0) {
totalSteps++;
}
today.add(Calendar.MINUTE, timeStep);
}
totalSteps -= 2;
final double dayLength = totalSteps * timeStep / 60.0;
int step = 1;
setupImportedMeshes();
// for (int minute = MINUTES_OF_DAY / 2; minute < MINUTES_OF_DAY / 2 + timeStep; minute += timeStep) { // test for 12 pm for comparison with shadow
for (int minute = 0; minute < MINUTES_OF_DAY; minute += timeStep) {
final ReadOnlyVector3 sunLocation = sunLocations[minute / timeStep];
if (sunLocation.getZ() > 0) {
final ReadOnlyVector3 directionTowardSun = sunLocation.normalize(null);
calculatePeakRadiation(directionTowardSun, dayLength);
for (final HousePart part : Scene.getInstance().getParts()) {
if (part.isDrawCompleted()) {
if (part instanceof Window) {
computeOnMesh(minute, directionTowardSun, part, part.getRadiationMesh(), (Mesh) part.getRadiationCollisionSpatial(), part.getNormal());
} else if (part instanceof Wall) {
if (((Wall) part).getType() == Wall.SOLID_WALL) {
computeOnMesh(minute, directionTowardSun, part, part.getRadiationMesh(), (Mesh) part.getRadiationCollisionSpatial(), part.getNormal());
}
} else if (part instanceof Door || part instanceof Floor) {
computeOnMesh(minute, directionTowardSun, part, part.getRadiationMesh(), (Mesh) part.getRadiationCollisionSpatial(), part.getNormal());
} else if (part instanceof Foundation) {
final Foundation foundation = (Foundation) part;
for (int i = 0; i < 5; i++) {
final Mesh radiationMesh = foundation.getRadiationMesh(i);
final ReadOnlyVector3 normal = i == 0 ? part.getNormal() : ((UserData) radiationMesh.getUserData()).getNormal();
computeOnMesh(minute, directionTowardSun, part, radiationMesh, foundation.getRadiationCollisionSpatial(i), normal);
}
if (!Scene.getInstance().getOnlySolarComponentsInSolarMap()) {
final List<Node> importedNodes = foundation.getImportedNodes();
if (importedNodes != null) {
for (final Node node : importedNodes) {
for (final Spatial s : node.getChildren()) {
final Mesh m = (Mesh) s;
computeOnImportedMesh(minute, directionTowardSun, foundation, m);
}
}
}
}
} else if (part instanceof Roof) {
for (final Spatial roofPart : ((Roof) part).getRoofPartsRoot().getChildren()) {
if (roofPart.getSceneHints().getCullHint() != CullHint.Always) {
final ReadOnlyVector3 faceDirection = (ReadOnlyVector3) roofPart.getUserData();
final Mesh mesh = (Mesh) ((Node) roofPart).getChild(6);
computeOnMesh(minute, directionTowardSun, part, mesh, mesh, faceDirection);
}
}
} else if (part instanceof SolarPanel) {
computeOnSolarPanel(minute, directionTowardSun, (SolarPanel) part);
} else if (part instanceof Rack) {
computeOnRack(minute, directionTowardSun, (Rack) part);
} else if (part instanceof Mirror) {
computeOnMirror(minute, directionTowardSun, (Mirror) part);
} else if (part instanceof FresnelReflector) {
computeOnFresnelReflector(minute, directionTowardSun, (FresnelReflector) part);
} else if (part instanceof ParabolicTrough) {
computeOnParabolicTrough(minute, directionTowardSun, (ParabolicTrough) part);
} else if (part instanceof ParabolicDish) {
computeOnParabolicDish(minute, directionTowardSun, (ParabolicDish) part);
} else if (part instanceof Sensor) {
computeOnSensor(minute, directionTowardSun, (Sensor) part);
}
}
}
computeOnLand(directionTowardSun);
EnergyPanel.getInstance().progress((int) Math.round(100.0 * step / totalSteps));
step++;
}
}
maxValue = Math.round((MINUTES_OF_DAY / timeStep + 1.0) * (1 - 0.01 * Scene.getInstance().getSolarHeatMapColorContrast()));
// If tracking the sun, the heliodon's calendar has been changed. Restore the time now.
resetTrackables();
}
use of com.ardor3d.scenegraph.Spatial in project energy3d by concord-consortium.
the class SolarRadiation method computeOnParabolicTrough.
// Unlike PV solar panels, no indirect (ambient or diffuse) radiation should be included in reflection calculation. The mesh is a parabolic surface.
private void computeOnParabolicTrough(final int minute, final ReadOnlyVector3 directionTowardSun, final ParabolicTrough trough) {
final int nAxis = trough.getNSectionAxis();
final int nPara = trough.getNSectionParabola();
final Calendar calendar = Heliodon.getInstance().getCalendar();
calendar.set(Calendar.HOUR_OF_DAY, (int) ((double) minute / (double) SolarRadiation.MINUTES_OF_DAY * 24.0));
calendar.set(Calendar.MINUTE, minute % 60);
trough.draw();
final ReadOnlyVector3 normal = trough.getNormal();
if (normal == null) {
throw new RuntimeException("Normal is null");
}
// nx*ny*60: nx*ny is to get the unit cell area of the nx*ny grid; 60 is to convert the unit of timeStep from minute to kWh
final double a = trough.getApertureWidth() * trough.getTroughLength() * Scene.getInstance().getTimeStep() / (nAxis * nPara * 60.0);
final Mesh mesh = trough.getRadiationMesh();
MeshDataStore data = onMesh.get(mesh);
if (data == null) {
// axis is row and parabola is column
data = initMeshTextureDataOnRectangle(mesh, nAxis, nPara);
}
final double dot = normal.dot(directionTowardSun);
double directRadiation = 0;
if (dot > 0) {
directRadiation += calculateDirectRadiation(directionTowardSun, normal);
}
final FloatBuffer vertexBuffer = mesh.getMeshData().getVertexBuffer();
// number of vertex coordinates on each end
final int j = vertexBuffer.limit() / 2;
// (0, 0)
final Vector3 p0 = new Vector3(vertexBuffer.get(0), vertexBuffer.get(1), vertexBuffer.get(2));
// (1, 0)
final Vector3 p1 = new Vector3(vertexBuffer.get(j - 3), vertexBuffer.get(j - 2), vertexBuffer.get(j - 1));
// (0, 1)
final Vector3 p2 = new Vector3(vertexBuffer.get(j), vertexBuffer.get(j + 1), vertexBuffer.get(j + 2));
// final Vector3 q0 = mesh.localToWorld(p0, null);
// final Vector3 q1 = mesh.localToWorld(p1, null);
// final Vector3 q2 = mesh.localToWorld(p2, null);
// System.out.println("***" + q0.distance(q1) * Scene.getInstance().getAnnotationScale() + "," + q0.distance(q2) * Scene.getInstance().getAnnotationScale());
// this is perpendicular to the direction of the cylinder axis (nPara)
final Vector3 u = p1.subtract(p0, null).normalizeLocal();
// this is parallel to the direction of the cylinder axis (nAxis)
final Vector3 v = p2.subtract(p0, null).normalizeLocal();
final double xSpacing = p1.distance(p0) / nPara;
final double ySpacing = p2.distance(p0) / nAxis;
// as the parabolic trough always faces the sun, we only have to deal with its aperture plane
final int iMinute = minute / Scene.getInstance().getTimeStep();
for (int x = 0; x < nPara; x++) {
for (int y = 0; y < nAxis; y++) {
if (EnergyPanel.getInstance().isCancelled()) {
throw new CancellationException();
}
final Vector3 u2 = u.multiply(xSpacing * (x + 0.5), null);
final Vector3 v2 = v.multiply(ySpacing * (y + 0.5), null);
// on the aperture plane of the parabolic trough
final Vector3 q = p0.add(v2, null).addLocal(u2);
final ReadOnlyVector3 p = mesh.localToWorld(q, null);
final Ray3 pickRay = new Ray3(p, directionTowardSun);
if (dot > 0) {
final PickResults pickResults = new PrimitivePickResults();
for (final Spatial spatial : collidables) {
if (spatial != mesh) {
PickingUtil.findPick(spatial, pickRay, pickResults, false);
if (pickResults.getNumber() != 0) {
break;
}
}
}
if (pickResults.getNumber() == 0) {
// for heat map generation
data.dailySolarIntensity[y][x] += directRadiation;
// sum all the solar energy up over all meshes and store in the foundation's solar potential array
trough.getSolarPotential()[iMinute] += directRadiation * a;
}
}
}
}
}
use of com.ardor3d.scenegraph.Spatial in project energy3d by concord-consortium.
the class SolarRadiation method updateTextures.
public void updateTextures() {
EnergyPanel.getInstance().setComputingStartMillis(System.currentTimeMillis());
maxValue = Math.round((MINUTES_OF_DAY / Scene.getInstance().getTimeStep() + 1.0) * (1 - 0.01 * Scene.getInstance().getSolarHeatMapColorContrast()));
applyTexture(SceneManager.getInstance().getSolarLand());
final int totalMeshes = Scene.getInstance().getParts().size() + Scene.getInstance().countMeshes();
int countMesh = 0;
for (final HousePart part : Scene.getInstance().getParts()) {
if (part instanceof SolarCollector) {
applyTexture(part.getRadiationMesh());
} else {
if (!Scene.getInstance().getOnlySolarComponentsInSolarMap()) {
if (part instanceof Wall || part instanceof Door || part instanceof Floor) {
applyTexture(part.getRadiationMesh());
} else if (part instanceof Foundation) {
final Foundation foundation = (Foundation) part;
for (int i = 0; i < 5; i++) {
applyTexture(foundation.getRadiationMesh(i));
}
final List<Node> importedNodes = foundation.getImportedNodes();
if (importedNodes != null) {
for (final Node node : importedNodes) {
for (final Spatial s : node.getChildren()) {
applyTexture((Mesh) s);
EnergyPanel.getInstance().progress((int) Math.round(100.0 * (countMesh++) / totalMeshes));
}
}
}
} else if (part instanceof Roof) {
for (final Spatial roofPart : ((Roof) part).getRoofPartsRoot().getChildren()) {
if (roofPart.getSceneHints().getCullHint() != CullHint.Always) {
final Mesh mesh = (Mesh) ((Node) roofPart).getChild(6);
applyTexture(mesh);
}
}
}
}
}
countMesh++;
}
EnergyPanel.getInstance().progress(0);
}
use of com.ardor3d.scenegraph.Spatial in project energy3d by concord-consortium.
the class SolarRadiation method computeOnMesh.
// Formula from http://en.wikipedia.org/wiki/Air_mass_(solar_energy)#Solar_intensity
private void computeOnMesh(final int minute, final ReadOnlyVector3 directionTowardSun, final HousePart housePart, final Mesh drawMesh, final Mesh collisionMesh, final ReadOnlyVector3 normal) {
if (normal == null) {
// FIXME: normal can be null sometimes, fix it
return;
}
if (Scene.getInstance().getOnlySolarComponentsInSolarMap()) {
return;
}
MeshDataStore data = onMesh.get(drawMesh);
if (data == null) {
data = initMeshTextureData(drawMesh, collisionMesh, normal, !(housePart instanceof Window));
}
/* needed in order to prevent picking collision with neighboring wall at wall edge (seem 0.1 is too small, 0.5 is about right) */
final ReadOnlyVector3 offset = directionTowardSun.multiply(0.5, null);
final double dot = normal.dot(directionTowardSun);
final double directRadiation = dot > 0 ? calculateDirectRadiation(directionTowardSun, normal) : 0;
final double indirectRadiation = calculateDiffuseAndReflectedRadiation(directionTowardSun, normal);
final int timeStep = Scene.getInstance().getTimeStep();
final double solarStep = Scene.getInstance().getSolarStep();
final double annotationScale = Scene.getInstance().getAnnotationScale();
final double scaleFactor = annotationScale * annotationScale / 60 * timeStep;
// a window itself doesn't really absorb solar energy, but it passes the energy into the house to be absorbed
final float absorption = housePart instanceof Window ? 1 : 1 - housePart.getAlbedo();
if (housePart instanceof Roof) {
// for now, only store this for roofs that have different meshes
if (data.solarPotential == null) {
data.solarPotential = new double[MINUTES_OF_DAY / timeStep];
}
if (data.heatLoss == null) {
data.heatLoss = new double[MINUTES_OF_DAY / timeStep];
}
}
for (int col = 0; col < data.cols; col++) {
final double w = col == data.cols - 1 ? data.p2.distance(data.u.multiply(col * solarStep, null).addLocal(data.p0)) : solarStep;
final ReadOnlyVector3 pU = data.u.multiply(col * solarStep + 0.5 * w, null).addLocal(data.p0);
for (int row = 0; row < data.rows; row++) {
if (EnergyPanel.getInstance().isCancelled()) {
throw new CancellationException();
}
if (data.dailySolarIntensity[row][col] == -1) {
continue;
}
final double h = row == data.rows - 1 ? data.p1.distance(data.p0) - row * solarStep : solarStep;
final ReadOnlyVector3 p = data.v.multiply(row * solarStep + 0.5 * h, null).addLocal(pU).add(offset, null);
final Ray3 pickRay = new Ray3(p, directionTowardSun);
final PickResults pickResults = new PrimitivePickResults();
// assuming that indirect (ambient or diffuse) radiation can always reach a grid point
double radiation = indirectRadiation;
final double scaledArea = w * h * scaleFactor;
if (dot > 0) {
for (final Spatial spatial : collidables) {
if (EnergyPanel.getInstance().isCancelled()) {
throw new CancellationException();
}
if (spatial != collisionMesh) {
PickingUtil.findPick(spatial, pickRay, pickResults, false);
if (pickResults.getNumber() != 0) {
if (housePart instanceof Foundation) {
// at this point, we only show radiation heat map on the first floor
final HousePart collidableOwner = collidablesToParts.get(spatial);
if (collidableOwner instanceof Window) {
radiation += directRadiation * ((Window) collidableOwner).getSolarHeatGainCoefficient();
}
}
break;
}
}
}
if (pickResults.getNumber() == 0) {
radiation += directRadiation;
}
}
data.dailySolarIntensity[row][col] += Scene.getInstance().getOnlyAbsorptionInSolarMap() ? absorption * radiation : radiation;
if (data.solarPotential != null) {
data.solarPotential[minute / timeStep] += radiation * scaledArea;
}
if (!(housePart instanceof Foundation)) {
// exclude radiation on foundation
housePart.getSolarPotential()[minute / timeStep] += radiation * scaledArea;
}
}
}
}
Aggregations