Forráskód Böngészése

Editor: Add texture parameters dialog. (#33651)

Michael Herzog 2 hete
szülő
commit
ae07d63ef9

+ 72 - 0
editor/css/main.css

@@ -936,6 +936,78 @@ hr {
 	margin-top: 15px;
 }
 
+.TextureParametersDialog-content {
+	min-width: 480px;
+}
+
+.TextureParametersDialog-split {
+	display: flex;
+	gap: 20px;
+}
+
+.TextureParametersDialog-preview {
+	flex: 0 0 auto;
+}
+
+.TextureParametersDialog-preview canvas {
+	border: 1px solid #888;
+	background: #000;
+	display: block;
+	max-width: 100%;
+	height: auto;
+}
+
+.TextureParametersDialog-form {
+	flex: 1 1 auto;
+	min-width: 0;
+}
+
+@media all and ( max-width: 600px ) {
+
+	.TextureParametersDialog-content {
+		min-width: 0;
+		width: 90vw;
+	}
+
+	.TextureParametersDialog-split {
+		flex-direction: column;
+	}
+
+	.TextureParametersDialog-preview {
+		align-self: center;
+		max-width: 100%;
+	}
+
+}
+
+.TextureParametersDialog-groupHeading {
+	margin: 12px 0 6px;
+	padding-bottom: 2px;
+	border-bottom: 1px solid #ccc;
+	font-weight: bold;
+	text-transform: uppercase;
+	font-size: 11px;
+	color: #666;
+}
+
+.TextureParametersDialog-groupHeading:first-child {
+	margin-top: 0;
+}
+
+@media ( prefers-color-scheme: dark ) {
+
+	.TextureParametersDialog-groupHeading {
+		border-bottom-color: #444;
+		color: #888;
+	}
+
+}
+
+.TextureSettingsButton {
+	padding: 0 4px;
+	line-height: 16px;
+}
+
 @media ( prefers-color-scheme: dark ) {
 
 	.Dialog-content {

+ 29 - 1
editor/js/Sidebar.Material.MapProperty.js

@@ -1,11 +1,13 @@
 import * as THREE from 'three';
 
-import { UICheckbox, UIDiv, UINumber, UIRow, UIText } from './libs/ui.js';
+import { UIButton, UICheckbox, UIDiv, UINumber, UIRow, UIText } from './libs/ui.js';
 import { UITexture } from './libs/ui.three.js';
 import { SetMaterialMapCommand } from './commands/SetMaterialMapCommand.js';
 import { SetMaterialValueCommand } from './commands/SetMaterialValueCommand.js';
 import { SetMaterialRangeCommand } from './commands/SetMaterialRangeCommand.js';
 import { SetMaterialVectorCommand } from './commands/SetMaterialVectorCommand.js';
+import { SetTextureParametersCommand } from './commands/SetTextureParametersCommand.js';
+import { TextureParametersDialog } from './TextureParametersDialog.js';
 
 function SidebarMaterialMapProperty( editor, property, name ) {
 
@@ -20,6 +22,10 @@ function SidebarMaterialMapProperty( editor, property, name ) {
 	const map = new UITexture( editor ).onChange( onMapChange );
 	container.add( map );
 
+	const settings = new UIButton( '⚙' ).setClass( 'TextureSettingsButton' ).setMarginRight( '4px' ).onClick( onSettingsClick );
+	settings.setDisabled( true );
+	container.add( settings );
+
 	const mapType = property.replace( 'Map', '' );
 
 	const colorMaps = [ 'map', 'emissiveMap', 'sheenColorMap', 'specularColorMap', 'envMap' ];
@@ -110,6 +116,26 @@ function SidebarMaterialMapProperty( editor, property, name ) {
 
 	}
 
+	async function onSettingsClick() {
+
+		const texture = map.getValue();
+		if ( texture === null ) return;
+
+		const dialog = new TextureParametersDialog( editor, texture );
+
+		try {
+
+			const parameters = await dialog.show();
+			editor.execute( new SetTextureParametersCommand( editor, texture, parameters ) );
+
+		} catch ( e ) {
+
+			// Edit cancelled
+
+		}
+
+	}
+
 	function onMapChange( texture ) {
 
 		if ( texture !== null ) {
@@ -124,6 +150,7 @@ function SidebarMaterialMapProperty( editor, property, name ) {
 		}
 
 		enabled.setDisabled( false );
+		settings.setDisabled( texture === null );
 
 		onChange();
 
@@ -193,6 +220,7 @@ function SidebarMaterialMapProperty( editor, property, name ) {
 
 			enabled.setValue( material[ property ] !== null );
 			enabled.setDisabled( map.getValue() === null );
+			settings.setDisabled( map.getValue() === null );
 
 			if ( intensity !== undefined ) {
 

+ 114 - 0
editor/js/Strings.js

@@ -35,6 +35,7 @@ function Strings( config ) {
 			'command/SetScene': 'تنظیم صحنه',
 			'command/SetScriptValue': 'تنظیم مقدار اسکریپت',
 			'command/SetShadowValue': 'تنظیم مقدار سایه',
+			'command/SetTextureParameters': 'تنظیم پارامترهای تکسچر',
 			'command/SetUuid': 'تنظیم UUID',
 			'command/SetValue': 'تنظیم مقدار',
 
@@ -427,6 +428,24 @@ function Strings( config ) {
 
 			'dialog/gltf/title': 'Import glTF',
 			'dialog/gltf/asScene': 'Import glTF as root scene',
+			'dialog/texture/title': 'Texture Parameters',
+			'dialog/texture/group/preview': 'Preview',
+			'dialog/texture/group/mapping': 'Mapping',
+			'dialog/texture/group/filtering': 'Filtering',
+			'dialog/texture/group/transform': 'Transform',
+			'dialog/texture/group/color': 'Color',
+			'dialog/texture/mapping': 'Mapping',
+			'dialog/texture/wrapS': 'Wrap S',
+			'dialog/texture/wrapT': 'Wrap T',
+			'dialog/texture/magFilter': 'Mag Filter',
+			'dialog/texture/minFilter': 'Min Filter',
+			'dialog/texture/anisotropy': 'Anisotropy',
+			'dialog/texture/offset': 'Offset',
+			'dialog/texture/repeat': 'Repeat',
+			'dialog/texture/center': 'Center',
+			'dialog/texture/rotation': 'Rotation',
+			'dialog/texture/premultiplyAlpha': 'Premultiply Alpha',
+			'dialog/texture/colorSpace': 'Color Space',
 			'dialog/ok': 'OK',
 			'dialog/cancel': 'Cancel'
 
@@ -464,6 +483,7 @@ function Strings( config ) {
 			'command/SetScene': 'Set Scene',
 			'command/SetScriptValue': 'Set Script Value',
 			'command/SetShadowValue': 'Set Shadow Value',
+			'command/SetTextureParameters': 'Set Texture Parameters',
 			'command/SetUuid': 'Set UUID',
 			'command/SetValue': 'Set Value',
 
@@ -856,6 +876,24 @@ function Strings( config ) {
 
 			'dialog/gltf/title': 'Import glTF',
 			'dialog/gltf/asScene': 'Import glTF as root scene',
+			'dialog/texture/title': 'Texture Parameters',
+			'dialog/texture/group/preview': 'Preview',
+			'dialog/texture/group/mapping': 'Mapping',
+			'dialog/texture/group/filtering': 'Filtering',
+			'dialog/texture/group/transform': 'Transform',
+			'dialog/texture/group/color': 'Color',
+			'dialog/texture/mapping': 'Mapping',
+			'dialog/texture/wrapS': 'Wrap S',
+			'dialog/texture/wrapT': 'Wrap T',
+			'dialog/texture/magFilter': 'Mag Filter',
+			'dialog/texture/minFilter': 'Min Filter',
+			'dialog/texture/anisotropy': 'Anisotropy',
+			'dialog/texture/offset': 'Offset',
+			'dialog/texture/repeat': 'Repeat',
+			'dialog/texture/center': 'Center',
+			'dialog/texture/rotation': 'Rotation',
+			'dialog/texture/premultiplyAlpha': 'Premultiply Alpha',
+			'dialog/texture/colorSpace': 'Color Space',
 			'dialog/ok': 'OK',
 			'dialog/cancel': 'Cancel'
 
@@ -894,6 +932,7 @@ function Strings( config ) {
 			'command/SetScene': 'Planter le décor',
 			'command/SetScriptValue': 'Définir la valeur du script',
 			'command/SetShadowValue': 'Set Shadow Value',
+			'command/SetTextureParameters': 'Définir les paramètres de la texture',
 			'command/SetUuid': 'Définir l’UUID',
 			'command/SetValue': 'Définir la valeur',
 
@@ -1286,6 +1325,24 @@ function Strings( config ) {
 
 			'dialog/gltf/title': 'Importer glTF',
 			'dialog/gltf/asScene': 'Importer glTF comme scène racine',
+			'dialog/texture/title': 'Paramètres de la texture',
+			'dialog/texture/group/preview': 'Aperçu',
+			'dialog/texture/group/mapping': 'Mapping',
+			'dialog/texture/group/filtering': 'Filtrage',
+			'dialog/texture/group/transform': 'Transformation',
+			'dialog/texture/group/color': 'Couleur',
+			'dialog/texture/mapping': 'Mapping',
+			'dialog/texture/wrapS': 'Wrap S',
+			'dialog/texture/wrapT': 'Wrap T',
+			'dialog/texture/magFilter': 'Filtre d’agrandissement',
+			'dialog/texture/minFilter': 'Filtre de réduction',
+			'dialog/texture/anisotropy': 'Anisotropie',
+			'dialog/texture/offset': 'Décalage',
+			'dialog/texture/repeat': 'Répétition',
+			'dialog/texture/center': 'Centre',
+			'dialog/texture/rotation': 'Rotation',
+			'dialog/texture/premultiplyAlpha': 'Pré-multiplier alpha',
+			'dialog/texture/colorSpace': 'Espace colorimétrique',
 			'dialog/ok': 'OK',
 			'dialog/cancel': 'Annuler'
 
@@ -1324,6 +1381,7 @@ function Strings( config ) {
 			'command/SetScene': '设置布景',
 			'command/SetScriptValue': '设置脚本值',
 			'command/SetShadowValue': '设置阴影值',
+			'command/SetTextureParameters': '设置纹理参数',
 			'command/SetUuid': '设置 UUID',
 			'command/SetValue': '设定值',
 
@@ -1716,6 +1774,24 @@ function Strings( config ) {
 
 			'dialog/gltf/title': '导入 glTF',
 			'dialog/gltf/asScene': '将 glTF 导入为根场景',
+			'dialog/texture/title': '纹理参数',
+			'dialog/texture/group/preview': '预览',
+			'dialog/texture/group/mapping': '映射',
+			'dialog/texture/group/filtering': '过滤',
+			'dialog/texture/group/transform': '变换',
+			'dialog/texture/group/color': '颜色',
+			'dialog/texture/mapping': '映射',
+			'dialog/texture/wrapS': '环绕 S',
+			'dialog/texture/wrapT': '环绕 T',
+			'dialog/texture/magFilter': '放大过滤器',
+			'dialog/texture/minFilter': '缩小过滤器',
+			'dialog/texture/anisotropy': '各向异性',
+			'dialog/texture/offset': '偏移',
+			'dialog/texture/repeat': '重复',
+			'dialog/texture/center': '中心',
+			'dialog/texture/rotation': '旋转',
+			'dialog/texture/premultiplyAlpha': '预乘 Alpha',
+			'dialog/texture/colorSpace': '颜色空间',
 			'dialog/ok': '确定',
 			'dialog/cancel': '取消'
 
@@ -1754,6 +1830,7 @@ function Strings( config ) {
 			'command/SetScene': 'セットシーン',
 			'command/SetScriptValue': 'スクリプト値の設定',
 			'command/SetShadowValue': 'Set Shadow Value',
+			'command/SetTextureParameters': 'テクスチャパラメータの設定',
 			'command/SetUuid': 'UUIDの設定',
 			'command/SetValue': '値の設定',
 
@@ -2146,6 +2223,24 @@ function Strings( config ) {
 
 			'dialog/gltf/title': 'glTFをインポート',
 			'dialog/gltf/asScene': 'glTFをルートシーンとしてインポート',
+			'dialog/texture/title': 'テクスチャパラメータ',
+			'dialog/texture/group/preview': 'プレビュー',
+			'dialog/texture/group/mapping': 'マッピング',
+			'dialog/texture/group/filtering': 'フィルタリング',
+			'dialog/texture/group/transform': '変換',
+			'dialog/texture/group/color': 'カラー',
+			'dialog/texture/mapping': 'マッピング',
+			'dialog/texture/wrapS': 'ラップ S',
+			'dialog/texture/wrapT': 'ラップ T',
+			'dialog/texture/magFilter': '拡大フィルター',
+			'dialog/texture/minFilter': '縮小フィルター',
+			'dialog/texture/anisotropy': '異方性',
+			'dialog/texture/offset': 'オフセット',
+			'dialog/texture/repeat': 'リピート',
+			'dialog/texture/center': '中心',
+			'dialog/texture/rotation': '回転',
+			'dialog/texture/premultiplyAlpha': 'プリマルチプライアルファ',
+			'dialog/texture/colorSpace': '色空間',
 			'dialog/ok': 'OK',
 			'dialog/cancel': 'キャンセル'
 
@@ -2183,6 +2278,7 @@ function Strings( config ) {
 			'command/SetScene': '장면 설정',
 			'command/SetScriptValue': '스크립트 값 설정',
 			'command/SetShadowValue': '그림자 값 설정',
+			'command/SetTextureParameters': '텍스처 매개변수 설정',
 			'command/SetUuid': 'UUID 설정',
 			'command/SetValue': '값 설정',
 
@@ -2575,6 +2671,24 @@ function Strings( config ) {
 
 			'dialog/gltf/title': 'glTF 가져오기',
 			'dialog/gltf/asScene': 'glTF를 루트 씬으로 가져오기',
+			'dialog/texture/title': '텍스처 매개변수',
+			'dialog/texture/group/preview': '미리보기',
+			'dialog/texture/group/mapping': '매핑',
+			'dialog/texture/group/filtering': '필터링',
+			'dialog/texture/group/transform': '변환',
+			'dialog/texture/group/color': '색상',
+			'dialog/texture/mapping': '매핑',
+			'dialog/texture/wrapS': '랩 S',
+			'dialog/texture/wrapT': '랩 T',
+			'dialog/texture/magFilter': '확대 필터',
+			'dialog/texture/minFilter': '축소 필터',
+			'dialog/texture/anisotropy': '이방성',
+			'dialog/texture/offset': '오프셋',
+			'dialog/texture/repeat': '반복',
+			'dialog/texture/center': '중심',
+			'dialog/texture/rotation': '회전',
+			'dialog/texture/premultiplyAlpha': '알파 미리 곱하기',
+			'dialog/texture/colorSpace': '색 공간',
 			'dialog/ok': '확인',
 			'dialog/cancel': '취소'
 		}

+ 293 - 0
editor/js/TextureParametersDialog.js

@@ -0,0 +1,293 @@
+import * as THREE from 'three';
+
+import { UIButton, UICheckbox, UIDiv, UINumber, UIRow, UISelect, UIText } from './libs/ui.js';
+import { renderToCanvas } from './libs/ui.three.js';
+
+class TextureParametersDialog {
+
+	constructor( editor, texture ) {
+
+		this.editor = editor;
+
+		this.strings = editor.strings;
+		this.texture = texture;
+
+		const dom = new UIDiv();
+		dom.setClass( 'Dialog' );
+		this.dom = dom.dom;
+
+		const background = new UIDiv();
+		background.setClass( 'Dialog-background' );
+		background.dom.addEventListener( 'click', () => this.cancel() );
+		dom.add( background );
+
+		const content = new UIDiv();
+		content.setClass( 'Dialog-content TextureParametersDialog-content' );
+		dom.add( content );
+
+		// Title
+
+		const titleBar = new UIDiv();
+		titleBar.setClass( 'Dialog-title' );
+		titleBar.setTextContent( this.strings.getKey( 'dialog/texture/title' ) );
+		content.add( titleBar );
+
+		// Body (split into preview + form)
+
+		const body = new UIDiv();
+		body.setClass( 'Dialog-body TextureParametersDialog-body' );
+		content.add( body );
+
+		const split = new UIDiv();
+		split.setClass( 'TextureParametersDialog-split' );
+		body.add( split );
+
+		// Preview
+
+		const previewWrapper = new UIDiv();
+		previewWrapper.setClass( 'TextureParametersDialog-preview' );
+		split.add( previewWrapper );
+
+		previewWrapper.add( createGroupHeading( this.strings.getKey( 'dialog/texture/group/preview' ) ) );
+
+		const previewCanvas = document.createElement( 'canvas' );
+		previewCanvas.width = 400;
+		previewCanvas.height = 400;
+		previewWrapper.dom.appendChild( previewCanvas );
+
+		this.previewCanvas = previewCanvas;
+		this.previewContext = previewCanvas.getContext( '2d' );
+		this.previewTexture = texture.clone();
+
+		const updatePreview = () => this.updatePreview();
+
+		// Form
+
+		const form = new UIDiv();
+		form.setClass( 'TextureParametersDialog-form' );
+		split.add( form );
+
+		// Mapping group
+
+		form.add( createGroupHeading( this.strings.getKey( 'dialog/texture/group/mapping' ) ) );
+
+		this.mapping = new UISelect().setOptions( {
+			[ THREE.UVMapping ]: 'UV',
+			[ THREE.EquirectangularReflectionMapping ]: 'Equirectangular Reflection',
+			[ THREE.EquirectangularRefractionMapping ]: 'Equirectangular Refraction',
+			[ THREE.CubeReflectionMapping ]: 'Cube Reflection',
+			[ THREE.CubeRefractionMapping ]: 'Cube Refraction',
+			[ THREE.CubeUVReflectionMapping ]: 'CubeUV Reflection'
+		} ).setValue( texture.mapping ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/mapping' ), this.mapping ) );
+
+		const wrapOptions = {
+			[ THREE.RepeatWrapping ]: 'Repeat',
+			[ THREE.ClampToEdgeWrapping ]: 'Clamp To Edge',
+			[ THREE.MirroredRepeatWrapping ]: 'Mirrored Repeat'
+		};
+
+		this.wrapS = new UISelect().setOptions( wrapOptions ).setValue( texture.wrapS ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/wrapS' ), this.wrapS ) );
+
+		this.wrapT = new UISelect().setOptions( wrapOptions ).setValue( texture.wrapT ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/wrapT' ), this.wrapT ) );
+
+		// Filtering group
+
+		form.add( createGroupHeading( this.strings.getKey( 'dialog/texture/group/filtering' ) ) );
+
+		this.minFilter = new UISelect().setOptions( {
+			[ THREE.NearestFilter ]: 'Nearest',
+			[ THREE.NearestMipmapNearestFilter ]: 'Nearest Mipmap Nearest',
+			[ THREE.NearestMipmapLinearFilter ]: 'Nearest Mipmap Linear',
+			[ THREE.LinearFilter ]: 'Linear',
+			[ THREE.LinearMipmapNearestFilter ]: 'Linear Mipmap Nearest',
+			[ THREE.LinearMipmapLinearFilter ]: 'Linear Mipmap Linear'
+		} ).setValue( texture.minFilter ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/minFilter' ), this.minFilter ) );
+
+		this.magFilter = new UISelect().setOptions( {
+			[ THREE.NearestFilter ]: 'Nearest',
+			[ THREE.LinearFilter ]: 'Linear'
+		} ).setValue( texture.magFilter ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/magFilter' ), this.magFilter ) );
+
+		this.anisotropy = new UINumber( texture.anisotropy ).setPrecision( 0 ).setRange( 1, 16 ).setNudge( 1 ).setStep( 1 ).setWidth( '60px' ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/anisotropy' ), this.anisotropy ) );
+
+		// Transform group
+
+		form.add( createGroupHeading( this.strings.getKey( 'dialog/texture/group/transform' ) ) );
+
+		this.offsetX = new UINumber( texture.offset.x ).setWidth( '60px' ).onChange( updatePreview );
+		this.offsetY = new UINumber( texture.offset.y ).setWidth( '60px' ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/offset' ), this.offsetX, this.offsetY ) );
+
+		this.repeatX = new UINumber( texture.repeat.x ).setWidth( '60px' ).onChange( updatePreview );
+		this.repeatY = new UINumber( texture.repeat.y ).setWidth( '60px' ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/repeat' ), this.repeatX, this.repeatY ) );
+
+		this.centerX = new UINumber( texture.center.x ).setWidth( '60px' ).onChange( updatePreview );
+		this.centerY = new UINumber( texture.center.y ).setWidth( '60px' ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/center' ), this.centerX, this.centerY ) );
+
+		this.rotation = new UINumber( texture.rotation * THREE.MathUtils.RAD2DEG ).setStep( 10 ).setNudge( 0.1 ).setUnit( '°' ).setWidth( '60px' ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/rotation' ), this.rotation ) );
+
+		// Color group
+
+		form.add( createGroupHeading( this.strings.getKey( 'dialog/texture/group/color' ) ) );
+
+		this.premultiplyAlpha = new UICheckbox( texture.premultiplyAlpha ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/premultiplyAlpha' ), this.premultiplyAlpha ) );
+
+		this.colorSpace = new UISelect().setOptions( {
+			[ THREE.NoColorSpace ]: 'No Color Space',
+			[ THREE.SRGBColorSpace ]: 'sRGB',
+			[ THREE.LinearSRGBColorSpace ]: 'Linear sRGB'
+		} ).setValue( texture.colorSpace ).onChange( updatePreview );
+		form.add( createRow( this.strings.getKey( 'dialog/texture/colorSpace' ), this.colorSpace ) );
+
+		updatePreview();
+
+		// Buttons
+
+		const buttonsRow = new UIDiv();
+		buttonsRow.setClass( 'Dialog-buttons' );
+		body.add( buttonsRow );
+
+		const okButton = new UIButton( this.strings.getKey( 'dialog/ok' ) );
+		okButton.setWidth( '80px' );
+		okButton.onClick( () => this.confirm() );
+		buttonsRow.add( okButton );
+
+		const cancelButton = new UIButton( this.strings.getKey( 'dialog/cancel' ) );
+		cancelButton.setWidth( '80px' );
+		cancelButton.setMarginLeft( '8px' );
+		cancelButton.onClick( () => this.cancel() );
+		buttonsRow.add( cancelButton );
+
+		// Promise handlers
+
+		this.resolve = null;
+		this.reject = null;
+
+	}
+
+	show() {
+
+		document.body.appendChild( this.dom );
+
+		return new Promise( ( resolve, reject ) => {
+
+			this.resolve = resolve;
+			this.reject = reject;
+
+		} );
+
+	}
+
+	getCurrentParameters() {
+
+		return {
+			mapping: parseInt( this.mapping.getValue() ),
+			wrapS: parseInt( this.wrapS.getValue() ),
+			wrapT: parseInt( this.wrapT.getValue() ),
+			magFilter: parseInt( this.magFilter.getValue() ),
+			minFilter: parseInt( this.minFilter.getValue() ),
+			anisotropy: this.anisotropy.getValue(),
+			offset: { x: this.offsetX.getValue(), y: this.offsetY.getValue() },
+			repeat: { x: this.repeatX.getValue(), y: this.repeatY.getValue() },
+			center: { x: this.centerX.getValue(), y: this.centerY.getValue() },
+			rotation: this.rotation.getValue() * THREE.MathUtils.DEG2RAD,
+			premultiplyAlpha: this.premultiplyAlpha.getValue(),
+			colorSpace: this.colorSpace.getValue()
+		};
+
+	}
+
+	updatePreview() {
+
+		applyParameters( this.previewTexture, this.getCurrentParameters() );
+
+		const rendered = renderToCanvas( this.previewTexture );
+
+		const canvas = this.previewCanvas;
+		const context = this.previewContext;
+		context.clearRect( 0, 0, canvas.width, canvas.height );
+
+		if ( rendered.width === 0 || rendered.height === 0 ) return;
+
+		const scale = Math.min( canvas.width / rendered.width, canvas.height / rendered.height );
+		const w = rendered.width * scale;
+		const h = rendered.height * scale;
+		context.drawImage( rendered, ( canvas.width - w ) / 2, ( canvas.height - h ) / 2, w, h );
+
+	}
+
+	confirm() {
+
+		const result = this.getCurrentParameters();
+
+		this.previewTexture.dispose();
+		this.dom.remove();
+
+		if ( this.resolve ) this.resolve( result );
+
+	}
+
+	cancel() {
+
+		this.previewTexture.dispose();
+		this.dom.remove();
+
+		if ( this.reject ) this.reject( new Error( 'Texture parameters edit cancelled' ) );
+
+	}
+
+}
+
+function createRow( label, ...inputs ) {
+
+	const row = new UIRow();
+	row.add( new UIText( label ).setClass( 'Label' ) );
+
+	for ( const input of inputs ) {
+
+		row.add( input );
+
+	}
+
+	return row;
+
+}
+
+function createGroupHeading( label ) {
+
+	const heading = new UIText( label );
+	heading.setClass( 'TextureParametersDialog-groupHeading' );
+	heading.setStyle( 'display', [ 'block' ] );
+	return heading;
+
+}
+
+function applyParameters( texture, p ) {
+
+	texture.mapping = p.mapping;
+	texture.wrapS = p.wrapS;
+	texture.wrapT = p.wrapT;
+	texture.magFilter = p.magFilter;
+	texture.minFilter = p.minFilter;
+	texture.anisotropy = p.anisotropy;
+	texture.offset.set( p.offset.x, p.offset.y );
+	texture.repeat.set( p.repeat.x, p.repeat.y );
+	texture.center.set( p.center.x, p.center.y );
+	texture.rotation = p.rotation;
+	texture.premultiplyAlpha = p.premultiplyAlpha;
+	texture.colorSpace = p.colorSpace;
+	texture.needsUpdate = true;
+
+}
+
+export { TextureParametersDialog };

+ 1 - 0
editor/js/commands/Commands.js

@@ -19,5 +19,6 @@ export { SetScaleCommand } from './SetScaleCommand.js';
 export { SetSceneCommand } from './SetSceneCommand.js';
 export { SetScriptValueCommand } from './SetScriptValueCommand.js';
 export { SetShadowValueCommand } from './SetShadowValueCommand.js';
+export { SetTextureParametersCommand } from './SetTextureParametersCommand.js';
 export { SetUuidCommand } from './SetUuidCommand.js';
 export { SetValueCommand } from './SetValueCommand.js';

+ 143 - 0
editor/js/commands/SetTextureParametersCommand.js

@@ -0,0 +1,143 @@
+import { Command } from '../Command.js';
+
+const VECTOR_KEYS = [ 'offset', 'repeat', 'center' ];
+
+class SetTextureParametersCommand extends Command {
+
+	/**
+	 * @param {Editor} editor
+	 * @param {THREE.Texture} texture
+	 * @param {Object} newParameters
+	 * @constructor
+	 */
+	constructor( editor, texture = null, newParameters = {} ) {
+
+		super( editor );
+
+		this.type = 'SetTextureParametersCommand';
+		this.name = editor.strings.getKey( 'command/SetTextureParameters' );
+
+		this.texture = texture;
+
+		this.oldParameters = ( texture !== null ) ? extractParameters( texture, newParameters ) : {};
+		this.newParameters = newParameters;
+
+	}
+
+	execute() {
+
+		applyParameters( this.texture, this.newParameters );
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	}
+
+	undo() {
+
+		applyParameters( this.texture, this.oldParameters );
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	}
+
+	toJSON() {
+
+		const output = super.toJSON( this );
+
+		output.textureUuid = this.texture.uuid;
+		output.oldParameters = this.oldParameters;
+		output.newParameters = this.newParameters;
+
+		return output;
+
+	}
+
+	fromJSON( json ) {
+
+		super.fromJSON( json );
+
+		this.texture = findTextureByUuid( this.editor, json.textureUuid );
+		this.oldParameters = json.oldParameters;
+		this.newParameters = json.newParameters;
+
+	}
+
+}
+
+function extractParameters( texture, reference ) {
+
+	const result = {};
+
+	for ( const key in reference ) {
+
+		const value = texture[ key ];
+
+		if ( VECTOR_KEYS.includes( key ) ) {
+
+			result[ key ] = { x: value.x, y: value.y };
+
+		} else {
+
+			result[ key ] = value;
+
+		}
+
+	}
+
+	return result;
+
+}
+
+function applyParameters( texture, parameters ) {
+
+	for ( const key in parameters ) {
+
+		const value = parameters[ key ];
+
+		if ( VECTOR_KEYS.includes( key ) ) {
+
+			texture[ key ].set( value.x, value.y );
+
+		} else {
+
+			texture[ key ] = value;
+
+		}
+
+	}
+
+	texture.needsUpdate = true;
+
+}
+
+function findTextureByUuid( editor, uuid ) {
+
+	let result = null;
+
+	editor.scene.traverse( ( object ) => {
+
+		if ( object.material === undefined ) return;
+
+		const materials = Array.isArray( object.material ) ? object.material : [ object.material ];
+
+		for ( const material of materials ) {
+
+			for ( const key in material ) {
+
+				const value = material[ key ];
+
+				if ( value && value.isTexture === true && value.uuid === uuid ) {
+
+					result = value;
+
+				}
+
+			}
+
+		}
+
+	} );
+
+	return result;
+
+}
+
+export { SetTextureParametersCommand };

+ 20 - 31
editor/js/libs/ui.three.js

@@ -53,13 +53,23 @@ class UITexture extends UISpan {
 
 			const hash = `${file.lastModified}_${file.size}_${file.name}`;
 
-			if ( cache.has( hash ) ) {
+			function deliver( texture ) {
+
+				if ( ! cache.has( hash ) ) cache.set( hash, texture );
 
-				const texture = cache.get( hash );
+				const cached = cache.get( hash );
+				const clone = cached.clone();
+				clone.sourceFile = cached.sourceFile;
 
-				scope.setValue( texture );
+				scope.setValue( clone );
 
-				if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
+				if ( scope.onChangeCallback ) scope.onChangeCallback( clone );
+
+			}
+
+			if ( cache.has( hash ) ) {
+
+				deliver( cache.get( hash ) );
 
 			} else if ( extension === 'hdr' || extension === 'pic' ) {
 
@@ -74,11 +84,7 @@ class UITexture extends UISpan {
 
 						hdrTexture.sourceFile = file.name;
 
-						cache.set( hash, hdrTexture );
-
-						scope.setValue( hdrTexture );
-
-						if ( scope.onChangeCallback ) scope.onChangeCallback( hdrTexture );
+						deliver( hdrTexture );
 
 					} );
 
@@ -98,12 +104,7 @@ class UITexture extends UISpan {
 						texture.colorSpace = THREE.SRGBColorSpace;
 						texture.sourceFile = file.name;
 
-						cache.set( hash, texture );
-
-						scope.setValue( texture );
-
-						if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
-
+						deliver( texture );
 
 					} );
 
@@ -129,11 +130,7 @@ class UITexture extends UISpan {
 						texture.sourceFile = file.name;
 						texture.needsUpdate = true;
 
-						cache.set( hash, texture );
-
-						scope.setValue( texture );
-
-						if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
+						deliver( texture );
 						ktx2Loader.dispose();
 
 					} );
@@ -157,11 +154,7 @@ class UITexture extends UISpan {
 						texture.sourceFile = file.name;
 						texture.needsUpdate = true;
 
-						cache.set( hash, texture );
-
-						scope.setValue( texture );
-
-						if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
+						deliver( texture );
 
 					} );
 
@@ -180,11 +173,7 @@ class UITexture extends UISpan {
 						texture.sourceFile = file.name;
 						texture.needsUpdate = true;
 
-						cache.set( hash, texture );
-
-						scope.setValue( texture );
-
-						if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
+						deliver( texture );
 
 					}, false );
 
@@ -882,4 +871,4 @@ function renderToCanvas( texture ) {
 
 }
 
-export { UITexture, UIOutliner, UIPoints, UIPoints2, UIPoints3, UIBoolean };
+export { UITexture, UIOutliner, UIPoints, UIPoints2, UIPoints3, UIBoolean, renderToCanvas };

粤ICP备19079148号