marketscript

引用:http://www.ddove.com/3dmax/sl/tut_controlling_foliage_with_maxscript.html

使用 MAXScript 控制植物

在本教程中,您将使用一种将树木的图像粘贴到平面上的技术。您将创建和修改用于创建布告牌效果的脚本,这种效果可使一个对象在整个动画期间都朝向另一个对象。朝向对象将只绕世界 Z 轴旋转。这种效果适用于树木、人物以及对平面对象使用位图的场景元素,可使这些对象始终朝向摄影机。

注意:本课程专为要学习如何使用 MAXScript 增强建筑模型的人士所设计。仅当您对学习 MAXScript 感兴趣时,才学习本课程。

 

您将通过模板创建脚本,并通过添加预置函数修改此脚本。然后,您将修改该脚本使对象选择更灵活。

这些步骤的目的在于,向您示范如何修改现有脚本以并入自己项目所需的函数。在此过程中,您会学习将 2D 树木对象用作 3D 树木代用品的方法。

技能级别:高级

完成时间:1 小时

本教程中介绍的功能

  • 使用 MAXScript 创建脚本

  • 使用预置 MAXScript 功能

  • 使用灯光的“倍增”参数创建阴影

教程文件

本教程的所有必需文件都可以在 3ds Max 8 附带的教程文件光盘上找到。在执行教程之前,请将 \tutorials\scripting_fx 目录从光盘复制到您的 \3dsmax8 本地安装目录中。

打开脚本的测试文件:

  1. 从 \tutorials\scripting_fx 文件夹中打开文件 tut_billboard_start.max

    注意:如果看到“文件加载:单位不匹配”对话框,请选择“按系统单位比例重缩放文件对象”选项。

     

    此场景将平面(布告牌)置于建筑物的前面,而不是树模型的前面。在布告牌上已经放置了橡树图像。每个图像都有一个 Alpha 通道,此通道用作不透明贴图,以使图像背景变得透明。除布告牌、灯光和摄影机外,所有对象都已冻结。

  2. 渲染位于第 0 帧的“Camera01”视口。

     

    场景渲染的速度要比存在 3D 树木的情况更快,而且从当前摄影机角度观看,布告牌树木也很逼真。由于场景使用光线跟踪阴影,因此来自树木贴图的阴影被正确渲染,但透明区域没有投影阴影。

  3. 拖动时间滑块以查看动画。

    已为摄影机设置动画,但布告牌并没有随摄影机移动变为朝向摄影机。

  4. 转到第 100 帧,然后渲染“Camera01”视口。

     

    从此角度观察,很明显树木只是粘贴在平面布告牌上。可以通过手动为布告牌设置动画以使其跟随摄影机,从而解决此问题。然而,如果有许多树木、人物和需要朝向摄影机的其他对象,则设置动画的过程将很快变得不切实际。

    可以改用脚本将脚本控制器添加到每个布告牌,从而强制布告牌始终朝向摄影机,甚至在对摄影机设置动画后。

试验此脚本:

首先,将运行脚本的已完成版本,以查看它如何工作。接着在本课程中,将学习自己编写此脚本。

  1. 转到“工具”面板。

  2. 单击“MAXScript”。

    将显示 MAXScript 卷展栏。

  3. 在“MAXScript”卷展栏上,单击“运行脚本”。

  4. 在“选择编辑器文件”对话框中,选择脚本 tut_billboard_01.ms

    会出现一个“Billboard”对话框,其中有一个标记为“Set Billboard Effect”的按钮。

     

    此脚本将处理选定对象。选择要注视摄影机的对象及摄影机本身。此脚本会计算出哪个对象是摄影机,并强制其他对象的局部 Z 轴朝向摄影机。

  5. 在“顶”视口中,选择摄影机和四个布告牌。

    注意:如果希望使用“按名称选择”对话框来选择对象,请按 H 键并使用对话框选择 Billboard01、Billboard02、Billboard03、Billboard04 和 Camera01。

  6. 在“Billboard”对话框中,单击“Set Billboard Effect”。

    当前帧上的平面变为朝向摄影机。

     

  7. 拖动时间滑块以查看动画。

    现在这些平面始终注视摄影机。

  8. 在“顶”视口中,移动摄影机,同时观察平面,然后单击右键以撤消所做的任何移动。

    移动摄影机时,平面变为朝向摄影机的位置。

    注意:当布告牌转变方向时,它投射的阴影会随之变化,这将在渲染的动画中创建一个不真实的效果。此问题将在本课程的后面解决。

检查脚本:

下面,将查看脚本的内容。

  1. 在“MAXScript”卷展栏上,单击“打开脚本”。选择文件 tut_billboard_01.ms

    脚本出现在“MAXScript”窗口中。在此窗口中,可以读取和编辑脚本。

  2. 浏览脚本并识别代码的以下常见部分:

    • 标头

      脚本的第一部分称为标头。这部分包含基本信息,如脚本文件名、脚本名称、脚本版本号、3ds Max 版本号、作者姓名、编写日期和编写目的。此信息按注释格式编写,这表示查找要执行的代码时,该程序会忽略此信息。跨多行的注释位于符号 /* 和 */ 之间,而单行注释前面带有双连字符 (--)。整个脚本包含临时注释,以告知您每行或段的用途。

    • makeBillboard 函数定义

      在标头之后,定义了 makeBillboard 函数,但不会执行。此函数将脚本控制器指定给每个布告牌对象的旋转。脚本控制器会计算使布告牌保持注视摄影机所需要的世界 Z 轴旋转。布告牌的局部 Z 轴定义对象的前面部分。选择此约定以匹配在“前”视口中创建的对象的方向。

      当运行此脚本时,该函数被加载,但并不执行。在脚本的后面,该函数将就绪以备调用。

    • 关闭对话框

      脚本执行的第一个任务是检查“Billboard”对话框是否已打开,如打开,则将其关闭。这样可避免对此对话框进行多次复制。

    • 卷展栏定义

      定义卷展栏并为其命名。在此定义中,已设置变量,并且定义了 UI 控制项(一个按钮)及其相关事件。摄影机变量名为 laObj(对于注视对象),而拥有布告牌对象的阵列名为 bbObjsArr(对于布告牌对象阵列)。

    • 浏览选定对象

      下一步,脚本将查看所有选定对象。通过检查每个对象的超类以识别摄影机。它将摄影机放置在 laObj 变量中,并将其余对象放置在 bbObjsArr 阵列中。

    • 调用 makeBillboard 函数

      脚本随后在 bbObjsArr 阵列中循环,并为阵列中列出的每个对象调用 makeBillboard 函数。

    • 创建对话框

      最后,脚本创建对话框,并等待您单击按钮。

检查 makeBillboard 函数:

makeBillboard 函数执行为注视摄影机的对象创建脚本控制器的实际工作。它包含执行不同任务的若干个部分。

  1. 查找以下代码部分:

    fn makeBillboard
    obj	-- The billboard object.
    targ	-- The lookat object.

    跟随 fn makeBillboard 的两个变量 obj 和 targ 是此函数的参数。参数是传递到函数的值,以便函数可以针对其执行操作。参数可以与 fn makeBillboard 出现在同一行上,但将它们放在单独的行上,可用于在每个参数后面添加注释。

    在此函数名称和参数后紧跟的是等号和左括号。定义此函数的代码从左括号后开始。

  2. 将光标放在 setWaitCursor() 行上的任意位置(行尾除外),然后按 Ctrl+B 组合键。

    此操作通过查找包含该函数的左括号和右括号,高亮显示整个函数定义。左括号就在 fn makeBillboard 声明和等号后面,而右括号将出现在许多行后面。

    提示:为使脚本执行不发生错误,每个左括号都必须有一个相应的右括号。若要快速找到一对特定圆括号内的代码部分,请将光标放在任一左括号后或其后的任意位置,然后按 Ctrl+B 组合键。位于括号内的代码部分将被选定,包含圆括号。再次按Ctrl+B 组合键将选择下一个括号内的代码。如果没有括号存在或只有一个括号,会听到哔声。此技术对于查找脚本中的错误非常有用。

    函数 setWaitCursor() 是在函数的开始处调用的,用于在处理函数时显示 Windows 沙漏。函数 setArrowCursor() 是在结尾处调用的,用于还原箭头光标。这两个函数都内置在 MAXScript 中。

    函数的剩余部分包含在 if...then... 子句中。这提供了错误处理,以确保将有效对象传递到函数。下一行可防止脚本尝试处理已删除或不存在的对象,这些操作会导致脚本失败。

    if obj!=undefined AND (NOT isDeleted obj) AND targ!=undefined AND (NOT isDeleted targ) then

    注意:操作数 != 表示“不等于”。

    if...then... 子句主体部分构成了一个字符串 scriptStr,此字符串包含脚本控制器的实际脚本。此脚本使用基本三角法计算所需的旋转角度。当使用脚本内的预置函数时,无须知道函数的详细信息。知道此函数的参数和返回的值就足够了。

    在此子句结尾,指定了脚本控制器,并扩展了脚本的时间范围。对于脚本控制器,活动时间范围将被自动设置为当前动画范围。如果决定在运行此脚本后增加动画中的帧数,则必须手动扩展控制器范围。通过将控制器的时间范围设置为非常大的间隔,脚本可避免这种情况发生。

    执行此步骤的代码如下:

    -- Assign rotation script controller.
    ctrl=obj.rotation.controller=rotation_script()
    -- Set time range wide in case user expands it later.
    setTimeRange ctrl (interval -1000 10000)
    -- Put script string into script controller.ctrl.
    script=scriptStr 

打开模板:

下面,您将利用模板重新创建脚本。在这里将向您介绍可用于创建您自己的脚本的工具。

  1. 重新打开文件 tut_billboard_start.max,从原始场景开始。不要保存更改。

  2. 选择“MAXScript”菜单 >“打开脚本”,然后打开文件 template_dialog.ms

    此模板拥有创建脚本所需的所有元素的占位符。

  3. 检查此代码,然后查找模板各部分。

    • 标头

    • 函数

    • 关闭对话框

    • 定义卷展栏:变量、UI 控制项、事件

    • 创建对话框

    创建新脚本时,使用模板可以节省时间。只需简单地填写每个占位符,就可以编写脚本。

  4. 将脚本另存为 my_billboard_01.ms

    此时更改文件名将避免覆盖模板。

填写标头:

模板的标头提供了标头的基本布局,其中带有文件名、版本和其他详细信息的条目。

  1. 在“MAXScript”窗口中,将 FILENAME 替换为文本 my_billboard_01.ms

  2. 将 SCRIPT_NAME 替换为名称 Billboard

  3. 将 ## 替换为 01

  4. 将 AUTHOR_NAME 替换为您的姓名。

  5. 将 MM.DD.YY 替换为当前日期。

  6. 将 INSERT_PURPOSE 替换为用您的语言简洁描述的脚本目的。

将函数添加到模板:

使用简单的复制并粘贴操作,可以将 makeBillboard 函数添加到模板。

  1. 在“MAXScript”窗口中,选择“文件”菜单 >“打开脚本”,然后打开 fn_makeBillboard.ms

  2. 选择所有文本。通过将光标放在文本内并按 Ctrl+A 组合键,可以执行此操作。

  3. 利用 Ctrl+C 组合键将文本复制到剪贴板。

  4. 在 my_billboard_01.ms 中,高亮显示 -- INSERT_FUNCTION,然后按 Ctrl+V 组合键以替换文本。

  5. 关闭 fn_makeBillboard.ms 脚本窗口。

填写卷展栏信息:

卷展栏声明为卷展栏标题提供了空间,当运行脚本时,将显示该标题。它还有一个内部卷展栏名称,您可以将其替换为一个更具描述性的名称。

  1. 将 ROLL_TITLE 替换为 My Billboard

  2. 将所有 rol_RNAME 替换为 rol_myBB

    提示:若要使此过程自动执行,请选择文本 rol_RNAME,按 Ctrl+H 组合键访问“替换”对话框,在“替换为”字段中键入 rol_myBB,然后单击“全部替换”。

  3. 在 --VARIABLES 后添加以下内容:

    local bbObjsArr -- Declare billboard objects array.
    local laObj -- Declare look-at object.

    local 声明设置变量的范围。在脚本中,变量可用于整个脚本,也可以仅用于声明所说明的位于圆括号内的代码特定区域。在本例中,local 命令表示这些变量应仅用于此段内容。在本例中,local 声明可确保这些变量仅用于卷展栏。这样可避免名称与代码的其他部分发生冲突,并防止变量的值被不应访问它们的代码覆盖。

  4. 在 -- UI CONTROL ITEMS 后添加:

    button but_setBBEffect "Set Billboard Effect" width:125 height:40\
    tooltip:"Select billboard objects and camera, then click button"

    在卷展栏上将创建按钮,并将其值指定给变量 but_setBBEffect。该按钮上出现的文本被设置为“Set Billboard Effect”。该按钮的宽度和高度已设置,并提供了鼠标悬停时的工具提示。

    提示:第一行末尾的反斜杠字符表示代码的后续部分位于另一行上。这使得长行的代码易于阅读。

    此卷展栏定义还定义了当单击其中一个卷展栏 UI 控制项时所发生的操作。此信息请参见 EVENTS 部分。

  5. 在 -- EVENTS 后添加:

    -- Set the billboard effect.
    on but_setBBEffect pressed do
    (
    
    ) 

    这声明了当按下此按钮时将要发生的事件。所有事件代码都位于圆括号内。

  6. 在第一个括号后面,添加:

    bbObjsArr=#() -- Initialize billboard objects array to null array.
    laObj=undefined -- Initialize lookat object variable to 'undefined'.

    使用先前创建的 local 命令声明了这两个变量,表示已将它们创建为数据的未来占位符。然而,在那时没有数据可替换它们。在上述两行中,已初始化这两个变量,表示已用一组开始数据替换这两个变量。

    • 变量 bbObjsArr 已设置为阵列。阵列是可以存储若干个值的列表或矩阵。在本例中,您希望阵列存储将被强制注视摄影机的所有对象。

    • 变量 laObj 已使用值“undefined”进行设置。该变量将仅存储摄影机。

     

    如果通过选择摄影机和其他对象,然后单击“Set Billboard Effect”按钮的方法来正确使用此脚本,则将使用对象名称填充这些变量。

    此时初始化这些变量(而不是在设置布告牌效果的脚本部分中),可提供一种用于以后在脚本中进行错误检查的方法。例如,如果在单击“Set Billboard Effect”后 laObj 仍是“undefined”,这表示未选定摄影机,并且此脚本也不会尝试执行布告牌效果。

    下面,您将创建一个循环。循环可多次执行一系列的指令。在本例中,循环将检查每个选定对象和测试,以确定它是摄影机还是其他类型的对象。它还测试对象是否为目标(如摄影机目标),但并不会将这些目标添加到注视摄影机的对象列表中。

  7. 添加下列内容,将空行添加为第一行:

    -- Note:User must manually select objects in viewport, or via 'Select by Name' dialog.
    --       Selection should include the billboard objects AND one camera.
    -- Loop through selected objects.
    for obj in selection do
    (
    if superClassOf obj==camera -- Check if object is the camera.
    then laObj=obj -- Assign camera object to 'laObj' variable.
    else if classOf obj!=targetObject then append bbObjsArr obj -- Append object to billboard objects array, but exclude target objects. 
    ) 

    当循环检查完当前选择后,要注视摄影机的对象都将位于阵列 bbObjsArr 中。剩下的一个任务是设置要检查 bbObjsArr 阵列的循环,并为阵列中的每个对象调用 makeBillboard 函数。在第二次循环前,需要测试当前选择中存在摄影机,并至少有一个对象要注视该摄影机。如果摄影机变量和对象阵列通过此测试,那么代码将继续为阵列中的各项调用 makeBillboard 函数。

  8. 在刚刚输入的行后直接添加以下行:

    -- Finally, set billboard effect:loop through billboard objects and call 'makeBillboard' fn.
    -- This assigns a script controller to the billboard objects' rotation.
    if bbObjsArr.count!=0 AND laObj!=undefined then -- Check to ensure objects have been selected.
    
    (
    for obj in bbObjsArr do makeBillboard obj laObj
    )

    仅当检测到摄影机和布告牌时,才应运行此循环。重新调用已初始化为空阵列 #() 的 bbObjsArr,以便在脚本开始处空阵列中没有元素。bbObjsArr.count 的值将告知您阵列中的元素数目。如果在选定对象中没有检测到布告牌对象(bbObjsArr.count 为 0),那么该检查将阻止循环运行。如果没有检测到摄影机对象,那么循环也不会运行。

    至此,就完成了脚本的创建。

测试脚本:

  1. 按 Ctrl+E 组合键运行脚本。

    如果脚本运行正常,则会出现“Billboard”对话框。

    选择布告牌和摄影机,然后单击“Set Billboard Effect”。

    布告牌应旋转以朝向摄影机。如果移动摄影机,那么布告牌应继续朝向摄影机。

     

    如果未出现“Billboard”对话框,或看到错误消息,则表示脚本出错。根据本课程检查脚本,看是否可以查出问题所在。可以在 tut_billboard_01.ms 文件中找到此脚本的完成版本。

  2. 将脚本另存为 my_billboard_01.ms

修改脚本:

此时,您将修改该脚本使对象选择更灵活。您将添加两个按钮以辅助选择过程。一个按钮用于通过“按名称选择”对话框选择布告牌对象。另一个按钮用于通过“按名称选择”对话框选择注视对象。除此之外,也不会像前一脚本中那样将注视对象限制到摄影机对象。

  1. 在标头中,将文件名更新为 my_billboard_02.ms

  2. 将脚本另存为 my_billboard_02.ms

    此时保存文件可防止您意外地覆盖以前版本。

  3. 在 -- UI CONTROL ITEMS 后添加两行,使代码看起来类似于以下内容:

    -- UI CONTROL ITEMS.
    button but_bbObjs "Select Billboard Objects"
    button but_laObj "Select LookAt Object"
    button but_setBBEffect "Set Billboard Effect" width:125 height:40

    此时,您将添加当按下第一个按钮时要发生的事件。

  4. 若要设置第一个按钮的功能,请在 -- EVENTS 后输入此文本:

    -- Select billboard objects, and put into array.
    on but_bbObjs pressed do
    (
    bbObjsArr=selectByName title:"Select Billboard Objects"
    ) 

    此按钮事件显示“按名称选择”对话框,然后接受来自此对话框的选择,并将这些选择放置在布告牌对象阵列 bbObjsArr 中。如果单击“取消”,或关闭此对话框而没有进行任何选择,则将返回值“undefined”。

    此时您将为第二个新按钮添加功能。

  5. 在第一个按钮代码后输入此文本:

    -- Select lookat object, and put into variable.
    on but_laObj pressed do
    (
    laObj=selectByName title:"Select LookAt Object" single:true
    ) 

    此按钮事件显示“按名称选择”对话框,接受来自此对话框的选择,并将该选择指定给注视对象 bbObjsArr。如果单击“取消”,或关闭此对话框而没有进行任何选择,则将返回值“undefined”。

    下面,您必须移除标识这些对象的现有代码,并将它们放在变量和阵列中。

  6. 在 setBBEffect 事件中以 on but_setBBEffect pressed do 开头的代码中,删除以 bbObjsArr=#() 开始,且在 -- Finally, set billboard effect. 之前的所有内容。

    此时,即完成修改的脚本。

  7. 将脚本另存为 my_billboard_02.ms

    可以在 tut_billboard_02.ms 文件中找到修改脚本的完成版本。

测试修改过的脚本:

  1. 在“MAXScript”窗口中,按 Ctrl+E 组合键对此脚本进行求值。

    将出现带有三个按钮的对话框:“Select Billboard Objects”、“Select LookAt Object”和“Set Billboard Effect”。

     

    如果出现错误,请在脚本中检查本课程中的指令,看是否可以找到问题所在。请根据需要进行更正。

  2. 单击“Select Billboard Objects”。

    将出现“按名称选择”对话框。

  3. 选择布告牌。

  4. 单击“Select LookAt Object”。

    再次出现“按名称选择”对话框。

  5. 选择 Camera01。

  6. 单击“Set Billboard Effect”以创建效果。

  7. 拖动时间滑块以查看动画。

    在动画期间,这些平面将继续朝向摄影机。

  8. 将场景另存为 my_billboard_facing.max。

    可以在 tut_billboard_facing.max 文件中找到此场景。

固定阴影:

当布告牌改变方向时,阴影会随之变化。通过为每个布告牌使用单独的灯光投影阴影,可以解决此问题。此方法的优点在于,所用的渲染时间要少于光线跟踪阴影。

首先,您将删除由树布告牌上的直接光投射的阴影。

  1. 选择所有布告牌。

  2. 右键单击任一视口。从四元菜单中选择“属性”。

  3. 在“对象属性”对话框中,禁用“投影阴影”。单击“确定”关闭该对话框。

    如果渲染摄影机视图,您会发现这些树木不再投影阴影。

  4. 右键单击任意视口,然后从四元菜单中选择“全部取消隐藏”。

    已将聚光灯放置在每个布告牌的中心位置,指向与直接光相同的方向。这些灯光当前处于禁用状态。所有聚光灯将被实例化,这样只需打开其中一个聚光灯即可打开全部聚光灯。

  5. 选择其中一个聚光灯。

  6. 在“修改”面板 >“常规参数”卷展栏 >“灯光类型”组中选中“启用”。

    此时所有四个聚光灯都已启用。在“强度/颜色/衰减”卷展栏中,还可以看到灯光的“倍增”值为负值。这会导致在灯光所到之处进行灯光移除,以创建阴影效果。

  7. 在“修改”面板上,下滚至“高级效果”卷展栏。

    在此卷展栏上,可以看到已将噪波贴图用作投影贴图应用到灯光。这会投影一个噪杂的黑白图案,以模拟树叶的图案。如果按 M 键打开“材质编辑器”,可以在第一个示例窗中看到此贴图。

  8. 渲染“Camera01”视口。

     

    这些阴影不像光线跟踪阴影那样明快,但它们也足以迷惑一扫而过的眼睛。可以在动画文件 tut_billboards.mov 中看到作为结果的动画。固定的阴影可造成这种幻觉:这些树木不是不断改变方向的布告牌,而是三维对象。

  9. 将场景另存为 my_billboard_shadows.max。

    可以在 tut_billboard_shadows.max 文件中找到完成的场景。

查看脚本控制器中的脚本:

布告牌脚本编写了一个脚本,并将其放在用于“旋转”轨迹的脚本控制器中。

  1. 选择其中一个布告牌。

  2. 转到“运动”面板。

  3. 如有必要,展开“指定控制器”卷展栏。

  4. 在“指定控制器”卷展栏上的变换轨迹列表中,单击以高亮显示“旋转”轨迹,然后右键单击此轨迹。从弹出菜单中选择“属性”。

    将显示“脚本控制器”对话框,其中显示了由 makeBillboard 函数生成的脚本。

将脚本粘贴到新窗口中:

现在,可以将脚本粘贴到新窗口中以查看它的带颜色的代码。通过颜色可以更容易查看各种脚本函数。

  1. 高亮显示“脚本控制器”对话框中的所有文本,使用 Ctrl+C 组合键进行复制。

  2. 选择“MAXScript”>“新建脚本”以打开新建脚本窗口。

  3. 使用 Ctrl+V 组合键将文本粘贴到新脚本中。

  4. 按 Ctrl+D 组合键为脚本设置颜色代码。

    脚本的主体包含在一个 try...catch... 子句中。这提供了错误处理。如果没有错误,则进行求值的最后一个表达式是 (eulerAngles 90 0 z_rot) as quat。如果有错误,则进行求值的最后一个表达式是 (eulerAngles 90 0) as quat。此脚本使用 Euler 角度进行计算,但使用 as quat 命令将它们作为(将它们转换为)四元数掷出。关于四元数旋转的信息,请参见课程装配腕部扭曲,或参见 MAXScript 文档。

    每次帧发生变化时,都要对脚本进行求值,但当对象控制器发生变化时,则不一定要进行此操作。当任一渲染的对象更改时,使用命令 dependsOn 可以对脚本进行求值。在本例中,当“Camera01”更改位置时,将对此脚本重新求值,以使这些平面实时注视摄影机。在 dependsOn 行上,摄影机的对象名称用单引号括起,以避免当名称包含空格、虚线或其他问题字符时发生错误。

    代码的其余部分使用基本三角法来计算实现布告牌效果所需的旋转角度。从布告牌对象指向目标对象的向量被投影到世界 XY 平面上。投影的向量作为构成直角三角形的斜边。直角三角形之斜边与 X 轴之间的角度即是所需的世界 Z 轴旋转角度。

     

    如果投影的向量的端点位于第三或第四象限,那么此角度的正弦函数为正值,如果端点位于第一或第二象限,那么此角度的正弦函数为负值。但此平面的局部轴并没有与世界轴对齐,因此必须包括 -90 度这一要素,并且必须将 X 轴旋转 90 度。

posted @ 2012-02-29 11:37  镇水古月  阅读(302)  评论(0编辑  收藏  举报