418341 ภาคต้น 2552: การบ้าน 2

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
  • ให้ไว้ ณ วันที่ 8 กันยายน 2552
  • ส่งภายในเวลา 23.59 น. วันอังคารที่ 22 กันยายน 2552

คำเืตือน: การบ้านนี้เป็นการบ้านที่ต้องเขียนโปรแกรมมาก และมีเวลาทำน้อย นอกจากนี้ยังต้องมีความรู้ภาษา C++ เป็นอย่างดี

ุถ้าคุณไม่มั่นใจว่าคุณมีความรู้ภาษา C++ ดีพอ คุณสามารถไปหาข้อมูลเพิ่มเติมได้จาก

นอกจากนี้ โค้ดที่ให้ไปในการบ้านนี้มีจำนวนมาก ผมได้อธิบายรายละิเอียดของโ้ด้ดพวกนี้ไปบางส่วนแล้วในเลคเชอร์วันที่ 19 และ 21 สิงหาคม การดู สไลด์ ประกอบการสอนจะช่วยคุณได้มาก

ดาวน์โหลดโค้ด

ขั้นแรกให้ดาวน์โหลดโค้ดจากงลิงนี้ก่อน: homework-02.zip หลัีงจากนั้นให้ขยายไฟล์แล้วเปิดไฟล์ด้วย homework-02.sln ด้วย Microsoft Visual Studio 2008 Express

คุณสามารถดู API documentation ของคลาสต่างๆ ที่เกี่ยวข้องใน directory doc/html ได้ หรือจะดูที่ ลิงก์นี้ ก็ได้

ข้อ 1: Matrix

เมื่อเปิดไฟล์ homework-02.sln แล้วให้ปิด project ทั้งหมดแล้วเหลือ project เหล่านี้เอาไว้

  • gtest
  • problem0
  • problem1
  • problem1_test

โดยการปิดโปรเจคใ้้ห้ทำโดยคลิกขวาที่ชื่อโปรเจคแล้วเลือก Unload Project

หลังจากนั้นให้เปิด problem1.cpp ในโปรเจค problem1 แล้วเติมฟังก์ชันเหล่านี้ให้สมบูรณ์

  • Matrix4x4 Matrix4x4::identity()
    คืน identity matrix ขนาด 4 คูณ 4
  • Matrix4x4 Matrix4x4::translate(float x, float y, float z)
    คืน matrix ของการเลื่อนแกนขนานไปตามแกน x เท่ากับอาร์กิวเมนต์ x, ไปตามแกน y เท่ากับอาร์กิวเมนต์ y, และไปตามแกน z เท่ากับอาร์กิวเมนต์ z
  • Matrix4x4 Matrix4x4::translate_x(float x)
    คืน matrix ของการเลื่อนแกนขนานไปตามแกน x เท่ากับอาร์กิวเมนต์ x
  • Matrix4x4 Matrix4x4::translate_y(float y)
    คืน matrix ของการเลื่อนแกนขนานไปตามแกน y เท่ากับอาร์กิวเมนต์ y
  • Matrix4x4 Matrix4x4::translate_z(float z)
    คืน matrix ของการเลื่อนแกนขนานไปตามแกน z เท่ากับอาร์กิวเมนต์ z
  • Matrix4x4 Matrix4x4::scale(float x, float y, float z)
    คืน matrix ของการย่อขยายขนาดตามแกน x เท่ากับอาร์กิวเมนต์ x เท่า, ตามแกน y เท่ากับอาร์กิวเมนต์ y เท่า, และตามแกน z เท่ากับอาร์กิวเมนต์ z เท่า
  • Matrix4x4 Matrix4x4::scale_x(float x)
    คืน matrix ของการย่อขยายขนาดตามแกน x เท่ากับอาร์กิวเมนต์ x เท่า
  • Matrix4x4 Matrix4x4::scale_y(float y)
    คืน matrix ของการย่อขยายขนาดตามแกน y เท่ากับอาร์กิวเมนต์ y เท่า
  • Matrix4x4 Matrix4x4::scale_z(float z)
    คืน matrix ของการย่อขยายขนาดตามแกน z เท่ากับอาร์กิวเมนต์ z เท่า
  • Matrix4x4 Matrix4x4::rotate(float degrees, Vector3 axis)
    คืน matrix ของการหมุนเป็นมุม degrees องศา รอบแกน axis
  • Matrix4x4 Matrix4x4::rotate_x(float degrees)
    คืน matrix ของการหมุนเป็นมุม degrees องศา รอบแกน x
  • Matrix4x4 Matrix4x4::rotate_y(float degrees)
    คืน matrix ของการหมุนเป็นมุม degrees องศา รอบแกน y
  • Matrix4x4 Matrix4x4::rotate_z(float degrees)
    คืน matrix ของการหมุนเป็นมุม degrees องศา รอบแกน z

เมื่อเติมเสร็จเรียบร้อยแล้วให้ทดลอง compile ด้วยการกด F7

ถ้าคอมไพล์ผ่านให้เลือกโปรเจค problem1_test เป็น Startup Project โดยคลิกขวาที่ชื่อโปรเจค problem1_test แล้วเลือก Set as Startup Project หลังจากนั้นจึงลองรันมันด้วยการกด Ctrl+F5 ถ้าโปรแกรมคุณทำงานได้ถูกต้อง คุณจะผ่านการทดสอบที่เขียนไว้ใน problem1_test.cpp

ข้อ 2: Transform

หลังจากทำข้อ 1 เสร็จแล้ว ให้เปิดโปรเจค problem2 และ problem2_test ด้วยการคลิกขวาที่ชื่อโปรเจคแล้วเลือก Load Project

ในข้อ 2 นี้เราจะทำการ implement ฟังก์ชันสำหรับสร้าง instance ของคลาส Transform ซึ่งใช้แทน affine transformation ที่สำคัญใน computer graphics

คลาส Transform มีส่วนประกอบเป็น matrix ขนาด 4 คูณ 4 สาม matrix ได้แก่

  • m เป็น matrix ของ transform
  • mi เป็น inverse ของ m
  • mit เป็น inverse transform ของ m (กล่าวคือเป็น transpose ของ mi)

ฟังก์ชันที่ต้ัองเติมให้สมบูรณ์อยู่ในไฟล์ชื่อ problem2.cpp ในโปรเจค problem2 มีดังต่อไปนี้

  • Transform Transform::identity()
    คืน Transform ที่แทน identity transformation
  • Transform Transform::translate(float x, float y, float z)
    คืน Transform ที่แทนการเลื่อนแกนขนานไปตามแกน x เท่ากับอาร์กิวเมนต์ x, ไปตามแกน y เท่ากับอาร์กิวเมนต์ y, และไปตามแกน z เท่ากับอาร์กิวเมนต์ z
  • Transform Transform::translate_x(float x)
    คืน Transform ที่แทนการเลื่อนแกนขนานไปตามแกน x เท่ากับอาร์กิวเมนต์ x
  • Transform Transform::translate_y(float y)
    คืน Transform ที่แทนการเลื่อนแกนขนานไปตามแกน y เท่ากับอาร์กิวเมนต์ y
  • Transform Transform::translate_z(float z)
    คืน Transform ที่แทนการเลื่อนแกนขนานไปตามแกน z เท่ากับอาร์กิวเมนต์ z
  • Transform Transform::scale(float x, float y, float z)
    คืน Transform ที่แทนการย่อขยายขนาดตามแกน x เท่ากับอาร์กิวเมนต์ x เท่า, ตามแกน y เท่ากับอาร์กิวเมนต์ y เท่า, และตามแกน z เท่ากับอาร์กิวเมนต์ z เท่า
  • Transform Transform::scale_x(float x)
    คืน Transform ที่แทนการย่อขยายขนาดตามแกน x เท่ากับอาร์กิวเมนต์ x เท่า
  • Transform Transform::scale_y(float y)
    คืน Transform ที่แทนการย่อขยายขนาดตามแกน y เท่ากับอาร์กิวเมนต์ y เท่า
  • Transform Transform::scale_z(float z)
    คืน Transform ที่แทนการย่อขยายขนาดตามแกน z เท่ากับอาร์กิวเมนต์ z เท่า
  • Transform Transform::rotate(float degrees, Vector3 axis)
    คืน Transform ที่แทนการหมุนเป็นมุม degrees องศา รอบแกน axis
  • Transform Transform::rotate_x(float degrees)
    คืน Transform ที่แทนการหมุนเป็นมุม degrees องศา รอบแกน x
  • Transform Transform::rotate_y(float degrees)
    คืน Transform ที่แทนการหมุนเป็นมุม degrees องศา รอบแกน y
  • Transform Transform::rotate_z(float degrees)
    คืน Transform ที่แทนการหมุนเป็นมุม degrees องศา รอบแกน z

เมื่อเติมเสร็จเรียบร้อยแล้วให้ทดลอง compile ด้วยการกด F7

ถ้าคอมไพล์ผ่านให้เลือกโปรเจค problem2_test เป็น Startup Project โดยคลิกขวาที่ชื่อโปรเจค problem1_test แล้วเลือก Set as Startup Project หลังจากนั้นจึงลองรันมันด้วยการกด Ctrl+F5 ถ้าโปรแกรมคุณทำงานได้ถูกต้อง คุณจะผ่านการทดสอบที่เขียนไว้ใน problem2_test.cpp

หมายเหตุ: เพื่อให้ข้อนี้ไม่ง่ายเกินไป ผมได้ลบฟังก์ชัน inverse ออกไปจาก matrix4x4.h คุณจะต้องหาทางหา inverse ของวัตถุต่างๆ เอาเอง

ข้อ 3: Mesh

เมื่อทำข้อ 3 เสร็จแล้วให้เปิดโปรเจค problem3 และ problem3_test

ในข้อนี้เราจะทำการ implement คลาส Mesh คุณสามารถดูรายละเอียดของคลาสนี้ได้จาก สไลด์

คุณสามารถแ้ก้ไข declaration ของคลาส Mesh ได้ในไฟล์ problem3.h แต่คุณไม่ควรแก้ลบ method ที่มีอยู่แล้วออกไปเนื่องจากโค้ดส่วนอื่นใช้ method เหล่านี้ อนึ่ง คลาส Mesh ที่ให้มาไม่มีฟีลด์ใดๆ อยู่เลย ฉะนั้นคุณจะต้องเพิ่มฟีลด์ที่เหมาะสมเข้าไปเอง

หลังจากแก้ไข declaration แล้วให้ไป implement method ต่างๆ ในไฟล์ problem3.cpp อย่าลืมว่าถ้าคุณเพิ่ม method ใดขึ้นมาคุณจะต้อง implement method นั้นด้วย

เมื่อคุณ implement คลาด Mesh เรียบร้อยแล้วให้ลองคอมไพล์ แล้วจึงเซต problem3_test เป็น Startup Project แล้วรัน ถ้าโปรแกรมคุณทำงานได้ถูกต้อง คุณจะผ่านการทดสอบที่เขียนไว้ใน problem3_test.cpp

ข้อ 4: อ่านไฟล์ .obj

ในข้อสี่ คุณจะทำการอ่านไฟล์ .obj เข้าใส่ในคลาส Mesh ที่เขียนไว้ในข้อ 3 อันดับแรกขอให้เปิดโปรเจคชื่อ problem4 และ problem4_test

คุณจะต้อง implement ฟังก์ชัน <geshi lang="c"> Mesh *parse_obj_file(const char *file_name) </geshi> ที่อยู่ในไฟล์ problem4/problem4.cpp

ฟังก์ชันนี้จะรับ string file_name ซึ่งบรรจุชื่อไฟล์ .obj เข้าไป คุณจะต้องเปิดอ่านไฟล์นี้ อ่านข้อมูลในไฟล์ .obj แล้ว allocate instance ของคลาส Mesh ขึ้นมาใหม่เพื่อเก็บข้อมูลจากไฟล์ เมื่อเสร็จแล้วให้คืน instance ที่ allocate ขึ้นมาใหม่กลับไปให้ผู้เรียกฟังก์ชัน

คุณสามารถอ่านรูปแบบของไฟล์ .obj ได้ในเวบไซต์นี้: http://www.royriggs.com/obj.html

คำสั่งในไฟล์ .obj ที่คุณจะต้องสามารถประมวลผลได้อย่างถูกต้องคือ

  • v (ใช้เพิ่มตำแหน่ง)
  • vn (ใช้เพิ่ม normal)
  • vt (ใช้เพิ่ม texture coordinate)
  • f (ใช้เพิ่ม face)

ส่วนคำสั่งอื่นถ้าอ่านเจอให้ข้ามไป ไม่ต้องเขียนโค้ดเพื่อประมวลผลคำสั่งเหล่านั้น

เมื่อคุณ implement ฟังก์ชันเสร็จแล้ว ให้ลองคอมไพล์โค้ดดู ถ้าคอมไพล์ได้ถูกต้องคุณจะสามารถรันโปรแกรม problem4_test ได้ คุณสามารถทดสอบว่าโปรแกรมของคุณทำงานได้ถูกต้องหรือไม่ได้โดยการรันคำสั่งต่อไปนี้ในไดเรคทอรีที่คุณขยายไฟล์ homework-02.zip เอาไว้

C:\Directory ที่ขยาย homework-02.zip> Debug\problem4_test <ชื่อไฟล์ .obj>

หรือ

C:\Directory ที่ขยาย homework-02.zip> Release\problem4_test <ชื่อไฟล์ .obj>

โดยจะใช้คำสั่งแรกหรือคำสั่งที่สองขึ้นอยู่กับว่าขณะนั้นคุณคอมไพล์โปรแกรมแบบ Debug (มีข้อมูลเกี่ยวกับการดีบัก ทำให้โค้ดช้า) หรือ Release (ดีบักไม่ได้ แต่เร็ว)

ตัวอย่างผลลัพธ์ที่ถูกต้อง

Problem 4 bunny.JPG Problem 4 garg.JPG Problem 4 torus.JPG
Release\problem4_test data\bunny.obj Release\problem4_test data\garg.obj Release\problem4_test data\torus.obj

คุณสามารถ download โปรแกรม problem4_test ที่ทำงานได้ถูกต้องไปดูเป็นตัวอย่างได้ที่นี่: homework-02-solution-sample.zip

คำแนะนำ

ในไฟล์ include\cglib\string.h จะมีฟังก์ชัน <geshi lang="C"> std::vector<std::string> split(const std::string &s, char delim); </geshi> ซึ่งคุณสามารถป้อนสตริง s และตัวอักษร delim แล้วมันจะคืน vector ของสตริงหลายตัวที่เิิกิดจากการตัด s เป็นท่อนๆ แต่ละท่อนขั้นด้วยตัวอักษร delim เช่นถ้าเรียก <geshi lang="C"> std::vector<std::string> parts = split("v 0.5 0.3 0.7", ' '); </geshi> คุณจะได้ว่า parts[0] = "v", parts[1] = "0.5", parts[2] = "0.3", และ parts[3] = "0.7"

ข้อ 5: แสดงผล MeshNode

หลังจากทำข้อ 4 เสร็จแล้ว ให้เปิดโปรเจค problem5 และ problem5_test

ในข้อ 5 คุณจะต้อง implement ฟังก์ชัน <geshi lang="C"> void render(MeshNode *node); </geshi> โดยฟังก์ชันนี้มีหน้าที่ใช้คำสั่ง OpenGL ในการแสดงผลคลาส MeshNode (ซึ่งแทน mesh อันหนึ่งพร้อมด้วย material ของ face แต่ละ face)

MeshNode

คลาส MeshNode มีส่วนประกอบอยู่สามส่วนคือ

  • mesh ซึ่งเป็น instance ของคลาส Mesh ที่คุณเขียนไปในข้อ 3 โดยคุณสามารถเอา pointer ไปยัง mesh ได้โดยการใช้เมธอด Mesh *MeshNode::get_mesh()
  • material_mapping ซึ่งเป็น instance ของคลาส MeshMaterialMapping (ดู include/cglib/mesh_material_mapping.h และ cglib/mesh_material_mapping.cpp) โดยคุณสามารถเอา pointer ไปยัง material_mapping ได้โดยการใช้เมธอด MeshMaterialMapping *MeshNode::get_material_mapping()
  • material_list ซึ่งเป็น instance ของคลาส MaterialList (ดู include/cglib/material_list.h และ cglib/material_list.h) โดยคุณสามารถเอา pointer ไปยัง material_list ได้ด้วยการใช้เมธอด MeshMaterialMapping *MeshNode::get_material_list()

โดยที่ mesh, material_mapping, และ material_list มีความสัมพันธ์กันดังต่อไปนี้

  • material ของ face ทั้งหมดจะถูกเก็บอยู่ใน material_list
    • คุณสามารถหาได้ว่ามี material อยู่ทั้งหมดกี่ material โดยใช้เมธอด int MaterialList::get_material_count();
    • material จะมีหมายเลขตั้งแต่ 0 ถึง get_material_count()-1
    • คุณสามารถเอา pointer ไปยัง material หมายเลข j ได้ด้วยการใช้เมธอด Material *MaterialList::get_material(int material_index); โดยในกรณีต้องเรียก material_list->get_material(j);
  • แต่ละ face ใน mesh จะมี material ของมันเอง
    • หมายเลขของ material ของ face ต่างๆ จะถูกเก็บไว้ใน material_mapping
    • คุณสามารถหาหมายเลขของ material ของ face หมายเลข k ได้ด้วยการใช้เมธอด int MeshMaterialMapping::get_face_material_index(int face_index); โดยในกรณีเราจะเรียก material_mapping->get_material_index(k);

การแสดงผล mesh

ในโค้ดที่ให้ไปมีโค้ดที่ใช้แสดงผล mesh อยู่แล้วใน problem4/problem4_test.cpp ฟังก์ชัน void MyApp::Draw()

<geshi lang="C++"> virtual void Draw() { FOR(face_index, mesh->get_face_count()) { glBegin(GL_POLYGON);

FOR(vertex_index, mesh->get_face_vertex_count(face_index)) { Point3 position = mesh->get_face_vertex_position(face_index, vertex_index); Vector3 normal = mesh->get_face_vertex_normal(face_index, vertex_index);

glNormal3f(normal.x, normal.y, normal.z); glVertex3f(position.x, position.y, position.z); }

glEnd(); } } </geshi>

ฟังก์ชัน Draw จะทำการวาด face ของ mesh ทีละ face ด้วยการใช้ GL_POLYGON

สิ่งที่แตกต่างกันระหว่างฟังก์ชัน Draw และ render คือในฟังก์ชัน Draw จะไม่มี material มาเกี่ยวข้อง แต่ปัญหาที่คุณจะต้องแก้ในการเขียนฟังก์ชัน render คือ ทำอย่างไรถึงใช้ข้อมูล material ของหน้าต่างๆ ที่เก็บอยู่ใน MeshNode ในการแสดงผล face ได้ด้วย

โด้ดที่คุณเขียนในฟังก์ชัน render ควรจะมีลักษณะคล้ายกับฟังก์ชัน Draw ข้างบน กล่าวคือ

  • โค้ดของคุณจะต้องไล่แสดงผล face ไปทีละ face ด้วยการใช้ GL_POLYGON คล้ายๆ ข้างบน
  • สำหรับ face แต่ละ face โค้ดของคุณจะต้องหาหมายเลขของ material ของ face นั้น พร้อมทั้ง pointer ไปยัง instance ของคลาส Material ที่แทน material ของ face นั้น
  • โค้ดของคุณจะต้องเข้าใจข้อมูลที่เก็บอยู่ใน instance ของคลาส Material และใช้คำสั่ง OpenGL กับข้อมูลเหล่านั้นเพื่อแสดงผล material ต่างๆ ให้เหมาะสม
  • โค้ดของคุณไม่ควรเรียกคำสั่งที่เปลี่ยนแปลง Modelview matrix หรือ lighting เลย มันควรจะเีรียกคำสั่งที่เกี่ยวข้องกับการวาดรูป และ glMaterialfv หรือ glMaterialf เท่านั้น

การจัดการกับ instance ของคลาส Material

ในการบ้าน 2 นี้เรามีคลาสที่เป็น subclass ของคลาส Material (เป็น abstract class) อยู่สองตัวคือ PhongMaterial (material ตาม Phong lighting model ธรรมดา) และ TexturedPhongMaterial (Phong lighting model โดยที่สี diffuse ถูกกำหนดโดย texture)

เมื่อคุณเรียก Material *material = material_list->get_material(face_index) เพื่อเอา instance ของคลาส Material มา คุณจะต้องหาให้ได้ว่ามันเป็น PhongMaterial หรือ TexturedPhongMaterial ถ้าเป็นในภาษา Java คุณสามารถว่ามันเป็นคลาสไหนได้ด้วยการใช้ instanceof แต่ในภาษา C++ ไม่มี instanceof แต่เราสามารถใช้ dynamic_cast (ดูข้อมูลจาก Wikipedia) ในการแยกนี้ได้

ถ้าสมมติว่าคุณต้องการทราบว่า material เป็น PhongMaterial หรือไม่ ให้ลอง dynamic_cast มันเป็น PhongMaterial ดังต่อไปนี้ <geshi lang="C++"> PhongMaterial *phong_material = dynamic_cast<PhongMaterial *>(material); </geshi> ด้วย material เป็น PhongMaterial แล้ว phong_material จะมีค่าไม่เท่ากับ NULL แต่ถ้ามันไม่ใช่ phong_material จะมีค่าเป็น NULL ฉะนั้นคุณสามารถเช็คความเป็น PhongMaterial ได้ดังต่อไปนี้ <geshi lang="C++"> if (phong_material != NULL) {

   ทำอะไรกับ phong_material ต่อไป...

} else {

   จัดการกรณีที่มันไม่ใช่ PhongMaterial...

} </geshi>

สำหรับการเช็คว่า material เป็น TexturedPhongMaterial หรือไม่ก็ทำวิธีการเดียวกับข้างบน

การใช้ Texture กับ Phong lighting model

ในชั้นเรียน เราเคยใช้ Phong lighting model แต่ในตอนนี้สีของ face แต่ละ face มีสี diffuse ได้เพียงสีเดียวเท่าันั้น อย่างไรก็ดีใน OpenGL คุณสามารถใช้ texture ในการกำหนดสี diffuse ของ Phong lighting model ได้ด้วยการสั่ง <geshi lang="C++"> glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); </geshi> หลังจาก bind texture เรียบร้อยแล้ว และก่อนจะวาดรูปโดยใช้ glBegin()

คุณควรจะใช้คำสั่งข้างบนนี้ในการจัดการกับ TexturedPhongMaterial

การตรวจสอบการทำงาน

เมื่อคุณเขียนฟังก์ชัน render เสร็จแล้ว ให้ลองคอมไพล์โปรแกรมดู แล้วรันโปรแกรม problem5_test ด้วยคำสั่ง

C:\Directory ที่ขยาย homework-02.zip> Debug\problem5_test <ชื่อไฟล์ .ezt>

หรือ

C:\Directory ที่ขยาย homework-02.zip> Release\problem5_test <ชื่อไฟล์ .ezt>

ใน directory data มีไฟล์นามสกุล .ezt ซึ่งอยู่สามไฟล์ คือ hatsune_miku.ezt, bunny.ezt, และ gourd.ezt เมื่อคุณรันโปรแกรมของคุณโดยให้ไฟล์เหล่านี้เป็นอินพุตแล้วควรจะได้ผลลัพธ์ดังต่อไปนี้

Problem 5 hatsune miku.JPG Problem 5 bunny.JPG Problem 5 gourd.JPG
Release\problem4_test data\hatsune_miku.ezt Release\problem4_test data\bunny.ezt (หลังจากหมุน) Release\problem4_test data\gourd.ezt (หลังจากหมุน)

อนึ่ง homework-02-solution-sample.zip มีโปรแกรม problem5_test ที่ทำงานได้ถูกต้องอยู่ด้วย

การส่งงาน

กรุณาส่งงานเป็น .zip ไฟล์มาที่ pramook at gmail dot com หรือ fscipmk at ku dot ac dot th ภายในเวลา 23.59 น. ของวันที่ 22 กันยายน 2552

ไฟล์ zip ของคุณควรจะมีโครงสร้างตามไฟล์ตัวอย่างนี้ homework-02-handin-sample.zip กล่าวคือเมื่อขยายออกมาแล้วควรจะมี directory ชื่อ homework-02 ซึ่งข้างใต้มี directory ย่อยอยู่ 5 directory และในแต่ละ directory มีไฟล์ดังต่อไปนี้

homework-02
    problem1
        problem1.cpp
    problem2
        problem2.cpp
    problem3
        problem3.h
        problem3.cpp
    problem4
        problem4.cpp
    problem2
        problem5.cpp

ไฟล์ที่คุณจะส่งเหล่านี้มีไฟล์ต้นแบบอยู่ใน homework-02.zip อยู่แล้ว คุณสามารถก๊อปปี้มันมาใส่ ZIP ไฟล์ที่จะส่งได้เลย

อนึ่ง ขอเตือนว่า

  • กรุณาสร้าง directory แบบที่ผมเขียนไว้ข้างบนอย่างเคร่งครัดด้วย เพราะผมจะเขียนโปรแกรมขยายไฟล์โดยอัตโนมัติื หากโปรแกรมผมหาไฟล์ไม่เจอ มันจะไม่ตรวจการบ้านให้ และคุณจะไม่ได้คะแนนการบ้านนี้
  • อย่างส่งไฟล์ .rar, .7z, หรือไฟล์แบบอื่นมาด้วย ผมจะไม่รับตรวจ
  • งานของคุณทั้งหมดจะต้องอยู่ในไฟล์ 6 ไฟล์ข้างบนนี้ ถ้าคุณใส่ไฟล์อื่นมาใน ZIP ไฟล์ด้วย ผมจะไม่สนใจไฟล์อื่นเหล่านั้นเลย
  • ถ้าไฟล์ที่คุณส่งมาคอมไพล์ไม่ผ่าน คุณจะไม่ได้คะแนนเลย