diff --git a/Assets/Materials/Frame.mat b/Assets/Materials/Frame.mat new file mode 100644 index 0000000..72c02d6 --- /dev/null +++ b/Assets/Materials/Frame.mat @@ -0,0 +1,137 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Frame + m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: + RenderType: Opaque + disabledShaderPasses: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AddPrecomputedVelocity: 0 + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.005 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _WorkflowMode: 1 + - _XRMotionVectorsPass: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 +--- !u!114 &6905797306856702536 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion + version: 10 diff --git a/Assets/Materials/Frame.mat.meta b/Assets/Materials/Frame.mat.meta new file mode 100644 index 0000000..47e062d --- /dev/null +++ b/Assets/Materials/Frame.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a7794896ef878a64399cca929db5c43c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Components/PictureFrameBuilder.cs b/Assets/Scripts/Components/PictureFrameBuilder.cs index 62fc400..20ec461 100644 --- a/Assets/Scripts/Components/PictureFrameBuilder.cs +++ b/Assets/Scripts/Components/PictureFrameBuilder.cs @@ -5,8 +5,9 @@ namespace Chameleon.Components { // Procedural picture frame: a box-profile frame with a recessed inner floor (FrameInnerDepth) and a // picture plane offset relative to it (PictureDepth: + sunken, - raised plinth), generated live in the - // editor (and at runtime) from the inspector parameters - resize via Width/Height, NOT via transform - // scale, so the frame profile never stretches (9-slice behaviour). Four submeshes with their own + // editor (and at runtime) from the inspector parameters - resize via Size (width/height follow the + // picture texture's aspect ratio), NOT via transform scale, so the frame profile never stretches + // (9-slice behaviour). Four submeshes with their own // material slots: 0 picture, 1 inner cavity (recess walls + padding ring), 2 frame (front ring, outer // sides, edge rounding), 3 back plate. Front faces -Z like Unity's built-in Quad. // @@ -27,28 +28,31 @@ namespace Chameleon.Components const int SubBack = 3; [Header("Size")] - [Tooltip("Outer width of the frame in meters.")] - [SerializeField, Min(0.01f)] float _width = 1f; - [Tooltip("Outer height of the frame in meters.")] - [SerializeField, Min(0.01f)] float _height = 1f; + [Tooltip("Length of the frame's longer side in meters; the shorter side follows the picture's aspect ratio.")] + [SerializeField, Min(0.01f)] float _size = 1f; + // Auto-resolved in the editor from the picture material's base map texture (longer side = 1, + // 1:1 when no texture is found) and serialized, because the source image file/importer is not + // available in a build. Manual edits are overwritten on the next rebuild. + [Tooltip("Width : height factors derived from the picture texture (auto-set in the editor, longer side = 1).")] + [SerializeField] Vector2 _aspect = Vector2.one; [Header("Frame")] [Tooltip("Border width of the frame profile in the picture plane.")] - [SerializeField, Min(0.001f)] float _frameThickness = 0.08f; + [SerializeField, Range(0.001f, 1.000f)] float _frameThickness = 0.08f; [Tooltip("Depth of the frame, front face to back face.")] - [SerializeField, Min(0.002f)] float _frameDepth = 0.12f; + [SerializeField, Range(0.002f, 0.500f)] float _frameDepth = 0.12f; [Tooltip("Rounding radius of the outer frame edges; 0 = sharp box. Clamped to thickness / half depth.")] - [SerializeField, Min(0f)] float _cornerRadius; + [SerializeField, Range(0.000f, 0.500f)] float _cornerRadius; [Tooltip("Arc segments of the edge rounding.")] - [SerializeField, Range(1, 12)] int _bevelSegments = 4; + [SerializeField, Range(1, 8)] int _bevelSegments = 4; [Tooltip("How deep behind the front face the inner floor (padding ring) sits.")] - [SerializeField, Min(0f)] float _frameInnerDepth = 0.06f; + [SerializeField, Range(0.000f, 0.498f)] float _frameInnerDepth = 0.06f; [Header("Picture")] [Tooltip("Gap between the frame's inner edge and the picture (visible inner-material ring).")] - [SerializeField, Min(0f)] float _padding = 0.02f; + [SerializeField, Range(0.000f, 1.000f)] float _padding = 0.02f; [Tooltip("Picture plane relative to the inner floor: positive sinks it into the frame, negative raises it out like a plinth, 0 = flush.")] - [SerializeField] float _pictureDepth; + [SerializeField, Range(-0.500f, 0.500f)] float _pictureDepth; [Header("Materials")] [SerializeField] Material _pictureMaterial; @@ -110,8 +114,11 @@ namespace Chameleon.Components var filter = GetComponent(); EnsureMesh(filter); - float w = Mathf.Max(_width, 0.01f); - float h = Mathf.Max(_height, 0.01f); +#if UNITY_EDITOR + ResolveAspect(); +#endif + float w = Mathf.Max(_size * _aspect.x, 0.01f); + float h = Mathf.Max(_size * _aspect.y, 0.01f); float t = Mathf.Clamp(_frameThickness, 0.001f, Mathf.Min(w, h) * 0.5f - 0.001f); float d = Mathf.Max(_frameDepth, 0.002f); float r = Mathf.Min(_cornerRadius, Mathf.Min(t, d * 0.5f) - 0.0005f); @@ -239,6 +246,56 @@ namespace Chameleon.Components meshRenderer.sharedMaterials = new[] { _pictureMaterial, _innerMaterial, _frameMaterial, _backMaterial }; } +#if UNITY_EDITOR + // Importer API for the source file's pixel size (public in Unity 6, internal in older + // versions - hence reflection; null-fallback is the imported texture size). + static readonly System.Reflection.MethodInfo s_sourceSize = typeof(UnityEditor.TextureImporter) + .GetMethod("GetSourceTextureWidthAndHeight", + System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic + | System.Reflection.BindingFlags.Instance); + + // Editor-only: derive _aspect (longer side = 1) from the picture material's base map and + // serialize it - the source image file is not available in a build. 1:1 whenever the + // material -> base map -> texture chain breaks. + void ResolveAspect() + { + var aspect = Vector2.one; + Texture tex = null; + if (_pictureMaterial) + { + if (_pictureMaterial.HasProperty("_BaseMap")) tex = _pictureMaterial.GetTexture("_BaseMap"); + if (!tex) tex = _pictureMaterial.mainTexture; + } + + if (tex) + { + // Prefer the source file's pixel size: importer NPOT/max-size scaling can distort the + // imported texture's proportions (e.g. 4:3 photo -> power-of-two texture). + int w = tex.width; + int h = tex.height; + string path = UnityEditor.AssetDatabase.GetAssetPath(tex); + if (s_sourceSize != null && !string.IsNullOrEmpty(path) + && UnityEditor.AssetImporter.GetAtPath(path) is UnityEditor.TextureImporter importer) + { + object[] size = { 0, 0 }; + s_sourceSize.Invoke(importer, size); + if ((int)size[0] > 0 && (int)size[1] > 0) + { + w = (int)size[0]; + h = (int)size[1]; + } + } + + if (w > 0 && h > 0) + aspect = w >= h ? new Vector2(1f, (float)h / w) : new Vector2((float)w / h, 1f); + } + + if ((aspect - _aspect).sqrMagnitude < 1e-12f) return; + _aspect = aspect; + UnityEditor.EditorUtility.SetDirty(this); + } +#endif + // Flat ring between two concentric rects at depth z, facing the viewer (-Z), mitred corners. void Ring(int sub, float ox, float oy, float ix, float iy, float z) { diff --git a/Assets/Scripts/Components/PictureFrameBuilder.cs.meta b/Assets/Scripts/Components/PictureFrameBuilder.cs.meta index c2b0ae0..92231bb 100644 --- a/Assets/Scripts/Components/PictureFrameBuilder.cs.meta +++ b/Assets/Scripts/Components/PictureFrameBuilder.cs.meta @@ -1,2 +1,15 @@ fileFormatVersion: 2 -guid: 86a75d0253697424fb4b506c394f13bc \ No newline at end of file +guid: 86a75d0253697424fb4b506c394f13bc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - _pictureMaterial: {instanceID: 0} + - _innerMaterial: {fileID: 2100000, guid: a7794896ef878a64399cca929db5c43c, type: 2} + - _frameMaterial: {fileID: 2100000, guid: a7794896ef878a64399cca929db5c43c, type: 2} + - _backMaterial: {fileID: 2100000, guid: a7794896ef878a64399cca929db5c43c, type: 2} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: