From ed2cab52fedf490ace745d7653495ff8f88f2e39 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:52:51 +0200 Subject: [PATCH 01/38] First steps to add sun source from url #4 --- src/index.ts | 29 ++++++++++++++++++++++------- src/sun.ts | 31 +++++++++++++++++++++++++++++++ src/utils.ts | 8 ++++++++ tests/sun.test.ts | 15 +++++++++++++++ tests/utils.test.ts | 11 +++++++++++ 5 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 src/utils.ts create mode 100644 tests/utils.test.ts diff --git a/src/index.ts b/src/index.ts index 6df7991..f3c0853 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,9 @@ import * as THREE from 'three'; import { BufferAttribute, BufferGeometry, TypedArray } from 'three'; import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'; import { viridis } from './colormaps'; -import { getRandomSunVectors } from './sun'; +import { convertSpericalToEuclidian, fetchIrradiance, getRandomSunVectors } from './sun'; import * as triangleUtils from './triangleUtils.js'; -import { ArrayType, Triangle } from './triangleUtils.js'; +import { isValidUrl } from './utils'; // @ts-ignore import { rayTracingWebGL } from './rayTracingWebGL.js'; @@ -94,7 +94,7 @@ export default class Scene { * @param numberSimulations Number of random sun positions that are used to calculate the PV yield * @returns */ - async calculate(numberSimulations: number = 80) { + async calculate(numberSimulations: number = 80, irradianceUrl: string | undefined) { console.log('Simulation package was called to calculate'); let simulationGeometry = BufferGeometryUtils.mergeGeometries(this.simulationGeometries); let shadingGeometry = BufferGeometryUtils.mergeGeometries(this.shadingGeometries); @@ -130,7 +130,7 @@ export default class Scene { } } // Compute unique intensities - const intensities = await this.rayTrace(midpointsArray, normalsArray, meshArray, numberSimulations); + const intensities = await this.rayTrace(midpointsArray, normalsArray, meshArray, numberSimulations, irradianceUrl); if (intensities === null) { throw new Error('Error raytracing in WebGL.'); @@ -183,11 +183,26 @@ export default class Scene { * @param midpoints midpoints of triangles for which to calculate intensities * @param normals normals for each midpoint * @param meshArray array of vertices for the shading mesh + * @param numberSimulations number of random sun positions that are used for the simulation. Either numberSimulations or irradianceUrl need to be given. + * @param irradianceUrl url where a 2D json of irradiance values lies. To generate such a json, visit https://github.com/open-pv/irradiance * @return * @memberof Scene */ - async rayTrace(midpoints: Float32Array, normals: TypedArray, meshArray: Float32Array, numberSimulations: number) { - let sunDirections = getRandomSunVectors(numberSimulations, this.latitude, this.longitude); - return rayTracingWebGL(midpoints, normals, meshArray, sunDirections); + async rayTrace( + midpoints: Float32Array, + normals: TypedArray, + meshArray: Float32Array, + numberSimulations: number | undefined, + irradianceUrl: string | undefined = undefined, + ) { + if (typeof irradianceUrl === 'string' && isValidUrl(irradianceUrl)) { + const irradiance = await fetchIrradiance(irradianceUrl, this.latitude, this.longitude); + return convertSpericalToEuclidian(irradiance); + } else if (typeof numberSimulations === 'undefined') { + throw new Error('Either number simulations or a valid irradianceUrl must be given.'); + } else { + let sunDirections = getRandomSunVectors(numberSimulations, this.latitude, this.longitude); + return rayTracingWebGL(midpoints, normals, meshArray, sunDirections); + } } } diff --git a/src/sun.ts b/src/sun.ts index f6b41cf..3c4d0fb 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -21,3 +21,34 @@ export function getRandomSunVectors(Ndates: number, lat: number, lon: number): F function getRandomDate(start: Date, end: Date): Date { return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); } + +export function convertSpericalToEuclidian(irradiance: number[][]): Float32Array { + const polarSamples = irradiance.length; + const azimuthSamples = irradiance[0].length; + const polarStepSize = Math.PI / 2 / polarSamples; + const azimuthStepSize = (Math.PI * 2) / azimuthSamples; + const sunVectors = new Float32Array(polarSamples * azimuthSamples * 3); + for (let i = 0; i <= polarSamples; i++) { + for (let j = 0; i <= azimuthSamples; i++) { + sunVectors[i * j] = -irradiance[i][j] * Math.cos(i * polarStepSize) * Math.sin(j * azimuthStepSize); + sunVectors[i * j + 1] = -irradiance[i][j] * Math.cos(i * polarStepSize) * Math.cos(j * azimuthStepSize); + sunVectors[3 * i + 2] = irradiance[i][j] * Math.sin(i * polarStepSize); + } + } + return sunVectors; +} + +export async function fetchIrradiance(url: string, lat: number, lon: number): Promise { + //TODO: Implement fullURL from url, lat, lon + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const jsonData = await response.json(); + return jsonData; + } catch (error) { + console.error('There was a problem with the fetch operation:', error); + throw error; + } +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..6e126b2 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,8 @@ +export function isValidUrl(urlString: string): boolean { + try { + new URL(urlString); + return true; + } catch (e) { + return false; + } +} diff --git a/tests/sun.test.ts b/tests/sun.test.ts index bcfa57b..cb60d90 100644 --- a/tests/sun.test.ts +++ b/tests/sun.test.ts @@ -1,6 +1,13 @@ import { describe, expect, test } from 'vitest'; import * as sun from '../src/sun'; +const irradianceSpherical: number[][] = [ + [1, 2, 3, 4], + [1, 2, 3, 4], +]; + +const irradianceEuclidian = new Float32Array([0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 1, 0, 0, 0, 2, 0, -3, 0, 0, 0, -4, 0]); + describe('Test functionalities from sun.ts: ', () => { const N = 50; let vectors = sun.getRandomSunVectors(N, 0, 0); @@ -19,4 +26,12 @@ describe('Test functionalities from sun.ts: ', () => { expect(z).toBeGreaterThan(0); } }); + test('ConvertSpericalToEuclidian works right.', () => { + const tolerance = 0.00001; + const calculatedIrradianceEuclidian = sun.convertSpericalToEuclidian(irradianceSpherical); + const allClose = + irradianceSpherical.length === irradianceEuclidian.length && + calculatedIrradianceEuclidian.every((value, index) => Math.abs(value - irradianceEuclidian[index]) <= tolerance); + expect(allClose).toBe(true); + }); }); diff --git a/tests/utils.test.ts b/tests/utils.test.ts new file mode 100644 index 0000000..ef00342 --- /dev/null +++ b/tests/utils.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, test } from 'vitest'; +import * as utils from '../src/utils'; + +describe('Test functionalities from utils.ts: ', () => { + test('isValidUrl returns true for valid url', () => { + expect(utils.isValidUrl('https://example.org')).toBe(true); + }); + test('isValidUrl returns false for wrong url', () => { + expect(utils.isValidUrl('this is not an url')).toBe(false); + }); +}); From 167d41be0a646b438aed51ab90ae76efe33607cf Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:58:11 +0200 Subject: [PATCH 02/38] Add elevation rasters to scene attributes #5 --- src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/index.ts b/src/index.ts index 6df7991..5118fdf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ import { rayTracingWebGL } from './rayTracingWebGL.js'; export default class Scene { simulationGeometries: Array; shadingGeometries: Array; + elevationRasters: Array>; latitude: number; longitude: number; @@ -31,6 +32,7 @@ export default class Scene { constructor(latitude: number, longitude: number) { this.simulationGeometries = []; this.shadingGeometries = []; + this.elevationRasters = []; this.latitude = latitude; this.longitude = longitude; } @@ -57,6 +59,10 @@ export default class Scene { this.shadingGeometries.push(geometry); } + addElevationRaster(raster: number[][]) { + this.elevationRasters.push(raster); + } + /** @ignore */ refineMesh(mesh: BufferGeometry, maxLength: number): BufferGeometry { const positions = mesh.attributes.position.array.slice(); From 9d8d690a32df7098e6c3c34779506db0353835b7 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 22 Apr 2024 13:14:31 +0200 Subject: [PATCH 03/38] Create function to calculate elevationAngles #5 Based on a 2D grid which will be given as GeoTIFFs on a running website. --- src/elevation.ts | 53 +++++++++++++++++++++++++++++++++++++++++ tests/elevation.test.ts | 31 ++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/elevation.ts create mode 100644 tests/elevation.test.ts diff --git a/src/elevation.ts b/src/elevation.ts new file mode 100644 index 0000000..8191928 --- /dev/null +++ b/src/elevation.ts @@ -0,0 +1,53 @@ +export type Point = { + x: number; + y: number; +}; + +function calculateDistance(p1: Point, p2: Point): number { + return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2); +} + +function calculateElevationAngle(elevationDifference: number, distance: number): number { + return Math.atan2(elevationDifference, distance); +} + +/** + * Calculates the maximum heights visible from an observer in a set of directions. + * Returns a 2D array. The first dimension is over all Z-values (heights) of the observer, the second dimension + * is over the number of theta directions defined with numDirections. + * @param grid 2D array of elevations, in the current implementation it needs to be a NxN grid where N is uneven! + * @param observer Point of interest, given as the indexes from the grid: [10,10] means that grid[10][10] is the point for + * which the elevation angles are calculated. + * @param observerZ List of height values of the observer. This will replace the grid[observer] values. + * @param numDirections Length of the returned list. + * @returns + */ +export function getMaxElevationAngles( + grid: number[][], + observer: Point, + observerZ: number[], + numDirections: number = 360, +): number[][] { + const maxAngles: number[][] = Array.from({ length: observerZ.length }, () => Array(numDirections).fill(-Infinity)); + const numRows = grid.length; + const numCols = grid[0].length; + + for (let row = 0; row < numRows; row++) { + for (let col = 0; col < numCols; col++) { + if (row === observer.y && col === observer.x) continue; + const targetPoint: Point = { x: col, y: row }; + const distance = calculateDistance(observer, targetPoint); + const angleToTarget = (Math.atan2(targetPoint.y - observer.y, targetPoint.x - observer.x) * 180) / Math.PI; + const adjustedAngle = angleToTarget < 0 ? angleToTarget + 360 : angleToTarget; + const thetaIndex = Math.floor(adjustedAngle / (360 / numDirections)); + for (let zIndex = 0; zIndex < observerZ.length; zIndex++) { + let elevationDifference = grid[row][col] - observerZ[zIndex]; + let elevationAngle = calculateElevationAngle(elevationDifference, distance); + if (elevationAngle > maxAngles[zIndex][thetaIndex]) { + maxAngles[zIndex][thetaIndex] = elevationAngle; + } + } + } + } + return maxAngles; +} diff --git a/tests/elevation.test.ts b/tests/elevation.test.ts new file mode 100644 index 0000000..cd747ef --- /dev/null +++ b/tests/elevation.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, test } from 'vitest'; +import * as elevation from '../src/elevation'; + +const observer: elevation.Point = { x: 1, y: 1 }; // Midpoint of the grid +describe('Test functionalities from elevation.ts: ', () => { + const N = 50; + const zValuesObserver = [0, 1, 5]; + const grid = [ + [1, 0, 2], + [3, 0, 4], + [5, 0, 6], + ]; + + const elevationAnglesExpectation = [ + [1.326, 1.339, 0, 1.295, 1.249, 0.615, 0, 0.955], + [1.249, 1.295, -0.785, 1.231, 1.107, 0, -0.785, 0.615], + [-0.785, 0.615, -1.373, 0, -1.107, -1.231, -1.373, -1.13], + ]; + const elevationAngles = elevation.getMaxElevationAngles(grid, observer, zValuesObserver, 8); + test('Get a list.', () => { + const tolerance = 0.01; + const allClose = + elevationAngles.length === elevationAnglesExpectation.length && + elevationAngles.every( + (row, rowIndex) => + row.length === elevationAnglesExpectation[rowIndex].length && + row.every((value, colIndex) => Math.abs(value - elevationAnglesExpectation[rowIndex][colIndex]) <= tolerance), + ); + expect(allClose).toBe(true); + }); +}); From f9f2ecad5997eca79c20c61c93eb3a77718a54a8 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:21:05 +0200 Subject: [PATCH 04/38] Receive elevation raster in shading simulation #5 --- src/index.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5118fdf..6c56e02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,9 @@ import * as THREE from 'three'; import { BufferAttribute, BufferGeometry, TypedArray } from 'three'; import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'; import { viridis } from './colormaps'; +import * as elevation from './elevation'; import { getRandomSunVectors } from './sun'; import * as triangleUtils from './triangleUtils.js'; -import { ArrayType, Triangle } from './triangleUtils.js'; // @ts-ignore import { rayTracingWebGL } from './rayTracingWebGL.js'; @@ -20,7 +20,8 @@ import { rayTracingWebGL } from './rayTracingWebGL.js'; export default class Scene { simulationGeometries: Array; shadingGeometries: Array; - elevationRasters: Array>; + elevationRasters: Array; + elevationRasterMidpoint: elevation.Point; latitude: number; longitude: number; @@ -33,6 +34,7 @@ export default class Scene { this.simulationGeometries = []; this.shadingGeometries = []; this.elevationRasters = []; + this.elevationRasterMidpoint = { x: 0, y: 0 }; this.latitude = latitude; this.longitude = longitude; } @@ -59,8 +61,9 @@ export default class Scene { this.shadingGeometries.push(geometry); } - addElevationRaster(raster: number[][]) { - this.elevationRasters.push(raster); + addElevationRaster(raster: number[][], midpoint: elevation.Point) { + this.elevationRasters = raster; + this.elevationRasterMidpoint = midpoint; } /** @ignore */ @@ -104,6 +107,19 @@ export default class Scene { console.log('Simulation package was called to calculate'); let simulationGeometry = BufferGeometryUtils.mergeGeometries(this.simulationGeometries); let shadingGeometry = BufferGeometryUtils.mergeGeometries(this.shadingGeometries); + if (this.elevationRasters.length > 0) { + let shadingElevationAngles = elevation.getMaxElevationAngles( + this.elevationRasters, + { + x: Math.round(this.elevationRasters.length - 1 / 2), + y: Math.round(this.elevationRasters[0].length - 1 / 2), + }, + [this.elevationRasters[this.elevationRasterMidpoint.x][this.elevationRasterMidpoint.y]], + // TODO: Right now this only includes one z value taken from the Midpoint, this can be extended + // to multiple z points + ); + } + // TODO: This breaks everything, why? simulationGeometry = this.refineMesh(simulationGeometry, 0.5); // TODO: make configurable From 854281e03caafde14391f14f9c88e6769ebb6e8f Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:23:00 +0200 Subject: [PATCH 05/38] Include shading from elevation to raytracing function #5 --- src/index.ts | 37 +++++++++++++++++++++++++------------ src/rayTracingWebGL.ts | 15 +++++++++++---- src/sun.ts | 32 +++++++++++++++++++++++++------- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6c56e02..4361d43 100644 --- a/src/index.ts +++ b/src/index.ts @@ -107,18 +107,6 @@ export default class Scene { console.log('Simulation package was called to calculate'); let simulationGeometry = BufferGeometryUtils.mergeGeometries(this.simulationGeometries); let shadingGeometry = BufferGeometryUtils.mergeGeometries(this.shadingGeometries); - if (this.elevationRasters.length > 0) { - let shadingElevationAngles = elevation.getMaxElevationAngles( - this.elevationRasters, - { - x: Math.round(this.elevationRasters.length - 1 / 2), - y: Math.round(this.elevationRasters[0].length - 1 / 2), - }, - [this.elevationRasters[this.elevationRasterMidpoint.x][this.elevationRasterMidpoint.y]], - // TODO: Right now this only includes one z value taken from the Midpoint, this can be extended - // to multiple z points - ); - } // TODO: This breaks everything, why? simulationGeometry = this.refineMesh(simulationGeometry, 0.5); // TODO: make configurable @@ -210,6 +198,31 @@ export default class Scene { */ async rayTrace(midpoints: Float32Array, normals: TypedArray, meshArray: Float32Array, numberSimulations: number) { let sunDirections = getRandomSunVectors(numberSimulations, this.latitude, this.longitude); + if (this.elevationRasters.length > 0) { + let shadingElevationAngles = elevation.getMaxElevationAngles( + this.elevationRasters, + { + x: Math.round(this.elevationRasters.length - 1 / 2), + y: Math.round(this.elevationRasters[0].length - 1 / 2), + }, + [this.elevationRasters[this.elevationRasterMidpoint.x][this.elevationRasterMidpoint.y]], + // TODO: Right now this only includes one z value taken from the Midpoint, this can be extended + // to multiple z points + ); + for (let i = 0; i < sunDirections.spherical.length; i += 2) { + const shadingElevationAnglesIndex = Math.round( + (sunDirections.spherical[i + 1] / 2 / Math.PI) * shadingElevationAngles.length, + ); + if (shadingElevationAngles[0][shadingElevationAnglesIndex] >= sunDirections.spherical[i]) { + sunDirections.cartesian = new Float32Array([ + ...sunDirections.cartesian.slice(0, i), + ...sunDirections.cartesian.slice(i + 3), + ]); + console.log('One ray was shaded by a nearby mountain.'); + } + } + } + return rayTracingWebGL(midpoints, normals, meshArray, sunDirections); } } diff --git a/src/rayTracingWebGL.ts b/src/rayTracingWebGL.ts index bdf9798..d2ea5a3 100644 --- a/src/rayTracingWebGL.ts +++ b/src/rayTracingWebGL.ts @@ -10,7 +10,10 @@ export function rayTracingWebGL( pointsArray: TypedArray, normals: TypedArray, trianglesArray: TypedArray, - sunDirections: Float32Array, + sunDirections: { + cartesian: Float32Array; + spherical: Float32Array; + }, ): Float32Array | null { const N_TRIANGLES = trianglesArray.length / 9; const width = pointsArray.length / 3; // Change this to the number of horizontal points in the grid @@ -178,11 +181,15 @@ export function rayTracingWebGL( var colorCodedArray = null; var isShadowedArray = null; - for (var i = 0; i < sunDirections.length; i += 3) { - console.log('Simulating sun position #', i / 3, '/', sunDirections.length / 3); + for (var i = 0; i < sunDirections.cartesian.length; i += 3) { + console.log('Simulating sun position #', i / 3, '/', sunDirections.cartesian.length / 3); // TODO: Iterate over sunDirection let sunDirectionUniformLocation = gl.getUniformLocation(program, 'u_sun_direction'); - gl.uniform3fv(sunDirectionUniformLocation, [sunDirections[i], sunDirections[i + 1], sunDirections[i + 2]]); + gl.uniform3fv(sunDirectionUniformLocation, [ + sunDirections.cartesian[i], + sunDirections.cartesian[i + 1], + sunDirections.cartesian[i + 2], + ]); drawArraysWithTransformFeedback(gl, tf, gl.POINTS, N_POINTS); diff --git a/src/sun.ts b/src/sun.ts index f6b41cf..71ed7cd 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -1,21 +1,39 @@ import { getPosition } from 'suncalc'; -export function getRandomSunVectors(Ndates: number, lat: number, lon: number): Float32Array { +/** + * Creates arrays of sun vectors. "cartesian" is a vector of length 3*Ndates where every three entries make up one vector. + * "spherical" is a vector of length 2*Ndates, where pairs of entries are altitude, azimuth. + * @param Ndates + * @param lat + * @param lon + * @returns + */ +export function getRandomSunVectors( + Ndates: number, + lat: number, + lon: number, +): { + cartesian: Float32Array; + spherical: Float32Array; +} { const sunVectors = new Float32Array(Ndates * 3); + const sunVectorsSpherical = new Float32Array(Ndates * 2); var i = 0; while (i < Ndates) { let date = getRandomDate(new Date(2023, 1, 1), new Date(2023, 12, 31)); - const pos = getPosition(date, lat, lon); - if (pos.altitude < 0.1 || pos.altitude == Number.NaN) { + const posSperical = getPosition(date, lat, lon); + if (posSperical.altitude < 0.1 || posSperical.altitude == Number.NaN) { continue; } - sunVectors[3 * i] = -Math.cos(pos.altitude) * Math.sin(pos.azimuth); - sunVectors[3 * i + 1] = -Math.cos(pos.altitude) * Math.cos(pos.azimuth); - sunVectors[3 * i + 2] = Math.sin(pos.altitude); + sunVectors[3 * i] = -Math.cos(posSperical.altitude) * Math.sin(posSperical.azimuth); + sunVectors[3 * i + 1] = -Math.cos(posSperical.altitude) * Math.cos(posSperical.azimuth); + sunVectors[3 * i + 2] = Math.sin(posSperical.altitude); + sunVectorsSpherical[2 * i] = posSperical.altitude; + sunVectorsSpherical[2 * i + 1] = posSperical.azimuth; i += 1; } - return sunVectors; + return { cartesian: sunVectors, spherical: sunVectorsSpherical }; } function getRandomDate(start: Date, end: Date): Date { From 99785c3a4de32faf19af783744307e5c0495f631 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:37:06 +0200 Subject: [PATCH 06/38] Add separation of spherical and cartesian coordinates #5 --- tests/sun.test.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/sun.test.ts b/tests/sun.test.ts index bcfa57b..cd6c651 100644 --- a/tests/sun.test.ts +++ b/tests/sun.test.ts @@ -4,19 +4,25 @@ import * as sun from '../src/sun'; describe('Test functionalities from sun.ts: ', () => { const N = 50; let vectors = sun.getRandomSunVectors(N, 0, 0); - test('Get Correct number of positions.', () => { - expect(vectors.length).toStrictEqual(3 * N); + test('Get Correct number of positions for cartesian coordiantes.', () => { + expect(vectors.cartesian.length).toStrictEqual(3 * N); + }); + test('Get Correct number of positions for spherical coordiantes.', () => { + expect(vectors.spherical.length).toStrictEqual(2 * N); }); test('Get normalized sun vectors.', () => { for (let i = 0; i < N / 3; i++) { - let length = vectors[3 * i] ** 2 + vectors[3 * i + 1] ** 2 + vectors[3 * i + 2] ** 2; + let length = vectors.cartesian[3 * i] ** 2 + vectors.cartesian[3 * i + 1] ** 2 + vectors.cartesian[3 * i + 2] ** 2; expect(length).to.closeTo(1, 0.001); } }); test('Sun is always above the horizon.', () => { for (let i = 0; i < N / 3; i++) { - let z = vectors[3 * i + 2]; + let z = vectors.cartesian[3 * i + 2]; + let altitude = vectors.spherical[2 * i]; expect(z).toBeGreaterThan(0); + expect(altitude).toBeGreaterThan(0); + expect; } }); }); From 554d80957aba20ab5fa585eef4d2c626b805ff57 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 22 Apr 2024 17:36:21 +0200 Subject: [PATCH 07/38] Delete unused variable #5 --- tests/sun.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/sun.test.ts b/tests/sun.test.ts index cd6c651..03bf7d7 100644 --- a/tests/sun.test.ts +++ b/tests/sun.test.ts @@ -22,7 +22,6 @@ describe('Test functionalities from sun.ts: ', () => { let altitude = vectors.spherical[2 * i]; expect(z).toBeGreaterThan(0); expect(altitude).toBeGreaterThan(0); - expect; } }); }); From 42b2f03c35530d966b826da2f6619de8e2a77478 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:05:02 +0200 Subject: [PATCH 08/38] Repair existing methods #4 --- src/sun.ts | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/sun.ts b/src/sun.ts index 3c4d0fb..a88abf0 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -7,6 +7,11 @@ export function getRandomSunVectors(Ndates: number, lat: number, lon: number): F let date = getRandomDate(new Date(2023, 1, 1), new Date(2023, 12, 31)); const pos = getPosition(date, lat, lon); + // pos.altitude: sun altitude above the horizon in radians, + // e.g. 0 at the horizon and PI/2 at the zenith (straight over your head) + // pos. azimuth: sun azimuth in radians (direction along the horizon, measured + // from south to west), e.g. 0 is south and Math.PI * 3/4 is northwest + if (pos.altitude < 0.1 || pos.altitude == Number.NaN) { continue; } @@ -22,24 +27,32 @@ function getRandomDate(start: Date, end: Date): Date { return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); } +/** + * Converts an 2d vector of irradiance values in sperical coordinates to a 1d vector in euclidian coordinates + * @param irradiance Vector of shape N_altitude x N_azimuth + * @returns Vector of shape 3 x N_altitude x N_azimuth + */ export function convertSpericalToEuclidian(irradiance: number[][]): Float32Array { - const polarSamples = irradiance.length; + const altitudeSamples = irradiance.length; const azimuthSamples = irradiance[0].length; - const polarStepSize = Math.PI / 2 / polarSamples; + const altitudeStepSize = altitudeSamples > 1 ? Math.PI / 2 / (altitudeSamples - 1) : Math.PI / 2; const azimuthStepSize = (Math.PI * 2) / azimuthSamples; - const sunVectors = new Float32Array(polarSamples * azimuthSamples * 3); - for (let i = 0; i <= polarSamples; i++) { - for (let j = 0; i <= azimuthSamples; i++) { - sunVectors[i * j] = -irradiance[i][j] * Math.cos(i * polarStepSize) * Math.sin(j * azimuthStepSize); - sunVectors[i * j + 1] = -irradiance[i][j] * Math.cos(i * polarStepSize) * Math.cos(j * azimuthStepSize); - sunVectors[3 * i + 2] = irradiance[i][j] * Math.sin(i * polarStepSize); + const sunVectors = new Float32Array(altitudeSamples * azimuthSamples * 3); + let index = 0; + for (let i = 0; i < altitudeSamples; i++) { + for (let j = 0; j < azimuthSamples; j++) { + sunVectors[index] = irradiance[i][j] * Math.sin(i * altitudeStepSize) * Math.cos(j * azimuthStepSize); + sunVectors[index + 1] = irradiance[i][j] * Math.sin(i * altitudeStepSize) * Math.sin(j * azimuthStepSize); + sunVectors[index + 2] = irradiance[i][j] * Math.cos(i * altitudeStepSize); + index += 3; } } return sunVectors; } -export async function fetchIrradiance(url: string, lat: number, lon: number): Promise { +export async function fetchIrradiance(baseUrl: string, lat: number, lon: number): Promise { //TODO: Implement fullURL from url, lat, lon + const url = baseUrl + '/' + lat.toFixed(1) + '/' + lon.toFixed(1) + '.json'; try { const response = await fetch(url); if (!response.ok) { From d16a4f02bb4fc6898ddf6d67a73af755087e1a76 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:05:16 +0200 Subject: [PATCH 09/38] Add test for fetching data from API #4 --- tests/sun.test.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/sun.test.ts b/tests/sun.test.ts index cb60d90..543aab7 100644 --- a/tests/sun.test.ts +++ b/tests/sun.test.ts @@ -28,10 +28,17 @@ describe('Test functionalities from sun.ts: ', () => { }); test('ConvertSpericalToEuclidian works right.', () => { const tolerance = 0.00001; + console.log(tolerance); const calculatedIrradianceEuclidian = sun.convertSpericalToEuclidian(irradianceSpherical); - const allClose = - irradianceSpherical.length === irradianceEuclidian.length && - calculatedIrradianceEuclidian.every((value, index) => Math.abs(value - irradianceEuclidian[index]) <= tolerance); + console.log(calculatedIrradianceEuclidian); + const allClose = calculatedIrradianceEuclidian.every( + (value, index) => Math.abs(value - irradianceEuclidian[index]) <= tolerance, + ); expect(allClose).toBe(true); }); + test('Fetch irradiance json from openpv url.', async () => { + const data = await sun.fetchIrradiance('https://www.openpv.de/data/irradiance', 50.0, 11.0); + + expect(data.length).toBeGreaterThan(5); + }); }); From 9ffeaa5ddbf8af193f3c691b7729aeee80f629c5 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:14:46 +0200 Subject: [PATCH 10/38] Add raytracing to case where irradianceURL is given #4 --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index f3c0853..9176746 100644 --- a/src/index.ts +++ b/src/index.ts @@ -197,7 +197,8 @@ export default class Scene { ) { if (typeof irradianceUrl === 'string' && isValidUrl(irradianceUrl)) { const irradiance = await fetchIrradiance(irradianceUrl, this.latitude, this.longitude); - return convertSpericalToEuclidian(irradiance); + const sunDirections = convertSpericalToEuclidian(irradiance); + return rayTracingWebGL(midpoints, normals, meshArray, sunDirections); } else if (typeof numberSimulations === 'undefined') { throw new Error('Either number simulations or a valid irradianceUrl must be given.'); } else { From d6e3d858e0148c1fd69e7e981f1c2717e42bad68 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 6 May 2024 17:54:03 +0200 Subject: [PATCH 11/38] Change 2d array to list of sphericalPoints #5 By doing so, the strange dependency on grid indeces got removed. Now the list of points has the information of precise locations. --- src/elevation.ts | 101 +++++++++++++++++++++++++++------------- tests/elevation.test.ts | 80 +++++++++++++++++++++---------- 2 files changed, 123 insertions(+), 58 deletions(-) diff --git a/src/elevation.ts b/src/elevation.ts index 8191928..ab248a4 100644 --- a/src/elevation.ts +++ b/src/elevation.ts @@ -1,53 +1,88 @@ export type Point = { x: number; y: number; + z: number; }; -function calculateDistance(p1: Point, p2: Point): number { - return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2); +export type SphericalPoint = { + altitude: number; + azimuth: number; +}; + +export function fillMissingAltitudes(maxAngles: SphericalPoint[]): void { + // First copy the maxAngles to a newAngles list, so that changes + // in the list do not affect the algorithm + let newAngles = maxAngles.map((angle) => ({ ...angle })); + for (let i = 0; i < newAngles.length; i++) { + if (newAngles[i].altitude != -Infinity) { + continue; + } + let distance = 1; + while (true) { + let prevIndex = (i - distance + newAngles.length) % newAngles.length; + let nextIndex = (i + distance) % newAngles.length; + + if (maxAngles[nextIndex].altitude !== -Infinity) { + newAngles[i].altitude = maxAngles[nextIndex].altitude; + break; + } else if (maxAngles[prevIndex].altitude !== -Infinity) { + newAngles[i].altitude = maxAngles[prevIndex].altitude; + break; + } else distance++; + } + } + // Overwrite the maxAngles to make changes in this vector global + for (let i = 0; i < maxAngles.length; i++) { + maxAngles[i] = newAngles[i]; + } } -function calculateElevationAngle(elevationDifference: number, distance: number): number { - return Math.atan2(elevationDifference, distance); +/** + * + * @param start + * @param end + * @returns azimuth from 0 to 2*PI and altitude from 0 to PI/2, where altitude = 0 is facing directly upwards + */ +export function calculateSphericalCoordinates(start: Point, end: Point): { azimuth: number; altitude: number } { + const dx = end.x - start.x; + const dy = end.y - start.y; + const dz = end.z - start.z; + + const r = Math.sqrt(dx * dx + dy * dy + dz * dz); + const altitude = Math.acos(dz / r); + let azimuth = Math.atan2(dy, dx); + + if (azimuth < 0) { + azimuth += 2 * Math.PI; // Adjust azimuth to be from 0 to 2PI + } + + return { azimuth, altitude }; } /** * Calculates the maximum heights visible from an observer in a set of directions. - * Returns a 2D array. The first dimension is over all Z-values (heights) of the observer, the second dimension - * is over the number of theta directions defined with numDirections. - * @param grid 2D array of elevations, in the current implementation it needs to be a NxN grid where N is uneven! + * Returns a list of spherical points of length numDirections. + * @param elevation list of points with x,y,z component * @param observer Point of interest, given as the indexes from the grid: [10,10] means that grid[10][10] is the point for * which the elevation angles are calculated. - * @param observerZ List of height values of the observer. This will replace the grid[observer] values. * @param numDirections Length of the returned list. * @returns */ -export function getMaxElevationAngles( - grid: number[][], - observer: Point, - observerZ: number[], - numDirections: number = 360, -): number[][] { - const maxAngles: number[][] = Array.from({ length: observerZ.length }, () => Array(numDirections).fill(-Infinity)); - const numRows = grid.length; - const numCols = grid[0].length; - - for (let row = 0; row < numRows; row++) { - for (let col = 0; col < numCols; col++) { - if (row === observer.y && col === observer.x) continue; - const targetPoint: Point = { x: col, y: row }; - const distance = calculateDistance(observer, targetPoint); - const angleToTarget = (Math.atan2(targetPoint.y - observer.y, targetPoint.x - observer.x) * 180) / Math.PI; - const adjustedAngle = angleToTarget < 0 ? angleToTarget + 360 : angleToTarget; - const thetaIndex = Math.floor(adjustedAngle / (360 / numDirections)); - for (let zIndex = 0; zIndex < observerZ.length; zIndex++) { - let elevationDifference = grid[row][col] - observerZ[zIndex]; - let elevationAngle = calculateElevationAngle(elevationDifference, distance); - if (elevationAngle > maxAngles[zIndex][thetaIndex]) { - maxAngles[zIndex][thetaIndex] = elevationAngle; - } - } +export function getMaxElevationAngles(elevation: Point[], observer: Point, numDirections: number = 360): SphericalPoint[] { + let maxAngles: SphericalPoint[] = Array.from({ length: numDirections }, (_, index) => ({ + azimuth: index * ((2 * Math.PI) / numDirections), + altitude: -Infinity, + })); + + for (let point of elevation) { + const { azimuth, altitude } = calculateSphericalCoordinates(observer, point); + console.log(azimuth, altitude); + const closestIndex = Math.round(azimuth / ((2 * Math.PI) / numDirections)) % numDirections; + + if (altitude > maxAngles[closestIndex].altitude) { + maxAngles[closestIndex].altitude = altitude; } } + fillMissingAltitudes(maxAngles); return maxAngles; } diff --git a/tests/elevation.test.ts b/tests/elevation.test.ts index cd747ef..6bb0715 100644 --- a/tests/elevation.test.ts +++ b/tests/elevation.test.ts @@ -1,31 +1,61 @@ import { describe, expect, test } from 'vitest'; import * as elevation from '../src/elevation'; -const observer: elevation.Point = { x: 1, y: 1 }; // Midpoint of the grid -describe('Test functionalities from elevation.ts: ', () => { - const N = 50; - const zValuesObserver = [0, 1, 5]; - const grid = [ - [1, 0, 2], - [3, 0, 4], - [5, 0, 6], - ]; +describe('calculateSphericalCoordinates', () => { + test('should calculate the correct spherical coordinates', () => { + const start = { x: 0, y: 0, z: 0 }; + const ends = [ + { x: 0, y: 0, z: 1 }, + { x: 1, y: 0, z: 1 }, + { x: 0, y: -1, z: 1 }, + ]; + const expectedResults = [ + { altitude: 0, azimuth: 0 }, + { altitude: Math.PI / 4, azimuth: 0 }, + { altitude: Math.PI / 4, azimuth: (3 / 2) * Math.PI }, + ]; - const elevationAnglesExpectation = [ - [1.326, 1.339, 0, 1.295, 1.249, 0.615, 0, 0.955], - [1.249, 1.295, -0.785, 1.231, 1.107, 0, -0.785, 0.615], - [-0.785, 0.615, -1.373, 0, -1.107, -1.231, -1.373, -1.13], - ]; - const elevationAngles = elevation.getMaxElevationAngles(grid, observer, zValuesObserver, 8); - test('Get a list.', () => { - const tolerance = 0.01; - const allClose = - elevationAngles.length === elevationAnglesExpectation.length && - elevationAngles.every( - (row, rowIndex) => - row.length === elevationAnglesExpectation[rowIndex].length && - row.every((value, colIndex) => Math.abs(value - elevationAnglesExpectation[rowIndex][colIndex]) <= tolerance), - ); - expect(allClose).toBe(true); + ends.forEach((end, index) => { + const result = elevation.calculateSphericalCoordinates(start, end); + const expected = expectedResults[index]; + expect(result.azimuth).toBeCloseTo(expected.azimuth); + expect(result.altitude).toBeCloseTo(expected.altitude); + }); + }); +}); + +describe('fillMissingAltitudes', () => { + test('should fill negative infinity altitude with the nearest non-negative infinity altitude', () => { + const points: elevation.SphericalPoint[] = [ + { altitude: -Infinity, azimuth: 0 }, + { altitude: 10, azimuth: 90 }, + { altitude: -Infinity, azimuth: 180 }, + { altitude: -Infinity, azimuth: 230 }, + { altitude: -Infinity, azimuth: 240 }, + { altitude: 20, azimuth: 270 }, + ]; + + elevation.fillMissingAltitudes(points); + + expect(points[0].altitude).toBe(10); + expect(points[2].altitude).toBe(10); + expect(points[3].altitude).toBe(20); + expect(points[4].altitude).toBe(20); + }); +}); + +describe('getMaxElevationAngles', () => { + test('should correctly calculate the maximum elevation angles for given elevation points and observer', () => { + const elevations: elevation.Point[] = [ + { x: 1, y: 1, z: 2 }, + { x: 1, y: -1, z: 4 }, + { x: -1, y: -1, z: 6 }, + { x: -1, y: 1, z: 8 }, + ]; + const observer: elevation.Point = { x: 0, y: 0, z: 0 }; + const numDirections = 20; + const result: elevation.SphericalPoint[] = elevation.getMaxElevationAngles(elevations, observer, numDirections); + console.log(result); + expect(result).to.be.an('array').that.has.lengthOf(numDirections); }); }); From 3ac9980b817bf6ba0f2305bceace7e84e49eb3ec Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Tue, 7 May 2024 08:01:29 +0200 Subject: [PATCH 12/38] Change type of elevation to list of points #5 --- src/index.ts | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4361d43..6a11b6c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,7 @@ import { rayTracingWebGL } from './rayTracingWebGL.js'; export default class Scene { simulationGeometries: Array; shadingGeometries: Array; - elevationRasters: Array; + elevationRaster: Array; elevationRasterMidpoint: elevation.Point; latitude: number; longitude: number; @@ -33,8 +33,8 @@ export default class Scene { constructor(latitude: number, longitude: number) { this.simulationGeometries = []; this.shadingGeometries = []; - this.elevationRasters = []; - this.elevationRasterMidpoint = { x: 0, y: 0 }; + this.elevationRaster = []; + this.elevationRasterMidpoint = { x: 0, y: 0, z: 0 }; this.latitude = latitude; this.longitude = longitude; } @@ -61,8 +61,8 @@ export default class Scene { this.shadingGeometries.push(geometry); } - addElevationRaster(raster: number[][], midpoint: elevation.Point) { - this.elevationRasters = raster; + addElevationRaster(raster: elevation.Point[], midpoint: elevation.Point) { + this.elevationRaster = raster; this.elevationRasterMidpoint = midpoint; } @@ -198,31 +198,14 @@ export default class Scene { */ async rayTrace(midpoints: Float32Array, normals: TypedArray, meshArray: Float32Array, numberSimulations: number) { let sunDirections = getRandomSunVectors(numberSimulations, this.latitude, this.longitude); - if (this.elevationRasters.length > 0) { + if (this.elevationRaster.length > 0) { let shadingElevationAngles = elevation.getMaxElevationAngles( - this.elevationRasters, - { - x: Math.round(this.elevationRasters.length - 1 / 2), - y: Math.round(this.elevationRasters[0].length - 1 / 2), - }, - [this.elevationRasters[this.elevationRasterMidpoint.x][this.elevationRasterMidpoint.y]], - // TODO: Right now this only includes one z value taken from the Midpoint, this can be extended - // to multiple z points + this.elevationRaster, + this.elevationRasterMidpoint, + numberSimulations, ); - for (let i = 0; i < sunDirections.spherical.length; i += 2) { - const shadingElevationAnglesIndex = Math.round( - (sunDirections.spherical[i + 1] / 2 / Math.PI) * shadingElevationAngles.length, - ); - if (shadingElevationAngles[0][shadingElevationAnglesIndex] >= sunDirections.spherical[i]) { - sunDirections.cartesian = new Float32Array([ - ...sunDirections.cartesian.slice(0, i), - ...sunDirections.cartesian.slice(i + 3), - ]); - console.log('One ray was shaded by a nearby mountain.'); - } - } } - + //TODO: add shading of elevation here return rayTracingWebGL(midpoints, normals, meshArray, sunDirections); } } From 67e84883c33b7e3ad578e1b1d2e3506e4eb0a795 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Tue, 7 May 2024 08:10:01 +0200 Subject: [PATCH 13/38] Correct numDirections parameter #5 --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 6a11b6c..0eda68e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -193,6 +193,7 @@ export default class Scene { * @param midpoints midpoints of triangles for which to calculate intensities * @param normals normals for each midpoint * @param meshArray array of vertices for the shading mesh + * @param numberSimulations * @return * @memberof Scene */ @@ -202,7 +203,7 @@ export default class Scene { let shadingElevationAngles = elevation.getMaxElevationAngles( this.elevationRaster, this.elevationRasterMidpoint, - numberSimulations, + sunDirections.spherical.length / 2, ); } //TODO: add shading of elevation here From 6f1a9a4c2d87250150881c575061148cc1285a03 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Tue, 7 May 2024 08:10:16 +0200 Subject: [PATCH 14/38] Correct function description #5 --- src/elevation.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/elevation.ts b/src/elevation.ts index ab248a4..7322f8a 100644 --- a/src/elevation.ts +++ b/src/elevation.ts @@ -63,9 +63,8 @@ export function calculateSphericalCoordinates(start: Point, end: Point): { azimu * Calculates the maximum heights visible from an observer in a set of directions. * Returns a list of spherical points of length numDirections. * @param elevation list of points with x,y,z component - * @param observer Point of interest, given as the indexes from the grid: [10,10] means that grid[10][10] is the point for - * which the elevation angles are calculated. - * @param numDirections Length of the returned list. + * @param observer Point of interest for which the elevation angles are calculated. + * @param numDirections Number of steps for the azimuth angle. * @returns */ export function getMaxElevationAngles(elevation: Point[], observer: Point, numDirections: number = 360): SphericalPoint[] { From 7da35ce5a15813b88a2115870641aa6d4ba49a81 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 08:07:46 +0200 Subject: [PATCH 15/38] Update data type of fetched irradiance #4 --- src/index.ts | 2 +- src/sun.ts | 2 +- tests/sun.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index a3eaf59..4302a5e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -209,7 +209,7 @@ export default class Scene { let sunDirections: any; if (typeof irradianceUrl === 'string' && isValidUrl(irradianceUrl)) { const irradiance = await sun.fetchIrradiance(irradianceUrl, this.latitude, this.longitude); - sunDirections = sun.convertSpericalToEuclidian(irradiance); + sunDirections = sun.convertSpericalToEuclidian(irradiance.data); } else if (typeof numberSimulations === 'undefined') { throw new Error('Either number simulations or a valid irradianceUrl must be given.'); } else { diff --git a/src/sun.ts b/src/sun.ts index a0249ad..763268e 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -68,7 +68,7 @@ export function convertSpericalToEuclidian(irradiance: number[][]): Float32Array return sunVectors; } -export async function fetchIrradiance(baseUrl: string, lat: number, lon: number): Promise { +export async function fetchIrradiance(baseUrl: string, lat: number, lon: number): Promise<{ [key: string]: any }> { //TODO: Implement fullURL from url, lat, lon const url = baseUrl + '/' + lat.toFixed(1) + '/' + lon.toFixed(1) + '.json'; try { diff --git a/tests/sun.test.ts b/tests/sun.test.ts index 00ffc44..2d92faf 100644 --- a/tests/sun.test.ts +++ b/tests/sun.test.ts @@ -42,8 +42,8 @@ describe('Test functionalities from sun.ts: ', () => { expect(allClose).toBe(true); }); test('Fetch irradiance json from openpv url.', async () => { - const data = await sun.fetchIrradiance('https://www.openpv.de/data/irradiance', 50.0, 11.0); + const result = await sun.fetchIrradiance('https://www.openpv.de/data/irradiance', 50.0, 11.0); - expect(data.length).toBeGreaterThan(5); + expect(result.data.length).toBeGreaterThan(5); }); }); From d2f95d3e21d7bb6194b143e0d28ebce6f102dcf3 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 08:29:57 +0200 Subject: [PATCH 16/38] Change to new irradiance API format #4 --- src/index.ts | 2 +- src/sun.ts | 37 +++++++++++++++++++++-------------- tests/sun.test.ts | 49 +++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4302a5e..a3eaf59 100644 --- a/src/index.ts +++ b/src/index.ts @@ -209,7 +209,7 @@ export default class Scene { let sunDirections: any; if (typeof irradianceUrl === 'string' && isValidUrl(irradianceUrl)) { const irradiance = await sun.fetchIrradiance(irradianceUrl, this.latitude, this.longitude); - sunDirections = sun.convertSpericalToEuclidian(irradiance.data); + sunDirections = sun.convertSpericalToEuclidian(irradiance); } else if (typeof numberSimulations === 'undefined') { throw new Error('Either number simulations or a valid irradianceUrl must be given.'); } else { diff --git a/src/sun.ts b/src/sun.ts index 763268e..edc73f7 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -1,5 +1,20 @@ import { getPosition } from 'suncalc'; +type SolarIrradianceData = { + metadata: { + description: string; + latitude: number; + longitude: number; + samples_phi: number; + samples_theta: number; + }; + data: Array<{ + theta: number; + phi: number; + radiance: number; + }>; +}; + /** * Creates arrays of sun vectors. "cartesian" is a vector of length 3*Ndates where every three entries make up one vector. * "spherical" is a vector of length 2*Ndates, where pairs of entries are altitude, azimuth. @@ -50,25 +65,19 @@ function getRandomDate(start: Date, end: Date): Date { * @param irradiance Vector of shape N_altitude x N_azimuth * @returns Vector of shape 3 x N_altitude x N_azimuth */ -export function convertSpericalToEuclidian(irradiance: number[][]): Float32Array { - const altitudeSamples = irradiance.length; - const azimuthSamples = irradiance[0].length; - const altitudeStepSize = altitudeSamples > 1 ? Math.PI / 2 / (altitudeSamples - 1) : Math.PI / 2; - const azimuthStepSize = (Math.PI * 2) / azimuthSamples; - const sunVectors = new Float32Array(altitudeSamples * azimuthSamples * 3); +export function convertSpericalToEuclidian(irradiance: SolarIrradianceData): Float32Array { + const sunVectors = new Float32Array(irradiance.metadata.samples_phi * irradiance.metadata.samples_theta * 3); let index = 0; - for (let i = 0; i < altitudeSamples; i++) { - for (let j = 0; j < azimuthSamples; j++) { - sunVectors[index] = irradiance[i][j] * Math.sin(i * altitudeStepSize) * Math.cos(j * azimuthStepSize); - sunVectors[index + 1] = irradiance[i][j] * Math.sin(i * altitudeStepSize) * Math.sin(j * azimuthStepSize); - sunVectors[index + 2] = irradiance[i][j] * Math.cos(i * altitudeStepSize); - index += 3; - } + for (let obj of irradiance.data) { + sunVectors[index] = obj.radiance * Math.sin(obj.theta) * Math.cos(obj.phi); + sunVectors[index + 1] = obj.radiance * Math.sin(obj.theta) * Math.sin(obj.phi); + sunVectors[index + 2] = obj.radiance * Math.cos(obj.theta); + index += 3; } return sunVectors; } -export async function fetchIrradiance(baseUrl: string, lat: number, lon: number): Promise<{ [key: string]: any }> { +export async function fetchIrradiance(baseUrl: string, lat: number, lon: number): Promise { //TODO: Implement fullURL from url, lat, lon const url = baseUrl + '/' + lat.toFixed(1) + '/' + lon.toFixed(1) + '.json'; try { diff --git a/tests/sun.test.ts b/tests/sun.test.ts index 2d92faf..bcffe9b 100644 --- a/tests/sun.test.ts +++ b/tests/sun.test.ts @@ -1,10 +1,51 @@ import { describe, expect, test } from 'vitest'; import * as sun from '../src/sun'; -const irradianceSpherical: number[][] = [ - [1, 2, 3, 4], - [1, 2, 3, 4], -]; +const irradianceSpherical = { + metadata: { description: '', latitude: 0, longitude: 0, samples_phi: 0, samples_theta: 0 }, + data: [ + { + theta: 0, + phi: 0, + radiance: 1, + }, + { + theta: 0, + phi: Math.PI / 2, + radiance: 2, + }, + { + theta: 0, + phi: Math.PI, + radiance: 3, + }, + { + theta: 0, + phi: (Math.PI * 3) / 2, + radiance: 4, + }, + { + theta: Math.PI / 2, + phi: 0, + radiance: 1, + }, + { + theta: Math.PI / 2, + phi: Math.PI / 2, + radiance: 2, + }, + { + theta: Math.PI / 2, + phi: Math.PI, + radiance: 3, + }, + { + theta: Math.PI / 2, + phi: (Math.PI * 3) / 2, + radiance: 4, + }, + ], +}; const irradianceEuclidian = new Float32Array([0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 1, 0, 0, 0, 2, 0, -3, 0, 0, 0, -4, 0]); From 339c809874cffd365dcbd500308ed27a50e6e549 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 09:03:59 +0200 Subject: [PATCH 17/38] Delete outdated comment #4 --- src/sun.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sun.ts b/src/sun.ts index edc73f7..17579ce 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -78,7 +78,6 @@ export function convertSpericalToEuclidian(irradiance: SolarIrradianceData): Flo } export async function fetchIrradiance(baseUrl: string, lat: number, lon: number): Promise { - //TODO: Implement fullURL from url, lat, lon const url = baseUrl + '/' + lat.toFixed(1) + '/' + lon.toFixed(1) + '.json'; try { const response = await fetch(url); From 17b710434f91c42648bda6230463e2685a3a1790 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 09:30:13 +0200 Subject: [PATCH 18/38] Move type definitions to utils #4 --- src/elevation.ts | 11 +---------- src/sun.ts | 16 +--------------- src/utils.ts | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/elevation.ts b/src/elevation.ts index 7322f8a..c908633 100644 --- a/src/elevation.ts +++ b/src/elevation.ts @@ -1,13 +1,4 @@ -export type Point = { - x: number; - y: number; - z: number; -}; - -export type SphericalPoint = { - altitude: number; - azimuth: number; -}; +import { Point, SphericalPoint } from './utils'; export function fillMissingAltitudes(maxAngles: SphericalPoint[]): void { // First copy the maxAngles to a newAngles list, so that changes diff --git a/src/sun.ts b/src/sun.ts index 17579ce..db233f6 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -1,19 +1,5 @@ import { getPosition } from 'suncalc'; - -type SolarIrradianceData = { - metadata: { - description: string; - latitude: number; - longitude: number; - samples_phi: number; - samples_theta: number; - }; - data: Array<{ - theta: number; - phi: number; - radiance: number; - }>; -}; +import { SolarIrradianceData, SphericalPoint } from './utils'; /** * Creates arrays of sun vectors. "cartesian" is a vector of length 3*Ndates where every three entries make up one vector. diff --git a/src/utils.ts b/src/utils.ts index 6e126b2..6c11518 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,3 +6,26 @@ export function isValidUrl(urlString: string): boolean { return false; } } +export type SolarIrradianceData = { + metadata: { + description: string; + latitude: number; + longitude: number; + samples_phi: number; + samples_theta: number; + }; + data: Array<{ + theta: number; + phi: number; + radiance: number; + }>; +}; +export type SphericalPoint = { + altitude: number; + azimuth: number; +}; +export type Point = { + x: number; + y: number; + z: number; +}; From 7ba184cf1a517048c6a6a50c29408edc8ea74ce2 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 09:32:40 +0200 Subject: [PATCH 19/38] Move type definition to utils #4 --- src/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index a3eaf59..9eeb05c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { viridis } from './colormaps'; import * as elevation from './elevation'; import * as sun from './sun'; import * as triangleUtils from './triangleUtils.js'; -import { isValidUrl } from './utils'; +import { Point, SphericalPoint, isValidUrl } from './utils'; // @ts-ignore import { rayTracingWebGL } from './rayTracingWebGL.js'; @@ -21,8 +21,8 @@ import { rayTracingWebGL } from './rayTracingWebGL.js'; export default class Scene { simulationGeometries: Array; shadingGeometries: Array; - elevationRaster: Array; - elevationRasterMidpoint: elevation.Point; + elevationRaster: Array; + elevationRasterMidpoint: Point; latitude: number; longitude: number; @@ -62,7 +62,7 @@ export default class Scene { this.shadingGeometries.push(geometry); } - addElevationRaster(raster: elevation.Point[], midpoint: elevation.Point) { + addElevationRaster(raster: Point[], midpoint: Point) { this.elevationRaster = raster; this.elevationRasterMidpoint = midpoint; } From d7aa394de326808217c14d6cddeafe0fb91eb37a Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 09:59:44 +0200 Subject: [PATCH 20/38] Convert to unified SpherialPoint type #4 --- src/elevation.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/elevation.ts b/src/elevation.ts index c908633..f978645 100644 --- a/src/elevation.ts +++ b/src/elevation.ts @@ -60,6 +60,7 @@ export function calculateSphericalCoordinates(start: Point, end: Point): { azimu */ export function getMaxElevationAngles(elevation: Point[], observer: Point, numDirections: number = 360): SphericalPoint[] { let maxAngles: SphericalPoint[] = Array.from({ length: numDirections }, (_, index) => ({ + radius: 1, azimuth: index * ((2 * Math.PI) / numDirections), altitude: -Infinity, })); From f12eaf0db31d769d37ce7a31a512b596283f37a5 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 10:00:16 +0200 Subject: [PATCH 21/38] Return sun vectors in unified Point types #4 --- src/index.ts | 2 +- src/sun.ts | 29 +++++++++++++++++------------ src/utils.ts | 1 + tests/elevation.test.ts | 21 +++++++++++---------- tests/sun.test.ts | 12 ++++++------ 5 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9eeb05c..3968c3e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { viridis } from './colormaps'; import * as elevation from './elevation'; import * as sun from './sun'; import * as triangleUtils from './triangleUtils.js'; -import { Point, SphericalPoint, isValidUrl } from './utils'; +import { Point, isValidUrl } from './utils'; // @ts-ignore import { rayTracingWebGL } from './rayTracingWebGL.js'; diff --git a/src/sun.ts b/src/sun.ts index db233f6..5ac1d80 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -1,5 +1,5 @@ import { getPosition } from 'suncalc'; -import { SolarIrradianceData, SphericalPoint } from './utils'; +import { Point, SolarIrradianceData, SphericalPoint } from './utils'; /** * Creates arrays of sun vectors. "cartesian" is a vector of length 3*Ndates where every three entries make up one vector. @@ -14,29 +14,34 @@ export function getRandomSunVectors( lat: number, lon: number, ): { - cartesian: Float32Array; - spherical: Float32Array; + cartesian: Point[]; + spherical: SphericalPoint[]; } { - const sunVectors = new Float32Array(Ndates * 3); - const sunVectorsSpherical = new Float32Array(Ndates * 2); + const sunVectors: Point[] = []; + const sunVectorsSpherical: SphericalPoint[] = []; var i = 0; while (i < Ndates) { let date = getRandomDate(new Date(2023, 1, 1), new Date(2023, 12, 31)); - const posSperical = getPosition(date, lat, lon); + const posSpherical = getPosition(date, lat, lon); // pos.altitude: sun altitude above the horizon in radians, // e.g. 0 at the horizon and PI/2 at the zenith (straight over your head) // pos. azimuth: sun azimuth in radians (direction along the horizon, measured // from south to west), e.g. 0 is south and Math.PI * 3/4 is northwest - if (posSperical.altitude < 0.1 || posSperical.altitude == Number.NaN) { + if (posSpherical.altitude < 0.1 || isNaN(posSpherical.altitude)) { continue; } - sunVectors[3 * i] = -Math.cos(posSperical.altitude) * Math.sin(posSperical.azimuth); - sunVectors[3 * i + 1] = -Math.cos(posSperical.altitude) * Math.cos(posSperical.azimuth); - sunVectors[3 * i + 2] = Math.sin(posSperical.altitude); - sunVectorsSpherical[2 * i] = posSperical.altitude; - sunVectorsSpherical[2 * i + 1] = posSperical.azimuth; + sunVectors.push({ + x: -Math.cos(posSpherical.altitude) * Math.sin(posSpherical.azimuth), + y: -Math.cos(posSpherical.altitude) * Math.cos(posSpherical.azimuth), + z: Math.sin(posSpherical.altitude), + }); + sunVectorsSpherical.push({ + radius: 1, + altitude: posSpherical.altitude, + azimuth: posSpherical.azimuth, + }); i += 1; } return { cartesian: sunVectors, spherical: sunVectorsSpherical }; diff --git a/src/utils.ts b/src/utils.ts index 6c11518..e73b151 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -21,6 +21,7 @@ export type SolarIrradianceData = { }>; }; export type SphericalPoint = { + radius: number; altitude: number; azimuth: number; }; diff --git a/tests/elevation.test.ts b/tests/elevation.test.ts index 6bb0715..cacb707 100644 --- a/tests/elevation.test.ts +++ b/tests/elevation.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from 'vitest'; import * as elevation from '../src/elevation'; +import { Point, SphericalPoint } from '../src/utils'; describe('calculateSphericalCoordinates', () => { test('should calculate the correct spherical coordinates', () => { @@ -26,13 +27,13 @@ describe('calculateSphericalCoordinates', () => { describe('fillMissingAltitudes', () => { test('should fill negative infinity altitude with the nearest non-negative infinity altitude', () => { - const points: elevation.SphericalPoint[] = [ - { altitude: -Infinity, azimuth: 0 }, - { altitude: 10, azimuth: 90 }, - { altitude: -Infinity, azimuth: 180 }, - { altitude: -Infinity, azimuth: 230 }, - { altitude: -Infinity, azimuth: 240 }, - { altitude: 20, azimuth: 270 }, + const points: SphericalPoint[] = [ + { radius: 1, altitude: -Infinity, azimuth: 0 }, + { radius: 1, altitude: 10, azimuth: 90 }, + { radius: 1, altitude: -Infinity, azimuth: 180 }, + { radius: 1, altitude: -Infinity, azimuth: 230 }, + { radius: 1, altitude: -Infinity, azimuth: 240 }, + { radius: 1, altitude: 20, azimuth: 270 }, ]; elevation.fillMissingAltitudes(points); @@ -46,15 +47,15 @@ describe('fillMissingAltitudes', () => { describe('getMaxElevationAngles', () => { test('should correctly calculate the maximum elevation angles for given elevation points and observer', () => { - const elevations: elevation.Point[] = [ + const elevations: Point[] = [ { x: 1, y: 1, z: 2 }, { x: 1, y: -1, z: 4 }, { x: -1, y: -1, z: 6 }, { x: -1, y: 1, z: 8 }, ]; - const observer: elevation.Point = { x: 0, y: 0, z: 0 }; + const observer: Point = { x: 0, y: 0, z: 0 }; const numDirections = 20; - const result: elevation.SphericalPoint[] = elevation.getMaxElevationAngles(elevations, observer, numDirections); + const result: SphericalPoint[] = elevation.getMaxElevationAngles(elevations, observer, numDirections); console.log(result); expect(result).to.be.an('array').that.has.lengthOf(numDirections); }); diff --git a/tests/sun.test.ts b/tests/sun.test.ts index bcffe9b..2518408 100644 --- a/tests/sun.test.ts +++ b/tests/sun.test.ts @@ -53,21 +53,21 @@ describe('Test functionalities from sun.ts: ', () => { const N = 50; let vectors = sun.getRandomSunVectors(N, 0, 0); test('Get Correct number of positions for cartesian coordiantes.', () => { - expect(vectors.cartesian.length).toStrictEqual(3 * N); + expect(vectors.cartesian.length).toStrictEqual(N); }); test('Get Correct number of positions for spherical coordiantes.', () => { - expect(vectors.spherical.length).toStrictEqual(2 * N); + expect(vectors.spherical.length).toStrictEqual(N); }); test('Get normalized sun vectors.', () => { - for (let i = 0; i < N / 3; i++) { - let length = vectors.cartesian[3 * i] ** 2 + vectors.cartesian[3 * i + 1] ** 2 + vectors.cartesian[3 * i + 2] ** 2; + for (let obj of vectors.cartesian) { + let length = obj.x ** 2 + obj.y ** 2 + obj.z ** 2; expect(length).to.closeTo(1, 0.001); } }); test('Sun is always above the horizon.', () => { for (let i = 0; i < N / 3; i++) { - let z = vectors.cartesian[3 * i + 2]; - let altitude = vectors.spherical[2 * i]; + let z = vectors.cartesian[i].z; + let altitude = vectors.spherical[i].altitude; expect(z).toBeGreaterThan(0); expect(altitude).toBeGreaterThan(0); } From 423ba055b323c8eaea15d84ff961ad37427da14d Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 13:01:27 +0200 Subject: [PATCH 22/38] Convert even more arrays to the point types #4 --- src/elevation.ts | 10 +++++-- src/index.ts | 46 +++++++++++++++--------------- src/rayTracingWebGL.ts | 16 +++++------ src/sun.ts | 55 +++++++++++++++++++----------------- src/utils.ts | 8 +++++- tests/elevation.test.ts | 6 ++-- tests/sun.test.ts | 62 +++++++++++++++++++++++++++++++++++------ 7 files changed, 130 insertions(+), 73 deletions(-) diff --git a/src/elevation.ts b/src/elevation.ts index f978645..ccb14e0 100644 --- a/src/elevation.ts +++ b/src/elevation.ts @@ -1,4 +1,4 @@ -import { Point, SphericalPoint } from './utils'; +import { CartesianPoint, SphericalPoint } from './utils'; export function fillMissingAltitudes(maxAngles: SphericalPoint[]): void { // First copy the maxAngles to a newAngles list, so that changes @@ -34,7 +34,7 @@ export function fillMissingAltitudes(maxAngles: SphericalPoint[]): void { * @param end * @returns azimuth from 0 to 2*PI and altitude from 0 to PI/2, where altitude = 0 is facing directly upwards */ -export function calculateSphericalCoordinates(start: Point, end: Point): { azimuth: number; altitude: number } { +export function calculateSphericalCoordinates(start: CartesianPoint, end: CartesianPoint): { azimuth: number; altitude: number } { const dx = end.x - start.x; const dy = end.y - start.y; const dz = end.z - start.z; @@ -58,7 +58,11 @@ export function calculateSphericalCoordinates(start: Point, end: Point): { azimu * @param numDirections Number of steps for the azimuth angle. * @returns */ -export function getMaxElevationAngles(elevation: Point[], observer: Point, numDirections: number = 360): SphericalPoint[] { +export function getMaxElevationAngles( + elevation: CartesianPoint[], + observer: CartesianPoint, + numDirections: number = 360, +): SphericalPoint[] { let maxAngles: SphericalPoint[] = Array.from({ length: numDirections }, (_, index) => ({ radius: 1, azimuth: index * ((2 * Math.PI) / numDirections), diff --git a/src/index.ts b/src/index.ts index 3968c3e..2383de3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { viridis } from './colormaps'; import * as elevation from './elevation'; import * as sun from './sun'; import * as triangleUtils from './triangleUtils.js'; -import { Point, isValidUrl } from './utils'; +import { CartesianPoint, Point, SphericalPoint, isValidUrl } from './utils'; // @ts-ignore import { rayTracingWebGL } from './rayTracingWebGL.js'; @@ -21,8 +21,8 @@ import { rayTracingWebGL } from './rayTracingWebGL.js'; export default class Scene { simulationGeometries: Array; shadingGeometries: Array; - elevationRaster: Array; - elevationRasterMidpoint: Point; + elevationRaster: Array; + elevationRasterMidpoint: CartesianPoint; latitude: number; longitude: number; @@ -62,7 +62,7 @@ export default class Scene { this.shadingGeometries.push(geometry); } - addElevationRaster(raster: Point[], midpoint: Point) { + addElevationRaster(raster: CartesianPoint[], midpoint: CartesianPoint) { this.elevationRaster = raster; this.elevationRasterMidpoint = midpoint; } @@ -195,7 +195,7 @@ export default class Scene { * @param normals normals for each midpoint * @param meshArray array of vertices for the shading mesh * @param numberSimulations number of random sun positions that are used for the simulation. Either numberSimulations or irradianceUrl need to be given. - * @param irradianceUrl url where a 2D json of irradiance values lies. To generate such a json, visit https://github.com/open-pv/irradiance + * @param diffuseIrradianceUrl url where a 2D json of irradiance values lies. To generate such a json, visit https://github.com/open-pv/irradiance * @return * @memberof Scene */ @@ -203,26 +203,28 @@ export default class Scene { midpoints: Float32Array, normals: TypedArray, meshArray: Float32Array, - numberSimulations: number | undefined, - irradianceUrl: string | undefined = undefined, + numberSimulations: number, + diffuseIrradianceUrl: string | undefined = undefined, ) { - let sunDirections: any; - if (typeof irradianceUrl === 'string' && isValidUrl(irradianceUrl)) { - const irradiance = await sun.fetchIrradiance(irradianceUrl, this.latitude, this.longitude); - sunDirections = sun.convertSpericalToEuclidian(irradiance); - } else if (typeof numberSimulations === 'undefined') { - throw new Error('Either number simulations or a valid irradianceUrl must be given.'); - } else { - sunDirections = sun.getRandomSunVectors(numberSimulations, this.latitude, this.longitude); + let directIrradiance: Point[] = []; + let diffuseIrradiance: Point[] = []; + let shadingElevationAngles: SphericalPoint[] = []; + + if (typeof diffuseIrradianceUrl === 'string' && isValidUrl(diffuseIrradianceUrl)) { + const diffuseIrradianceSpherical = await sun.fetchIrradiance(diffuseIrradianceUrl, this.latitude, this.longitude); + diffuseIrradiance = sun.convertSpericalToEuclidian(diffuseIrradianceSpherical); + } else if (typeof diffuseIrradianceUrl != 'undefined') { + throw new Error('The given url for diffuse Irradiance is not valid.'); } + directIrradiance = sun.getRandomSunVectors(numberSimulations, this.latitude, this.longitude); if (this.elevationRaster.length > 0) { - const shadingElevationAngles = elevation.getMaxElevationAngles( - this.elevationRaster, - this.elevationRasterMidpoint, - sunDirections.spherical.length / 2, - ); + shadingElevationAngles = elevation.getMaxElevationAngles(this.elevationRaster, this.elevationRasterMidpoint, 360); + directIrradiance = sun.shadeIrradianceFromElevation(directIrradiance, shadingElevationAngles); + if (diffuseIrradiance.length > 0) { + diffuseIrradiance = sun.shadeIrradianceFromElevation(diffuseIrradiance, shadingElevationAngles); + } } - //TODO: add shading of elevation here - return rayTracingWebGL(midpoints, normals, meshArray, sunDirections); + + return rayTracingWebGL(midpoints, normals, meshArray, directIrradiance); } } diff --git a/src/rayTracingWebGL.ts b/src/rayTracingWebGL.ts index d2ea5a3..189d1ec 100644 --- a/src/rayTracingWebGL.ts +++ b/src/rayTracingWebGL.ts @@ -1,4 +1,5 @@ import { TypedArray } from 'three'; +import { Point } from './utils'; function addToArray(ar1: Float32Array, ar2: Float32Array) { for (var i = 0; i < ar1.length; i++) { @@ -10,10 +11,7 @@ export function rayTracingWebGL( pointsArray: TypedArray, normals: TypedArray, trianglesArray: TypedArray, - sunDirections: { - cartesian: Float32Array; - spherical: Float32Array; - }, + sunDirections: Point[], ): Float32Array | null { const N_TRIANGLES = trianglesArray.length / 9; const width = pointsArray.length / 3; // Change this to the number of horizontal points in the grid @@ -181,14 +179,14 @@ export function rayTracingWebGL( var colorCodedArray = null; var isShadowedArray = null; - for (var i = 0; i < sunDirections.cartesian.length; i += 3) { - console.log('Simulating sun position #', i / 3, '/', sunDirections.cartesian.length / 3); + for (var i = 0; i < sunDirections.length; i += 1) { + console.log('Simulating sun position #', i, '/', sunDirections.length); // TODO: Iterate over sunDirection let sunDirectionUniformLocation = gl.getUniformLocation(program, 'u_sun_direction'); gl.uniform3fv(sunDirectionUniformLocation, [ - sunDirections.cartesian[i], - sunDirections.cartesian[i + 1], - sunDirections.cartesian[i + 2], + sunDirections[i].cartesian.x, + sunDirections[i].cartesian.y, + sunDirections[i].cartesian.z, ]); drawArraysWithTransformFeedback(gl, tf, gl.POINTS, N_POINTS); diff --git a/src/sun.ts b/src/sun.ts index 5ac1d80..7322b6f 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -1,5 +1,5 @@ import { getPosition } from 'suncalc'; -import { Point, SolarIrradianceData, SphericalPoint } from './utils'; +import { CartesianPoint, Point, SolarIrradianceData, SphericalPoint } from './utils'; /** * Creates arrays of sun vectors. "cartesian" is a vector of length 3*Ndates where every three entries make up one vector. @@ -9,16 +9,9 @@ import { Point, SolarIrradianceData, SphericalPoint } from './utils'; * @param lon * @returns */ -export function getRandomSunVectors( - Ndates: number, - lat: number, - lon: number, -): { - cartesian: Point[]; - spherical: SphericalPoint[]; -} { +export function getRandomSunVectors(Ndates: number, lat: number, lon: number): Point[] { const sunVectors: Point[] = []; - const sunVectorsSpherical: SphericalPoint[] = []; + var i = 0; while (i < Ndates) { let date = getRandomDate(new Date(2023, 1, 1), new Date(2023, 12, 31)); @@ -33,18 +26,20 @@ export function getRandomSunVectors( continue; } sunVectors.push({ - x: -Math.cos(posSpherical.altitude) * Math.sin(posSpherical.azimuth), - y: -Math.cos(posSpherical.altitude) * Math.cos(posSpherical.azimuth), - z: Math.sin(posSpherical.altitude), - }); - sunVectorsSpherical.push({ - radius: 1, - altitude: posSpherical.altitude, - azimuth: posSpherical.azimuth, + cartesian: { + x: -Math.cos(posSpherical.altitude) * Math.sin(posSpherical.azimuth), + y: -Math.cos(posSpherical.altitude) * Math.cos(posSpherical.azimuth), + z: Math.sin(posSpherical.altitude), + }, + spherical: { + radius: 1, + altitude: posSpherical.altitude, + azimuth: posSpherical.azimuth, + }, }); i += 1; } - return { cartesian: sunVectors, spherical: sunVectorsSpherical }; + return sunVectors; } function getRandomDate(start: Date, end: Date): Date { @@ -56,14 +51,18 @@ function getRandomDate(start: Date, end: Date): Date { * @param irradiance Vector of shape N_altitude x N_azimuth * @returns Vector of shape 3 x N_altitude x N_azimuth */ -export function convertSpericalToEuclidian(irradiance: SolarIrradianceData): Float32Array { - const sunVectors = new Float32Array(irradiance.metadata.samples_phi * irradiance.metadata.samples_theta * 3); - let index = 0; +export function convertSpericalToEuclidian(irradiance: SolarIrradianceData): Point[] { + const sunVectors: Point[] = []; + for (let obj of irradiance.data) { - sunVectors[index] = obj.radiance * Math.sin(obj.theta) * Math.cos(obj.phi); - sunVectors[index + 1] = obj.radiance * Math.sin(obj.theta) * Math.sin(obj.phi); - sunVectors[index + 2] = obj.radiance * Math.cos(obj.theta); - index += 3; + sunVectors.push({ + cartesian: { + x: obj.radiance * Math.sin(obj.theta) * Math.cos(obj.phi), + y: obj.radiance * Math.sin(obj.theta) * Math.sin(obj.phi), + z: obj.radiance * Math.cos(obj.theta), + }, + spherical: { radius: obj.radiance, azimuth: obj.phi, altitude: obj.theta }, + }); } return sunVectors; } @@ -82,3 +81,7 @@ export async function fetchIrradiance(baseUrl: string, lat: number, lon: number) throw error; } } + +export function shadeIrradianceFromElevation(directIrradiance: Point[], shadingElevationAngles: SphericalPoint[]): Point[] { + throw new Error('Function not implemented.'); +} diff --git a/src/utils.ts b/src/utils.ts index e73b151..9728cbd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -25,8 +25,14 @@ export type SphericalPoint = { altitude: number; azimuth: number; }; -export type Point = { + +export type CartesianPoint = { x: number; y: number; z: number; }; + +export type Point = { + cartesian: CartesianPoint; + spherical: SphericalPoint; +}; diff --git a/tests/elevation.test.ts b/tests/elevation.test.ts index cacb707..5d404d3 100644 --- a/tests/elevation.test.ts +++ b/tests/elevation.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest'; import * as elevation from '../src/elevation'; -import { Point, SphericalPoint } from '../src/utils'; +import { CartesianPoint, SphericalPoint } from '../src/utils'; describe('calculateSphericalCoordinates', () => { test('should calculate the correct spherical coordinates', () => { @@ -47,13 +47,13 @@ describe('fillMissingAltitudes', () => { describe('getMaxElevationAngles', () => { test('should correctly calculate the maximum elevation angles for given elevation points and observer', () => { - const elevations: Point[] = [ + const elevations: CartesianPoint[] = [ { x: 1, y: 1, z: 2 }, { x: 1, y: -1, z: 4 }, { x: -1, y: -1, z: 6 }, { x: -1, y: 1, z: 8 }, ]; - const observer: Point = { x: 0, y: 0, z: 0 }; + const observer: CartesianPoint = { x: 0, y: 0, z: 0 }; const numDirections = 20; const result: SphericalPoint[] = elevation.getMaxElevationAngles(elevations, observer, numDirections); console.log(result); diff --git a/tests/sun.test.ts b/tests/sun.test.ts index 2518408..4ca9de4 100644 --- a/tests/sun.test.ts +++ b/tests/sun.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from 'vitest'; import * as sun from '../src/sun'; +import { CartesianPoint } from '../src/utils'; const irradianceSpherical = { metadata: { description: '', latitude: 0, longitude: 0, samples_phi: 0, samples_theta: 0 }, @@ -47,38 +48,81 @@ const irradianceSpherical = { ], }; -const irradianceEuclidian = new Float32Array([0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 1, 0, 0, 0, 2, 0, -3, 0, 0, 0, -4, 0]); +const irradianceEuclidian: CartesianPoint[] = [ + { + x: 0, + y: 0, + z: 1, + }, + { + x: 0, + y: 0, + z: 2, + }, + { + x: 0, + y: 0, + z: 3, + }, + { + x: 0, + y: 0, + z: 4, + }, + { + x: 1, + y: 0, + z: 0, + }, + { + x: 0, + y: 2, + z: 0, + }, + { + x: -3, + y: 0, + z: 0, + }, + { + x: 0, + y: -4, + z: 0, + }, +]; describe('Test functionalities from sun.ts: ', () => { const N = 50; let vectors = sun.getRandomSunVectors(N, 0, 0); test('Get Correct number of positions for cartesian coordiantes.', () => { - expect(vectors.cartesian.length).toStrictEqual(N); + expect(vectors.length).toStrictEqual(N); }); test('Get Correct number of positions for spherical coordiantes.', () => { - expect(vectors.spherical.length).toStrictEqual(N); + expect(vectors.length).toStrictEqual(N); }); test('Get normalized sun vectors.', () => { - for (let obj of vectors.cartesian) { - let length = obj.x ** 2 + obj.y ** 2 + obj.z ** 2; + for (let obj of vectors) { + let length = obj.cartesian.x ** 2 + obj.cartesian.y ** 2 + obj.cartesian.z ** 2; expect(length).to.closeTo(1, 0.001); } }); test('Sun is always above the horizon.', () => { for (let i = 0; i < N / 3; i++) { - let z = vectors.cartesian[i].z; - let altitude = vectors.spherical[i].altitude; + let z = vectors[i].cartesian.z; + let altitude = vectors[i].spherical.altitude; expect(z).toBeGreaterThan(0); expect(altitude).toBeGreaterThan(0); } }); test('ConvertSpericalToEuclidian works right.', () => { const tolerance = 0.00001; - console.log(tolerance); const calculatedIrradianceEuclidian = sun.convertSpericalToEuclidian(irradianceSpherical); console.log(calculatedIrradianceEuclidian); const allClose = calculatedIrradianceEuclidian.every( - (value, index) => Math.abs(value - irradianceEuclidian[index]) <= tolerance, + (point, index) => + Math.abs(point.cartesian.x - irradianceEuclidian[index].x) <= tolerance && + Math.abs(point.cartesian.y - irradianceEuclidian[index].y) <= tolerance && + Math.abs(point.cartesian.z - irradianceEuclidian[index].z) <= tolerance, ); expect(allClose).toBe(true); }); From 38869f4a8bbeee8a3badefab5634a4ac9cf0c6e8 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 15:47:49 +0200 Subject: [PATCH 23/38] Add skeleton function for elevation shading #4 --- src/index.ts | 6 +++--- src/sun.ts | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2383de3..7a72986 100644 --- a/src/index.ts +++ b/src/index.ts @@ -219,12 +219,12 @@ export default class Scene { directIrradiance = sun.getRandomSunVectors(numberSimulations, this.latitude, this.longitude); if (this.elevationRaster.length > 0) { shadingElevationAngles = elevation.getMaxElevationAngles(this.elevationRaster, this.elevationRasterMidpoint, 360); - directIrradiance = sun.shadeIrradianceFromElevation(directIrradiance, shadingElevationAngles); + sun.shadeIrradianceFromElevation(directIrradiance, shadingElevationAngles); if (diffuseIrradiance.length > 0) { - diffuseIrradiance = sun.shadeIrradianceFromElevation(diffuseIrradiance, shadingElevationAngles); + sun.shadeIrradianceFromElevation(diffuseIrradiance, shadingElevationAngles); } } - return rayTracingWebGL(midpoints, normals, meshArray, directIrradiance); + return rayTracingWebGL(midpoints, normals, meshArray, directIrradiance, diffuseIrradiance); } } diff --git a/src/sun.ts b/src/sun.ts index 7322b6f..78335af 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -1,5 +1,5 @@ import { getPosition } from 'suncalc'; -import { CartesianPoint, Point, SolarIrradianceData, SphericalPoint } from './utils'; +import { Point, SolarIrradianceData, SphericalPoint } from './utils'; /** * Creates arrays of sun vectors. "cartesian" is a vector of length 3*Ndates where every three entries make up one vector. @@ -82,6 +82,4 @@ export async function fetchIrradiance(baseUrl: string, lat: number, lon: number) } } -export function shadeIrradianceFromElevation(directIrradiance: Point[], shadingElevationAngles: SphericalPoint[]): Point[] { - throw new Error('Function not implemented.'); -} +export function shadeIrradianceFromElevation(directIrradiance: Point[], shadingElevationAngles: SphericalPoint[]): void {} From bac77de6b8f1787730fb2d65ca03092aff75f3bf Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 15:48:39 +0200 Subject: [PATCH 24/38] Add diffuse rad parameter to WebGL function #4 --- src/rayTracingWebGL.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/rayTracingWebGL.ts b/src/rayTracingWebGL.ts index 189d1ec..dffac53 100644 --- a/src/rayTracingWebGL.ts +++ b/src/rayTracingWebGL.ts @@ -11,7 +11,8 @@ export function rayTracingWebGL( pointsArray: TypedArray, normals: TypedArray, trianglesArray: TypedArray, - sunDirections: Point[], + directRadiance: Point[], + diffuseRadiance: Point[], ): Float32Array | null { const N_TRIANGLES = trianglesArray.length / 9; const width = pointsArray.length / 3; // Change this to the number of horizontal points in the grid @@ -179,14 +180,14 @@ export function rayTracingWebGL( var colorCodedArray = null; var isShadowedArray = null; - for (var i = 0; i < sunDirections.length; i += 1) { - console.log('Simulating sun position #', i, '/', sunDirections.length); + for (var i = 0; i < directRadiance.length; i += 1) { + console.log('Simulating sun position #', i, '/', directRadiance.length); // TODO: Iterate over sunDirection let sunDirectionUniformLocation = gl.getUniformLocation(program, 'u_sun_direction'); gl.uniform3fv(sunDirectionUniformLocation, [ - sunDirections[i].cartesian.x, - sunDirections[i].cartesian.y, - sunDirections[i].cartesian.z, + directRadiance[i].cartesian.x, + directRadiance[i].cartesian.y, + directRadiance[i].cartesian.z, ]); drawArraysWithTransformFeedback(gl, tf, gl.POINTS, N_POINTS); From 1dea7fb028d04c1ef567497db7f50f823bc73c69 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 16:04:35 +0200 Subject: [PATCH 25/38] Repair errors from merge #4 --- src/index.ts | 11 +++++++++-- src/rayTracingWebGL.ts | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index a9be107..aeb8c59 100644 --- a/src/index.ts +++ b/src/index.ts @@ -107,7 +107,7 @@ export default class Scene { async calculate( numberSimulations: number = 80, - irradianceUrl: string | undefined + irradianceUrl: string | undefined, progressCallback: (progress: number, total: number) => void = (progress, total) => console.log(`Progress: ${progress}/${total}%`), ) { @@ -148,7 +148,14 @@ export default class Scene { } // Compute unique intensities - const intensities = await this.rayTrace(midpointsArray, normalsArray, meshArray, numberSimulations, irradianceUrl, progressCallback); + const intensities = await this.rayTrace( + midpointsArray, + normalsArray, + meshArray, + numberSimulations, + irradianceUrl, + progressCallback, + ); if (intensities === null) { throw new Error('Error raytracing in WebGL.'); diff --git a/src/rayTracingWebGL.ts b/src/rayTracingWebGL.ts index 09ff685..c3ed774 100644 --- a/src/rayTracingWebGL.ts +++ b/src/rayTracingWebGL.ts @@ -183,7 +183,7 @@ export function rayTracingWebGL( var isShadowedArray = null; for (var i = 0; i < directRadiance.length; i += 1) { - progressCallback(i, sunDirections.length); + progressCallback(i, directRadiance.length); // TODO: Iterate over sunDirection let sunDirectionUniformLocation = gl.getUniformLocation(program, 'u_sun_direction'); From 2e25ba0f6caae9dd4b9c1a18059a20e7d702cf56 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 13 May 2024 16:04:51 +0200 Subject: [PATCH 26/38] Stop running tests on draft PRs --- .github/workflows/tests.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bff6a5b..a3efee1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,27 +2,28 @@ name: Unit tests on: pull_request: - branches: [ main ] + branches: [main] workflow_dispatch: jobs: build: runs-on: ubuntu-latest + if: ${{ !github.event.pull_request.draft }} strategy: matrix: node-version: [18.x, 20.x] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} - - name: Install dependencies - run: yarn install + - name: Install dependencies + run: yarn install - - name: Run Vitest - run: yarn test + - name: Run Vitest + run: yarn test From 4d12be799ea178c8487fa80ca5289a0b1bba03c6 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Thu, 23 May 2024 11:42:46 +0200 Subject: [PATCH 27/38] Delete unused console.log #4 --- tests/elevation.test.ts | 1 - tests/sun.test.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/elevation.test.ts b/tests/elevation.test.ts index 5d404d3..de045d0 100644 --- a/tests/elevation.test.ts +++ b/tests/elevation.test.ts @@ -56,7 +56,6 @@ describe('getMaxElevationAngles', () => { const observer: CartesianPoint = { x: 0, y: 0, z: 0 }; const numDirections = 20; const result: SphericalPoint[] = elevation.getMaxElevationAngles(elevations, observer, numDirections); - console.log(result); expect(result).to.be.an('array').that.has.lengthOf(numDirections); }); }); diff --git a/tests/sun.test.ts b/tests/sun.test.ts index 4ca9de4..8688c6d 100644 --- a/tests/sun.test.ts +++ b/tests/sun.test.ts @@ -117,7 +117,6 @@ describe('Test functionalities from sun.ts: ', () => { test('ConvertSpericalToEuclidian works right.', () => { const tolerance = 0.00001; const calculatedIrradianceEuclidian = sun.convertSpericalToEuclidian(irradianceSpherical); - console.log(calculatedIrradianceEuclidian); const allClose = calculatedIrradianceEuclidian.every( (point, index) => Math.abs(point.cartesian.x - irradianceEuclidian[index].x) <= tolerance && From d011dd2c43f22b8a2a28cbceb47c9d8558c575b5 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Thu, 23 May 2024 11:43:12 +0200 Subject: [PATCH 28/38] Delete link to module #4 This was necessary to make the minimalApp repo work --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 6d37ebc..e3cf646 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "version": "0.0.1a1", "description": "Simulating Shadows for PV Potential Analysis on 3D Data on the GPU.", "main": "./dist/index.js", - "module": "./dist/index.mjs", "type": "module", "types": "./dist/index.d.ts", "files": [ From 2443424dba96b03134116bdb1bc1ae81d79c524c Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Thu, 23 May 2024 11:43:44 +0200 Subject: [PATCH 29/38] Some package updates #4 --- yarn.lock | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8192af4..b72ac3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3648,7 +3648,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -3675,7 +3684,14 @@ string-width@^7.0.0: get-east-asian-width "^1.0.0" strip-ansi "^7.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -4083,7 +4099,16 @@ why-is-node-running@^2.2.2: siginfo "^2.0.0" stackback "0.0.2" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From fbc96d413397b86e005ab3eb1656b3436f7c2c6f Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Thu, 23 May 2024 11:45:58 +0200 Subject: [PATCH 30/38] Delete unused logs #4 --- src/elevation.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/elevation.ts b/src/elevation.ts index ccb14e0..5e74031 100644 --- a/src/elevation.ts +++ b/src/elevation.ts @@ -71,7 +71,6 @@ export function getMaxElevationAngles( for (let point of elevation) { const { azimuth, altitude } = calculateSphericalCoordinates(observer, point); - console.log(azimuth, altitude); const closestIndex = Math.round(azimuth / ((2 * Math.PI) / numDirections)) % numDirections; if (altitude > maxAngles[closestIndex].altitude) { From 0f2eab6df1284d2f8ffd4a95cbc548d99977ab8b Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Thu, 23 May 2024 11:46:31 +0200 Subject: [PATCH 31/38] Rename scene object #4 To avoid confusion with three.js scene objects --- src/index.ts | 18 ++++++++++++------ tests/index.test.ts | 6 +++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index aeb8c59..9fd7de7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,13 +12,13 @@ import { rayTracingWebGL } from './rayTracingWebGL.js'; /** * This class holds all information about the scene that is simulated. - * A scene is typically equipped with the following attributes: + * A ShadingScene is typically equipped with the following attributes: * * A pair of coordinates to locate the scene * * Simulation geometries, where the PV potential is calculated * * Shading geometries, where no PV potential is calculated but which are * responsible for shading */ -export default class Scene { +export default class ShadingScene { simulationGeometries: Array; shadingGeometries: Array; elevationRaster: Array; @@ -32,6 +32,9 @@ export default class Scene { * @param longitude Longitude of the midpoint of the scene. */ constructor(latitude: number, longitude: number) { + if (latitude === undefined || longitude === undefined) { + throw new Error('Latitude and Longitude must be defined'); + } this.simulationGeometries = []; this.shadingGeometries = []; this.elevationRaster = []; @@ -147,6 +150,7 @@ export default class Scene { } } // Compute unique intensities + console.log('Calling this.rayTrace'); const intensities = await this.rayTrace( midpointsArray, @@ -174,10 +178,10 @@ export default class Scene { intensities[i] /= numberSimulations; } - return this.show(simulationGeometry, intensities); + return this.createMesh(simulationGeometry, intensities); } /** @ignore */ - show(subdividedGeometry: BufferGeometry, intensities: Float32Array) { + createMesh(subdividedGeometry: BufferGeometry, intensities: Float32Array): THREE.Mesh { const Npoints = subdividedGeometry.attributes.position.array.length / 9; var newColors = new Float32Array(Npoints * 9); for (var i = 0; i < Npoints; i++) { @@ -218,7 +222,7 @@ export default class Scene { normals: TypedArray, meshArray: Float32Array, numberSimulations: number, - diffuseIrradianceUrl: string | undefined = undefined, + diffuseIrradianceUrl: string | undefined, progressCallback: (progress: number, total: number) => void, ) { let directIrradiance: Point[] = []; @@ -231,7 +235,9 @@ export default class Scene { } else if (typeof diffuseIrradianceUrl != 'undefined') { throw new Error('The given url for diffuse Irradiance is not valid.'); } + console.log('Calling getRandomSunVectors'); directIrradiance = sun.getRandomSunVectors(numberSimulations, this.latitude, this.longitude); + console.log(directIrradiance); if (this.elevationRaster.length > 0) { shadingElevationAngles = elevation.getMaxElevationAngles(this.elevationRaster, this.elevationRasterMidpoint, 360); sun.shadeIrradianceFromElevation(directIrradiance, shadingElevationAngles); @@ -239,7 +245,7 @@ export default class Scene { sun.shadeIrradianceFromElevation(diffuseIrradiance, shadingElevationAngles); } } - + console.log('Calling rayTracingWebGL'); return rayTracingWebGL(midpoints, normals, meshArray, directIrradiance, diffuseIrradiance, progressCallback); } } diff --git a/tests/index.test.ts b/tests/index.test.ts index 06066c0..8600ea0 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'vitest'; -import Scene from '../src/index'; +import ShadingScene from '../src/index'; import { BufferAttribute, BufferGeometry } from 'three'; // Import Three.js components if available or mock them @@ -13,7 +13,7 @@ function createTestGeometry(positions: number[], normals: number[]): BufferGeome describe('Scene refineMesh', () => { test('subdivides triangle edges longer than maxLength', () => { - const scene = new Scene(0, 0); + const scene = new ShadingScene(0, 0); const positions = [0, 0, 0, 1, 0, 0, 0, 1, 0]; // Diagonal longer than 1 const normals = [0, 0, 1, 0, 0, 1, 0, 0, 1]; const testGeometry = createTestGeometry(positions, normals); @@ -22,7 +22,7 @@ describe('Scene refineMesh', () => { }); test('does not subdivide triangle edges shorter than or equal to maxLength', () => { - const scene = new Scene(0, 0); + const scene = new ShadingScene(0, 0); const positions = [0, 0, 0, 1, 0, 0, 0, 1, 0]; const normals = [0, 0, 1, 0, 0, 1, 0, 0, 1]; const testGeometry = createTestGeometry(positions, normals); From 0c3b00b25270df1a3b11fee99be9453f6ec7fba0 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Thu, 23 May 2024 11:47:07 +0200 Subject: [PATCH 32/38] Fix import of suncalc #4 This was needed to make the minimalApp run --- src/sun.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sun.ts b/src/sun.ts index 78335af..7fec4b0 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -1,4 +1,4 @@ -import { getPosition } from 'suncalc'; +import SunCalc from 'suncalc'; import { Point, SolarIrradianceData, SphericalPoint } from './utils'; /** @@ -10,18 +10,17 @@ import { Point, SolarIrradianceData, SphericalPoint } from './utils'; * @returns */ export function getRandomSunVectors(Ndates: number, lat: number, lon: number): Point[] { - const sunVectors: Point[] = []; + let sunVectors: Point[] = []; - var i = 0; + let i: number = 0; while (i < Ndates) { let date = getRandomDate(new Date(2023, 1, 1), new Date(2023, 12, 31)); - const posSpherical = getPosition(date, lat, lon); + const posSpherical = SunCalc.getPosition(date, lat, lon); // pos.altitude: sun altitude above the horizon in radians, // e.g. 0 at the horizon and PI/2 at the zenith (straight over your head) // pos. azimuth: sun azimuth in radians (direction along the horizon, measured // from south to west), e.g. 0 is south and Math.PI * 3/4 is northwest - if (posSpherical.altitude < 0.1 || isNaN(posSpherical.altitude)) { continue; } @@ -37,7 +36,7 @@ export function getRandomSunVectors(Ndates: number, lat: number, lon: number): P azimuth: posSpherical.azimuth, }, }); - i += 1; + i++; } return sunVectors; } From adb1bbf99ad169fb9726117a52a2dee75296db05 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 27 May 2024 08:20:33 +0200 Subject: [PATCH 33/38] Fix raytracing bug #13 --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 9fd7de7..f67968f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -246,6 +246,7 @@ export default class ShadingScene { } } console.log('Calling rayTracingWebGL'); + normals = normals.filter((_, index) => index % 9 < 3); return rayTracingWebGL(midpoints, normals, meshArray, directIrradiance, diffuseIrradiance, progressCallback); } } From 90b7094f796b77ce7bc03d83fbc6a4681278691b Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 27 May 2024 10:15:49 +0200 Subject: [PATCH 34/38] Create type SunVector #4 --- src/index.ts | 8 ++--- src/rayTracingWebGL.ts | 12 ++++---- src/sun.ts | 66 +++++++++++++++++++++++++++++------------- src/utils.ts | 5 ++++ tests/sun.test.ts | 12 ++++---- 5 files changed, 67 insertions(+), 36 deletions(-) diff --git a/src/index.ts b/src/index.ts index f67968f..3b5b3ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { viridis } from './colormaps'; import * as elevation from './elevation'; import * as sun from './sun'; import * as triangleUtils from './triangleUtils.js'; -import { CartesianPoint, Point, SphericalPoint, isValidUrl } from './utils'; +import { CartesianPoint, Point, SphericalPoint, SunVector, isValidUrl } from './utils'; // @ts-ignore import { rayTracingWebGL } from './rayTracingWebGL.js'; @@ -225,8 +225,8 @@ export default class ShadingScene { diffuseIrradianceUrl: string | undefined, progressCallback: (progress: number, total: number) => void, ) { - let directIrradiance: Point[] = []; - let diffuseIrradiance: Point[] = []; + let directIrradiance: SunVector[] = []; + let diffuseIrradiance: SunVector[] = []; let shadingElevationAngles: SphericalPoint[] = []; if (typeof diffuseIrradianceUrl === 'string' && isValidUrl(diffuseIrradianceUrl)) { @@ -239,7 +239,7 @@ export default class ShadingScene { directIrradiance = sun.getRandomSunVectors(numberSimulations, this.latitude, this.longitude); console.log(directIrradiance); if (this.elevationRaster.length > 0) { - shadingElevationAngles = elevation.getMaxElevationAngles(this.elevationRaster, this.elevationRasterMidpoint, 360); + shadingElevationAngles = elevation.getMaxElevationAngles(this.elevationRaster, this.elevationRasterMidpoint, 60); sun.shadeIrradianceFromElevation(directIrradiance, shadingElevationAngles); if (diffuseIrradiance.length > 0) { sun.shadeIrradianceFromElevation(diffuseIrradiance, shadingElevationAngles); diff --git a/src/rayTracingWebGL.ts b/src/rayTracingWebGL.ts index c3ed774..2dd0b18 100644 --- a/src/rayTracingWebGL.ts +++ b/src/rayTracingWebGL.ts @@ -1,5 +1,5 @@ import { TypedArray } from 'three'; -import { Point } from './utils'; +import { Point, SunVector } from './utils'; function addToArray(ar1: Float32Array, ar2: Float32Array) { for (var i = 0; i < ar1.length; i++) { @@ -11,8 +11,8 @@ export function rayTracingWebGL( pointsArray: TypedArray, normals: TypedArray, trianglesArray: TypedArray, - directRadiance: Point[], - diffuseRadiance: Point[], + directRadiance: SunVector[], + diffuseRadiance: SunVector[], progressCallback: (progress: number, total: number) => void, ): Float32Array | null { const N_TRIANGLES = trianglesArray.length / 9; @@ -188,9 +188,9 @@ export function rayTracingWebGL( // TODO: Iterate over sunDirection let sunDirectionUniformLocation = gl.getUniformLocation(program, 'u_sun_direction'); gl.uniform3fv(sunDirectionUniformLocation, [ - directRadiance[i].cartesian.x, - directRadiance[i].cartesian.y, - directRadiance[i].cartesian.z, + directRadiance[i].vector.cartesian.x, + directRadiance[i].vector.cartesian.y, + directRadiance[i].vector.cartesian.z, ]); drawArraysWithTransformFeedback(gl, tf, gl.POINTS, N_POINTS); diff --git a/src/sun.ts b/src/sun.ts index 7fec4b0..da3960a 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -1,5 +1,5 @@ import SunCalc from 'suncalc'; -import { Point, SolarIrradianceData, SphericalPoint } from './utils'; +import { Point, SolarIrradianceData, SphericalPoint, SunVector } from './utils'; /** * Creates arrays of sun vectors. "cartesian" is a vector of length 3*Ndates where every three entries make up one vector. @@ -9,8 +9,8 @@ import { Point, SolarIrradianceData, SphericalPoint } from './utils'; * @param lon * @returns */ -export function getRandomSunVectors(Ndates: number, lat: number, lon: number): Point[] { - let sunVectors: Point[] = []; +export function getRandomSunVectors(Ndates: number, lat: number, lon: number): SunVector[] { + let sunVectors: SunVector[] = []; let i: number = 0; while (i < Ndates) { @@ -25,16 +25,19 @@ export function getRandomSunVectors(Ndates: number, lat: number, lon: number): P continue; } sunVectors.push({ - cartesian: { - x: -Math.cos(posSpherical.altitude) * Math.sin(posSpherical.azimuth), - y: -Math.cos(posSpherical.altitude) * Math.cos(posSpherical.azimuth), - z: Math.sin(posSpherical.altitude), - }, - spherical: { - radius: 1, - altitude: posSpherical.altitude, - azimuth: posSpherical.azimuth, + vector: { + cartesian: { + x: -Math.cos(posSpherical.altitude) * Math.sin(posSpherical.azimuth), + y: -Math.cos(posSpherical.altitude) * Math.cos(posSpherical.azimuth), + z: Math.sin(posSpherical.altitude), + }, + spherical: { + radius: 1, + altitude: posSpherical.altitude, + azimuth: posSpherical.azimuth, + }, }, + isShadedByElevation: false, }); i++; } @@ -50,17 +53,20 @@ function getRandomDate(start: Date, end: Date): Date { * @param irradiance Vector of shape N_altitude x N_azimuth * @returns Vector of shape 3 x N_altitude x N_azimuth */ -export function convertSpericalToEuclidian(irradiance: SolarIrradianceData): Point[] { - const sunVectors: Point[] = []; +export function convertSpericalToEuclidian(irradiance: SolarIrradianceData): SunVector[] { + const sunVectors: SunVector[] = []; for (let obj of irradiance.data) { sunVectors.push({ - cartesian: { - x: obj.radiance * Math.sin(obj.theta) * Math.cos(obj.phi), - y: obj.radiance * Math.sin(obj.theta) * Math.sin(obj.phi), - z: obj.radiance * Math.cos(obj.theta), + vector: { + cartesian: { + x: obj.radiance * Math.sin(obj.theta) * Math.cos(obj.phi), + y: obj.radiance * Math.sin(obj.theta) * Math.sin(obj.phi), + z: obj.radiance * Math.cos(obj.theta), + }, + spherical: { radius: obj.radiance, azimuth: obj.phi, altitude: obj.theta }, }, - spherical: { radius: obj.radiance, azimuth: obj.phi, altitude: obj.theta }, + isShadedByElevation: false, }); } return sunVectors; @@ -81,4 +87,24 @@ export async function fetchIrradiance(baseUrl: string, lat: number, lon: number) } } -export function shadeIrradianceFromElevation(directIrradiance: Point[], shadingElevationAngles: SphericalPoint[]): void {} +export function shadeIrradianceFromElevation(Irradiance: SunVector[], shadingElevationAngles: SphericalPoint[]): void { + console.log('shadingElevationAngles', shadingElevationAngles); + + function findShadingElevation(azimuth: number): SphericalPoint { + return shadingElevationAngles.reduce((prev, curr) => + Math.abs(curr.azimuth - azimuth) < Math.abs(prev.azimuth - azimuth) ? curr : prev, + ); + } + + for (let i = Irradiance.length - 1; i >= 0; i--) { + const point = Irradiance[i]; + const shadingElevation = findShadingElevation(point.vector.spherical.azimuth); + console.log('point', point); + console.log('shadingElevation', shadingElevation); + if (shadingElevation && point.vector.spherical.altitude < shadingElevation.altitude) { + Irradiance[i].isShadedByElevation = true; + } + } + console.log('Irradiance after ELevation'); + console.log(Irradiance); +} diff --git a/src/utils.ts b/src/utils.ts index 9728cbd..425ed8e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -36,3 +36,8 @@ export type Point = { cartesian: CartesianPoint; spherical: SphericalPoint; }; + +export type SunVector = { + vector: Point; + isShadedByElevation: boolean; +}; diff --git a/tests/sun.test.ts b/tests/sun.test.ts index 8688c6d..16966a0 100644 --- a/tests/sun.test.ts +++ b/tests/sun.test.ts @@ -102,14 +102,14 @@ describe('Test functionalities from sun.ts: ', () => { }); test('Get normalized sun vectors.', () => { for (let obj of vectors) { - let length = obj.cartesian.x ** 2 + obj.cartesian.y ** 2 + obj.cartesian.z ** 2; + let length = obj.vector.cartesian.x ** 2 + obj.vector.cartesian.y ** 2 + obj.vector.cartesian.z ** 2; expect(length).to.closeTo(1, 0.001); } }); test('Sun is always above the horizon.', () => { for (let i = 0; i < N / 3; i++) { - let z = vectors[i].cartesian.z; - let altitude = vectors[i].spherical.altitude; + let z = vectors[i].vector.cartesian.z; + let altitude = vectors[i].vector.spherical.altitude; expect(z).toBeGreaterThan(0); expect(altitude).toBeGreaterThan(0); } @@ -119,9 +119,9 @@ describe('Test functionalities from sun.ts: ', () => { const calculatedIrradianceEuclidian = sun.convertSpericalToEuclidian(irradianceSpherical); const allClose = calculatedIrradianceEuclidian.every( (point, index) => - Math.abs(point.cartesian.x - irradianceEuclidian[index].x) <= tolerance && - Math.abs(point.cartesian.y - irradianceEuclidian[index].y) <= tolerance && - Math.abs(point.cartesian.z - irradianceEuclidian[index].z) <= tolerance, + Math.abs(point.vector.cartesian.x - irradianceEuclidian[index].x) <= tolerance && + Math.abs(point.vector.cartesian.y - irradianceEuclidian[index].y) <= tolerance && + Math.abs(point.vector.cartesian.z - irradianceEuclidian[index].z) <= tolerance, ); expect(allClose).toBe(true); }); From 500825c18235562c19f87d173f361fa0f475ae3b Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 27 May 2024 14:16:02 +0200 Subject: [PATCH 35/38] Create functionality for elevation shading #4 --- src/elevation.ts | 21 ++++++++++----------- src/index.ts | 11 +++++++++-- src/sun.ts | 6 ------ src/utils.ts | 13 +++++++++++++ tests/elevation.test.ts | 27 +++++++++++++++++++-------- 5 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/elevation.ts b/src/elevation.ts index 5e74031..bf760b5 100644 --- a/src/elevation.ts +++ b/src/elevation.ts @@ -29,25 +29,25 @@ export function fillMissingAltitudes(maxAngles: SphericalPoint[]): void { } /** - * + * Returns the vector from start to end in the Horizontal coordinate system * @param start * @param end - * @returns azimuth from 0 to 2*PI and altitude from 0 to PI/2, where altitude = 0 is facing directly upwards + * @returns */ -export function calculateSphericalCoordinates(start: CartesianPoint, end: CartesianPoint): { azimuth: number; altitude: number } { +export function calculateSphericalCoordinates(start: CartesianPoint, end: CartesianPoint): SphericalPoint { const dx = end.x - start.x; const dy = end.y - start.y; const dz = end.z - start.z; + if (dx == 0 && dy == 0) { + return { radius: 1, azimuth: 0, altitude: 0 }; + } const r = Math.sqrt(dx * dx + dy * dy + dz * dz); - const altitude = Math.acos(dz / r); - let azimuth = Math.atan2(dy, dx); + const altitude = Math.asin(dz / r); - if (azimuth < 0) { - azimuth += 2 * Math.PI; // Adjust azimuth to be from 0 to 2PI - } + let azimuth = (2 * Math.PI - Math.atan2(dy, dx)) % (2 * Math.PI); - return { azimuth, altitude }; + return { radius: 1, azimuth, altitude }; } /** @@ -61,7 +61,7 @@ export function calculateSphericalCoordinates(start: CartesianPoint, end: Cartes export function getMaxElevationAngles( elevation: CartesianPoint[], observer: CartesianPoint, - numDirections: number = 360, + numDirections: number, ): SphericalPoint[] { let maxAngles: SphericalPoint[] = Array.from({ length: numDirections }, (_, index) => ({ radius: 1, @@ -72,7 +72,6 @@ export function getMaxElevationAngles( for (let point of elevation) { const { azimuth, altitude } = calculateSphericalCoordinates(observer, point); const closestIndex = Math.round(azimuth / ((2 * Math.PI) / numDirections)) % numDirections; - if (altitude > maxAngles[closestIndex].altitude) { maxAngles[closestIndex].altitude = altitude; } diff --git a/src/index.ts b/src/index.ts index 3b5b3ca..bef3e5a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,7 @@ export default class ShadingScene { elevationRasterMidpoint: CartesianPoint; latitude: number; longitude: number; + elevationAzimuthDivisions: number; /** * @@ -41,6 +42,7 @@ export default class ShadingScene { this.elevationRasterMidpoint = { x: 0, y: 0, z: 0 }; this.latitude = latitude; this.longitude = longitude; + this.elevationAzimuthDivisions = 60; } /** @@ -65,7 +67,8 @@ export default class ShadingScene { this.shadingGeometries.push(geometry); } - addElevationRaster(raster: CartesianPoint[], midpoint: CartesianPoint) { + addElevationRaster(raster: CartesianPoint[], midpoint: CartesianPoint, azimuthDivisions: number) { + this.elevationAzimuthDivisions = azimuthDivisions; this.elevationRaster = raster; this.elevationRasterMidpoint = midpoint; } @@ -239,7 +242,11 @@ export default class ShadingScene { directIrradiance = sun.getRandomSunVectors(numberSimulations, this.latitude, this.longitude); console.log(directIrradiance); if (this.elevationRaster.length > 0) { - shadingElevationAngles = elevation.getMaxElevationAngles(this.elevationRaster, this.elevationRasterMidpoint, 60); + shadingElevationAngles = elevation.getMaxElevationAngles( + this.elevationRaster, + this.elevationRasterMidpoint, + this.elevationAzimuthDivisions, + ); sun.shadeIrradianceFromElevation(directIrradiance, shadingElevationAngles); if (diffuseIrradiance.length > 0) { sun.shadeIrradianceFromElevation(diffuseIrradiance, shadingElevationAngles); diff --git a/src/sun.ts b/src/sun.ts index da3960a..e98489f 100644 --- a/src/sun.ts +++ b/src/sun.ts @@ -88,8 +88,6 @@ export async function fetchIrradiance(baseUrl: string, lat: number, lon: number) } export function shadeIrradianceFromElevation(Irradiance: SunVector[], shadingElevationAngles: SphericalPoint[]): void { - console.log('shadingElevationAngles', shadingElevationAngles); - function findShadingElevation(azimuth: number): SphericalPoint { return shadingElevationAngles.reduce((prev, curr) => Math.abs(curr.azimuth - azimuth) < Math.abs(prev.azimuth - azimuth) ? curr : prev, @@ -99,12 +97,8 @@ export function shadeIrradianceFromElevation(Irradiance: SunVector[], shadingEle for (let i = Irradiance.length - 1; i >= 0; i--) { const point = Irradiance[i]; const shadingElevation = findShadingElevation(point.vector.spherical.azimuth); - console.log('point', point); - console.log('shadingElevation', shadingElevation); if (shadingElevation && point.vector.spherical.altitude < shadingElevation.altitude) { Irradiance[i].isShadedByElevation = true; } } - console.log('Irradiance after ELevation'); - console.log(Irradiance); } diff --git a/src/utils.ts b/src/utils.ts index 425ed8e..8ced9f3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -20,12 +20,25 @@ export type SolarIrradianceData = { radiance: number; }>; }; + +/** + * Spherical Coordinate of a point. + * + * Azimuth = 0 is North, Azimuth = PI/2 is East. + * + * Altitude = 0 is the horizon, Altitude = PI/2 is upwards / Zenith. + */ export type SphericalPoint = { radius: number; altitude: number; azimuth: number; }; +/** + * Cartesian Coordinate of a point. + * + * Positive X-Axis is north. + */ export type CartesianPoint = { x: number; y: number; diff --git a/tests/elevation.test.ts b/tests/elevation.test.ts index de045d0..125a5d6 100644 --- a/tests/elevation.test.ts +++ b/tests/elevation.test.ts @@ -6,14 +6,14 @@ describe('calculateSphericalCoordinates', () => { test('should calculate the correct spherical coordinates', () => { const start = { x: 0, y: 0, z: 0 }; const ends = [ - { x: 0, y: 0, z: 1 }, + { x: 0, y: 1, z: 1 }, { x: 1, y: 0, z: 1 }, { x: 0, y: -1, z: 1 }, ]; const expectedResults = [ - { altitude: 0, azimuth: 0 }, - { altitude: Math.PI / 4, azimuth: 0 }, { altitude: Math.PI / 4, azimuth: (3 / 2) * Math.PI }, + { altitude: Math.PI / 4, azimuth: 0 }, + { altitude: Math.PI / 4, azimuth: (1 / 2) * Math.PI }, ]; ends.forEach((end, index) => { @@ -48,14 +48,25 @@ describe('fillMissingAltitudes', () => { describe('getMaxElevationAngles', () => { test('should correctly calculate the maximum elevation angles for given elevation points and observer', () => { const elevations: CartesianPoint[] = [ - { x: 1, y: 1, z: 2 }, - { x: 1, y: -1, z: 4 }, - { x: -1, y: -1, z: 6 }, - { x: -1, y: 1, z: 8 }, + { x: 1, y: 0, z: 1 }, + { x: 0, y: -1, z: 1 }, + { x: -1, y: 0, z: 1 }, + { x: 0, y: 1, z: 1 }, + ]; + const expectedResult: SphericalPoint[] = [ + { radius: 1, altitude: Math.PI / 4, azimuth: 0 }, + { radius: 1, altitude: Math.PI / 4, azimuth: Math.PI / 2 }, + { radius: 1, altitude: Math.PI / 4, azimuth: Math.PI }, + { radius: 1, altitude: Math.PI / 4, azimuth: (3 * Math.PI) / 2 }, ]; const observer: CartesianPoint = { x: 0, y: 0, z: 0 }; - const numDirections = 20; + const numDirections = elevations.length; const result: SphericalPoint[] = elevation.getMaxElevationAngles(elevations, observer, numDirections); + console.log(result); expect(result).to.be.an('array').that.has.lengthOf(numDirections); + result.forEach((point, index) => { + expect(point.azimuth).toBeCloseTo(expectedResult[index].azimuth); + expect(point.altitude).toBeCloseTo(expectedResult[index].altitude); + }); }); }); From 29691d4ce50b2c83eddb2ecfb2b448f479cac219 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 27 May 2024 14:23:26 +0200 Subject: [PATCH 36/38] Do raytracing only for non shaded radiance #4 If it is shaded by the elevation, it is not used in this raytracing simulation --- src/rayTracingWebGL.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rayTracingWebGL.ts b/src/rayTracingWebGL.ts index 2dd0b18..6284e55 100644 --- a/src/rayTracingWebGL.ts +++ b/src/rayTracingWebGL.ts @@ -183,6 +183,9 @@ export function rayTracingWebGL( var isShadowedArray = null; for (var i = 0; i < directRadiance.length; i += 1) { + if (directRadiance[i].isShadedByElevation) { + continue; + } progressCallback(i, directRadiance.length); // TODO: Iterate over sunDirection From f42698d7d916d130be540f150c3b3357ad424c32 Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 27 May 2024 14:29:07 +0200 Subject: [PATCH 37/38] Add docstring to addShadingGeometry #4 --- src/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index bef3e5a..c3d448a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,7 +66,14 @@ export default class ShadingScene { addShadingGeometry(geometry: BufferGeometry) { this.shadingGeometries.push(geometry); } - + /** + * IMPORTANT: Make sure that the DEM and the building mesh are in the same units, for example 1 step in + * DEM coordinates should be equal to 1 step in the SimulationGeometry coordinates. + * @param raster List of Points with x,y,z coordinates, representing a digital elevation model (DEM) + * @param midpoint The point of the observer, ie the center of the building + * @param azimuthDivisions Number of divisions of the azimuth Angle, i.e. the list of the azimuth + * angle will be [0, ..., 2Pi] where the list has a lenght of azimuthDivisions + */ addElevationRaster(raster: CartesianPoint[], midpoint: CartesianPoint, azimuthDivisions: number) { this.elevationAzimuthDivisions = azimuthDivisions; this.elevationRaster = raster; From e087e1628b6018ac5716483ace44975db9f31adb Mon Sep 17 00:00:00 2001 From: Florian Kotthoff <74312290+FlorianK13@users.noreply.github.com> Date: Mon, 27 May 2024 15:06:45 +0200 Subject: [PATCH 38/38] Delete console log in test #4 --- tests/elevation.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/elevation.test.ts b/tests/elevation.test.ts index 125a5d6..c982dee 100644 --- a/tests/elevation.test.ts +++ b/tests/elevation.test.ts @@ -62,7 +62,6 @@ describe('getMaxElevationAngles', () => { const observer: CartesianPoint = { x: 0, y: 0, z: 0 }; const numDirections = elevations.length; const result: SphericalPoint[] = elevation.getMaxElevationAngles(elevations, observer, numDirections); - console.log(result); expect(result).to.be.an('array').that.has.lengthOf(numDirections); result.forEach((point, index) => { expect(point.azimuth).toBeCloseTo(expectedResult[index].azimuth);