Update Healer Man server
This commit is contained in:
1 parent
6114a2295f
commit
69809f7228
593 files changed
+903643
-40774
No files matched your search
@@ -0,0 +1,249 @@
|
||||
import * as THREE from "three";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
||||
import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js";
|
||||
import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";
|
||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
||||
import { FORSAKEN_ABBEY_DEFINITION } from "../../src/game/dungeons/forsakenAbbey";
|
||||
import { RUNEWAKER_DUNGEON_DEFINITIONS } from "../../src/game/generated/runewakerDungeonDefinitions.generated";
|
||||
import { prepareDungeonMaterial } from "../../src/scene/productionDungeonMaterials";
|
||||
import { applyDungeonMeshGraphics } from "../../src/scene/graphicsMaterialQuality";
|
||||
import { repairHallOfSurvivorsVisual } from "../../src/scene/hallOfSurvivorsVisual";
|
||||
import { withRunewakerDungeonEnvironment } from "../../src/game/runewakerDungeonEnvironment";
|
||||
import type { DungeonDefinition } from "../../src/game/dungeonTypes";
|
||||
import { configureRunewakerTerrainLoader } from "../../src/scene/runewakerTerrainMaterial";
|
||||
import { configureRunewakerSurfaceDetailLoader } from "../../src/scene/runewakerSurfaceDetailMaterial";
|
||||
import { configureRunewakerWaterLoader } from "../../src/scene/runewakerWaterMaterial";
|
||||
import { resolveRunewakerNativeLighting } from "../../src/game/runewakerNativeLighting";
|
||||
import { applyRunewakerNativeLighting } from "../../src/scene/runewakerNativeLightingRuntime";
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const id = params.get("dungeon") ?? "forsaken-abbey";
|
||||
const definitions: readonly DungeonDefinition[] = [FORSAKEN_ABBEY_DEFINITION, ...RUNEWAKER_DUNGEON_DEFINITIONS];
|
||||
const original = definitions.find((entry) => entry.id === id);
|
||||
if (!original) throw new Error(`Unknown RoM dungeon ${id}`);
|
||||
const definition = params.has("before") ? original : withRunewakerDungeonEnvironment(original);
|
||||
const p = definition.presentation;
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
|
||||
renderer.setSize(innerWidth, innerHeight);
|
||||
renderer.setPixelRatio(1);
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
renderer.shadowMap.enabled = true;
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
document.body.append(renderer.domElement);
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(p.fog.color);
|
||||
scene.fog = new THREE.Fog(p.fog.color, p.fog.near, p.fog.far);
|
||||
const ambient = new THREE.AmbientLight(p.ambientLight.color, p.ambientLight.intensity);
|
||||
const hemisphere = new THREE.HemisphereLight(p.hemisphereLight.skyColor, p.hemisphereLight.groundColor, p.hemisphereLight.intensity);
|
||||
scene.add(ambient, hemisphere);
|
||||
const light = new THREE.DirectionalLight(p.directionalLight.color, p.directionalLight.intensity);
|
||||
light.castShadow = true;
|
||||
light.shadow.mapSize.set(2048, 2048);
|
||||
Object.assign(light.shadow.camera, { left: -26, right: 26, top: 26, bottom: -26, near: 0.5, far: 90 });
|
||||
light.shadow.bias = -0.00025;
|
||||
scene.add(light, light.target);
|
||||
const sourceAmbient = new THREE.AmbientLight();
|
||||
const sourceDirections = [new THREE.DirectionalLight(), new THREE.DirectionalLight(), new THREE.DirectionalLight()];
|
||||
const sourceLights = new THREE.Group();
|
||||
sourceLights.add(sourceAmbient, ...sourceDirections);
|
||||
sourceLights.visible = false;
|
||||
sourceDirections[0].castShadow = true;
|
||||
sourceDirections[0].shadow.mapSize.set(2048, 2048);
|
||||
Object.assign(sourceDirections[0].shadow.camera, { left: -26, right: 26, top: 26, bottom: -26, near: 0.5, far: 90 });
|
||||
sourceDirections[0].shadow.bias = -0.00025;
|
||||
scene.add(sourceLights, ...sourceDirections.map(source => source.target));
|
||||
let sourceLightingEnabled = !params.has("before") && !params.has("profile-only");
|
||||
let sourceLightingPoint = definition.entrance.footPosition;
|
||||
let resolvedSourceLighting: ReturnType<typeof resolveRunewakerNativeLighting> = null;
|
||||
|
||||
function updateSourceLighting() {
|
||||
resolvedSourceLighting = sourceLightingEnabled
|
||||
? resolveRunewakerNativeLighting(definition, sourceLightingPoint, { cameraFarDistance: camera.far })
|
||||
: null;
|
||||
sourceLights.visible = resolvedSourceLighting !== null;
|
||||
ambient.visible = hemisphere.visible = light.visible = resolvedSourceLighting === null;
|
||||
if (resolvedSourceLighting) applyRunewakerNativeLighting(scene, sourceAmbient, sourceDirections, resolvedSourceLighting);
|
||||
else {
|
||||
scene.background = new THREE.Color(p.fog.color);
|
||||
scene.fog = new THREE.Fog(p.fog.color, p.fog.near, p.fog.far);
|
||||
delete scene.userData.runewakerNativeLighting;
|
||||
}
|
||||
}
|
||||
for (const entry of p.pointLights ?? []) {
|
||||
const point = new THREE.PointLight(entry.color, entry.intensity, entry.distance, 2);
|
||||
point.position.fromArray(entry.position);
|
||||
scene.add(point);
|
||||
}
|
||||
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 2000);
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
const loader = new GLTFLoader();
|
||||
configureRunewakerTerrainLoader(loader);
|
||||
configureRunewakerSurfaceDetailLoader(loader);
|
||||
configureRunewakerWaterLoader(loader);
|
||||
const decoder = new KTX2Loader().setTranscoderPath("/basis/").detectSupport(renderer);
|
||||
loader.setKTX2Loader(decoder).setMeshoptDecoder(MeshoptDecoder);
|
||||
const root = new THREE.Group();
|
||||
root.position.fromArray(definition.transform.position);
|
||||
root.rotation.fromArray([...definition.transform.rotation, "XYZ"]);
|
||||
root.scale.setScalar(definition.transform.scale);
|
||||
scene.add(root);
|
||||
for (const asset of definition.assets.visual) {
|
||||
const assetUrl = id === "the-origin" && params.get("stage") === "origin-water-cliffs"
|
||||
? "/artifacts/playtest/rom-continuation/origin-full-ground/the-origin-full-native-water-cliffs-optimized.glb"
|
||||
: id === "the-origin" && params.get("stage") === "origin-full-native-water"
|
||||
? "/artifacts/playtest/rom-continuation/origin-full-ground/the-origin-full-native-water-optimized.glb"
|
||||
: id === "the-origin" && params.get("stage") === "origin-full-ground"
|
||||
? "/artifacts/playtest/rom-continuation/origin-full-ground/the-origin-full-native-optimized.glb"
|
||||
: params.get("stage") === "converted"
|
||||
? `/runewaker-export-work/${id}/converted/${id}-visual.glb`
|
||||
: id === "sand-vortex" && params.get("stage") === "cliff-before"
|
||||
? "/artifacts/rom-environment-quality-audit/sand-vortex-cliffs/before-cliff-projection.glb"
|
||||
: `/src/assets/game/dungeons/${id}/${asset.fileName}`;
|
||||
const gltf = await loader.loadAsync(assetUrl);
|
||||
gltf.scene.traverse((node) => {
|
||||
if (!(node instanceof THREE.Mesh)) return;
|
||||
node.material = Array.isArray(node.material)
|
||||
? node.material.map((material) => prepareDungeonMaterial(material, p.materials, definition.environmentMode))
|
||||
: prepareDungeonMaterial(node.material, p.materials, definition.environmentMode);
|
||||
});
|
||||
if (!params.has("unrepaired")) repairHallOfSurvivorsVisual(gltf.scene, id);
|
||||
applyDungeonMeshGraphics(gltf.scene, "environment", "high", 16, renderer.capabilities.getMaxAnisotropy());
|
||||
root.add(gltf.scene);
|
||||
}
|
||||
const viewpoints = [
|
||||
{ name: "entrance", position: definition.entrance.footPosition, forward: definition.entrance.forward },
|
||||
...definition.bosses.map((boss) => ({ name: boss.name, position: boss.position, forward: definition.entrance.forward })),
|
||||
];
|
||||
function view(index: number, yaw = 0) {
|
||||
const entry = viewpoints[Math.min(index, viewpoints.length - 1)];
|
||||
sourceLightingPoint = entry.position;
|
||||
updateSourceLighting();
|
||||
const eye = new THREE.Vector3(...entry.position).add(new THREE.Vector3(0, 2.2, 0));
|
||||
const forward = new THREE.Vector3(...entry.forward).normalize().applyAxisAngle(new THREE.Vector3(0, 1, 0), yaw);
|
||||
camera.position.copy(eye);
|
||||
controls.target.copy(eye).add(forward.multiplyScalar(8)).add(new THREE.Vector3(0, -1.2, 0));
|
||||
controls.update();
|
||||
document.querySelector("#status")!.textContent = `${definition.title} · ${entry.name} · ${params.has("before") ? "previous lighting" : "revised lighting"} · 16× filtering / High shadows`;
|
||||
}
|
||||
view(Number(params.get("view") ?? 0));
|
||||
let frames = 0;
|
||||
let frameStart = performance.now();
|
||||
renderer.setAnimationLoop(() => {
|
||||
controls.update();
|
||||
light.target.position.copy(camera.position);
|
||||
if (resolvedSourceLighting) {
|
||||
sourceDirections.forEach((source, index) => {
|
||||
source.target.position.copy(camera.position);
|
||||
source.position.copy(camera.position).addScaledVector(new THREE.Vector3(...resolvedSourceLighting!.directionalLights[index].toLightDirection), 36);
|
||||
source.target.updateMatrixWorld();
|
||||
});
|
||||
}
|
||||
light.position.copy(camera.position).add(new THREE.Vector3(...p.directionalLight.offset));
|
||||
renderer.render(scene, camera);
|
||||
frames += 1;
|
||||
});
|
||||
function lighting(before: boolean) {
|
||||
sourceLightingEnabled = !before;
|
||||
updateSourceLighting();
|
||||
const selected = before ? original!.presentation : definition.presentation;
|
||||
ambient.color.set(selected.ambientLight.color);
|
||||
ambient.intensity = selected.ambientLight.intensity;
|
||||
hemisphere.color.set(selected.hemisphereLight.skyColor);
|
||||
hemisphere.groundColor.set(selected.hemisphereLight.groundColor);
|
||||
hemisphere.intensity = selected.hemisphereLight.intensity;
|
||||
light.color.set(selected.directionalLight.color);
|
||||
light.intensity = selected.directionalLight.intensity;
|
||||
scene.background = new THREE.Color(selected.fog.color);
|
||||
scene.fog = new THREE.Fog(selected.fog.color, selected.fog.near, selected.fog.far);
|
||||
if (resolvedSourceLighting) applyRunewakerNativeLighting(scene, sourceAmbient, sourceDirections, resolvedSourceLighting);
|
||||
const status = document.querySelector("#status")!;
|
||||
status.textContent = status.textContent!.replace(/previous lighting|revised lighting/, before ? "previous lighting" : "revised lighting");
|
||||
}
|
||||
function meshDiagnostics() {
|
||||
const meshes: unknown[] = [];
|
||||
root.updateMatrixWorld(true);
|
||||
root.traverse((node) => {
|
||||
if (!(node instanceof THREE.Mesh)) return;
|
||||
const bounds = new THREE.Box3().setFromObject(node);
|
||||
meshes.push({ name: node.name, material: Array.isArray(node.material) ? node.material.map((entry) => entry.name) : node.material.name,
|
||||
instanced: (node as THREE.InstancedMesh).isInstancedMesh ?? false, min: bounds.min.toArray(), max: bounds.max.toArray() });
|
||||
});
|
||||
return meshes;
|
||||
}
|
||||
function floorProbe() {
|
||||
root.updateMatrixWorld(true);
|
||||
const ray = new THREE.Raycaster();
|
||||
const position = new THREE.Vector3(...definition.entrance.footPosition);
|
||||
const results: unknown[] = [];
|
||||
for (const forward of [0, 3, 8, 15, 25]) {
|
||||
const origin = position.clone().addScaledVector(new THREE.Vector3(...definition.entrance.forward), forward).add(new THREE.Vector3(0, 5, 0));
|
||||
ray.set(origin, new THREE.Vector3(0, -1, 0));
|
||||
const hit = ray.intersectObject(root, true)[0];
|
||||
if (!hit?.face) continue;
|
||||
const mesh = hit.object as THREE.Mesh;
|
||||
const uv = mesh.geometry.getAttribute("uv");
|
||||
const material = Array.isArray(mesh.material) ? mesh.material[hit.face.materialIndex] : mesh.material;
|
||||
const map = (material as THREE.MeshStandardMaterial).map;
|
||||
results.push({ forward, point: hit.point.toArray(), mesh: mesh.name, material: material.name, texture: map?.name,
|
||||
face: hit.face, uv: hit.uv?.toArray(), triangleUvs: uv ? [hit.face.a, hit.face.b, hit.face.c].map((i) => [uv.getX(i), uv.getY(i)]) : null });
|
||||
}
|
||||
return results;
|
||||
}
|
||||
function resetFrameSample() {
|
||||
frames = 0;
|
||||
frameStart = performance.now();
|
||||
}
|
||||
function frameSample() {
|
||||
const elapsedMs = performance.now() - frameStart;
|
||||
return { frames, elapsedMs, fps: elapsedMs > 0 ? frames * 1000 / elapsedMs : null };
|
||||
}
|
||||
function inspectAt(position: [number, number, number], target: [number, number, number]) {
|
||||
sourceLightingPoint = target;
|
||||
updateSourceLighting();
|
||||
camera.position.fromArray(position);
|
||||
controls.target.fromArray(target);
|
||||
controls.update();
|
||||
camera.updateMatrixWorld(true);
|
||||
document.querySelector("#status")!.textContent = `${definition.title} · mapping hotspot review · revised lighting`;
|
||||
}
|
||||
function raycastAt(x: number, y: number, limit = 6) {
|
||||
// Diagnostic scans may change several poses between animation frames.
|
||||
// Raycaster must use the selected pose, not the preceding rendered matrix.
|
||||
camera.updateMatrixWorld(true);
|
||||
const ray = new THREE.Raycaster();
|
||||
ray.setFromCamera(new THREE.Vector2(x / innerWidth * 2 - 1, 1 - y / innerHeight * 2), camera);
|
||||
return ray.intersectObject(root, true).slice(0, limit).map(hit => {
|
||||
const mesh = hit.object as THREE.Mesh;
|
||||
const material = Array.isArray(mesh.material) ? mesh.material[hit.face?.materialIndex ?? 0] : mesh.material;
|
||||
return { node: mesh.name, instanceId: hit.instanceId ?? null, material: material.name,
|
||||
texture: (material as THREE.MeshStandardMaterial).map?.name ?? null,
|
||||
nativeSourceModel: material.userData.nativeSourceModel ?? null,
|
||||
nativeMaterialIndex: material.userData.nativeMaterialIndex ?? null,
|
||||
alphaTest: material.alphaTest, transparent: material.transparent,
|
||||
point: hit.point.toArray(), uv: hit.uv?.toArray() ?? null, distance: hit.distance };
|
||||
});
|
||||
}
|
||||
function waterDiagnostics() {
|
||||
const water: unknown[] = [];
|
||||
root.traverse(node => {
|
||||
if (!(node instanceof THREE.Mesh)) return;
|
||||
for (const material of Array.isArray(node.material) ? node.material : [node.material]) {
|
||||
const uniforms = (material as THREE.Material & { uniforms?: Record<string, { value: unknown }> }).uniforms;
|
||||
const frame = uniforms?.romWaterNormalFrame?.value;
|
||||
if (!(frame instanceof THREE.Texture)) continue;
|
||||
const bounds = new THREE.Box3().setFromObject(node);
|
||||
water.push({ node: node.name, min: bounds.min.toArray(), max: bounds.max.toArray(),
|
||||
elapsed: uniforms?.romWaterElapsedSeconds.value, frame: frame.name, uuid: frame.uuid,
|
||||
fullModelEnabled: material.userData.runewakerWater?.fullModelEnabled ?? false,
|
||||
sourceSplitPlaneHeight: node.userData.runewakerWaterSourceSplitPlaneHeight ?? null,
|
||||
screenEffectsReady: uniforms?.romWaterScreenEffectsReady?.value ?? 0,
|
||||
reflection: (uniforms?.romWaterReflection?.value as THREE.Texture | null)?.uuid ?? null,
|
||||
refraction: (uniforms?.romWaterRefraction?.value as THREE.Texture | null)?.uuid ?? null });
|
||||
}
|
||||
});
|
||||
return water;
|
||||
}
|
||||
Object.assign(window, { romEnvironmentReview: { ready: true, id, viewpoints, view, inspectAt, raycastAt, lighting, floorProbe, meshDiagnostics, waterDiagnostics, resetFrameSample, frameSample, presentation: p,
|
||||
sourceLighting: () => resolvedSourceLighting,
|
||||
enableSourceLighting: (enabled: boolean) => { sourceLightingEnabled = enabled; updateSourceLighting(); },
|
||||
drawCalls: () => renderer.info.render.calls } });
|
||||
Reference in new issue
Block a user