From 45899ecc6a06a5af9c04928e786aad243ef5ab6f Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 31 Mar 2026 20:05:58 +0200 Subject: [PATCH] auto-git: [change] src/viewport-three/viewport-host.ts --- src/viewport-three/viewport-host.ts | 196 ++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/src/viewport-three/viewport-host.ts b/src/viewport-three/viewport-host.ts index 13547594..a297b0cd 100644 --- a/src/viewport-three/viewport-host.ts +++ b/src/viewport-three/viewport-host.ts @@ -327,6 +327,49 @@ export class ViewportHost { this.camera.lookAt(this.cameraTarget); } + private applyWorld() { + if (this.currentWorld === null) { + return; + } + + const world = this.currentWorld; + this.ambientLight.color.set(world.ambientLight.colorHex); + this.ambientLight.intensity = world.ambientLight.intensity; + this.sunLight.color.set(world.sunLight.colorHex); + this.sunLight.intensity = world.sunLight.intensity; + this.sunLight.position.set(world.sunLight.direction.x, world.sunLight.direction.y, world.sunLight.direction.z).normalize().multiplyScalar(18); + + if (world.background.mode === "image") { + this.scene.background = this.loadedImageAssets[world.background.assetId]?.texture ?? null; + return; + } + + this.scene.background = null; + } + + private rebuildLocalLights(document: SceneDocument) { + this.clearLocalLights(); + + for (const entity of getEntityInstances(document.entities)) { + const selected = this.currentSelection.kind === "entities" && this.currentSelection.ids.includes(entity.id); + + switch (entity.kind) { + case "pointLight": { + const renderObjects = this.createPointLightRenderObjects(entity, selected); + this.localLightGroup.add(renderObjects.group); + this.localLightRenderObjects.set(entity.id, renderObjects); + break; + } + case "spotLight": { + const renderObjects = this.createSpotLightRenderObjects(entity, selected); + this.localLightGroup.add(renderObjects.group); + this.localLightRenderObjects.set(entity.id, renderObjects); + break; + } + } + } + } + private rebuildBrushMeshes(document: SceneDocument, selection: EditorSelection) { this.clearBrushMeshes(); @@ -395,6 +438,18 @@ export class ViewportHost { private createEntityRenderObjects(entity: EntityInstance, selected: boolean): EntityRenderObjects { switch (entity.kind) { + case "pointLight": + return this.createPointLightGizmoRenderObjects(entity.id, entity.position, entity.distance, entity.colorHex, selected); + case "spotLight": + return this.createSpotLightGizmoRenderObjects( + entity.id, + entity.position, + entity.direction, + entity.distance, + entity.angleDegrees, + entity.colorHex, + selected + ); case "playerStart": return this.createPlayerStartRenderObjects(entity.id, entity.position, entity.yawDegrees, selected); case "soundEmitter": @@ -414,6 +469,147 @@ export class ViewportHost { group.add(mesh); } + private createPointLightRenderObjects( + entityId: string, + position: Vec3, + distance: number, + colorHex: string, + selected: boolean + ): EntityRenderObjects { + const markerColor = colorHex; + const displayRadius = Math.max(0.5, distance); + const group = new Group(); + group.position.set(position.x, position.y, position.z); + + const core = new Mesh( + new SphereGeometry(0.16, 16, 12), + new MeshStandardMaterial({ + color: markerColor, + emissive: markerColor, + emissiveIntensity: selected ? 0.22 : 0.1, + roughness: 0.28, + metalness: 0.05 + }) + ); + + const range = new Mesh( + new SphereGeometry(displayRadius, 16, 12), + new MeshStandardMaterial({ + color: markerColor, + emissive: markerColor, + emissiveIntensity: selected ? 0.08 : 0.03, + roughness: 0.85, + metalness: 0, + transparent: true, + opacity: selected ? 0.16 : 0.08, + wireframe: true + }) + ); + + for (const mesh of [core, range]) { + this.tagEntityMesh(mesh, entityId, "pointLight", group); + } + + return { + group, + meshes: [core, range] + }; + } + + private createSpotLightRenderObjects( + entityId: string, + position: Vec3, + direction: Vec3, + distance: number, + angleDegrees: number, + colorHex: string, + selected: boolean + ): EntityRenderObjects { + const markerColor = colorHex; + const group = new Group(); + group.position.set(position.x, position.y, position.z); + + const forward = new Vector3(direction.x, direction.y, direction.z).normalize(); + const coneLength = Math.max(0.85, distance); + const coneRadius = Math.max(0.16, Math.tan((angleDegrees * Math.PI) / 360) * coneLength); + const orientation = new Quaternion().setFromUnitVectors(new Vector3(0, 1, 0), forward); + group.quaternion.copy(orientation); + + const core = new Mesh( + new SphereGeometry(0.16, 14, 10), + new MeshStandardMaterial({ + color: markerColor, + emissive: markerColor, + emissiveIntensity: selected ? 0.24 : 0.1, + roughness: 0.28, + metalness: 0.05 + }) + ); + + const cone = new Mesh( + new CylinderGeometry(coneRadius, 0, coneLength, 20, 1, true), + new MeshStandardMaterial({ + color: markerColor, + emissive: markerColor, + emissiveIntensity: selected ? 0.08 : 0.03, + roughness: 0.85, + metalness: 0, + transparent: true, + opacity: selected ? 0.16 : 0.08, + wireframe: true + }) + ); + cone.position.y = coneLength * 0.5; + + for (const mesh of [core, cone]) { + this.tagEntityMesh(mesh, entityId, "spotLight", group); + } + + return { + group, + meshes: [core, cone] + }; + } + + private createSpotLightObjects(entity: SpotLightEntity): Group { + const group = new Group(); + const light = new SpotLight(entity.colorHex, entity.intensity, entity.distance, (entity.angleDegrees * Math.PI) / 180, 0.18, 1); + const direction = new Vector3(entity.direction.x, entity.direction.y, entity.direction.z).normalize(); + const orientation = new Quaternion().setFromUnitVectors(new Vector3(0, 1, 0), direction); + + group.position.set(entity.position.x, entity.position.y, entity.position.z); + group.quaternion.copy(orientation); + light.position.set(0, 0, 0); + light.target.position.set(0, 1, 0); + group.add(light); + group.add(light.target); + + return group; + } + + private createPointLightObjects(entity: PointLightEntity): Group { + const group = new Group(); + const light = new PointLight(entity.colorHex, entity.intensity, entity.distance); + + group.position.set(entity.position.x, entity.position.y, entity.position.z); + light.position.set(0, 0, 0); + group.add(light); + + return group; + } + + private createPointLightRenderObjectsWrapper(entity: PointLightEntity): LocalLightRenderObjects { + return { + group: this.createPointLightObjects(entity) + }; + } + + private createSpotLightRenderObjectsWrapper(entity: SpotLightEntity): LocalLightRenderObjects { + return { + group: this.createSpotLightObjects(entity) + }; + } + private createPlayerStartRenderObjects(entityId: string, position: Vec3, yawDegrees: number, selected: boolean): EntityRenderObjects { const markerColor = selected ? PLAYER_START_SELECTED_COLOR : PLAYER_START_COLOR; const group = new Group();