Skip to content

Commit

Permalink
Refactor labels
Browse files Browse the repository at this point in the history
  • Loading branch information
KyleGough committed Jul 29, 2023
1 parent 308c8d7 commit e133fc9
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 122 deletions.
13 changes: 3 additions & 10 deletions src/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { createEnvironmentMap } from "./setup/environment-map";
import { createLights } from "./setup/lights";
import { createSolarSystem } from "./setup/solar-system";
import { createGUI, options } from "./setup/gui";
import { getLabelOpacity } from "./setup/label";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass";
Expand Down Expand Up @@ -34,8 +33,8 @@ const changeFocus = (oldFocus: string, newFocus: string) => {
const minDistance = solarSystem[newFocus].getMinDistance();
controls.minDistance = minDistance;
fakeCamera.position.set(minDistance, minDistance / 3, 0);
solarSystem[oldFocus].hideLabels();
solarSystem[newFocus].showLabels();
solarSystem[oldFocus].labels.hideAll();
solarSystem[newFocus].labels.showAll();
(document.querySelector(".caption p") as HTMLElement).innerHTML = newFocus;
};

Expand Down Expand Up @@ -147,13 +146,7 @@ createGUI(ambientLight, solarSystem, clock, fakeCamera);

// Update labels
const currentBody = solarSystem[options.focus];
currentBody.labels.forEach((l) => {
l.container.style.opacity = getLabelOpacity(
fakeCamera,
l.label,
currentBody.radius
).toString();
});
currentBody.labels.update(fakeCamera);

// Render
bloomComposer.render();
Expand Down
171 changes: 93 additions & 78 deletions src/setup/label.ts
Original file line number Diff line number Diff line change
@@ -1,86 +1,101 @@
import * as THREE from "three";
import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
import { PlanetaryObject } from "./planetary-object";

export const getLabelOpacity = (
camera: THREE.Camera,
label: CSS2DObject,
radius: number
) => {
const rotationOpacity = getRotationOpacity(camera, label);
const distanceOpacity = getDistanceOpacity(camera, radius);
return rotationOpacity * distanceOpacity;
};

const getRotationOpacity = (
camera: THREE.Camera,
label: CSS2DObject
): number => {
const hideThreshold = 1;
const fadeThreshold = 0.75;
const cameraVector = camera.position.clone().normalize();
const labelVector = label.position.clone().normalize();
const delta = Math.acos(cameraVector.dot(labelVector));

if (delta > hideThreshold) {
return 0;
} else if (delta > fadeThreshold) {
return (hideThreshold - delta) / (hideThreshold - fadeThreshold);
} else {
return 1;
}
};

const getDistanceOpacity = (camera: THREE.Camera, radius: number): number => {
const hideThreshold = radius * 12;
const fadeThreshold = radius * 8;
const distance = camera.position.length();

if (distance > hideThreshold) {
return 0;
} else if (distance > fadeThreshold) {
return (hideThreshold - distance) / (hideThreshold - fadeThreshold);
} else {
return 1;
}
};

export const rotateLabel = (radius: number, y: number, z: number) => {
const vector = new THREE.Vector3(radius, 0, 0);
vector.applyAxisAngle(new THREE.Vector3(0, 1, 0), y);
vector.applyAxisAngle(new THREE.Vector3(0, 0, 1), z);
return vector;
};

export const createLabel = (
name: string,
y: number,
z: number,
parent: PlanetaryObject,
type = ""
): [CSS2DObject, HTMLElement] => {
const container = document.createElement("div");
container.className = "label";

if (type) {
const img = document.createElement("img");
img.src = `./icons/${type}.svg`;
container.appendChild(img);

export interface PointOfInterest {
name: string;
y: number;
z: number;
type?: string;
}

export class Label {
parent: THREE.Object3D;
parentRadius: number;
elements: CSS2DObject[];

constructor(parent: THREE.Object3D, parentRadius: number) {
this.parent = parent;
this.parentRadius = parentRadius;
this.elements = [];
}

const text = document.createElement("p");
text.textContent = name;
container.appendChild(text);
createPOILabel = (poi: PointOfInterest) => {
const container = document.createElement("div");
container.className = "label";

if (poi.type) {
const img = document.createElement("img");
img.src = `./icons/${poi.type}.svg`;
container.appendChild(img);
}

const text = document.createElement("p");
text.textContent = poi.name;
container.appendChild(text);

const label = new CSS2DObject(container);
label.visible = false;
label.center.set(0, 0);
label.layers.set(2);

const labelPosition = this.rotateLabel(poi.y, poi.z).toArray();
label.position.set(...labelPosition);

this.parent.add(label);
this.elements.push(label);
};

showAll = () => {
this.elements.forEach((label) => (label.visible = true));
};

hideAll = () => {
this.elements.forEach((label) => (label.visible = false));
};

update = (camera: THREE.Camera) => {
this.elements.forEach((label) => {
const rotationOpacity = this.getRotationOpacity(camera, label);
const distanceOpacity = this.getDistanceOpacity(camera);
const opacity = rotationOpacity * distanceOpacity;
label.element.style.opacity = opacity.toString();
});
};

rotateLabel = (y: number, z: number) => {
const vector = new THREE.Vector3(this.parentRadius, 0, 0);
vector.applyAxisAngle(new THREE.Vector3(0, 1, 0), y);
vector.applyAxisAngle(new THREE.Vector3(0, 0, 1), z);
return vector;
};

const label = new CSS2DObject(container);
label.visible = false;
label.center.set(0, 0);
label.layers.set(2);
getRotationOpacity = (camera: THREE.Camera, label: CSS2DObject): number => {
const hideThreshold = 1;
const fadeThreshold = 0.75;
const cameraVector = camera.position.clone().normalize();
const labelVector = label.position.clone().normalize();
const delta = Math.acos(cameraVector.dot(labelVector));

const labelPosition = rotateLabel(parent.radius, y, z).toArray();
label.position.set(...labelPosition);
if (delta > hideThreshold) {
return 0;
} else if (delta > fadeThreshold) {
return (hideThreshold - delta) / (hideThreshold - fadeThreshold);
} else {
return 1;
}
};

parent.addLabel(label, container);
getDistanceOpacity = (camera: THREE.Camera): number => {
const hideThreshold = this.parentRadius * 12;
const fadeThreshold = this.parentRadius * 8;
const distance = camera.position.length();

return [label, container];
};
if (distance > hideThreshold) {
return 0;
} else if (distance > fadeThreshold) {
return (hideThreshold - distance) / (hideThreshold - fadeThreshold);
} else {
return 1;
}
};
}
41 changes: 14 additions & 27 deletions src/setup/planetary-object.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as THREE from "three";
import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
import { createRingMesh } from "./rings";
import { createPath } from "./path";
import { loadTexture } from "./textures";
import { Label } from "./label";
import { PointOfInterest } from "./label";

export interface Body {
name: string;
Expand All @@ -19,13 +20,6 @@ export interface Body {
offset?: number;
}

interface PointOfInterest {
name: string;
y: number;
z: number;
type?: string;
}

interface TexturePaths {
map: string;
bump?: string;
Expand All @@ -39,11 +33,6 @@ interface Atmosphere {
alpha?: THREE.Texture;
}

interface Label {
label: CSS2DObject;
container: HTMLElement;
}

const timeFactor = 8 * Math.PI * 2; // 1s real-time => 8h simulation time

const normaliseRadius = (radius: number): number => {
Expand Down Expand Up @@ -73,7 +62,7 @@ export class PlanetaryObject {
bumpMap?: THREE.Texture;
specularMap?: THREE.Texture;
atmosphere: Atmosphere = {};
labels: Label[] = [];
labels: Label;

constructor(body: Body) {
const { radius, distance, period, daylength, orbits, type, tilt } = body;
Expand All @@ -98,8 +87,19 @@ export class PlanetaryObject {
if (this.atmosphere.map) {
this.mesh.add(this.createAtmosphereMesh());
}

this.initLabels(body.labels);
}

initLabels = (labels?: PointOfInterest[]) => {
this.labels = new Label(this.mesh, this.radius);
if (labels) {
labels.forEach((poi) => {
this.labels.createPOILabel(poi);
});
}
};

loadTextures(textures: TexturePaths) {
this.map = loadTexture(textures.map);
if (textures.bump) {
Expand Down Expand Up @@ -198,19 +198,6 @@ export class PlanetaryObject {
}
};

addLabel = (label: CSS2DObject, container: HTMLElement) => {
this.mesh.add(label);
this.labels.push({ label, container });
};

showLabels = () => {
this.labels.forEach((label) => (label.label.visible = true));
};

hideLabels = () => {
this.labels.forEach((label) => (label.label.visible = false));
};

getMinDistance = (): number => {
return this.radius * 3.5;
};
Expand Down
7 changes: 0 additions & 7 deletions src/setup/solar-system.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { PlanetaryObject } from "./planetary-object";
import planetData from "../planets.json";
import { Body } from "./planetary-object";
import { createLabel } from "./label";
import { setTextureCount } from "./textures";

export type SolarSystem = Record<string, PlanetaryObject>;
Expand Down Expand Up @@ -34,12 +33,6 @@ export const createSolarSystem = (
object.path && parentMesh.add(object.path);
}

if (planet.labels) {
planet.labels.forEach((label) => {
createLabel(label.name, label.y, label.z, object, label.type || "");
});
}

if (planet.traversable) {
traversable.push(planet.name);
}
Expand Down

0 comments on commit e133fc9

Please sign in to comment.