SlideShare a Scribd company logo
1 of 244
S w
908A08
CINEPLEX ENTERTAINMENT: THE LOYALTY PROGRAM
Renée Zatzman wrote this case under the supervision of
Professor Kenneth G. Hardy solely to provide material for class
discussion.
The authors do not intend to illustrate either effective or
ineffective handling of a managerial situation. The authors may
have
disguised certain names and other identifying information to
protect confidentiality.
Ivey Management Services prohibits any form of reproduction,
storage or transmittal without its written permission.
Reproduction of
this material is not covered under authorization by any
reproduction rights organization. To order copies or request
permission to
reproduce materials, contact Ivey Publishing, Ivey Management
Services, c/o Richard Ivey School of Business, The University
of
Western Ontario, London, Ontario, Canada, N6A 3K7; phone
(519) 661-3208; fax (519) 661-3882; e-mail [email protected]
Copyright © 2008, Ivey Management Services Version: (A)
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
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),
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.
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.
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”
“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.
“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.
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
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
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
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,
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
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
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
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
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,
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
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
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
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.
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
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
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
• 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
• 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%
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
– 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.
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
• 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
• 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
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
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
Exhibit 7
LARGE MEDIA MARKETS
SMALL- AND MEDIUM SIZED MEDIA MARKETS
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
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
[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
// 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
#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):
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
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
{ 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
float Xrot, Yrot; // rotation angles in degrees
float time_anima;
bool LineOn = true;
bool PointOn = true;
struct Point
{
float x0, y0, z0; // initial coordinates
float x, y, z; // animated coordinates
void setPt(float x0, float y0, float z0) {
this->x0 = x0;
this->y0 = y0;
this->z0 = z0;
}
void reset() {
x = x0;
y = y0;
z = z0;
}
};
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
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];
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 );
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( );
// 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);
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 |
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. );
// 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 );
}
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();
line2.color(0., 1., 1.);
line2.p0.setPt(1, 1, 0);
line2.p1.setPt(1.5 + (Time), .5, 0);
line2.p2.setPt(1.5 + (Time), -.5, 0);
line2.p3.setPt(1, -1, 0);
line2.reset();
line3.color(0., 1., 1.);
line3.p0.setPt(-1, 1, 2);
line3.p1.setPt(-1.5 - (Time), .5, 2);
line3.p2.setPt(-1.5 - (Time), -.5, 2);
line3.p3.setPt(-1, -1, 2);
line3.reset();
line4.color(0., 1., 1.);
line4.p0.setPt(1, 1, 2);
line4.p1.setPt(1.5 + (Time), .5, 2);
line4.p2.setPt(1.5 + (Time), -.5, 2);
line4.p3.setPt(1, -1, 2);
line4.reset();
line5.color(1., 1., 0.);
line5.p0.setPt(1, 1, 0);
line5.p1.setPt(.5, 1.5 + (Time), 0);
line5.p2.setPt(-.5, 1.5 + (Time), 0);
line5.p3.setPt(-1, 1, 0);
line5.reset();
line6.color(1., 1., 0.);
line6.p0.setPt(1, -1, 0);
line6.p1.setPt(.5, -1.5 - (Time), 0);
line6.p2.setPt(-.5, -1.5 - (Time), 0);
line6.p3.setPt(-1, -1, 0);
line6.reset();
line7.color(1., 1., 0.);
line7.p0.setPt(1, 1, 2);
line7.p1.setPt(.5, 1.5 + (Time), 2);
line7.p2.setPt(-.5, 1.5 + (Time), 2);
line7.p3.setPt(-1, 1, 2);
line7.reset();
line8.color(1., 1., 0.);
line8.p0.setPt(1, -1, 2);
line8.p1.setPt(.5, -1.5 - (Time), 2);
line8.p2.setPt(-.5, -1.5 - (Time), 2);
line8.p3.setPt(-1, -1, 2);
line8.reset();
line9.color(0., 1., 0.);
line9.p0.setPt(-1, 1, 0);
line9.p1.setPt(-1, 1, .5);
line9.p2.setPt(-1, 1, 1.5);
line9.p3.setPt(-1, 1, 2);
line9.reset();
line10.color(0., 1., 0.);
line10.p0.setPt(-1, -1, 0);
line10.p1.setPt(-1, -1, .5);
line10.p2.setPt(-1, -1, 1.5);
line10.p3.setPt(-1, -1, 2);
line10.reset();
line11.color(0., 1., 0.);
line11.p0.setPt(1, -1, 0);
line11.p1.setPt(1, -1, .5);
line11.p2.setPt(1, -1, 1.5);
line11.p3.setPt(1, -1, 2);
line11.reset();
line12.color(0., 1., 0.);
line12.p0.setPt(1, 1, 0);
line12.p1.setPt(1, 1, .5);
line12.p2.setPt(1, 1, 1.5);
line12.p3.setPt(1, 1, 2);
line12.reset();
CreateBezierCur(line1, 12);
printf("%fn", line1.p1.x);
CreateBezierCur(line2, 12);
CreateBezierCur(line3, 12);
CreateBezierCur(line4, 12);
CreateBezierCur(line5, 12);
CreateBezierCur(line6, 12);
CreateBezierCur(line7, 12);
CreateBezierCur(line8, 12);
CreateBezierCur(line9, 12);
CreateBezierCur(line10, 12);
CreateBezierCur(line11, 12);
CreateBezierCur(line12, 12);
// draw the current object:
// draw some gratuitous text that just rotates on top of the
scene:
// 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
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( );
}
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
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:
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( )
{
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 );
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 );
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:
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 );
// 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
glutSetWindow( MainWindow );
glutDisplayFunc( Display );
glutReshapeFunc( Resize );
glutKeyboardFunc( Keyboard );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
glutPassiveMotionFunc( NULL );
glutVisibilityFunc( Visibility );
glutEntryFunc( NULL );
glutSpecialFunc( NULL );
glutSpaceballMotionFunc( NULL );
glutSpaceballRotateFunc( NULL );
glutSpaceballButtonFunc( NULL );
glutButtonBoxFunc( NULL );
glutDialsFunc( NULL );
glutTabletMotionFunc( NULL );
glutTabletButtonFunc( NULL );
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
}
// 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 );
glTexCoord2f( 1., 1. );
glVertex3f( dx, dy, -dz );
glTexCoord2f( 1., 0. );
glVertex3f( dx, -dy, -dz );
glColor3f( 1., 0., 0. );
glNormal3f( 1., 0., 0. );
glVertex3f( dx, -dy, dz );
glVertex3f( dx, -dy, -dz );
glVertex3f( dx, dy, -dz );
glVertex3f( dx, dy, dz );
glNormal3f( -1., 0., 0. );
glVertex3f( -dx, -dy, dz );
glVertex3f( -dx, dy, dz );
glVertex3f( -dx, dy, -dz );
glVertex3f( -dx, -dy, -dz );
glColor3f( 0., 1., 0. );
glNormal3f( 0., 1., 0. );
glVertex3f( -dx, dy, dz );
glVertex3f( dx, dy, dz );
glVertex3f( dx, dy, -dz );
glVertex3f( -dx, dy, -dz );
glNormal3f( 0., -1., 0. );
glVertex3f( -dx, -dy, dz );
glVertex3f( -dx, -dy, -dz );
glVertex3f( dx, -dy, -dz );
glVertex3f( dx, -dy, dz );
glEnd( );
glEndList( );
// 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
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",
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
}
}
// 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 );
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 );
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
};
static float xy[ ] = {
-.5f, .5f, .5f, -.5f
};
static int xorder[ ] = {
1, 2, -3, 4
};
static float yx[ ] = {
0.f, 0.f, -.5f, .5f
};
static float yy[ ] = {
0.f, .6f, 1.f, 1.f
};
static int yorder[ ] = {
1, 2, 3, -2, 4
};
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:
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 );
}
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( );
}
// 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;
}
// 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;
}
rgb[0] = r;
rgb[1] = g;
rgb[2] = b;
}
void CreateBezierCur(Curve curve, GLfloat width) {
if (LineOn) {
glBegin(GL_LINE_STRIP);
glColor3f(0.8, 0.8, 0.8);
glVertex3f(curve.p0.x, curve.p0.y, curve.p0.z);
glVertex3f(curve.p1.x, curve.p1.y, curve.p1.z);
glVertex3f(curve.p2.x, curve.p2.y, curve.p2.z);
glVertex3f(curve.p3.x, curve.p3.y, curve.p3.z);
glEnd();
}
if (PointOn) {
glPushMatrix();
glTranslatef(curve.p0.x, curve.p0.y, curve.p0.z);
glColor3f(1, 1, 1);
glutSolidSphere(0.04, 50, 50);
glPopMatrix();
glPushMatrix();
glTranslatef(curve.p1.x, curve.p1.y, curve.p1.z);
glColor3f(0.8, 0.8, 0.8);
glutSolidSphere(0.03, 50, 50);
glPopMatrix();
glPushMatrix();
glTranslatef(curve.p2.x, curve.p2.y, curve.p2.z);
glColor3f(0.8, 0.8, 0.8);
glutSolidSphere(0.03, 50, 50);
glPopMatrix();
glPushMatrix();
glTranslatef(curve.p3.x, curve.p3.y, curve.p3.z);
glColor3f(1, 1, 1);
glutSolidSphere(0.04, 50, 50);
glPopMatrix();
}
glLineWidth(width);
glColor3f(curve.r, curve.g, curve.b);
glBegin(GL_LINE_STRIP);
for (int it = 0; it <= NUMPOINTS; it++) {
float t = (float)it / (float)NUMPOINTS;
float omt = 1.f - t;
float x = omt * omt*omt*curve.p0.x +
3.f*t*omt*omt*curve.p1.x + 3.f*t*t*omt*curve.p2.x + t *
t*t*curve.p3.x;
float y = omt * omt*omt*curve.p0.y +
3.f*t*omt*omt*curve.p1.y + 3.f*t*t*omt*curve.p2.y + t *
t*t*curve.p3.y;
float z = omt * omt*omt*curve.p0.z +
3.f*t*omt*omt*curve.p1.z + 3.f*t*t*omt*curve.p2.z + t *
t*t*curve.p3.z;
glVertex3f(x, y, z);
}
glEnd();
glLineWidth(1.);
}
void setanimatedcoor(Curve* curve) {
float time = Time;
curve->p0.x = curve->p0.x0;
curve->p0.y = curve->p0.y0;
curve->p0.z = curve->p0.z0;
curve->p1.x = curve->p1.x0 + curve->p1.x0 * sinf(time *
M_PI);
curve->p1.y = curve->p1.y0 + curve->p1.y0 * sinf(time *
M_PI);
curve->p1.z = curve->p1.z0 + curve->p1.z0 * sinf(time *
M_PI);
curve->p2.x = curve->p2.x0 + curve->p2.x0 * sinf(time *
M_PI);
curve->p2.y = curve->p2.y0 + curve->p2.y0 * sinf(time *
M_PI);
curve->p2.z = curve->p2.z0 + curve->p2.z0 * sinf(time *
M_PI);
curve->p3.x = curve->p3.x0;
curve->p3.y = curve->p3.y0;
curve->p3.z = curve->p3.z0;
}
#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
// 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
// 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):
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
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
{ 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
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*);
float texDistort;
shortReadShort(FILE*);
struct bmfh
{
short bfType;
int bfSize;
short bfReserved1;
short bfReserved2;
int bfOffBits;
} FileHeader;
struct bmih
{
int biSize;
int biWidth;
int biHeight;
short biPlanes;
short biBitCount;
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)
{
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.;
return array;
}
float*
MulArray3(float factor, float array0[3])
{
static float array[4];
array[0] = factor * array0[0];
array[1] = factor * array0[1];
array[2] = factor * array0[2];
array[3] = 1.;
return array;
}
void MaterialSetting(float r, float g, float b, float shininess) {
glMaterialfv(GL_BACK, GL_EMISSION, Array3(0., 0.,
0.));
glMaterialfv(GL_BACK, GL_AMBIENT, MulArray3(.4f,
Gray));
glMaterialfv(GL_BACK, GL_DIFFUSE, MulArray3(1.,
Gray));
glMaterialfv(GL_BACK, GL_SPECULAR, Array3(0., 0.,
0.));
glMaterialf(GL_BACK, GL_SHININESS, 2.f);
glMaterialfv(GL_FRONT, GL_EMISSION, Array3(0., 0.,
0.));
glMaterialfv(GL_FRONT, GL_AMBIENT, Array3(r, g,
b));
glMaterialfv(GL_FRONT, GL_DIFFUSE, Array3(r, g, b));
glMaterialfv(GL_FRONT, GL_SPECULAR,
MulArray3(.8f, White));
glMaterialf(GL_FRONT, GL_SHININESS, shininess);
}
void
SetPointLight(int ilight, float x, float y, float z, float r, float g,
float b)
{
glLightfv(ilight, GL_POSITION, Array3(x, y, z));
glLightfv(ilight, GL_AMBIENT, Array3(0., 0., 0.));
glLightfv(ilight, GL_DIFFUSE, Array3(r, g, b));
glLightfv(ilight, GL_SPECULAR, Array3(r, g, b));
glLightf(ilight, GL_CONSTANT_ATTENUATION, 1.);
glLightf(ilight, GL_LINEAR_ATTENUATION, 0.);
glLightf(ilight, GL_QUADRATIC_ATTENUATION, 0.);
glEnable(ilight);
}
void
SetSpotLight(int ilight, float x, float y, float z, float xdir, float
ydir, float zdir, float r, float g, float b)
{
glLightfv(ilight, GL_POSITION, Array3(x, y, z));
glLightfv(ilight, GL_SPOT_DIRECTION, Array3(xdir,
ydir, zdir));
glLightf(ilight, GL_SPOT_EXPONENT, 1.);
glLightf(ilight, GL_SPOT_CUTOFF, 45.);
glLightfv(ilight, GL_AMBIENT, Array3(0., 0., 0.));
glLightfv(ilight, GL_DIFFUSE, Array3(r, g, b));
glLightfv(ilight, GL_SPECULAR, Array3(r, g, b));
glLightf(ilight, GL_CONSTANT_ATTENUATION, 1.);
glLightf(ilight, GL_LINEAR_ATTENUATION, 0.);
glLightf(ilight, GL_QUADRATIC_ATTENUATION, 0.);
glEnable(ilight);
}
const int birgb = { 0 };
// 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);
void Axes(float);
void HsvRgb(float[3], float[3]);
void MaterialSetting(float, float, float, float);
void SetPointLight(int, float, float, float, float, float, float);
void SetSpotLight(int, float, float, float, float, float, float, float,
float, float);
void
MjbSphere(float radius, int slices, int stacks);
void
DrawPoint(struct point* p);
unsigned char*
BmpToTexture(char* filename, int* width, int* height);
// 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();
// 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);
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");
}
// 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);
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.);
// place the objects into the scene:
glMatrixMode(GL_MODELVIEW);
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;
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);
}
// possibly draw the axes:
if (AxesOn != 0)
{
glColor3fv(&Colors[WhichColor][0]);
glCallList(AxesList);
}
if (Light0On)
{
glEnable(GL_LIGHT0);
}
else
{
glDisable(GL_LIGHT0);
}
if (Light1On)
{
glEnable(GL_LIGHT1);
}
else
{
glDisable(GL_LIGHT1);
}
if (Light2On)
{
glEnable(GL_LIGHT2);
}
else
{
glDisable(GL_LIGHT2);
}
unsigned char* Texture;
Texture = BmpToTexture("worldtexture.bmp",
&bmp_width, &bmp_height);
int level, ncomps, border;
level = 0;
ncomps = 4;
border = 0;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &tex01);
glBindTexture(GL_TEXTURE_2D, tex01);
glTexImage2D(GL_TEXTURE_2D, level, ncomps,
bmp_width, bmp_height, border, GL_RGB,
GL_UNSIGNED_BYTE, Texture);
glEnable(GL_NORMALIZE);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT,
MulArray3(.2, White));
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,
GL_FALSE);
glPushMatrix();
glShadeModel(GL_SMOOTH);
glTranslatef(0., 0., 1.5);
MaterialSetting(1., 1., 1., 50.);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexEnvf(GL_TEXTURE_ENV,
GL_TEXTURE_ENV_MODE, GL_MODULATE);
glEnable(GL_TEXTURE_2D);
MjbSphere(0.5, 100, 100);
glPopMatrix();
glDisable(GL_TEXTURE_2D);
glPushMatrix();
glShadeModel(GL_SMOOTH);
glTranslatef(0., 1.5, 0.);
glRotatef(90, 1., 0., 0.);
glScalef(1., 1., 0.5);
MaterialSetting(0., 1., 0., 50.);
glutSolidTorus(0.5, 1., 30., 30.);
glPopMatrix();
glPushMatrix();
glShadeModel(GL_FLAT);
glTranslatef(0., 2 * cos(Time * M_PI * 2), 0.);
glDisable(GL_LIGHTING);
glColor3f(1., 0., 0.);
glutSolidCube(0.5);
glPopMatrix();
SetPointLight(GL_LIGHT0, 1., 0., 0.5, 1., 1., 1.);
glPushMatrix();
glDisable(GL_LIGHTING);
glColor3f(1., 1., 0.);
glTranslatef(1., 0., 0.5);
glutSolidSphere(0.1, 50, 50);
glEnd();
glEnable(GL_LIGHTING);
glPopMatrix();
SetPointLight(GL_LIGHT1, 0., 2 * cos(Time * M_PI * 2)
+ 0.2, 0., 0., 0., 1.);
glPushMatrix();
glDisable(GL_LIGHTING);
glColor3f(0., 0., 1.);
glTranslatef(0., 2 * cos(Time * M_PI * 2) + 0.25, 0.);
glutSolidSphere(0.1, 50, 50);
glEnd();
glEnable(GL_LIGHTING);
glPopMatrix();
SetSpotLight(GL_LIGHT2, 0., 0., 0.5, 0., 0., 5., 1., 0., 0.);
glPushMatrix();
glDisable(GL_LIGHTING);
glColor3f(1., 0., 1.);
glTranslatef(0., 0., 0.5);
glutSolidSphere(0.1, 50., 50.);
glEnd();
glEnable(GL_LIGHTING);
glPopMatrix();
// 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
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();
}
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 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_2
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);
}
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()
{
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);
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);
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:
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);
// 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
glutSetWindow(MainWindow);
glutDisplayFunc(Display);
glutReshapeFunc(Resize);
glutKeyboardFunc(Keyboard);
glutMouseFunc(MouseButton);
glutMotionFunc(MouseMotion);
glutPassiveMotionFunc(NULL);
glutVisibilityFunc(Visibility);
glutEntryFunc(NULL);
glutSpecialFunc(NULL);
glutSpaceballMotionFunc(NULL);
glutSpaceballRotateFunc(NULL);
glutSpaceballButtonFunc(NULL);
glutButtonBoxFunc(NULL);
glutDialsFunc(NULL);
glutTabletMotionFunc(NULL);
glutTabletButtonFunc(NULL);
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
}
// 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);
glTexCoord2f(1., 1.);
glVertex3f(dx, dy, -dz);
glTexCoord2f(1., 0.);
glVertex3f(dx, -dy, -dz);
glColor3f(1., 0., 0.);
glNormal3f(1., 0., 0.);
glVertex3f(dx, -dy, dz);
glVertex3f(dx, -dy, -dz);
glVertex3f(dx, dy, -dz);
glVertex3f(dx, dy, dz);
glNormal3f(-1., 0., 0.);
glVertex3f(-dx, -dy, dz);
glVertex3f(-dx, dy, dz);
glVertex3f(-dx, dy, -dz);
glVertex3f(-dx, -dy, -dz);
glColor3f(0., 1., 0.);
glNormal3f(0., 1., 0.);
glVertex3f(-dx, dy, dz);
glVertex3f(dx, dy, dz);
glVertex3f(dx, dy, -dz);
glVertex3f(-dx, dy, -dz);
glNormal3f(0., -1., 0.);
glVertex3f(-dx, -dy, dz);
glVertex3f(-dx, -dy, -dz);
glVertex3f(dx, -dy, -dz);
glVertex3f(dx, -dy, dz);
glEnd();
glEndList();
// 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 '0':
Light0On = !Light0On;
break;
case '1':
Light1On = !Light1On;
break;
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:
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",
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
}
}
// 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);
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;
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);
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
};
static float xy[] = {
-.5f, .5f, .5f, -.5f
};
static int xorder[] = {
1, 2, -3, 4
};
static float yx[] = {
0.f, 0.f, -.5f, .5f
};
static float yy[] = {
0.f, .6f, 1.f, 1.f
};
static int yorder[] = {
1, 2, 3, -2, 4
};
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:
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);
}
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();
}
// 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;
}
// 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;
}
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;
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;
}
FileHeader.bfSize = ReadInt(fp);
FileHeader.bfReserved1 = ReadShort(fp);
FileHeader.bfReserved2 = ReadShort(fp);
FileHeader.bfOffBits = ReadInt(fp);
InfoHeader.biSize = ReadInt(fp);
InfoHeader.biWidth = ReadInt(fp);
InfoHeader.biHeight = ReadInt(fp);
nums = InfoHeader.biWidth;
numt = InfoHeader.biHeight;
InfoHeader.biPlanes = ReadShort(fp);
InfoHeader.biBitCount = ReadShort(fp);
InfoHeader.biCompression = ReadInt(fp);
InfoHeader.biSizeImage = ReadInt(fp);
InfoHeader.biXPelsPerMeter = ReadInt(fp);
InfoHeader.biYPelsPerMeter = ReadInt(fp);
InfoHeader.biClrUsed = ReadInt(fp);
InfoHeader.biClrImportant = ReadInt(fp);
// fprintf( stderr, "Image size found: %d x %dn",
ImageWidth, ImageHeight );
texture = new unsigned char[3 * nums * numt];
if (texture == NULL)
{
fprintf(stderr, "Cannot allocate the texture array!b");
return NULL;
}
// extra padding bytes:
numextra = 4 * (((3 * InfoHeader.biWidth) + 3) / 4) - 3 *
InfoHeader.biWidth;
// we do not support compression:
if (InfoHeader.biCompression != birgb)
{
fprintf(stderr, "Wrong type of image compression:
%dn", InfoHeader.biCompression);
fclose(fp);
return NULL;
}
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx
S w  908A08    CINEPLEX ENTERTAINMENT THE LOYALTY.docx

More Related Content

Similar to S w 908A08 CINEPLEX ENTERTAINMENT THE LOYALTY.docx

Exhibition and Exchange
Exhibition and ExchangeExhibition and Exchange
Exhibition and Exchangehollyetty123
 
Exhibition and Exchange
Exhibition and ExchangeExhibition and Exchange
Exhibition and Exchangekeshaathakrar
 
Distribution and exhibition
Distribution and exhibitionDistribution and exhibition
Distribution and exhibitionksomel
 
Producers And Audiences CGS
Producers And Audiences CGSProducers And Audiences CGS
Producers And Audiences CGSfilmcgs
 
Distribution lesson part 2
Distribution lesson part 2Distribution lesson part 2
Distribution lesson part 2hasnmedia
 
Jan4th2012
Jan4th2012Jan4th2012
Jan4th2012slayas
 
WCC COMM 101 chapter #6 FILM (updated2) LUTHER
WCC COMM 101 chapter #6 FILM (updated2) LUTHERWCC COMM 101 chapter #6 FILM (updated2) LUTHER
WCC COMM 101 chapter #6 FILM (updated2) LUTHERprofluther
 
Section B Institutions and Audiences Revision Guide
Section B Institutions and Audiences Revision GuideSection B Institutions and Audiences Revision Guide
Section B Institutions and Audiences Revision Guidejphibbert
 
Article Critique - Marketing Myopia.pdf
Article Critique - Marketing Myopia.pdfArticle Critique - Marketing Myopia.pdf
Article Critique - Marketing Myopia.pdfOsayiIkponmwosaEweka1
 
WCC COMM 101 CHAPTER #6
WCC COMM 101 CHAPTER #6WCC COMM 101 CHAPTER #6
WCC COMM 101 CHAPTER #6profluther
 
Film and tv revision
Film and tv revisionFilm and tv revision
Film and tv revisionEmma Leslie
 
Gorman IMAX-marketing plan
Gorman IMAX-marketing planGorman IMAX-marketing plan
Gorman IMAX-marketing planBritanyGorman
 
Triwar Presentation 06/14
Triwar Presentation 06/14Triwar Presentation 06/14
Triwar Presentation 06/14Nicole Kruex
 
Alternative Investment To Startups For VC, Affluent HNW Investors, Hedge Fund...
Alternative Investment To Startups For VC, Affluent HNW Investors, Hedge Fund...Alternative Investment To Startups For VC, Affluent HNW Investors, Hedge Fund...
Alternative Investment To Startups For VC, Affluent HNW Investors, Hedge Fund...Yuri Rutman
 
Production funding
Production fundingProduction funding
Production fundinglucasmcnally
 
New Technologies & Falling Box Office Figures
New Technologies & Falling Box Office FiguresNew Technologies & Falling Box Office Figures
New Technologies & Falling Box Office FiguresSouth Sefton College
 
Customer Relationship Management - Cineplex
Customer Relationship Management - CineplexCustomer Relationship Management - Cineplex
Customer Relationship Management - CineplexJoshua Favaro
 

Similar to S w 908A08 CINEPLEX ENTERTAINMENT THE LOYALTY.docx (20)

Exhibition and Exchange
Exhibition and ExchangeExhibition and Exchange
Exhibition and Exchange
 
Exhibition and Exchange
Exhibition and ExchangeExhibition and Exchange
Exhibition and Exchange
 
Distribution and exhibition
Distribution and exhibitionDistribution and exhibition
Distribution and exhibition
 
Producers And Audiences CGS
Producers And Audiences CGSProducers And Audiences CGS
Producers And Audiences CGS
 
Distribution lesson part 2
Distribution lesson part 2Distribution lesson part 2
Distribution lesson part 2
 
Jan4th2012
Jan4th2012Jan4th2012
Jan4th2012
 
revision
revisionrevision
revision
 
WCC COMM 101 chapter #6 FILM (updated2) LUTHER
WCC COMM 101 chapter #6 FILM (updated2) LUTHERWCC COMM 101 chapter #6 FILM (updated2) LUTHER
WCC COMM 101 chapter #6 FILM (updated2) LUTHER
 
Section B Institutions and Audiences Revision Guide
Section B Institutions and Audiences Revision GuideSection B Institutions and Audiences Revision Guide
Section B Institutions and Audiences Revision Guide
 
Article Critique - Marketing Myopia.pdf
Article Critique - Marketing Myopia.pdfArticle Critique - Marketing Myopia.pdf
Article Critique - Marketing Myopia.pdf
 
WCC COMM 101 CHAPTER #6
WCC COMM 101 CHAPTER #6WCC COMM 101 CHAPTER #6
WCC COMM 101 CHAPTER #6
 
Film and tv revision
Film and tv revisionFilm and tv revision
Film and tv revision
 
Gorman IMAX-marketing plan
Gorman IMAX-marketing planGorman IMAX-marketing plan
Gorman IMAX-marketing plan
 
Triwar Presentation 06/14
Triwar Presentation 06/14Triwar Presentation 06/14
Triwar Presentation 06/14
 
Alternative Investment To Startups For VC, Affluent HNW Investors, Hedge Fund...
Alternative Investment To Startups For VC, Affluent HNW Investors, Hedge Fund...Alternative Investment To Startups For VC, Affluent HNW Investors, Hedge Fund...
Alternative Investment To Startups For VC, Affluent HNW Investors, Hedge Fund...
 
Production funding
Production fundingProduction funding
Production funding
 
Production funding1
Production funding1Production funding1
Production funding1
 
New Technologies & Falling Box Office Figures
New Technologies & Falling Box Office FiguresNew Technologies & Falling Box Office Figures
New Technologies & Falling Box Office Figures
 
Customer Relationship Management - Cineplex
Customer Relationship Management - CineplexCustomer Relationship Management - Cineplex
Customer Relationship Management - Cineplex
 
MGMT 4710 Disney Case
MGMT 4710 Disney CaseMGMT 4710 Disney Case
MGMT 4710 Disney Case
 

More from jeffsrosalyn

Problem 7.  Dollars for WaitingJeffrey Swift has been a messenger.docx
Problem 7.  Dollars for WaitingJeffrey Swift has been a messenger.docxProblem 7.  Dollars for WaitingJeffrey Swift has been a messenger.docx
Problem 7.  Dollars for WaitingJeffrey Swift has been a messenger.docxjeffsrosalyn
 
Problem 8-2B(a) Journalize the transactions, including explanation.docx
Problem 8-2B(a) Journalize the transactions, including explanation.docxProblem 8-2B(a) Journalize the transactions, including explanation.docx
Problem 8-2B(a) Journalize the transactions, including explanation.docxjeffsrosalyn
 
Problem 14-4AFinancial information for Ernie Bishop Company is pre.docx
Problem 14-4AFinancial information for Ernie Bishop Company is pre.docxProblem 14-4AFinancial information for Ernie Bishop Company is pre.docx
Problem 14-4AFinancial information for Ernie Bishop Company is pre.docxjeffsrosalyn
 
Problem and solution essay  about the difficulties of speaking Engli.docx
Problem and solution essay  about the difficulties of speaking Engli.docxProblem and solution essay  about the difficulties of speaking Engli.docx
Problem and solution essay  about the difficulties of speaking Engli.docxjeffsrosalyn
 
problem 8-6 (LO 4) Worksheet, direct and indirect holding, interco.docx
problem 8-6 (LO 4) Worksheet, direct and indirect holding, interco.docxproblem 8-6 (LO 4) Worksheet, direct and indirect holding, interco.docx
problem 8-6 (LO 4) Worksheet, direct and indirect holding, interco.docxjeffsrosalyn
 
Problem 4-5ADevine Brown opened Devine’s Carpet Cleaners on March .docx
Problem 4-5ADevine Brown opened Devine’s Carpet Cleaners on March .docxProblem 4-5ADevine Brown opened Devine’s Carpet Cleaners on March .docx
Problem 4-5ADevine Brown opened Devine’s Carpet Cleaners on March .docxjeffsrosalyn
 
Problem 1-4A (Part Level Submission)Matt Stiner started a delivery.docx
Problem 1-4A (Part Level Submission)Matt Stiner started a delivery.docxProblem 1-4A (Part Level Submission)Matt Stiner started a delivery.docx
Problem 1-4A (Part Level Submission)Matt Stiner started a delivery.docxjeffsrosalyn
 
PROBLEM 5-5BPrepare a correct detailed multiple-step income stat.docx
PROBLEM 5-5BPrepare a correct detailed multiple-step income stat.docxPROBLEM 5-5BPrepare a correct detailed multiple-step income stat.docx
PROBLEM 5-5BPrepare a correct detailed multiple-step income stat.docxjeffsrosalyn
 
Problem 12-9ACondensed financial data of Odgers Inc. follow.ODGE.docx
Problem 12-9ACondensed financial data of Odgers Inc. follow.ODGE.docxProblem 12-9ACondensed financial data of Odgers Inc. follow.ODGE.docx
Problem 12-9ACondensed financial data of Odgers Inc. follow.ODGE.docxjeffsrosalyn
 
Problem 13-6AIrwin Corporation has been authorized to issue 20,80.docx
Problem 13-6AIrwin Corporation has been authorized to issue 20,80.docxProblem 13-6AIrwin Corporation has been authorized to issue 20,80.docx
Problem 13-6AIrwin Corporation has been authorized to issue 20,80.docxjeffsrosalyn
 
Problem 1-2A (Part Level Submission)On August 31, the balance sh.docx
Problem 1-2A (Part Level Submission)On August 31, the balance sh.docxProblem 1-2A (Part Level Submission)On August 31, the balance sh.docx
Problem 1-2A (Part Level Submission)On August 31, the balance sh.docxjeffsrosalyn
 
Problem 1-2A (Part Level Submission)On August 31, the balance shee.docx
Problem 1-2A (Part Level Submission)On August 31, the balance shee.docxProblem 1-2A (Part Level Submission)On August 31, the balance shee.docx
Problem 1-2A (Part Level Submission)On August 31, the balance shee.docxjeffsrosalyn
 
Prior to posting in this discussion, completeThe Parking Garage.docx
Prior to posting in this discussion, completeThe Parking Garage.docxPrior to posting in this discussion, completeThe Parking Garage.docx
Prior to posting in this discussion, completeThe Parking Garage.docxjeffsrosalyn
 
Prior to engaging in this discussion, read Chapters 10 and 11 in y.docx
Prior to engaging in this discussion, read Chapters 10 and 11 in y.docxPrior to engaging in this discussion, read Chapters 10 and 11 in y.docx
Prior to engaging in this discussion, read Chapters 10 and 11 in y.docxjeffsrosalyn
 
Privacy in a Technological AgePrivacy protection is a hot top.docx
Privacy in a Technological AgePrivacy protection is a hot top.docxPrivacy in a Technological AgePrivacy protection is a hot top.docx
Privacy in a Technological AgePrivacy protection is a hot top.docxjeffsrosalyn
 
Privacy Introduction Does the technology today Pene.docx
Privacy Introduction Does the technology today Pene.docxPrivacy Introduction Does the technology today Pene.docx
Privacy Introduction Does the technology today Pene.docxjeffsrosalyn
 
Prisoner rights in America are based largely on the provisions of th.docx
Prisoner rights in America are based largely on the provisions of th.docxPrisoner rights in America are based largely on the provisions of th.docx
Prisoner rights in America are based largely on the provisions of th.docxjeffsrosalyn
 
Principles of Supply and Demanda brief example of supply and deman.docx
Principles of Supply and Demanda brief example of supply and deman.docxPrinciples of Supply and Demanda brief example of supply and deman.docx
Principles of Supply and Demanda brief example of supply and deman.docxjeffsrosalyn
 
Primary Task Response Within the Discussion Board area, write 300.docx
Primary Task Response Within the Discussion Board area, write 300.docxPrimary Task Response Within the Discussion Board area, write 300.docx
Primary Task Response Within the Discussion Board area, write 300.docxjeffsrosalyn
 
Pretend you are a British government official during the time leadin.docx
Pretend you are a British government official during the time leadin.docxPretend you are a British government official during the time leadin.docx
Pretend you are a British government official during the time leadin.docxjeffsrosalyn
 

More from jeffsrosalyn (20)

Problem 7.  Dollars for WaitingJeffrey Swift has been a messenger.docx
Problem 7.  Dollars for WaitingJeffrey Swift has been a messenger.docxProblem 7.  Dollars for WaitingJeffrey Swift has been a messenger.docx
Problem 7.  Dollars for WaitingJeffrey Swift has been a messenger.docx
 
Problem 8-2B(a) Journalize the transactions, including explanation.docx
Problem 8-2B(a) Journalize the transactions, including explanation.docxProblem 8-2B(a) Journalize the transactions, including explanation.docx
Problem 8-2B(a) Journalize the transactions, including explanation.docx
 
Problem 14-4AFinancial information for Ernie Bishop Company is pre.docx
Problem 14-4AFinancial information for Ernie Bishop Company is pre.docxProblem 14-4AFinancial information for Ernie Bishop Company is pre.docx
Problem 14-4AFinancial information for Ernie Bishop Company is pre.docx
 
Problem and solution essay  about the difficulties of speaking Engli.docx
Problem and solution essay  about the difficulties of speaking Engli.docxProblem and solution essay  about the difficulties of speaking Engli.docx
Problem and solution essay  about the difficulties of speaking Engli.docx
 
problem 8-6 (LO 4) Worksheet, direct and indirect holding, interco.docx
problem 8-6 (LO 4) Worksheet, direct and indirect holding, interco.docxproblem 8-6 (LO 4) Worksheet, direct and indirect holding, interco.docx
problem 8-6 (LO 4) Worksheet, direct and indirect holding, interco.docx
 
Problem 4-5ADevine Brown opened Devine’s Carpet Cleaners on March .docx
Problem 4-5ADevine Brown opened Devine’s Carpet Cleaners on March .docxProblem 4-5ADevine Brown opened Devine’s Carpet Cleaners on March .docx
Problem 4-5ADevine Brown opened Devine’s Carpet Cleaners on March .docx
 
Problem 1-4A (Part Level Submission)Matt Stiner started a delivery.docx
Problem 1-4A (Part Level Submission)Matt Stiner started a delivery.docxProblem 1-4A (Part Level Submission)Matt Stiner started a delivery.docx
Problem 1-4A (Part Level Submission)Matt Stiner started a delivery.docx
 
PROBLEM 5-5BPrepare a correct detailed multiple-step income stat.docx
PROBLEM 5-5BPrepare a correct detailed multiple-step income stat.docxPROBLEM 5-5BPrepare a correct detailed multiple-step income stat.docx
PROBLEM 5-5BPrepare a correct detailed multiple-step income stat.docx
 
Problem 12-9ACondensed financial data of Odgers Inc. follow.ODGE.docx
Problem 12-9ACondensed financial data of Odgers Inc. follow.ODGE.docxProblem 12-9ACondensed financial data of Odgers Inc. follow.ODGE.docx
Problem 12-9ACondensed financial data of Odgers Inc. follow.ODGE.docx
 
Problem 13-6AIrwin Corporation has been authorized to issue 20,80.docx
Problem 13-6AIrwin Corporation has been authorized to issue 20,80.docxProblem 13-6AIrwin Corporation has been authorized to issue 20,80.docx
Problem 13-6AIrwin Corporation has been authorized to issue 20,80.docx
 
Problem 1-2A (Part Level Submission)On August 31, the balance sh.docx
Problem 1-2A (Part Level Submission)On August 31, the balance sh.docxProblem 1-2A (Part Level Submission)On August 31, the balance sh.docx
Problem 1-2A (Part Level Submission)On August 31, the balance sh.docx
 
Problem 1-2A (Part Level Submission)On August 31, the balance shee.docx
Problem 1-2A (Part Level Submission)On August 31, the balance shee.docxProblem 1-2A (Part Level Submission)On August 31, the balance shee.docx
Problem 1-2A (Part Level Submission)On August 31, the balance shee.docx
 
Prior to posting in this discussion, completeThe Parking Garage.docx
Prior to posting in this discussion, completeThe Parking Garage.docxPrior to posting in this discussion, completeThe Parking Garage.docx
Prior to posting in this discussion, completeThe Parking Garage.docx
 
Prior to engaging in this discussion, read Chapters 10 and 11 in y.docx
Prior to engaging in this discussion, read Chapters 10 and 11 in y.docxPrior to engaging in this discussion, read Chapters 10 and 11 in y.docx
Prior to engaging in this discussion, read Chapters 10 and 11 in y.docx
 
Privacy in a Technological AgePrivacy protection is a hot top.docx
Privacy in a Technological AgePrivacy protection is a hot top.docxPrivacy in a Technological AgePrivacy protection is a hot top.docx
Privacy in a Technological AgePrivacy protection is a hot top.docx
 
Privacy Introduction Does the technology today Pene.docx
Privacy Introduction Does the technology today Pene.docxPrivacy Introduction Does the technology today Pene.docx
Privacy Introduction Does the technology today Pene.docx
 
Prisoner rights in America are based largely on the provisions of th.docx
Prisoner rights in America are based largely on the provisions of th.docxPrisoner rights in America are based largely on the provisions of th.docx
Prisoner rights in America are based largely on the provisions of th.docx
 
Principles of Supply and Demanda brief example of supply and deman.docx
Principles of Supply and Demanda brief example of supply and deman.docxPrinciples of Supply and Demanda brief example of supply and deman.docx
Principles of Supply and Demanda brief example of supply and deman.docx
 
Primary Task Response Within the Discussion Board area, write 300.docx
Primary Task Response Within the Discussion Board area, write 300.docxPrimary Task Response Within the Discussion Board area, write 300.docx
Primary Task Response Within the Discussion Board area, write 300.docx
 
Pretend you are a British government official during the time leadin.docx
Pretend you are a British government official during the time leadin.docxPretend you are a British government official during the time leadin.docx
Pretend you are a British government official during the time leadin.docx
 

Recently uploaded

SOCIAL AND HISTORICAL CONTEXT - LFTVD.pptx
SOCIAL AND HISTORICAL CONTEXT - LFTVD.pptxSOCIAL AND HISTORICAL CONTEXT - LFTVD.pptx
SOCIAL AND HISTORICAL CONTEXT - LFTVD.pptxiammrhaywood
 
1029 - Danh muc Sach Giao Khoa 10 . pdf
1029 -  Danh muc Sach Giao Khoa 10 . pdf1029 -  Danh muc Sach Giao Khoa 10 . pdf
1029 - Danh muc Sach Giao Khoa 10 . pdfQucHHunhnh
 
Paris 2024 Olympic Geographies - an activity
Paris 2024 Olympic Geographies - an activityParis 2024 Olympic Geographies - an activity
Paris 2024 Olympic Geographies - an activityGeoBlogs
 
CARE OF CHILD IN INCUBATOR..........pptx
CARE OF CHILD IN INCUBATOR..........pptxCARE OF CHILD IN INCUBATOR..........pptx
CARE OF CHILD IN INCUBATOR..........pptxGaneshChakor2
 
The byproduct of sericulture in different industries.pptx
The byproduct of sericulture in different industries.pptxThe byproduct of sericulture in different industries.pptx
The byproduct of sericulture in different industries.pptxShobhayan Kirtania
 
Nutritional Needs Presentation - HLTH 104
Nutritional Needs Presentation - HLTH 104Nutritional Needs Presentation - HLTH 104
Nutritional Needs Presentation - HLTH 104misteraugie
 
Activity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdfActivity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdfciinovamais
 
Web & Social Media Analytics Previous Year Question Paper.pdf
Web & Social Media Analytics Previous Year Question Paper.pdfWeb & Social Media Analytics Previous Year Question Paper.pdf
Web & Social Media Analytics Previous Year Question Paper.pdfJayanti Pande
 
POINT- BIOCHEMISTRY SEM 2 ENZYMES UNIT 5.pptx
POINT- BIOCHEMISTRY SEM 2 ENZYMES UNIT 5.pptxPOINT- BIOCHEMISTRY SEM 2 ENZYMES UNIT 5.pptx
POINT- BIOCHEMISTRY SEM 2 ENZYMES UNIT 5.pptxSayali Powar
 
Z Score,T Score, Percential Rank and Box Plot Graph
Z Score,T Score, Percential Rank and Box Plot GraphZ Score,T Score, Percential Rank and Box Plot Graph
Z Score,T Score, Percential Rank and Box Plot GraphThiyagu K
 
Russian Call Girls in Andheri Airport Mumbai WhatsApp 9167673311 💞 Full Nigh...
Russian Call Girls in Andheri Airport Mumbai WhatsApp  9167673311 💞 Full Nigh...Russian Call Girls in Andheri Airport Mumbai WhatsApp  9167673311 💞 Full Nigh...
Russian Call Girls in Andheri Airport Mumbai WhatsApp 9167673311 💞 Full Nigh...Pooja Nehwal
 
Arihant handbook biology for class 11 .pdf
Arihant handbook biology for class 11 .pdfArihant handbook biology for class 11 .pdf
Arihant handbook biology for class 11 .pdfchloefrazer622
 
BASLIQ CURRENT LOOKBOOK LOOKBOOK(1) (1).pdf
BASLIQ CURRENT LOOKBOOK  LOOKBOOK(1) (1).pdfBASLIQ CURRENT LOOKBOOK  LOOKBOOK(1) (1).pdf
BASLIQ CURRENT LOOKBOOK LOOKBOOK(1) (1).pdfSoniaTolstoy
 
Introduction to Nonprofit Accounting: The Basics
Introduction to Nonprofit Accounting: The BasicsIntroduction to Nonprofit Accounting: The Basics
Introduction to Nonprofit Accounting: The BasicsTechSoup
 
Sports & Fitness Value Added Course FY..
Sports & Fitness Value Added Course FY..Sports & Fitness Value Added Course FY..
Sports & Fitness Value Added Course FY..Disha Kariya
 
9548086042 for call girls in Indira Nagar with room service
9548086042  for call girls in Indira Nagar  with room service9548086042  for call girls in Indira Nagar  with room service
9548086042 for call girls in Indira Nagar with room servicediscovermytutordmt
 
Interactive Powerpoint_How to Master effective communication
Interactive Powerpoint_How to Master effective communicationInteractive Powerpoint_How to Master effective communication
Interactive Powerpoint_How to Master effective communicationnomboosow
 
mini mental status format.docx
mini    mental       status     format.docxmini    mental       status     format.docx
mini mental status format.docxPoojaSen20
 
Measures of Central Tendency: Mean, Median and Mode
Measures of Central Tendency: Mean, Median and ModeMeasures of Central Tendency: Mean, Median and Mode
Measures of Central Tendency: Mean, Median and ModeThiyagu K
 
Ecosystem Interactions Class Discussion Presentation in Blue Green Lined Styl...
Ecosystem Interactions Class Discussion Presentation in Blue Green Lined Styl...Ecosystem Interactions Class Discussion Presentation in Blue Green Lined Styl...
Ecosystem Interactions Class Discussion Presentation in Blue Green Lined Styl...fonyou31
 

Recently uploaded (20)

SOCIAL AND HISTORICAL CONTEXT - LFTVD.pptx
SOCIAL AND HISTORICAL CONTEXT - LFTVD.pptxSOCIAL AND HISTORICAL CONTEXT - LFTVD.pptx
SOCIAL AND HISTORICAL CONTEXT - LFTVD.pptx
 
1029 - Danh muc Sach Giao Khoa 10 . pdf
1029 -  Danh muc Sach Giao Khoa 10 . pdf1029 -  Danh muc Sach Giao Khoa 10 . pdf
1029 - Danh muc Sach Giao Khoa 10 . pdf
 
Paris 2024 Olympic Geographies - an activity
Paris 2024 Olympic Geographies - an activityParis 2024 Olympic Geographies - an activity
Paris 2024 Olympic Geographies - an activity
 
CARE OF CHILD IN INCUBATOR..........pptx
CARE OF CHILD IN INCUBATOR..........pptxCARE OF CHILD IN INCUBATOR..........pptx
CARE OF CHILD IN INCUBATOR..........pptx
 
The byproduct of sericulture in different industries.pptx
The byproduct of sericulture in different industries.pptxThe byproduct of sericulture in different industries.pptx
The byproduct of sericulture in different industries.pptx
 
Nutritional Needs Presentation - HLTH 104
Nutritional Needs Presentation - HLTH 104Nutritional Needs Presentation - HLTH 104
Nutritional Needs Presentation - HLTH 104
 
Activity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdfActivity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdf
 
Web & Social Media Analytics Previous Year Question Paper.pdf
Web & Social Media Analytics Previous Year Question Paper.pdfWeb & Social Media Analytics Previous Year Question Paper.pdf
Web & Social Media Analytics Previous Year Question Paper.pdf
 
POINT- BIOCHEMISTRY SEM 2 ENZYMES UNIT 5.pptx
POINT- BIOCHEMISTRY SEM 2 ENZYMES UNIT 5.pptxPOINT- BIOCHEMISTRY SEM 2 ENZYMES UNIT 5.pptx
POINT- BIOCHEMISTRY SEM 2 ENZYMES UNIT 5.pptx
 
Z Score,T Score, Percential Rank and Box Plot Graph
Z Score,T Score, Percential Rank and Box Plot GraphZ Score,T Score, Percential Rank and Box Plot Graph
Z Score,T Score, Percential Rank and Box Plot Graph
 
Russian Call Girls in Andheri Airport Mumbai WhatsApp 9167673311 💞 Full Nigh...
Russian Call Girls in Andheri Airport Mumbai WhatsApp  9167673311 💞 Full Nigh...Russian Call Girls in Andheri Airport Mumbai WhatsApp  9167673311 💞 Full Nigh...
Russian Call Girls in Andheri Airport Mumbai WhatsApp 9167673311 💞 Full Nigh...
 
Arihant handbook biology for class 11 .pdf
Arihant handbook biology for class 11 .pdfArihant handbook biology for class 11 .pdf
Arihant handbook biology for class 11 .pdf
 
BASLIQ CURRENT LOOKBOOK LOOKBOOK(1) (1).pdf
BASLIQ CURRENT LOOKBOOK  LOOKBOOK(1) (1).pdfBASLIQ CURRENT LOOKBOOK  LOOKBOOK(1) (1).pdf
BASLIQ CURRENT LOOKBOOK LOOKBOOK(1) (1).pdf
 
Introduction to Nonprofit Accounting: The Basics
Introduction to Nonprofit Accounting: The BasicsIntroduction to Nonprofit Accounting: The Basics
Introduction to Nonprofit Accounting: The Basics
 
Sports & Fitness Value Added Course FY..
Sports & Fitness Value Added Course FY..Sports & Fitness Value Added Course FY..
Sports & Fitness Value Added Course FY..
 
9548086042 for call girls in Indira Nagar with room service
9548086042  for call girls in Indira Nagar  with room service9548086042  for call girls in Indira Nagar  with room service
9548086042 for call girls in Indira Nagar with room service
 
Interactive Powerpoint_How to Master effective communication
Interactive Powerpoint_How to Master effective communicationInteractive Powerpoint_How to Master effective communication
Interactive Powerpoint_How to Master effective communication
 
mini mental status format.docx
mini    mental       status     format.docxmini    mental       status     format.docx
mini mental status format.docx
 
Measures of Central Tendency: Mean, Median and Mode
Measures of Central Tendency: Mean, Median and ModeMeasures of Central Tendency: Mean, Median and Mode
Measures of Central Tendency: Mean, Median and Mode
 
Ecosystem Interactions Class Discussion Presentation in Blue Green Lined Styl...
Ecosystem Interactions Class Discussion Presentation in Blue Green Lined Styl...Ecosystem Interactions Class Discussion Presentation in Blue Green Lined Styl...
Ecosystem Interactions Class Discussion Presentation in Blue Green Lined Styl...
 

S w 908A08 CINEPLEX ENTERTAINMENT THE LOYALTY.docx

  • 1. S w 908A08 CINEPLEX ENTERTAINMENT: THE LOYALTY PROGRAM Renée Zatzman wrote this case under the supervision of Professor Kenneth G. Hardy solely to provide material for class discussion. The authors do not intend to illustrate either effective or ineffective handling of a managerial situation. The authors may have disguised certain names and other identifying information to protect confidentiality. Ivey Management Services prohibits any form of reproduction, storage or transmittal without its written permission. Reproduction of this material is not covered under authorization by any reproduction rights organization. To order copies or request permission to reproduce materials, contact Ivey Publishing, Ivey Management Services, c/o Richard Ivey School of Business, The University of Western Ontario, London, Ontario, Canada, N6A 3K7; phone (519) 661-3208; fax (519) 661-3882; e-mail [email protected] Copyright © 2008, Ivey Management Services Version: (A)
  • 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
  • 34. Exhibit 7 LARGE MEDIA MARKETS SMALL- AND MEDIUM SIZED MEDIA MARKETS
  • 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
  • 43. float Xrot, Yrot; // rotation angles in degrees float time_anima; bool LineOn = true; bool PointOn = true; struct Point { float x0, y0, z0; // initial coordinates float x, y, z; // animated coordinates void setPt(float x0, float y0, float z0) { this->x0 = x0; this->y0 = y0; this->z0 = z0; } void reset() { x = x0; y = y0; z = z0; } };
  • 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();
  • 53. line2.color(0., 1., 1.); line2.p0.setPt(1, 1, 0); line2.p1.setPt(1.5 + (Time), .5, 0); line2.p2.setPt(1.5 + (Time), -.5, 0); line2.p3.setPt(1, -1, 0); line2.reset(); line3.color(0., 1., 1.); line3.p0.setPt(-1, 1, 2); line3.p1.setPt(-1.5 - (Time), .5, 2); line3.p2.setPt(-1.5 - (Time), -.5, 2); line3.p3.setPt(-1, -1, 2); line3.reset(); line4.color(0., 1., 1.); line4.p0.setPt(1, 1, 2); line4.p1.setPt(1.5 + (Time), .5, 2); line4.p2.setPt(1.5 + (Time), -.5, 2);
  • 54. line4.p3.setPt(1, -1, 2); line4.reset(); line5.color(1., 1., 0.); line5.p0.setPt(1, 1, 0); line5.p1.setPt(.5, 1.5 + (Time), 0); line5.p2.setPt(-.5, 1.5 + (Time), 0); line5.p3.setPt(-1, 1, 0); line5.reset(); line6.color(1., 1., 0.); line6.p0.setPt(1, -1, 0); line6.p1.setPt(.5, -1.5 - (Time), 0); line6.p2.setPt(-.5, -1.5 - (Time), 0); line6.p3.setPt(-1, -1, 0); line6.reset(); line7.color(1., 1., 0.);
  • 55. line7.p0.setPt(1, 1, 2); line7.p1.setPt(.5, 1.5 + (Time), 2); line7.p2.setPt(-.5, 1.5 + (Time), 2); line7.p3.setPt(-1, 1, 2); line7.reset(); line8.color(1., 1., 0.); line8.p0.setPt(1, -1, 2); line8.p1.setPt(.5, -1.5 - (Time), 2); line8.p2.setPt(-.5, -1.5 - (Time), 2); line8.p3.setPt(-1, -1, 2); line8.reset(); line9.color(0., 1., 0.); line9.p0.setPt(-1, 1, 0); line9.p1.setPt(-1, 1, .5); line9.p2.setPt(-1, 1, 1.5); line9.p3.setPt(-1, 1, 2);
  • 56. line9.reset(); line10.color(0., 1., 0.); line10.p0.setPt(-1, -1, 0); line10.p1.setPt(-1, -1, .5); line10.p2.setPt(-1, -1, 1.5); line10.p3.setPt(-1, -1, 2); line10.reset(); line11.color(0., 1., 0.); line11.p0.setPt(1, -1, 0); line11.p1.setPt(1, -1, .5); line11.p2.setPt(1, -1, 1.5); line11.p3.setPt(1, -1, 2); line11.reset(); line12.color(0., 1., 0.); line12.p0.setPt(1, 1, 0);
  • 57. line12.p1.setPt(1, 1, .5); line12.p2.setPt(1, 1, 1.5); line12.p3.setPt(1, 1, 2); line12.reset(); CreateBezierCur(line1, 12); printf("%fn", line1.p1.x); CreateBezierCur(line2, 12); CreateBezierCur(line3, 12); CreateBezierCur(line4, 12); CreateBezierCur(line5, 12); CreateBezierCur(line6, 12); CreateBezierCur(line7, 12);
  • 58. CreateBezierCur(line8, 12); CreateBezierCur(line9, 12); CreateBezierCur(line10, 12); CreateBezierCur(line11, 12); CreateBezierCur(line12, 12); // draw the current object: // draw some gratuitous text that just rotates on top of the scene:
  • 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( )
  • 64. { 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 ); 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 ); int projmenu = glutCreateMenu( DoProjectMenu ); glutAddMenuEntry( "Orthographic", ORTHO ); glutAddMenuEntry( "Perspective", PERSP );
  • 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
  • 67. glutSetWindow( MainWindow ); glutDisplayFunc( Display ); glutReshapeFunc( Resize ); glutKeyboardFunc( Keyboard ); glutMouseFunc( MouseButton ); glutMotionFunc( MouseMotion ); glutPassiveMotionFunc( NULL ); glutVisibilityFunc( Visibility ); glutEntryFunc( NULL ); glutSpecialFunc( NULL ); glutSpaceballMotionFunc( NULL ); glutSpaceballRotateFunc( NULL ); glutSpaceballButtonFunc( NULL ); glutButtonBoxFunc( NULL ); glutDialsFunc( NULL ); glutTabletMotionFunc( NULL ); glutTabletButtonFunc( NULL ); 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
  • 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 );
  • 69. glTexCoord2f( 1., 1. ); glVertex3f( dx, dy, -dz ); glTexCoord2f( 1., 0. ); glVertex3f( dx, -dy, -dz ); glColor3f( 1., 0., 0. ); glNormal3f( 1., 0., 0. ); glVertex3f( dx, -dy, dz ); glVertex3f( dx, -dy, -dz ); glVertex3f( dx, dy, -dz ); glVertex3f( dx, dy, dz ); glNormal3f( -1., 0., 0. ); glVertex3f( -dx, -dy, dz ); glVertex3f( -dx, dy, dz ); glVertex3f( -dx, dy, -dz ); glVertex3f( -dx, -dy, -dz ); glColor3f( 0., 1., 0. ); glNormal3f( 0., 1., 0. ); glVertex3f( -dx, dy, dz ); glVertex3f( dx, dy, dz ); glVertex3f( dx, dy, -dz ); glVertex3f( -dx, dy, -dz ); glNormal3f( 0., -1., 0. ); glVertex3f( -dx, -dy, dz ); glVertex3f( -dx, -dy, -dz ); glVertex3f( dx, -dy, -dz ); glVertex3f( dx, -dy, dz ); glEnd( ); glEndList( );
  • 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 };
  • 76. static float xy[ ] = { -.5f, .5f, .5f, -.5f }; static int xorder[ ] = { 1, 2, -3, 4 }; static float yx[ ] = { 0.f, 0.f, -.5f, .5f }; static float yy[ ] = { 0.f, .6f, 1.f, 1.f }; static int yorder[ ] = { 1, 2, 3, -2, 4 }; 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;
  • 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; }
  • 81. rgb[0] = r; rgb[1] = g; rgb[2] = b; } void CreateBezierCur(Curve curve, GLfloat width) { if (LineOn) { glBegin(GL_LINE_STRIP); glColor3f(0.8, 0.8, 0.8); glVertex3f(curve.p0.x, curve.p0.y, curve.p0.z); glVertex3f(curve.p1.x, curve.p1.y, curve.p1.z); glVertex3f(curve.p2.x, curve.p2.y, curve.p2.z); glVertex3f(curve.p3.x, curve.p3.y, curve.p3.z); glEnd(); } if (PointOn) { glPushMatrix(); glTranslatef(curve.p0.x, curve.p0.y, curve.p0.z);
  • 82. glColor3f(1, 1, 1); glutSolidSphere(0.04, 50, 50); glPopMatrix(); glPushMatrix(); glTranslatef(curve.p1.x, curve.p1.y, curve.p1.z); glColor3f(0.8, 0.8, 0.8); glutSolidSphere(0.03, 50, 50); glPopMatrix(); glPushMatrix(); glTranslatef(curve.p2.x, curve.p2.y, curve.p2.z); glColor3f(0.8, 0.8, 0.8); glutSolidSphere(0.03, 50, 50); glPopMatrix(); glPushMatrix(); glTranslatef(curve.p3.x, curve.p3.y, curve.p3.z);
  • 83. glColor3f(1, 1, 1); glutSolidSphere(0.04, 50, 50); glPopMatrix(); } glLineWidth(width); glColor3f(curve.r, curve.g, curve.b); glBegin(GL_LINE_STRIP); for (int it = 0; it <= NUMPOINTS; it++) { float t = (float)it / (float)NUMPOINTS; float omt = 1.f - t; float x = omt * omt*omt*curve.p0.x + 3.f*t*omt*omt*curve.p1.x + 3.f*t*t*omt*curve.p2.x + t * t*t*curve.p3.x; float y = omt * omt*omt*curve.p0.y + 3.f*t*omt*omt*curve.p1.y + 3.f*t*t*omt*curve.p2.y + t * t*t*curve.p3.y; float z = omt * omt*omt*curve.p0.z + 3.f*t*omt*omt*curve.p1.z + 3.f*t*t*omt*curve.p2.z + t * t*t*curve.p3.z; glVertex3f(x, y, z);
  • 84. } glEnd(); glLineWidth(1.); } void setanimatedcoor(Curve* curve) { float time = Time; curve->p0.x = curve->p0.x0; curve->p0.y = curve->p0.y0; curve->p0.z = curve->p0.z0; curve->p1.x = curve->p1.x0 + curve->p1.x0 * sinf(time * M_PI); curve->p1.y = curve->p1.y0 + curve->p1.y0 * sinf(time * M_PI); curve->p1.z = curve->p1.z0 + curve->p1.z0 * sinf(time * M_PI); curve->p2.x = curve->p2.x0 + curve->p2.x0 * sinf(time * M_PI);
  • 85. curve->p2.y = curve->p2.y0 + curve->p2.y0 * sinf(time * M_PI); curve->p2.z = curve->p2.z0 + curve->p2.z0 * sinf(time * M_PI); curve->p3.x = curve->p3.x0; curve->p3.y = curve->p3.y0; curve->p3.z = curve->p3.z0; } #include <stdio.h> #include <stdlib.h> #include <ctype.h> #define _USE_MATH_DEFINES #include <math.h> #ifdef WIN32 #include <windows.h>
  • 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,
  • 91. PERSP }; // which button: enum ButtonVals { RESET, QUIT }; // window background color (rgba): const GLfloat BACKCOLOR[] = { 0., 0., 0., 1. };
  • 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
  • 94. 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 { 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 };
  • 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*);
  • 97. float texDistort; shortReadShort(FILE*); struct bmfh { short bfType; int bfSize; short bfReserved1; short bfReserved2; int bfOffBits; } FileHeader; struct bmih { int biSize; int biWidth; int biHeight; short biPlanes; short biBitCount;
  • 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.;
  • 100. return array; } float* MulArray3(float factor, float array0[3]) { static float array[4]; array[0] = factor * array0[0]; array[1] = factor * array0[1]; array[2] = factor * array0[2]; array[3] = 1.; return array; } void MaterialSetting(float r, float g, float b, float shininess) { glMaterialfv(GL_BACK, GL_EMISSION, Array3(0., 0., 0.)); glMaterialfv(GL_BACK, GL_AMBIENT, MulArray3(.4f, Gray));
  • 101. glMaterialfv(GL_BACK, GL_DIFFUSE, MulArray3(1., Gray)); glMaterialfv(GL_BACK, GL_SPECULAR, Array3(0., 0., 0.)); glMaterialf(GL_BACK, GL_SHININESS, 2.f); glMaterialfv(GL_FRONT, GL_EMISSION, Array3(0., 0., 0.)); glMaterialfv(GL_FRONT, GL_AMBIENT, Array3(r, g, b)); glMaterialfv(GL_FRONT, GL_DIFFUSE, Array3(r, g, b)); glMaterialfv(GL_FRONT, GL_SPECULAR, MulArray3(.8f, White)); glMaterialf(GL_FRONT, GL_SHININESS, shininess); } void SetPointLight(int ilight, float x, float y, float z, float r, float g, float b) { glLightfv(ilight, GL_POSITION, Array3(x, y, z));
  • 102. glLightfv(ilight, GL_AMBIENT, Array3(0., 0., 0.)); glLightfv(ilight, GL_DIFFUSE, Array3(r, g, b)); glLightfv(ilight, GL_SPECULAR, Array3(r, g, b)); glLightf(ilight, GL_CONSTANT_ATTENUATION, 1.); glLightf(ilight, GL_LINEAR_ATTENUATION, 0.); glLightf(ilight, GL_QUADRATIC_ATTENUATION, 0.); glEnable(ilight); } void SetSpotLight(int ilight, float x, float y, float z, float xdir, float ydir, float zdir, float r, float g, float b) { glLightfv(ilight, GL_POSITION, Array3(x, y, z)); glLightfv(ilight, GL_SPOT_DIRECTION, Array3(xdir, ydir, zdir)); glLightf(ilight, GL_SPOT_EXPONENT, 1.); glLightf(ilight, GL_SPOT_CUTOFF, 45.); glLightfv(ilight, GL_AMBIENT, Array3(0., 0., 0.));
  • 103. glLightfv(ilight, GL_DIFFUSE, Array3(r, g, b)); glLightfv(ilight, GL_SPECULAR, Array3(r, g, b)); glLightf(ilight, GL_CONSTANT_ATTENUATION, 1.); glLightf(ilight, GL_LINEAR_ATTENUATION, 0.); glLightf(ilight, GL_QUADRATIC_ATTENUATION, 0.); glEnable(ilight); } const int birgb = { 0 }; // function prototypes: void Animate(); void Display(); void DoAxesMenu(int); void DoColorMenu(int); void DoDepthBufferMenu(int);
  • 104. 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);
  • 105. void Axes(float); void HsvRgb(float[3], float[3]); void MaterialSetting(float, float, float, float); void SetPointLight(int, float, float, float, float, float, float); void SetSpotLight(int, float, float, float, float, float, float, float, float, float); void MjbSphere(float radius, int slices, int stacks); void DrawPoint(struct point* p); unsigned char* BmpToTexture(char* filename, int* width, int* height); // main program: int main(int argc, char *argv[]) {
  • 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);
  • 117. unsigned char* Texture; Texture = BmpToTexture("worldtexture.bmp", &bmp_width, &bmp_height); int level, ncomps, border; level = 0; ncomps = 4; border = 0; glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glGenTextures(1, &tex01); glBindTexture(GL_TEXTURE_2D, tex01); glTexImage2D(GL_TEXTURE_2D, level, ncomps, bmp_width, bmp_height, border, GL_RGB, GL_UNSIGNED_BYTE, Texture); glEnable(GL_NORMALIZE);
  • 118. glLightModelfv(GL_LIGHT_MODEL_AMBIENT, MulArray3(.2, White)); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); glPushMatrix(); glShadeModel(GL_SMOOTH); glTranslatef(0., 0., 1.5); MaterialSetting(1., 1., 1., 50.); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexEnvf(GL_TEXTURE_ENV,
  • 119. GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnable(GL_TEXTURE_2D); MjbSphere(0.5, 100, 100); glPopMatrix(); glDisable(GL_TEXTURE_2D); glPushMatrix(); glShadeModel(GL_SMOOTH); glTranslatef(0., 1.5, 0.); glRotatef(90, 1., 0., 0.); glScalef(1., 1., 0.5); MaterialSetting(0., 1., 0., 50.); glutSolidTorus(0.5, 1., 30., 30.); glPopMatrix(); glPushMatrix(); glShadeModel(GL_FLAT);
  • 120. glTranslatef(0., 2 * cos(Time * M_PI * 2), 0.); glDisable(GL_LIGHTING); glColor3f(1., 0., 0.); glutSolidCube(0.5); glPopMatrix(); SetPointLight(GL_LIGHT0, 1., 0., 0.5, 1., 1., 1.); glPushMatrix(); glDisable(GL_LIGHTING); glColor3f(1., 1., 0.); glTranslatef(1., 0., 0.5); glutSolidSphere(0.1, 50, 50); glEnd(); glEnable(GL_LIGHTING); glPopMatrix(); SetPointLight(GL_LIGHT1, 0., 2 * cos(Time * M_PI * 2) + 0.2, 0., 0., 0., 1.);
  • 121. glPushMatrix(); glDisable(GL_LIGHTING); glColor3f(0., 0., 1.); glTranslatef(0., 2 * cos(Time * M_PI * 2) + 0.25, 0.); glutSolidSphere(0.1, 50, 50); glEnd(); glEnable(GL_LIGHTING); glPopMatrix(); SetSpotLight(GL_LIGHT2, 0., 0., 0.5, 0., 0., 5., 1., 0., 0.); glPushMatrix(); glDisable(GL_LIGHTING); glColor3f(1., 0., 1.); glTranslatef(0., 0., 0.5); glutSolidSphere(0.1, 50., 50.); glEnd(); glEnable(GL_LIGHTING); glPopMatrix();
  • 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;
  • 123. glutSetWindow(MainWindow); glutPostRedisplay(); } void DoColorMenu(int id) { WhichColor = id - RED; glutSetWindow(MainWindow); glutPostRedisplay(); } void DoDebugMenu(int id)
  • 124. { DebugOn = id; glutSetWindow(MainWindow); glutPostRedisplay(); } void DoDepthBufferMenu(int id) { DepthBufferOn = id; glutSetWindow(MainWindow); glutPostRedisplay(); }
  • 125. void DoDepthFightingMenu(int id) { DepthFightingOn = id; glutSetWindow(MainWindow); glutPostRedisplay(); } void DoDepthMenu(int id) { DepthCueOn = id; glutSetWindow(MainWindow); glutPostRedisplay(); }
  • 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);
  • 127. glFinish(); glutDestroyWindow(MainWindow); exit(0); break; default: fprintf(stderr, "Don't know what to do with Main Menu ID %dn", id); } glutSetWindow(MainWindow); glutPostRedisplay(); } void DoProjectMenu(int id) {
  • 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:
  • 140. 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); glTexCoord2f(1., 1.);
  • 141. glVertex3f(dx, dy, -dz); glTexCoord2f(1., 0.); glVertex3f(dx, -dy, -dz); glColor3f(1., 0., 0.); glNormal3f(1., 0., 0.); glVertex3f(dx, -dy, dz); glVertex3f(dx, -dy, -dz); glVertex3f(dx, dy, -dz); glVertex3f(dx, dy, dz); glNormal3f(-1., 0., 0.); glVertex3f(-dx, -dy, dz); glVertex3f(-dx, dy, dz); glVertex3f(-dx, dy, -dz); glVertex3f(-dx, -dy, -dz); glColor3f(0., 1., 0.);
  • 142. glNormal3f(0., 1., 0.); glVertex3f(-dx, dy, dz); glVertex3f(dx, dy, dz); glVertex3f(dx, dy, -dz); glVertex3f(-dx, dy, -dz); glNormal3f(0., -1., 0.); glVertex3f(-dx, -dy, dz); glVertex3f(-dx, -dy, -dz); glVertex3f(dx, -dy, -dz); glVertex3f(dx, -dy, dz); glEnd(); glEndList(); // create the axes:
  • 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 };
  • 155. static float xy[] = { -.5f, .5f, .5f, -.5f }; static int xorder[] = { 1, 2, -3, 4 }; static float yx[] = { 0.f, 0.f, -.5f, .5f }; static float yy[] = { 0.f, .6f, 1.f, 1.f }; static int yorder[] = { 1, 2, 3, -2, 4
  • 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;
  • 167. } FileHeader.bfSize = ReadInt(fp); FileHeader.bfReserved1 = ReadShort(fp); FileHeader.bfReserved2 = ReadShort(fp); FileHeader.bfOffBits = ReadInt(fp); InfoHeader.biSize = ReadInt(fp); InfoHeader.biWidth = ReadInt(fp); InfoHeader.biHeight = ReadInt(fp); nums = InfoHeader.biWidth; numt = InfoHeader.biHeight; InfoHeader.biPlanes = ReadShort(fp); InfoHeader.biBitCount = ReadShort(fp);
  • 168. InfoHeader.biCompression = ReadInt(fp); InfoHeader.biSizeImage = ReadInt(fp); InfoHeader.biXPelsPerMeter = ReadInt(fp); InfoHeader.biYPelsPerMeter = ReadInt(fp); InfoHeader.biClrUsed = ReadInt(fp); InfoHeader.biClrImportant = ReadInt(fp); // fprintf( stderr, "Image size found: %d x %dn", ImageWidth, ImageHeight ); texture = new unsigned char[3 * nums * numt]; if (texture == NULL) { fprintf(stderr, "Cannot allocate the texture array!b"); return NULL; }
  • 169. // extra padding bytes: numextra = 4 * (((3 * InfoHeader.biWidth) + 3) / 4) - 3 * InfoHeader.biWidth; // we do not support compression: if (InfoHeader.biCompression != birgb) { fprintf(stderr, "Wrong type of image compression: %dn", InfoHeader.biCompression); fclose(fp); return NULL; }