feat(world): Gebäude, die ein Ort ersetzt, beim Chunk-Aufbau weglassen

withoutReplacedBuildings filtert die Grundrisse, in denen ein Weltpunkt aus PLACES[].replaces liegt; ChunkStreamer baut die Geometrie daraus, main.js übergibt die Punkte aller Orte. Damit entfallen Kasten, Schatten und Kollision des alten Gebäudes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Opus 5 committed 2026-09-14 20:13:47 +02:00
1 parent f7506ac556
commit e33c1817ec
3 files changed
+80 -8

No files matched your search

+7 -2
View File
@@ -8,7 +8,7 @@ import { createMaterials } from "./world/materials.js";
import { createSky } from "./world/sky.js";
import { createSunShadows, prepareLitMaterials } from "./world/shadows.js";
import { PropLayer } from "./world/props.js";
import { PlaceLayer } from "./world/places.js";
import { PLACES, PlaceLayer } from "./world/places.js";
import { chunkForWorld, threeToWorld } from "./world/coords.js";
import { createCharacterModule } from "./characters/index.js";
import { activeVehicleOrNull, playerIsGrounded, playerPosition } from "./player.js";
@@ -236,7 +236,12 @@ await props.load();
// Zeitschranke, die danach die Kollision ohne vorhandenen Boden einschaltet, würde in eine
// endlose Fall-Auffang-Schleife laufen, sobald der Chunk dauerhaft ausbleibt. Nach 15s gibt
// es nur noch eine einmalige Warnung, kein Verhaltenswechsel.
const streamer = new ChunkStreamer({ scene, player, materials, props, pinned: VEHICLE_PINNED_CHUNKS });
// Gebäudekästen aus der Extraktion, an deren Stelle ein Ort steht (PLACES[].replaces), baut der Streamer
// gar nicht erst — sonst stünde zum Beispiel die Tribüne mitten in ihrem alten Kasten.
const streamer = new ChunkStreamer({
scene, player, materials, props, pinned: VEHICLE_PINNED_CHUNKS,
replacedBuildings: PLACES.flatMap((place) => place.replaces ?? []),
});
// warmUp fürs Vorladen eines Orts (PlaceLayer unten): lädt seine Texturen schon hier auf die GPU und
// übersetzt seine CSM-Shader, statt das im ersten Bild nach dem Einsetzen zu tun — dort kostete genau
// das gemessen rund 130 ms auf einmal (docs/superpowers/specs/2026-09-14-hood-yard-design.md,
+18 -3
View File
@@ -1,6 +1,7 @@
import { Group, Mesh } from "three";
import { CHUNKS_PER_SIDE, CHUNK_SIZE, SLICE } from "./coords.js";
import { buildChunkGeometry } from "./geometry.js";
import { pointInPolygon } from "./props.js";
const key = (i, j) => `${i}_${j}`;
const DECALS = new Set(["roads", "sand", "park", "urban"]);
@@ -31,9 +32,23 @@ function dist(k, position) {
return Math.hypot(c.x - position.x, c.z - position.z);
}
/**
* Der Chunk ohne die Gebäudegrundrisse, die ein Ort ersetzt (PLACES[].replaces in src/world/places.js):
* jeder Grundriss, in dem einer der Weltpunkte [x, y] liegt, entfällt — und mit ihm Kasten, Schatten und
* Kollision. Wirkt wie remove_building der Feinkalibrierung, nur zur Laufzeit. Trifft kein Punkt, kommt
* derselbe Chunk zurück.
*/
export function withoutReplacedBuildings(chunk, points) {
if (points.length === 0) return chunk;
const [ox, oz] = chunk.origin;
const buildings = chunk.buildings.filter((building) =>
!points.some(([x, y]) => pointInPolygon(x - ox, -y - oz, building.poly)));
return buildings.length === chunk.buildings.length ? chunk : { ...chunk, buildings };
}
export class ChunkStreamer {
constructor({ scene, player, materials, props, baseUrl = "./data/vice-a", pinned = [] }) {
Object.assign(this, { scene, player, materials, props, baseUrl });
constructor({ scene, player, materials, props, baseUrl = "./data/vice-a", pinned = [], replacedBuildings = [] }) {
Object.assign(this, { scene, player, materials, props, baseUrl, replacedBuildings });
this.active = new Map(); // key -> { group, handle }
this.pending = new Set();
this.queue = [];
@@ -63,7 +78,7 @@ export class ChunkStreamer {
const [i, j] = k.split("_").map(Number);
const group = new Group();
group.position.set(chunk.origin[0], 0, chunk.origin[1]);
const geos = buildChunkGeometry(chunk);
const geos = buildChunkGeometry(withoutReplacedBuildings(chunk, this.replacedBuildings));
const collide = new Group();
for (const [name, geo] of Object.entries(geos)) {
if (!geo) continue;