Unity HDRP Product Visualizer 1.0.0
Interactive 3D product showcase built with Unity 6 and HDRP
Loading...
Searching...
No Matches
ProductVisualizerSetup.cs
Go to the documentation of this file.
1#if UNITY_EDITOR
6
7using System.Collections.Generic;
8using UnityEditor;
9using UnityEditor.SceneManagement;
10using UnityEngine;
11using UnityEngine.Rendering;
12using UnityEngine.Rendering.HighDefinition;
13using UnityEngine.UI;
14using TMPro;
15
36public static class ProductVisualizerSetup
37{
39 const string ASSET_DIR = "Assets/ProductVisualizer/Generated";
40
42 const string ROOT_NAME = "=== PRODUCT VISUALIZER ===";
43
44 // -------------------------------------------------------------------------
49 [MenuItem("Tools/Product Visualizer/Setup Scene", priority = 0)]
50 public static void SetupScene()
51 {
52 EnsureDirectories();
53
54 // Remove stale setup if it exists
55 var old = GameObject.Find(ROOT_NAME);
56 if (old != null) Object.DestroyImmediate(old);
57
58 // Disable the original sun so it doesn't compete
59 var sun = GameObject.Find("Sun") ?? GameObject.Find("Directional Light");
60 if (sun != null) sun.SetActive(false);
61
62 GameObject root = new(ROOT_NAME);
63
64 // --- Sub-systems ---
65 SetupPostProcess(root);
66 SetupLights(root);
67 var (backdropRenderer, groundRenderer) = SetupEnvironment(root);
68 var (productRoot, swapper) = SetupProduct(root);
69 SetupPedestal(root);
70 var (cam, orbit) = SetupCamera(root);
71 var bgCtrl = SetupBackgroundController(root, backdropRenderer, groundRenderer);
72 SetupUI(root, orbit, swapper, bgCtrl);
73
74 EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
75 Selection.activeGameObject = root;
76 Debug.Log("[ProductVisualizer] Scene ready. Press Play!");
77 }
78
79 // -------------------------------------------------------------------------
80 // DIRECTORY HELPERS
81 // -------------------------------------------------------------------------
83 static void EnsureDirectories()
84 {
85 string[] parts = ASSET_DIR.Split('/');
86 string path = parts[0];
87 for (int i = 1; i < parts.Length; i++)
88 {
89 string next = path + "/" + parts[i];
90 if (!AssetDatabase.IsValidFolder(next))
91 AssetDatabase.CreateFolder(path, parts[i]);
92 path = next;
93 }
94 }
95
100 static string SaveAsset(Object asset, string name)
101 {
102 string path = $"{ASSET_DIR}/{name}";
103 AssetDatabase.CreateAsset(asset, path);
104 AssetDatabase.SaveAssets();
105 return path;
106 }
107
108 // -------------------------------------------------------------------------
109 // POST-PROCESSING
110 // -------------------------------------------------------------------------
115 static void SetupPostProcess(GameObject root)
116 {
117 var go = new GameObject("PostProcess Volume");
118 go.transform.SetParent(root.transform);
119
120 var volume = go.AddComponent<Volume>();
121 volume.isGlobal = true;
122 volume.priority = 1f;
123
124 var profile = ScriptableObject.CreateInstance<VolumeProfile>();
125
126 var bloom = profile.Add<Bloom>(true);
127 bloom.threshold.Override(0.85f);
128 bloom.intensity.Override(0.4f);
129 bloom.scatter.Override(0.55f);
130
131 var color = profile.Add<ColorAdjustments>(true);
132 color.contrast.Override(12f);
133 color.saturation.Override(8f);
134
135 var tone = profile.Add<Tonemapping>(true);
136 tone.mode.Override(TonemappingMode.ACES);
137
138 var ao = profile.Add<ScreenSpaceAmbientOcclusion>(true);
139 ao.intensity.Override(1.5f);
140 ao.directLightingStrength.Override(0.25f);
141
142 var vig = profile.Add<Vignette>(true);
143 vig.intensity.Override(0.2f);
144 vig.smoothness.Override(0.35f);
145
146 profile.name = "ProductVisualizerProfile";
147 string p = SaveAsset(profile, "ProductVisualizerProfile.asset");
148 volume.sharedProfile = AssetDatabase.LoadAssetAtPath<VolumeProfile>(p);
149 }
150
151 // -------------------------------------------------------------------------
152 // LIGHTS (3-point studio setup)
153 // -------------------------------------------------------------------------
163 static void SetupLights(GameObject root)
164 {
165 var lightRoot = new GameObject("Lights");
166 lightRoot.transform.SetParent(root.transform);
167
168 // Key – warm, front-left
169 MakeAreaLight(lightRoot, "Key Light",
170 pos: new Vector3(-1.6f, 2.8f, -1.2f),
171 rot: Quaternion.Euler(48f, 35f, 0f),
172 color: new Color(1f, 0.95f, 0.85f),
173 intensity: 3500f,
174 size: new Vector2(0.8f, 1.4f));
175
176 // Fill – cool, right-back
177 MakeAreaLight(lightRoot, "Fill Light",
178 pos: new Vector3(2.2f, 1.8f, 1f),
179 rot: Quaternion.Euler(28f, -125f, 0f),
180 color: new Color(0.65f, 0.8f, 1f),
181 intensity: 1200f,
182 size: new Vector2(1.8f, 0.9f));
183
184 // Rim – backlight highlight
185 MakeAreaLight(lightRoot, "Rim Light",
186 pos: new Vector3(0f, 2.2f, 2f),
187 rot: Quaternion.Euler(18f, 180f, 0f),
188 color: new Color(0.9f, 0.95f, 1f),
189 intensity: 2200f,
190 size: new Vector2(0.4f, 2f));
191
192 // Bounce – subtle floor reflection
193 MakeAreaLight(lightRoot, "Bounce Light",
194 pos: new Vector3(0f, -0.2f, 0f),
195 rot: Quaternion.Euler(-90f, 0f, 0f),
196 color: new Color(0.5f, 0.5f, 0.7f),
197 intensity: 350f,
198 size: new Vector2(3f, 3f));
199 }
200
209 static void MakeAreaLight(GameObject parent, string name,
210 Vector3 pos, Quaternion rot, Color color, float intensity, Vector2 size)
211 {
212 var go = new GameObject(name);
213 go.transform.SetParent(parent.transform);
214 go.transform.localPosition = pos;
215 go.transform.localRotation = rot;
216
217 var light = go.AddComponent<Light>();
218 light.type = LightType.Rectangle;
219 light.color = color;
220 light.intensity = intensity;
221 light.shadows = LightShadows.Soft;
222
223 var hd = go.GetComponent<HDAdditionalLightData>();
224 if (hd == null) hd = go.AddComponent<HDAdditionalLightData>();
225 hd.shapeWidth = size.x;
226 hd.shapeHeight = size.y;
227 hd.lightUnit = LightUnit.Lumen;
228 hd.intensity = intensity;
229
230 hd.normalBias = 0.3f;
231 }
232
233 // -------------------------------------------------------------------------
234 // ENVIRONMENT (backdrop + ground)
235 // -------------------------------------------------------------------------
242 static (Renderer backdrop, Renderer ground) SetupEnvironment(GameObject root)
243 {
244 var envRoot = new GameObject("Environment");
245 envRoot.transform.SetParent(root.transform);
246
247 // ---- Backdrop quad --------------------------------------------------
248 var backdropGO = new GameObject("Backdrop");
249 backdropGO.transform.SetParent(envRoot.transform);
250 backdropGO.transform.localPosition = new Vector3(0f, 1.5f, 3.5f);
251 backdropGO.transform.localScale = new Vector3(9f, 7f, 1f);
252
253 var bmf = backdropGO.AddComponent<MeshFilter>();
254 bmf.sharedMesh = Resources.GetBuiltinResource<Mesh>("Quad.fbx");
255 var bmr = backdropGO.AddComponent<MeshRenderer>();
256
257 Shader gradShader = Shader.Find("Custom/GradientBackground")
258 ?? Shader.Find("HDRP/Unlit");
259
260 var backdropMat = new Material(gradShader) { name = "Backdrop_Mat" };
261 if (gradShader.name.Contains("Gradient"))
262 {
263 backdropMat.SetColor("_TopColor", new Color(0.06f, 0.06f, 0.18f));
264 backdropMat.SetColor("_BottomColor", new Color(0.01f, 0.01f, 0.04f));
265 backdropMat.SetFloat("_VignetteAmount", 1.2f);
266 }
267 else
268 {
269 backdropMat.SetColor("_UnlitColor", new Color(0.03f, 0.03f, 0.1f));
270 }
271 bmr.sharedMaterial = AssetDatabase.LoadAssetAtPath<Material>(
272 SaveAsset(backdropMat, "BackdropMat.mat"));
273
274 // ---- Ground quad ----------------------------------------------------
275 var groundGO = new GameObject("Ground");
276 groundGO.transform.SetParent(envRoot.transform);
277 groundGO.transform.localPosition = Vector3.zero;
278 groundGO.transform.localRotation = Quaternion.Euler(90f, 0f, 0f);
279 groundGO.transform.localScale = new Vector3(10f, 10f, 1f);
280
281 var gmf = groundGO.AddComponent<MeshFilter>();
282 gmf.sharedMesh = Resources.GetBuiltinResource<Mesh>("Quad.fbx");
283 var gmr = groundGO.AddComponent<MeshRenderer>();
284
285 var groundMat = new Material(Shader.Find("HDRP/Lit")) { name = "Ground_Mat" };
286 groundMat.SetColor("_BaseColor", new Color(0.08f, 0.08f, 0.1f));
287 groundMat.SetFloat("_Metallic", 0.4f);
288 groundMat.SetFloat("_Smoothness", 0.9f);
289 gmr.sharedMaterial = AssetDatabase.LoadAssetAtPath<Material>(
290 SaveAsset(groundMat, "GroundMat.mat"));
291
292 return (bmr, gmr);
293 }
294
295 // -------------------------------------------------------------------------
296 // PEDESTAL — uses 64-sided smooth cylinder to avoid jagged shadow edges
297 // -------------------------------------------------------------------------
298 static void SetupPedestal(GameObject root)
299 {
300 var pedestalRoot = new GameObject("Pedestal");
301 pedestalRoot.transform.SetParent(root.transform);
302 pedestalRoot.transform.localPosition = Vector3.zero;
303
304 var mat = new Material(Shader.Find("HDRP/Lit")) { name = "Pedestal_Mat" };
305 mat.SetColor("_BaseColor", new Color(0.14f, 0.14f, 0.17f));
306 mat.SetFloat("_Metallic", 0.85f);
307 mat.SetFloat("_Smoothness", 0.92f);
308 var savedMat = AssetDatabase.LoadAssetAtPath<Material>(SaveAsset(mat, "PedestalMat.mat"));
309
310 PlaceSmoothCylinder(pedestalRoot.transform, "Pedestal_Body",
311 pos: new Vector3(0f, 0.15f, 0f), scale: new Vector3(1.2f, 0.15f, 1.2f), mat: savedMat);
312
313 PlaceSmoothCylinder(pedestalRoot.transform, "Pedestal_Top",
314 pos: new Vector3(0f, 0.31f, 0f), scale: new Vector3(1.32f, 0.02f, 1.32f), mat: savedMat);
315 }
316
326 static void PlaceSmoothCylinder(Transform parent, string name, Vector3 pos, Vector3 scale, Material mat)
327 {
328 const int sides = 64;
329 var mesh = BuildCylinderMesh(sides);
330 mesh.name = name + "_Mesh";
331 string meshPath = $"{ASSET_DIR}/{name}_Mesh.asset";
332 AssetDatabase.CreateAsset(mesh, meshPath);
333
334 var go = new GameObject(name);
335 go.transform.SetParent(parent);
336 go.transform.localPosition = pos;
337 go.transform.localScale = scale;
338
339 go.AddComponent<MeshFilter>().sharedMesh =
340 AssetDatabase.LoadAssetAtPath<Mesh>(meshPath);
341 go.AddComponent<MeshRenderer>().sharedMaterial = mat;
342 go.AddComponent<MeshCollider>().sharedMesh =
343 AssetDatabase.LoadAssetAtPath<Mesh>(meshPath);
344 }
345
354 static Mesh BuildCylinderMesh(int sides)
355 {
356 var mesh = new Mesh();
357
358 int rings = 2; // top and bottom rings only
359 int vertsPerRing = sides + 1;
360 int totalVerts = vertsPerRing * rings + 2; // + 2 for cap centres
361
362 var vertices = new Vector3[totalVerts];
363 var normals = new Vector3[totalVerts];
364 var uvs = new Vector2[totalVerts];
365
366 // Build side rings
367 for (int ring = 0; ring < rings; ring++)
368 {
369 float y = ring == 0 ? -1f : 1f;
370 for (int i = 0; i <= sides; i++)
371 {
372 float angle = i / (float)sides * Mathf.PI * 2f;
373 float x = Mathf.Cos(angle);
374 float z = Mathf.Sin(angle);
375 int idx = ring * vertsPerRing + i;
376 vertices[idx] = new Vector3(x, y, z);
377 normals[idx] = new Vector3(x, 0f, z);
378 uvs[idx] = new Vector2(i / (float)sides, ring);
379 }
380 }
381
382 // Cap centres
383 int bottomCentre = vertsPerRing * rings;
384 int topCentre = bottomCentre + 1;
385 vertices[bottomCentre] = new Vector3(0f, -1f, 0f);
386 normals[bottomCentre] = Vector3.down;
387 uvs[bottomCentre] = new Vector2(0.5f, 0f);
388 vertices[topCentre] = new Vector3(0f, 1f, 0f);
389 normals[topCentre] = Vector3.up;
390 uvs[topCentre] = new Vector2(0.5f, 1f);
391
392 mesh.vertices = vertices;
393 mesh.normals = normals;
394 mesh.uv = uvs;
395
396 // Triangles – sides
397 var tris = new System.Collections.Generic.List<int>();
398 for (int i = 0; i < sides; i++)
399 {
400 int b0 = i, b1 = i + 1;
401 int t0 = vertsPerRing + i, t1 = vertsPerRing + i + 1;
402 tris.AddRange(new[] { b0, t0, b1, b1, t0, t1 });
403 }
404
405 // Triangles – bottom cap
406 for (int i = 0; i < sides; i++)
407 tris.AddRange(new[] { bottomCentre, i + 1, i });
408
409 // Triangles – top cap
410 for (int i = 0; i < sides; i++)
411 tris.AddRange(new[] { topCentre, vertsPerRing + i, vertsPerRing + i + 1 });
412
413 mesh.triangles = tris.ToArray();
414 mesh.RecalculateBounds();
415 return mesh;
416 }
417
418 // -------------------------------------------------------------------------
419 // PRODUCT (real FBX can from Resources/Prefabs/Can)
420 // -------------------------------------------------------------------------
428 static (GameObject productRoot, CanMaterialSwapper swapper) SetupProduct(GameObject root)
429 {
430 var productRoot = new GameObject("Product");
431 productRoot.transform.SetParent(root.transform);
432 productRoot.transform.localPosition = new Vector3(0f, 0.72f, 0f);
433 productRoot.AddComponent<ProductFloatAnimation>();
434
435 // ---- Instantiate the real FBX can -----------------------------------
436 var canPrefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Resources/Prefabs/Can.prefab");
437 GameObject canGO;
438 if (canPrefab != null)
439 {
440 canGO = (GameObject)PrefabUtility.InstantiatePrefab(canPrefab, productRoot.transform);
441 canGO.transform.localPosition = Vector3.zero;
442 canGO.transform.localRotation = Quaternion.identity;
443 canGO.transform.localScale = Vector3.one;
444 }
445 else
446 {
447 // Fallback: simple cylinder if prefab not found
448 canGO = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
449 canGO.transform.SetParent(productRoot.transform);
450 canGO.transform.localPosition = Vector3.zero;
451 canGO.transform.localScale = new Vector3(0.36f, 0.55f, 0.36f);
452 Debug.LogWarning("[ProductVisualizer] Can.prefab not found at Assets/Resources/Prefabs/Can.prefab");
453 }
454
455 // ---- Shared aluminium cap material (slot 1) -------------------------
456 var capMat = new Material(Shader.Find("HDRP/Lit")) { name = "Can_Cap_Mat" };
457 capMat.SetColor("_BaseColor", new Color(0.76f, 0.76f, 0.80f));
458 capMat.SetFloat("_Metallic", 0.96f);
459 capMat.SetFloat("_Smoothness", 0.92f);
460 var savedCapMat = AssetDatabase.LoadAssetAtPath<Material>(SaveAsset(capMat, "Can_Cap_Mat.mat"));
461
462 // ---- One label material per brand (slot 0) --------------------------
463 var brands = new (string name, string texPath)[]
464 {
465 ("Coca-Cola", "Assets/Resources/textures/Coke Can.jpeg"),
466 ("Pepsi", "Assets/Resources/textures/Pepsi Can.jpeg"),
467 ("Mountain Dew", "Assets/Resources/textures/Mountain Dew Can.jpeg"),
468 ("Sprite", "Assets/Resources/textures/Spritei Can.jpeg"),
469 };
470
471 var litShader = Shader.Find("HDRP/Lit");
472 var swapper = productRoot.AddComponent<CanMaterialSwapper>();
473 swapper.canRenderer = canGO.GetComponent<Renderer>()
474 ?? canGO.GetComponentInChildren<Renderer>();
475
476 foreach (var (brandName, texPath) in brands)
477 {
478 var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(texPath);
479 if (tex == null)
480 {
481 Debug.LogWarning($"[ProductVisualizer] Texture not found: {texPath}");
482 continue;
483 }
484
485 string safeName = brandName.Replace(" ", "_");
486 var labelMat = new Material(litShader) { name = $"Can_Label_{safeName}" };
487 labelMat.SetTexture("_BaseColorMap", tex);
488 labelMat.SetColor("_BaseColor", Color.white);
489 labelMat.SetFloat("_Metallic", 0.15f);
490 labelMat.SetFloat("_Smoothness", 0.50f);
491 var savedLabelMat = AssetDatabase.LoadAssetAtPath<Material>(
492 SaveAsset(labelMat, $"Can_Label_{safeName}.mat"));
493
494 swapper.variants.Add(new CanVariant
495 {
496 variantName = brandName,
497 labelMaterial = savedLabelMat,
498 capMaterial = savedCapMat,
499 });
500 }
501
502 return (productRoot, swapper);
503 }
504
505 // -------------------------------------------------------------------------
506 // CAMERA
507 // -------------------------------------------------------------------------
513 static (GameObject camGO, OrbitCamera orbit) SetupCamera(GameObject root)
514 {
515 // Target
516 var target = new GameObject("Camera Target");
517 target.transform.SetParent(root.transform);
518 target.transform.localPosition = new Vector3(0f, 0.7f, 0f);
519
520 // Reuse or create main camera
521 Camera mainCam = Camera.main;
522 GameObject camGO;
523 if (mainCam == null)
524 {
525 camGO = new GameObject("Main Camera");
526 camGO.tag = "MainCamera";
527 mainCam = camGO.AddComponent<Camera>();
528 camGO.AddComponent<AudioListener>();
529 }
530 else
531 {
532 camGO = mainCam.gameObject;
533 }
534
535 camGO.transform.SetParent(root.transform);
536 camGO.transform.localPosition = new Vector3(0f, 0.7f, -3f);
537 mainCam.usePhysicalProperties = true;
538 mainCam.nearClipPlane = 0.1f;
539 mainCam.farClipPlane = 50f;
540
541 var hd = camGO.GetComponent<HDAdditionalCameraData>();
542 if (hd == null) hd = camGO.AddComponent<HDAdditionalCameraData>();
543
544 var orbit = camGO.AddComponent<OrbitCamera>();
545 orbit.target = target.transform;
546 orbit.distance = 3f;
547 orbit.autoRotate = true;
548 orbit.autoRotateSpeed = 20f;
549
550 // Reflection probe
551 var probeGO = new GameObject("Reflection Probe");
552 probeGO.transform.SetParent(root.transform);
553 probeGO.transform.localPosition = new Vector3(0f, 1f, 0f);
554 var probe = probeGO.AddComponent<ReflectionProbe>();
555 probe.size = new Vector3(10f, 6f, 10f);
556 probe.resolution = 256;
557 probe.mode = ReflectionProbeMode.Realtime;
558 probe.refreshMode = ReflectionProbeRefreshMode.EveryFrame;
559
560 return (camGO, orbit);
561 }
562
563 // -------------------------------------------------------------------------
564 // BACKGROUND CONTROLLER
565 // -------------------------------------------------------------------------
571 static BackgroundController SetupBackgroundController(
572 GameObject root, Renderer backdrop, Renderer ground)
573 {
574 var go = new GameObject("Background Controller");
575 go.transform.SetParent(root.transform);
576
577 var ctrl = go.AddComponent<BackgroundController>();
578 ctrl.backdropRenderer = backdrop;
579 ctrl.groundRenderer = ground;
580
581 ctrl.presets = new List<BackgroundPreset>
582 {
583 new() { presetName = "Dark Studio",
584 topColor = new Color(0.06f, 0.06f, 0.18f), bottomColor = new Color(0.01f, 0.01f, 0.04f),
585 vignette = 1.2f, groundTint = new Color(0.08f, 0.08f, 0.10f) },
586
587 new() { presetName = "Warm Amber",
588 topColor = new Color(0.20f, 0.10f, 0.03f), bottomColor = new Color(0.04f, 0.02f, 0.01f),
589 vignette = 1.0f, groundTint = new Color(0.12f, 0.08f, 0.04f) },
590
591 new() { presetName = "Deep Space",
592 topColor = new Color(0.01f, 0.02f, 0.08f), bottomColor = new Color(0.00f, 0.00f, 0.02f),
593 vignette = 1.5f, groundTint = new Color(0.03f, 0.03f, 0.06f) },
594
595 new() { presetName = "Emerald Night",
596 topColor = new Color(0.02f, 0.10f, 0.06f), bottomColor = new Color(0.01f, 0.03f, 0.02f),
597 vignette = 1.1f, groundTint = new Color(0.04f, 0.08f, 0.05f) },
598 };
599
600 return ctrl;
601 }
602
603 // -------------------------------------------------------------------------
604 // UI
605 // -------------------------------------------------------------------------
613 static void SetupUI(GameObject root,
615 {
616 // Canvas
617 var canvasGO = new GameObject("UI Canvas");
618 canvasGO.transform.SetParent(root.transform);
619 var canvas = canvasGO.AddComponent<Canvas>();
620 canvas.renderMode = RenderMode.ScreenSpaceOverlay;
621 canvas.sortingOrder = 10;
622 var scaler = canvasGO.AddComponent<CanvasScaler>();
623 scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
624 scaler.referenceResolution = new Vector2(1920f, 1080f);
625 scaler.matchWidthOrHeight = 0.5f;
626 canvasGO.AddComponent<GraphicRaycaster>();
627
628 // EventSystem
629 if (Object.FindFirstObjectByType<UnityEngine.EventSystems.EventSystem>() == null)
630 {
631 var es = new GameObject("EventSystem");
632 es.transform.SetParent(root.transform);
633 es.AddComponent<UnityEngine.EventSystems.EventSystem>();
634 es.AddComponent<UnityEngine.EventSystems.StandaloneInputModule>();
635 }
636
637 // ---- Bottom control bar ---
638 var bar = MakePanel(canvasGO.transform, "Controls Bar",
639 anchorMin: new Vector2(0f, 0f), anchorMax: new Vector2(1f, 0f),
640 pivot: new Vector2(0.5f, 0f), size: new Vector2(0f, 72f), pos: Vector2.zero);
641 var barImg = bar.AddComponent<Image>();
642 barImg.color = new Color(0f, 0f, 0f, 0.55f);
643
644 float bw = 150f, bh = 48f, gap = 160f;
645 float startX = -gap * 2f;
646
647 var btnAutoRot = MakeButton(bar.transform, "Auto-Rotate", new Vector2(startX, 36f), new Vector2(bw, bh));
648 var btnPrevCol = MakeButton(bar.transform, "< Brand", new Vector2(startX + gap, 36f), new Vector2(bw, bh));
649 var btnNextCol = MakeButton(bar.transform, "Brand >", new Vector2(startX + gap*2, 36f), new Vector2(bw, bh));
650 var btnBg = MakeButton(bar.transform, "Background", new Vector2(startX + gap*3, 36f), new Vector2(bw, bh));
651 var btnReset = MakeButton(bar.transform, "Reset Camera", new Vector2(startX + gap*4, 36f), new Vector2(bw, bh));
652
653 // ---- Top-left info panel ---
654 var info = MakePanel(canvasGO.transform, "Info Panel",
655 anchorMin: new Vector2(0f, 1f), anchorMax: new Vector2(0f, 1f),
656 pivot: new Vector2(0f, 1f), size: new Vector2(240f, 110f), pos: new Vector2(16f, -16f));
657 var infoImg = info.AddComponent<Image>();
658 infoImg.color = new Color(0f, 0f, 0f, 0.45f);
659
660 var lblVariant = MakeLabel(info.transform, "VariantLabel", "Brand: Coca-Cola", new Vector2(12f, -12f));
661 var lblBg = MakeLabel(info.transform, "BgLabel", "Scene: Dark Studio", new Vector2(12f, -42f));
662 var lblRot = MakeLabel(info.transform, "RotLabel", "Auto-Rotate: ON", new Vector2(12f, -72f));
663
664 // ---- Wire VisualizerUI ---
665 var ui = canvasGO.AddComponent<VisualizerUI>();
666 ui.orbitCamera = orbit;
667 ui.canSwapper = swapper;
668 ui.backgroundController = bgCtrl;
669 ui.autoRotateButton = btnAutoRot;
670 ui.nextColorButton = btnNextCol;
671 ui.prevColorButton = btnPrevCol;
672 ui.nextBgButton = btnBg;
673 ui.resetCameraButton = btnReset;
674 ui.variantLabel = lblVariant;
675 ui.backgroundLabel = lblBg;
676 ui.rotationLabel = lblRot;
677 }
678
679 // ---- UI Helpers ---
681 static GameObject MakePanel(Transform parent, string name,
682 Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot, Vector2 size, Vector2 pos)
683 {
684 var go = new GameObject(name);
685 go.transform.SetParent(parent, false);
686 var rt = go.AddComponent<RectTransform>();
687 rt.anchorMin = anchorMin;
688 rt.anchorMax = anchorMax;
689 rt.pivot = pivot;
690 rt.sizeDelta = size;
691 rt.anchoredPosition = pos;
692 return go;
693 }
694
696 static Button MakeButton(Transform parent, string label, Vector2 pos, Vector2 size)
697 {
698 var go = new GameObject($"Btn_{label}");
699 go.transform.SetParent(parent, false);
700 var rt = go.AddComponent<RectTransform>();
701 rt.anchoredPosition = pos;
702 rt.sizeDelta = size;
703
704 var img = go.AddComponent<Image>();
705 img.color = new Color(0.12f, 0.12f, 0.18f, 0.92f);
706
707 var btn = go.AddComponent<Button>();
708 ColorBlock cb = btn.colors;
709 cb.highlightedColor = new Color(0.22f, 0.22f, 0.35f, 1f);
710 cb.pressedColor = new Color(0.08f, 0.08f, 0.12f, 1f);
711 btn.colors = cb;
712 btn.targetGraphic = img;
713
714 var textGO = new GameObject("Label");
715 textGO.transform.SetParent(go.transform, false);
716 var textRT = textGO.AddComponent<RectTransform>();
717 textRT.anchorMin = Vector2.zero;
718 textRT.anchorMax = Vector2.one;
719 textRT.offsetMin = Vector2.zero;
720 textRT.offsetMax = Vector2.zero;
721
722 var tmp = textGO.AddComponent<TextMeshProUGUI>();
723 tmp.text = label;
724 tmp.fontSize = 15f;
725 tmp.alignment = TextAlignmentOptions.Center;
726 tmp.color = Color.white;
727
728 return btn;
729 }
730
731 static TextMeshProUGUI MakeLabel(Transform parent, string name, string text, Vector2 pos)
732 {
733 var go = new GameObject(name);
734 go.transform.SetParent(parent, false);
735 var rt = go.AddComponent<RectTransform>();
736 rt.anchorMin = new Vector2(0f, 1f);
737 rt.anchorMax = new Vector2(1f, 1f);
738 rt.pivot = new Vector2(0f, 1f);
739 rt.anchoredPosition = pos;
740 rt.sizeDelta = new Vector2(-24f, 26f);
741
742 var tmp = go.AddComponent<TextMeshProUGUI>();
743 tmp.text = text;
744 tmp.fontSize = 15f;
745 tmp.color = Color.white;
746 return tmp;
747 }
748}
749#endif
Cycles through BackgroundPreset entries by writing to MaterialPropertyBlocks.
Cycles through brand variants by replacing the full material array on the can renderer.
List< CanVariant > variants
Ordered list of brand variants available in the visualizer.
Data container that pairs a brand label material with an aluminium cap material.
Spherical-coordinate camera that orbits around a target Transform.
Animates a GameObject with a gentle sinusoidal float and dual-axis tilt.
Connects Unity UI buttons and TextMeshPro labels to the three scene controllers.