feat: 50 игр на Lua + импорт Roblox для всех + поддержка Lua в плеере #39
@ -526,11 +526,73 @@ const InspectorPanel = ({
|
|||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ padding: '4px 0' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 }}>
|
||||||
|
<span>Заливка теней</span>
|
||||||
|
<span style={{ opacity: 0.6 }}>{(selection.sceneAmbient ?? 0.3).toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range" min="0" max="1" step="0.05"
|
||||||
|
value={selection.sceneAmbient ?? 0.3}
|
||||||
|
onChange={(e) => props.onSetLightingProps?.({ sceneAmbient: parseFloat(e.target.value) })}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
<div style={{ fontSize: 10, color: 'var(--text-dim)', marginTop: 2 }}>
|
||||||
|
Подсветка теней — цвет в затенённых гранях. 0 = чёрные тени, 1 = плоско.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div style={{ fontSize: 11, color: 'var(--text-dim)', fontStyle: 'italic', marginTop: 4 }}>
|
<div style={{ fontSize: 11, color: 'var(--text-dim)', fontStyle: 'italic', marginTop: 4 }}>
|
||||||
<Icon name="sparkle" size={11} /> Цвет окружающего света подбирается автоматически по времени суток.
|
<Icon name="sparkle" size={11} /> Цвет окружающего света подбирается автоматически по времени суток.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Цветокоррекция */}
|
||||||
|
<div className={cl.section}>
|
||||||
|
<div className={cl.sectionTitle}><Icon name="sparkle" size={12} /> Цветокоррекция</div>
|
||||||
|
<div style={{ padding: '4px 0' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 }}>
|
||||||
|
<span>Экспозиция</span>
|
||||||
|
<span style={{ opacity: 0.6 }}>{(selection.exposure ?? 1.0).toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range" min="0.3" max="2" step="0.05"
|
||||||
|
value={selection.exposure ?? 1.0}
|
||||||
|
onChange={(e) => props.onSetLightingProps?.({ exposure: parseFloat(e.target.value) })}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
<div style={{ fontSize: 10, color: 'var(--text-dim)', marginTop: 2 }}>
|
||||||
|
Общая яркость. <1 = темнее, >1 = светлее.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: '4px 0' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 }}>
|
||||||
|
<span>Контраст</span>
|
||||||
|
<span style={{ opacity: 0.6 }}>{(selection.contrast ?? 1.0).toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range" min="0.5" max="2" step="0.05"
|
||||||
|
value={selection.contrast ?? 1.0}
|
||||||
|
onChange={(e) => props.onSetLightingProps?.({ contrast: parseFloat(e.target.value) })}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: '4px 0' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 }}>
|
||||||
|
<span>Насыщенность</span>
|
||||||
|
<span style={{ opacity: 0.6 }}>{(selection.saturation ?? 1.0).toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range" min="0" max="2" step="0.05"
|
||||||
|
value={selection.saturation ?? 1.0}
|
||||||
|
onChange={(e) => props.onSetLightingProps?.({ saturation: parseFloat(e.target.value) })}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
<div style={{ fontSize: 10, color: 'var(--text-dim)', marginTop: 2 }}>
|
||||||
|
0 = чёрно-белое, 1 = норма, 2 = очень сочно.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Туман */}
|
{/* Туман */}
|
||||||
<div className={cl.section}>
|
<div className={cl.section}>
|
||||||
<div className={cl.sectionTitle}><Icon name="fog" size={12} /> Туман</div>
|
<div className={cl.sectionTitle}><Icon name="fog" size={12} /> Туман</div>
|
||||||
|
|||||||
@ -37,6 +37,7 @@ import {
|
|||||||
Ray,
|
Ray,
|
||||||
PointerEventTypes,
|
PointerEventTypes,
|
||||||
Tools as BabylonTools,
|
Tools as BabylonTools,
|
||||||
|
ColorCurves,
|
||||||
} from '@babylonjs/core';
|
} from '@babylonjs/core';
|
||||||
import { PlacementManager } from './PlacementManager';
|
import { PlacementManager } from './PlacementManager';
|
||||||
import { ShopInventoryUi } from './ShopInventoryUi';
|
import { ShopInventoryUi } from './ShopInventoryUi';
|
||||||
@ -1885,9 +1886,41 @@ export class BabylonScene {
|
|||||||
}
|
}
|
||||||
if (typeof patch.sunIntensity === 'number' && this._sunLight) {
|
if (typeof patch.sunIntensity === 'number' && this._sunLight) {
|
||||||
this._sunLight.intensity = Math.max(0, patch.sunIntensity);
|
this._sunLight.intensity = Math.max(0, patch.sunIntensity);
|
||||||
|
this._sunIntensity = patch.sunIntensity;
|
||||||
}
|
}
|
||||||
if (typeof patch.hemiIntensity === 'number' && this._hemiLight) {
|
if (typeof patch.hemiIntensity === 'number' && this._hemiLight) {
|
||||||
this._hemiLight.intensity = Math.max(0, patch.hemiIntensity);
|
this._hemiLight.intensity = Math.max(0, patch.hemiIntensity);
|
||||||
|
this._hemiIntensity = patch.hemiIntensity;
|
||||||
|
}
|
||||||
|
// Окружающий свет (scene.ambientColor) — отдельный множитель.
|
||||||
|
// Применяется ко всем материалам через ambient*ambient.
|
||||||
|
if (typeof patch.sceneAmbient === 'number') {
|
||||||
|
const v = Math.max(0, Math.min(1, patch.sceneAmbient));
|
||||||
|
this.scene.ambientColor = new Color3(v, v, v);
|
||||||
|
this._sceneAmbient = v;
|
||||||
|
}
|
||||||
|
// Цветокоррекция — экспозиция, контраст, насыщенность через
|
||||||
|
// imageProcessingConfiguration (включает HDR pipeline).
|
||||||
|
if (typeof patch.exposure === 'number' || typeof patch.contrast === 'number'
|
||||||
|
|| typeof patch.saturation === 'number') {
|
||||||
|
const ipc = this.scene.imageProcessingConfiguration;
|
||||||
|
ipc.isEnabled = true;
|
||||||
|
if (typeof patch.exposure === 'number') {
|
||||||
|
ipc.exposure = Math.max(0.1, Math.min(3, patch.exposure));
|
||||||
|
this._exposure = ipc.exposure;
|
||||||
|
}
|
||||||
|
if (typeof patch.contrast === 'number') {
|
||||||
|
ipc.contrast = Math.max(0.5, Math.min(2.5, patch.contrast));
|
||||||
|
this._contrast = ipc.contrast;
|
||||||
|
}
|
||||||
|
if (typeof patch.saturation === 'number') {
|
||||||
|
// colorCurves для saturation (стандартный Babylon приём)
|
||||||
|
if (!ipc.colorCurves) ipc.colorCurves = new ColorCurves();
|
||||||
|
const s = Math.max(-100, Math.min(100, (patch.saturation - 1) * 100));
|
||||||
|
ipc.colorCurves.globalSaturation = s;
|
||||||
|
ipc.colorCurvesEnabled = true;
|
||||||
|
this._saturation = patch.saturation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this.environment && typeof this.environment.setFog === 'function') {
|
if (this.environment && typeof this.environment.setFog === 'function') {
|
||||||
// Текущие значения берём из Environment, поверх накладываем patch
|
// Текущие значения берём из Environment, поверх накладываем patch
|
||||||
|
|||||||
@ -506,11 +506,9 @@ export class PrimitiveManager {
|
|||||||
_applyMaterial(mesh, typeDef, color, material, textureUrl) {
|
_applyMaterial(mesh, typeDef, color, material, textureUrl) {
|
||||||
const matName = `${mesh.name}_mat`;
|
const matName = `${mesh.name}_mat`;
|
||||||
const mat = new StandardMaterial(matName, this.scene);
|
const mat = new StandardMaterial(matName, this.scene);
|
||||||
const dc = Color3.FromHexString(color || '#888888');
|
mat.diffuseColor = Color3.FromHexString(color || '#888888');
|
||||||
mat.diffuseColor = dc;
|
// ambient = default (0,0,0). Освещение настраивается через
|
||||||
// ambient = 40% от цвета. Roblox-look: тень окрашена, но не
|
// глобальную панель «Свет и атмосфера» (sun/hemi/saturation).
|
||||||
// суммируется с прямым светом в пересвет. Белые остаются белыми.
|
|
||||||
mat.ambientColor = new Color3(dc.r * 0.4, dc.g * 0.4, dc.b * 0.4);
|
|
||||||
|
|
||||||
// Если задан textureUrl — подгружаем PNG как diffuseTexture. Это
|
// Если задан textureUrl — подгружаем PNG как diffuseTexture. Это
|
||||||
// используется для GD-скинов куба (например /gd/skins/cube_smile.png).
|
// используется для GD-скинов куба (например /gd/skins/cube_smile.png).
|
||||||
|
|||||||
@ -282,6 +282,11 @@ export class SelectionManager {
|
|||||||
fogColor: env ? `#${[env.fogColor?.[0] ?? 0.7, env.fogColor?.[1] ?? 0.8, env.fogColor?.[2] ?? 0.9].map(c => Math.round(Math.max(0, Math.min(1, c)) * 255).toString(16).padStart(2, '0')).join('')}` : '#b0c8e6',
|
fogColor: env ? `#${[env.fogColor?.[0] ?? 0.7, env.fogColor?.[1] ?? 0.8, env.fogColor?.[2] ?? 0.9].map(c => Math.round(Math.max(0, Math.min(1, c)) * 255).toString(16).padStart(2, '0')).join('')}` : '#b0c8e6',
|
||||||
shadowQuality: this._scene3d.getShadowQuality?.() || 'soft',
|
shadowQuality: this._scene3d.getShadowQuality?.() || 'soft',
|
||||||
ssaoEnabled: this._scene3d.getSsaoEnabled?.() || false,
|
ssaoEnabled: this._scene3d.getSsaoEnabled?.() || false,
|
||||||
|
// Новые: глобальный ambient + image processing
|
||||||
|
sceneAmbient: this._scene3d._sceneAmbient ?? 0.3,
|
||||||
|
exposure: this._scene3d._exposure ?? 1.0,
|
||||||
|
contrast: this._scene3d._contrast ?? 1.0,
|
||||||
|
saturation: this._scene3d._saturation ?? 1.0,
|
||||||
};
|
};
|
||||||
this._notifyChange();
|
this._notifyChange();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user