Android OpenGL顯示任意3D模型文件

轉(zhuǎn)載請注明出處:【huachao1001的簡書:http://www.reibang.com/users/0a7e42698e4b/latest_articles】

前面兩篇文章我們介紹了OpenGL相關(guān)的基本知識设拟,現(xiàn)在我們已經(jīng)會繪制基本的圖案了纳胧,但是還遠(yuǎn)遠(yuǎn)不能滿足我們的需求。我們要做的是顯示任意的模型万皿,這也是本文所要做的事情相寇。在閱讀本文之前钮科,請先確保你已經(jīng)看過我前面兩篇文章:

雖然標(biāo)題是說顯示任意3D文件绵脯,但是本文主要是以STL格式文件為例蛆挫。其他的格式本質(zhì)上都是一樣的妙黍,只是解析部分的代碼不同而已。接下來我們開始學(xué)習(xí)~

1 STL文件

它是標(biāo)準(zhǔn)的3D文件格式可免,一般3D打印機(jī)都是支持打印STL文件浇借,關(guān)于STL文件的格式怕品、以及相關(guān)介紹請參考百度百科:【stl格式】肉康。當(dāng)然了,我在代碼的注釋中也會進(jìn)行相關(guān)解釋涨薪。

1.1 解析準(zhǔn)備

首先纹安,在解析STL文件格式之前,我們需要進(jìn)行構(gòu)思光督。我們無非就是把STL文件中的三角形的頂點(diǎn)信息提取出來。因此我們的主要目標(biāo)就是把所有點(diǎn)信息讀取出來筐摘。

但是船老,3D模型的坐標(biāo)位置很隨機(jī)柳畔,大小也隨機(jī)。而不同的模型所處的位置不同确沸,為了能夠讓模型處于手機(jī)顯示中心罗捎,我們必須對模型進(jìn)行移動拉盾、放縮處理。使得任意大小倒得、任意位置的模型都能在我們的GLSurfaceView中以“相同”的大小顯示屎暇。

因此驻粟,我們不僅僅要讀取頂點(diǎn)信息,而且還要獲取模型的邊界信息蜀撑。我們想象成一個(gè)立方體,這個(gè)立方體剛好包裹住模型矿卑。即我們要讀取x母廷、y、z三個(gè)方向上的最大值最小值琴昆。

1.2 開始解析

首先业舍,我們定義一個(gè)Model類,用于表示一個(gè)模型對象:

package com.hc.opengl;

import java.nio.FloatBuffer;

/**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/7/28.
 */
public class Model {
    //三角面?zhèn)€數(shù)
    private int facetCount;
    //頂點(diǎn)坐標(biāo)數(shù)組
    private float[] verts;
    //每個(gè)頂點(diǎn)對應(yīng)的法向量數(shù)組
    private float[] vnorms;
    //每個(gè)三角面的屬性信息
    private short[] remarks;

    //頂點(diǎn)數(shù)組轉(zhuǎn)換而來的Buffer
    private FloatBuffer vertBuffer;
    
    //每個(gè)頂點(diǎn)對應(yīng)的法向量轉(zhuǎn)換而來的Buffer
    private FloatBuffer vnormBuffer;
    //以下分別保存所有點(diǎn)在x,y,z方向上的最大值态罪、最小值
    float maxX;
    float minX;
    float maxY;
    float minY;
    float maxZ;
    float minZ;

    //返回模型的中心點(diǎn)
    public Point getCentrePoint() {
        float cx = minX + (maxX - minX) / 2;
        float cy = minY + (maxY - minY) / 2;
        float cz = minZ + (maxZ - minZ) / 2;
        return new Point(cx, cy, cz);
    }

    //包裹模型的最大半徑
    public float getR() {
        float dx = (maxX - minX);
        float dy = (maxY - minY);
        float dz = (maxZ - minZ);
        float max = dx;
        if (dy > max)
            max = dy;
        if (dz > max)
            max = dz;
        return max;
    }
    
    //設(shè)置頂點(diǎn)數(shù)組的同時(shí)复颈,設(shè)置對應(yīng)的Buffer
    public void setVerts(float[] verts) {
        this.verts = verts;
        vertBuffer = Util.floatToBuffer(verts);
    }

    //設(shè)置頂點(diǎn)數(shù)組法向量的同時(shí)券膀,設(shè)置對應(yīng)的Buffer
    public void setVnorms(float[] vnorms) {
        this.vnorms = vnorms;
        vnormBuffer = Util.floatToBuffer(vnorms);
    }

   //···
   //其他屬性對應(yīng)的setter驯遇、getter函數(shù)
   //···

}

接下來就是將stl文件轉(zhuǎn)換成Model對象叉庐,我們定義一個(gè)STLReader類:

package com.hc.opengl;

import android.content.Context;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/7/28.
 */
public class STLReader {
    private StlLoadListener stlLoadListener;

    public Model parserBinStlInSDCard(String path)
                             throws IOException {
                             
        File file = new File(path);
        FileInputStream fis = new FileInputStream(file);
        return parserBinStl(fis);
    }

    public Model parserBinStlInAssets(Context context, String fileName) 
                            throws IOException {
                            
        InputStream is = context.getAssets().open(fileName);
        return parserBinStl(is);
    }
    //解析二進(jìn)制的Stl文件
    public Model parserBinStl(InputStream in) throws IOException {
        if (stlLoadListener != null)
            stlLoadListener.onstart();
        Model model = new Model();
        //前面80字節(jié)是文件頭陡叠,用于存貯文件名枉阵;
        in.skip(80);

        //緊接著用 4 個(gè)字節(jié)的整數(shù)來描述模型的三角面片個(gè)數(shù)
        byte[] bytes = new byte[4];
        in.read(bytes);// 讀取三角面片個(gè)數(shù)
        int facetCount = Util.byte4ToInt(bytes, 0);
        model.setFacetCount(facetCount);
        if (facetCount == 0) {
            in.close();
            return model;
        }

        // 每個(gè)三角面片占用固定的50個(gè)字節(jié)
        byte[] facetBytes = new byte[50 * facetCount];
        // 將所有的三角面片讀取到字節(jié)數(shù)組
        in.read(facetBytes);
        //數(shù)據(jù)讀取完畢后兴溜,可以把輸入流關(guān)閉
        in.close();


        parseModel(model, facetBytes);


        if (stlLoadListener != null)
            stlLoadListener.onFinished();
        return model;
    }

    /**
     * 解析模型數(shù)據(jù)拙徽,包括頂點(diǎn)數(shù)據(jù)诗宣、法向量數(shù)據(jù)、所占空間范圍等
     */
    private void parseModel(Model model, byte[] facetBytes) {
        int facetCount = model.getFacetCount();
        /**
         *  每個(gè)三角面片占用固定的50個(gè)字節(jié),50字節(jié)當(dāng)中:
         *  三角片的法向量:(1個(gè)向量相當(dāng)于一個(gè)點(diǎn))*(3維/點(diǎn))*(4字節(jié)浮點(diǎn)數(shù)/維)=12字節(jié)
         *  三角片的三個(gè)點(diǎn)坐標(biāo):(3個(gè)點(diǎn))*(3維/點(diǎn))*(4字節(jié)浮點(diǎn)數(shù)/維)=36字節(jié)
         *  最后2個(gè)字節(jié)用來描述三角面片的屬性信息
         * **/
        // 保存所有頂點(diǎn)坐標(biāo)信息,一個(gè)三角形3個(gè)頂點(diǎn)岛心,一個(gè)頂點(diǎn)3個(gè)坐標(biāo)軸
        float[] verts = new float[facetCount * 3 * 3];
        // 保存所有三角面對應(yīng)的法向量位置忘古,
        // 一個(gè)三角面對應(yīng)一個(gè)法向量,一個(gè)法向量有3個(gè)點(diǎn)
        // 而繪制模型時(shí)存皂,是針對需要每個(gè)頂點(diǎn)對應(yīng)的法向量旦袋,因此存儲長度需要*3
        // 又同一個(gè)三角面的三個(gè)頂點(diǎn)的法向量是相同的,
        // 因此后面寫入法向量數(shù)據(jù)的時(shí)候商乎,只需連續(xù)寫入3個(gè)相同的法向量即可
        float[] vnorms = new float[facetCount * 3 * 3];
        //保存所有三角面的屬性信息
        short[] remarks = new short[facetCount];

        int stlOffset = 0;
        try {
            for (int i = 0; i < facetCount; i++) {
                if (stlLoadListener != null) {
                    stlLoadListener.onLoading(i, facetCount);
                }
                for (int j = 0; j < 4; j++) {
                    float x = Util.byte4ToFloat(facetBytes, stlOffset);
                    float y = Util.byte4ToFloat(facetBytes, stlOffset + 4);
                    float z = Util.byte4ToFloat(facetBytes, stlOffset + 8);
                    stlOffset += 12;

                    if (j == 0) {//法向量 
                        vnorms[i * 9] = x;
                        vnorms[i * 9 + 1] = y;
                        vnorms[i * 9 + 2] = z;
                        vnorms[i * 9 + 3] = x;
                        vnorms[i * 9 + 4] = y;
                        vnorms[i * 9 + 5] = z;
                        vnorms[i * 9 + 6] = x;
                        vnorms[i * 9 + 7] = y;
                        vnorms[i * 9 + 8] = z;
                    } else {//三個(gè)頂點(diǎn)
                        verts[i * 9 + (j - 1) * 3] = x;
                        verts[i * 9 + (j - 1) * 3 + 1] = y;
                        verts[i * 9 + (j - 1) * 3 + 2] = z;

                        //記錄模型中三個(gè)坐標(biāo)軸方向的最大最小值
                        if (i == 0 && j == 1) {
                            model.minX = model.maxX = x;
                            model.minY = model.maxY = y;
                            model.minZ = model.maxZ = z;
                        } else {
                            model.minX = Math.min(model.minX, x);
                            model.minY = Math.min(model.minY, y);
                            model.minZ = Math.min(model.minZ, z);
                            model.maxX = Math.max(model.maxX, x);
                            model.maxY = Math.max(model.maxY, y);
                            model.maxZ = Math.max(model.maxZ, z);
                        }
                    }
                }
                short r = Util.byte2ToShort(facetBytes, stlOffset);
                stlOffset = stlOffset + 2;
                remarks[i] = r;
            }
        } catch (Exception e) {
            if (stlLoadListener != null) {
                stlLoadListener.onFailure(e);
            } else {
                e.printStackTrace();
            }
        }
        //將讀取的數(shù)據(jù)設(shè)置到Model對象中
        model.setVerts(verts);
        model.setVnorms(vnorms);
        model.setRemarks(remarks);

    }

    public static interface StlLoadListener {
        void onstart();

        void onLoading(int cur, int total);

        void onFinished();

        void onFailure(Exception e);
    }
}

注意到,我們需要頻繁的將byte數(shù)組轉(zhuǎn)為short抹凳、float類型赢底,我們直接把這些函數(shù)裝到一個(gè)工具類Util中:

package com.hc.opengl;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

/**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/7/28.
 */
public class Util {

    public static FloatBuffer floatToBuffer(float[] a) {
        //先初始化buffer柏蘑,數(shù)組的長度*4,因?yàn)橐粋€(gè)float占4個(gè)字節(jié)
        ByteBuffer bb = ByteBuffer.allocateDirect(a.length * 4);
        //數(shù)組排序用nativeOrder
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer buffer = bb.asFloatBuffer();
        buffer.put(a);
        buffer.position(0);
        return buffer;
    }

    public static int byte4ToInt(byte[] bytes, int offset) {
        int b3 = bytes[offset + 3] & 0xFF;
        int b2 = bytes[offset + 2] & 0xFF;
        int b1 = bytes[offset + 1] & 0xFF;
        int b0 = bytes[offset + 0] & 0xFF;
        return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
    }

    public static short byte2ToShort(byte[] bytes, int offset) {
        int b1 = bytes[offset + 1] & 0xFF;
        int b0 = bytes[offset + 0] & 0xFF;
        return (short) ((b1 << 8) | b0);
    }

    public static float byte4ToFloat(byte[] bytes, int offset) {

        return Float.intBitsToFloat(byte4ToInt(bytes, offset));
    }

}

為了更好的表示三維坐標(biāo)系下的一個(gè)點(diǎn),我們定義Point類:

/**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/7/28.
 */
public class Point {
    public float x;
    public float y;
    public float z;

    public Point(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;

    }
}

2 編寫Render

上一節(jié)我們只是拿數(shù)據(jù)而已革半,還沒開始繪制,真正的大招現(xiàn)在才開始不傅。因?yàn)槲覀兡繕?biāo)是顯示任意模型访娶,因此觉阅,必須把模型移動到我們的“視野”中秘车,才能看得到(當(dāng)然了叮趴,如果圖形本身就是在我們的視野中眯亦,那就不一定需要這樣的操作了)般码。廢話不多說,直接看源碼:

 /**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/7/28.
 */
public class GLRenderer implements GLSurfaceView.Renderer {

    private Model model;
    private Point mCenterPoint;
    private Point eye = new Point(0, 0, -3);
    private Point up = new Point(0, 1, 0);
    private Point center = new Point(0, 0, 0);
    private float mScalef = 1;
    private float mDegree = 0;

    public GLRenderer(Context context) {
        try {

            model = new STLReader().parserBinStlInAssets(context, "huba.stl");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void rotate(float degree) {
        mDegree = degree;
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        // 清除屏幕和深度緩存
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);


        gl.glLoadIdentity();// 重置當(dāng)前的模型觀察矩陣


        //眼睛對著原點(diǎn)看 
        GLU.gluLookAt(gl, eye.x, eye.y, eye.z, center.x,
                center.y, center.z, up.x, up.y, up.z);

        //為了能有立體感覺宫静,通過改變mDegree值孤里,讓模型不斷旋轉(zhuǎn)
        gl.glRotatef(mDegree, 0, 1, 0);
        
        //將模型放縮到View剛好裝下
        gl.glScalef(mScalef, mScalef, mScalef);
        //把模型移動到原點(diǎn)
        gl.glTranslatef(-mCenterPoint.x, -mCenterPoint.y,
                -mCenterPoint.z);


        //===================begin==============================//

        //允許給每個(gè)頂點(diǎn)設(shè)置法向量
        gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
        // 允許設(shè)置頂點(diǎn)
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        // 允許設(shè)置顏色

        //設(shè)置法向量數(shù)據(jù)源
        gl.glNormalPointer(GL10.GL_FLOAT, 0, model.getVnormBuffer());
        // 設(shè)置三角形頂點(diǎn)數(shù)據(jù)源
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, model.getVertBuffer());

        // 繪制三角形
        gl.glDrawArrays(GL10.GL_TRIANGLES, 0, model.getFacetCount() * 3);

        // 取消頂點(diǎn)設(shè)置
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        //取消法向量設(shè)置
        gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);

        //=====================end============================//

    }


    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {

        // 設(shè)置OpenGL場景的大小,(0,0)表示窗口內(nèi)部視口的左下角捌袜,(width, height)指定了視口的大小
        gl.glViewport(0, 0, width, height);

        gl.glMatrixMode(GL10.GL_PROJECTION); // 設(shè)置投影矩陣
        gl.glLoadIdentity(); // 設(shè)置矩陣為單位矩陣琢蛤,相當(dāng)于重置矩陣
        GLU.gluPerspective(gl, 45.0f, ((float) width) / height, 1f, 100f);// 設(shè)置透視范圍

        //以下兩句聲明抛虏,以后所有的變換都是針對模型(即我們繪制的圖形)
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();


    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        gl.glEnable(GL10.GL_DEPTH_TEST); // 啟用深度緩存
        gl.glClearDepthf(1.0f); // 設(shè)置深度緩存值
        gl.glDepthFunc(GL10.GL_LEQUAL); // 設(shè)置深度緩存比較函數(shù)
        gl.glShadeModel(GL10.GL_SMOOTH);// 設(shè)置陰影模式GL_SMOOTH
        float r = model.getR();
        //r是半徑迂猴,不是直徑沸毁,因此用0.5/r可以算出放縮比例
        mScalef = 0.5f / r;
        mCenterPoint = model.getCentrePoint();
    }
}

在MainActivity中不斷調(diào)用旋轉(zhuǎn)函數(shù):

package com.hc.opengl;

 
public class MainActivity extends AppCompatActivity {

    private boolean supportsEs2;
    private GLSurfaceView glView;
    private float rotateDegreen = 0;
    private GLRenderer glRenderer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        checkSupported();

        if (supportsEs2) {
            glView = new GLSurfaceView(this);
            glRenderer = new GLRenderer(this);
            glView.setRenderer(glRenderer);
            setContentView(glView);
        } else {
            setContentView(R.layout.activity_main);
            Toast.makeText(this, "當(dāng)前設(shè)備不支持OpenGL ES 2.0!", Toast.LENGTH_SHORT).show();
        }
    }

    public void rotate(float degree) {
        glRenderer.rotate(degree);
        glView.invalidate();
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            rotate(rotateDegreen);
        }
    };

    @Override
    protected void onResume() {
        super.onResume();
        if (glView != null) {
            glView.onResume();

            //不斷改變r(jià)otateDegreen值息尺,實(shí)現(xiàn)旋轉(zhuǎn)
            new Thread() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            sleep(100);

                            rotateDegreen += 5;
                            handler.sendEmptyMessage(0x001);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                }
            }.start();
        }


    }

    private void checkSupported() {
        ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
        ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
        supportsEs2 = configurationInfo.reqGlEsVersion >= 0x2000;

        boolean isEmulator = Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
                && (Build.FINGERPRINT.startsWith("generic")
                || Build.FINGERPRINT.startsWith("unknown")
                || Build.MODEL.contains("google_sdk")
                || Build.MODEL.contains("Emulator")
                || Build.MODEL.contains("Android SDK built for x86"));

        supportsEs2 = supportsEs2 || isEmulator;
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (glView != null) {
            glView.onPause();
        }
    }


}

3 最后一步

一切看起來都已經(jīng)完成了搂誉,但似乎少了點(diǎn)什么炭懊。啊哈~,少了STL文件侮腹,其實(shí)網(wǎng)上有很多STL模型文件免費(fèi)下載,大家可以隨便搜索愈涩。我下載了一個(gè)胡巴的模型:

模型截圖
模型截圖

下載完成后履婉,運(yùn)行如下:

運(yùn)行結(jié)果

看到結(jié)果是不是覺得很失望谐鼎?貌似看不到輪廓趣惠,其實(shí),主要是跟燈光有關(guān)草戈,我們程序中沒有設(shè)置燈光侍瑟。我們知道涨颜,我們在真實(shí)世界中看到物體主要是物體表面發(fā)生漫反射。我們所看到的物體跟光源的位置星持、物體的材質(zhì)等等有關(guān)。另外督暂,也可以通過貼紋理來做到逻翁。但是到目前為止捡鱼,我們還沒有這些知識,代碼里面也沒有涉及到這些辽社,因此我們這能看到當(dāng)前這個(gè)樣子。后面我們會繼續(xù)深入學(xué)習(xí)相關(guān)知識戳葵,歡迎關(guān)注~拱烁。

好啦噩翠,最后獻(xiàn)上源碼吧~,注意擅笔,下載的源碼中Model類的getCentrePoint函數(shù)需要修改屯援,請以本文中的Model類為主。

源碼地址http://download.csdn.net/detail/huachao1001/9588619

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弯淘,一起剝皮案震驚了整個(gè)濱河市庐橙,隨后出現(xiàn)的幾起案子借嗽,更是在濱河造成了極大的恐慌,老刑警劉巖浆竭,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甲锡,死亡現(xiàn)場離奇詭異缤沦,居然都是意外死亡易稠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門测萎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來届巩,“玉大人恕汇,你說我怎么就攤上這事≡娼樱” “怎么了缺谴?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵湿蛔,是天一觀的道長。 經(jīng)常有香客問我妓肢,道長苫纤,這世上最難降的妖魔是什么卷拘? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任栗弟,我火速辦了婚禮乍赫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雷厂。我一直安慰自己改鲫,他們只是感情好林束,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布壶冒。 她就那樣靜靜地躺著胖腾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怎披。 梳的紋絲不亂的頭發(fā)上胸嘁,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音凉逛,去河邊找鬼性宏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛状飞,可吹牛的內(nèi)容都是我干的毫胜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诬辈,長吁一口氣:“原來是場噩夢啊……” “哼酵使!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起焙糟,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缺脉,失蹤者是張志新(化名)和其女友劉穎攻礼,沒想到半個(gè)月后礁扮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體太伊,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绣的,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年芭概,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了罢洲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惹苗。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡劳闹,死狀恐怖本涕,靈堂內(nèi)的尸體忽然破棺而出菩颖,到底是詐尸還是另有隱情放祟,我是刑警寧澤舞竿,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站执桌,受9級特大地震影響仰挣,放射性物質(zhì)發(fā)生泄漏错蝴。R本人自食惡果不足惜顷锰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望州藕。 院中可真熱鬧毁涉,春花似錦薪丁、人聲如沸严嗜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汗盘。三九已至,卻和暖如春菱阵,著一層夾襖步出監(jiān)牢的瞬間晴及,已是汗流浹背琳钉。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茸塞。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像效扫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子菌仁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

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