use of com.ardor3d.scenegraph.Spatial in project energy3d by concord-consortium.
the class SolarRadiation method computeOnLand.
private void computeOnLand(final ReadOnlyVector3 directionTowardSun) {
final double indirectRadiation = calculateDiffuseAndReflectedRadiation(directionTowardSun, Vector3.UNIT_Z);
final double totalRadiation = calculateDirectRadiation(directionTowardSun, Vector3.UNIT_Z) + indirectRadiation;
final double step = Scene.getInstance().getSolarStep() * 4;
final int rows = (int) (256 / step);
final int cols = rows;
MeshDataStore data = onMesh.get(SceneManager.getInstance().getSolarLand());
if (data == null) {
data = new MeshDataStore();
data.dailySolarIntensity = new double[rows][cols];
onMesh.put(SceneManager.getInstance().getSolarLand(), data);
}
final Vector3 p = new Vector3();
final double absorption = 1 - Scene.getInstance().getGround().getAdjustedAlbedo(Heliodon.getInstance().getCalendar().get(Calendar.MONTH));
for (int col = 0; col < cols; col++) {
p.setX((col - cols / 2) * step + step / 2.0);
for (int row = 0; row < rows; row++) {
if (EnergyPanel.getInstance().isCancelled()) {
throw new CancellationException();
}
p.setY((row - rows / 2) * step + step / 2.0);
final Ray3 pickRay = new Ray3(p, directionTowardSun);
final PickResults pickResults = new PrimitivePickResults();
for (final Spatial spatial : collidables) {
PickingUtil.findPick(spatial, pickRay, pickResults, false);
if (pickResults.getNumber() != 0) {
break;
}
}
if (pickResults.getNumber() == 0) {
data.dailySolarIntensity[row][col] += Scene.getInstance().getOnlyAbsorptionInSolarMap() ? totalRadiation * absorption : totalRadiation;
} else {
// if shaded, it still receives indirect radiation
data.dailySolarIntensity[row][col] += Scene.getInstance().getOnlyAbsorptionInSolarMap() ? indirectRadiation * absorption : indirectRadiation;
}
}
}
}
use of com.ardor3d.scenegraph.Spatial in project energy3d by concord-consortium.
the class SolarRadiation method computeOnRack.
// TODO: we probably should handle the radiation heat map visualization on the rack using a coarse grid and the energy calculation using a fine grid
private void computeOnRack(final int minute, final ReadOnlyVector3 directionTowardSun, final Rack rack) {
if (rack.getTracker() != SolarPanel.NO_TRACKER) {
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);
rack.draw();
}
if (!rack.isMonolithic()) {
return;
}
final ReadOnlyVector3 normal = rack.getNormal();
if (normal == null) {
throw new RuntimeException("Normal is null");
}
int nx = Scene.getInstance().getRackNx();
int ny = Scene.getInstance().getRackNy();
final Mesh drawMesh = rack.getRadiationMesh();
final Mesh collisionMesh = (Mesh) rack.getRadiationCollisionSpatial();
MeshDataStore data = onMesh.get(drawMesh);
if (data == null) {
data = initMeshTextureDataOnRectangle(drawMesh, nx, ny);
}
final ReadOnlyVector3 offset = directionTowardSun.multiply(1, null);
final double dot = normal.dot(directionTowardSun);
double directRadiation = 0;
if (dot > 0) {
directRadiation += calculateDirectRadiation(directionTowardSun, normal);
}
final double indirectRadiation = calculateDiffuseAndReflectedRadiation(directionTowardSun, normal);
final FloatBuffer vertexBuffer = drawMesh.getMeshData().getVertexBuffer();
// (0, 0)
final Vector3 p0 = new Vector3(vertexBuffer.get(3), vertexBuffer.get(4), vertexBuffer.get(5));
// (1, 0)
final Vector3 p1 = new Vector3(vertexBuffer.get(6), vertexBuffer.get(7), vertexBuffer.get(8));
// (0, 1)
final Vector3 p2 = new Vector3(vertexBuffer.get(0), vertexBuffer.get(1), vertexBuffer.get(2));
// this is the longer side (supposed to be y)
final double d10 = p1.distance(p0);
// this is the shorter side (supposed to be x)
final double d20 = p2.distance(p0);
final Vector3 p10 = p1.subtract(p0, null).normalizeLocal();
final Vector3 p20 = p2.subtract(p0, null).normalizeLocal();
// generate the heat map first. this doesn't affect the energy calculation, it just shows the distribution of solar radiation on the rack.
// x and y must be swapped to have correct heat map texture, because nx represents rows and ny columns as we call initMeshTextureDataOnRectangle(mesh, nx, ny)
double xSpacing = d10 / nx;
double ySpacing = d20 / ny;
Vector3 u = p10;
Vector3 v = p20;
final int iMinute = minute / Scene.getInstance().getTimeStep();
for (int x = 0; x < nx; x++) {
for (int y = 0; y < ny; 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);
final ReadOnlyVector3 p = drawMesh.getWorldTransform().applyForward(p0.add(v2, null).addLocal(u2)).addLocal(offset);
final Ray3 pickRay = new Ray3(p, directionTowardSun);
// assuming that indirect (ambient or diffuse) radiation can always reach a grid point
double radiation = indirectRadiation;
if (dot > 0) {
final PickResults pickResults = new PrimitivePickResults();
for (final Spatial spatial : collidables) {
if (spatial != collisionMesh) {
PickingUtil.findPick(spatial, pickRay, pickResults, false);
if (pickResults.getNumber() != 0) {
break;
}
}
}
if (pickResults.getNumber() == 0) {
radiation += directRadiation;
}
}
data.dailySolarIntensity[x][y] += radiation;
}
}
// now do the calculation to get the total energy generated by the cells
final double airTemperature = Weather.getInstance().getOutsideTemperatureAtMinute(dailyAirTemperatures[1], dailyAirTemperatures[0], minute);
// system efficiency
double syseff;
// output at a cell center
double output;
// cell temperature
double tcell;
final SolarPanel panel = rack.getSolarPanel();
if (Scene.getInstance().isRackModelExact()) {
// exactly model each solar cell on each solar panel
final int[] rc = rack.getSolarPanelRowAndColumnNumbers();
// numbers of solar panels in x and y directions
final int nxPanels = rc[0];
final int nyPanels = rc[1];
// numbers of solar cells on each panel in x and y directions
int nxCells, nyCells;
if (panel.isRotated()) {
nxCells = panel.getNumberOfCellsInY();
nyCells = panel.getNumberOfCellsInX();
} else {
nxCells = panel.getNumberOfCellsInX();
nyCells = panel.getNumberOfCellsInY();
}
nx = nxCells * rc[0];
ny = nyCells * rc[1];
// get the area of a solar cell. 60 converts the unit of timeStep from minute to kWh
final double a = panel.getPanelWidth() * panel.getPanelHeight() * Scene.getInstance().getTimeStep() / (panel.getNumberOfCellsInX() * panel.getNumberOfCellsInY() * 60.0);
// swap the x and y back to correct order
xSpacing = d20 / nx;
ySpacing = d10 / ny;
u = p20;
v = p10;
if (cellOutputs == null || cellOutputs.length != nx || cellOutputs[0].length != ny) {
cellOutputs = new double[nx][ny];
}
// calculate the solar radiation first without worrying about the underlying cell wiring and distributed efficiency
for (int x = 0; x < nx; x++) {
for (int y = 0; y < ny; 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);
final ReadOnlyVector3 p = drawMesh.getWorldTransform().applyForward(p0.add(v2, null).addLocal(u2)).addLocal(offset);
final Ray3 pickRay = new Ray3(p, directionTowardSun);
// assuming that indirect (ambient or diffuse) radiation can always reach a grid point
double radiation = indirectRadiation;
if (dot > 0) {
final PickResults pickResults = new PrimitivePickResults();
for (final Spatial spatial : collidables) {
if (spatial != collisionMesh) {
PickingUtil.findPick(spatial, pickRay, pickResults, false);
if (pickResults.getNumber() != 0) {
break;
}
}
}
if (pickResults.getNumber() == 0) {
radiation += directRadiation;
}
}
cellOutputs[x][y] = radiation * a;
}
}
// Tcell = Tair + (NOCT - 20) / 80 * R, where the unit of R is mW/cm^2
final double noctFactor = (panel.getNominalOperatingCellTemperature() - 20.0) * 100.0 / (a * 80.0);
// now consider cell wiring and distributed efficiency. TODO: This is very inaccurate. The output depends on both cell wiring and panel wiring.
switch(// the ideal case that probably doesn't exist in reality
panel.getShadeTolerance()) {
case SolarPanel.HIGH_SHADE_TOLERANCE:
for (int x = 0; x < nx; x++) {
for (int y = 0; y < ny; y++) {
output = cellOutputs[x][y];
tcell = airTemperature + output * noctFactor;
syseff = panel.getSystemEfficiency(tcell);
rack.getSolarPotential()[iMinute] += output * syseff;
}
}
break;
case // assuming that all the cells on a panel are connected in series and all panels are connected in parallel
SolarPanel.NO_SHADE_TOLERANCE:
double min = Double.MAX_VALUE;
for (int ix = 0; ix < nxPanels; ix++) {
// panel by panel
for (int iy = 0; iy < nyPanels; iy++) {
min = Double.MAX_VALUE;
for (int jx = 0; jx < nxCells; jx++) {
// cell by cell on each panel
for (int jy = 0; jy < nyCells; jy++) {
output = cellOutputs[ix * nxCells + jx][iy * nyCells + jy];
tcell = airTemperature + output * noctFactor;
syseff = panel.getSystemEfficiency(tcell);
output *= syseff;
if (output < min) {
min = output;
}
}
}
rack.getSolarPotential()[iMinute] += min * nxCells * nyCells;
}
}
break;
case // assuming each panel uses a diode bypass to connect two columns of cells
SolarPanel.PARTIAL_SHADE_TOLERANCE:
for (int ix = 0; ix < nxPanels; ix++) {
// panel by panel
for (int iy = 0; iy < nyPanels; iy++) {
min = Double.MAX_VALUE;
if (panel.isRotated()) {
// landscape: nxCells = 10, nyCells = 6
for (int jy = 0; jy < nyCells; jy++) {
// cell by cell on each panel
if (jy % 2 == 0) {
// reset min every two columns of cells
min = Double.MAX_VALUE;
}
for (int jx = 0; jx < nxCells; jx++) {
output = cellOutputs[ix * nxCells + jx][iy * nyCells + jy];
tcell = airTemperature + output * noctFactor;
syseff = panel.getSystemEfficiency(tcell);
output *= syseff;
if (output < min) {
min = output;
}
}
if (jy % 2 == 1) {
rack.getSolarPotential()[iMinute] += min * 2 * nxCells;
}
}
} else {
// portrait: nxCells = 6, nyCells = 10
for (int jx = 0; jx < nxCells; jx++) {
// cell by cell on each panel
if (jx % 2 == 0) {
// reset min every two columns of cells
min = Double.MAX_VALUE;
}
for (int jy = 0; jy < nyCells; jy++) {
output = cellOutputs[ix * nxCells + jx][iy * nyCells + jy];
tcell = airTemperature + output * noctFactor;
syseff = panel.getSystemEfficiency(tcell);
output *= syseff;
if (output < min) {
min = output;
}
}
if (jx % 2 == 1) {
rack.getSolarPotential()[iMinute] += min * 2 * nyCells;
}
}
}
}
}
break;
}
} else {
// for simulation speed, approximate rack model doesn't compute panel by panel and cell by cell
ySpacing = xSpacing = Scene.getInstance().getRackCellSize() / Scene.getInstance().getAnnotationScale();
// swap the x and y back to correct order
nx = Math.max(2, (int) (d20 / xSpacing));
ny = Math.max(2, (int) (d10 / ySpacing));
// nx*ny*60: dividing the total rack area by nx*ny gets the unit cell area of the nx*ny grid; 60 converts the unit of timeStep from minute to kWh
final double a = rack.getRackWidth() * rack.getRackHeight() * Scene.getInstance().getTimeStep() / (nx * ny * 60.0);
u = p20;
v = p10;
if (cellOutputs == null || cellOutputs.length != nx || cellOutputs[0].length != ny) {
cellOutputs = new double[nx][ny];
}
// calculate the solar radiation first without worrying about the underlying cell wiring and distributed efficiency
for (int x = 0; x < nx; x++) {
for (int y = 0; y < ny; 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);
final ReadOnlyVector3 p = drawMesh.getWorldTransform().applyForward(p0.add(v2, null).addLocal(u2)).addLocal(offset);
final Ray3 pickRay = new Ray3(p, directionTowardSun);
// assuming that indirect (ambient or diffuse) radiation can always reach a grid point
double radiation = indirectRadiation;
if (dot > 0) {
final PickResults pickResults = new PrimitivePickResults();
for (final Spatial spatial : collidables) {
if (spatial != collisionMesh) {
PickingUtil.findPick(spatial, pickRay, pickResults, false);
if (pickResults.getNumber() != 0) {
break;
}
}
}
if (pickResults.getNumber() == 0) {
radiation += directRadiation;
}
}
cellOutputs[x][y] = radiation * a;
}
}
// Tcell = Tair + (NOCT - 20) / 80 * R, where the unit of R is mW/cm^2
final double noctFactor = (panel.getNominalOperatingCellTemperature() - 20.0) * 100.0 / (a * 80.0);
// now consider cell wiring and distributed efficiency. TODO: This is very inaccurate. The output depends on both cell wiring and panel wiring.
switch(panel.getShadeTolerance()) {
case // the ideal case that probably doesn't exist in reality
SolarPanel.HIGH_SHADE_TOLERANCE:
for (int x = 0; x < nx; x++) {
for (int y = 0; y < ny; y++) {
output = cellOutputs[x][y];
tcell = airTemperature + output * noctFactor;
syseff = panel.getSystemEfficiency(tcell);
rack.getSolarPotential()[iMinute] += output * syseff;
}
}
break;
case SolarPanel.NO_SHADE_TOLERANCE:
double min = Double.MAX_VALUE;
for (int x = 0; x < nx; x++) {
for (int y = 0; y < ny; y++) {
output = cellOutputs[x][y];
tcell = airTemperature + output * noctFactor;
syseff = panel.getSystemEfficiency(tcell);
output *= syseff;
if (output < min) {
min = output;
}
}
}
rack.getSolarPotential()[iMinute] += min * ny * nx;
break;
case SolarPanel.PARTIAL_SHADE_TOLERANCE:
for (int x = 0; x < nx; x++) {
min = Double.MAX_VALUE;
for (int y = 0; y < ny; y++) {
output = cellOutputs[x][y];
tcell = airTemperature + output * noctFactor;
syseff = panel.getSystemEfficiency(tcell);
output *= syseff;
if (output < min) {
min = output;
}
}
rack.getSolarPotential()[iMinute] += min * ny;
}
break;
}
}
}
use of com.ardor3d.scenegraph.Spatial in project energy3d by concord-consortium.
the class Heliodon method showSunTriangle.
public void showSunTriangle(final boolean b) {
sunTriangle.setVisible(b);
for (final Spatial s : angles.getChildren()) {
final AngleAnnotation a = (AngleAnnotation) s;
a.mesh.setVisible(b);
a.label.setVisible(b);
}
}
use of com.ardor3d.scenegraph.Spatial in project energy3d by concord-consortium.
the class PrintController method computePageDimension.
private void computePageDimension() {
spaceBetweenParts = Scene.getInstance().areAnnotationsVisible() ? 3.0 : 0;
double fromPageToWorldCoord;
if (!isScaleToFit) {
fromPageToWorldCoord = exactFromPageToWorldCoord / (Scene.getInstance().getAnnotationScale() / 10.0);
} else {
double maxWidth = 0;
double maxHeight = 0;
for (final HousePart printPart : printParts) {
if (printPart.isPrintable()) {
if (printPart instanceof Roof) {
for (final Spatial roofPartNode : ((Roof) printPart).getRoofPartsRoot().getChildren()) {
if (roofPartNode.getSceneHints().getCullHint() != CullHint.Always) {
final OrientedBoundingBox boundingBox = (OrientedBoundingBox) ((Node) roofPartNode).getChild(0).getWorldBound().asType(Type.OBB);
final double width = Math.min(boundingBox.getExtent().getX(), boundingBox.getExtent().getZ());
final double height = Math.max(boundingBox.getExtent().getX(), boundingBox.getExtent().getZ());
if (width > maxWidth) {
maxWidth = width;
}
if (height > maxHeight) {
maxHeight = height;
}
}
}
} else {
final OrientedBoundingBox boundingBox = (OrientedBoundingBox) printPart.getMesh().getWorldBound().asType(Type.OBB);
final double width = Math.min(boundingBox.getExtent().getX(), boundingBox.getExtent().getZ());
final double height = Math.max(boundingBox.getExtent().getX(), boundingBox.getExtent().getZ());
if (width > maxWidth) {
maxWidth = width;
}
if (height > maxHeight) {
maxHeight = height;
}
}
}
}
maxWidth *= 2;
maxHeight *= 2;
maxWidth += 2 * spaceBetweenParts;
maxHeight += 2 * spaceBetweenParts;
final double ratio = pageFormat.getImageableWidth() / pageFormat.getImageableHeight();
if (maxWidth / maxHeight > ratio) {
pageWidth = ratio < 1 ? Math.min(maxWidth, maxHeight) : Math.max(maxWidth, maxHeight);
pageHeight = pageWidth / ratio;
} else {
pageHeight = ratio < 1 ? Math.max(maxWidth, maxHeight) : Math.min(maxWidth, maxHeight);
pageWidth = pageHeight * ratio;
}
fromPageToWorldCoord = pageWidth / pageFormat.getImageableWidth();
}
pageLeft = pageFormat.getImageableX() * fromPageToWorldCoord + spaceBetweenParts / 2.0;
pageRight = (pageFormat.getImageableX() + pageFormat.getImageableWidth()) * fromPageToWorldCoord - spaceBetweenParts / 2.0;
pageTop = pageFormat.getImageableY() * fromPageToWorldCoord + spaceBetweenParts / 2.0;
if (labelHeight == 0.0) {
final BMText label = Annotation.makeNewLabel(1);
label.setFontScale(0.5);
labelHeight = label.getHeight();
}
pageBottom = (pageFormat.getImageableY() + pageFormat.getImageableHeight()) * fromPageToWorldCoord;
pageWidth = pageFormat.getWidth() * fromPageToWorldCoord;
pageHeight = pageFormat.getHeight() * fromPageToWorldCoord;
}
use of com.ardor3d.scenegraph.Spatial in project energy3d by concord-consortium.
the class PrintController method fitInPage.
private boolean fitInPage(final Spatial printPart, final ArrayList<Spatial> page) {
for (final Spatial neighborPart : page) {
final Vector3 neighborPartCenter = ((UserData) neighborPart.getUserData()).getPrintCenter();
final OrientedBoundingBox neighborBound = (OrientedBoundingBox) neighborPart.getWorldBound().asType(Type.OBB);
final OrientedBoundingBox printPartBound = (OrientedBoundingBox) printPart.getWorldBound().asType(Type.OBB);
final double xExtend = neighborBound.getExtent().getX() + printPartBound.getExtent().getX() + spaceBetweenParts;
final double zExtend = neighborBound.getExtent().getZ() + printPartBound.getExtent().getZ() + spaceBetweenParts;
for (double angleQuarter = 0; angleQuarter < 4; angleQuarter++) {
final boolean isHorizontal = angleQuarter % 2 == 0;
final Vector3 tryCenter = new Matrix3().fromAngles(0, angleQuarter * Math.PI / 2.0, 0).applyPost(new Vector3(isHorizontal ? xExtend : zExtend, 0, 0), null);
tryCenter.addLocal(neighborPartCenter);
if (!isHorizontal) {
tryCenter.setX(pageLeft + printPartBound.getExtent().getX());
}
if (!isHorizontal) {
tryCenter.setX(MathUtils.clamp(tryCenter.getX(), pageLeft + printPartBound.getExtent().getX(), pageRight - printPartBound.getExtent().getX()));
} else {
tryCenter.setZ(MathUtils.clamp(tryCenter.getZ(), -pageBottom + printPartBound.getExtent().getZ(), -pageTop - printPartBound.getExtent().getZ()));
}
tryCenter.setY(Scene.getOriginalHouseRoot().getWorldBound().getCenter().getY());
boolean collision = false;
if (tryCenter.getX() - printPartBound.getExtent().getX() < pageLeft - MathUtils.ZERO_TOLERANCE || tryCenter.getX() + printPartBound.getExtent().getX() > pageRight + MathUtils.ZERO_TOLERANCE || tryCenter.getZ() + printPartBound.getExtent().getZ() > -pageTop + MathUtils.ZERO_TOLERANCE || tryCenter.getZ() - printPartBound.getExtent().getZ() < -pageBottom - MathUtils.ZERO_TOLERANCE) {
collision = true;
} else {
for (final Spatial otherPart : page) {
printPartBound.setCenter(tryCenter);
final OrientedBoundingBox otherPartBound = (OrientedBoundingBox) otherPart.getWorldBound().asType(Type.OBB);
otherPartBound.setCenter(((UserData) otherPart.getUserData()).getPrintCenter());
if (printPartBound.getExtent().getX() + otherPartBound.getExtent().getX() > Math.abs(printPartBound.getCenter().getX() - otherPartBound.getCenter().getX()) - spaceBetweenParts + MathUtils.ZERO_TOLERANCE && printPartBound.getExtent().getZ() + otherPartBound.getExtent().getZ() > Math.abs(printPartBound.getCenter().getZ() - otherPartBound.getCenter().getZ()) - spaceBetweenParts + MathUtils.ZERO_TOLERANCE) {
collision = true;
break;
}
}
}
if (!collision) {
((UserData) printPart.getUserData()).setPrintCenter(tryCenter);
page.add(printPart);
return true;
}
}
}
return false;
}
Aggregations