From 4191b70e63350ad07ddabbc521c7c097717a9ffc Mon Sep 17 00:00:00 2001 From: Senrokai Date: Tue, 29 Sep 2026 20:31:27 +0200 Subject: [PATCH] =?UTF-8?q?Tint=20a=20star=20measured=20in=20Gaia's=20BP?= =?UTF-8?q?=E2=88=92RP=20as=20the=20B=E2=88=92V=20of=20the=20dwarf=20of=20?= =?UTF-8?q?that=20colour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit colorIndexToRgb maps a B−V onto the star field's tint ramp, and its one caller passed it every star's colour index, whatever colorSystem said. BP−RP is the larger of the two for the same star — 0.823 against 0.65 for a G2 dwarf, 1.84 against 1.42 for an M0 (Pecaut & Mamajek) — so the 376 703 stars measured in it, 83 % of the catalogue, were tinted as later types than they are, and redder than HYG's stars of the same type, and than their own disc in the system view. A BP−RP is now carried to the B−V of the dwarf sequence at that colour (the table's own B−V column, which DwarfSequencePoint now returns, clamped at its ends), then tinted as before. The median Gaia colour, BP−RP 0.883, a G7 dwarf, read as a K2: its tint goes from (1, 0.972, 0.955) to (0.975, 0.982, 1), the same as HYG's G7 stars. Controls, each failing its named test: BP−RP read as B−V (2 of 792 failed), the star field not passing the colour system (1 of 792), and the B−V taken from the wrong column (3 of 792). Co-Authored-By: Claude Opus 5.5 (1M context) --- .../galaxy-system/star-field-renderer.spec.ts | 20 ++++++++++++++++++- .../galaxy-system/star-field-renderer.ts | 14 +++++++++---- src/app/shared/astro/spectral.spec.ts | 4 ++-- src/app/shared/astro/spectral.ts | 3 +++ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/app/features/galaxy-system/star-field-renderer.spec.ts b/src/app/features/galaxy-system/star-field-renderer.spec.ts index df25d91..e30b921 100644 --- a/src/app/features/galaxy-system/star-field-renderer.spec.ts +++ b/src/app/features/galaxy-system/star-field-renderer.spec.ts @@ -85,6 +85,24 @@ describe('colorIndexToRgb', () => { }); }); + it('reads a BP−RP colour as the B−V of the dwarf of that colour, so a type tints alike in either', () => { + // G2 V is B−V 0.65 or BP−RP 0.823, M0 V 1.42 or 1.84 (Pecaut & Mamajek). + for (const [bMinusV, bpRp] of [[0.65, 0.823], [1.42, 1.84]]) { + const [fromBpRp, fromBv] = [colorIndexToRgb(bpRp, undefined, 'BP-RP'), colorIndexToRgb(bMinusV)]; + expect([fromBpRp.r, fromBpRp.g, fromBpRp.b].map((channel) => channel.toFixed(5))).toEqual([fromBv.r, fromBv.g, fromBv.b].map((channel) => channel.toFixed(5))); + expect(colorIndexToRgb(bpRp).b).toBeLessThan(fromBv.b); + } + }); + + it('draws a star measured in BP−RP in that colour', () => { + const gaia = star({ id: 5, x: 0, y: 0, z: -10, colorIndex: 1.84, colorSystem: 'BP-RP', spectralType: 'Unknown' }); + const renderer = new StarFieldRenderer([gaia], packPositions([gaia]), 1); + renderer.refocus({ centre: { x: 0, y: 0, z: 0 } }); + const { colorAttribute } = renderer as unknown as { colorAttribute: THREE.InstancedBufferAttribute }; + expect(colorAttribute.getZ(0)).toBeCloseTo(colorIndexToRgb(1.42).b, 5); + renderer.dispose(); + }); + it('prefers a measured index over the spectral type', () => { const measured = colorIndexToRgb(-0.3, 'M5'); expect(measured.b).toBeGreaterThan(measured.r); @@ -460,7 +478,7 @@ describe('StarFieldRenderer refocus', () => { for (let instance = 0; instance < renderer.drawnCount; instance++) { const drawnStar = catalogue.find((candidate) => candidate.id === renderer.starIdAt(instance))!; - const expected = colorIndexToRgb(drawnStar.colorIndex, drawnStar.spectralType); + const expected = colorIndexToRgb(drawnStar.colorIndex, drawnStar.spectralType, drawnStar.colorSystem); expect(colorAttribute.getX(instance)).toBeCloseTo(expected.r, 5); expect(colorAttribute.getZ(instance)).toBeCloseTo(expected.b, 5); expect(sizeAttribute.getX(instance)).toBeGreaterThan(0); diff --git a/src/app/features/galaxy-system/star-field-renderer.ts b/src/app/features/galaxy-system/star-field-renderer.ts index 49e7874..f7a765f 100644 --- a/src/app/features/galaxy-system/star-field-renderer.ts +++ b/src/app/features/galaxy-system/star-field-renderer.ts @@ -2,7 +2,7 @@ import * as THREE from 'three/webgpu'; import { float, instancedBufferAttribute, mix, modelViewMatrix, smoothstep, uniform, uv, vec2, vec4 } from 'three/tsl'; import { BrightnessIndex, brightnessIndex, Positioned } from '../../shared/astro/brightest'; -import { spectralTypeToColorIndex } from '../../shared/astro/spectral'; +import { dwarfSequenceAtColor, spectralTypeToColorIndex } from '../../shared/astro/spectral'; import { SceneCamera } from '../../core/engine/engine.service'; import { StarRecord } from '../../shared/models/star.model'; import { PIXELS_TO_ANGULAR_SIZE, REFERENCE_FOV_DEGREES, REFERENCE_VIEWPORT_HEIGHT_PX } from './angular-size'; @@ -109,9 +109,15 @@ const WARM_STAR_COLOR = new THREE.Color(1.0, 0.6, 0.35); * value derived from `spectralType`, and to neutral white only when the catalog records no * classification at all — never to 0, which is itself a real color index meaning "hot A-type" * and would paint several hundred red dwarfs blue-white. + * + * A Gaia BP−RP is carried to the B−V of the dwarf of that colour first. It is the larger of the + * two for the same star — 0.82 against 0.65 for a G2 dwarf, 1.84 against 1.42 for an M0 — and read + * as B−V it tinted the 376 703 stars measured in it, 83 % of the map, as a K2 where they were a G7 + * at the median, and an M0 dwarf as an M5. */ -export function colorIndexToRgb(colorIndex: number | null, spectralType?: string): THREE.Color { - const resolved = colorIndex ?? spectralTypeToColorIndex(spectralType); +export function colorIndexToRgb(colorIndex: number | null, spectralType?: string, colorSystem?: 'B-V' | 'BP-RP'): THREE.Color { + const measured = colorIndex !== null && colorSystem === 'BP-RP' ? dwarfSequenceAtColor(colorIndex, 'BP-RP', true)!.bMinusV : colorIndex; + const resolved = measured ?? spectralTypeToColorIndex(spectralType); const color = new THREE.Color(); if (resolved === null) { return color.copy(NEUTRAL_STAR_COLOR); @@ -295,7 +301,7 @@ export class StarFieldRenderer { this.catalogueColors = new Float32Array(catalogue.length * 3); this.catalogueSizes = new Float32Array(catalogue.length); catalogue.forEach((star, index) => { - const color = colorIndexToRgb(star.colorIndex, star.spectralType); + const color = colorIndexToRgb(star.colorIndex, star.spectralType, star.colorSystem); this.catalogueColors[index * 3] = color.r; this.catalogueColors[index * 3 + 1] = color.g; this.catalogueColors[index * 3 + 2] = color.b; diff --git a/src/app/shared/astro/spectral.spec.ts b/src/app/shared/astro/spectral.spec.ts index 6e63e9b..e5c8625 100644 --- a/src/app/shared/astro/spectral.spec.ts +++ b/src/app/shared/astro/spectral.spec.ts @@ -168,8 +168,8 @@ describe('dwarfSequenceAtColor', () => { }); it('reads the row at the end a colour is past, when asked to', () => { - expect(dwarfSequenceAtColor(2.2, 'B-V', true)).toEqual({ temperatureK: 2420, bolometricCorrectionV: -5.78, gMinusV: -3.09 }); - expect(dwarfSequenceAtColor(-0.15, 'BP-RP', true)).toEqual({ temperatureK: 10700, bolometricCorrectionV: -0.42, gMinusV: 0.018 }); + expect(dwarfSequenceAtColor(2.2, 'B-V', true)).toEqual({ bMinusV: 2.16, temperatureK: 2420, bolometricCorrectionV: -5.78, gMinusV: -3.09 }); + expect(dwarfSequenceAtColor(-0.15, 'BP-RP', true)).toEqual({ bMinusV: -0.07, temperatureK: 10700, bolometricCorrectionV: -0.42, gMinusV: 0.018 }); expect(dwarfSequenceAtColor(null, 'B-V', true)).toBeNull(); }); }); diff --git a/src/app/shared/astro/spectral.ts b/src/app/shared/astro/spectral.ts index e1f7728..c26c721 100644 --- a/src/app/shared/astro/spectral.ts +++ b/src/app/shared/astro/spectral.ts @@ -181,6 +181,8 @@ export function spectralTypeFromColor(colorIndex: number | null, system: 'B-V' | /** What the dwarf sequence says of a star of a given colour. */ export interface DwarfSequencePoint { + /** B−V, the colour in the other system's terms where it was read off BP−RP. */ + bMinusV: number; temperatureK: number; /** Bolometric correction to V: what V leaves out of the star's total output, in magnitudes. */ bolometricCorrectionV: number; @@ -208,6 +210,7 @@ export function dwarfSequenceAtColor(colorIndex: number | null, system: 'B-V' | const t = (colour - blue[column]!) / (red[column]! - blue[column]!); const lerp = (from: number, to: number): number => from + (to - from) * t; return { + bMinusV: lerp(blue[1], red[1]), temperatureK: lerp(blue[3], red[3]), bolometricCorrectionV: lerp(blue[4], red[4]), gMinusV: blue[5] === null || red[5] === null ? null : lerp(blue[5], red[5])