LucienXian's Blog


  • 首页

  • 归档

  • 标签

CPU调度

发表于 2017-11-14

CPU调度

一般来说,CPU调度==处理机调度==进程调度

发生时刻

  • running to waiting
  • running to ready
  • waiting to ready
  • terminates

调度方式

  • 抢占式:一旦给某个进程分配完cpu就让该进程进行下去;
  • 非抢占式:当一个进程在运行中,系统基于某种原则,剥夺分配给该进程的CPU;

调度策略

先来看几个指标,这几个指标可以用来判断调度策略优劣的:

Throughput(吞吐量):单位时间内进程完成的数量;

Turnaround time(周转时间):执行一个进程花费的时间(从进入就绪队列到完成 Termination time - Arrival time);

Waiting time:一个进程在ready队列中等待的时间;

Response time:从进程提出请求道首次被响应的时间(不包括执行完成);

First come, First served

非抢占式的,根据它们的到达时间,系统维一个FIFO的进程列表;

Shortest-Job-First

系统在ready queue中,优先选择那些CPU burst time最少的进程。

这种调度策略分为抢占式和非抢占式两种,抢占式的没什么好说,非抢占式的:当一个新的需要更少CPU burst time的进程到达时,CPU便会重新分配。

SJF还有一种变形,即highest response ration next(等待时间+要求执行时间/要求执行时间)

Priority

同样,优先级策略也分为抢占式和非抢占式两种。

这种调度策略会导致一个非常严重的问题——starvation,优先级低的进程可能一直无法被执行。为了解决这个问题,我们可以采取动态优先级的策略,随着时间推移,我们提升某些进程的优先级。

优先级范围

  • windows的线程
    • windows的每一个线程都有优先级,范围是0-31,0最低,31最高;
  • linux的进程
    • linux的进程范围是-20-19,值越小,优先级越高。通常情况下,linux的进程优先级是由父进程继承而来,通常是0;
    • nice值反映了进程优先级;
    • 我们可以通过nice命令来对一个将要执行的命令进程nice值;通过renice重新设置nice值;

Round Robin

这是一种抢占式的策略,给队列里的每个进程分配相同的时间片,无论进程是否执行完,当时间懂啊了都切换到下一个进程。

当时间片Q太大时,就变成了FIFO;

当Q太小,但不能小到切换上下文的花费比Q还小时;

这种策略的turnaround time会比SJF更高,因为SJF选择的是更低burst time,避免等待时间增多;但response time会更少,因为它的切换频率更快,使得响应更快;

Multilevel Queue

分配进程到多个队列中,每个队列使用不同的调度算法:

前台交互式(foreground)——RR

后台批处理(background)——FCFS

Multilevel Feedback

这是一种动态的调度策略,该策略需要定义以下这些部分:

队列个数;

每个队列的调度算法;

更新队列所属队列的方法;

提升或降低进程优先级的方法;

volatile限定符——CPP

发表于 2017-11-13

volatile 限定符

为什么使用volatile

根据CPP之父的说法:

A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.

直接处理硬件的程序往往会包含这样的数据元素,这些元素可能会被程序之外的过程控制,因此对象的值很有可能会被程序之外修改,这时应该把对象声明为volatile。

声明为volatile的变量在编译器访问它时,编译器就不再对它做任何优化,从而提供稳定的地址访问。例如,系统总是会重新从内存中读取该数据,即便在它前面的指令中已经都读取过数据并存储在寄存器中。

一般来说,volatile用在以下几个方面:

  • 中断服务程序中修改的的供其它程序检测的变量需要加volatile;
  • 多任务环境下多任务共同分享的标志需要加volatile;
  • 存储器映射的硬件寄存器需要加volatile(因为每次的读写意义不同);

通常情况下,是在多线程共享变量时,一个线程改变变量,使得该变量对其它线程visible。也可以避免当两个线程用到同一个变量时,一个线程使用寄存器中的变量,一个线程使用内存中的变量;

volatile指针

volatile与const之间没有什么影响,一个变量可以既是volatile的也是const。

1
2
3
4
volatile int v; // v是一个volatile的int
int *volatile vip; // vip是一个volatile指针,指向int
volatile int* ivp; // ivp是一个指针,它指向volatile int
volatile int *volatile vivp; //vivp是一个volatile指针,指向volatile int

同const,只能将一个volatile的对象赋予一个指向volatile的指针。

拷贝构造

与const不同,我们不能使用合成的拷贝/移动构造函数以及赋值运算符来初始化一个volatile对象。因为合成的成员接受的实参是一个非volatile的常量引用,我们不能将一个非volatile引用绑定到volatile对象上。

因此我们必须自定义拷贝或者赋值操作:

1
2
3
4
5
6
class Foo{
public:
Foo(const volatile Foo&);
Foo& operator=(volatile const Foo&);//将一个volatile对象赋值给非volatile对象
Foo& operator=(volatile const Foo&) volatile;//将一个volatile对象赋值给volatile对象
}

进程与线程

发表于 2017-11-12

进程与线程

定义

进程

一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程;一个进程由这些部分组成:PC;寄存器;stack;heap;data section;

线程

CPU调度的最小单元。一个线程包括:thread ID;PC;寄存器集;stack。

区别

  1. 进程具有独立的地址空间,而同一个进程下的线程则共享进程的地址空间;由于建立进程时,需要分配数据块,栈块和代码块等来维护这个进程,而线程则共享大部分数据,甚至可以访问进程的每一块内存,所以进程的创立和切换都是比较昂贵的工作模式,而线程则相对轻量;
  2. 线程的通信会更加方便,这也是建立在线程共享大部分数据的基础上。而且,一个线程建立的heap能被同一进程下的其它线程访问;
  3. 多进程比多线程健壮,大多数情况下,一个线程挂了整个进程都会挂了,而进程则是独立的。
  4. 多线程与多进程:多进程需要开辟更多的独立空间,消耗大量的资源;多线程则是共享数据,轻量级并发。对于需要频繁交互数据,或者是高并发的情况下,用多线程;而不需要频繁交互数据的并发编程则用多进程。

进程

进程状态

进程有五种状态:new、ready、running、waiting、terminate。

状态变化如下:

img
img

组成进程的部分

  • program code、data
  • PCB:stack、os的资源、registers

PCB

进程控制块:每个进程都信息相关:

  • process state;
  • program counter;
  • CPU registers;
  • CPU scheduling info;
  • memory-management info;
  • accounting info;
  • file management;
  • IO status info;

在linux里,在头文件sched.h中的task_struct存着进程的有关信息。

pid

以unix为例,fork()之后,父进程与子进程具有相同上下文。

在子进程中,fork()返回值为0;在父进程中,pid()返回值为子进程的pid号。

进程间通信

通信机制:

  • 信号量:用来实现进程间的同步与互斥,但不能存储通信数据;
  • 消息队列:由链表,存放在内核中并由消息队列标识符标识。
  • 共享内存:能实现内存被多个进程共享;
  • 管道(pipe):半双工的通信方式,单向通信,而且只能在具有亲缘关系的进程间通信;
  • 命名管道:同样半双工,但允许非亲缘关系的进程通信;
  • 信号:通知某个进程事件的发生;
  • 套接字;

线程

user与kernel 线程

user 线程:

用户线程由应用进程进行维护;

内核不知道用户线程的存在;

用户线程的切换不需要内核特权;

如果用户现场发起系统调用而阻塞了,则整个进程都在等待;

kernel 线程:

内核维护进程和线程的上下文;

线程的切换由内核完成;

时间片会由CPU分配给线程,因此多线程的进程会获得更多的CPU时间;

一个线程阻塞,不会影响其他线程的运行;

  • 比较:
    • 用户线程由于不用陷入内核,所以切换速度更快;
    • 用户线程允许用户进程自定义调度算法;
    • 用户线程跨平台会更容易;
    • 用户线程难以解决阻塞的问题;
    • 用户线程由于没有时钟,难以调度;

linux下的使用

四个常用函数:pthread_create; pthread_join; pthread_self; pthread_exit;

同步与互斥

  • 互斥是指在同一时间只允许一个访问者进行访问,具有唯一性和排他性;
  • 同步是指在实现互斥的基础上,通过某些机制实现访问者对资源的有序访问;

static的学习——CPP

发表于 2017-11-11

static的学习——CPP

作用

  1. 延长生命周期
  2. 限制作用域
  3. 唯一性

修饰局部变量

存储区域

static在修饰局部变量时,被修饰的变量存储在静态存储区(与之对应的非静态局部变量则存储在栈中,栈的分配比静态存储区快)。

生命周期

局部静态变量,即便在退出所在函数之后也不会被销毁。

  • 在main()之前static变量已经被初始化;
  • 由系统在程序退出时销毁;

修饰全局变量

限制作用域

对于全局变量,由于全局变量本身就是存储在静态存储区;但是static会限制了它的连接属性。

打个比方:

假如在头文件中定义了一个全局变量;而一旦这个全局变量被两个.cpp文件include进来了,那么回发送重定义的错误。

这时有两个解决办法:一个是上一次提到过的使用extern,而另一个则是使用static去修饰定义。由于static全局变量只能被包含它的文件定义,也就是说,static修饰的全局变量是不能被其它文件(非包含该变量的文件)访问,这时两个.cpp中static是不一样的。

修饰函数

  • 对于静态函数,定义和声明要放在一起,不能一个在头文件中,另一个在.cpp文件中。因此,在多人合作的项目中,可以定义static函数,避免冲突。

与C的不同

由于CPP类的出现,如果类中定义static的成员变量和函数,那么所有对象都共享一个实例。

extern的学习——CPP

发表于 2017-11-10

extern学习

变量声明

由于CPP支持分离式编译,所以如果程序被分成若干个文件,则需要一种方法来保证代码共享。因此,CPP将声明和定义分开,声明使得名字为程序所知,而定义则是创建与该名字相关联的实体。

因此,如果想要声明一个变量,而不是定义它,则在变量名前加上extern关键字。如果extern语句中包含初始值,则不再是声明了,而是定义:

1
2
extern int i; //声明
extern int i = 0; //定义

注意:

  1. 不能在函数里试图初始化一个由extern关键字标记的变量,会造成重定义的错误。
  2. extern时要严格对应声明时的类型,例如:定义的是数组,不可以extern指针。

用法:通常在模块的头文件中对本模块提供给其它模块引用的的函数和全局变量以extern声明,然后其它的模块在使用时只需要include该头文件即可。

extern "c"

CPP程序有时需要调用其他语言编写的函数,最常见的就是调用C语言编写的函数,虽然编译器在处理其调用方式上与CPP相同,但生成的代码会有所区别。例如CPP为了解决多态性的问题,会将函数名字与参数连在一起生成一个中间函数的名称,而C语言不会,因此这样会造成编译器找不到该函数。

为了避免这个问题,CPP提供了链接指示,指出如何处理非CPP的语言。常见做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifdef __cplusplus
#if __cplusplus
extern "C"{
 #endif
 #endif /* __cplusplus */
 …
 …
 //.h文件结束的地方
 #ifdef __cplusplus
 #if __cplusplus
}
#endif
#endif /* __cplusplus */

参考

http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html

区块链学习-3

发表于 2017-11-02

共识算法

共识(consensus)与昨天提到的一致性经常是放在一起讨论的,一致性指的是系统的结果状态,共识则是一个手段,过程,解决的是多个节点对某个proposal达成一致意见的过程。

一般把出现故障但不伪造信息的情况成为“非拜占庭错误”;

出现故障并且伪造信息进行恶意相应的称为“拜占庭错误”,同时对应结点,称为拜占庭节点;


常见算法


Paxos 算法 与 Raft算法

Paxos算法将节点分为了三个逻辑角色:

  • proposer:提出一个题案,等待其它结点选择、批准,通常担任该角色的为客户端;每个编号都应该有一个ID,通常是递增ID;
  • accepter: 负责对题案进行投票,是否接受该题案;
  • learner:获取投票结果,但不参与投票,可以将投票结果传播到其他地方;

基本过程:多个proposer争取到了提出提案的权利,得到权利的proposer发送提案给所有人,得到多数人确认通过的提案会成为最终批准的提案。

为了保证accepter能够获知其它accepter也收到了自己的投票信息,验证自己的投票结果是否构成了大多数的意见,并且proposer能收到自己的投票,该算法得实现两个提交阶段:

  • prepare:
    • 有权利的proposer发送自己的提案到accepter;
    • accepter保留着收到过的提案中最大的编号。如果收到了新的提案,而该提案比保留编号要大,则返回已接受的编号和提案内容,更新当前最大编号,意味着不接受小于当前最大编号的提案;
  • commit
    • proposer如果接受到多数的回复,则准备发出带有刚才提案ID的接收消息。如果收到的回复不带有新的提案,则说明锁定成功,可以发送自己的提案内容;如果收到了新的提案内容,则替换提案ID为接受的提案中最大的ID值;如果没有收到回复,则重发;
    • accepter收到消息之后,发现该提案ID不小于已接受的最大提案ID,则接受该提案,并更新信息;
  • 一旦多数accepter接受了共同的提案ID,则形成最终决议。


Raft算法也将服务器节点分为三种逻辑角色:

  • Leader: 处理所有的客户端交互,一次只有一个leader;
  • Follower: 刚开始集群中所有节点都是follower;
  • Candidate: 能被宣称leader;

基本过程:

  1. 首先是选举。任何一个follower都能成为一个candidate,成为candidate后向其它的follower发起选举自己的请求,自己也可以投票给自己,一旦票数大于二分之一,则表示通过、达成一致,成为leader;选出leader后,leader需要定期向follower发送信息,表示自己的通知,如果在一定时间内follower没有收到信息,则认为leader宕机,重新选leader;
  2. 以日志复制来表现raft算法,假设leader被选出后,此时客户端要求增加一个日志,leader会要求follower将该日志追加到自己的日志记录中去。多数的follower在追加日志成功后,会发出确认信息。然后,leader会通知所有的follower完成commit。

如果在这个过程中,由于网络分区或者故障,leader只能访问部分follower,那么另外一部分的f大多数follower则会选出新的leader,与客户端交互。这个新的leader会按以上步骤通知大多数的follower。此时,如果网络恢复了,旧的leader会变成follower,此时旧的leader的更新都必须回滚,从新的leader处同步所有数据。

其它一致性的保障可以参考:http://www.cnblogs.com/mindwind/p/5231986.html

区块链学习-2

发表于 2017-10-31

区块链学习<二> 一致性问题


由于区块链系统是一个分布式系统,因此一致性问题是实现基于该系统上的业务必须有所保障的。


定义

一致性(consistency),是指对于分布式系统中的多个服务节点,在给定一系列操作之后,在事先约定的协议保障下,对操作结果达成认同,系统对外呈现出一致的状态。


问题

  • 服务节点间的网络通信会在一定程度上出现消息延迟、乱序和内容不正确等;
  • 同步调用会降低分布式系统的扩展性,使其退化为单节点系统;
  • 无法保障节点的处理时间;

然而,如今处理分布式系统一致性的思路普遍集中在将可能发生不一致的并行操作进行串行化

特点

  • termination:操作必须要在一定时间内得出结果;
  • agreement:决策结果相同,这多数体现在分布式系统对transaction进行排序,并且所排顺序得到大家认可;
  • validity:transaction必须由某个节点提出;

区块链学习<1>

发表于 2017-10-30

区块链学习<一> 核心技术概览


定义与原理

区块链最早出现应用在比特币项目中,作为比特币背后的分布式记账平台。

  • transaction:一次对账本的操作,导致账本状态的改变;
  • block:记录着一段时间内交易状态和结果;
  • chain:由block按照发生顺序连接而成,相当于账本的日志记录;


假设存在一个分布式的记账本,这个账本只允许添加,不允许删除。而这个账本的底层数据结构是由一个线性链表组成的,其中每个元素即block。后续block的加入要加入前一个block的信息,并必须得到共识机制来选择。

任何结点都可以提一个区块。


特点

基于区块链的分布式账本应具备以下特点:

  • 维护一条不断增长的链,只能添加,禁止删除;
  • 去中心化,避免集中控制,达成共识过程采用分布式的做法;
  • 基于密码学的特性来避免交易被破坏;


分布式共识

其实这个。。。我也不大懂,大概意思就是怎样确保某个transaction能在分布式网络中得到一致的、同步的执行结果,而且这个执行结果被多方确认,同时这个执行结果是一旦确定就无法被推翻的。

目前学术界和工业界大概确定了两类算法思想:

  • 基于概率的算法;
  • 确定性算法;

性能

区块链衡量性能的指标主要以transactions per second即tps为主,区块链系统不能通过单纯增加结点来进行扩展,很大程度上取决于单个节点的处理能力,因为当节点数过多时,可能会因为一致性的达成过程所需时间过长而降低全网的性能。

chaincode_lifecycle

发表于 2017-10-28

先附上一张早上刚刚“新鲜出炉”的chaincode生命周期的流程图,欢迎指教;

img
img


四种声明周期

Chaincode 的生命周期有四种:package、install、instantiate、upgrade

Package

    • CDS根据代码和其他属性(名字和版本)定义chaincode包
    • 实例化政策能在endorsement政策中描述

    • 拥有chaincode的实体进行签名

      • 签名目的:建立chaincode的所有者、验证包的内容,防止被篡改
    • Create package

      • 两种方法:

        • 一种是我们希望该chaincode有多个owner,因此需要创建一个可以被前面的chaincode包(signerCDS),然后按顺序地被每个owner签名;
        • 另一种则是仅部署具有发出安装事物的结点签名的signedCDS
      • signerCDS包含3个元素

        • 源码,名字和版本(即CDS)
        • 实例化策略
        • chaincode的所有者,由endorsement定义


Install

  • 安装事务将源码组装成CDS的格式,然后install到运行该chaincode的结点上;
  • 只有chaincode的拥有者拥有的结点能执行chaincode,其它的结点可以验证和提交;
  • 安装chaincode的时候,会发送一个signer protocol到lifecycle system chaincode(LSCC);


Instantiate

  • 实例化事务通过调用LSCC来创建和实例化一个chaincode到channel上;
  • chaincode可以安装到多个channel上,它们相互之间是独立的;
  • 默认的实例化策略:链码实例化事物的创建者必须是channel管理员的成员;
  • 实例化事物的创建者必须满足signedCDS中的实例化策略,并且还是channel的writer;否则,会有可能出现恶意实体部署chaincode到未绑定的channel上或者欺骗成员在未绑定的channel上执行链码;
  • transaction到达endorser时,endorser根据实例化策略来验证创建者的签名;在提交大账本前,也会再做一次验证;
  • 实例化事物也会设立一个endorsement policy,这个策略用来被channel其它成员去接受,认可;


经过上述三个周期,chaincode进入活跃状态;


upgrade

  • chaincode名必须要一样;
  • 新的chaincode在安装、实例化到一个channel上时,其它绑定了旧的chaincode仍然运行旧的版本;
  • (旧的版本与新的版本可能共存,所以需要我们手动删除)
  • 与实例化事物不一样,检查upgrade事务是根据当前chaincode的实例化策略,而不是新的策略,这是为了保证只有当前chaincode实例化策略的成员才能upgrade

关于stop与start

据说新版本会推出stop与start两种生命周期的管理,拭目以待;

  • 没有停止和启动生命周期

    • 只能从每个endorser中删除signedCDS,然后从承载对等节点运行的主机或者虚拟机里删除chaincode的容器

OpenGL的矩阵使用——绘制桌子

发表于 2017-10-25

要求

其中最左边的桌子循环上移(即匀速上移到一定位置后回到原点继续匀速上移),中间的桌子不断旋转(即绕自身中间轴旋转),最右边的桌子循环缩小(即不断缩小到一定大小后回归原来大小继续缩小)。

img
img

桌子的模型尺寸如下:

img
img


操作方法和实验步骤

绘制桌子

根据题目要求,主要实现的是桌子的移动、旋转和缩放,因此首先需要绘制出桌子的形状。

由于题目要求的桌子主要由五个立法体组成,分别是一个桌面和四个桌脚,而立方体又是由6个面组成的,因此通过绘制模式GL_QUAD绘制所有的面即可;如图:

img
img

一个立方体的大小和位置有中心点位置和长宽高决定(假设中点位置坐标为x、y、z),因此我们可以得到四个面、二十四个点的位置为:

1
2
3
4
5
6
7
8
9
10
11
{ x + width,y + depth,z },{ x + width,y - depth,z },{ x - width,y - depth,z },{ x - width,y + depth,z },

{ x - width,y + depth,z },{ x - width,y - depth,z },{ x - width,y - depth,z - height },{ x - width,y + depth,z - height },

{ x - width,y + depth,z - height },{ x - width,y - depth,z - height },{ x + width,y - depth,z - height },{ x + width,y + depth,z - height },

{ x + width,y + depth,z - height },{ x + width,y - depth,z - height },{ x + width,y - depth,z },{ x + width,y + depth,z },

{ x + width,y + depth,z },{ x - width,y + depth,z },{ x - width,y + depth,z - height },{ x + width,y + depth,z - height },

{ x + width,y - depth,z },{ x - width,y - depth,z },{ x - width,y - depth,z - height },{ x + width,y - depth,z - height }

设置多边形的绘制模式为glPolygonMode(GL_FRONT_AND_BACK,GL_LINE),绘制出立方体来:

1
2
3
4
5
glBegin(GL_QUADS);
for (int i = 0; i < 24; i++)
glVertex3f(point[i][0], point[i][1],point[i][2]);
glEnd();

最后把绘制立方体的过程封装成函数,传入中心点位置和长宽高即可绘制出桌子;


桌子循环上移、旋转、缩小放大

循环上移通过函数glTranslatef、glRotate、glScale完成桌子移动、旋转和缩放的操作;这几个函数的操作原理是将当前的栈顶矩阵乘以一个操作矩阵(移动、旋转、缩放),因此每次绘制前我们都需要通过glLoadIdentity()将当前的操作矩阵矩阵重置为单位矩阵;然在具体的移动或旋转或缩放操作时,将操作矩阵压进栈中,避免这三个操作相互影响;


移动

void glTranslatef(GLfloatx,GLfloat y,GLfloat z);

函数功能: 沿X轴正方向平移x个单位

​ 沿Y轴正方向平移y个单位

​ 沿Z轴正方向平移z个单位

img
img

旋转

void glRotatef(GLfloatangle,GLfloat x,GLfloat y,GLfloat z);

函数功能:以点(0,0,0)到点(x,y,z)为轴,旋转angle角度;

img
img

根据题目要求,绕着y轴旋转,即用右手握住这条y轴向量,大拇指指向向量的正方向,四指环绕的方向就是旋转的方向;

缩放

void glScalef(GLfloat Sx, GLfloat Sy, GLfloat Sz);

函数功能:Sx,Sy,Sz分别为模型在x,y,z轴方向的缩放比;

img
img


源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include "glut.h"

float fTranslate;
float fRotate;
float fScale = 1.0f;

void Draw_Cube(GLfloat x, GLfloat y , GLfloat z,GLfloat width_, GLfloat depth_, GLfloat height_) {
GLfloat width = width_ * 0.5;
GLfloat depth = depth_ * 0.5;
GLfloat height = height_;
//calculate all the points
GLfloat point[24][3] = {
{ x + width,y + depth,z },{ x + width,y - depth,z },{ x - width,y - depth,z },{ x - width,y + depth,z },
{ x - width,y + depth,z },{ x - width,y - depth,z },{ x - width,y - depth,z - height },{ x - width,y + depth,z - height },
{ x - width,y + depth,z - height },{ x - width,y - depth,z - height },{ x + width,y - depth,z - height },{ x + width,y + depth,z - height },
{ x + width,y + depth,z - height },{ x + width,y - depth,z - height },{ x + width,y - depth,z },{ x + width,y + depth,z },
{ x + width,y + depth,z },{ x - width,y + depth,z },{ x - width,y + depth,z - height },{ x + width,y + depth,z - height },
{ x + width,y - depth,z },{ x - width,y - depth,z },{ x - width,y - depth,z - height },{ x + width,y - depth,z - height }
};

glBegin(GL_QUADS);
for (int i = 0; i < 24; i++)
glVertex3f(point[i][0], point[i][1],point[i][2]);
glEnd();
}

void Draw_Dest() // This function draws a triangle with RGB colors
{
Draw_Cube(0, 0, 0, 1, 0.8, 0.2); //桌面
Draw_Cube(0.3, 0.2, -0.2, 0.2, 0.2, 0.6);//桌脚
Draw_Cube(-0.3, 0.2, -0.2, 0.2, 0.2, 0.6);
Draw_Cube(0.3, -0.2, -0.2, 0.2, 0.2, 0.6);
Draw_Cube(-0.3, -0.2, -0.2, 0.2, 0.2, 0.6);
}

void reshape(int width, int height)
{
if (height==0) // Prevent A Divide By Zero By
{
height=1; // Making Height Equal One
}

glViewport(0,0,width,height); // Reset The Current Viewport

glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
glLoadIdentity(); // Reset The Projection Matrix

// Calculate The Aspect Ratio Of The Window
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);

glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
glLoadIdentity(); // Reset The Modelview Matrix
}

void idle()
{
glutPostRedisplay();
}

void redraw()
{

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity(); // Reset The Current Modelview Matrix

glPushMatrix();
glTranslatef(-2.0f, 0.0f,-6.0f); // Place the triangle Left
glTranslatef(0.0f, fTranslate, 0.0f); // Translate in Y direction
Draw_Dest(); // Draw desk
glPopMatrix();

glPushMatrix();
glTranslatef(0.0f, 0.0f,-6.0f); // Place the dest at Center
glRotatef(fRotate, 0, 3.0f, 0); // Rotate around Y axis
Draw_Dest(); // Draw desk
glPopMatrix();

glPushMatrix();
glTranslatef(2.0f, 0.0f, -6.0f); // Place the dest right
glScalef(fScale, fScale, fScale); // Scale
Draw_Dest(); // Draw Draw desk
glPopMatrix();

fScale -= 0.005f;
fTranslate += 0.005f;
fRotate += 0.3f;

if (fTranslate > 0.5f) fTranslate = 0.0f;
if (fScale < 0.5f) fScale = 1.0f;

glutSwapBuffers();
}

void processNormalKeys(unsigned char key, int x, int y)
{

if (key == 27) //按ESC退出
exit(0);
}

int main (int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowSize(640,480);
glutCreateWindow("Exercise2");

glutDisplayFunc(redraw);
glutReshapeFunc(reshape);
glutIdleFunc(idle);

glutKeyboardFunc(processNormalKeys);

glutMainLoop();

return 0;
}


效果

img
img
<i class="fa fa-angle-left"></i>1…262728<i class="fa fa-angle-right"></i>

278 日志
29 标签
RSS
© 2025 LucienXian
由 Hexo 强力驱动
主题 - NexT.Pisces