Sync with Gitea: update scene, lights, camera; add models and textures

This commit is contained in:
2026-02-19 18:36:05 +03:00
parent aedfd6a217
commit 3a2f116255
14 changed files with 2412 additions and 41 deletions

View File

@@ -1,12 +1,12 @@
import * as THREE from 'three';
export const camera = new THREE.PerspectiveCamera(
30,
27,
window.innerWidth / window.innerHeight,
0.1,
1000
);
// Камера смотрит в комнату через открытую стену
camera.position.set(0, 0, 18);
camera.lookAt(0, 1, 0);
camera.position.set(0, 0, 0);
camera.lookAt(0, 0, 0);

View File

@@ -1,18 +1,22 @@
import * as THREE from 'three';
import { RectAreaLightUniformsLib } from 'three/examples/jsm/lights/RectAreaLightUniformsLib.js';
import { RectAreaLightHelper } from 'three/examples/jsm/helpers/RectAreaLightHelper.js';
RectAreaLightUniformsLib.init();
/**
* Базовый свет сцены (Ambient + Directional)
*/
export function setupLights(scene) {
const ambient = new THREE.AmbientLight(0xffffff, 1.4);
const ambient = new THREE.AmbientLight(0xffffff, 0.8);
scene.add(ambient);
const key = new THREE.DirectionalLight(0xffffff, 0.8);
key.position.set(5, 6, 4);
const key = new THREE.DirectionalLight(0xffffff, 0.2);
key.position.set(0, 0, 0);
scene.add(key);
const fill = new THREE.DirectionalLight(0xffffff, 0.2);
fill.position.set(-5, 3, -4);
fill.position.set(0, 0, 0);
scene.add(fill);
}
@@ -39,7 +43,7 @@ export function setupRoomLights(room) {
80, // distance
Math.PI / 4, // angle
0.5, // penumbra
2 // decay (физически корректный)
1.5 // decay (физически корректный)
);
spotObject.add(spotLight);
@@ -65,10 +69,170 @@ export function setupRoomLights(room) {
// CircleGeometry смотрит по +Z → поворачиваем вниз
bulb.rotation.x = -Math.PI;
bulb.position.set(0, 0, -0.005);
bulb.position.set(0, 0, 0.001);
spotObject.add(bulb);
console.log(`Спот корректно создан: ${spotName}`);
});
}
/**
* Создает LED-ленту с рассеянным светом на объекте ceilinglight
* @param {THREE.Object3D} ceilinglight - объект для привязки LED-ленты
* @param {Object} options - настройки { position: [x,y,z], rotationDeg: [x,y,z], ... }
*/
function createLEDStrip(ceilinglight, options = {}) {
// Конвертация градусов в радианы
const toRad = (deg) => deg * Math.PI / 180;
// Размеры LED-ленты (подстрой под модель)
const width = options.width || 0.01;
const height = options.height || 1.0;
// RectAreaLight - рассеянный свет
const rectLight = new THREE.RectAreaLight(
options.color || 0xffffff,
options.intensity || 6,
width,
height
);
ceilinglight.add(rectLight);
// Позиция
if (options.position) {
rectLight.position.set(...options.position);
}
// Поворот (градусы)
if (options.rotationDeg) {
rectLight.rotation.set(...options.rotationDeg.map(toRad));
}
// Направление света (target - локальные координаты)
if (options.target) {
const targetObj = new THREE.Object3D();
targetObj.position.set(...options.target);
ceilinglight.add(targetObj);
rectLight.target = targetObj;
}
// Визуальная полоса LED
const ledMesh = new THREE.Mesh(
new THREE.PlaneGeometry(width, height),
new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: 0xffffff,
emissiveIntensity: 2,
side: THREE.DoubleSide
})
);
if (options.position) {
ledMesh.position.set(...options.position);
}
if (options.rotationDeg) {
ledMesh.rotation.set(...options.rotationDeg.map(toRad));
}
ceilinglight.add(ledMesh);
// Helper для визуализации RectAreaLight
const rectLightHelper = new RectAreaLightHelper(rectLight, 0xff0000);
if (options.position) {
rectLightHelper.position.set(...options.position);
}
if (options.rotationDeg) {
rectLightHelper.rotation.set(...options.rotationDeg.map(toRad));
}
ceilinglight.add(rectLightHelper);
// DirectionalLight для теней
const shadowLight = new THREE.DirectionalLight(0xffffff, 0.5);
shadowLight.castShadow = true;
shadowLight.shadow.mapSize.width = 1024;
shadowLight.shadow.mapSize.height = 1024;
shadowLight.shadow.camera.near = 0.1;
shadowLight.shadow.camera.far = 10;
shadowLight.shadow.camera.left = -5;
shadowLight.shadow.camera.right = 5;
shadowLight.shadow.camera.top = 5;
shadowLight.shadow.camera.bottom = -5;
shadowLight.shadow.bias = -0.001;
if (options.shadowPosition) {
shadowLight.position.set(...options.shadowPosition);
} else {
shadowLight.position.set(0, 0, 0);
}
shadowLight.target.position.set(0, 1, 0);
ceilinglight.add(shadowLight);
ceilinglight.add(shadowLight.target);
}
/**
* Создает LED-ленты на всех объектах ceilinglight_001 - ceilinglight_004
* @param {THREE.Object3D} room - объект комнаты, содержащий ceilinglight объекты
*/
export function setupLEDStrip(room) {
// Настройки для каждой ленты
const ledConfigs = {
ceilinglight_001: {
position: [0, 0, 0.01],
rotationDeg: [0, 0, 0],
target: [0, 1, 0],
shadowPosition: [0, 0, 0],
width: 0.01,
height: 1.75,
color: 0xffffff,
intensity: 50
},
ceilinglight_002: {
position: [0, 0, 0.01],
rotationDeg: [0, 0, 90],
target: [0, 1, 0],
shadowPosition: [0, 0, 0],
width: 0.01,
height: 3.6,
color: 0xffffff,
intensity: 50
},
ceilinglight_003: {
position: [0, 0, 0.01],
rotationDeg: [0, 0, 0],
target: [0, 1, 0],
shadowPosition: [0, 0, 0],
width: 0.01,
height: 1.75,
color: 0xffffff,
intensity: 50
},
ceilinglight_004: {
position: [0, 0, 0.01],
rotationDeg: [0, 0, 90],
target: [0, 1, 0],
shadowPosition: [0, 0, 0],
width: 0.01,
height: 3.6,
color: 0xffffff,
intensity: 50
}
};
let created = 0;
for (let i = 1; i <= 4; i++) {
const name = `ceilinglight_${String(i).padStart(3, '0')}`;
const ceilinglight = room.getObjectByName(name);
const options = ledConfigs[name] || {};
if (ceilinglight) {
createLEDStrip(ceilinglight, options);
const worldPos = new THREE.Vector3();
ceilinglight.getWorldPosition(worldPos);
console.log(`LED-лента создана: ${name} | позиция: (${worldPos.x.toFixed(2)}, ${worldPos.y.toFixed(2)}, ${worldPos.z.toFixed(2)})`);
created++;
} else {
console.warn(`Объект не найден: ${name}`);
}
}
console.log(`Создано LED-лент: ${created}`);
}

View File

@@ -1,25 +0,0 @@
// materials.js
import * as THREE from 'three';
/**
* Применяет стандартный матовый материал ко всем мешам объекта
* @param {THREE.Object3D} object - объект сцены или room
* @param {Object} options - {color, roughness, metalness}
*/
export function applyMatteMaterial(object, options = {}) {
const color = options.color !== undefined ? options.color : 0xffffff;
const roughness = options.roughness !== undefined ? options.roughness : 1;
const metalness = options.metalness !== undefined ? options.metalness : 0;
object.traverse((child) => {
if (child.isMesh) {
child.material = new THREE.MeshStandardMaterial({
color,
roughness,
metalness
});
child.castShadow = true;
child.receiveShadow = true;
}
});
}

View File

@@ -1,6 +1,7 @@
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { setupLights, setupRoomLights } from './lights.js';
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
import { setupLights, setupRoomLights, setupLEDStrip } from './lights.js';
import { camera } from './camera.js';
const canvas = document.getElementById('scene');
@@ -22,6 +23,22 @@ renderer.toneMappingExposure = 0.3;
// Свет
setupLights(scene);
// ===== HDRI =====
const rgbeLoader = new RGBELoader();
rgbeLoader.load(
'textures/hdri_1.exr',
(texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
scene.background = texture;
console.log('HDRI загружен: textures/hdri_1.exr');
},
undefined,
(error) => {
console.error('HDRI LOAD ERROR', error);
}
);
// ===== ROOM =====
export let room = null; // Экспортируем для вращения
const loader = new GLTFLoader();
@@ -30,17 +47,17 @@ loader.load(
(gltf) => {
room = gltf.scene;
// Центрируем модель
// Центрируем модель по Y (только вверх/вниз)
const box = new THREE.Box3().setFromObject(room);
const center = box.getCenter(new THREE.Vector3());
room.position.sub(center);
// Масштаб 1:1 так как модель уже в метрах
room.scale.set(1, 1, 1);
const centerY = (box.min.y + box.max.y) / 2;
room.position.y = -centerY;
// Инициализируем свет комнаты
setupRoomLights(room);
// LED-ленты на ceilinglight_001 - ceilinglight_004
setupLEDStrip(room);
scene.add(room);
},
undefined,