UNPKG

@openpv/simshady

Version:

Simulating Shadows for PV Potential Analysis on 3D Data on the GPU.

1 lines 61.7 kB
{"version":3,"sources":["../src/index.ts","../src/colormaps.ts","../src/main.ts","../src/elevation.ts","../src/sun.ts","../src/triangleUtils.ts","../src/utils.ts","../src/rayTracingWebGL.ts"],"sourcesContent":["export * as colormaps from './colormaps';\nexport { ShadingScene } from './main';\n","import { Color, ColorMap } from './utils';\n\n/**\n * The viridis color Map, as defined in https://observablehq.com/@flimsyhat/webgl-color-maps\n * @param t parameter in [0,1] to go through viridis color map\n * @returns\n */\nexport function viridis(t: number): Color {\n t = Math.min(Math.max(t, 0), 1);\n const c0 = [0.2777273272234177, 0.005407344544966578, 0.3340998053353061];\n const c1 = [0.1050930431085774, 1.404613529898575, 1.384590162594685];\n const c2 = [-0.3308618287255563, 0.214847559468213, 0.09509516302823659];\n const c3 = [-4.634230498983486, -5.799100973351585, -19.33244095627987];\n const c4 = [6.228269936347081, 14.17993336680509, 56.69055260068105];\n const c5 = [4.776384997670288, -13.74514537774601, -65.35303263337234];\n const c6 = [-5.435455855934631, 4.645852612178535, 26.3124352495832];\n return [\n c0[0] + t * (c1[0] + t * (c2[0] + t * (c3[0] + t * (c4[0] + t * (c5[0] + t * c6[0]))))),\n c0[1] + t * (c1[1] + t * (c2[1] + t * (c3[1] + t * (c4[1] + t * (c5[1] + t * c6[1]))))),\n c0[2] + t * (c1[2] + t * (c2[2] + t * (c3[2] + t * (c4[2] + t * (c5[2] + t * c6[2]))))),\n ];\n}\n\n/**\n * Creates a color map function that interpolates between two colors.\n * @param {Object} colors - The input colors.\n * @param {Color} colors.c0 - The starting color.\n * @param {Color} colors.c1 - The ending color.\n * @returns {ColorMap} A function that takes a value t (0 to 1) and returns an interpolated color.\n */\nexport function interpolateTwoColors(colors: { c0: Color; c1: Color }): ColorMap {\n const { c0, c1 } = colors;\n\n return (t: number): Color => {\n // Clamp t between 0 and 1\n t = Math.min(Math.max(t, 0), 1);\n\n // Interpolate between c0 and c1 for R, G, and B channels\n const r = c0[0] * (1 - t) + c1[0] * t;\n const g = c0[1] * (1 - t) + c1[1] * t;\n const b = c0[2] * (1 - t) + c1[2] * t;\n\n return [r, g, b];\n };\n}\n\n/**\n * Creates a color map function that interpolates between three colors using quadratic interpolation.\n * @param {Object} colors - The input colors.\n * @param {Color} colors.c0 - The first color.\n * @param {Color} colors.c1 - The second color.\n * @param {Color} colors.c2 - The third color.\n * @returns {ColorMap} A function that takes a value t (0 to 1) and returns an interpolated color.\n */\nexport function interpolateThreeColors(colors: { c0: Color; c1: Color; c2: Color }): ColorMap {\n const { c0, c1, c2 } = colors;\n\n return (t: number): Color => {\n // Clamp t between 0 and 1\n t = Math.max(0, Math.min(1, t));\n\n function quadraticInterpolation(t: number, v0: number, v1: number, v2: number): number {\n return (1 - t) * (1 - t) * v0 + 2 * (1 - t) * t * v1 + t * t * v2;\n }\n\n const r = quadraticInterpolation(t, c0[0], c1[0], c2[0]);\n const g = quadraticInterpolation(t, c0[1], c1[1], c2[1]);\n const b = quadraticInterpolation(t, c0[2], c1[2], c2[2]);\n\n return [r, g, b];\n };\n}\n","import * as THREE from 'three';\nimport { BufferAttribute, BufferGeometry, TypedArray } from 'three';\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';\nimport { viridis } from './colormaps.js';\nimport * as elevation from './elevation.js';\nimport * as sun from './sun.js';\nimport * as triangleUtils from './triangleUtils.js';\nimport { CalculateParams, CartesianPoint, ColorMap, SolarIrradianceData, logNaNCount } from './utils.js';\n\n// @ts-ignore\nimport { rayTracingWebGL } from './rayTracingWebGL.js';\n\n/**\n * This class holds all information about the scene that is simulated.\n * A ShadingScene is typically equipped with the following attributes:\n * * Simulation geometry, where the PV potential is calculated\n * * Shading geometry, where no PV potential is calculated but which are\n * responsible for shading\n * * Solar Irradiance Data that contains information about incoming irradiance\n * in the format of sky domes\n * The Usage of this class and its methods is explained in the \"Getting Started\" Section\n * of this site.\n */\nexport class ShadingScene {\n /**\n * A Three.js geometry holding the main object of the scene,\n * see {@link ShadingScene.addShadingGeometry}\n */\n public simulationGeometry: BufferGeometry | undefined;\n /**\n * A Three.js geometry holding the objects that cause shading,\n * see {@link ShadingScene.addShadingGeometry}\n */\n public shadingGeometry: BufferGeometry | undefined;\n /**\n * A Raster (2D Matrix) holding rasterized data of the terrain,\n * see {@link ShadingScene.addElevationRaster}\n */\n public elevationRaster: Array<CartesianPoint>;\n /**\n * The midpoint of the elevationRaster, where the main object of\n * the scene is located.\n * See {@link ShadingScene.addElevationRaster}\n */\n private elevationRasterMidpoint: CartesianPoint;\n /**\n * A timeseries of Skydomes holding averaged direct and diffuse\n * irradiance data.\n * See {@link ShadingScene.addSolarIrradiance}\n */\n public solarIrradiance: SolarIrradianceData[] | null;\n private colorMap: (t: number) => [number, number, number];\n\n constructor() {\n this.elevationRaster = [];\n this.elevationRasterMidpoint = { x: 0, y: 0, z: 0 };\n this.solarIrradiance = null;\n this.colorMap = viridis;\n }\n\n /**\n * Adds a geometry as a target for the shading simulation.\n * For these geometries, the PV potential will be simulated.\n * This geometry will also be used as a shading geometry, hence\n * it is not needed to additionally add it by using `addShadingGeometry`.\n *\n * @param geometry Flat Buffer Array of a Three.js geometry, where three\n * consecutive numbers of the array represent one 3D point and nine consecutive\n * numbers represent one triangle.\n */\n addSimulationGeometry(geometry: BufferGeometry) {\n geometry = geometry.toNonIndexed();\n if (!this.simulationGeometry) {\n this.simulationGeometry = geometry;\n } else {\n this.simulationGeometry = BufferGeometryUtils.mergeGeometries([this.simulationGeometry, geometry]);\n }\n if (!this.shadingGeometry) {\n this.shadingGeometry = geometry;\n } else {\n this.shadingGeometry = BufferGeometryUtils.mergeGeometries([this.shadingGeometry, geometry]);\n }\n }\n\n /**\n * Adds a geometry as an outer geometry for the shading simulation.\n * These geometries are responsible for shading.\n *\n * @param geometry Flat Buffer Array of a Three.js geometry, where three\n * consecutive numbers of the array represent one 3D point and nine consecutive\n * numbers represent one triangle.\n */\n addShadingGeometry(geometry: BufferGeometry) {\n geometry = geometry.toNonIndexed();\n if (!this.shadingGeometry) {\n this.shadingGeometry = geometry;\n } else {\n this.shadingGeometry = BufferGeometryUtils.mergeGeometries([this.shadingGeometry, geometry]);\n }\n }\n /**\n * Add a elevation model to the simulation scene.\n * @param raster List of Points with x,y,z coordinates, representing a digital elevation model (DEM). It is\n * important that all values of x,y and z are given with same units. If x and y are given in lat / lon and\n * z is given in meters, this will result in wrong simulation Results.\n * @param midpoint The point of the observer, ie the center of the building\n * angle will be [0, ..., 2Pi] where the list has a lenght of azimuthDivisions\n */\n addElevationRaster(raster: CartesianPoint[], midpoint: CartesianPoint) {\n this.elevationRaster = raster;\n this.elevationRasterMidpoint = midpoint;\n }\n /**\n * Add data of solar irradiance to the scene. If it comes as a List of SolarIrradianceData,\n * this is interpreted as a time series of skydomes.\n *\n * **Important Note:** The first skydome of the list is used for the coloring of the final mesh!\n * Check out the type definition of {@link utils.SolarIrradianceData} for more information.\n * @param irradiance\n */\n addSolarIrradiance(irradiance: SolarIrradianceData[] | SolarIrradianceData) {\n // solarIrradiance is a time series of skydomes. If only one skydome is given\n // this one will be placed in a list\n if (!Array.isArray(irradiance)) {\n irradiance = [irradiance];\n }\n this.solarIrradiance = irradiance;\n }\n /**\n * Fetches a SolarIrradiance Object from a url and adds it to the\n * ShadingScene.\n * @param url\n */\n async addSolarIrradianceFromURL(url: string): Promise<void> {\n const response = await fetch(url);\n const data = await response.json();\n this.addSolarIrradiance(data);\n }\n\n /**\n * Change the Color Map that is used for the colors of the simulated Three.js mesh. This is\n * optional, the default colorMap is viridis (blue to green to yellow). Other options are\n * {@link colormaps.interpolateTwoColors} or {@link colormaps.interpolateThreeColors}\n * @param colorMap\n */\n addColorMap(colorMap: ColorMap) {\n this.colorMap = colorMap;\n }\n\n /** @ignore\n * Gets a BufferGeometry representing a mesh. Refines the triangles until all triangles\n * have sites smaller maxLength.\n */\n\n refineMesh(mesh: BufferGeometry, maxLength: number): BufferGeometry {\n const positions = mesh.attributes.position.array.slice();\n\n let newTriangles: number[] = [];\n let newNormals: number[] = [];\n // Iterate over triangles\n for (let i = 0; i < positions.length; i += 9) {\n let normal = triangleUtils.normal(positions, i);\n if (normal[2] < -0.9) {\n // Triangle is facing down, we can skip this\n continue;\n }\n let triangles = triangleUtils.subdivide(positions, i, maxLength);\n newTriangles = newTriangles.concat(triangles);\n // copy normal for each subdivided triangle\n newNormals = newNormals.concat(triangles.map((_, i) => normal[i % 3]));\n }\n\n let geometry = new BufferGeometry();\n const normalsArray = new Float32Array(newNormals);\n const positionArray = new Float32Array(newTriangles);\n geometry.setAttribute('position', new BufferAttribute(positionArray, 3));\n geometry.setAttribute('normal', new BufferAttribute(normalsArray, 3));\n geometry.attributes.position.needsUpdate = true;\n geometry.attributes.normal.needsUpdate = true;\n\n return geometry;\n }\n\n /**\n * This function is called as a last step, after the scene is fully build.\n * It runs the shading simulation and returns a THREE.js colored mesh.\n * The colors are chosen from the defined colorMap.\n * @param params The input object containing information about the simulation.\n\n * @returns A three.js colored mesh of the simulationGeometry. Each triangle gets an \n * attribute called intensity, that holds the annual electricity in kwh/m2 that a PV\n * system can generate. If {@link ShadingScene.solarIrradiance} is a timeseries of sky\n * domes, the resulting intensities attribute is a flattened Float32Array of length T*N.\n */\n async calculate(params: CalculateParams = {}) {\n const {\n solarToElectricityConversionEfficiency = 0.15,\n maxYieldPerSquareMeter = 1400 * 0.15,\n progressCallback = (progress, total) => console.log(`Progress: ${progress}/${total}%`),\n } = params;\n\n // Validate class parameters\n if (!this.validateClassParams()) {\n throw new Error(\n 'Invalid Class Parameters: You need to supply at least Shading Geometry, a Simulation Geometry, and Irradiance Data.',\n );\n }\n\n // Merge geometries\n\n this.simulationGeometry = this.refineMesh(this.simulationGeometry, 1.0);\n\n // Extract and validate geometry attributes\n // Flattened Mx3 array for M points\n const meshArray = <Float32Array>this.shadingGeometry.attributes.position.array;\n // Flattened Nx3 array for N points\n const points = this.simulationGeometry.attributes.position.array;\n // Flattened (N/3)x3 array for N/3 triangles, each triangle with a normal\n // Originally, every N point has one normal\n // Keeping only the first 3 elements out of every 9 so we have one normal\n // per triangle, not one normal per triangle edge point\n const normalsArray = this.simulationGeometry.attributes.normal.array.filter((_, index) => index % 9 < 3);\n\n const midpointsArray = this.computeMidpoints(points);\n\n // Check for NaN values in geometry data\n logNaNCount('midpoints', midpointsArray);\n logNaNCount('mesh', meshArray);\n\n // Perform ray tracing to calculate intensities\n const shadedScene = await this.rayTrace(\n midpointsArray,\n normalsArray,\n meshArray,\n this.solarIrradiance!, // Non-null assertion\n (i, total) => progressCallback(i + total, total),\n );\n\n const pvYield = sun.calculatePVYield(\n shadedScene,\n solarToElectricityConversionEfficiency,\n this.solarIrradiance[0].metadata.valid_timesteps_for_aggregation,\n );\n\n return this.createMesh(this.simulationGeometry, pvYield, maxYieldPerSquareMeter);\n }\n\n // Type Guard function to validate class parameters\n private validateClassParams(): this is {\n shadingGeometry: NonNullable<BufferGeometry>;\n simulationGeometry: NonNullable<BufferGeometry>;\n solarIrradiance: NonNullable<SolarIrradianceData>;\n } {\n return (\n this.shadingGeometry !== null &&\n this.shadingGeometry !== undefined &&\n this.simulationGeometry !== null &&\n this.simulationGeometry !== undefined &&\n this.solarIrradiance != null\n );\n }\n\n // Helper to compute midpoints of triangles and track NaN values\n private computeMidpoints(points: TypedArray): Float32Array {\n let midpoints: number[] = [];\n for (let i = 0; i < points.length; i += 9) {\n const midpoint = triangleUtils.midpoint(points, i);\n midpoints.push(...midpoint);\n }\n return new Float32Array(midpoints);\n }\n\n /**\n * @ignore\n * This function does two things:\n * - it assigns a color to the given simulationGeometry. The color is assigned\n * using the FIRST value of the intensities time series and the maxYieldPerSquareMeter\n * as upper boundary.\n * - it flattens the time series of intensities and sets them as attribute to the simulationGeometry\n *\n * @param simulationGeometry Nx9 Array with the edge points of N triangles\n * @param intensities T x N intensities, one for every triangle and every time step\n * @param maxYieldPerSquareMeter number defining the upper boundary of the color map\n * @returns Mesh with color and new attribute \"intensities\" that has length T*N\n */\n private createMesh(\n simulationGeometry: BufferGeometry,\n intensities: Float32Array[],\n maxYieldPerSquareMeter: number,\n ): THREE.Mesh {\n const Npoints = simulationGeometry.attributes.position.array.length / 9;\n var newColors = new Float32Array(Npoints * 9);\n\n for (var i = 0; i < Npoints; i++) {\n const col = this.colorMap(Math.min(maxYieldPerSquareMeter, intensities[0][i]) / maxYieldPerSquareMeter);\n for (let j = 0; j < 9; j += 3) {\n newColors[9 * i + j] = col[0];\n newColors[9 * i + j + 1] = col[1];\n newColors[9 * i + j + 2] = col[2];\n }\n }\n\n simulationGeometry.setAttribute('color', new THREE.Float32BufferAttribute(newColors, 3));\n var material = new THREE.MeshStandardMaterial({\n vertexColors: true,\n side: THREE.DoubleSide,\n });\n // In THREE, only Flat arrays can be set as an attribute\n const flatIntensities = new Float32Array(intensities.map((arr) => Array.from(arr)).flat());\n\n // Set the T*N Float32Array of intensities as attributes. On the website, this intensities\n // attribute needs to be divided again in T parts for the T time steps.\n simulationGeometry.setAttribute('intensities', new THREE.Float32BufferAttribute(flatIntensities, 1));\n let mesh = new THREE.Mesh(simulationGeometry, material);\n\n return mesh;\n }\n\n /** @ignore\n * This function returns a time series of intensities of shape T x N, with N the number of midpoints.\n * It includes the shading of geometries, the dot product of normal vector and sky segment vector,\n * and the radiation values from diffuse and direct irradiance.\n *\n * @param midpoints midpoints of triangles for which to calculate intensities\n * @param normals normals for each midpoint\n * @param meshArray array of vertices for the shading mesh\n * @param irradiance Time Series of sky domes\n * @return\n */\n private async rayTrace(\n midpoints: Float32Array,\n normals: TypedArray,\n meshArray: Float32Array,\n irradiance: SolarIrradianceData[],\n progressCallback: (progress: number, total: number) => void,\n ): Promise<Float32Array[]> {\n /**\n * Converts a list of solarIrradiance objects to a flat Float32Array containing only\n * the normalized cartesian coordinates (x, y, z) of the skysegments\n * and a list of Float32Arrays containing the absolute values of radiances at each\n * sky segment.\n * @param solarIrradiance\n * @returns skysegmentDirections as Sx3 flattened Float32Array with S being number of skysegments\n * skysegmentRadiation List of Float32Array, List has lenght T as the number of time steps,\n * each Float32Array has lenght S with on radiation value for each segment\n */\n function convertSolarIrradianceToFloat32Array(solarIrradiance: SolarIrradianceData[]): {\n skysegmentDirections: Float32Array;\n skysegmentRadiation: Float32Array[];\n } {\n const directions: number[] = [];\n const radiation: Float32Array[] = [];\n\n for (const entry of solarIrradiance) {\n const radiances: number[] = [];\n for (const point of entry.data) {\n const altRad = (point.altitude_deg * Math.PI) / 180;\n const azRad = (point.azimuth_deg * Math.PI) / 180;\n\n const x = Math.cos(altRad) * Math.sin(azRad);\n const y = Math.cos(altRad) * Math.cos(azRad);\n const z = Math.sin(altRad);\n\n directions.push(x, y, z);\n radiances.push(point.average_radiance_W_m2_sr);\n }\n radiation.push(new Float32Array(radiances));\n }\n\n return {\n skysegmentDirections: new Float32Array(directions),\n skysegmentRadiation: radiation,\n };\n }\n\n // Convert the existing array to a flat Float32Array\n const { skysegmentDirections, skysegmentRadiation } = convertSolarIrradianceToFloat32Array(irradiance);\n\n const shadedMaskScenes = await rayTracingWebGL(midpoints, normals, meshArray, skysegmentDirections, progressCallback);\n if (shadedMaskScenes === null) {\n throw new Error('Error occured when running the Raytracing in WebGL.');\n }\n //TODO Insert shading from ELevationRaster here\n if (this.elevationRaster.length > 0) {\n const elevationShadingMask = elevation.getElevationShadingMask(\n this.elevationRaster,\n this.elevationRasterMidpoint,\n // extract the altitude azimuth pairs from the first skysegment\n irradiance[0].data.map(({ altitude_deg, azimuth_deg }) => [altitude_deg, azimuth_deg]),\n );\n }\n\n //At this point we have one shaded mask array (length N) of normalized vectors\n //for the sky segment\n //And a time series of skySegmentRadiation (which are the absolute values of the sky segment\n // vectors)\n\n // Initializize Intensities of shape T x N, with one intensity per time step per midpoint\n let intensities = skysegmentRadiation.map(() => new Float32Array(midpoints.length / 3));\n\n //iterate over each sky segment\n for (let i = 0; i < shadedMaskScenes.length; i++) {\n // iterate over each midpoint\n for (let j = 0; j < midpoints.length; j++) {\n for (let t = 0; t < intensities.length; t++) {\n intensities[t][j] += shadedMaskScenes[i][j] * skysegmentRadiation[t][i];\n }\n }\n }\n return intensities;\n }\n}\n","import { CartesianPoint, SphericalPoint } from './utils';\n\nexport function fillMissingAltitudes(maxAngles: SphericalPoint[]): void {\n // First copy the maxAngles to a newAngles list, so that changes\n // in the list do not affect the algorithm\n let newAngles = maxAngles.map((angle) => ({ ...angle }));\n for (let i = 0; i < newAngles.length; i++) {\n if (newAngles[i].altitude != -Infinity) {\n continue;\n }\n let distance = 1;\n while (true) {\n let prevIndex = (i - distance + newAngles.length) % newAngles.length;\n let nextIndex = (i + distance) % newAngles.length;\n\n if (maxAngles[nextIndex].altitude !== -Infinity) {\n newAngles[i].altitude = maxAngles[nextIndex].altitude;\n break;\n } else if (maxAngles[prevIndex].altitude !== -Infinity) {\n newAngles[i].altitude = maxAngles[prevIndex].altitude;\n break;\n } else distance++;\n }\n }\n // Overwrite the maxAngles to make changes in this vector global\n for (let i = 0; i < maxAngles.length; i++) {\n maxAngles[i] = newAngles[i];\n }\n}\n\n/**\n * Returns the vector from start to end in the Horizontal coordinate system\n * @param start\n * @param end\n * @returns\n */\nexport function calculateSphericalCoordinates(start: CartesianPoint, end: CartesianPoint): SphericalPoint {\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n const dz = end.z - start.z;\n if (dx == 0 && dy == 0) {\n return { radius: 1, azimuth: 0, altitude: 0 };\n }\n\n const r = Math.sqrt(dx * dx + dy * dy + dz * dz);\n const altitude = Math.asin(dz / r);\n\n let azimuth = (2 * Math.PI - Math.atan2(dy, dx)) % (2 * Math.PI);\n\n return { radius: 1, azimuth, altitude };\n}\n\n/**\n * Calculates the maximum heights visible from an observer in a set of directions.\n * Returns a list of spherical points of length numDirections.\n * @param elevation list of points with x,y,z component\n * @param observer Point of interest for which the elevation angles are calculated.\n * @param directions List of altitude azimuth pairs. Angles in degree and conform to the\n * coordinate space definition of simshady.\n * @returns\n */\nexport function getElevationShadingMask(\n elevation: CartesianPoint[],\n observer: CartesianPoint,\n directions: [number, number][],\n): [number, number, number][] {\n const shadingMask: [number, number, number][] = [];\n\n for (const [altDeg, azDeg] of directions) {\n const azRad = (azDeg * Math.PI) / 180;\n const altRad = (altDeg * Math.PI) / 180;\n let maxAltitude = -Infinity;\n\n for (const point of elevation) {\n const { azimuth, altitude } = calculateSphericalCoordinates(observer, point);\n const azDiff = Math.abs(((azimuth - azRad + Math.PI) % (2 * Math.PI)) - Math.PI);\n if (azDiff < Math.PI / 180) {\n // approx 1 degree tolerance\n if (altitude > maxAltitude) maxAltitude = altitude;\n }\n }\n\n const isVisible = altRad > maxAltitude ? 1 : 0;\n shadingMask.push([altDeg, azDeg, isVisible]);\n }\n\n return shadingMask;\n}\n","import { SolarIrradianceData, SphericalPoint, SunVector } from './utils';\n\nexport async function fetchIrradiance(baseUrl: string, lat: number, lon: number): Promise<SolarIrradianceData> {\n const url = baseUrl + '/' + lat.toFixed(0) + '.0/' + lon.toFixed(0) + '.0.json';\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error('Network response was not ok');\n }\n const jsonData = await response.json();\n return jsonData;\n } catch (error) {\n console.error('There was a problem with the fetch operation:', error);\n throw error;\n }\n}\n\nexport function shadeIrradianceFromElevation(Irradiance: SunVector[], shadingElevationAngles: SphericalPoint[]): void {\n function findShadingElevation(azimuth: number): SphericalPoint {\n return shadingElevationAngles.reduce((prev, curr) =>\n Math.abs(curr.azimuth - azimuth) < Math.abs(prev.azimuth - azimuth) ? curr : prev,\n );\n }\n\n for (let i = Irradiance.length - 1; i >= 0; i--) {\n const point = Irradiance[i];\n const shadingElevation = findShadingElevation(point.vector.spherical.azimuth);\n if (shadingElevation && point.vector.spherical.altitude < shadingElevation.altitude) {\n Irradiance[i].isShadedByElevation = true;\n }\n }\n}\n\n/**\n * Converts intensities, which are solar energy per time per area, into\n * electric energy per time per area. This means multiplying every element of\n * intensities with the solarToElectricityConversionEfficiency.\n * @param intensities\n * @param solarToElectricityConversionEfficiency\n * @returns\n */\nexport function calculatePVYield(\n intensities: Float32Array[],\n solarToElectricityConversionEfficiency: number,\n totalHours: number,\n): Float32Array[] {\n // solarIrradiance.metadata.valid_timesteps_for_aggregation is hours over which you have to aggregate the mean intensity\n // 0.065 is solid angle (Raumwinkel) per skypixel in the HEALpix Level 4\n // 1/1000 is Watt to kiloWatt\n const factor = ((totalHours * 0.065) / 1000) * solarToElectricityConversionEfficiency;\n return intensities.map((arr) => new Float32Array(arr.map((x) => x * factor)));\n}\n","export type ArrayType =\n | Int8Array\n | Uint8Array\n | Uint8ClampedArray\n | Int16Array\n | Uint16Array\n | Int32Array\n | Uint32Array\n | Float32Array\n | Float64Array\n | number[];\n\nexport type Triangle = [number, number, number, number, number, number, number, number, number];\n\nexport function normalAndArea(positions: ArrayType, startIndex: number): [[number, number, number], number] {\n const [x0, y0, z0, x1, y1, z1, x2, y2, z2] = positions.slice(startIndex, startIndex + 9);\n\n const d01x = x1 - x0;\n const d01y = y1 - y0;\n const d01z = z1 - z0;\n const d02x = x2 - x0;\n const d02y = y2 - y0;\n const d02z = z2 - z0;\n\n const crsx = d01y * d02z - d01z * d02y;\n const crsy = d01z * d02x - d01x * d02z;\n const crsz = d01x * d02y - d01y * d02x;\n\n const crs_norm = Math.sqrt(crsx * crsx + crsy * crsy + crsz * crsz);\n const area = crs_norm / 2;\n const normal: [number, number, number] = [crsx / crs_norm, crsy / crs_norm, crsz / crs_norm];\n return [normal, area];\n}\n\nexport function normal(positions: ArrayType, startIndex: number): [number, number, number] {\n return normalAndArea(positions, startIndex)[0];\n}\n\nexport function area(positions: ArrayType, startIndex: number): number {\n return normalAndArea(positions, startIndex)[1];\n}\n\nexport function subdivide(positions: ArrayType, startIndex: number, threshold: number): number[] {\n const triangle = positions.slice(startIndex, startIndex + 9);\n const [x0, y0, z0, x1, y1, z1, x2, y2, z2] = triangle;\n\n const d01x = x1 - x0;\n const d01y = y1 - y0;\n const d01z = z1 - z0;\n const d02x = x2 - x0;\n const d02y = y2 - y0;\n const d02z = z2 - z0;\n const d12x = x2 - x1;\n const d12y = y2 - y1;\n const d12z = z2 - z1;\n\n const l01 = d01x * d01x + d01y * d01y + d01z * d01z;\n const l02 = d02x * d02x + d02y * d02y + d02z * d02z;\n const l12 = d12x * d12x + d12y * d12y + d12z * d12z;\n\n const longest = Math.max(l01, l02, l12);\n if (longest <= threshold * threshold) {\n return Array.from(triangle);\n }\n if (l01 == longest) {\n const xm = (x0 + x1) / 2;\n const ym = (y0 + y1) / 2;\n const zm = (z0 + z1) / 2;\n\n const tri1 = [x0, y0, z0, xm, ym, zm, x2, y2, z2];\n const tri2 = [x1, y1, z1, x2, y2, z2, xm, ym, zm];\n\n return subdivide(tri1, 0, threshold).concat(subdivide(tri2, 0, threshold));\n } else if (l02 == longest) {\n const xm = (x0 + x2) / 2;\n const ym = (y0 + y2) / 2;\n const zm = (z0 + z2) / 2;\n\n const tri1 = [x0, y0, z0, x1, y1, z1, xm, ym, zm];\n const tri2 = [x1, y1, z1, x2, y2, z2, xm, ym, zm];\n\n return subdivide(tri1, 0, threshold).concat(subdivide(tri2, 0, threshold));\n } else if (l12 == longest) {\n const xm = (x1 + x2) / 2;\n const ym = (y1 + y2) / 2;\n const zm = (z1 + z2) / 2;\n\n const tri1 = [x0, y0, z0, x1, y1, z1, xm, ym, zm];\n const tri2 = [x2, y2, z2, x0, y0, z0, xm, ym, zm];\n\n return subdivide(tri1, 0, threshold).concat(subdivide(tri2, 0, threshold));\n } else {\n throw new Error(\"No edge is longest, this shouldn't happen\");\n }\n}\n\nexport function midpoint(positions: ArrayType, startIndex: number): [number, number, number] {\n const [x0, y0, z0, x1, y1, z1, x2, y2, z2] = positions.slice(startIndex, startIndex + 9);\n return [(x0 + x1 + x2) / 3, (y0 + y1 + y2) / 3, (z0 + z1 + z2) / 3];\n}\n","/**\n * Solar irradiance data. `metadata` json holds the coordinates\n * where the irradiance data can be used. valid_timesteps_for_aggregation\n * is the number of hours of daylight in the considered timeframe. If\n * the skydome represents a whole year, this is about 8760.\n * \n * `data` holds a list of\n * sky segments, where altitude_deg and azimuth_deg define the position\n * and average_radiance_W_m2_sr defines the averaged incoming irradinace in W per m2 per sr. \n * Read more about it in the \"How does simshady work\" section of the docs page.\n *\n * Definition of the coordiante system in `simshady`:\n * Angles are expected in degree.\n * Azimuth = 0 is North, Azimuth = 90° is East.\n * Altitude = 0 is the horizon, Altitude = 90° is upwards / Zenith.\n * \n * Example Data:\n * ```json\n * {\n \"data\": [\n {\n \"altitude_deg\": 78.28,\n \"azimuth_deg\": 45.0,\n \"average_radiance_W_m2_sr\": 17.034986301369866\n },\n {\n \"altitude_deg\": 78.28,\n \"azimuth_deg\": 315.0,\n \"average_radiance_W_m2_sr\": 17.034986301369866\n },\n ...\n ],\n \"metadata\": {\n \"latitude\": 49.8,\n \"longitude\": 8.6,\n \"valid_timesteps_for_aggregation\": 8760,\n }\n}\n ```\n */\nexport type SolarIrradianceData = {\n metadata: { latitude: number; longitude: number; valid_timesteps_for_aggregation: number };\n data: Array<{ altitude_deg: number; azimuth_deg: number; average_radiance_W_m2_sr: number }>;\n};\n\n/**\n * Spherical Coordinate of a point.\n *\n * Azimuth = 0 is North, Azimuth = PI/2 is East.\n *\n * Altitude = 0 is the horizon, Altitude = PI/2 is upwards / Zenith.\n */\nexport type SphericalPoint = { radius: number; altitude: number; azimuth: number };\n\n/**\n * Cartesian Coordinate of a point.\n *\n * Positive X-axis is east.\n * Positive Y-axis is north.\n * Positive z-axis is upwards.\n */\nexport type CartesianPoint = { x: number; y: number; z: number };\n\nexport type Point = { cartesian: CartesianPoint; spherical: SphericalPoint };\n\n/**\n @ignore\n */\nexport type SunVector = { vector: Point; isShadedByElevation: boolean };\n\n/**\n * RGB values of a color, where all values are in intervall [0,1]\n */\nexport type Color = [number, number, number];\n\n/**\n * A color Map maps a value t in [0,1] to a color\n */\nexport type ColorMap = (t: number) => Color;\n\n/**\n * Interface for the parameter object for {@link index.ShadingScene.calculate}\n */\nexport interface CalculateParams {\n /**\n * Efficiency of the conversion from solar energy to electricity. This includes the\n * pv cell efficiency (about 20%) as well as the coverage density of PV panels per area\n * (about 70%).\n * Value in [0,1].\n * @defaultValue 0.15\n */\n solarToElectricityConversionEfficiency?: number;\n /**\n * Upper boundary of annual yield in kWh/m2/year. This value is used to normalize\n * the color of the returned three.js mesh.\n * In Germany this is something like 1400 kWh/m2/year multiplied with the given\n * solarToElectricityConversionEfficiency.\n * @defaultValue 1400*0.15\n */\n maxYieldPerSquareMeter?: number;\n /**\n * Callback function to indicate the progress of the simulation\n * @param progress number indicating the current progress\n * @param total number indicating the final number that progress needs to reach\n * @returns\n */\n progressCallback?: (progress: number, total: number) => void;\n}\n\n/**\n * @ignore\n * Mimics a for-loop but schedules each loop iteration using `setTimeout`, so that\n * event handles, react updates, etc. can run in-between\n */\nexport async function timeoutForLoop(start: number, end: number, body: (i: number) => void, step: number = 1) {\n return new Promise<void>((resolve) => {\n const inner = (i: number) => {\n body(i);\n i = i + step;\n if (i >= end) {\n resolve();\n } else {\n setTimeout(() => inner(i), 0);\n }\n };\n setTimeout(() => inner(start), 0);\n });\n}\n\n/**\n * @ignore\n * Helper to log NaN counts in data arrays. If no NaN values are found\n * nothing is logged.\n */\nexport function logNaNCount(name: string, array: Float32Array): void {\n const nanCount = Array.from(array).filter(isNaN).length;\n if (nanCount > 0) {\n console.log(`${nanCount}/${array.length} ${name} coordinates are NaN`);\n }\n}\n","import { TypedArray } from 'three';\nimport { timeoutForLoop } from './utils';\n\n/**\n * this function calculates\n * 1) for each surface point\n * 2) for each sky segment\n * 3) the dot product of normalized skysegment direction vector and the normal of the surface point\n * 4) if the sky segment is visible from the point\n * @param midpointsArray flattened Nx3 array with N the number of surface points for which shading is calculated\n * @param normals flattened Nx3 array, N times a vector of 3 components (the midpoint normals)\n * @param trianglesArray flattened Mx9 array with M the number of shading triangles\n * @param skysegmentDirectionArray flattened Sx3 array with S being the number of sky segments (normalized!)\n * @param progressCallback flattened Callback indicating the progress\n * @returns shadedMaskScenes: NxS array containing the dot product of the direction vector of the skysegment and the normal of the midpoint\n */\nexport async function rayTracingWebGL(\n midpointsArray: TypedArray,\n normals: TypedArray,\n trianglesArray: TypedArray,\n skysegmentDirectionArray: Float32Array,\n progressCallback: (progress: number, total: number) => void,\n): Promise<Float32Array[] | null> {\n const N_TRIANGLES = trianglesArray.length / 9;\n const width = midpointsArray.length / 3; // Change this to the number of horizontal points in the grid\n const N_POINTS = width;\n\n const gl = document.createElement('canvas').getContext('webgl2');\n if (!gl) {\n throw new Error('Browser does not support WebGL2');\n }\n\n // Vertex shader code\n const vertexShaderSource = `#version 300 es\n #define INFINITY 1000000.0\n precision highp float;\n\n\n uniform sampler2D u_triangles;\n uniform vec3 u_sun_direction;\n uniform int textureWidth;\n\n in vec3 a_position;\n in vec3 a_normal;\n\n out vec4 outColor;\n\n vec3 cross1(vec3 a, vec3 b) {\n vec3 c = vec3(0, 0, 0);\n c.x = a[1] * b[2] - a[2] * b[1];\n c.y = a[2] * b[0] - a[0] * b[2];\n c.z = a[0] * b[1] - a[1] * b[0];\n return c;\n }\n\n float TriangleIntersect( vec3 v0, vec3 v1, vec3 v2, vec3 rayOrigin, vec3 rayDirection, int isDoubleSided )\n {\n vec3 edge1 = v1 - v0;\n vec3 edge2 = v2 - v0;\n\n vec3 pvec = cross(rayDirection, edge2);\n\n float epsilon = 0.000001; // Add epsilon to avoid division by zero\n float det = dot(edge1, pvec);\n if (abs(det) < epsilon) // Check if det is too close to zero\n return INFINITY;\n\n float inv_det = 1.0 / det;\n if ( isDoubleSided == 0 && det < 0.0 ) \n return INFINITY;\n\n vec3 tvec = rayOrigin - v0;\n float u = dot(tvec, pvec) * inv_det;\n vec3 qvec = cross(tvec, edge1);\n float v = dot(rayDirection, qvec) * inv_det;\n float t = dot(edge2, qvec) * inv_det;\n float x = dot(pvec,pvec);\n return (u < 0.0 || u > 1.0 || v < 0.0 || u + v > 1.0 || t <= 0.01) ? INFINITY : t;\n\n }\n\n\n bool Calculate_Shading_at_Point(vec3 vertex_position, vec3 sun_direction) {\n float d;\n float t = INFINITY;\n bool is_shadowed = false;\n for (int i = 0; i < ${N_TRIANGLES}; i++) {\n int index = i * 3;\n int x = index % textureWidth;\n int y = index / textureWidth;\n vec3 v0 = texelFetch(u_triangles, ivec2(x, y), 0).rgb;\n\n index = i * 3 + 1;\n x = index % textureWidth;\n y = index / textureWidth;\n vec3 v1 = texelFetch(u_triangles, ivec2(x, y), 0).rgb;\n\n index = i * 3 + 2;\n x = index % textureWidth;\n y = index / textureWidth;\n vec3 v2 = texelFetch(u_triangles, ivec2(x, y), 0).rgb;\n d = TriangleIntersect(v0, v1, v2, vertex_position, sun_direction, 1);\n if (d < t && abs(d)>0.0001) {\n return true;\n\n }\n }\n return is_shadowed;\n }\n\n void main() {\n if (Calculate_Shading_at_Point(a_position.xyz, u_sun_direction)) {\n outColor = vec4(0, 0, 0, 0); // Shadowed\n } else {\n float intensity = abs(dot(a_normal.xyz, u_sun_direction));\n outColor = vec4(intensity, intensity, intensity, intensity); // Not shadowed\n }\n\n }`;\n\n // Fragment shader code\n const fragmentShaderSource = `#version 300 es\n precision highp float;\n void main() {\n }\n `;\n\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);\n const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);\n\n const program = createProgram(gl, vertexShader, fragmentShader, ['outColor']);\n\n const vao = gl.createVertexArray();\n gl.bindVertexArray(vao);\n\n var maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);\n\n var textureWidth = Math.min(3 * N_TRIANGLES, Math.floor(maxTextureSize / 9) * 9);\n var textureHeight = Math.ceil((3 * N_TRIANGLES) / textureWidth);\n\n const colorBuffer = makeBuffer(gl, N_POINTS * 16);\n const tf = makeTransformFeedback(gl, colorBuffer);\n // gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);\n // gl.pixelStorei(gl.PACK_ALIGNMENT, 1);\n\n gl.useProgram(program);\n\n var texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n\n var alignedTrianglesArray;\n if (textureHeight == 1) {\n alignedTrianglesArray = trianglesArray;\n } else {\n alignedTrianglesArray = new Float32Array(textureWidth * textureHeight * 3);\n\n for (var i = 0; i < 3 * N_TRIANGLES; i++) {\n var x = (3 * i) % textureWidth;\n var y = Math.floor((3 * i) / textureWidth);\n var index = y * textureWidth + x;\n for (var j = 0; j < 3; j++) {\n alignedTrianglesArray[index + j] = trianglesArray[3 * i + j];\n }\n }\n }\n\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB32F, textureWidth, textureHeight, 0, gl.RGB, gl.FLOAT, alignedTrianglesArray);\n\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.bindTexture(gl.TEXTURE_2D, null);\n\n var u_trianglesLocation = gl.getUniformLocation(program, 'u_triangles');\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.uniform1i(u_trianglesLocation, 0);\n\n var u_textureWidth = gl.getUniformLocation(program, 'textureWidth');\n gl.uniform1i(u_textureWidth, textureWidth);\n\n const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');\n const normalAttributeLocation = gl.getAttribLocation(program, 'a_normal');\n\n const positionBuffer = makeBufferAndSetAttribute(gl, midpointsArray, positionAttributeLocation);\n const normalBuffer = makeBufferAndSetAttribute(gl, normals, normalAttributeLocation);\n\n //var colorCodedArray = null;\n var shadedMaskScenes: Float32Array[] = [];\n // each element of this shadedIrradianceScenes represents the shading\n // caused by one ray of the irradiance list\n\n await timeoutForLoop(\n 0,\n skysegmentDirectionArray.length,\n (i) => {\n // For the progress callback, we need to report the current vector index (i/3)\n // out of the total number of vectors (length/3)\n progressCallback(Math.floor(i / 3), Math.floor(skysegmentDirectionArray.length / 3));\n\n let x = skysegmentDirectionArray[i];\n let y = skysegmentDirectionArray[i + 1];\n let z = skysegmentDirectionArray[i + 2];\n\n let magnitude = Math.sqrt(x * x + y * y + z * z);\n\n // Handle zero or near-zero magnitude\n if (magnitude < 1e-10) {\n // Default direction if vector is effectively zero\n x = 0;\n y = 0;\n z = 1;\n } else {\n x = x / magnitude;\n y = y / magnitude;\n z = z / magnitude;\n }\n\n let sunDirectionUniformLocation = gl.getUniformLocation(program, 'u_sun_direction');\n gl.uniform3fv(sunDirectionUniformLocation, [x, y, z]);\n\n drawArraysWithTransformFeedback(gl, tf, gl.POINTS, N_POINTS);\n let colorCodedArray = getResults(gl, colorBuffer, N_POINTS);\n\n // Store the result at the correct index in shadedMaskScenes (i/3 for the vector index)\n shadedMaskScenes[Math.floor(i / 3)] = colorCodedArray.filter((_, index) => (index + 1) % 4 === 0);\n },\n 3,\n ); // Add step parameter of 3 for the loop\n\n gl.deleteTexture(texture);\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n gl.deleteProgram(program);\n gl.deleteBuffer(positionBuffer);\n gl.deleteBuffer(normalBuffer);\n gl.deleteTransformFeedback(tf);\n gl.deleteBuffer(colorBuffer);\n return shadedMaskScenes;\n}\n\nfunction getResults(gl: WebGL2RenderingContext, buffer: WebGLBuffer | null, N_POINTS: number) {\n let results = new Float32Array(N_POINTS * 4);\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.getBufferSubData(\n gl.ARRAY_BUFFER,\n 0, // byte offset into GPU buffer,\n results,\n );\n\n gl.bindBuffer(gl.ARRAY_BUFFER, null); // productBuffer was still bound to ARRAY_BUFFER so unbind it\n return results;\n}\n\nfunction createShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader | null {\n const shader = gl.createShader(type);\n if (shader === null) {\n return null;\n }\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);\n if (success) {\n return shader;\n }\n console.error(gl.getShaderInfoLog(shader));\n gl.deleteShader(shader);\n return null;\n}\n\nfunction createProgram(\n gl: WebGL2RenderingContext,\n vertexShader: WebGLShader | null,\n fragmentShader: WebGLShader | null,\n variables_of_interest: Iterable<string>,\n): WebGLProgram {\n const program = gl.createProgram();\n\n if (program === null || vertexShader === null || fragmentShader === null) {\n throw new Error('abortSimulation');\n } else {\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n gl.transformFeedbackVaryings(program, variables_of_interest, gl.SEPARATE_ATTRIBS);\n gl.linkProgram(program);\n const success = gl.getProgramParameter(program, gl.LINK_STATUS);\n if (success) {\n return program;\n }\n console.error(gl.getProgramInfoLog(program));\n gl.deleteProgram(program);\n }\n throw new Error('Program compilation error.');\n}\n\nfunction makeBuffer(gl: WebGL2RenderingContext, sizeOrData: BufferSource | number) {\n const buf = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n //@ts-ignore\n gl.bufferData(gl.ARRAY_BUFFER, sizeOrData, gl.DYNAMIC_DRAW);\n return buf;\n}\n\nfunction makeTransformFeedback(gl: WebGL2RenderingContext, buffer: WebGLBuffer | null) {\n const tf = gl.createTransformFeedback();\n gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);\n gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buffer);\n return tf;\n}\n\nfunction makeBufferAndSetAttribute(gl: WebGL2RenderingContext, data: ArrayBuffer, loc: number): WebGLBuffer | null {\n const buf = makeBuffer(gl, data);\n // setup our attributes to tell WebGL how to pull\n // the data from the buffer above to the attribute\n gl.enableVertexAttribArray(loc);\n gl.vertexAttribPointer(\n loc,\n 3, // size (num components)\n gl.FLOAT, // type of data in buffer\n false, // normalize\n 0, // stride (0 = auto)\n 0, // offset\n );\n return buf;\n}\n\nfunction drawArraysWithTransformFeedback(\n gl: WebGL2RenderingContext,\n tf: WebGLTransformFeedback | null,\n primitiveType: number,\n count: number,\n) {\n // turn of using the fragment shader\n gl.enable(gl.RASTERIZER_DISCARD);\n\n gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);\n gl.beginTransformFeedback(gl.POINTS);\n gl.drawArrays(primitiveType, 0, count);\n gl.endTransformFeedback();\n gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);\n\n // unbind the buffer from the TRANFORM_FEEDBACK_BUFFER\n gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);\n\n // turn on using fragment shaders again\n gl.disable(gl.RASTERIZER_DISCARD);\n}\n"],"mappings":"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