Core Animation 第五章 變換

往期回顧:
序章
第一章 - 圖層樹
第二章 - 寄宿圖
第三章 - 圖層幾何
第四章 - 視覺效果
項目中使用的代碼

這一章我們主要來研究一下可以用來對圖層進行旋轉(zhuǎn)胃碾,平移和縮放的CGAffineTransform 已經(jīng)可以將平面圖層轉(zhuǎn)換為3D對象的CATransform3D纷责。

仿射變化


還記得第三章中我們創(chuàng)建的那個時鐘么橡淑,在那里面我們用到了UIViewtransform屬性,下面我們來具體說明一下背后的原理揪阶,transform屬性是一個CGAffineTransform類型昧碉,用于在二維空間做旋轉(zhuǎn)鸥跟,縮放和平移。CGAffineTransform是一個可以和二維空間向量(例如CGPoint)做乘法的3X2的矩陣秋忙。

用矩陣表示CGAffineTransform和CGPoint

通過矩陣計算就可以得到新的CGPoint彩掐,書中提到雖然CGAffineTransform是3x3的矩陣,即 以行為主的格式灰追,但是也會出現(xiàn)三行兩列(注意不能使兩行三列堵幽,那樣的話無法與矩陣相乘)也就是以列為主的格式。只要能保持一致弹澎,哪種格式都是可以的朴下。
四邊形中的每一個點都會做出對應(yīng)的變換,從而得出一個新的四邊形苦蒿。本節(jié)標(biāo)題中仿射的意思是無論矩陣用什么值變換殴胧,變換前后保持平行的對邊依然保持平行。

創(chuàng)建CGAffineTransform

Core Graphics提供了一系列函數(shù)佩迟,方便我們即使對矩陣不是很了解也可以輕松地創(chuàng)建變換矩陣团滥,如下幾個函數(shù)都創(chuàng)建了一個CGAffineTransform實例:

CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)

上面三個方法分別進行了旋轉(zhuǎn),縮放和平移的變換报强。下面我們來做一個簡單的例子灸姊,把一個視圖旋轉(zhuǎn)45°。

- (IBAction)rotationClick:(id)sender {
    CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);
    self.layerView.layer.affineTransform = transform;
}

這里使用了CALayer的屬性affineTransform秉溉,在UIView中對應(yīng)的屬性為transform力惯。順便再說一下,UIView中與顯示相關(guān)的屬性都是在對CALayer中的屬性做存取召嘶,UIView本身不處理顯示渲染夯膀。

M_PI_4 表示四分之一πiOS中變換使用的是弧度而不是度數(shù)苍蔬,所以我們在做旋轉(zhuǎn)的時候應(yīng)該傳入弧度而不是度數(shù)诱建,使用下面的宏方便你獲取各種角度對應(yīng)的弧度:

#define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0)
仿射變換旋轉(zhuǎn)45°后的視圖

混合變換

Core Graphics提供了一系列的函數(shù)可以在一個變換的基礎(chǔ)上做更深層次的變換,比如同時進行縮放和旋轉(zhuǎn)等:

CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)

對應(yīng)的除了進行變換碟绑,我們也需要一個空值俺猿,原始狀態(tài)茎匠,不進行任何變化呢,Core Graphics提供了一個常量

CGAffineTransformIdentity

最后押袍,混合兩個已經(jīng)存在的變換矩陣使用的方法為:

CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);

接下來我們來做一個混合變化诵冒,先縮小50%,再旋轉(zhuǎn)30°谊惭,最后再向右平移200:

- (IBAction)complexChangeClick:(id)sender {
    //創(chuàng)建一個transform
    CGAffineTransform transform = CGAffineTransformIdentity;
    //縮小50%
    transform = CGAffineTransformScale(transform, 0.5, 0.5);
    //旋轉(zhuǎn)30°
    transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0);
    //平移
    transform = CGAffineTransformTranslate(transform, 200, 0);
    
    self.layerView.layer.affineTransform = transform;
}
混合變換后的視圖

你可能會發(fā)現(xiàn)我們的視圖并沒有水平的向下平移200的長度汽馋,而且還有點向下偏,那是因為在視圖旋轉(zhuǎn)的同時圈盔,我們平移的方向也被旋轉(zhuǎn)了30°豹芯,也就是說先旋轉(zhuǎn)再平移跟先平移再旋轉(zhuǎn)的結(jié)果是不同,這也跟矩陣相乘順序的特性保持一致驱敲。

3D變換


因為CGAffineTransform屬于Core Graphics框架铁蹈,所以這是一個嚴格意義上的2D變換的框架,我們在第一章也提到過圖層的一大特性是可以進行3D變換众眨,也提到過zPosition握牧,在這里我們要用得到屬性就是CATransform3D

對一個3D像素點做CATransform3D矩陣變化

CGAffineTransform矩陣類似娩梨,Core Animation提供了一系列的方法用來創(chuàng)建和組合CATransform3D類型的矩陣沿腰,和Core Graphics的函數(shù)類似,但是3D的平移和旋轉(zhuǎn)多處了一個z參數(shù)狈定,并且旋轉(zhuǎn)函數(shù)除了angle之外多出了x,y,z三個參數(shù)矫俺,分別決定了每個坐標(biāo)軸方向上的旋轉(zhuǎn):

CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz) 
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)

如果你對圍繞某個軸旋轉(zhuǎn)感到陌生,那么你可以通過下圖來了解一下:

X, Y, Z 軸掸冤,已經(jīng)圍繞他們旋轉(zhuǎn)的方向

可以看出來2D仿射變換中的旋轉(zhuǎn)實際就是在圍繞z軸進行旋轉(zhuǎn)±逋校現(xiàn)在我們把視圖繞Y軸旋轉(zhuǎn)45°來看一下效果:

- (IBAction)YRotationClick:(id)sender {
    CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
    self.layerView.layer.transform = transform;
    
}
繞Y軸旋轉(zhuǎn)45°的視圖

看起來好像我們的視圖只是被壓縮了一點,這是因為沒有透視效果的原因稿湿。

透視投影

雖然Core Animation幫我們計算了變化矩陣铅匹,但我們依然可以自己修改矩陣來實現(xiàn)自己想要的效果,比如修改CATransform的元素m34饺藤。m34元素用于按比例縮小XY的值來計算離視角有多遠包斑。

使用m34元素來做透視

m34的默認值是0,我們可以通過設(shè)置m34為-1.0 / d來應(yīng)用透視效果涕俗,d代表了想象中視角相機和屏幕之間的距離罗丰,具體的值可以自己把握。

- (IBAction)prespectiveRotation:(id)sender {
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = -1.0 / 500.0;
    transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0);
    self.layerView.layer.transform = transform;
    
}
具有透視效果的旋轉(zhuǎn)變換

滅點

當(dāng)在透視角度繪圖的時候再姑,遠離相機視角的物體將會變小變遠萌抵,當(dāng)遠離到一個極限距離,它們可能就縮成了一個點,于是所有的物體最后都匯聚消失在同一個點绍填。
在現(xiàn)實中霎桅,這個點通常是視圖的中心,于是為了在應(yīng)用中創(chuàng)建擬真效果的透視讨永,這個點應(yīng)該聚在屏幕中點滔驶,或者至少是包含所有3D對象的視圖中點。

滅點

當(dāng)改變一個圖層的position卿闹,你也改變了它的滅點揭糕,做3D變換的時候要時刻記住這一點,當(dāng)你視圖通過調(diào)整m34來讓它更加有3D效果锻霎,應(yīng)該首先把它放置于屏幕中央著角,然后通過平移來把它移動到指定位置(而不是直接改變它的position),這樣所有的3D圖層都共享一個滅點量窘。

sublayerTransform屬性

上文提到了滅點的概念也就是說多個圖層的m34的值應(yīng)該保持一致。CALayer有一個叫做sublayerTransform屬性氢拥,它也是CATransform3D類型蚌铜,而且它能夠影響到所有的自圖層。

- (IBAction)vanishClick:(id)sender {
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    self.containerView.layer.sublayerTransform = perspective;
    CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
    self.layerView.layer.transform = transform1;
    CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
    self.layerView2.layer.transform = transform2;
}
設(shè)置了父視圖的m34后進行變換

背面

我們既然可以在3D場景下旋轉(zhuǎn)圖層嫩海,那么也可以從背面去觀察它冬殃。如果我們在把角度修改為M_PI(180°)而不是當(dāng)前的M_PI_4(45°),那么將會把圖層完全旋轉(zhuǎn)一個半圈叁怪,于是完全背對了相機視角审葬。

正面與背面

如你所見,圖層是雙面繪制的奕谭,反面顯示的是正面的一個鏡像圖片涣觉。
但這并不是一個很好的特性,因為如果圖層包含文本或者其他控件血柳,那用戶看到這些內(nèi)容的鏡像圖片當(dāng)然會感到困惑官册。另外也有可能造成資源的浪費:想象用這些圖層形成一個不透明的固態(tài)立方體,既然永遠都看不見這些圖層的背面难捌,那為什么浪費GPU來繪制它們呢膝宁?
CALayer有一個叫做doubleSided的屬性來控制圖層的背面是否要被繪制。這是一個BOOL類型根吁,默認為YES员淫,如果設(shè)置為NO,那么當(dāng)圖層正面從相機視角消失的時候击敌,它將不會被繪制介返。

固體對象


上面說了那么多接下來我們來嘗試創(chuàng)建一個立方體。

#define kSize 200
#define kFontSize 60

@interface CubeViewController ()
@property (strong, nonatomic) NSMutableArray <UIView *> *faces;
@end

@implementation CubeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.faces = [NSMutableArray new];
    
    //set up the container sublayer transform
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    self.view.layer.sublayerTransform = perspective;
    
    //add cube face 1
    CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);
    [self addFace:0 withTransform:transform];
    //add cube face 2
    transform = CATransform3DMakeTranslation(100, 0, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
    [self addFace:1 withTransform:transform];
    //add cube face 3
    transform = CATransform3DMakeTranslation(0, -100, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
    [self addFace:2 withTransform:transform];
    //add cube face 4
    transform = CATransform3DMakeTranslation(0, 100, 0);
    transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);
    [self addFace:3 withTransform:transform];
    //add cube face 5
    transform = CATransform3DMakeTranslation(-100, 0, 0);
    transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
    [self addFace:4 withTransform:transform];
    //add cube face 6
    transform = CATransform3DMakeTranslation(0, 0, -100);
    transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);
    [self addFace:5 withTransform:transform];
}

- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform {
    UIView *face = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kSize, kSize)];
    face.backgroundColor = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f];
    face.center = CGPointMake([UIScreen mainScreen].bounds.size.width / 2, [UIScreen mainScreen].bounds.size.height / 2);
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
    button.titleLabel.textAlignment = NSTextAlignmentCenter;
    button.titleLabel.font = [UIFont systemFontOfSize:kFontSize weight:10];
    [button setTitleColor:[UIColor colorWithRed:arc4random_uniform(255) / 255.0 green:arc4random_uniform(255) / 255.0 blue:arc4random_uniform(255) / 255.0 alpha:1] forState:UIControlStateNormal];
    [button setTitle:@(index + 1).stringValue forState:UIControlStateNormal];
    button.layer.cornerRadius = 5.0;
    button.layer.borderWidth = 1.0f;
    button.layer.borderColor = [UIColor lightGrayColor].CGColor;
    [button addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
    [face addSubview:button];
    [self.view addSubview:face];
    [self.faces addObject:face];
    face.layer.transform = transform;
}
- (void)btnClick:(UIButton *)sender {
    
}

@end
正面朝上的立方體

這個角度我們看到的是一個方形,接下來我們來換一個角度映皆,在viewDidLoad中添加如下代碼

perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);
perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);
換一個角度

光亮和陰影

Core Animation可以顯示3D的圖層挤聘,但是它本身并沒有光線的概念,我們這里可以使用GLKit來計算陰影和光線:

#define kSize 200
#define kFontSize 60
#define LIGHT_DIRECTION 0, 1, -0.5
#define AMBIENT_LIGHT 0.5

@interface CubeViewController ()
@property (strong, nonatomic) NSMutableArray <UIView *> *faces;
@end

@implementation CubeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.faces = [NSMutableArray new];
    
    //set up the container sublayer transform
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);
    perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);
    self.view.layer.sublayerTransform = perspective;
    
    //add cube face 1
    CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);
    [self addFace:0 withTransform:transform];
    //add cube face 2
    transform = CATransform3DMakeTranslation(100, 0, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
    [self addFace:1 withTransform:transform];
    //add cube face 3
    transform = CATransform3DMakeTranslation(0, -100, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
    [self addFace:2 withTransform:transform];
    //add cube face 4
    transform = CATransform3DMakeTranslation(0, 100, 0);
    transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);
    [self addFace:3 withTransform:transform];
    //add cube face 5
    transform = CATransform3DMakeTranslation(-100, 0, 0);
    transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
    [self addFace:4 withTransform:transform];
    //add cube face 6
    transform = CATransform3DMakeTranslation(0, 0, -100);
    transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);
    [self addFace:5 withTransform:transform];
}

- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform {
    UIView *face = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kSize, kSize)];
    face.backgroundColor = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f];
    face.center = CGPointMake([UIScreen mainScreen].bounds.size.width / 2, [UIScreen mainScreen].bounds.size.height / 2);
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
    button.titleLabel.textAlignment = NSTextAlignmentCenter;
    button.titleLabel.font = [UIFont systemFontOfSize:kFontSize weight:10];
    [button setTitleColor:[UIColor colorWithRed:arc4random_uniform(255) / 255.0 green:arc4random_uniform(255) / 255.0 blue:arc4random_uniform(255) / 255.0 alpha:1] forState:UIControlStateNormal];
    [button setTitle:@(index + 1).stringValue forState:UIControlStateNormal];
    button.layer.cornerRadius = 5.0;
    button.layer.borderWidth = 1.0f;
    button.layer.borderColor = [UIColor lightGrayColor].CGColor;
    [button addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
    [face addSubview:button];
    [self.view addSubview:face];
    [self.faces addObject:face];
    face.layer.transform = transform;
    [self applyLightingToFace:face.layer];
}

- (void)applyLightingToFace:(CALayer *)face {
    //添加光線圖層
    CALayer *layer = [CALayer layer];
    layer.frame = face.bounds;
    [face addSublayer:layer];
    //轉(zhuǎn)換transform到矩陣
    //GLKMatrix4和CATransform3D內(nèi)存結(jié)構(gòu)一致捅彻,但坐標(biāo)類型有長度區(qū)別组去,所以理論上應(yīng)該做一次float到CGFloat的轉(zhuǎn)換
    CATransform3D transform = face.transform;
    GLKMatrix4 matrix4 = [self matrixFrom3DTransformation:transform];
    GLKMatrix3 matrix3 = GLKMatrix4GetMatrix3(matrix4);
    //get face normal
    GLKVector3 normal = GLKVector3Make(0, 0, 1);
    normal = GLKMatrix3MultiplyVector3(matrix3, normal);
    normal = GLKVector3Normalize(normal);
    //get dot product with light direction
    GLKVector3 light = GLKVector3Normalize(GLKVector3Make(LIGHT_DIRECTION));
    float dotProduct = GLKVector3DotProduct(light, normal);
    //set lighting layer opacity
    CGFloat shadow = 1 + dotProduct - AMBIENT_LIGHT;
    UIColor *color = [UIColor colorWithWhite:0 alpha:shadow];
    layer.backgroundColor = color.CGColor;

}

- (GLKMatrix4)matrixFrom3DTransformation:(CATransform3D)transform {
    GLKMatrix4 matrix = GLKMatrix4Make(transform.m11, transform.m12, transform.m13, transform.m14,
                                       transform.m21, transform.m22, transform.m23, transform.m24,
                                       transform.m31, transform.m32, transform.m33, transform.m34,
                                       transform.m41, transform.m42, transform.m43, transform.m44);
    return matrix;
}

- (void)btnClick:(UIButton *)sender {
    
}

@end
動態(tài)計算光影效果后的立方體

點擊事件

你可能會發(fā)現(xiàn)我們明明已經(jīng)給Button綁定了點擊事件,但是卻沒有觸發(fā)步淹,實際上是因為4从隆,5,6這三個圖層的位置位于1缭裆,2键闺,3上面。這里我們設(shè)定為除了3以外的face的userInteractionEnabled均為NO澈驼。

響應(yīng)點擊事件

總結(jié)


這一章主要講述了仿射變化與3D變換辛燥,以及變換背后的原理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缝其,一起剝皮案震驚了整個濱河市挎塌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌内边,老刑警劉巖榴都,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異漠其,居然都是意外死亡嘴高,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門和屎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拴驮,“玉大人,你說我怎么就攤上這事柴信∮ㄌ溃” “怎么了?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵颠印,是天一觀的道長纲岭。 經(jīng)常有香客問我,道長线罕,這世上最難降的妖魔是什么止潮? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮钞楼,結(jié)果婚禮上喇闸,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好燃乍,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布唆樊。 她就那樣靜靜地躺著,像睡著了一般刻蟹。 火紅的嫁衣襯著肌膚如雪逗旁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天,我揣著相機與錄音,去河邊找鬼性芬。 笑死,一個胖子當(dāng)著我的面吹牛赫段,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼膨桥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起唠叛,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤只嚣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后玻墅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體介牙,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡壮虫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年澳厢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囚似。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡剩拢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饶唤,到底是詐尸還是另有隱情徐伐,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布募狂,位于F島的核電站办素,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏祸穷。R本人自食惡果不足惜性穿,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雷滚。 院中可真熱鬧需曾,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谋减,卻和暖如春牡彻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逃顶。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工讨便, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人以政。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓霸褒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盈蛮。 傳聞我的和親對象是個殘疾皇子废菱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容