mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-04-02 21:47:24 +02:00
feat: Introduce SceneModule for managing map and WebGL canvas integration
This commit is contained in:
parent
125403b82f
commit
42557881bb
6 changed files with 197 additions and 11 deletions
|
|
@ -13,6 +13,8 @@ const ERROR = true;
|
|||
// detect device
|
||||
const MOBILE = window.innerWidth < 600 || navigator.userAgentData?.mobile;
|
||||
|
||||
Scene.bootstrap();
|
||||
|
||||
if (PRODUCTION && "serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker.register("./sw.js").catch(err => {
|
||||
|
|
@ -32,7 +34,7 @@ if (PRODUCTION && "serviceWorker" in navigator) {
|
|||
}
|
||||
|
||||
// append svg layers (in default order)
|
||||
let svg = d3.select("#map");
|
||||
let svg = d3.select(Scene.getMapSvg());
|
||||
let defs = svg.select("#deftemp");
|
||||
let viewbox = svg.select("#viewbox");
|
||||
let scaleBar = svg.select("#scaleBar");
|
||||
|
|
|
|||
|
|
@ -302,11 +302,11 @@ async function parseLoadedData(data, mapVersion) {
|
|||
|
||||
{
|
||||
svg.remove();
|
||||
document.body.insertAdjacentHTML("afterbegin", data[5]);
|
||||
const mapSvg = Scene.replaceMapSvg(data[5]);
|
||||
svg = d3.select(mapSvg);
|
||||
}
|
||||
|
||||
{
|
||||
svg = d3.select("#map");
|
||||
defs = svg.select("#deftemp");
|
||||
viewbox = svg.select("#viewbox");
|
||||
scaleBar = svg.select("#scaleBar");
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import "./relief-generator";
|
|||
import "./religions-generator";
|
||||
import "./river-generator";
|
||||
import "./routes-generator";
|
||||
import "./scene";
|
||||
import "./states-generator";
|
||||
import "./voronoi";
|
||||
import "./webgl-layer";
|
||||
|
|
|
|||
176
src/modules/scene.ts
Normal file
176
src/modules/scene.ts
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
const MAP_CONTAINER_ID = "map-container";
|
||||
const SCENE_CONTAINER_ID = "map-scene";
|
||||
const MAP_ID = "map";
|
||||
const WEBGL_CANVAS_ID = "webgl-canvas";
|
||||
const RUNTIME_DEFS_HOST_ID = "runtime-defs-host";
|
||||
const RUNTIME_DEFS_ID = "runtime-defs";
|
||||
|
||||
export class SceneModule {
|
||||
private mapContainer: HTMLElement | null = null;
|
||||
private sceneContainer: HTMLDivElement | null = null;
|
||||
private mapSvg: SVGSVGElement | null = null;
|
||||
private canvas: HTMLCanvasElement | null = null;
|
||||
private defsHost: SVGSVGElement | null = null;
|
||||
private runtimeDefs: SVGGElement | null = null;
|
||||
|
||||
bootstrap() {
|
||||
const mapContainer = this.requireMapContainer();
|
||||
const sceneContainer = this.ensureSceneContainer(mapContainer);
|
||||
const defsHost = this.ensureDefsHost(mapContainer, sceneContainer);
|
||||
const runtimeDefs = this.ensureRuntimeDefs(defsHost);
|
||||
|
||||
const mapSvg = document.getElementById(MAP_ID);
|
||||
if (mapSvg instanceof SVGSVGElement) {
|
||||
sceneContainer.append(mapSvg);
|
||||
}
|
||||
|
||||
const canvas = document.getElementById(WEBGL_CANVAS_ID);
|
||||
if (canvas instanceof HTMLCanvasElement) {
|
||||
sceneContainer.append(canvas);
|
||||
}
|
||||
|
||||
this.mapContainer = mapContainer;
|
||||
this.sceneContainer = sceneContainer;
|
||||
this.mapSvg = mapSvg instanceof SVGSVGElement ? mapSvg : null;
|
||||
this.canvas = canvas instanceof HTMLCanvasElement ? canvas : null;
|
||||
this.defsHost = defsHost;
|
||||
this.runtimeDefs = runtimeDefs;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
replaceMapSvg(markup: string) {
|
||||
this.bootstrap();
|
||||
|
||||
this.mapSvg?.remove();
|
||||
this.sceneContainer?.querySelector(`#${MAP_ID}`)?.remove();
|
||||
this.getSceneContainer().insertAdjacentHTML("afterbegin", markup);
|
||||
|
||||
const mapSvg = this.getSceneContainer().querySelector(`#${MAP_ID}`);
|
||||
if (!(mapSvg instanceof SVGSVGElement)) {
|
||||
throw new Error("Scene could not rebind the map SVG after reload");
|
||||
}
|
||||
|
||||
this.mapSvg = mapSvg;
|
||||
if (this.canvas) {
|
||||
this.getSceneContainer().append(this.canvas);
|
||||
}
|
||||
|
||||
return mapSvg;
|
||||
}
|
||||
|
||||
getMapContainer() {
|
||||
this.bootstrap();
|
||||
return this.mapContainer!;
|
||||
}
|
||||
|
||||
getSceneContainer() {
|
||||
this.bootstrap();
|
||||
return this.sceneContainer!;
|
||||
}
|
||||
|
||||
getMapSvg() {
|
||||
this.bootstrap();
|
||||
if (!this.mapSvg) {
|
||||
throw new Error("Scene map SVG is not available");
|
||||
}
|
||||
|
||||
return this.mapSvg;
|
||||
}
|
||||
|
||||
getCanvas() {
|
||||
this.bootstrap();
|
||||
if (!this.canvas) {
|
||||
throw new Error("Scene WebGL canvas is not available");
|
||||
}
|
||||
|
||||
return this.canvas;
|
||||
}
|
||||
|
||||
getDefsHost() {
|
||||
this.bootstrap();
|
||||
return this.defsHost!;
|
||||
}
|
||||
|
||||
getRuntimeDefs() {
|
||||
this.bootstrap();
|
||||
return this.runtimeDefs!;
|
||||
}
|
||||
|
||||
private requireMapContainer() {
|
||||
const mapContainer = document.getElementById(MAP_CONTAINER_ID);
|
||||
if (!(mapContainer instanceof HTMLElement)) {
|
||||
throw new Error("Scene map container is not available");
|
||||
}
|
||||
|
||||
return mapContainer;
|
||||
}
|
||||
|
||||
private ensureSceneContainer(mapContainer: HTMLElement) {
|
||||
const existingSceneContainer = document.getElementById(SCENE_CONTAINER_ID);
|
||||
if (existingSceneContainer instanceof HTMLDivElement) {
|
||||
return existingSceneContainer;
|
||||
}
|
||||
|
||||
const sceneContainer = document.createElement("div");
|
||||
sceneContainer.id = SCENE_CONTAINER_ID;
|
||||
sceneContainer.style.position = "absolute";
|
||||
sceneContainer.style.inset = "0";
|
||||
mapContainer.prepend(sceneContainer);
|
||||
return sceneContainer;
|
||||
}
|
||||
|
||||
private ensureDefsHost(
|
||||
mapContainer: HTMLElement,
|
||||
sceneContainer: HTMLDivElement,
|
||||
) {
|
||||
const existingDefsHost = document.getElementById(RUNTIME_DEFS_HOST_ID);
|
||||
if (existingDefsHost instanceof SVGSVGElement) {
|
||||
if (sceneContainer.contains(existingDefsHost)) {
|
||||
mapContainer.append(existingDefsHost);
|
||||
}
|
||||
|
||||
return existingDefsHost;
|
||||
}
|
||||
|
||||
const defsHost = document.createElementNS(SVG_NS, "svg");
|
||||
defsHost.setAttribute("id", RUNTIME_DEFS_HOST_ID);
|
||||
defsHost.setAttribute("width", "0");
|
||||
defsHost.setAttribute("height", "0");
|
||||
defsHost.setAttribute("aria-hidden", "true");
|
||||
defsHost.style.position = "absolute";
|
||||
defsHost.style.width = "0";
|
||||
defsHost.style.height = "0";
|
||||
defsHost.style.overflow = "hidden";
|
||||
|
||||
const defsElement = document.createElementNS(SVG_NS, "defs");
|
||||
defsHost.append(defsElement);
|
||||
mapContainer.append(defsHost);
|
||||
return defsHost;
|
||||
}
|
||||
|
||||
private ensureRuntimeDefs(defsHost: SVGSVGElement) {
|
||||
const existingRuntimeDefs = defsHost.querySelector(`#${RUNTIME_DEFS_ID}`);
|
||||
if (existingRuntimeDefs instanceof SVGGElement) {
|
||||
return existingRuntimeDefs;
|
||||
}
|
||||
|
||||
let defsElement = defsHost.querySelector("defs");
|
||||
if (!(defsElement instanceof SVGDefsElement)) {
|
||||
defsElement = document.createElementNS(SVG_NS, "defs");
|
||||
defsHost.append(defsElement);
|
||||
}
|
||||
|
||||
const runtimeDefs = document.createElementNS(SVG_NS, "g");
|
||||
runtimeDefs.setAttribute("id", RUNTIME_DEFS_ID);
|
||||
defsElement.append(runtimeDefs);
|
||||
return runtimeDefs;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
var Scene: SceneModule;
|
||||
}
|
||||
|
||||
window.Scene = new SceneModule();
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
import { Group, OrthographicCamera, Scene, WebGLRenderer } from "three";
|
||||
import { byId } from "../utils";
|
||||
import {
|
||||
Group,
|
||||
OrthographicCamera,
|
||||
Scene as ThreeScene,
|
||||
WebGLRenderer,
|
||||
} from "three";
|
||||
|
||||
export interface WebGLLayerConfig {
|
||||
id: string;
|
||||
|
|
@ -13,25 +17,26 @@ interface RegisteredLayer {
|
|||
}
|
||||
|
||||
export class WebGL2LayerClass {
|
||||
private canvas = byId("webgl-canvas")!;
|
||||
private renderer: WebGLRenderer | null = null;
|
||||
private camera: OrthographicCamera | null = null;
|
||||
private scene: Scene | null = null;
|
||||
private scene: ThreeScene | null = null;
|
||||
private layers: Map<string, RegisteredLayer> = new Map();
|
||||
private pendingConfigs: WebGLLayerConfig[] = []; // queue for register() before init()
|
||||
private rafId: number | null = null;
|
||||
|
||||
init(): boolean {
|
||||
const canvas = Scene.getCanvas();
|
||||
|
||||
this.renderer = new WebGLRenderer({
|
||||
canvas: this.canvas,
|
||||
canvas,
|
||||
antialias: false,
|
||||
alpha: true,
|
||||
});
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio || 1);
|
||||
this.canvas.style.width = `${graphWidth}px`;
|
||||
this.canvas.style.height = `${graphHeight}px`;
|
||||
canvas.style.width = `${graphWidth}px`;
|
||||
canvas.style.height = `${graphHeight}px`;
|
||||
this.renderer.setSize(graphWidth, graphHeight, false);
|
||||
this.scene = new Scene();
|
||||
this.scene = new ThreeScene();
|
||||
this.camera = new OrthographicCamera(0, graphWidth, 0, graphHeight, -1, 1);
|
||||
|
||||
// Process pre-init registrations (register() before init() is explicitly safe)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Selection } from "d3";
|
||||
import type { NameBase } from "../modules/names-generator";
|
||||
import type { SceneModule } from "../modules/scene";
|
||||
import type { PackedGraph } from "./PackedGraph";
|
||||
|
||||
declare global {
|
||||
|
|
@ -91,4 +92,5 @@ declare global {
|
|||
var viewY: number;
|
||||
var changeFont: () => void;
|
||||
var getFriendlyHeight: (coords: [number, number]) => string;
|
||||
var Scene: SceneModule;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue