Derive picture frame size from picture texture aspect ratio
Replace Width/Height with a single Size (longer side); the shorter side follows the picture material's base map source-image aspect (editor- resolved, serialized for builds, 1:1 fallback). Add Range attributes on all frame parameters and default material references (new Frame.mat). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bac32a87a9
commit
850c92bcce
4 files changed
+231
-16
No files matched your search
@@ -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<MeshFilter>();
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -1,2 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86a75d0253697424fb4b506c394f13bc
|
||||
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:
|
||||
Reference in new issue
Block a user