use of com.ardor3d.intersection.PickResults in project energy3d by concord-consortium.
the class SolarRadiation method computeOnMirror.
// unlike PV solar panels, no indirect (ambient or diffuse) radiation should be included in reflection calculation
private void computeOnMirror(final int minute, final ReadOnlyVector3 directionTowardSun, final Mirror mirror) {
final int nx = Scene.getInstance().getMirrorNx();
final int ny = Scene.getInstance().getMirrorNy();
final Foundation target = mirror.getReceiver();
if (target != null) {
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);
mirror.draw();
}
// 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 = mirror.getMirrorWidth() * mirror.getMirrorHeight() * Scene.getInstance().getTimeStep() / (nx * ny * 60.0);
final ReadOnlyVector3 normal = mirror.getNormal();
if (normal == null) {
throw new RuntimeException("Normal is null");
}
final Mesh mesh = mirror.getRadiationMesh();
MeshDataStore data = onMesh.get(mesh);
if (data == null) {
data = initMeshTextureDataOnRectangle(mesh, 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 FloatBuffer vertexBuffer = mesh.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));
// final Vector3 q0 = drawMesh.localToWorld(p0, null);
// final Vector3 q1 = drawMesh.localToWorld(p1, null);
// final Vector3 q2 = drawMesh.localToWorld(p2, null);
// System.out.println("***" + q0.distance(q1) * Scene.getInstance().getAnnotationScale() + "," + q0.distance(q2) * Scene.getInstance().getAnnotationScale());
// this is the longer side (supposed to be y)
final Vector3 u = p1.subtract(p0, null).normalizeLocal();
// this is the shorter side (supposed to be x)
final Vector3 v = p2.subtract(p0, null).normalizeLocal();
// 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)
final double xSpacing = p1.distance(p0) / nx;
final double ySpacing = p2.distance(p0) / ny;
final Vector3 receiver = target != null ? target.getSolarReceiverCenter() : null;
List<Mesh> towerCollisionMeshes = null;
if (target != null) {
towerCollisionMeshes = new ArrayList<Mesh>();
for (final HousePart child : target.getChildren()) {
towerCollisionMeshes.add((Mesh) child.getRadiationCollisionSpatial());
}
final List<Roof> roofs = target.getRoofs();
if (!roofs.isEmpty()) {
for (final Roof r : roofs) {
for (final Spatial roofPart : r.getRoofPartsRoot().getChildren()) {
towerCollisionMeshes.add((Mesh) ((Node) roofPart).getChild(6));
}
}
}
}
final int iMinute = minute / Scene.getInstance().getTimeStep();
final boolean reflectionMapOnly = Scene.getInstance().getOnlyReflectedEnergyInMirrorSolarMap();
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 = mesh.getWorldTransform().applyForward(p0.add(v2, null).addLocal(u2)).addLocal(offset);
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
if (!reflectionMapOnly) {
data.dailySolarIntensity[x][y] += directRadiation;
}
if (receiver != null) {
// for concentrated energy calculation
final Vector3 toReceiver = receiver.subtract(p, null);
final Ray3 rayToReceiver = new Ray3(p, toReceiver.normalize(null));
final PickResults pickResultsToReceiver = new PrimitivePickResults();
for (final Spatial spatial : collidables) {
if (spatial != mesh) {
if (towerCollisionMeshes == null || (towerCollisionMeshes != null && !towerCollisionMeshes.contains(spatial))) {
PickingUtil.findPick(spatial, rayToReceiver, pickResultsToReceiver, false);
if (pickResultsToReceiver.getNumber() != 0) {
break;
}
}
}
}
if (pickResultsToReceiver.getNumber() == 0) {
final double r = directRadiation * Atmosphere.getTransmittance(toReceiver.length() * Scene.getInstance().getAnnotationScale() * 0.001, false);
mirror.getSolarPotential()[iMinute] += r * a;
if (reflectionMapOnly) {
data.dailySolarIntensity[x][y] += r;
}
}
}
}
}
}
}
}
use of com.ardor3d.intersection.PickResults in project energy3d by concord-consortium.
the class SolarRadiation method computeOnImportedMesh.
private void computeOnImportedMesh(final int minute, final ReadOnlyVector3 directionTowardSun, final Foundation foundation, final Mesh mesh) {
final UserData userData = (UserData) mesh.getUserData();
if (!userData.isReachable()) {
return;
}
final ReadOnlyVector3 normal = userData.getRotatedNormal() == null ? userData.getNormal() : userData.getRotatedNormal();
final MeshDataStore data = onMesh.get(mesh);
final int timeStep = Scene.getInstance().getTimeStep();
final int iMinute = minute / timeStep;
final double dot = normal.dot(directionTowardSun);
final double directRadiation = dot > 0 ? calculateDirectRadiation(directionTowardSun, normal) : 0;
final double indirectRadiation = calculateDiffuseAndReflectedRadiation(directionTowardSun, normal);
final double solarStep = Scene.getInstance().getSolarStep();
final double annotationScale = Scene.getInstance().getAnnotationScale();
final double scaleFactor = annotationScale * annotationScale / 60 * timeStep;
final float absorption = 1 - foundation.getAlbedo();
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 double w = col == data.cols - 1 ? data.p2.distance(data.p0) - col * solarStep : 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;
// cannot do offset as in computeOnMesh
final ReadOnlyVector3 p = data.v.multiply(row * solarStep + 0.5 * h, null).addLocal(pU);
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 != mesh) {
PickingUtil.findPick(spatial, pickRay, pickResults, false);
if (pickResults.getNumber() != 0) {
break;
}
}
}
if (pickResults.getNumber() == 0) {
radiation += directRadiation;
}
}
data.dailySolarIntensity[row][col] += Scene.getInstance().getOnlyAbsorptionInSolarMap() ? absorption * radiation : radiation;
if (data.solarPotential != null) {
data.solarPotential[iMinute] += radiation * scaledArea;
}
// sum all the solar energy up over all meshes and store in the foundation's solar potential array
foundation.getSolarPotential()[iMinute] += radiation * scaledArea;
}
}
}
use of com.ardor3d.intersection.PickResults in project energy3d by concord-consortium.
the class SolarRadiation method computeOnFresnelReflector.
// unlike PV solar panels, no indirect (ambient or diffuse) radiation should be included in reflection calculation
private void computeOnFresnelReflector(final int minute, final ReadOnlyVector3 directionTowardSun, final FresnelReflector reflector) {
final int nx = reflector.getNSectionLength();
final int ny = reflector.getNSectionWidth();
final Foundation target = reflector.getReceiver();
if (target != null) {
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);
reflector.draw();
}
// 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 = reflector.getModuleWidth() * reflector.getLength() * Scene.getInstance().getTimeStep() / (nx * ny * 60.0);
final ReadOnlyVector3 normal = reflector.getNormal();
if (normal == null) {
throw new RuntimeException("Normal is null");
}
final Mesh mesh = reflector.getRadiationMesh();
MeshDataStore data = onMesh.get(mesh);
if (data == null) {
data = initMeshTextureDataOnRectangle(mesh, 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 FloatBuffer vertexBuffer = mesh.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));
// 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 the longer side (supposed to be y)
final Vector3 u = p1.subtract(p0, null).normalizeLocal();
// this is the shorter side (supposed to be x)
final Vector3 v = p2.subtract(p0, null).normalizeLocal();
// 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)
final double xSpacing = p1.distance(p0) / nx;
final double ySpacing = p2.distance(p0) / ny;
final Vector3 absorber = target != null ? target.getSolarReceiverCenter() : null;
List<Mesh> absorberCollisionMeshes = null;
if (target != null) {
absorberCollisionMeshes = new ArrayList<Mesh>();
for (final HousePart child : target.getChildren()) {
absorberCollisionMeshes.add((Mesh) child.getRadiationCollisionSpatial());
}
final List<Roof> roofs = target.getRoofs();
if (!roofs.isEmpty()) {
for (final Roof r : roofs) {
for (final Spatial roofPart : r.getRoofPartsRoot().getChildren()) {
absorberCollisionMeshes.add((Mesh) ((Node) roofPart).getChild(6));
}
}
}
}
final int iMinute = minute / Scene.getInstance().getTimeStep();
final boolean reflectionMapOnly = Scene.getInstance().getOnlyReflectedEnergyInMirrorSolarMap();
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 = mesh.getWorldTransform().applyForward(p0.add(v2, null).addLocal(u2)).addLocal(offset);
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
if (!reflectionMapOnly) {
data.dailySolarIntensity[x][y] += directRadiation;
}
// TODO: Edge losses are not considered yet
if (absorber != null) {
// TODO: This calculation is not exactly accurate as the collision detection assumes that the ray emits from a grid point on the reflector to
// the parallel position on the absorber tube -- without considering the actual direction of the reflected light
final Vector3 toAbsorber = absorber.subtract(p, null);
toAbsorber.setY(0);
final Ray3 rayToAbsorber = new Ray3(p, toAbsorber.normalize(null));
final PickResults pickResultsToAbsorber = new PrimitivePickResults();
for (final Spatial spatial : collidables) {
if (spatial != mesh) {
if (absorberCollisionMeshes == null || (absorberCollisionMeshes != null && !absorberCollisionMeshes.contains(spatial))) {
PickingUtil.findPick(spatial, rayToAbsorber, pickResultsToAbsorber, false);
if (pickResultsToAbsorber.getNumber() != 0) {
// FIXME: how to stop the ray when it hits the absorber?
break;
}
}
}
}
if (pickResultsToAbsorber.getNumber() == 0) {
final double r = directRadiation * Atmosphere.getTransmittance(toAbsorber.length() * Scene.getInstance().getAnnotationScale() * 0.001, false);
reflector.getSolarPotential()[iMinute] += r * a;
if (reflectionMapOnly) {
data.dailySolarIntensity[x][y] += r;
}
}
}
}
}
}
}
}
use of com.ardor3d.intersection.PickResults in project energy3d by concord-consortium.
the class MeshLib method applyHoles.
public static void applyHoles(final Node root, final List<Window> windows) {
final Map<Window, List<ReadOnlyVector3>> holes = new HashMap<Window, List<ReadOnlyVector3>>();
for (final Window window : windows) {
final ArrayList<ReadOnlyVector3> hole = new ArrayList<ReadOnlyVector3>();
hole.add(window.getAbsPoint(0).multiplyLocal(1, 1, 0));
hole.add(window.getAbsPoint(2).multiplyLocal(1, 1, 0));
hole.add(window.getAbsPoint(3).multiplyLocal(1, 1, 0));
hole.add(window.getAbsPoint(1).multiplyLocal(1, 1, 0));
holes.put(window, hole);
}
for (int roofIndex = 0; roofIndex < root.getChildren().size(); roofIndex++) {
final Spatial roofPart = root.getChildren().get(roofIndex);
if (roofPart.getSceneHints().getCullHint() != CullHint.Always) {
final ReadOnlyVector3 normal = (ReadOnlyVector3) roofPart.getUserData();
final AnyToXYTransform toXY = new AnyToXYTransform(normal.getX(), normal.getY(), normal.getZ());
final XYToAnyTransform fromXY = new XYToAnyTransform(normal.getX(), normal.getY(), normal.getZ());
final Mesh mesh = (Mesh) ((Node) roofPart).getChild(0);
final ArrayList<ReadOnlyVector3> points3D = computeOutline(mesh.getMeshData().getVertexBuffer());
final List<PolygonPoint> points2D = new ArrayList<PolygonPoint>();
final ReadOnlyVector3 firstPoint = points3D.get(0);
final double scale = Scene.getInstance().getTextureMode() == TextureMode.Simple ? 0.5 : 0.1;
final TPoint o;
final TPoint u;
final TPoint v;
if (normal.dot(Vector3.UNIT_Z) == 1) {
o = new TPoint(firstPoint.getX(), firstPoint.getY(), firstPoint.getZ());
u = new TPoint(1 / scale, 0, 0);
v = new TPoint(0, 1 / scale, 0);
} else {
final ReadOnlyVector3 u3 = Vector3.UNIT_Z.cross(normal, null).normalizeLocal();
final ReadOnlyVector3 ou3 = u3.divide(scale, null).add(firstPoint, null);
final ReadOnlyVector3 ov3 = normal.cross(u3, null).divideLocal(scale).addLocal(firstPoint);
o = new TPoint(firstPoint.getX(), firstPoint.getY(), firstPoint.getZ());
u = new TPoint(ou3.getX(), ou3.getY(), ou3.getZ());
v = new TPoint(ov3.getX(), ov3.getY(), ov3.getZ());
toXY.transform(o);
toXY.transform(u);
toXY.transform(v);
u.set(u.getX() - o.getX(), u.getY() - o.getY(), 0);
v.set(v.getX() - o.getX(), v.getY() - o.getY(), 0);
}
final Vector2 o2 = new Vector2(firstPoint.getX(), firstPoint.getY());
final Vector2 ou2 = o2.add(new Vector2(u.getX(), u.getY()), null);
final Vector2 ov2 = o2.add(new Vector2(v.getX(), v.getY()), null);
double minLineScaleU = Double.MAX_VALUE;
double minLineScaleV = Double.MAX_VALUE;
for (final ReadOnlyVector3 p : points3D) {
final PolygonPoint polygonPoint = new PolygonPoint(p.getX(), p.getY(), p.getZ());
toXY.transform(polygonPoint);
points2D.add(polygonPoint);
final Vector2 p2 = new Vector2(polygonPoint.getX(), polygonPoint.getY());
final double lineScaleU = Util.projectPointOnLineScale(p2, o2, ou2);
final double lineScaleV = Util.projectPointOnLineScale(p2, o2, ov2);
if (lineScaleU < minLineScaleU) {
minLineScaleU = lineScaleU;
}
if (lineScaleV < minLineScaleV) {
minLineScaleV = lineScaleV;
}
}
o2.addLocal(new Vector2(u.getX(), u.getY()).multiplyLocal(minLineScaleU));
o2.addLocal(new Vector2(v.getX(), v.getY()).multiplyLocal(minLineScaleV));
final PolygonWithHoles polygon = new PolygonWithHoles(points2D);
o.set(o2.getX(), o2.getY(), 0);
roofPart.updateWorldBound(true);
for (final Window window : windows) {
if (holes.get(window) == null) {
continue;
}
final List<PolygonPoint> holePolygon = new ArrayList<PolygonPoint>();
boolean outside = false;
for (final ReadOnlyVector3 holePoint : holes.get(window)) {
final PickResults pickResults = new PrimitivePickResults();
PickingUtil.findPick(((Node) roofPart).getChild(0), new Ray3(holePoint, Vector3.UNIT_Z), pickResults, false);
if (pickResults.getNumber() > 0) {
final ReadOnlyVector3 intersectionPoint = pickResults.getPickData(0).getIntersectionRecord().getIntersectionPoint(0);
final PolygonPoint polygonPoint = new PolygonPoint(intersectionPoint.getX(), intersectionPoint.getY(), intersectionPoint.getZ());
toXY.transform(polygonPoint);
holePolygon.add(polygonPoint);
} else {
outside = true;
break;
}
}
if (!outside) {
polygon.addHole(new PolygonWithHoles(holePolygon));
holes.remove(window);
window.setRoofIndex(roofIndex);
}
}
final Mesh meshWithHoles = (Mesh) ((Node) roofPart).getChild(6);
try {
fillMeshWithPolygon(meshWithHoles, polygon, fromXY, true, o, v, u, false);
} catch (final RuntimeException e) {
e.printStackTrace();
final Mesh meshWithoutHoles = (Mesh) ((Node) roofPart).getChild(0);
meshWithHoles.setMeshData(meshWithoutHoles.getMeshData());
}
}
}
}
use of com.ardor3d.intersection.PickResults in project energy3d by concord-consortium.
the class SolarRadiation method computeOnSolarPanel.
// a solar panel typically has 6x10 cells, 6 and 10 are not power of 2 for texture. so we need some special handling here
private void computeOnSolarPanel(final int minute, final ReadOnlyVector3 directionTowardSun, final SolarPanel panel) {
if (panel.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);
panel.draw();
}
final ReadOnlyVector3 normal = panel.getNormal();
if (normal == null) {
throw new RuntimeException("Normal is null");
}
int nx = Scene.getInstance().getSolarPanelNx();
int ny = Scene.getInstance().getSolarPanelNy();
final Mesh drawMesh = panel.getRadiationMesh();
final Mesh collisionMesh = (Mesh) panel.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 panel.
// 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;
}
}
if (panel.isRotated()) {
// landscape
nx = panel.getNumberOfCellsInY();
ny = panel.getNumberOfCellsInX();
} else {
// portrait
nx = panel.getNumberOfCellsInX();
ny = panel.getNumberOfCellsInY();
}
// 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 = panel.getPanelWidth() * panel.getPanelHeight() * Scene.getInstance().getTimeStep() / (nx * ny * 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;
}
}
final double airTemperature = Weather.getInstance().getOutsideTemperatureAtMinute(dailyAirTemperatures[1], dailyAirTemperatures[0], minute);
double syseff;
double output;
// cell temperature
double tcell;
// 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 (Nice demo at: https://www.youtube.com/watch?v=UNPJapaZlCU)
switch(panel.getShadeTolerance()) {
case // the most ideal assumption that probably doesn't exist in reality (just keep it here in case someone has a breakthrough in the future)
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);
panel.getSolarPotential()[iMinute] += output * syseff;
}
}
break;
case // all the cells are connected in a single series, so the total output is (easily) determined by the minimum
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;
}
}
}
panel.getSolarPotential()[iMinute] += min * ny * nx;
break;
case // assuming each panel uses a diode bypass to connect two columns of cells
SolarPanel.PARTIAL_SHADE_TOLERANCE:
min = Double.MAX_VALUE;
if (panel.isRotated()) {
// landscape: nx = 10, ny = 6
for (int y = 0; y < ny; y++) {
if (y % 2 == 0) {
// reset min every two columns of cells
min = Double.MAX_VALUE;
}
for (int x = 0; x < nx; x++) {
output = cellOutputs[x][y];
tcell = airTemperature + output * noctFactor;
syseff = panel.getSystemEfficiency(tcell);
output *= syseff;
if (output < min) {
min = output;
}
}
if (y % 2 == 1) {
panel.getSolarPotential()[iMinute] += min * nx * 2;
}
}
} else {
// portrait: nx = 6, ny = 10
for (int x = 0; x < nx; x++) {
if (x % 2 == 0) {
// reset min every two columns of cells
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;
}
}
if (x % 2 == 1) {
panel.getSolarPotential()[iMinute] += min * ny * 2;
}
}
}
break;
}
}
Aggregations