Release Healer Man 0.1.24

This commit is contained in:
phenom committed 2026-09-09 16:43:37 -04:00
1 parent c4346751a9
commit bac0da9300
118 files changed
+857 -289

No files matched your search

+54
View File
@@ -1,10 +1,14 @@
import {
AdditiveBlending,
BufferAttribute,
BufferGeometry,
DoubleSide,
Float32BufferAttribute,
Group,
Mesh,
MeshBasicMaterial,
MeshStandardMaterial,
NormalBlending,
} from "three";
import { describe, expect, it } from "vitest";
import {
@@ -12,6 +16,16 @@ import {
collisionTrimeshes,
prepareCollision,
} from "./productionDungeonCollision";
import { prepareDungeonMaterial } from "./productionDungeonMaterials";
const EMPTY_MATERIAL_ROLES = {
cutoutPatterns: [],
blendPatterns: [],
additivePatterns: [],
cutoutNames: [],
blendNames: [],
additiveNames: [],
};
describe("streamed dungeon collision preparation", () => {
it("dequantizes normalized Meshopt position buffers before Rapier consumes them", () => {
@@ -94,3 +108,43 @@ describe("streamed dungeon collision preparation", () => {
]);
});
});
describe("streamed dungeon material preparation", () => {
it("preserves alpha blending declared by the source GLB", () => {
const source = new MeshStandardMaterial({ transparent: true, opacity: 0.2 });
const prepared = prepareDungeonMaterial(source, EMPTY_MATERIAL_ROLES);
expect(prepared).not.toBe(source);
expect(prepared.transparent).toBe(true);
expect(prepared.opacity).toBe(0.2);
expect(prepared.depthWrite).toBe(false);
expect(prepared.blending).toBe(NormalBlending);
expect(prepared.side).toBe(DoubleSide);
});
it("preserves alpha masking declared by the source GLB", () => {
const source = new MeshStandardMaterial({ alphaTest: 0.37, transparent: false });
const prepared = prepareDungeonMaterial(source, EMPTY_MATERIAL_ROLES);
expect(prepared.transparent).toBe(false);
expect(prepared.opacity).toBe(1);
expect(prepared.alphaTest).toBe(0.37);
expect(prepared.depthWrite).toBe(true);
});
it("keeps explicit dungeon presentation roles authoritative", () => {
const source = new MeshStandardMaterial({ name: "light-volume", alphaTest: 0.5 });
const roles = {
...EMPTY_MATERIAL_ROLES,
additiveNames: ["LIGHT-VOLUME"],
};
const prepared = prepareDungeonMaterial(source, roles);
expect(prepared.transparent).toBe(true);
expect(prepared.depthWrite).toBe(false);
expect(prepared.blending).toBe(AdditiveBlending);
});
});
+2 -41
View File
@@ -1,10 +1,7 @@
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { RigidBody, TrimeshCollider } from "@react-three/rapier";
import {
AdditiveBlending,
DoubleSide,
Mesh,
NormalBlending,
type InstancedMesh,
type Material,
type Object3D,
@@ -24,49 +21,13 @@ import {
import { disposeObject3DResources } from "./threeResourceDisposal";
import { instanceRepeatedStaticMeshes } from "./staticSceneInstancing";
import { useGameGLTF } from "./useGameGLTF";
import { prepareDungeonMaterial } from "./productionDungeonMaterials";
import {
DUNGEON_COLLISION_LOAD_BATCH_SIZE,
DUNGEON_VISUAL_LOAD_BATCH_SIZE,
advanceDungeonAssetCount,
} from "./dungeonLoadSequencing";
function matchesRole(name: string, patterns: readonly string[], names: readonly string[]): boolean {
const lower = name.toLowerCase();
return names.some((candidate) => candidate.toLowerCase() === lower)
|| patterns.some((pattern) => new RegExp(pattern, "i").test(name));
}
function prepareMaterial(source: Material, roles: DungeonMaterialRoles): Material {
const material = source.clone();
const name = material.name;
material.side = DoubleSide;
if (matchesRole(name, roles.cutoutPatterns, roles.cutoutNames)) {
material.transparent = false;
material.opacity = 1;
material.alphaTest = 0.5;
material.depthWrite = true;
material.blending = NormalBlending;
} else if (matchesRole(name, roles.additivePatterns, roles.additiveNames)) {
material.transparent = true;
material.depthWrite = false;
material.blending = AdditiveBlending;
} else if (matchesRole(name, roles.blendPatterns, roles.blendNames)) {
material.transparent = true;
material.depthWrite = false;
material.blending = NormalBlending;
} else {
material.transparent = false;
material.opacity = 1;
material.alphaTest = 0;
material.depthWrite = true;
material.blending = NormalBlending;
}
material.needsUpdate = true;
return material;
}
function prepareVisual(root: Object3D, roles: DungeonMaterialRoles): Object3D {
const clone = root.clone(true);
const clonedMaterials = new Map<string, Material>();
@@ -77,7 +38,7 @@ function prepareVisual(root: Object3D, roles: DungeonMaterialRoles): Object3D {
const prepared = sources.map((source) => {
const cached = clonedMaterials.get(source.uuid);
if (cached) return cached;
const next = prepareMaterial(source, roles);
const next = prepareDungeonMaterial(source, roles);
clonedMaterials.set(source.uuid, next);
return next;
});
+54
View File
@@ -0,0 +1,54 @@
import {
AdditiveBlending,
DoubleSide,
NormalBlending,
type Material,
} from "three";
import type { DungeonMaterialRoles } from "../game/dungeonTypes";
function matchesRole(name: string, patterns: readonly string[], names: readonly string[]): boolean {
const lower = name.toLowerCase();
return names.some((candidate) => candidate.toLowerCase() === lower)
|| patterns.some((pattern) => new RegExp(pattern, "i").test(name));
}
export function prepareDungeonMaterial(source: Material, roles: DungeonMaterialRoles): Material {
const material = source.clone();
const name = material.name;
material.side = DoubleSide;
if (matchesRole(name, roles.cutoutPatterns, roles.cutoutNames)) {
material.transparent = false;
material.opacity = 1;
material.alphaTest = 0.5;
material.depthWrite = true;
material.blending = NormalBlending;
} else if (matchesRole(name, roles.additivePatterns, roles.additiveNames)) {
material.transparent = true;
material.depthWrite = false;
material.blending = AdditiveBlending;
} else if (matchesRole(name, roles.blendPatterns, roles.blendNames)) {
material.transparent = true;
material.depthWrite = false;
material.blending = NormalBlending;
} else if (material.alphaTest > 0) {
// Preserve a source GLB's MASK semantics when no presentation override exists.
material.transparent = false;
material.opacity = 1;
material.depthWrite = true;
material.blending = NormalBlending;
} else if (material.transparent) {
// Preserve a source GLB's BLEND semantics when no presentation override exists.
material.depthWrite = false;
material.blending = NormalBlending;
} else {
material.transparent = false;
material.opacity = 1;
material.alphaTest = 0;
material.depthWrite = true;
material.blending = NormalBlending;
}
material.needsUpdate = true;
return material;
}