diff --git a/src/shaders/matterjet.glsl b/src/shaders/matterjet.glsl index 42b9ed115..a4a4f0e31 100644 --- a/src/shaders/matterjet.glsl +++ b/src/shaders/matterjet.glsl @@ -24,6 +24,10 @@ uniform sampler2D depthSampler;// the depth map of the camera uniform float time; +uniform mat4 inverseRotation; + +uniform float dipoleTilt; + #include "./utils/camera.glsl"; #include "./utils/object.glsl"; @@ -36,79 +40,189 @@ uniform float time; #include "./utils/removeAxialTilt.glsl"; -// from https://www.shadertoy.com/view/MtcXWr -bool rayIntersectCone(vec3 rayOrigin, vec3 rayDir, vec3 tipPosition, vec3 orientation, float coneAngle, out float t1, out float t2) { - vec3 co = rayOrigin - tipPosition; - - float a = dot(rayDir, orientation)*dot(rayDir, orientation) - coneAngle*coneAngle; - float b = 2. * (dot(rayDir, orientation)*dot(co, orientation) - dot(rayDir, co)*coneAngle*coneAngle); - float c = dot(co, orientation)*dot(co, orientation) - dot(co, co)*coneAngle*coneAngle; - - float det = b*b - 4.*a*c; - if (det < 0.) return false; - - det = sqrt(det); - t1 = (-b - det) / (2. * a); - t2 = (-b + det) / (2. * a); - - // This is a bit messy; there ought to be a more elegant solution. - float t = t1; - if (t < 0. || t2 > 0. && t2 < t) t = t2; - if (t < 0.) return false; - - vec3 cp = rayOrigin + t*rayDir - tipPosition; - float h = dot(cp, orientation); - - vec3 n = normalize(cp * dot(orientation, cp) / dot(cp, cp) - orientation); - - return true; -} - -// see https://www.shadertoy.com/view/tslcW4 -const float a=1.0; -const float b=.1759; -const float PI=3.14159265359; - -float spiralSDF(float theta, float radius) { - - float t=theta; - // t=(t+PI)/(2.*PI); - float r=radius; - - float n=(log(r/a)/b-t)/(2.*PI); - - // Cap the spiral - // float nm = (log(0.11)/b-t)/(2.0*PI); - // n = min(n,nm); - // return (n+1.0)/100.0; - float upper_r=a*exp(b*(t+2.*PI*ceil(n))); - float lower_r=a*exp(b*(t+2.*PI*floor(n))); - // float lower_r = 0.0; - - return min(abs(upper_r-r), abs(r-lower_r)); +float sdSpiral(vec3 p, float coneTheta, float frequency) { + // Y-axis is now the spiral direction + float spiralPos = p.y; // Using Y instead of Z + float radius = spiralPos * tan(coneTheta); + float theta = frequency * spiralPos; + + // Spiral now wraps around Y-axis + vec3 spiralPoint = vec3( + radius * cos(theta), + spiralPos, // Y position is our spiral progress + radius * sin(theta) // Z component completes the circular motion + ); + + return length(p - spiralPoint); } -float spiralDensity(vec3 pointOnCone, vec3 coneAxis, float coneMaxHeight) { - // Then we rotate that point so that we eliminate the axial tilt of the star from the equation - vec3 pointOnYCone = removeAxialTilt(pointOnCone, coneAxis); +float spiralDensity(vec3 p, float coneTheta, float coneHeight) { + float frequency = 0.1; + float dist = sdSpiral(p, coneTheta, frequency); + + float heightFraction = abs(p.y) / coneHeight; - vec2 pointOnXZPlane = vec2(pointOnYCone.x, pointOnYCone.z); - float theta = atan(pointOnXZPlane.y, pointOnXZPlane.x) + 3.14 * min(0.0, sign(dot(pointOnCone, coneAxis))); - float heightFraction = abs(pointOnYCone.y) / coneMaxHeight; + dist /= heightFraction; float density = 1.0; - // smoothstep fadeout when the height is too much (outside of cone) + density *= exp(-0.02 * dist * dist); + density *= 1.0 - smoothstep(0.0, 1.0, heightFraction); + + return density; +} - float d = spiralSDF(theta + time, 0.2 + sqrt(heightFraction) / 2.0) / (0.3 + heightFraction * 2.0); - //d = pow(d, 4.0); - - density *= smoothstep(0.6, 1.0, pow(1.0 - d, 8.0)) * 2.0; //smoothstep(0.85, 1.0, 1.0 - d) * 2.0; +// from https://www.shadertoy.com/view/MtcXWr +// Returns true if the ray intersects the closed cone. +// If so, 't' is set to the first hit along the ray (entry if outside, exit if inside) +// and 'distTrough' is set to the chord length of the ray inside the cone. +bool rayIntersectCone(vec3 rayOrigin, vec3 rayDir, + float coneHeight, float cosTheta, + out float t, out float distTrough) +{ + vec3 coneUp = vec3(0.0, 1.0, 0.0); + + float cosTheta2 = cosTheta * cosTheta; + + // Compute tangent once. + float sinTheta = sqrt(max(0.0, 1.0 - cosTheta2)); + float tanTheta = sinTheta / cosTheta; + + // --- Determine if ray origin is inside the cone volume --- + bool inside = false; + { + vec3 v = rayOrigin; + float h = v.y; + if (h >= 0.0 && h <= coneHeight) { + float rAtH = h * tanTheta; + float d = length(v - coneUp * h); + if (d <= rAtH) + inside = true; + } + } + + // --- Collect candidate intersection t's --- + // We will add valid intersections from the lateral surface and the base. + float tCandidates[3]; + int count = 0; + + // Intersection with the infinite cone's lateral surface. + vec3 co = rayOrigin; + float A = rayDir.y * rayDir.y - cosTheta2; + float B = 2.0 * (rayDir.y * co.y - dot(rayDir, co) * cosTheta2); + float C = co.y * co.y - dot(co, co) * cosTheta2; + + float det = B * B - 4.0 * A * C; + if (det >= 0.0) + { + float sqrtDet = sqrt(det); + float t1 = (-B - sqrtDet) / (2.0 * A); + float t2 = (-B + sqrtDet) / (2.0 * A); + + // Check t1 for validity (only consider if t>=0) + if (t1 >= 0.0) + { + vec3 cp1 = rayOrigin + t1 * rayDir; + float h1 = cp1.y; + if (h1 >= 0.0 && h1 <= coneHeight) + tCandidates[count++] = t1; + } + // Check t2 + if (t2 >= 0.0) + { + vec3 cp2 = rayOrigin + t2 * rayDir; + float h2 = cp2.y; + if (h2 >= 0.0 && h2 <= coneHeight) + tCandidates[count++] = t2; + } + } + + // Intersection with the base plane. + vec3 baseCenter = coneUp * coneHeight; + float denom = rayDir.y; + if (abs(denom) > 1e-6) + { + float tBase = (baseCenter - rayOrigin).y / denom; + if (tBase >= 0.0) + { + vec3 hitPoint = rayOrigin + tBase * rayDir; + // Check if hitPoint is within the circular base. + float baseRadius = coneHeight * tanTheta; + if (length(hitPoint - baseCenter) <= baseRadius) + tCandidates[count++] = tBase; + } + } + + // If no valid intersections were found, return false. + if (count == 0) + return false; + + // --- Sort the candidate intersections in increasing order --- + for (int i = 0; i < count - 1; i++) + { + for (int j = i + 1; j < count; j++) + { + if (tCandidates[j] < tCandidates[i]) + { + float tmp = tCandidates[i]; + tCandidates[i] = tCandidates[j]; + tCandidates[j] = tmp; + } + } + } + + // --- Determine entry and exit t values --- + float tEntry, tExit; + if (inside) + { + // If the ray origin is inside, entry is at t = 0 and the exit is the first candidate. + tEntry = 0.0; + tExit = tCandidates[0]; + } + else + { + // Outside: entry is the first hit, exit is the second (if present). + tEntry = tCandidates[0]; + if (count > 1) + tExit = tCandidates[1]; + else + tExit = tEntry; // Tangential hit: chord length is zero. + } + + // Set the outputs. + // We return tEntry as the “hit” point (for an outside ray, where the ray first enters the cone; + // for an inside ray, tEntry=0 is the starting point so we return the exit instead). + if (inside) + t = 0.0; + else + t = tEntry; + + distTrough = tExit - tEntry; + + return true; +} - //density *= d * 500.0; +vec3 rayMarchSpiral(vec3 rayOrigin, vec3 rayDir, float distThrough, int nbSteps, float coneTheta, float coneHeight, out float transmittance) { + vec3 col = vec3(0.0); + + transmittance = 1.0; + + float stepSize = distThrough / float(nbSteps); + + for(int i = 0; i < nbSteps; i++) { + vec3 p = rayOrigin + float(i) * stepSize * rayDir; + + float density = spiralDensity(p, coneTheta * 0.7, coneHeight); + + vec3 emission = 3.0 * vec3(0.3, 0.6, 1.0) * density; + float absorption = 0.2 * density; + + transmittance *= exp(-absorption * stepSize); + col += emission * transmittance * stepSize; + } - return density; + return col; } void main() { @@ -123,29 +237,42 @@ void main() { vec3 rayDir = normalize(pixelWorldPosition - camera_position);// normalized direction of the ray - vec4 finalColor = screenColor; + float scaling_factor = object_radius * 10000.0; - const float jetHeight = 10000000e3; - const vec3 jetColor = vec3(0.5, 0.5, 1.0); + // move in object's local space to simplify the calculations + vec3 rayOriginLocalSpace = mat3(inverseRotation) * (camera_position - object_position) / scaling_factor; + vec3 rayDirLocalSpace = mat3(inverseRotation) * rayDir; + float coneTheta = dipoleTilt; + float coneHeight = 100.0; - float t1, t2; - if (rayIntersectCone(camera_position, rayDir, object_position, object_rotationAxis, 0.95, t1, t2)) { - if (t2 > 0.0 && t2 < maximumDistance) { - vec3 jetPointPosition2 = camera_position + t2 * rayDir - object_position; + vec3 color = screenColor.rgb; + float finalAlpha = screenColor.a; - float density2 = spiralDensity(jetPointPosition2, object_rotationAxis, jetHeight); + float t, distThrough; + if(rayIntersectCone(rayOriginLocalSpace, rayDirLocalSpace, coneHeight, cos(coneTheta), t, distThrough) && t * scaling_factor < maximumDistance) { + vec3 startPoint = rayOriginLocalSpace + t * rayDirLocalSpace; - finalColor.rgb = mix(finalColor.rgb, jetColor, density2); - } - if (t1 > 0.0 && t1 < maximumDistance) { - vec3 jetPointPosition1 = camera_position + t1 * rayDir - object_position; + float transmittance; + vec3 jetColor = rayMarchSpiral(startPoint, rayDirLocalSpace, distThrough, 100, coneTheta, coneHeight, transmittance); - float density1 = spiralDensity(jetPointPosition1, object_rotationAxis, jetHeight); + color = mix(jetColor, color, transmittance); + finalAlpha = max(transmittance, finalAlpha); + } - finalColor.rgb = mix(finalColor.rgb, jetColor, density1); - } + // flip the coordinates to display the other cone + rayOriginLocalSpace *= -1.0; + rayDirLocalSpace *= -1.0; + + if(rayIntersectCone(rayOriginLocalSpace, rayDirLocalSpace, coneHeight, cos(coneTheta), t, distThrough) && t * scaling_factor < maximumDistance) { + vec3 startPoint = rayOriginLocalSpace + t * rayDirLocalSpace; + + float transmittance; + vec3 jetColor = rayMarchSpiral(startPoint, rayDirLocalSpace, distThrough, 100, coneTheta, coneHeight, transmittance); + + color = mix(jetColor, color, transmittance); + finalAlpha = max(transmittance, finalAlpha); } - gl_FragColor = finalColor;// displaying the final color + gl_FragColor = vec4(color, finalAlpha); } \ No newline at end of file diff --git a/src/ts/playground.ts b/src/ts/playground.ts index b32a9c80e..5cb22863e 100644 --- a/src/ts/playground.ts +++ b/src/ts/playground.ts @@ -30,6 +30,7 @@ import { createDebugAssetsScene } from "./playgrounds/debugAssets"; import { createSpaceStationScene } from "./playgrounds/spaceStation"; import { createXrScene } from "./playgrounds/xr"; import { createFlightDemoScene } from "./playgrounds/flightDemo"; +import { createNeutronStarScene } from "./playgrounds/neutronStar"; const canvas = document.getElementById("renderer") as HTMLCanvasElement; canvas.width = window.innerWidth; @@ -65,6 +66,9 @@ switch (requestedScene) { case "flightDemo": scene = await createFlightDemoScene(engine); break; + case "neutronStar": + scene = await createNeutronStarScene(engine); + break; default: scene = await createAutomaticLandingScene(engine); } diff --git a/src/ts/playgrounds/neutronStar.ts b/src/ts/playgrounds/neutronStar.ts new file mode 100644 index 000000000..8aabd4086 --- /dev/null +++ b/src/ts/playgrounds/neutronStar.ts @@ -0,0 +1,92 @@ +// This file is part of Cosmos Journeyer +// +// Copyright (C) 2024 Barthélemy Paléologue +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import { AbstractEngine } from "@babylonjs/core/Engines/abstractEngine"; +import { Scene } from "@babylonjs/core/scene"; +import { Vector3 } from "@babylonjs/core/Maths/math.vector"; +import { enablePhysics } from "./utils"; +import { DefaultControls } from "../defaultControls/defaultControls"; +import { NeutronStar } from "../stellarObjects/neutronStar/neutronStar"; +import { newSeededNeutronStarModel } from "../stellarObjects/neutronStar/neutronStarModelGenerator"; +import { MatterJetPostProcess } from "../postProcesses/matterJetPostProcess"; +import { VolumetricLight } from "../volumetricLight/volumetricLight"; +import { translate } from "../uberCore/transforms/basicTransform"; +import { Textures } from "../assets/textures"; +import { AssetsManager, Axis } from "@babylonjs/core"; +import { LensFlarePostProcess } from "../postProcesses/lensFlarePostProcess"; +import { getRgbFromTemperature } from "../utils/specrend"; + +export async function createNeutronStarScene(engine: AbstractEngine): Promise { + const scene = new Scene(engine); + scene.useRightHandedSystem = true; + + await enablePhysics(scene); + + const assetsManager = new AssetsManager(scene); + Textures.EnqueueTasks(assetsManager, scene); + await assetsManager.loadAsync(); + + const defaultControls = new DefaultControls(scene); + defaultControls.speed = 2000; + + const camera = defaultControls.getActiveCamera(); + camera.attachControl(); + + scene.activeCamera = camera; + + scene.enableDepthRenderer(camera, false, true); + + const neutronStarModel = newSeededNeutronStarModel(456, "Neutron Star Demo", []); + const neutronStar = new NeutronStar(neutronStarModel, scene); + neutronStar.getTransform().position = new Vector3(0, 0, 1).scaleInPlace(neutronStar.getRadius() * 2000000); + + const volumetricLight = new VolumetricLight(neutronStar.mesh, neutronStar.volumetricLightUniforms, [], scene); + camera.attachPostProcess(volumetricLight); + + const matterJets = new MatterJetPostProcess( + neutronStar.getTransform(), + neutronStar.getRadius(), + neutronStar.model.dipoleTilt, + scene + ); + camera.attachPostProcess(matterJets); + + const lensFlare = new LensFlarePostProcess( + neutronStar.getTransform(), + neutronStar.getRadius(), + getRgbFromTemperature(neutronStarModel.blackBodyTemperature), + scene + ); + camera.attachPostProcess(lensFlare); + + camera.maxZ = 1e12; + defaultControls.getTransform().lookAt(neutronStar.getTransform().position); + + scene.onBeforePhysicsObservable.add(() => { + const deltaSeconds = engine.getDeltaTime() / 1000; + const displacement = defaultControls.update(deltaSeconds); + + neutronStar.getTransform().rotate(Axis.Y, (2 * Math.PI * deltaSeconds) / neutronStarModel.siderealDaySeconds); + + translate(defaultControls.getTransform(), displacement.negate()); + translate(neutronStar.getTransform(), displacement.negate()); + + matterJets.update(deltaSeconds); + }); + + return scene; +} diff --git a/src/ts/postProcesses/matterJetPostProcess.ts b/src/ts/postProcesses/matterJetPostProcess.ts index 09114c5f4..d119e8cfa 100644 --- a/src/ts/postProcesses/matterJetPostProcess.ts +++ b/src/ts/postProcesses/matterJetPostProcess.ts @@ -27,36 +27,38 @@ import { Constants } from "@babylonjs/core/Engines/constants"; import { Camera } from "@babylonjs/core/Cameras/camera"; import { Scene } from "@babylonjs/core/scene"; import { TransformNode } from "@babylonjs/core/Meshes/transformNode"; +import { Matrix } from "@babylonjs/core/Maths/math.vector"; export type MatterJetUniforms = { - // the rotation period in seconds of the matter jet - rotationPeriod: number; - time: number; + elapsedSeconds: number; + inverseRotation: Matrix; + dipoleTilt: number; }; /** * Post process for rendering matter jets that are used by neutron stars for example */ export class MatterJetPostProcess extends PostProcess implements UpdatablePostProcess { - matterJetUniforms: MatterJetUniforms; + readonly matterJetUniforms: MatterJetUniforms; private activeCamera: Camera | null = null; - constructor(stellarTransform: TransformNode, boundingRadius: number, scene: Scene) { + constructor(stellarTransform: TransformNode, boundingRadius: number, dipoleTilt: number, scene: Scene) { const shaderName = "matterjet"; if (Effect.ShadersStore[`${shaderName}FragmentShader`] === undefined) { Effect.ShadersStore[`${shaderName}FragmentShader`] = matterJetFragment; } const settings: MatterJetUniforms = { - rotationPeriod: 1.5, - time: 0 + elapsedSeconds: 0, + inverseRotation: Matrix.Identity(), + dipoleTilt: dipoleTilt }; const MatterJetUniformNames = { TIME: "time", - ROTATION_PERIOD: "rotationPeriod", - ROTATION_AXIS: "rotationAxis" + INVERSE_ROTATION: "inverseRotation", + DIPOLE_TILT: "dipoleTilt" }; const uniforms: string[] = [ @@ -95,18 +97,20 @@ export class MatterJetPostProcess extends PostProcess implements UpdatablePostPr setCameraUniforms(effect, this.activeCamera); setObjectUniforms(effect, stellarTransform, boundingRadius); - effect.setFloat( - MatterJetUniformNames.TIME, - this.matterJetUniforms.time % (this.matterJetUniforms.rotationPeriod * 10000) - ); - effect.setFloat(MatterJetUniformNames.ROTATION_PERIOD, this.matterJetUniforms.rotationPeriod); - effect.setVector3(MatterJetUniformNames.ROTATION_AXIS, stellarTransform.up); + effect.setFloat(MatterJetUniformNames.TIME, this.matterJetUniforms.elapsedSeconds % 10000); + + stellarTransform.getWorldMatrix().getRotationMatrixToRef(this.matterJetUniforms.inverseRotation); + this.matterJetUniforms.inverseRotation.transposeToRef(this.matterJetUniforms.inverseRotation); + + effect.setMatrix(MatterJetUniformNames.INVERSE_ROTATION, this.matterJetUniforms.inverseRotation); + + effect.setFloat(MatterJetUniformNames.DIPOLE_TILT, this.matterJetUniforms.dipoleTilt); setSamplerUniforms(effect, this.activeCamera, scene); }); } - public update(deltaTime: number): void { - this.matterJetUniforms.time += deltaTime; + public update(deltaSeconds: number): void { + this.matterJetUniforms.elapsedSeconds += deltaSeconds; } } diff --git a/src/ts/postProcesses/postProcessManager.ts b/src/ts/postProcesses/postProcessManager.ts index 35deb1fa8..c63121801 100644 --- a/src/ts/postProcesses/postProcessManager.ts +++ b/src/ts/postProcesses/postProcessManager.ts @@ -321,6 +321,7 @@ export class PostProcessManager { const matterJets = new MatterJetPostProcess( neutronStar.getTransform(), neutronStar.getBoundingRadius(), + neutronStar.model.dipoleTilt, this.scene ); this.matterJets.push(matterJets); diff --git a/src/ts/stellarObjects/neutronStar/neutronStarModel.ts b/src/ts/stellarObjects/neutronStar/neutronStarModel.ts index 3c4c557db..13cfbc154 100644 --- a/src/ts/stellarObjects/neutronStar/neutronStarModel.ts +++ b/src/ts/stellarObjects/neutronStar/neutronStarModel.ts @@ -16,13 +16,19 @@ // along with this program. If not, see . import { HasSeed } from "../../architecture/hasSeed"; -import { OrbitalObjectModelBase } from "../../architecture/orbitalObjectModelBase"; +import { CelestialBodyModelBase } from "../../architecture/orbitalObjectModelBase"; import { OrbitalObjectType } from "../../architecture/orbitalObjectType"; import { RingsModel } from "../../rings/ringsModel"; -export type NeutronStarModel = OrbitalObjectModelBase & +export type NeutronStarModel = CelestialBodyModelBase & HasSeed & { readonly blackBodyTemperature: number; - readonly radius: number; + + /** + * The angle between the magnetic dipole axis and the rotation axis. + * If the magnetic field were perfectly aligned with the rotation axis, this angle would be 0. + */ + readonly dipoleTilt: number; + readonly rings: RingsModel | null; }; diff --git a/src/ts/stellarObjects/neutronStar/neutronStarModelGenerator.ts b/src/ts/stellarObjects/neutronStar/neutronStarModelGenerator.ts index ac912b13b..804b11417 100644 --- a/src/ts/stellarObjects/neutronStar/neutronStarModelGenerator.ts +++ b/src/ts/stellarObjects/neutronStar/neutronStarModelGenerator.ts @@ -16,7 +16,7 @@ // along with this program. If not, see . import { Orbit } from "../../orbit/orbit"; -import { normalRandom, randRangeInt, uniformRandBool } from "extended-random"; +import { normalRandom, randRange, randRangeInt, uniformRandBool } from "extended-random"; import { clamp } from "../../utils/math"; import { newSeededRingsModel } from "../../rings/ringsModel"; import { GenerationSteps } from "../../utils/generationSteps"; @@ -25,6 +25,14 @@ import { OrbitalObjectModel } from "../../architecture/orbitalObjectModel"; import { NeutronStarModel } from "./neutronStarModel"; import { OrbitalObjectType } from "../../architecture/orbitalObjectType"; +/** + * Creates a new pseudo-random neutron star model + * @param seed The seed to use for the pseudo-random number generator + * @param name The name of the neutron star + * @param parentBodies The parent bodies of the neutron star (an empty array if it is the primary body of the system) + * @returns A new neutron star model + * @see https://arxiv.org/pdf/2402.14030 "On the initial spin period distribution of neutron stars" + */ export function newSeededNeutronStarModel( seed: number, name: string, @@ -32,14 +40,18 @@ export function newSeededNeutronStarModel( ): NeutronStarModel { const rng = getRngFromSeed(seed); - const temperature = randRangeInt(200_000, 5_000_000_000, rng, GenerationSteps.TEMPERATURE); - const mass = 1000; - const siderealDaySeconds = 24 * 60 * 60; - const blackBodyTemperature = temperature; + const mass = 1.9885e30; //TODO: compute mass from physical properties + + // https://arxiv.org/pdf/2402.14030 and https://en.wikipedia.org/wiki/Neutron_star#:~:text=Because%20it%20has%20only%20a,1.4%20ms%20to%2030%20s. + const siderealDaySeconds = clamp(1.4e-3, 30, normalRandom(0.5e-2, 5e-3, rng, GenerationSteps.SIDEREAL_DAY_SECONDS)); + + const blackBodyTemperature = randRangeInt(200_000, 5_000_000_000, rng, GenerationSteps.TEMPERATURE); const axialTilt = 0; const radius = clamp(normalRandom(10e3, 1e3, rng, GenerationSteps.RADIUS), 2e3, 50e3); + const dipoleTilt = randRange(-Math.PI / 6, Math.PI / 6, rng, GenerationSteps.DIPOLE_TILT); + // Todo: do not hardcode const orbitRadius = rng(GenerationSteps.ORBIT) * 5000000e3; @@ -58,15 +70,16 @@ export function newSeededNeutronStarModel( const rings = uniformRandBool(ringProportion, rng, GenerationSteps.RINGS) ? newSeededRingsModel(rng) : null; return { - name: name, - seed: seed, type: OrbitalObjectType.NEUTRON_STAR, - blackBodyTemperature: blackBodyTemperature, + name, + seed, + blackBodyTemperature, + dipoleTilt, mass, siderealDaySeconds, axialTilt, - radius: radius, - orbit: orbit, - rings: rings + radius, + orbit, + rings }; } diff --git a/src/ts/stellarObjects/star/starMaterialLut.ts b/src/ts/stellarObjects/star/starMaterialLut.ts index 7ea9e1582..573b81521 100644 --- a/src/ts/stellarObjects/star/starMaterialLut.ts +++ b/src/ts/stellarObjects/star/starMaterialLut.ts @@ -27,7 +27,7 @@ export class StarMaterialLut { Effect.ShadersStore["starLutFragmentShader"] = lutFragment; } - this.lut = new ProceduralTexture(`StarMaterialLut`, 4096, "starLut", scene, null, true, false); + this.lut = new ProceduralTexture(`StarMaterialLut`, 256, "starLut", scene, null, true, false); this.lut.refreshRate = 0; } diff --git a/src/ts/utils/generationSteps.ts b/src/ts/utils/generationSteps.ts index 4f64ca2bb..1932c3800 100644 --- a/src/ts/utils/generationSteps.ts +++ b/src/ts/utils/generationSteps.ts @@ -31,5 +31,9 @@ export const enum GenerationSteps { PRESSURE = 1800, WATER_AMOUNT = 1700, - TERRAIN = 1500 + TERRAIN = 1500, + + DIPOLE_TILT = 1300, + + SIDEREAL_DAY_SECONDS = 2500 } diff --git a/src/ts/volumetricLight/volumetricLight.ts b/src/ts/volumetricLight/volumetricLight.ts index 2a54444d6..8f0a5f526 100644 --- a/src/ts/volumetricLight/volumetricLight.ts +++ b/src/ts/volumetricLight/volumetricLight.ts @@ -29,7 +29,7 @@ export class VolumetricLight extends VolumetricLightScatteringPostProcess { excludedMeshes: AbstractMesh[], scene: Scene ) { - if (scene.activeCameras === null || scene.activeCameras.length === 0) throw new Error("no camera"); + if (scene.activeCamera === null) throw new Error("no camera"); super( `${starMesh.name}VolumetricLight`, 1,