游戏算法(2):查找优化之四叉树的应用

本文主要介绍四叉树的应用场景和实现。

本文链接  游戏算法(2):查找优化之四叉树的应用

相关文章  游戏算法(1):实现AStar寻路算法

一、四叉树概念

    在二分查找算法中,整个查找对象结构化关联起来,就是一棵二叉树。在线性数据快速查找中,二分查找有着不错的性能。

    同理,我们可以应用到N叉树。    

    那么在平面中,我们则可以利用到四叉树。这是因为平面时二维的,x、y两个轴刚好将平面空间分成四份。因此在平面查找算法中,四叉树被广泛应用,来优化查找的效率。四叉树或四元树也被称为Q树(Q-Tree)。

    那八叉树呢?没错,三维空间中,x、y、z轴将空间划分为8个部分,来优化查找效率。

二、四叉树的应用

    四叉树或四元树也被称为Q树(Q-Tree)。四叉树广泛应用于图像处理、空间数据索引、2D中的快速碰撞检测、存储稀疏数据等

    图像处理:比如像素分象限区间剔除

    空间数据索引:2d场景中的对象查找、地图元素查找。如查找在屏幕范围内的随想集合,则只需先判断屏幕窗口在场景的哪个子树中。

    2D中的快速碰撞检测:碰撞检测中对大量碰撞对象的查找、剔除。如不在同一子树区间的对象集合,不可能碰撞,可直接剔除。

    存储稀疏数据:可以存储MxN的矩阵。M和N的维度和树的高度直接相关。为0的区域,可以做成空节点树。

    八叉树则应用在三维空间货期它场景。

    N叉树同理。

    以上的应用都至少带来了,比直接遍历所有对象数据,更优的效率。

    例如下图的四叉树碰撞应用,A区域的物体和B区域的物体不可能发生碰撞,那么他们之间就不用进行碰撞判断逻辑了。仅需检测每个区域内的物体之间的碰撞。大幅降低碰撞检测压力。

四叉树碰撞应用


三、四叉树的实现

        实现四叉树QuadTree。这里采用x、y轴进行区域划分,节点进行矩形均分,来实现较为标准的分割,对象是否在区域内是以点是否在矩形内的判定为例。

        当然,这些分割标准是可以完全自定义的,不一定要是矩形或者x、y轴分割。甚至是各种形状的图形。

        因为四叉树只需要保证逻辑节点有四个子节点即可。至于节点对象的定义完全由开发者决定,几点是否在某个区域内的判定函数也可以是自定义的。

        1、实现四叉树节点QuadTreeNode

/**

 * 四叉树节点数据结构

 * 四叉树或四元树也被称为Q树(Q-Tree)。

 * 四叉树广泛应用于图像处理、空间数据索引、2D中的快速碰撞检测、存储稀疏数据等

 */

export class QuadTreeNode<T> {

    /** 绑定的四叉树树对象引用 */

    protected _tree: QuadTree<T>;

    /** 节点的高度 */

    protected _height: number;

    /** 节点的深度 */

    protected _depth: number;

    /** 父节点 */

    protected _parentNode: QuadTreeNode<T>;

    /** 邻节点指针 */

    public _nextNode: QuadTreeNode<T>;

    /** 子节点链表 */

    protected _childLinkList: LinkList<QuadTreeNode<T>>;

    /** 对象 */

    protected _data: QuadTreeNodeData<T>;

    /** 矩形区域 */

    protected _rect: Rectangle;

    /** 本节点是否禁用搜索 */

    protected _disableSearch: boolean = false;


    public leftTop: {x: number, y: number} = null;

    public leftBottom: {x: number, y: number} = null;

    public rightBottom: {x: number, y: number} = null;

    public rightTop: {x: number, y: number} = null;

    /** 数据是否为空 */

    protected _isDataEmpty: boolean = true;

    /**

     * 构造函数

     * @param rect 二维空间数据

     * @param depth 节点的深度

     */

    constructor(tree: QuadTree<T>, parentNode: QuadTreeNode<T>, rect: Rectangle, depth: number) {

        this._data = new QuadTreeNodeData<T>();

        this._tree = tree;

        this._parentNode = parentNode;

        this._childLinkList = new LinkList<QuadTreeNode<T>>();

        this._rect = rect;

        this._depth = depth;

        this._height = this._depth;

        this.leftTop = {x: this._rect.x, y: this._rect.y};

        this.leftBottom = {x: this._rect.x, y: this._rect.y + this._rect.height};

        this.rightBottom = {x: this._rect.x + this._rect.width, y: this._rect.y + this._rect.height};

        this.rightTop = {x: this._rect.x + this._rect.width, y: this._rect.y};

        if (depth > 0) {

            this._createChildNode();

        }

    }

    /**

     * 创建子节点

     */

    protected _createChildNode(): void {

        if (this._rect.width > 0 && this._rect.height > 0) {

            let leftTop = new QuadTreeNode<T>(this._tree, this,

                new Rectangle(this._rect.x, this._rect.y, this._rect.width / 2, this._rect.height / 2), this._depth - 1);

            this._childLinkList.insert(leftTop);

            let rightTop = new QuadTreeNode<T>(this._tree, this,

                new Rectangle(this._rect.x + this._rect.width / 2, this._rect.y, this._rect.width / 2, this._rect.height / 2), this._depth - 1);

            this._childLinkList.insert(rightTop);

            leftTop._nextNode = rightTop;

            let rightDown = new QuadTreeNode<T>(this._tree, this,

                new Rectangle(this._rect.x + this._rect.width / 2, this._rect.y + this._rect.height / 2, this._rect.width / 2, this._rect.height / 2), this._depth - 1);

            this._childLinkList.insert(rightDown);

            rightTop._nextNode = rightDown;

            let leftDown = new QuadTreeNode<T>(this._tree, this,

                new Rectangle(this._rect.x, this._rect.y + this._rect.height / 2, this._rect.width / 2, this._rect.height / 2), this._depth - 1);

            this._childLinkList.insert(leftDown);

            rightDown._nextNode = leftDown;

        }

    }

    /**

     * 获取数据

     */

    public get data(): QuadTreeNodeData<T> {

        return this._data;

    }

    /**

     * 获取对象集合

     */

    public get targets(): T[] {

        return this._data.getTargets();

    }

    /**

     * 获取左上区域对象集合

     */

    public get leftTopTargets(): T[] {

        return this._childLinkList.getItemByIndex(0).value.targets;

    }

    /**

     * 获取右上区域对象集合

     */

    public get rightTopTargets(): T[] {

        return this._childLinkList.getItemByIndex(1).value.targets;

    }

    /**

     * 获取右下区域对象集合

     */

    public get rightDownTargets(): T[] {

        return this._childLinkList.getItemByIndex(2).value.targets;

    }

    /**

     * 获取左下区域对象集合

     */

    public get leftDownTargets(): T[] {

        return this._childLinkList.getItemByIndex(3).value.targets;

    }

    /**

     * 获取左边第一个子节点

     */

    public getLeftChild(): QuadTreeNode<T> {

        return this._childLinkList.head();

    }

    /**

     * 获取区域数据

     */

    public get rect(): Rectangle {

        return this._rect;

    }

    /**

     * 获取节点的高度

     */

    public get height(): number {

        return this._height;

    }

    /**

     * 获取节点的深度

     */

    public get depth(): number {

        return this._depth;

    }

    /**

     * 获取节点的邻节点

     */

    public get nextNode(): QuadTreeNode<T> {

        return this._nextNode;

    }

    /**

     * 获取节点的父节点

     */

    public get parentNode(): QuadTreeNode<T> {

        return this._parentNode;

    }

    /**

     * 获取节点数据是否为空

     */

    public get isDataEmpty() {

        return this._isDataEmpty;

    }

    /**

     * 设置节点数据是否为空

     */

    public set isDataEmpty(isEmpty) {

        this._isDataEmpty = isEmpty;

    }

    /**

     * 获取子节点数组

     */

    public getChildsAsArray(): QuadTreeNode<T>[] {

        return this._childLinkList.toArray();

    }

    /**

     * 是否根节点

     */

    public isRoot(): boolean {

        return !this._parentNode;

    }

    /**

     * 是否叶节点

     */

    public isLeaf(): boolean {

        return this._childLinkList.size() <= 0;

    }

    /**

     * 设置开启或关闭搜索

     */

    public set disableSearch(disable: boolean) {

        this._disableSearch = disable;

    }

    /**

     * 获取开启或关闭搜索

     */

    public get disableSearch() {

        return this._disableSearch;

    }

    /**

     * 是否包含某个坐标点

     */

    public isContains(x: number, y: number): boolean {

        return this._rect.contains(x, y);

    }

    /**

     * 移除对象

     */

    public removeTarget(target: T): void {

        this._data.removeTarget(target);

        this.isDataEmpty = true;

        this.updateParentEmptyState();

    }

    /**

     * 增加对象

     */

    public addTarget(target: T): void {

        this._data.addTarget(target);

        this.isDataEmpty = false;

        this.updateParentEmptyState();

    }

    /**

     * 获取rect的四个坐标

     */

    public getVertexPoints() {

        let leftTop: {x: number, y: number} = {x: this._rect.x, y: this._rect.y};

        let leftBottom: {x: number, y: number} = {x: this._rect.x, y: this._rect.y + this._rect.height};

        let rightBottom: {x: number, y: number} = {x: this._rect.x + this._rect.width, y: this._rect.y + this._rect.height};

        let rightTop: {x: number, y: number} = {x: this._rect.x + this._rect.width, y: this._rect.y};

        return {

            leftTop: leftTop,

            leftBottom: leftBottom,

            rightBottom: rightBottom,

            rightTop: rightTop,

            x: this._rect.x,

            y: this._rect.y,

            width: this._rect.width,

            height: this._rect.height,

        };

    }

    /** 更新父节点数据是否为空 */

    public updateParentEmptyState() {

        let parentNode = this.parentNode;

        while (parentNode) {

            if (!this.isDataEmpty) {

                parentNode.isDataEmpty = false;

            }else {

                parentNode.calcEmptyState();

            }

            parentNode = parentNode.parentNode;

        }

    }

    /** 计算空状态 */

    public calcEmptyState() {

        let isEmpty = true;

        this._childLinkList.foreach2((value: QuadTreeNode<T>) => {

            isEmpty = isEmpty && value.isDataEmpty;

            if (!isEmpty) {

                return true;

            }

        });

        this.isDataEmpty = isEmpty;

    }

}

/**

 * 四叉树节点数据对象

 * by longhui

 * (c) copyright 2014 - 2035

 * All Rights Reserved. 

 */

export class QuadTreeNodeData<T> {

    /** 对象集合 */

    protected _targets: T[] = [];

    constructor() {

    }

    /** 获取对象集合 */

    public getTargets(): T[] {

        return this._targets;

    }

    /** 从集合中移除对象 */

    public removeTarget(target: T): boolean {

        let index = this._targets.indexOf(target);

        if (index < 0) {

            return false;

        }

        this._targets.splice(index, 1);

    }

    /** 添加一个对象到集合中 */

    public addTarget(target: T): void {

        this._targets.push(target);

    }

}

2、实现四叉树对象QuadTree

/**

 * 四叉树(基于2D平面空间分割)数据结构

 * 四叉树或四元树也被称为Q树(Q-Tree)。

 * 四叉树广泛应用于图像处理、空间数据索引、2D中的快速碰撞检测、存储稀疏数据等

 * 为提升性能

 * 1、广度优先搜索

 * 2、仅搜索叶节点

 */

export class QuadTree<T> {

    /** 树的高度 */

    protected _height: number;

    /** 树的深度 */

    protected _depth: number;

    /** 根节点 */

    protected _rootNode: QuadTreeNode<T>;

    /** 矩形区域 */

    protected _rect: Rectangle;

    /** 广度搜索队列(仅包含叶节点) */

    public breadthSearchQueue: Queue<QuadTreeNode<T>>;

    /** 对象与叶节点的映射表 */

    public targetNodeMap: HashTable<QuadTreeNode<T>>;


    /**

     * 构造函数

     * @param rect 二维空间数据

     * @param depth 树的深度

     */

    constructor(rect: Rectangle, depth: number, targets?: T[]) {

        this._rect = rect;

        this._depth = depth;

        this._height = this._depth;

        this.breadthSearchQueue = new Queue<QuadTreeNode<T>>();

        this.targetNodeMap = new HashTable<QuadTreeNode<T>>();

        if (depth >= 0) {

            this._rootNode = new QuadTreeNode<T>(this, null, rect, depth);

        }

        this.initBreadthSearchQueue();

    }

    /**

     * 创建广度搜索队列

     * 仅包含叶节点

     */

    protected initBreadthSearchQueue(): void {

        let node: QuadTreeNode<T>;

        let findFromLeft = function(nodes: QuadTreeNode<T>[]) {

            if (nodes.length <= 0) {

                return;

            }

            let childsArr = [];

            // 先遍历邻节点

            for (let i = 0; i < nodes.length; i++) {

                node = nodes[i];

                if (node.isLeaf()) {

                    this.breadthSearchQueue.push(node);

                }

                childsArr = childsArr.concat(node.getChildsAsArray());

            }

            // 再访问子节点数组

            findFromLeft(childsArr);

        }.bind(this);

        findFromLeft([this._rootNode]);

    }

    // /**

    //  * 绑定对象集合

    //  */

    // public bindTargets(targets: T[]): void {

    //     for (let i = 0; i < targets.length; i++) {

    //         const target = targets[i];

    //         let node = this.find(target["x"], target["y"]);

    //         if (node) {

    //             this.targetNodeMap.insert(target["hashCode"], node, true);

    //         }else {

    //             console.debug(this, "未找到关联的节点1", target["x"], target["y"], this._rootNode);

    //         }

    //     }

    // }

    /**

     * 获取对象所属节点

     */

    public getTargetNode(target: T): QuadTreeNode<T> {

        // LogUtils.debug(this, "状态表", target["x"], target["y"], this.targetNodeMap.get(target["hashCode"]));

        return this.targetNodeMap.get(target["hashCode"]);

    }

    /**

     * 获取所有对象合集

     */

    public get targets(): T[] {

        return this._rootNode.targets;

    }

    /**

     * 获取树的高度

     */

    public get height(): number {

        return this._height;

    }

    /**

     * 获取树的深度

     */

    public get depth(): number {

        return this._depth;

    }


    /**

     * 搜索目标对象所在区域节点

     * @param x 目标对象x坐标

     * @param y 目标对象y坐标

     * @param skipEmpty 是否忽略空数据节点

     */

    public find(x: number, y: number, skipEmpty: boolean = false): QuadTreeNode<T> {

        let node: QuadTreeNode<T> = null;

        let findFromNode: Function = (pNode: QuadTreeNode<T>): QuadTreeNode<T> => {

            if (!pNode) {

                return null;

            }

            if (skipEmpty && pNode.isDataEmpty) {

                return null;

            }

            if (!pNode.isContains(x, y)) {

                return null;

            }

            if (pNode.isLeaf()) {

                return pNode;

            }

            let leftChild = pNode.getLeftChild();

            while (leftChild) {

                node = findFromNode(leftChild);

                if (node) {

                    return node;

                }else {

                    leftChild = leftChild.nextNode;

                }

            }

        };

        // 从根节点开始查找

        return findFromNode(this._rootNode);

    }

    /**

     * 搜索圆形目标对象所在树根区域节点

     * @param x 目标对象x坐标

     * @param y 目标对象y坐标

     * @param skipEmpty 是否忽略空数据节点

     */

    public findNodesInCircle(x: number, y: number, radius: number, skipEmpty: boolean = false): QuadTreeNode<T>[] {

        let nodes: QuadTreeNode<T>[] = [];

        let findFromNode: Function = (pNode: QuadTreeNode<T>): QuadTreeNode<T> => {

            if (!pNode) {

                return null;

            }

            if (skipEmpty && pNode.isDataEmpty) {

                return null;

            }

            if (pNode.isLeaf()) {

                if (BaseTool.isPointsInCircle(x, y, radius, [pNode.leftTop.x, pNode.leftTop.y, pNode.leftBottom.x, pNode.leftBottom.y, 

                    pNode.rightBottom.x, pNode.rightBottom.y, pNode.rightTop.x, pNode.rightTop.y])) {

                    nodes.push(pNode);

                }

                return;

            }

            let leftChild = pNode.getLeftChild();

            while (leftChild) {

                findFromNode(leftChild);

                leftChild = leftChild.nextNode;

            }

        };

        // 从根节点开始查找

        findFromNode(this._rootNode);

        return nodes;

    }

    /**

     * 搜索矩形目标对象所在树根区域节点

     * @param x 目标对象x坐标

     * @param y 目标对象y坐标

     * @param width 目标对象宽

     * @param height 目标对象高

     * @param skipEmpty 是否忽略空数据节点

     */

    public findNodesInRect(x: number, y: number, width: number, height: number, skipEmpty: boolean = false): QuadTreeNode<T>[] {

        let nodes: QuadTreeNode<T>[] = [];

        let findFromNode: Function = (pNode: QuadTreeNode<T>): QuadTreeNode<T> => {

            if (!pNode) {

                return null;

            }

            if (skipEmpty && pNode.isDataEmpty) {

                return null;

            }

            // if (pNode.isLeaf()) {

            //     if (BaseTool.isRectIntersect(x, y, width, height, pNode.rect.x, pNode.rect.y, pNode.rect.width, pNode.rect.height)) {

            //         nodes.push(pNode);

            //     }

            //     return;

            // }

            if (BaseTool.isRectIntersect(x, y, width, height, pNode.rect.x, pNode.rect.y, pNode.rect.width, pNode.rect.height)) {

                if (pNode.isLeaf()) {

                    nodes.push(pNode);

                }else {

                    let leftChild = pNode.getLeftChild();

                    while (leftChild) {

                        findFromNode(leftChild);

                        leftChild = leftChild.nextNode;

                    }

                }

            }else {

                // console.log("不相交====", x, y, width, height, "///", pNode.rect.x, pNode.rect.y, pNode.rect.width, pNode.rect.height);

            }

        };

        // 从根节点开始查找

        findFromNode(this._rootNode);

        return nodes;

    }

    /**

     * 搜索凸多边形目标对象所在树根区域节点

     * @param x 目标对象x坐标

     * @param y 目标对象y坐标

     * @param width 目标对象宽

     * @param height 目标对象高

     * @param skipEmpty 是否忽略空数据节点

     */

    public findNodesInPolygon(polygonPoints: number[], skipEmpty: boolean = false): QuadTreeNode<T>[] {

        let nodes: QuadTreeNode<T>[] = [];

        let findFromNode: Function = (pNode: QuadTreeNode<T>): QuadTreeNode<T> => {

            if (!pNode) {

                return null;

            }

            if (skipEmpty && pNode.isDataEmpty) {

                return null;

            }

            // if (pNode.isLeaf()) {

            //     if (BaseTool.isRectIntersect(x, y, width, height, pNode.rect.x, pNode.rect.y, pNode.rect.width, pNode.rect.height)) {

            //         nodes.push(pNode);

            //     }

            //     return;

            // }

            if (BaseTool.isPointsInRect(pNode.rect.x, pNode.rect.y, pNode.rect.width, pNode.rect.height, polygonPoints)) {

                if (pNode.isLeaf()) {

                    nodes.push(pNode);

                }else {

                    let leftChild = pNode.getLeftChild();

                    while (leftChild) {

                        findFromNode(leftChild);

                        leftChild = leftChild.nextNode;

                    }

                }

            }else {

                // console.log("不相交====", x, y, width, height, "///", pNode.rect.x, pNode.rect.y, pNode.rect.width, pNode.rect.height);

            }

        };

        // 从根节点开始查找

        findFromNode(this._rootNode);

        return nodes;

    }

    /**

     * 搜索椭圆形目标对象所在树根区域节点

     * @param cx 椭圆中心x坐标

     * @param cy 椭圆中心y坐标

     * @param rx 椭圆横半轴

     * @param ry 椭圆纵半轴

     * @param angle 旋转角度

     * @param skipEmpty 是否忽略空数据节点

     */

    public findNodesInEllipse(cx: number, cy: number, rx: number, ry: number, angle: number, skipEmpty: boolean = false): QuadTreeNode<T>[] {

        let nodes: QuadTreeNode<T>[] = [];

        let findFromNode: Function = (pNode: QuadTreeNode<T>): QuadTreeNode<T> => {

            if (!pNode) {

                return null;

            }

            if (skipEmpty && pNode.isDataEmpty) {

                return null;

            }

            // if (pNode.isLeaf()) {

            //     if (BaseTool.isRectIntersect(x, y, width, height, pNode.rect.x, pNode.rect.y, pNode.rect.width, pNode.rect.height)) {

            //         nodes.push(pNode);

            //     }

            //     return;

            // }

            if (BaseTool.isRectIntersectEllipse(pNode.rect.x, pNode.rect.y, pNode.rect.width, pNode.rect.height, cx, cy, rx, ry, angle)) {

                if (pNode.isLeaf()) {

                    nodes.push(pNode);

                }else {

                    let leftChild = pNode.getLeftChild();

                    while (leftChild) {

                        findFromNode(leftChild);

                        leftChild = leftChild.nextNode;

                    }

                }

            }else {

                // console.log("不相交====", x, y, width, height, "///", pNode.rect.x, pNode.rect.y, pNode.rect.width, pNode.rect.height);

            }

        };

        // 从根节点开始查找

        findFromNode(this._rootNode);

        return nodes;

    }

    /** 

     * 对象坐标发生更新的通知

     */

    public onTargetPosUpdate(target: T): void {

        // let x = target["x"];

        // let y = target["y"];

        // let curNode = this.targetNodeMap.get(target["hashCode"]);

        // if (curNode && curNode.isContains(x, y)) {

        //     return;

        // }else {

        //     let node = this.find(x, y);

        //     if (node) {

        //         this.targetNodeMap.insert(target["hashCode"], node, true);

        //         this._onTargetNodeChanged(target, curNode, node);

        //     }else {

        //         console.debug(this, "未找到关联的节点2", x, y, this._rootNode);

        //     }

        // }

    }

    /** 

     * 通知对象 节点发生切换

     */

    public _onTargetNodeChanged(target: T, oldNode: QuadTreeNode<T>, newNode: QuadTreeNode<T>): void {

        if (oldNode) {

            oldNode.removeTarget(target);

        }

        if (newNode) {

            newNode.addTarget(target);

        }

        if (target["onTreeNodeChanged"]) {

            target["onTreeNodeChanged"](oldNode, newNode);

        }

    }

}

本文链接  游戏算法(2):查找优化之四叉树的应用

相关文章  游戏算法(1):实现AStar寻路算法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,042评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,996评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,674评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,340评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,404评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,749评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,902评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,662评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,110评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,577评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,258评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,848评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,726评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,952评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,271评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,452评论 2 348