mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-04-14 03:06:06 +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
|
// detect device
|
||||||
const MOBILE = window.innerWidth < 600 || navigator.userAgentData?.mobile;
|
const MOBILE = window.innerWidth < 600 || navigator.userAgentData?.mobile;
|
||||||
|
|
||||||
|
Scene.bootstrap();
|
||||||
|
|
||||||
if (PRODUCTION && "serviceWorker" in navigator) {
|
if (PRODUCTION && "serviceWorker" in navigator) {
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
navigator.serviceWorker.register("./sw.js").catch(err => {
|
navigator.serviceWorker.register("./sw.js").catch(err => {
|
||||||
|
|
@ -32,7 +34,7 @@ if (PRODUCTION && "serviceWorker" in navigator) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// append svg layers (in default order)
|
// append svg layers (in default order)
|
||||||
let svg = d3.select("#map");
|
let svg = d3.select(Scene.getMapSvg());
|
||||||
let defs = svg.select("#deftemp");
|
let defs = svg.select("#deftemp");
|
||||||
let viewbox = svg.select("#viewbox");
|
let viewbox = svg.select("#viewbox");
|
||||||
let scaleBar = svg.select("#scaleBar");
|
let scaleBar = svg.select("#scaleBar");
|
||||||
|
|
|
||||||
|
|
@ -302,11 +302,11 @@ async function parseLoadedData(data, mapVersion) {
|
||||||
|
|
||||||
{
|
{
|
||||||
svg.remove();
|
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");
|
defs = svg.select("#deftemp");
|
||||||
viewbox = svg.select("#viewbox");
|
viewbox = svg.select("#viewbox");
|
||||||
scaleBar = svg.select("#scaleBar");
|
scaleBar = svg.select("#scaleBar");
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import "./relief-generator";
|
||||||
import "./religions-generator";
|
import "./religions-generator";
|
||||||
import "./river-generator";
|
import "./river-generator";
|
||||||
import "./routes-generator";
|
import "./routes-generator";
|
||||||
|
import "./scene";
|
||||||
import "./states-generator";
|
import "./states-generator";
|
||||||
import "./voronoi";
|
import "./voronoi";
|
||||||
import "./webgl-layer";
|
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 {
|
||||||
import { byId } from "../utils";
|
Group,
|
||||||
|
OrthographicCamera,
|
||||||
|
Scene as ThreeScene,
|
||||||
|
WebGLRenderer,
|
||||||
|
} from "three";
|
||||||
|
|
||||||
export interface WebGLLayerConfig {
|
export interface WebGLLayerConfig {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -13,25 +17,26 @@ interface RegisteredLayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebGL2LayerClass {
|
export class WebGL2LayerClass {
|
||||||
private canvas = byId("webgl-canvas")!;
|
|
||||||
private renderer: WebGLRenderer | null = null;
|
private renderer: WebGLRenderer | null = null;
|
||||||
private camera: OrthographicCamera | 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 layers: Map<string, RegisteredLayer> = new Map();
|
||||||
private pendingConfigs: WebGLLayerConfig[] = []; // queue for register() before init()
|
private pendingConfigs: WebGLLayerConfig[] = []; // queue for register() before init()
|
||||||
private rafId: number | null = null;
|
private rafId: number | null = null;
|
||||||
|
|
||||||
init(): boolean {
|
init(): boolean {
|
||||||
|
const canvas = Scene.getCanvas();
|
||||||
|
|
||||||
this.renderer = new WebGLRenderer({
|
this.renderer = new WebGLRenderer({
|
||||||
canvas: this.canvas,
|
canvas,
|
||||||
antialias: false,
|
antialias: false,
|
||||||
alpha: true,
|
alpha: true,
|
||||||
});
|
});
|
||||||
this.renderer.setPixelRatio(window.devicePixelRatio || 1);
|
this.renderer.setPixelRatio(window.devicePixelRatio || 1);
|
||||||
this.canvas.style.width = `${graphWidth}px`;
|
canvas.style.width = `${graphWidth}px`;
|
||||||
this.canvas.style.height = `${graphHeight}px`;
|
canvas.style.height = `${graphHeight}px`;
|
||||||
this.renderer.setSize(graphWidth, graphHeight, false);
|
this.renderer.setSize(graphWidth, graphHeight, false);
|
||||||
this.scene = new Scene();
|
this.scene = new ThreeScene();
|
||||||
this.camera = new OrthographicCamera(0, graphWidth, 0, graphHeight, -1, 1);
|
this.camera = new OrthographicCamera(0, graphWidth, 0, graphHeight, -1, 1);
|
||||||
|
|
||||||
// Process pre-init registrations (register() before init() is explicitly safe)
|
// Process pre-init registrations (register() before init() is explicitly safe)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type { Selection } from "d3";
|
import type { Selection } from "d3";
|
||||||
import type { NameBase } from "../modules/names-generator";
|
import type { NameBase } from "../modules/names-generator";
|
||||||
|
import type { SceneModule } from "../modules/scene";
|
||||||
import type { PackedGraph } from "./PackedGraph";
|
import type { PackedGraph } from "./PackedGraph";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|
@ -91,4 +92,5 @@ declare global {
|
||||||
var viewY: number;
|
var viewY: number;
|
||||||
var changeFont: () => void;
|
var changeFont: () => void;
|
||||||
var getFriendlyHeight: (coords: [number, number]) => string;
|
var getFriendlyHeight: (coords: [number, number]) => string;
|
||||||
|
var Scene: SceneModule;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue