2. 2009-05-15
INTRODUCTION
Sarah Lewthwaite, marketing director for Cineplex
Entertainment, was approached by chief executive
officer (CEO) Ellis Jacob in August 2006 to resume the
development of a loyalty program. The movie
industry yielded inconsistent revenues each year, and Jacob
wanted to increase and stabilize Cineplex’s
revenues. As chair of the Loyalty Steering Committee (the
committee), Lewthwaite was scheduled to
present her recommendations to the committee the following
week. She would need to make a persuasive
argument that included recommendations on program
development, the reward structure and the type of
promotional campaign that would be most effective under the
existing budget constraints. Finally, she
needed to suggest whether the program should launch regionally
or nationally. Her recommendations
would be reviewed by senior Cineplex executives to ensure that
the recommendations aligned with their
criteria.
CINEPLEX ENTERTAINMENT
Cineplex Entertainment (Cineplex) was founded in 1979 as a
small chain of movie theaters under the
Cineplex Odeon name. In 2003, under the direction of Onex
Corporation, a Canadian private equity firm
that held a major ownership claim in the company, Cineplex
merged with Galaxy Entertainment Inc.
(Galaxy). The CEO of Galaxy, Ellis Jacob, took over the newly
3. merged company. In late 2005, Cineplex
Galaxy acquired its largest competitor, Famous Players, and
became Cineplex Entertainment — Canada’s
largest film exhibitor. With a box-office market share of 64 per
cent, the chain enjoyed approximately 40
million visits per year under the Cineplex Odeon, Galaxy,
Famous Players and Cinema City brands.1
Cineplex’s corporate mission focused on offering movie-goers
“an exceptional entertainment experience.”
In addition to seeing a movie, customers could eat at various
branded concession counters or play in the
arcade. In 2005, Cineplex expanded its strategy to focus on
developing new markets, using the theaters’
1Cineplex Galaxy Income Fund 2005 Annual
Report,http://dplus.cineplexgalaxy.com/content/objects/Annual
%20Report%
202005.pdf, accessed January 3, 2008.
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 2 9B08A008
large screens to showcase live events, such as major hockey
games, wrestling matches and the
Metropolitan Opera. These events contributed greatly to
Cineplex’s success, which was measured
primarily on customer traffic and revenue per guest (RPG),
4. which was in turn composed of box-office and
concession revenues.
In 2005, weak box-office attendance throughout the movie
theater industry had affected Cineplex’s
operating performance (see Exhibit 1 for Cineplex’s income
statements for 2003, 2004 and 2005).
Following the acquisition of Famous Players in 2005, Cineplex
executives adjusted the pricing and
products in the food and beverage concessions in 2006. With
these moves, Cineplex was able to increase
its average box-office RPG to $7.73 and its average concession
RPG to $3.44 (see Exhibit 2).
A GROWTH OPPORTUNITY
Like the entire industry, Cineplex faced variable attendance
levels depending on the crop of new movies.
Additionally, RPG fluctuated based on the film genre. Cineplex
executives knew that audiences for action-
themed and children’s movies purchased a high volume of
concession items, which typically resulted in a
higher RPG than dramas. From these viewing patterns, Cineplex
executives were able to distinguish the
groups of customers that were particularly valuable. However,
with no actual link to individual customers,
they faced challenges targeting customers for specific movies
and special events. Although market
research was helpful on an aggregate level, Cineplex executives
wanted to link box-office and concession
purchases to a particular customer. Senior executives were
supportive of Lewthwaite and the committee
collecting this information through a customer relationship
management program.
5. FILM EXHIBITION
The first Canadian film screening took place in 1896, in
Montreal, Quebec, and the earliest cinema opened
in 1906.2 Attending the cinemas, also known as theaters,
became a popular social activity; by the 1930s, a
variety of independent and studio-owned theaters competed for
customer attention. In 1979, Canada’s first
18-theater multiplex opened in Toronto, Ontario, with several
other multiplexes following in subsequent
years. After a series of consolidations, by 2005, only three
major theater companies existed in the Canadian
movie and event exhibition market.
To showcase films, theaters required licensing from distributors
who purchased rights from the production
studios. The licensing agreement stipulated the “box-office
split,” also known as the percentage of
proceeds that the theater received from a given film over a
specified duration. Although both parties were
mutually dependent, distributors held the balance of power and
theaters relied heavily on concession
revenues, of which they retained 100 per cent of the receipts.
The margins on customers’ purchases of
concession treats and beverages were 65 per cent on average.3
Table 1 (below) shows one way of
characterizing the motivations and frequency of movie-going
behavior according to various age segments.
2Marcus Robinson, “A History of Film Exhibition in Canada,”
Playback: Canada's Broadcast and Production Journal (2005),
accessed December 30, 2007.
6. 3 Janet Wasko, How Hollywood Works, Sage Publications,
London, 2003.
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 3 9B08A008
Table 1
OBSERVATIONS ON THE MOTIVATIONS AND
FREQUENCY OF MOVIE ATTENDANCE BY AGE
Frequency,
reasons for
attendance*
Age Segment Labels
13-15 16-19 20-24 25-35 36-54 55+
“Teenagers” “Young
Adults”
“Young
Working”
“Young
Families”
7. “Older
Families”
“Retirees”
Low
(Special Events) X X
Medium (Special
Movies) X
High
(Routine) X X X
*These observations were drawn from an independent focus
group study conducted in 2003.
“Teenagers” — Teenagers use the movie theatre and arcade for
social gatherings because locations are
accessible and movie-viewing is considered by parents to be an
appropriate social activity. They are among
the highest frequency of visitors.
“Young Adults” — This segment has access to a variety of other
social venues because they can drive.
Some in this segment are still in high school and others are
post-secondary students; this segment visits
theatres with high frequency.
“Young Working” — This segment has disposable income and
they combine movies with socializing at
other venues such as bars and restaurants. This segment has a
high frequency of movie visits.
8. “Young Families” — This segment struggles to balance family
and work-related obligations; they take
their children to special movies occasionally.
“Older Families” — With a busy work and family life and
varying interests within the household, older
families attend theatres only for special events, and seldom
attend as a family unit.
“Retirees” — This segment has significant free time to attend
movies. They attend movies at a medium
frequency.
CUSTOMER RELATIONSHIP MANAGEMENT (CRM)
Customer relationship management (CRM) is a marketing
approach in which a company collects
individual purchasing information to improve its ability to
understand and respond to customer desires and
buying patterns. The information is typically stored in a central
database from which the company
managers can analyse trends and the purchasing behavior of
particular market segments. A better
understanding of customers enables organizations to develop
targeted campaigns to increase marketing
effectiveness, such as restructuring its products and services.
For Cineplex, a CRM program could also be
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
9. Page 4 9B08A008
used to share valuable information with concession suppliers
and movie distributors. Through the sharing
of this information, partners would be better able to develop
products for Cineplex’s customer base.
Although several mechanisms were available to collect
customer information, the most frequently used
systems were point-of-sale systems, which scanned barcodes on
wallet-sized cards or key chains. A recent
trend for CRM programs was to offer incentives such as
discounts or points that could be collected and
redeemed for merchandise in return for the customer’s
permission for the company to collect data on the
customer’s buying habits. Among the Canadian companies
following this trend were Shoppers Drug Mart
with the Optimum card program, Air Canada with the Aeroplan
rewards program and Office Depot and
Boston Pizza which both participated in the Flight Miles card
campaign.
CREATING LOYALTY
Even with 65 per cent market share in Canada, Cineplex had to
aggressively compete for customer
attention. Ongoing film piracy, rental movies, concerts and
sporting events, combined with inconsistent
box-office revenues encouraged Cineplex managers to explore
ways to increase customer spending and
frequency, particularly within the lucrative 16- to 24-year-old
segment. Before merging with Cineplex
10. Odeon, Galaxy Entertainment had established the Galaxy Elite
card, which offered customers the
opportunity to accumulate points toward free movie viewing.
Although the program had no CRM
capabilities, it had been successful in driving customer traffic.
During the merger with Cineplex, the
program had been disbanded and Galaxy’s customer traffic had
promptly waned. In a survey of Cineplex
customers in May and June 2005, 95 per cent of respondents
stated they were interested in joining a movie
rewards program (see Exhibit 3).
In 2004, a steering committee composed of different department
representatives was established to
investigate CRM opportunities for Cineplex. After being put on
hold during the acquisition of Famous
Players, the committee was anxious to move forward in
investigating a joint loyalty/CRM program. Senior
managers had several concerns, primarily regarding data control
and ownership, which would be relevant
if the program were disbanded. Another criterion concerned
resource requirements; a program this size
would be a costly investment and would likely require new
employees to manage it. Lewthwaite would
need to prove that it was a worthy financial investment. Finally,
the committee needed to consider the
length of time required to establish a new database because
most committee members believed that
conclusive information on customer behavior could be drawn
only from a minimum of 500,000 members.
Further, although they thought that an investment in such a
program could be largely beneficial for
Cineplex, if implemented poorly, the organization’s image and
its ability to deliver customer value could
suffer widespread harm. Lewthwaite knew that although the
following partner options might not meet all
11. the committee’s criteria, she had to evaluate the most important
considerations.
LOYALTY PARTNER OPTIONS
Internal Development
Under this option, Cineplex managers would develop and
operate the program; they would then know their
brand best and would have complete control over the direction
of the program and the data ownership.
However, the organization would incur the entire cost estimated
at $5.5 million in the first year with
diminishing costs in subsequent years. The company would also
be fully exposed to the financial risk of
unredeemed points and could face difficulty in divesting the
program if it proved unsuccessful; a new
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 5 9B08A008
department would need to be created to manage the exit of the
program. This option would also require a
new database, which, depending on promotional effectiveness,
could take several years to create. However,
because of the unlimited data access and control, this option
appealed to several members of the
12. committee.
Flight Miles Partnership
With 72 per cent of Canadian households as active members,
Flight Miles was the top Canadian loyalty
program.4 This program gave cardholders the opportunity to
earn leisure and travel rewards by purchasing
products at various retailers across the country. Flight Miles
executives viewed Cineplex as an opportunity
to increase its youth membership, and their executives
approached Cineplex executives to propose a
special joint program. In this program, traditional Flight Miles
cards would be used to collect points.
Supplementary key tags would be issued for movie customers
who opted to receive additional member
benefits and rewards. Although the key tags might confuse other
existing Flight Miles members, the
proposal seemed to offer numerous benefits to Cineplex,
including immediate entrance into a database of
seven million people. Cineplex would also have the opportunity
to access data from other Flight Miles
partners, which would be beneficial in targeting specific retail
buyers for niche films.
Lewthwaite estimated that access to the Flight Miles program
would cost Cineplex yearly fees of
approximately $5 million. Cineplex would also be required to
pay $0.09 for each point issued. Lewthwaite
thought users of the program would expect each movie
transaction to be worth a minimum of 10 Flight
Miles points. Cineplex would also be required to pay each time
it accessed the data, which Flight Miles
would own. A commitment of three years would be required,
and if Cineplex decided to leave the program,
13. it would lose all access to accumulated data. Lewthwaite
recognized that Cineplex would be required to
adhere to the partnership’s decisions; no easy out was available
if she did not like some aspect of the
program after they signed the deal. To make the proposal more
attractive, Flight Miles executives offered
to contribute $250,000 to launch a Cineplex-designed and -
initiated marketing campaign.
Scotiabank Proposal
Just as Lewthwaite and her committee sat down to examine the
two options in further detail, Scotiabank
executives approached Cineplex as a potential loyalty partner.
The bank had a relationship with Cineplex
derived from earlier corporate sponsorships. As one of the Big
Five banks in Canada, Scotiabank offered a
diverse range of financial services, including domestic banking,
wholesale banking and wealth
management. Through 950 branches, Scotiabank served
approximately 6.8 million Canadians in 2005.5
Because banks competed in an intensely competitive
marketplace, many banks aligned their brands with
sporting events, venues and other companies through corporate
sponsorship.
Scotiabank executives were interested in acquiring new youth
accounts and increasing overall transactions,
so they viewed a partnership with Cineplex as a means to
achieve their objectives while sharing financial
risk. Scotiabank, which had prior experience with data
management companies through its gold credit card
program, proposed 50-50 cost-sharing. In return for partnering
on the program, Scotiabank expected
naming rights on three major theaters and an exclusivity
14. agreement for Scotiabank bank machines in all
Cineplex theaters.
4 “Air Miles Rewards Program,”
http://www.loyalty.com/what/airmiles/index.html. accessed
November 2, 2007.
5 Scotiabank, 2005 Annual Report,
http://cgi.scotiabank.com/annrep2005/en/rbl_ov.html, accessed
February 10, 2008.
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 6 9B08A008
Scotiabank proposed a three-card rewards strategy. The basic
reward card would be Cineplex-branded and
used at theaters; the Scotiabank debit and credit cards would act
as reward accelerators that accumulated
additional points based on customers’ purchasing habits. Any
Scotiabank debit- or credit-card user enrolled
in this program would be issued the Cineplex card, and holders
of basic Cineplex theater cards would not
be required to open an account at Scotiabank.
Lewthwaite considered that the multiple card system might
discourage some customers who disliked
carrying additional cards. Secondly, because it would be a 50-
50 partnership, Cineplex’s decision-making
15. power would be constrained, and the direction of the program
would be subject to mutual agreement. Also,
owing to privacy laws, Cineplex executives would not be able to
access individual-level banking
information on the Scotiabank program users, data that might be
helpful in targeting specific retail
consumers. However, this program could be promoted in
theaters and bank branches across the country.
The costs to develop and maintain Cineplex’s portion of the
partnership were estimated to be $3 million,
$1.7 million and $1.9 million in years 1, 2 and 3 respectively.
Lewthwaite had to fully consider the potential benefits and
drawbacks of each proposal and weigh them
against Cineplex’s criteria before recommending which partner
to select. She also acknowledged other
options were available beyond those that were presented. She
knew that this decision could not be made
without analysing the potential reward structure of the program
because the committee would expect a
detailed net benefit analysis to support her recommendation.
STRUCTURING THE REWARD PROGRAM
Lewthwaite believed it was essential to create a program that
would appeal to customers. However,
creating a program with valuable and easy-to-gain rewards
might be too costly to carry out for an extended
period of time. If Cineplex went forward with the Flight Miles
partnership, an offer of 10 Flight Miles
points per transaction would be required to align with
cardholder expectations and could be supplemented
with Cineplex discounts. If Cineplex went forward with other
loyalty partnerships, it would have full
design control over the reward structure of the program. Points
16. could be earned based on box-office
transactions, concession transactions, or both. The points could
then be used towards movies and
concession items. Determining the number and value of points
to be given per transaction and the required
price per transaction were aspects that Lewthwaite needed to
determine. She also needed to decide on the
number of points required for particular rewards and whether
different reward levels should be created.
Among the other options, Cineplex could reward cardholders
with a permanent discount on theater tickets
or concession items (or both) or possibly provide first access to
special events. If Lewthwaite went forward
with free or discounted movies and concession items, she would
need to estimate the extent to which she
would be rewarding customers who would have attended
without being offered any rewards,6 the so-called
cannibalization rate (see Exhibit 4). To determine the other
potential revenues, Lewthwaite needed to
perform a sensitivity analysis around any increases in the
concession RPG, which she hoped might
increase by five to 15 per cent for loyalty program members.
She also had the option of charging a nominal
one-time or annual membership fee of $2 to $5. Finally, as with
any loyalty point program, Lewthwaite
knew that only 40 per cent of earned points would be redeemed
annually. She drafted a preliminary list of
four unique reward structures she thought could be effective,
but was unsure which, if any, would
maximize customer appeal through retail value while
minimizing costs (see Exhibit 5).
6 Cannibalization refers to the number of free visits redeemed
that would have been paid visits in the absence of a loyalty
17. program.
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 7 9B08A008
SELECTING THE DATABASE VENDOR
If a recommendation were made to move forward with program
development, the committee would need
to select a database vendor to manage customer data and the e-
communication site. This vendor would
need strong website design capabilities and a technology
platform that could collect a variety of data on
Cineplex’s customers. Because Canada had only a few such
vendors, Cineplex released a request for
proposal (RFP) to three major companies: Alpha, Kappa, and
Gamma. Each company responded with a
unique proposal for the project (see Exhibit 6).
THE MARKETING COMMUNICATIONS CAMPAIGN
Cineplex executives wanted to enroll 500,000 customers per
year for the first three years in any loyalty
program, After the first year, she believed the data bank would
be large enough to derive meaningful
customer information, and the organization could then focus on
customer retention. To meet these targets,
18. Cineplex would need to build substantial awareness of the
program, particularly in markets where the
Galaxy Elite card had previously existed. Launching the loyalty
card would also require a marketing
campaign to fit a variety of geographic markets, including
Quebec, a province whose official language was
French. Lewthwaite had a budget of $300,000, and she needed
to make some creative decisions, including
the name of the program, the marketing message to customers
and the media to be used to deliver the
message.
In-Theater Advertising
In 2005, Cineplex served 5.3 million unique visitors annually
with an average of 7.5 visits per guest. No
costs were associated with in-theater advertising, and
Lewthwaite knew it was an excellent way to reach
the market but she was unsure which media would be most
effective without overwhelming movie-goers.
The program could be promoted on concession products, point-
of-purchase displays, backlit posters or on
the website. The program could also be advertised to a captive
audience via the digital pre-show or during
the presentation of upcoming attractions.
Newspaper Advertising
Lewthwaite wondered whether the target market would respond
to regional newspaper advertisements. She
knew that the committee was opposed to advertising in a
national newspaper, such as the Globe and Mail,
because it did not have strong reach in every market in which
Cineplex operated. However, Cineplex was
19. accustomed to promoting events through half-page ads in
regional papers. Although this option would be
more costly than advertising solely in a national paper, several
more movie-going markets could be
reached. The average weekly cost per half-page ad in the small
to medium markets was $1,200, and $3,600
for larger markets, with a development cost of $850 for each
advertisement. If this option were selected,
Lewthwaite would need to determine in which papers to
advertise, and the message and frequency of the
insertions (see Exhibit 7).
Radio Advertising
Local radio advertisements could achieve significant coverage
in key markets across Canada. The average
weekly cost per 30-second commercial was $160 in small-and
medium-sized markets and $225 in larger
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 8 9B08A008
markets. Development of local radio ads would cost
approximately $1,100 per city. Because Cineplex had
used this medium for other events, particularly in rural theater
markets, Lewthwaite was confident
Cineplex could also negotiate free advertisement space on many
20. radio station’s websites.
Online Advertising
In addition to advertising on the Cineplex website, the program
could be promoted through various
websites, such as Google, Muchmusic.ca, MTV.ca and
canoe.qc.ca, a French-language news site. Costs
varied according to advertisement format and site (see Exhibit
8).
Grass Roots Initiatives
Lewthwaite had also considered smaller initiatives with the goal
of spreading word-of-mouth publicity.
Event teams could promote on college and university campuses
or at highly visited attractions, thereby
raising awareness for the program. Cineplex could also engage
in corporate sponsorships. She was unsure
what costs would be associated with these options.
LAUNCH
Launching the program was the final recommendation to be
made. Cineplex’s head office was located in
Toronto, Ontario, and the company operated in six provincial
markets — Quebec, Ontario, Manitoba,
Saskatchewan, Alberta and British Columbia — but none of the
four Atlantic provinces.
Lewthwaite would have to decide whether the program should
be launched regionally or across all six
provinces. In early 2006, Cineplex had completed the
21. installation of a new point-of-sale platform, which
had the technological capability to support a national loyalty
rollout. A national launch was appealing to
Lewthwaite because it would be cost-efficient and would accrue
revenues faster than a regional rollout.
However, it was also riskier than a regional rollout: any
problem would affect all markets. A regional
launch would give Cineplex the opportunity to resolve problems
before full implementation. The regional
rollout would be more expensive at completion, but it would
allow Cineplex to stretch funds over a longer
time period. If Lewthwaite recommended the regional option,
she would need to decide how the regional
launch would be phased in.
Lewthwaite knew several complex decisions needed to be made,
and she had little time before the steering
committee’s meeting the following week. Having a more
comprehensive understanding of customer
behavior and demographics was important in improving
Cineplex’s success, but could a loyalty program
be implemented in such a way to fit senior management’s
criteria? If she recommended going ahead with
the program, which loyalty partner should she use? How should
the rewards be structured and promoted?
What would the promotional campaign entail, and how should
the launch take place? As she leaned back
in her chair, she knew it was going to be a very long week.
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
22. Page 9 9B08A008
Exhibit 1
CINEPLEX ENTERTAINMENT INCOME STATEMENTS
2003–2005
(Cdn$ in Thousands)
2005 2004 2003
Total revenue 490,299 315,786 295,540
Cost of operations 421,529 248,818 242,636
Gross income 68,770 66,968 52,904
Amortization 42,948 22,530 18,404
Loss on debt 4,156 – –
Impairment on assets 4,296 – –
Loss (gain) on disposal
of assets
122 (111) (92)
Interest on long-term
debt
18,401 8,280 4,020
Interest on loan 14,000 14,000 1,381
Interest income (378) (473) (922)
Foreign exchange gain – – (3,696)
Income taxes (1,463) (1,149) 366
23. Income from
discontinued operations
28,116 6,357 6,184
Non-controlling interest 1,828 – 304
Net Income 12,976 30,248 39,323
Source: Cineplex Galaxy Income Fund 2005 Annual Report,
http://dplus.cineplexgalaxy.com/content/objects/annual%20repo
rt%202005.pdf, accessed January 3, 2008.
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 10 9B08A008
Exhibit 2
CINEPLEX ENTERTAINMENT ATTENDANCE AND
REVENUE PER GUEST DATA
2006E 2005 2004 2003
Attendance 61,000,000 39,945,000 28,096,000 27,073,000
24. Box office RPG - $7.73 $7.45 $7.28
Concession RPG - $3.44 $3.04 $2.91
Film cost as a per
cent of box-office
revenue
- 51.7% 51.6% 52.1%
Source: Cineplex Galaxy Income Fund 2005 Annual Report,
http://dplus.cineplexgalaxy.com/content/objects/annual%20repo
rt%202005.pdf, accessed January 3, 2008.
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 11 9B08A008
Exhibit 3
HIGHLIGHTS FROM CINEPLEX EMAIL SURVEY OF
CURRENT CUSTOMERS
Survey Period: May–June 17, 2005
Respondents: 4,261
25. • 95 per cent of respondents were interested in joining a
Cineplex Entertainment movie rewards program
• 87 per cent of respondents currently belonged to the Flight
Miles program, and 39 per cent identified
Flight Miles as their “favorite rewards program”
• 31 per cent of respondents were interested in the opportunity
to collect Aeroplan points
• 56 per cent of respondents indicated that they would be
interested in receiving a 10 per cent discount at
concessions
• The majority of respondents suggested that they would be
more inclined to join if there were no
additional card to carry
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 12 9B08A008
Exhibit 4
SUMMARY OF REVENUES AND CANNIBALIZATION
RATES
26. • Membership fee possibilities, a one-time fee of $2 to $5
• Increase in concession RPG of from 5 per cent to 15 per cent
• Net increase in attendance (actual incremental attendance
times 1- the estimated cannibalization rate)
• Cannibalization rate assumptions
Worst: 50 per cent
Most Likely: 25 per cent
Best: 12.5 per cent
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 13 9B08A008
Exhibit 5
PRELIMINARY REWARD STRUCTURE OPTIONS
Option 1 Option 2 Option 3 Option 4
Membership fee No One-time $2 Annually $5 No
Permanent concessions
discount – 10% 15% 10%
27. Points? Yes Yes No Yes
Sign-up points 500 100 – 250
Points per adult movie
transaction 100 100 – 100
Points per concession
combo transaction – 75 – –
Reward Items and Maximum Retail Value
Points Required
500
Free child
admission
$8.50
– – –
750
Free concession
combo
$12.37
– – –
1000
Free adult
admission
$10.95
Free adult
admission
$10.95
28. – Free adult admission $10.95
1500
Free event
admission1
$19.95
Free event
admission
$19.95
– Free adult admission/concession combo ($23.32)
2,000 – – – Free adult/2 children admission$27.95
2,500 –
Night out
package2
$37.47
– –
1 Includes admission to the following viewings: the
Metropolitan Opera, NHL series, or WWE series.
2 A Night out package includes two adult movie admissions,
two large sodas and one large popcorn.
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
29. Page 14 9B08A008
Exhibit 6
SUMMARY OF CINEPLEX’S REQUEST FOR PROPOSAL
PROGRAM OVERVIEW
Cineplex Entertainment is looking into the possibility of
creating a new entertainment-focused loyalty
program. Members will earn points that can be redeemed for
free movies or other entertainment-related
rewards. An ongoing marketing program requiring a member
database and website is required.
VENDORS TO PROVIDE
• A proposed approach and high level design concept for the
website that is creative and functional
• Pricing for the database and website build
WEBSITE GOALS
• Acquire new customers and deepen relationships with existing
customers by enticing them to sign up,
then encouraging them to remain active in the loyalty program
• Provide an easy way to sign up, check status of points earned,
get information on rewards that can be
earned, redeem points, and interact with other members
30. • The site will be a major marketing channel to reach members.
It will be used for viral and targeted
online promotions
• Provide an online community for members
DATABASE USE
• For program administration, analysis and reporting
• For analysis and reporting on moviegoer’s behavior and
preferences
• For marketing to customers
THE TARGET MARKET
• Is very comfortable with the online environment, text
messaging, downloading, and browsing
• Wants and expects discounts and free offers in an attainable
timeframe
• Wants simplicity and convenience
WEBSITE REQUIRES
• A public section accessible to all, a member’s section
accessible with member ID and password and an
administrative site to be used for customer support
• Site must connect to program database to collect, maintain,
retrieve and report member data including
demographic information and points data
• Integration with Cineplex’s POS equipment and mobile
channels for marketing
31. • Site will link to and from the sites of main partners and
vendors
• Site must be available in English and French
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 15 9B08A008
Exhibit 6 (continued)
VENDORS’ RESPONSES TO THE CINEPLEX REQUEST FOR
PROPOSAL
Alpha
Alpha was a leading marketing firm specializing in loyalty
programs and performance improvement. As a
global company, Alpha’s clients include American Express,
Coca-Cola, Hewlett-Packard, and Microsoft.
Alpha has served the Canadian marketplace since 1980, and its
focus is helping organizations identify,
retain, and build customer relationships in order to maximize
profit and drive long-term success. With a
history of designing and implementing loyalty programs,
Alpha’s technology platforms focused on
customer behavior tracking and loyalty rewards fulfillment. In
preparing its response, Alpha held focus
groups to help determine what type of website appealed to
Cineplex’s target market. These groups
32. indicated the importance of security, easy navigation, and
keeping site content up-to-date; they also spoke
out against pop-up advertisements. All respondents were
familiar with e-newsletters, and noted that loyalty
members should have the option to opt in, because they do not
want to be overwhelmed with promotional
messages. Alpha used this information in conjunction with
Cineplex’s specifications to present how the
website would be designed. The approximate investment cost
for the program design was $500,000 with
$40,000 per month required for website upkeep.
Kappa
Known for managing data for the Royal Bank of Canada, Kappa
was one of the largest global marketing
agencies. With a strong focus on customer loyalty programs,
Kappa offered a high standard in data privacy
and security and was the undisputed industry leader in mobile
marketing, which linked strongly to
Cineplex’s target market. The Kappa proposal focused on
creating a youth-driven brand identity that
engaged viewers to join the program through program incentives
and links to third-party social networking
sites, such as MySpace. With a significant portfolio of
integrated loyalty program solutions, Kappa also
had entertainment industry experience, having previously
worked on technology platforms with Famous
Players, the Toronto International Film Festival and IMAX.
Kappa’s main differentiating factor was its
proposal to have two distinct sites, one for members and one for
non-members. Although similar in nature,
one site would focus on member acquisition and program
information while the other would focus on
member retention through contest promotions and access to
33. personal account activity. Approximate costs
would be $1 million.
Gamma
Gamma, a competitor in the Canadian marketplace for four
years, had vast experience in information
technology strategy and a track record of developing CRM
programs for leading organizations, such as
Kaplan University and Citi Financial. Gamma’s response to the
RFP included a proposal to plan, design,
and manage Cineplex’s marketing and technology programs on
its specialized marketing platform that
supported all aspects of email management and e-
communication campaigns. This platform would also
enable Cineplex to track members on an ongoing basis through
different promotional mediums, such as
web advertisements and search functions, and to respond
instantly to member behavior through messaging
for those leaving the site. Gamma’s offer was appealing because
it included a fixed-price, fixed-time
model. Gamma was unable to provide costs for data
management because it was unsure of Cineplex’s
technical capabilities, but preliminary planning and design costs
were estimated at around $200,000.
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 16 9B08A008
35. Market Newspaper Radio
Calgary Calgary Herald VIBE 98.5
Edmonton Edmonton Journal Sonic 102.9
Montreal Montreal Gazette Q92
Ottawa Ottawa Citizen BOB FM
Toronto Toronto Star Mix 99.9
Vancouver Vancouver Sun Z95 FM
Market Newspaper Radio
Barrie Barrie Examiner Rock 95 FM
Cornwall Standard Freeholder Rock 101.9
Guelph Guelph Mercury Magic FM
Kitchener Kitchener Record KOOL FM
London London Free Press Fresh FM
North Bay North Bay Nugget EZ Rock
Owen Sound Owen Sound Sun Times Mix 106
Quebec City Quebec City Journale Le 93.3
Regina Regina Leader Post Z-99
Saskatoon The Star Phoenix C95
Sault Ste. Marie Sault Ste. Marie Star EZ Rock 100.5
St. Thomas St. Thomas Times-Journal Fresh FM
Sudbury Sudbury Star Big Daddy 103.9 FM
Thunderbay Chronicle Journal Rock 94
36. Windsor Windsor Star 89X
Winnipeg Winnipeg Free Press Q94
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
[email protected] or 800-988-0886 for additional copies.
Page 17 9B08A008
Exhibit 8
COST PER THOUSAND IMPRESSIONS
(in Cdn$)
Website Big Box Advertisement Banner Advertisement
google.ca 20 12
mtv.ca 27 35
muchmusic.ca 29 32
yahoo.ca 19 13
imdb.com 17 9
canoe.qc.ca 26 –
This document is authorized for use only by Pavan Teja
Kondisetti ([email protected]). Copying or posting is an
infringement of copyright. Please contact
37. [email protected] or 800-988-0886 for additional copies.
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define _USE_MATH_DEFINES
#include <math.h>
#ifdef WIN32
#include <windows.h>
#pragma warning(disable:4996)
#include "glew.h"
#endif
#include <GL/gl.h>
#include <GL/glu.h>
#include "glut.h"
// This is a sample OpenGL / GLUT program
//
// The objective is to draw a 3d object and change the color
of the axes
// with a glut menu
//
// The left mouse button does rotation
// The middle mouse button does scaling
// The user interface allows:
// 1. The axes to be turned on and off
// 2. The color of the axes to be changed
// 3. Debugging to be turned on and off
// 4. Depth cueing to be turned on and off
// 5. The projection to be changed
// 6. The transformations to be reset
38. // 7. The program to quit
//
// Author: Joe Graphics
// NOTE: There are a lot of good reasons to use const variables
instead
// of #define's. However, Visual C++ does not allow a const
variable
// to be used as an array size or as the case in a switch( )
statement. So in
// the following, all constants are const variables except those
which need to
// be array sizes or cases in switch( ) statements. Those are
#defines.
// title of these windows:
const char *WINDOWTITLE = { "OpenGL / GLUT Sample --
Joe Graphics" };
const char *GLUITITLE = { "User Interface Window" };
// what the glui package defines as true and false:
const int GLUITRUE = { true };
const int GLUIFALSE = { false };
// the escape key:
#define ESCAPE 0x1b
//animation
39. #define MS_PER_CYCLE 2048
float Time;
#define NUMCURVES 5
#define NUMPOINTS 20
#define M_PI 3.14159
// initial window size:
const int INIT_WINDOW_SIZE = { 600 };
// size of the box:
const float BOXSIZE = { 2.f };
// multiplication factors for input interaction:
// (these are known from previous experience)
const float ANGFACT = { 1. };
const float SCLFACT = { 0.005f };
// minimum allowable scale factor:
const float MINSCALE = { 0.05f };
// active mouse buttons (or them together):
40. const int LEFT = { 4 };
const int MIDDLE = { 2 };
const int RIGHT = { 1 };
// which projection:
enum Projections
{
ORTHO,
PERSP
};
// which button:
enum ButtonVals
{
RESET,
QUIT
};
// window background color (rgba):
const GLfloat BACKCOLOR[ ] = { 0., 0., 0., 1. };
// line width for the axes:
const GLfloat AXES_WIDTH = { 3. };
// the color numbers:
// this order must match the radio button order
41. enum Colors
{
RED,
YELLOW,
GREEN,
CYAN,
BLUE,
MAGENTA,
WHITE,
BLACK
};
char * ColorNames[ ] =
{
"Red",
"Yellow",
"Green",
"Cyan",
"Blue",
"Magenta",
"White",
"Black"
};
// the color definitions:
// this order must match the menu order
const GLfloat Colors[ ][3] =
{
{ 1., 0., 0. }, // red
{ 1., 1., 0. }, // yellow
{ 0., 1., 0. }, // green
{ 0., 1., 1. }, // cyan
{ 0., 0., 1. }, // blue
{ 1., 0., 1. }, // magenta
42. { 1., 1., 1. }, // white
{ 0., 0., 0. }, // black
};
// fog parameters:
const GLfloat FOGCOLOR[4] = { .0, .0, .0, 1. };
const GLenum FOGMODE = { GL_LINEAR };
const GLfloat FOGDENSITY = { 0.30f };
const GLfloat FOGSTART = { 1.5 };
const GLfloat FOGEND = { 4. };
// non-constant global variables:
int ActiveButton; // current button that is
down
GLuint AxesList; // list to hold the axes
int AxesOn; // != 0 means to draw
the axes
int DebugOn; // != 0 means to print
debugging info
int DepthCueOn; // != 0 means to use
intensity depth cueing
int DepthBufferOn; // != 0 means to use
the z-buffer
int DepthFightingOn; // != 0 means to use the z-
buffer
GLuint BoxList; // object display list
int MainWindow; // window id for main
graphics window
float Scale; // scaling factor
int WhichColor; // index into Colors[ ]
int WhichProjection; // ORTHO or PERSP
int Xmouse, Ymouse; // mouse values
44. struct Curve
{
float r, g, b;
Point p0, p1, p2, p3;
void color(float r, float g, float b) {
this->r = r;
this->g = g;
this->b = b;
}
void reset() {
p0.reset();
p1.reset();
p2.reset();
p3.reset();
}
};
Curve Curves[NUMCURVES]; // if you are creating a
pattern of curves
Curve Stem; // if you are not
45. Curve line1, line2, line3, line4, line5, line6, line7, line8, line9,
line10, line11, line12;
float *
Array3(float a, float b, float c)
{
static float array[4];
array[0] = a;
array[1] = b;
array[2] = c;
array[3] = 1.;
return array;
}
// utility to create an array from a multiplier and an array:
float *
MulArray3(float factor, float array0[3])
{
static float array[4];
array[0] = factor * array0[0];
46. array[1] = factor * array0[1];
array[2] = factor * array0[2];
array[3] = 1.;
return array;
}
// function prototypes:
void Animate( );
void Display( );
void DoAxesMenu( int );
void DoColorMenu( int );
void DoDepthBufferMenu( int );
void DoDepthFightingMenu( int );
void DoDepthMenu( int );
void DoDebugMenu( int );
void DoMainMenu( int );
void DoProjectMenu( int );
void DoRasterString( float, float, float, char * );
void DoStrokeString( float, float, float, float, char * );
float ElapsedSeconds( );
void InitGraphics( );
void InitLists( );
void InitMenus( );
void Keyboard( unsigned char, int, int );
void MouseButton( int, int, int, int );
void MouseMotion( int, int );
void Reset( );
void Resize( int, int );
void Visibility( int );
47. void Axes( float );
void HsvRgb( float[3], float [3] );
void RotateX(Point *p, float deg, float xc, float yc, float zc);
void RotateY(Point *p, float deg, float xc, float yc, float zc);
void RotateZ(Point *p, float deg, float xc, float yc, float zc);
void CreateBezierCur(Curve curve, GLfloat width);
void setanimatedcoor(Curve* curve);
// main program:
int
main( int argc, char *argv[ ] )
{
// turn on the glut package:
// (do this before checking argc and argv since it might
// pull some command line arguments out)
glutInit( &argc, argv );
// setup all the graphics stuff:
InitGraphics( );
// create the display structures that will not change:
InitLists( );
// init all the global variables used by Display( ):
// this will also post a redisplay
Reset( );
48. // setup all the user interface stuff:
InitMenus( );
// draw the scene once and wait for some interaction:
// (this will never return)
glutSetWindow( MainWindow );
glutMainLoop( );
// this is here to make the compiler happy:
return 0;
}
// this is where one would put code that is to be called
// everytime the glut main loop has nothing to do
//
// this is typically where animation parameters are set
//
// do not call Display( ) from here -- let glutMainLoop( ) do it
void
Animate( )
{
// put animation stuff in here -- change some global
variables
// for Display( ) to find:
// force a call to Display( ) next time it is convenient:
int ms = glutGet(GLUT_ELAPSED_TIME);
49. ms %= MS_PER_CYCLE;
Time = (float)ms / (float)MS_PER_CYCLE; //
[0.,1.)
glutSetWindow( MainWindow );
glutPostRedisplay( );
}
// draw the complete scene:
void
Display( )
{
if( DebugOn != 0 )
{
fprintf( stderr, "Displayn" );
}
printf("%f", Time);
// set which window we want to do the graphics into:
glutSetWindow( MainWindow );
// erase the background:
glDrawBuffer( GL_BACK );
glClear( GL_COLOR_BUFFER_BIT |
50. GL_DEPTH_BUFFER_BIT );
if( DepthBufferOn != 0 )
glEnable( GL_DEPTH_TEST );
else
glDisable( GL_DEPTH_TEST );
// specify shading to be flat:
glShadeModel( GL_FLAT );
// set the viewport to a square centered in the window:
GLsizei vx = glutGet( GLUT_WINDOW_WIDTH );
GLsizei vy = glutGet( GLUT_WINDOW_HEIGHT );
GLsizei v = vx < vy ? vx : vy; // minimum
dimension
GLint xl = ( vx - v ) / 2;
GLint yb = ( vy - v ) / 2;
glViewport( xl, yb, v, v );
// set the viewing volume:
// remember that the Z clipping values are actually
// given as DISTANCES IN FRONT OF THE EYE
// USE gluOrtho2D( ) IF YOU ARE DOING 2D !
glMatrixMode( GL_PROJECTION );
glLoadIdentity( );
if( WhichProjection == ORTHO )
glOrtho( -3., 3., -3., 3., 0.1, 1000. );
else
gluPerspective( 90., 1., 0.1, 1000. );
51. // place the objects into the scene:
glMatrixMode( GL_MODELVIEW );
glLoadIdentity( );
// set the eye position, look-at position, and up-vector:
gluLookAt( 0., 0., 5., 0., 0., 0., 0., 1., 0. );
// rotate the scene:
glRotatef( (GLfloat)Yrot, 0., 1., 0. );
glRotatef( (GLfloat)Xrot, 1., 0., 0. );
// uniformly scale the scene:
if( Scale < MINSCALE )
Scale = MINSCALE;
glScalef( (GLfloat)Scale, (GLfloat)Scale, (GLfloat)Scale );
// set the fog parameters:
if( DepthCueOn != 0 )
{
glFogi( GL_FOG_MODE, FOGMODE );
glFogfv( GL_FOG_COLOR, FOGCOLOR );
glFogf( GL_FOG_DENSITY, FOGDENSITY );
glFogf( GL_FOG_START, FOGSTART );
glFogf( GL_FOG_END, FOGEND );
glEnable( GL_FOG );
}
52. else
{
glDisable( GL_FOG );
}
// possibly draw the axes:
if( AxesOn != 0 )
{
glColor3fv( &Colors[WhichColor][0] );
glCallList( AxesList );
}
// since we are using glScalef( ), be sure normals get
unitized:
glEnable( GL_NORMALIZE );
glLightfv(GL_LIGHT0, GL_POSITION, Array3(0, 15,
15));
line1.color(0., 1., 1.);
line1.p0.setPt(-1, 1, 0);
line1.p1.setPt(-1.5 - (Time), .5, 0);
line1.p2.setPt(-1.5 - (Time), -.5, 0);
line1.p3.setPt(-1, -1, 0);
line1.reset();
59. // draw some gratuitous text that is fixed on the screen:
//
// the projection matrix is reset to define a scene whose
// world coordinate system goes from 0-100 in each axis
//
// this is called "percent units", and is just a convenience
//
// the modelview matrix is reset to identity as we don't
// want to transform these coordinates
// swap the double-buffered framebuffers:
glutSwapBuffers( );
// be sure the graphics buffer has been sent:
// note: be sure to use glFlush( ) here, not glFinish( ) !
glFlush( );
}
void
DoAxesMenu( int id )
{
AxesOn = id;
glutSetWindow( MainWindow );
glutPostRedisplay( );
}
void
60. DoColorMenu( int id )
{
WhichColor = id - RED;
glutSetWindow( MainWindow );
glutPostRedisplay( );
}
void
DoDebugMenu( int id )
{
DebugOn = id;
glutSetWindow( MainWindow );
glutPostRedisplay( );
}
void
DoDepthBufferMenu( int id )
{
DepthBufferOn = id;
glutSetWindow( MainWindow );
glutPostRedisplay( );
}
void
DoDepthFightingMenu( int id )
{
DepthFightingOn = id;
glutSetWindow( MainWindow );
glutPostRedisplay( );
61. }
void
DoDepthMenu( int id )
{
DepthCueOn = id;
glutSetWindow( MainWindow );
glutPostRedisplay( );
}
// main menu callback:
void
DoMainMenu( int id )
{
switch( id )
{
case RESET:
Reset( );
break;
case QUIT:
// gracefully close out the graphics:
// gracefully close the graphics window:
// gracefully exit the program:
glutSetWindow( MainWindow );
glFinish( );
glutDestroyWindow( MainWindow );
exit( 0 );
break;
default:
fprintf( stderr, "Don't know what to do with
62. Main Menu ID %dn", id );
}
glutSetWindow( MainWindow );
glutPostRedisplay( );
}
void
DoProjectMenu( int id )
{
WhichProjection = id;
glutSetWindow( MainWindow );
glutPostRedisplay( );
}
// use glut to display a string of characters using a raster font:
void
DoRasterString( float x, float y, float z, char *s )
{
glRasterPos3f( (GLfloat)x, (GLfloat)y, (GLfloat)z );
char c; // one character to print
for( ; ( c = *s ) != '0'; s++ )
{
glutBitmapCharacter(
GLUT_BITMAP_TIMES_ROMAN_24, c );
}
}
// use glut to display a string of characters using a stroke font:
63. void
DoStrokeString( float x, float y, float z, float ht, char *s )
{
glPushMatrix( );
glTranslatef( (GLfloat)x, (GLfloat)y, (GLfloat)z );
float sf = ht / ( 119.05f + 33.33f );
glScalef( (GLfloat)sf, (GLfloat)sf, (GLfloat)sf );
char c; // one character to print
for( ; ( c = *s ) != '0'; s++ )
{
glutStrokeCharacter( GLUT_STROKE_ROMAN,
c );
}
glPopMatrix( );
}
// return the number of seconds since the start of the program:
float
ElapsedSeconds( )
{
// get # of milliseconds since the start of the program:
int ms = glutGet( GLUT_ELAPSED_TIME );
// convert it to seconds:
return (float)ms / 1000.f;
}
// initialize the glui window:
void
InitMenus( )
65. int mainmenu = glutCreateMenu( DoMainMenu );
glutAddSubMenu( "Axes", axesmenu);
glutAddSubMenu( "Colors", colormenu);
glutAddSubMenu( "Depth Buffer", depthbuffermenu);
glutAddSubMenu( "Depth Fighting",depthfightingmenu);
glutAddSubMenu( "Depth Cue", depthcuemenu);
glutAddSubMenu( "Projection", projmenu );
glutAddMenuEntry( "Reset", RESET );
glutAddSubMenu( "Debug", debugmenu);
glutAddMenuEntry( "Quit", QUIT );
// attach the pop-up menu to the right mouse button:
glutAttachMenu( GLUT_RIGHT_BUTTON );
}
// initialize the glut and OpenGL libraries:
// also setup display lists and callback functions
void
InitGraphics( )
{
// request the display modes:
// ask for red-green-blue-alpha color, double-buffering,
and z-buffering:
glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE |
GLUT_DEPTH );
// set the initial window configuration:
glutInitWindowPosition( 0, 0 );
glutInitWindowSize( INIT_WINDOW_SIZE,
INIT_WINDOW_SIZE );
66. // open the window and set its title:
MainWindow = glutCreateWindow( WINDOWTITLE );
glutSetWindowTitle( WINDOWTITLE );
// set the framebuffer clear values:
glClearColor( BACKCOLOR[0], BACKCOLOR[1],
BACKCOLOR[2], BACKCOLOR[3] );
// setup the callback functions:
// DisplayFunc -- redraw the window
// ReshapeFunc -- handle the user resizing the window
// KeyboardFunc -- handle a keyboard input
// MouseFunc -- handle the mouse button going down or up
// MotionFunc -- handle the mouse moving with a button
down
// PassiveMotionFunc -- handle the mouse moving with a
button up
// VisibilityFunc -- handle a change in window visibility
// EntryFunc -- handle the cursor entering or leaving the
window
// SpecialFunc -- handle special keys on the keyboard
// SpaceballMotionFunc -- handle spaceball translation
// SpaceballRotateFunc -- handle spaceball rotation
// SpaceballButtonFunc -- handle spaceball button hits
// ButtonBoxFunc -- handle button box hits
// DialsFunc -- handle dial rotations
// TabletMotionFunc -- handle digitizing tablet motion
// TabletButtonFunc -- handle digitizing tablet button hits
// MenuStateFunc -- declare when a pop-up menu is in use
// TimerFunc -- trigger something to happen a certain time
from now
// IdleFunc -- what to do when nothing else is going on
68. }
// initialize the display lists that will not change:
// (a display list is a way to store opengl commands in
// memory so that they can be played back efficiently at a later
time
// with a call to glCallList( )
void
InitLists( )
{
float dx = BOXSIZE / 2.f;
float dy = BOXSIZE / 2.f;
float dz = BOXSIZE / 2.f;
glutSetWindow( MainWindow );
// create the object:
BoxList = glGenLists( 1 );
glNewList( BoxList, GL_COMPILE );
glBegin( GL_QUADS );
glColor3f( 0., 0., 1. );
glNormal3f( 0., 0., 1. );
glVertex3f( -dx, -dy, dz );
glVertex3f( dx, -dy, dz );
glVertex3f( dx, dy, dz );
glVertex3f( -dx, dy, dz );
glNormal3f( 0., 0., -1. );
glTexCoord2f( 0., 0. );
glVertex3f( -dx, -dy, -dz );
glTexCoord2f( 0., 1. );
glVertex3f( -dx, dy, -dz );
70. // create the axes:
AxesList = glGenLists( 1 );
glNewList( AxesList, GL_COMPILE );
glLineWidth( AXES_WIDTH );
Axes( 1.5 );
glLineWidth( 1. );
glEndList( );
}
// the keyboard callback:
void
Keyboard( unsigned char c, int x, int y )
{
if( DebugOn != 0 )
fprintf( stderr, "Keyboard: '%c' (0x%0x)n", c, c );
switch( c )
{
case 'o':
case 'O':
WhichProjection = ORTHO;
break;
case 'p':
case 'P':
WhichProjection = PERSP;
break;
case 'q':
case 'Q':
case ESCAPE:
DoMainMenu( QUIT ); // will not return here
break; // happy compiler
71. case '1':
LineOn = !LineOn;
break;
//light
case '2':
PointOn = !PointOn;
break;
default:
fprintf( stderr, "Don't know what to do with
keyboard hit: '%c' (0x%0x)n", c, c );
}
// force a call to Display( ):
glutSetWindow( MainWindow );
glutPostRedisplay( );
}
// called when the mouse button transitions down or up:
void
MouseButton( int button, int state, int x, int y )
{
int b = 0; // LEFT, MIDDLE, or RIGHT
if( DebugOn != 0 )
fprintf( stderr, "MouseButton: %d, %d, %d, %dn",
72. button, state, x, y );
// get the proper button bit mask:
switch( button )
{
case GLUT_LEFT_BUTTON:
b = LEFT; break;
case GLUT_MIDDLE_BUTTON:
b = MIDDLE; break;
case GLUT_RIGHT_BUTTON:
b = RIGHT; break;
default:
b = 0;
fprintf( stderr, "Unknown mouse button: %dn",
button );
}
// button down sets the bit, up clears the bit:
if( state == GLUT_DOWN )
{
Xmouse = x;
Ymouse = y;
ActiveButton |= b; // set the proper bit
}
else
{
ActiveButton &= ~b; // clear the proper bit
}
}
73. // called when the mouse moves while a button is down:
void
MouseMotion( int x, int y )
{
if( DebugOn != 0 )
fprintf( stderr, "MouseMotion: %d, %dn", x, y );
int dx = x - Xmouse; // change in mouse coords
int dy = y - Ymouse;
if( ( ActiveButton & LEFT ) != 0 )
{
Xrot += ( ANGFACT*dy );
Yrot += ( ANGFACT*dx );
}
if( ( ActiveButton & MIDDLE ) != 0 )
{
Scale += SCLFACT * (float) ( dx - dy );
// keep object from turning inside-out or
disappearing:
if( Scale < MINSCALE )
Scale = MINSCALE;
}
Xmouse = x; // new current position
Ymouse = y;
glutSetWindow( MainWindow );
74. glutPostRedisplay( );
}
// reset the transformations and the colors:
// this only sets the global variables --
// the glut main loop is responsible for redrawing the scene
void
Reset( )
{
ActiveButton = 0;
AxesOn = 1;
DebugOn = 0;
DepthBufferOn = 1;
DepthFightingOn = 0;
DepthCueOn = 0;
Scale = 1.0;
WhichColor = WHITE;
WhichProjection = PERSP;
Xrot = Yrot = 0.;
}
// called when user resizes the window:
void
Resize( int width, int height )
{
if( DebugOn != 0 )
fprintf( stderr, "ReSize: %d, %dn", width, height );
// don't really need to do anything since window size is
// checked each time in Display( ):
glutSetWindow( MainWindow );
75. glutPostRedisplay( );
}
// handle a change to the window's visibility:
void
Visibility ( int state )
{
if( DebugOn != 0 )
fprintf( stderr, "Visibility: %dn", state );
if( state == GLUT_VISIBLE )
{
glutSetWindow( MainWindow );
glutPostRedisplay( );
}
else
{
// could optimize by keeping track of the fact
// that the window is not visible and avoid
// animating or redrawing it ...
}
}
/////////////////////////////////////// HANDY UTILITIES:
//////////////////////////
// the stroke characters 'X' 'Y' 'Z' :
static float xx[ ] = {
0.f, 1.f, 0.f, 1.f
};
77. // fraction of length to use as start location of the characters:
const float BASEFRAC = 1.10f;
// Draw a set of 3D axes:
// (length is the axis length in world coordinates)
void
Axes( float length )
{
glBegin( GL_LINE_STRIP );
glVertex3f( length, 0., 0. );
glVertex3f( 0., 0., 0. );
glVertex3f( 0., length, 0. );
glEnd( );
glBegin( GL_LINE_STRIP );
glVertex3f( 0., 0., 0. );
glVertex3f( 0., 0., length );
glEnd( );
float fact = LENFRAC * length;
float base = BASEFRAC * length;
glBegin( GL_LINE_STRIP );
for( int i = 0; i < 4; i++ )
{
int j = xorder[i];
if( j < 0 )
{
glEnd( );
glBegin( GL_LINE_STRIP );
j = -j;
}
j--;
glVertex3f( base + fact*xx[j], fact*xy[j], 0.0 );
}
78. glEnd( );
glBegin( GL_LINE_STRIP );
for( int i = 0; i < 5; i++ )
{
int j = yorder[i];
if( j < 0 )
{
glEnd( );
glBegin( GL_LINE_STRIP );
j = -j;
}
j--;
glVertex3f( fact*yx[j], base + fact*yy[j], 0.0 );
}
glEnd( );
glBegin( GL_LINE_STRIP );
for( int i = 0; i < 6; i++ )
{
int j = zorder[i];
if( j < 0 )
{
glEnd( );
glBegin( GL_LINE_STRIP );
j = -j;
}
j--;
glVertex3f( 0.0, fact*zy[j], base + fact*zx[j] );
}
glEnd( );
}
79. // function to convert HSV to RGB
// 0. <= s, v, r, g, b <= 1.
// 0. <= h <= 360.
// when this returns, call:
// glColor3fv( rgb );
void
HsvRgb( float hsv[3], float rgb[3] )
{
// guarantee valid input:
float h = hsv[0] / 60.f;
while( h >= 6. ) h -= 6.;
while( h < 0. ) h += 6.;
float s = hsv[1];
if( s < 0. )
s = 0.;
if( s > 1. )
s = 1.;
float v = hsv[2];
if( v < 0. )
v = 0.;
if( v > 1. )
v = 1.;
// if sat==0, then is a gray:
if( s == 0.0 )
{
rgb[0] = rgb[1] = rgb[2] = v;
return;
}
80. // get an rgb from the hue itself:
float i = floor( h );
float f = h - i;
float p = v * ( 1.f - s );
float q = v * ( 1.f - s*f );
float t = v * ( 1.f - ( s * (1.f-f) ) );
float r, g, b; // red, green, blue
switch( (int) i )
{
case 0:
r = v; g = t; b = p;
break;
case 1:
r = q; g = v; b = p;
break;
case 2:
r = p; g = v; b = t;
break;
case 3:
r = p; g = q; b = v;
break;
case 4:
r = t;g = p; b = v;
break;
case 5:
r = v; g = p; b = q;
break;
}
86. #pragma warning(disable:4996)
#include "glew.h"
#endif
#include <GL/gl.h>
#include <GL/glu.h>
#include "glut.h"
// This is a sample OpenGL / GLUT program
//
// The objective is to draw a 3d object and change the color
of the axes
// with a glut menu
//
// The left mouse button does rotation
// The middle mouse button does scaling
// The user interface allows:
87. // 1. The axes to be turned on and off
// 2. The color of the axes to be changed
// 3. Debugging to be turned on and off
// 4. Depth cueing to be turned on and off
// 5. The projection to be changed
// 6. The transformations to be reset
// 7. The program to quit
//
// Author: Joe Graphics
// NOTE: There are a lot of good reasons to use const variables
instead
// of #define's. However, Visual C++ does not allow a const
variable
// to be used as an array size or as the case in a switch( )
statement. So in
// the following, all constants are const variables except those
which need to
// be array sizes or cases in switch( ) statements. Those are
#defines.
88. // title of these windows:
const char *WINDOWTITLE = { "OpenGL / GLUT Sample --
Joe Graphics" };
const char *GLUITITLE = { "User Interface Window" };
// what the glui package defines as true and false:
const int GLUITRUE = { true };
const int GLUIFALSE = { false };
// the escape key:
#define ESCAPE 0x1b
89. // initial window size:
const int INIT_WINDOW_SIZE = { 600 };
// size of the box:
const float BOXSIZE = { 2.f };
// multiplication factors for input interaction:
// (these are known from previous experience)
const float ANGFACT = { 1. };
const float SCLFACT = { 0.005f };
90. // minimum allowable scale factor:
const float MINSCALE = { 0.05f };
// active mouse buttons (or them together):
const int LEFT = { 4 };
const int MIDDLE = { 2 };
const int RIGHT = { 1 };
// which projection:
enum Projections
{
ORTHO,
92. // line width for the axes:
const GLfloat AXES_WIDTH = { 3. };
// the color numbers:
// this order must match the radio button order
enum Colors
{
RED,
YELLOW,
GREEN,
CYAN,
BLUE,
MAGENTA,
WHITE,
BLACK
93. };
char * ColorNames[] =
{
"Red",
"Yellow",
"Green",
"Cyan",
"Blue",
"Magenta",
"White",
"Black"
};
// the color definitions:
// this order must match the menu order
95. const GLfloat FOGSTART = { 1.5 };
const GLfloat FOGEND = { 4. };
// non-constant global variables:
int ActiveButton; // current button that is
down
GLuint AxesList; // list to hold the axes
int AxesOn; // != 0 means to draw
the axes
int DebugOn; // != 0 means to print
debugging info
int DepthCueOn; // != 0 means to use
intensity depth cueing
int DepthBufferOn; // != 0 means to use
the z-buffer
int DepthFightingOn; // != 0 means to use the z-
buffer
GLuint BoxList; // object display list
int MainWindow; // window id for main
graphics window
96. float Scale; // scaling factor
int WhichColor; // index into Colors[ ]
int WhichProjection; // ORTHO or PERSP
int Xmouse, Ymouse; // mouse values
float Xrot, Yrot; // rotation angles in degrees
float Time;
bool Light0On = true;
bool Light1On = true;
bool Light2On = true;
bool Frozen = true;
#define MS_PER_CYCLE 4096
float texdistort;
GLuint tex01;
bool Distort;
int Distorton;
int bmp_width = 1024;
int bmp_height = 512;
int ReadInt(FILE*);
98. int biCompression;
int biSizeImage;
int biXPelsPerMeter;
int biYPelsPerMeter;
int biClrUsed;
int biClrImportant;
} InfoHeader;
struct point {
float x, y, z; // coordinates
float nx, ny, nz; // surface normal
float s, t; // texture coords
};
int NumLngs, NumLats;
struct point* Pts;
struct point*
PtsPointer(int lat, int lng)
{
99. if (lat < 0) lat += (NumLats - 1);
if (lng < 0) lng += (NumLngs - 1);
if (lat > NumLats - 1) lat -= (NumLats - 1);
if (lng > NumLngs - 1) lng -= (NumLngs - 1);
return &Pts[NumLngs * lat + lng];
}
float Gray[] = { 0.5,0.5,0.5,1. };
float White[] = { 1.,1.,1.,1. };
float*
Array3(float a, float b, float c)
{
static float array[4];
array[0] = a;
array[1] = b;
array[2] = c;
array[3] = 1.;
106. // turn on the glut package:
// (do this before checking argc and argv since it might
// pull some command line arguments out)
glutInit(&argc, argv);
// setup all the graphics stuff:
InitGraphics();
// create the display structures that will not change:
InitLists();
// init all the global variables used by Display( ):
107. // this will also post a redisplay
Reset();
// setup all the user interface stuff:
InitMenus();
// draw the scene once and wait for some interaction:
// (this will never return)
glutSetWindow(MainWindow);
glutMainLoop();
// this is here to make the compiler happy:
108. return 0;
}
// this is where one would put code that is to be called
// everytime the glut main loop has nothing to do
//
// this is typically where animation parameters are set
//
// do not call Display( ) from here -- let glutMainLoop( ) do it
void
Animate()
{
// put animation stuff in here -- change some global
variables
// for Display( ) to find:
109. // force a call to Display( ) next time it is convenient:
int ms = glutGet(GLUT_ELAPSED_TIME);
ms %= MS_PER_CYCLE;
Time = (float)ms / (float)MS_PER_CYCLE; //
[0.,1.)
glutSetWindow(MainWindow);
glutPostRedisplay();
}
// draw the complete scene:
void
Display()
{
110. if (DebugOn != 0)
{
fprintf(stderr, "Displayn");
}
// set which window we want to do the graphics into:
glutSetWindow(MainWindow);
// erase the background:
glDrawBuffer(GL_BACK);
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT);
if (DepthBufferOn != 0)
glEnable(GL_DEPTH_TEST);
111. else
glDisable(GL_DEPTH_TEST);
// specify shading to be flat:
glShadeModel(GL_FLAT);
// set the viewport to a square centered in the window:
GLsizei vx = glutGet(GLUT_WINDOW_WIDTH);
GLsizei vy = glutGet(GLUT_WINDOW_HEIGHT);
GLsizei v = vx < vy ? vx : vy; // minimum
dimension
GLint xl = (vx - v) / 2;
GLint yb = (vy - v) / 2;
glViewport(xl, yb, v, v);
112. // set the viewing volume:
// remember that the Z clipping values are actually
// given as DISTANCES IN FRONT OF THE EYE
// USE gluOrtho2D( ) IF YOU ARE DOING 2D !
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (WhichProjection == ORTHO)
glOrtho(-3., 3., -3., 3., 0.1, 1000.);
else
gluPerspective(90., 1., 0.1, 1000.);
// place the objects into the scene:
glMatrixMode(GL_MODELVIEW);
113. glLoadIdentity();
// set the eye position, look-at position, and up-vector:
gluLookAt(0., 0., 3., 0., 0., 0., 0., 1., 0.);
// rotate the scene:
glRotatef((GLfloat)Yrot, 0., 1., 0.);
glRotatef((GLfloat)Xrot, 1., 0., 0.);
// uniformly scale the scene:
if (Scale < MINSCALE)
Scale = MINSCALE;
114. glScalef((GLfloat)Scale, (GLfloat)Scale, (GLfloat)Scale);
// set the fog parameters:
if (DepthCueOn != 0)
{
glFogi(GL_FOG_MODE, FOGMODE);
glFogfv(GL_FOG_COLOR, FOGCOLOR);
glFogf(GL_FOG_DENSITY, FOGDENSITY);
glFogf(GL_FOG_START, FOGSTART);
glFogf(GL_FOG_END, FOGEND);
glEnable(GL_FOG);
}
else
{
glDisable(GL_FOG);
}
115. // possibly draw the axes:
if (AxesOn != 0)
{
glColor3fv(&Colors[WhichColor][0]);
glCallList(AxesList);
}
if (Light0On)
{
glEnable(GL_LIGHT0);
}
else
{
glDisable(GL_LIGHT0);
122. // swap the double-buffered framebuffers:
glutSwapBuffers();
// be sure the graphics buffer has been sent:
// note: be sure to use glFlush( ) here, not glFinish( ) !
glFlush();
}
void
DoAxesMenu(int id)
{
AxesOn = id;
126. // main menu callback:
void
DoMainMenu(int id)
{
switch (id)
{
case RESET:
Reset();
break;
case QUIT:
// gracefully close out the graphics:
// gracefully close the graphics window:
// gracefully exit the program:
glutSetWindow(MainWindow);
128. WhichProjection = id;
glutSetWindow(MainWindow);
glutPostRedisplay();
}
// use glut to display a string of characters using a raster font:
void
DoRasterString(float x, float y, float z, char *s)
{
glRasterPos3f((GLfloat)x, (GLfloat)y, (GLfloat)z);
char c; // one character to print
for (; (c = *s) != '0'; s++)
{
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_2
129. 4, c);
}
}
// use glut to display a string of characters using a stroke font:
void
DoStrokeString(float x, float y, float z, float ht, char *s)
{
glPushMatrix();
glTranslatef((GLfloat)x, (GLfloat)y, (GLfloat)z);
float sf = ht / (119.05f + 33.33f);
glScalef((GLfloat)sf, (GLfloat)sf, (GLfloat)sf);
char c; // one character to print
for (; (c = *s) != '0'; s++)
{
glutStrokeCharacter(GLUT_STROKE_ROMAN, c);
130. }
glPopMatrix();
}
// return the number of seconds since the start of the program:
float
ElapsedSeconds()
{
// get # of milliseconds since the start of the program:
int ms = glutGet(GLUT_ELAPSED_TIME);
// convert it to seconds:
return (float)ms / 1000.f;
}
131. // initialize the glui window:
void
InitMenus()
{
glutSetWindow(MainWindow);
int numColors = sizeof(Colors) / (3 * sizeof(int));
int colormenu = glutCreateMenu(DoColorMenu);
for (int i = 0; i < numColors; i++)
{
glutAddMenuEntry(ColorNames[i], i);
}
int axesmenu = glutCreateMenu(DoAxesMenu);
glutAddMenuEntry("Off", 0);
132. glutAddMenuEntry("On", 1);
int depthcuemenu = glutCreateMenu(DoDepthMenu);
glutAddMenuEntry("Off", 0);
glutAddMenuEntry("On", 1);
int depthbuffermenu =
glutCreateMenu(DoDepthBufferMenu);
glutAddMenuEntry("Off", 0);
glutAddMenuEntry("On", 1);
int depthfightingmenu =
glutCreateMenu(DoDepthFightingMenu);
glutAddMenuEntry("Off", 0);
glutAddMenuEntry("On", 1);
int debugmenu = glutCreateMenu(DoDebugMenu);
glutAddMenuEntry("Off", 0);
glutAddMenuEntry("On", 1);
133. int projmenu = glutCreateMenu(DoProjectMenu);
glutAddMenuEntry("Orthographic", ORTHO);
glutAddMenuEntry("Perspective", PERSP);
int mainmenu = glutCreateMenu(DoMainMenu);
glutAddSubMenu("Axes", axesmenu);
glutAddSubMenu("Colors", colormenu);
glutAddSubMenu("Depth Buffer", depthbuffermenu);
glutAddSubMenu("Depth Fighting", depthfightingmenu);
glutAddSubMenu("Depth Cue", depthcuemenu);
glutAddSubMenu("Projection", projmenu);
glutAddMenuEntry("Reset", RESET);
glutAddSubMenu("Debug", debugmenu);
glutAddMenuEntry("Quit", QUIT);
// attach the pop-up menu to the right mouse button:
134. glutAttachMenu(GLUT_RIGHT_BUTTON);
}
// initialize the glut and OpenGL libraries:
// also setup display lists and callback functions
void
InitGraphics()
{
// request the display modes:
// ask for red-green-blue-alpha color, double-buffering,
and z-buffering:
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE |
GLUT_DEPTH);
// set the initial window configuration:
135. glutInitWindowPosition(0, 0);
glutInitWindowSize(INIT_WINDOW_SIZE,
INIT_WINDOW_SIZE);
// open the window and set its title:
MainWindow = glutCreateWindow(WINDOWTITLE);
glutSetWindowTitle(WINDOWTITLE);
// set the framebuffer clear values:
glClearColor(BACKCOLOR[0], BACKCOLOR[1],
BACKCOLOR[2], BACKCOLOR[3]);
// setup the callback functions:
// DisplayFunc -- redraw the window
// ReshapeFunc -- handle the user resizing the window
// KeyboardFunc -- handle a keyboard input
136. // MouseFunc -- handle the mouse button going down or up
// MotionFunc -- handle the mouse moving with a button
down
// PassiveMotionFunc -- handle the mouse moving with a
button up
// VisibilityFunc -- handle a change in window visibility
// EntryFunc -- handle the cursor entering or leaving the
window
// SpecialFunc -- handle special keys on the keyboard
// SpaceballMotionFunc -- handle spaceball translation
// SpaceballRotateFunc -- handle spaceball rotation
// SpaceballButtonFunc -- handle spaceball button hits
// ButtonBoxFunc -- handle button box hits
// DialsFunc -- handle dial rotations
// TabletMotionFunc -- handle digitizing tablet motion
// TabletButtonFunc -- handle digitizing tablet button hits
// MenuStateFunc -- declare when a pop-up menu is in use
// TimerFunc -- trigger something to happen a certain time
from now
// IdleFunc -- what to do when nothing else is going on
138. glutMenuStateFunc(NULL);
glutTimerFunc(-1, NULL, 0);
glutIdleFunc(Animate);
// init glew (a window must be open to do this):
#ifdef WIN32
GLenum err = glewInit();
if (err != GLEW_OK)
{
fprintf(stderr, "glewInit Errorn");
}
else
fprintf(stderr, "GLEW initialized OKn");
fprintf(stderr, "Status: Using GLEW %sn",
glewGetString(GLEW_VERSION));
#endif
}
139. // initialize the display lists that will not change:
// (a display list is a way to store opengl commands in
// memory so that they can be played back efficiently at a later
time
// with a call to glCallList( )
void
InitLists()
{
float dx = BOXSIZE / 2.f;
float dy = BOXSIZE / 2.f;
float dz = BOXSIZE / 2.f;
glutSetWindow(MainWindow);
// create the object:
143. AxesList = glGenLists(1);
glNewList(AxesList, GL_COMPILE);
glLineWidth(AXES_WIDTH);
Axes(1.5);
glLineWidth(1.);
glEndList();
}
// the keyboard callback:
void
Keyboard(unsigned char c, int x, int y)
{
if (DebugOn != 0)
fprintf(stderr, "Keyboard: '%c' (0x%0x)n", c, c);
144. switch (c)
{
case 'o':
case 'O':
WhichProjection = ORTHO;
break;
case 'p':
case 'P':
WhichProjection = PERSP;
break;
case '0':
Light0On = !Light0On;
break;
case '1':
Light1On = !Light1On;
break;
145. case '2':
Light2On = !Light2On;
break;
case 'f':
case 'F':
Frozen = !Frozen;
if (Frozen)
glutIdleFunc(NULL);
else
glutIdleFunc(Animate);
break;
case 'q':
case 'Q':
case ESCAPE:
DoMainMenu(QUIT); // will not return here
break; // happy compiler
default:
146. fprintf(stderr, "Don't know what to do with keyboard
hit: '%c' (0x%0x)n", c, c);
}
// force a call to Display( ):
glutSetWindow(MainWindow);
glutPostRedisplay();
}
// called when the mouse button transitions down or up:
void
MouseButton(int button, int state, int x, int y)
{
int b = 0; // LEFT, MIDDLE, or RIGHT
if (DebugOn != 0)
147. fprintf(stderr, "MouseButton: %d, %d, %d, %dn",
button, state, x, y);
// get the proper button bit mask:
switch (button)
{
case GLUT_LEFT_BUTTON:
b = LEFT; break;
case GLUT_MIDDLE_BUTTON:
b = MIDDLE; break;
case GLUT_RIGHT_BUTTON:
b = RIGHT; break;
default:
148. b = 0;
fprintf(stderr, "Unknown mouse button: %dn",
button);
}
// button down sets the bit, up clears the bit:
if (state == GLUT_DOWN)
{
Xmouse = x;
Ymouse = y;
ActiveButton |= b; // set the proper bit
}
else
{
ActiveButton &= ~b; // clear the proper bit
}
}
149. // called when the mouse moves while a button is down:
void
MouseMotion(int x, int y)
{
if (DebugOn != 0)
fprintf(stderr, "MouseMotion: %d, %dn", x, y);
int dx = x - Xmouse; // change in mouse coords
int dy = y - Ymouse;
if ((ActiveButton & LEFT) != 0)
{
Xrot += (ANGFACT*dy);
Yrot += (ANGFACT*dx);
150. }
if ((ActiveButton & MIDDLE) != 0)
{
Scale += SCLFACT * (float)(dx - dy);
// keep object from turning inside-out or
disappearing:
if (Scale < MINSCALE)
Scale = MINSCALE;
}
Xmouse = x; // new current position
Ymouse = y;
glutSetWindow(MainWindow);
151. glutPostRedisplay();
}
// reset the transformations and the colors:
// this only sets the global variables --
// the glut main loop is responsible for redrawing the scene
void
Reset()
{
ActiveButton = 0;
AxesOn = 0;
DebugOn = 0;
DepthBufferOn = 1;
DepthFightingOn = 0;
DepthCueOn = 0;
Scale = 1.0;
152. WhichColor = WHITE;
WhichProjection = PERSP;
Xrot = Yrot = 0.;
}
// called when user resizes the window:
void
Resize(int width, int height)
{
if (DebugOn != 0)
fprintf(stderr, "ReSize: %d, %dn", width, height);
// don't really need to do anything since window size is
// checked each time in Display( ):
glutSetWindow(MainWindow);
153. glutPostRedisplay();
}
// handle a change to the window's visibility:
void
Visibility(int state)
{
if (DebugOn != 0)
fprintf(stderr, "Visibility: %dn", state);
if (state == GLUT_VISIBLE)
{
glutSetWindow(MainWindow);
glutPostRedisplay();
}
else
154. {
// could optimize by keeping track of the fact
// that the window is not visible and avoid
// animating or redrawing it ...
}
}
/////////////////////////////////////// HANDY UTILITIES:
//////////////////////////
// the stroke characters 'X' 'Y' 'Z' :
static float xx[] = {
0.f, 1.f, 0.f, 1.f
};
156. };
static float zx[] = {
1.f, 0.f, 1.f, 0.f, .25f, .75f
};
static float zy[] = {
.5f, .5f, -.5f, -.5f, 0.f, 0.f
};
static int zorder[] = {
1, 2, 3, 4, -5, 6
};
// fraction of the length to use as height of the characters:
const float LENFRAC = 0.10f;
// fraction of length to use as start location of the characters:
157. const float BASEFRAC = 1.10f;
// Draw a set of 3D axes:
// (length is the axis length in world coordinates)
void
Axes(float length)
{
glBegin(GL_LINE_STRIP);
glVertex3f(length, 0., 0.);
glVertex3f(0., 0., 0.);
glVertex3f(0., length, 0.);
glEnd();
glBegin(GL_LINE_STRIP);
glVertex3f(0., 0., 0.);
glVertex3f(0., 0., length);
glEnd();
158. float fact = LENFRAC * length;
float base = BASEFRAC * length;
glBegin(GL_LINE_STRIP);
for (int i = 0; i < 4; i++)
{
int j = xorder[i];
if (j < 0)
{
glEnd();
glBegin(GL_LINE_STRIP);
j = -j;
}
j--;
glVertex3f(base + fact * xx[j], fact*xy[j], 0.0);
}
glEnd();
159. glBegin(GL_LINE_STRIP);
for (int i = 0; i < 5; i++)
{
int j = yorder[i];
if (j < 0)
{
glEnd();
glBegin(GL_LINE_STRIP);
j = -j;
}
j--;
glVertex3f(fact*yx[j], base + fact * yy[j], 0.0);
}
glEnd();
glBegin(GL_LINE_STRIP);
160. for (int i = 0; i < 6; i++)
{
int j = zorder[i];
if (j < 0)
{
glEnd();
glBegin(GL_LINE_STRIP);
j = -j;
}
j--;
glVertex3f(0.0, fact*zy[j], base + fact * zx[j]);
}
glEnd();
}
161. // function to convert HSV to RGB
// 0. <= s, v, r, g, b <= 1.
// 0. <= h <= 360.
// when this returns, call:
// glColor3fv( rgb );
void
HsvRgb(float hsv[3], float rgb[3])
{
// guarantee valid input:
float h = hsv[0] / 60.f;
while (h >= 6.) h -= 6.;
while (h < 0.) h += 6.;
float s = hsv[1];
if (s < 0.)
s = 0.;
162. if (s > 1.)
s = 1.;
float v = hsv[2];
if (v < 0.)
v = 0.;
if (v > 1.)
v = 1.;
// if sat==0, then is a gray:
if (s == 0.0)
{
rgb[0] = rgb[1] = rgb[2] = v;
return;
}
// get an rgb from the hue itself:
163. float i = floor(h);
float f = h - i;
float p = v * (1.f - s);
float q = v * (1.f - s * f);
float t = v * (1.f - (s * (1.f - f)));
float r, g, b; // red, green, blue
switch ((int)i)
{
case 0:
r = v; g = t; b = p;
break;
case 1:
r = q; g = v; b = p;
break;
164. case 2:
r = p; g = v; b = t;
break;
case 3:
r = p; g = q; b = v;
break;
case 4:
r = t;g = p; b = v;
break;
case 5:
r = v; g = p; b = q;
break;
}
165. rgb[0] = r;
rgb[1] = g;
rgb[2] = b;
}
unsigned char*
BmpToTexture(char* filename, int* width, int* height)
{
int s, t, e; // counters
int numextra; // # extra bytes each line in the file is
padded with
FILE* fp;
unsigned char* texture;
int nums, numt;
unsigned char* tp;
166. fp = fopen(filename, "rb");
if (fp == NULL)
{
fprintf(stderr, "Cannot open Bmp file '%s'n",
filename);
return NULL;
}
FileHeader.bfType = ReadShort(fp);
// if bfType is not 0x4d42, the file is not a bmp:
if (FileHeader.bfType != 0x4d42)
{
fprintf(stderr, "Wrong type of file: 0x%0xn",
FileHeader.bfType);
fclose(fp);
return NULL;