অ্যাপ ডেভেলপার হিসেবে, আমরা শুধু কোডার নই - আমরা স্রষ্টা, নির্মাতা এবং কখনও কখনও মায়াবাদী। অ্যাপ ডেভেলপমেন্টের শিল্প শুধু কোড এবং ডিজাইনের বাইরে যায়। কখনও কখনও, এটি বিস্ময় এবং বিভ্রমের একটি উপাদান তৈরি করার বিষয়ে যা ব্যবহারকারীদের মনোযোগ আকর্ষণ করে এবং একটি নিমগ্ন অভিজ্ঞতা তৈরি করে৷ এই সময়ে, আমরা 2D বিশ্বের আমাদের আরামের অঞ্চল থেকে বেরিয়ে আসছি, এবং 3D- এর মনোমুগ্ধকর জগতে একটি সাহসী লাফিয়ে উঠছি৷
ইউআইকিট ইউজার ইন্টারফেস তৈরির জন্য একাধিক টুলের সেট। এটি একটি শক্তিশালী টুলকিট যা সঠিকভাবে ব্যবহার করলে আশ্চর্যজনক ভিজ্যুয়াল এফেক্ট তৈরি করতে পারে। এই নিবন্ধে, আমরা UIKit এর গভীরে খনন করব এবং আয়নার মতো প্রতিফলন তৈরি করার জন্য একটি কৌশল দেখাব। এই প্রভাবটি আপনার অ্যাপটিকে একটি দৃশ্যত চিত্তাকর্ষক এবং আকর্ষক চেহারা দিতে পারে যা সাধারণত শুধুমাত্র জটিল গ্রাফিকাল সরঞ্জামগুলির সাথে অর্জনযোগ্য বলে মনে হয়, তবুও এটি কোড ছাড়া আর কিছুই দিয়ে তৈরি নয়৷
এই সুন্দর, চকচকে কিউবটি দেখুন। এটি কখনই মরিচা পড়বে না, কারণ এটি কোনও ধাতু ব্যবহার করে না।
এখন, কোড ব্যবহার করে কীভাবে এটি তৈরি করা যায় তা শিখে নেওয়া যাক।
আমাদের উদ্দেশ্যে, UIKit কোয়ার্টজ কোরের উপরে একটি পাতলা স্তর হিসাবে কাজ করে, যা আমাদেরকে এর 3D ক্ষমতাগুলিতে বিনামূল্যে অ্যাক্সেস প্রদান করে। একটি UIView
একটি CALayer
অবজেক্টের একটি রেফারেন্স ধারণ করে, যা OS অন-স্ক্রীন রেন্ডারিংয়ের জন্য ব্যবহার করে আসল উপাদান। CALayer
তিনটি বৈশিষ্ট্য রয়েছে যা এর অন-স্ক্রিন উপস্থাপনাকে প্রভাবিত করে: অবস্থান, সীমানা এবং রূপান্তর। প্রথম দুটি মোটামুটি স্ব-ব্যাখ্যামূলক, যখন transform
যেকোন নির্বিচারে 4x4 ম্যাট্রিক্স দিয়ে শুরু করা যেতে পারে। যখন একাধিক 3D স্তর একসাথে উপস্থাপন করা প্রয়োজন, তখন আমাদের অবশ্যই একটি বিশেষায়িত CATransformLayer নিয়োগ করতে হবে, যা একটি 2D সমতলে চ্যাপ্টা করার পরিবর্তে এর শিশু স্তরগুলির 3D স্থানকে অনন্যভাবে সংরক্ষণ করে।
চলুন শুরু করা যাক একটি সাধারণ কিউব আঁকার মাধ্যমে। প্রথমত, আমরা প্রতিটি পাশের অবস্থান সামঞ্জস্য করতে একটি সহায়ক ফাংশন তৈরি করব:
func setupFace( layer: CALayer, size: CGFloat, baseTransform: CATransform3D, translation: (x: CGFloat, y: CGFloat, z: CGFloat), rotation: (angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat) ) { layer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size, height: size)) var transform = baseTransform transform = CATransform3DTranslate(transform, translation.x, translation.y, translation.z) transform = CATransform3DRotate(transform, rotation.angle, rotation.x, rotation.y, rotation.z) layer.transform = transform }
এরপরে, আমাদের ViewController এর viewDidLoad
ফাংশনের বডিতে, আমরা কিউবের ছয়টি দিক একত্র করব:
let cubeLayer = CATransformLayer() cubeLayer.position = CGPoint(x: view.bounds.midX, y: view.bounds.midY) view.layer.addSublayer(cubeLayer) let cubeSize: CGFloat = 200.0 var baseTransform = CATransform3DIdentity baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) baseTransform = CATransform3DRotate(baseTransform, -0.5, 1.0, 0.0, 0.0) let frontFace = CALayer() frontFace.isDoubleSided = false frontFace.backgroundColor = UIColor.blue.cgColor setupFace(layer: frontFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, 0.0, cubeSize * 0.5), rotation: (0.0, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(frontFace) let backFace = CALayer() backFace.isDoubleSided = false backFace.backgroundColor = UIColor.red.cgColor setupFace(layer: backFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, 0.0, -cubeSize * 0.5), rotation: (-.pi, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(backFace) let leftFace = CALayer() leftFace.isDoubleSided = false leftFace.backgroundColor = UIColor.green.cgColor setupFace(layer: leftFace, size: cubeSize, baseTransform: baseTransform, translation: (-cubeSize * 0.5, 0.0, 0.0), rotation: (-.pi * 0.5, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(leftFace) let rightFace = CALayer() rightFace.isDoubleSided = false rightFace.backgroundColor = UIColor.yellow.cgColor setupFace(layer: rightFace, size: cubeSize, baseTransform: baseTransform, translation: (cubeSize * 0.5, 0.0, 0.0), rotation: (.pi * 0.5, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(rightFace) let topFace = CALayer() topFace.isDoubleSided = false topFace.backgroundColor = UIColor.cyan.cgColor setupFace(layer: topFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, -cubeSize * 0.5, 0.0), rotation: (.pi * 0.5, 1.0, 0.0, 0.0)) cubeLayer.addSublayer(topFace) let bottomFace = CALayer() bottomFace.isDoubleSided = false bottomFace.backgroundColor = UIColor.gray.cgColor setupFace(layer: bottomFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, cubeSize * 0.5, 0.0), rotation: (-.pi * 0.5, 1.0, 0.0, 0.0)) cubeLayer.addSublayer(bottomFace)
এই কোডটি কর্মে কেমন দেখায় তা এখানে:
এটা নিঃসন্দেহে 3D, কিন্তু কিছু একটা খারাপ লাগছে, তাই না? শিল্পে 3D দৃষ্টিভঙ্গির ধারণাটি 15 শতকে ইতালীয় রেনেসাঁ চিত্রশিল্পীরা প্রথম আয়ত্ত করেছিলেন। সৌভাগ্যবশত, আমরা শুধু একটি পরিপ্রেক্ষিত অভিক্ষেপ ম্যাট্রিক্স ব্যবহার করে অনুরূপ প্রভাব অর্জন করতে পারি:
var baseTransform = CATransform3DIdentity baseTransform.m34 = -1.0 / 400.0 baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) baseTransform = CATransform3DRotate(baseTransform, -0.5, 1.0, 0.0, 0.0)
ভাল, তাই না? m34-এ -1.0 / 400.0
শব্দটি দৃষ্টিকোণ প্রভাব তৈরি করে৷ প্রকৃত গণিতের জন্য, https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/building-basic-perspective-projection-matrix.html দেখুন
আমাদের লক্ষ্য একটি মিরর প্রভাব প্রদর্শন করা, তাই আমাদের প্রতিফলিত করার জন্য কিছু প্রয়োজন হবে। 3D গ্রাফিক্সে, ঘনক্ষেত্র মানচিত্রগুলি সাধারণত প্রতিফলিত পৃষ্ঠের অনুকরণ করতে ব্যবহৃত হয়। আমাদের উদাহরণে, আমরা আগে তৈরি করা প্রকৃত ঘনক্ষেত্র ব্যবহার করে একটি তৈরি করতে পারি। প্রথমত, আমরা সংশ্লিষ্ট মুখগুলিতে ছবি বরাদ্দ করি:
frontFace.contents = UIImage(named: "front")?.cgImage backFace.contents = UIImage(named: "back")?.cgImage leftFace.contents = UIImage(named: "left")?.cgImage rightFace.contents = UIImage(named: "right")?.cgImage topFace.contents = UIImage(named: "up")?.cgImage bottomFace.contents = UIImage(named: "down")?.cgImage
এর পরে, প্রতিটি মুখের জন্য, আমরা isDoubleSided = true
সেট করি এবং কিউবের আকারকে cubeSize: CGFloat = 2000.0
। এটি মূলত কিউবের ভিতরে "ক্যামেরা" রাখে:
এর পরে, যেহেতু আমরা একসাথে বেশ কয়েকটি কিউব তৈরি করতে যাচ্ছি, আসুন সেটআপ ফাংশনগুলিকে সহজ করা যাক:
enum CubeFace: CaseIterable { case front case back case left case right case top case bottom func translationAndRotation(size: CGFloat) -> (translation: (x: CGFloat, y: CGFloat, z: CGFloat), rotation: (angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat)) { switch self { case .front: return ((0.0, 0.0, size * 0.5), (0.0, 0.0, 1.0, 0.0)) case .back: return ((0.0, 0.0, -size * 0.5), (-.pi, 0.0, 1.0, 0.0)) case .left: return ((-size * 0.5, 0.0, 0.0), (-.pi * 0.5, 0.0, 1.0, 0.0)) case .right: return ((size * 0.5, 0.0, 0.0), (.pi * 0.5, 0.0, 1.0, 0.0)) case .top: return ((0.0, -size * 0.5, 0.0), (.pi * 0.5, 1.0, 0.0, 0.0)) case .bottom: return ((0.0, size * 0.5, 0.0), (-.pi * 0.5, 1.0, 0.0, 0.0)) } } func texture() -> UIImage? { ... } func color() -> UIColor { ... } } func setupFace( layer: CALayer, size: CGFloat, baseTransform: CATransform3D, face: CubeFace, textured: Bool ) { layer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size, height: size)) layer.isDoubleSided = textured let (translation, rotation) = face.translationAndRotation(size: size) var transform = baseTransform transform = CATransform3DTranslate(transform, translation.x, translation.y, translation.z) transform = CATransform3DRotate(transform, rotation.angle, rotation.x, rotation.y, rotation.z) layer.transform = transform if textured { layer.contents = face.texture()?.cgImage } else { layer.backgroundColor = face.color().cgColor } } func setupCube( view: UIView, size: CGFloat, textured: Bool, baseTransform: CATransform3D, faces: [CubeFace] ) -> CATransformLayer { let cubeLayer = CATransformLayer() cubeLayer.position = CGPoint(x: view.bounds.midX, y: view.bounds.midY) for face in faces { let faceLayer = CALayer() setupFace(layer: faceLayer, size: size, baseTransform: baseTransform, face: face, textured: textured) cubeLayer.addSublayer(faceLayer) } return cubeLayer }
এখন, কিউব ম্যাপ এবং একটি ছোট কিউব একই সাথে রেন্ডার করা যাক:
var baseTransform = CATransform3DIdentity baseTransform.m34 = -1.0 / 400.0 baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) view.layer.addSublayer(setupCube(view: view, size: 2000.0, textured: true, baseTransform: baseTransform)) view.layer.addSublayer(setupCube(view: view, size: 100.0, textured: false, baseTransform: baseTransform))
UIKit একটি শক্তিশালী ফ্রেমওয়ার্ক, তবুও জটিল ভিজ্যুয়াল এফেক্টের জন্য এতে অন্তর্নির্মিত বৈশিষ্ট্যের অভাব রয়েছে। যাইহোক, এটি বস্তুতে নির্বিচারে মুখোশ প্রয়োগ করার ক্ষমতা অফার করে এবং এটিই আমরা মিরর প্রভাব তৈরি করতে শোষণ করতে যাচ্ছি। মূলত, আমরা পরিবেশকে ছয়বার রেন্ডার করব, প্রতিটি সংশ্লিষ্ট ঘনক মুখ দ্বারা মুখোশযুক্ত।
জটিল দিকটি হল যে আমরা সরাসরি একটি CATransformLayer
মাস্ক করতে পারি না। যাইহোক, আমরা একটি CALayer
কন্টেইনারের ভিতরে বাসা বাঁধার মাধ্যমে এই সীমাবদ্ধতাটি অতিক্রম করতে পারি:
func setupReflectiveFace( view: UIView, size: CGFloat, baseTransform: CATransform3D, face: CubeFace ) -> CALayer { let maskLayer = CALayer() maskLayer.frame = view.bounds maskLayer.addSublayer(setupCube(view: view, size: size, textured: false, baseTransform: baseTransform, faces: [face])) let colorLayer = CALayer() colorLayer.frame = view.bounds colorLayer.mask = maskLayer colorLayer.addSublayer(setupCube(view: view, size: 2000.0, textured: true, baseTransform: baseTransform, faces: [.front, .back, .left, .right, .top, .bottom])) return colorLayer }
এবং এখন, আমাদের ভিউডিডলোড এইরকম হওয়া উচিত:
var baseTransform = CATransform3DIdentity baseTransform.m34 = -1.0 / 400.0 baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) for face in CubeFace.allCases { view.layer.addSublayer(setupReflectiveFace(view: view, size: 100.0, baseTransform: baseTransform, face: face)) }
এই চিত্রটি ইতিমধ্যেই ঘনিষ্ঠভাবে সাদৃশ্যপূর্ণ যা আমরা অর্জন করতে চেয়েছিলাম, কিন্তু এই মুহুর্তে, ঘনক্ষেত্রটি কেবলমাত্র কিউব মানচিত্রের উপর একটি 3D-এসক মাস্ক। সুতরাং, কিভাবে আমরা এটি একটি প্রকৃত আয়নায় রূপান্তর করব?
এটি দেখা যাচ্ছে যে 3D স্পেসে একটি নির্বিচারে সমতলের তুলনায় বিশ্বকে মিরর করার একটি সহজ পদ্ধতি রয়েছে। জটিল গণিতের মধ্যে না পড়ে, এই ম্যাট্রিক্সটি আমরা খুঁজছি:
func mirrorMatrix(planePoint: Vector4D, planeTransform: CATransform3D, planeNormal: Vector4D) -> CATransform3D { let pt = applyTransform(transform: planeTransform, point: planePoint) let normalTransform = CATransform3DInvert(planeTransform).transposed let normal = applyTransform(transform: normalTransform, point: planeNormal).normalized() let a = normal.x let b = normal.y let c = normal.z let d = -(a * pt.x + b * pt.y + c * pt.z) return CATransform3D([ 1 - 2 * a * a, -2 * a * b, -2 * a * c, -2 * a * d, -2 * a * b, 1 - 2 * b * b, -2 * b * c, -2 * b * d, -2 * a * c, -2 * b * c, 1 - 2 * c * c, -2 * c * d, 0.0, 0.0, 0.0, 1.0 ]).transposed }
এর পরে, আমরা কিউব সেটআপ ফাংশনে নিম্নলিখিত কোডটি অন্তর্ভুক্ত করি:
func setupCube( view: UIView, size: CGFloat, textured: Bool, baseTransform: CATransform3D, faces: [CubeFace], mirrorFace: CubeFace? = nil ) -> CATransformLayer { ... if let mirrorFace { let mirrorPlane = mirrorFace.transform(size: size, baseTransform: baseTransform) let mirror = mirrorMatrix(planePoint: Vector4D(x: 0.0, y: 0.0, z: 0.0, w: 1.0), planeTransform: mirrorPlane, planeNormal: Vector4D(x: 0.0, y: 0.0, z: 1.0, w: 1.0)) cubeLayer.sublayerTransform = mirror } }
এবং অবশেষে, আমরা যে চকচকে কিউবটির জন্য চেষ্টা করছি তা দেখতে পাচ্ছি:
অবশ্যই, একই প্রভাব অর্জন করা মেটাল বা SceneKit-এর মতো মেটাল-ভিত্তিক কাঠামোর সাথে সহজ বলে মনে হতে পারে। কিন্তু এগুলি তাদের নিজস্ব সীমাবদ্ধতার সাথে আসে। বড় এক? আপনি মেটাল দ্বারা আঁকা 3D সামগ্রীতে লাইভ UIKit ভিউ আনতে পারবেন না।
এই নিবন্ধে আমরা যে পদ্ধতিটি দেখেছি তা আমাদেরকে একটি 3D সেটিংয়ে সমস্ত ধরণের সামগ্রী প্রদর্শন করতে দেয়৷ এর মধ্যে রয়েছে মানচিত্র, ভিডিও এবং ইন্টারেক্টিভ ভিউ। এছাড়াও, আপনি ব্যবহার করতে চান এমন যেকোনো UIKit অ্যানিমেশনের সাথে এটি মসৃণভাবে মিশে যেতে পারে।
এই নিবন্ধটির উত্স কোড, কিছু সহায়ক ফাংশন সহ, https://github.com/petertechstories/uikit-mirrors এ পাওয়া যাবে
শুভ কোডিং!