• Nenhum resultado encontrado

Introdução à Engenharia ENG1000

N/A
N/A
Protected

Academic year: 2021

Share "Introdução à Engenharia ENG1000"

Copied!
44
0
0

Texto

(1)

Aula 16 – Pseudo3D em Jogos de Corrida

Introdução à Engenharia

ENG1000

2018.1

Prof. Pedro Sampaio

(2)

Pseudo3D vs 3D

Forza Horizon 3 Top Gear 2

Câmera fixa;

Sprites 2D;

Matemática simplificada;

Pipeline gráfico pequeno

e simples.

Pseudo3D

Câmera livre;

Modelos 3D;

Matemática robusta;

Pipeline gráfico extenso

e complexo.

(3)

3D Rendering

Operações matemáticas armazenadas em estruturas de matrizes e vetores

(4)

3D Rendering

(5)
(6)

Projeção 3D -> 2D

Projetando um ponto do universo 3D para uma tela 2D:

(x_world, y_world, z_world) -> (x_screen, y_screen)

Usando semelhança de triângulos calculamos nossa

nova coordenada y (y_screen):

y_screen = y_world * dist / z_world

(7)

Pseudo3D - Transformações

(8)

Transformação de Escala

Precisamos escalar nosso ponto de acordo com

a resolução e a orientação computacional de

(9)

Field of View

Podemos configurar o campo de visão da nossa câmera

alterando a distância entra a câmera e o plano 2D

(10)
(11)

Estrada – Estrutura de Dados

function buildRoad() segments = {}

for n = 1, 500 do -- tamanho arbitrário para estrada (n segmentos)

seg = {

index = n, -- indíce do segmento na tabela

(12)

Estrada – Estrutura de Dados

pSegment = findSegment(playerZ)["index"]

segments[pSegment + 2]["color"] = COLORS.START segments[pSegment + 3]["color"] = COLORS.START segments[pSegment + 4]["color"] = COLORS.START segments[pSegment + 5]["color"] = COLORS.START

trackLength = tablelength(segments) * segmentLength end function findSegment(z) return segments[(math.floor(z/segmentLength) % tablelength(segments)) + 1] end playerZ=cameraHeight*cameraDepth

(13)

Esquema de Cores

-- cores skyblue = {135, 206, 235} darkgray = {169, 169, 169} greenmedium = {10, 111, 10} whitesmoke = {245, 245, 245} darkgreen = { 0, 100, 0} white = {255, 255, 255} -- esquema de cores COLORS = { SKY = skyblue,

LIGHT = { road = darkgray, grass = greenmedium, lane=whitesmoke }, DARK = { road = darkgray, grass = darkgreen },

(14)

Movimentação – Update

function love.update(dt)

dx = dt * 5000 –- deslocamento no eixo x

if (keyLeft) then -- deslocar para esquerda

playerX = playerX - dx;

elseif (keyRight) then -- deloscar para direita

playerX = playerX + dx; end

if (keyFaster) then -- acelerar movimento

speed = speed + 25

elseif (keySlower) then –- desacelerar movimento

speed = speed - 25

else -- desaceleração natural

if speed > 0 then -- considerando que movimento

speed = speed – 5 -- ocorre para frente e para trás

elseif speed < 0 then speed = speed + 5 else

speed = 0 end

(15)

Movimentação – Update

speed = limit(speed, -10000, 10000) -– limita velocidade

playerX = limit(playerX, -10000, 10000) -- limita pos x

position = increase(position, dt * speed, trackLength) end

function limit(value, minValue, maxValue)

return math.max(minValue, math.min(value, maxValue)) end

function increase(start, increment, maxValue) -- com looping

result = start + increment; -- da estrada

if (result >= maxValue) then

result = result - maxValue; end

if (result < 0) then

result = result + maxValue; end

(16)

Estrada – Projeção e Draw

function love.draw()

drawRoad() -- desenha a estrada

end

function project(p, cameraX, cameraY, cameraZ, cameraDepth, width, height, roadWidth)

local proj = {} -- coordeanadas após projeção

-- translação das coordenadas de acordo com a câmera

p.camera.x = (p.world.x or 0) - cameraX p.camera.y = (p.world.y or 0) - cameraY p.camera.z = (p.world.z or 0) - cameraZ

-- escala de acordo com a posição z da ponto no mundo 3D

p.screen.scale = cameraDepth/p.camera.z

-- projeção das coodernadas (x,y) e da largura da metade do segmento

proj.x = p.screen.scale * p.camera.x proj.y = p.screen.scale * p.camera.y proj.w = p.screen.scale * roadWidth

-- escala do plano matemático normalizado para o computacional

p.screen.x = round((width/2) + (proj.x * width/2)) p.screen.y = round((height/2) - (proj.y * height/2)) p.screen.w = round((proj.w * width/2))

end

(17)

Estrada - Draw

function drawRoad()

baseSegment = findSegment(position)

-- desenha a partir do baseSegment

for n = 0, (drawDistance)-1 do

segment = segments[((baseSegment["index"] + n) % tablelength(segments)) + 1]

segment["looped"] = segment["index"] < baseSegment["index"]

-- projeta p1 na tela 2D

project(segment["p1"], playerX, cameraHeight,

position -(segment.looped and trackLength or 0), cameraDepth, width, height, roadWidth)

-- projeta p2 na tela 2D

project(segment["p2"], playerX, cameraHeight,

position - (segment.looped and trackLength or 0), cameraDepth, width, height, roadWidth)

-- desenha segmentos com p1 e p2 já projetados

drawSegment(width, lanes, segment["p1"]["screen"]["x"],

segment["p1"]["screen"]["y"], segment["p1"]["screen"]["w"], segment["p2"]["screen"]["x"], segment["p2"]["screen"]["y"], segment["p2"]["screen"]["w"], segment["color"])

end end

(18)

Estrada - Draw

function drawSegment (width, lanes, x1, y1, w1, x2, y2, w2, color)

-- desenha piso do cenário (grama) no segmento atual

love.graphics.setColor(color.grass)

love.graphics.rectangle("fill", 0, y2, width, y1 - y2)

-- desenha segmento da rua sobre a grama já desenhada

drawPolygon(x1-w1, y1, x1+w1, y1, x2+w2, y2, x2-w2,y2, color.road)

if (color.lane) then

l1 = w1 / (8*lanes) -- largura inferior da faixa

l2 = w2 / (8*lanes) -- largura superior da faixa

lane_w1 = w1*2/lanes –- largura inferior da pista

lane_w2 = w2*2/lanes –- largura superior da pista

lane_x1 = x1 - w1 + lane_w1 –- posição x inferior da pista

lane_x2 = x2 - w2 + lane_w2 –- posição x superior da pista

for lane = 1, lanes-1 do -- loop de desenho das faixas

drawPolygon(lane_x1 - l1/2, y1, lane_x1 + l1/2, y1, lane_x2 + l2/2, y2, lane_x2 - l2/2, y2, color.lane)

lane_x1 = lane_x1 + lane_w1 -- incrementa posição para

lane_x2 = lane_x2 + lane_w2 -- desenhar próxima faixa

end end end

Cor das faixas (color.lane) só está definida se o segmento possuir esquema de cor LIGHT

A variável “lanes” corresponde ao número de pistas que a estrada possuirá. A função “drawPolygon” utiliza a função

(19)

Importante - Desabilitar VSync

function love.load()

-- desabilitar VSync para evitar problemas relacionados -- à performance do jogo

love.window.setMode(width, height, {vsync=false})

(20)

Exercício 1

1) Adicione o sprite de carro ao exemplo disponibilizado e faça a animação

de acordo com a movimentação do jogador

Resources:

(21)

Curvas 3D

Rascunho de curvas em um mundo 3D

Mudança real na estrutura física dos

segmentos;

(22)

Curvas Pseudo3D

Curvas falsas em um mundo Pseudo3D

Não há mudanças na estrutura dos

segmentos, apenas na projeção;

Cálculos simples, sem nenhuma rotação

envolvida;

(23)

Curvas Pseudo3D

Curvas falsas em um mundo Pseudo3D

Como funciona?

- É necessário apenas modificar a linha

central (p1, p2) de cada segmento durante

a projeção;

- Para o efeito de curva, basta inclinar

levemente a linha central de segmentos

consecutivos criando um formato de curva.

p1

p2

(24)

Curvas Pseudo3D

Curvas falsas em um mundo Pseudo3D

Precisamos armazenar o valor que representa

a inclinação de cada segmento na nossa

estrutura de segmentos;

- Valores negativos indicam curvas à esquerda

- Valores positivos indicam curvas à direita

- Valores mais altos indicam curvas acentuadas

- Valores mais baixos indicam curvas suaves

p1

p2

(25)

Estrada – Estrutura Revisada

function addSegment(curve) –- adiciona um segmento da estrada

n = tablelength(segments) + 1; seg = {

index = n, -- index é o tamanho atual da tabela (+1 -> lua)

p1 = { world = { z = (n-1) * segmentLength }, camera = {}, screen = {} },

p2 = { world = { z = n * segmentLength }, camera = {}, screen = {} } ,

color = randomColorLightness(math.floor(n/rumbleLength)), looped = false,

curve = curve –- valor da inclinação do segmento

}

(26)

Estrada – Estrutura Revisada

-- adiciona uma porção de segmentos, suavizando curvas quando necessário

function addRoad(enter, hold, leave, curve) for n = 0, enter-1 do

-- interpola valores para suavizar entrada da curva

addSegment(interpolate(0, curve, n/enter)) end

for n = 0, hold-1 do addSegment(curve) end

for n = 0, leave-1 do

-- interpola valores para suavizar saída da curva

addSegment(interpolate(curve, 0, n/leave)) end

end

-- interpolação - adiciona ao valor inicial uma porcentagem do que resta para o valor final

function interpolate(a,b,percent) return a + (b-a)*percent

(27)

Estrada – Estrutura Revisada

-- Parâmetros de uma parcela da estrada (conjunto de segmentos)

ROAD = {

LENGTH = { NONE = 0, SHORT = 25, MEDIUM = 40, LONG = 80 }, CURVE = { NONE = 0, EASY = 2, MEDIUM = 4, HARD = 6 } }

-- Adiciona parcelas de estrada retas

function addStraight(size)

size = size or ROAD.LENGTH.MEDIUM

addRoad(size, size, size, 0) –- sem curva nessa parcela

End

-- Adiciona parcelas de estrada com curvas

function addCurve(size, curve)

size = size or ROAD.LENGTH.MEDIUM curve = curve or ROAD.CURVE.MEDIUM

addRoad(size, size, size, curve) –- parcela com curva

(28)

Estrada – Estrutura Revisada

-- Adiciona parcelas de estradas com curvas em S

function addSCurves() addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY) addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM) addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY) addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY) addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM) end

-- constrói a estrada completa adicionando as parcelas desejadas

function buildRoad() (...)

addStraight(ROAD.LENGTH.SHORT/4) –- reta pequena

addSCurves() -- curva em S

addStraight(ROAD.LENGTH.LONG) -- reta comprida

addSCurves() -- curva em S

addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.MEDIUM) -- curva à esquerda...

addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM) -- curva à direita...

(29)

Estrada – Draw Revisado

function drawRoad()

baseSegment = findSegment(position)

basePercent = percentOf (position, segmentLength) –- evita transições de segmentos bruscas

x = 0; -- deslocamento x em p1

dx = - (baseSegment["curve"] * basePercent) –- deslocamento x em p2 relativo ao x em p1

for n = 0, (drawDistance)-1 do (...)

project(segment["p1"], playerX – x, (...)) -- desloca x da câmera

project(segment["p2"], playerX - x – dx, (...)) -- em p1 e em p2 -- atualiza valores de x e dx para próximo segmento

x = x + dx

dx = dx + segment["curve"] (...)

end end

-- retorna o percentual atingido por um valor em um total

function percentOf(n, total) return (n%total)/total

(30)

Exercício 2

1) Adicione ao exemplo disponibilizado uma força centrífuga que simule o

movimento de um veículo durante uma curva

Resources:

(31)

Adicionando Sprites

Sprites 2D compatíveis com a visão da

câmera

Requer posicionamento e escala de acordo

com a perspectiva do mundo

(32)

Adicionando Sprites

Temos as informações necessárias para o desenho de sprites em cada ponto

projetado!

Podemos utilizar a escala, o x e y, e a largura da pista (w) ao projetarmos os sprites em

um ponto de um segmento

Passos para adicionar um sprite:

Armazenar em um segmento da pista

Atualizar o segmento atual do sprite (no caso de sprites móveis)

(33)

Armazenando Sprites

function addSegment(…) (…)

seg = { (…)

sprites = {}, -- contém todos os sprites fixos de um segmento

cars = {}, -- contém todos os sprites móveis de um segmento

(…) } (…) end

-- constantes relacionadas aos sprites

SPRITES = {

TREE1 = { x = 0, y = 50, w = 232, h = 153}, CAR01 = { x = 0, y = 0, w = 82, h = 46}, CAR02 = { x = 180, y = 0, w = 82, h = 46} }

(34)

Armazenando Sprites

-- função que adiciona sprites fixos a um segmento n

function addSprite(n, sprite, offset)

-- offset é o deslocamento do sprite no eixo x

sprite = {source = sprite, offset = offset}

-- insere o sprite no segmento desejado

table.insert(segments[n]["sprites"], sprite) end

-- função que popula a estrada com sprites fixos

function buildSprites()

addSprite(15, SPRITES.TREE1, -roadWidth) -- adiciona sprite TREE1 nos

addSprite(20, SPRITES.TREE1, -roadWidth) -- segmentos 15 e 20 à -- esquerda da estrada

n = 21 -- a partir do segmento 21, adiciona sprite TREE1 à esquerda -- e à direita da estrada, a cada rumbleLength * 2 segmentos

while n < tablelength(segments) do

addSprite(n, SPRITES.TREE1, -roadWidth) addSprite(n, SPRITES.TREE1, roadWidth) n = n + (rumbleLength * 2)

(35)

Armazenando Sprites

-- função que popula a estrada com sprites móveis

function buildCars()

cars = {} -- tabela auxiliar para facilitar a atualização dos sprites -- insere sprites móveis na estrada randomicamente

for n = 0, totalCars do -- totalCars equivale a qtd de sprites móveis -- randomiza um offset no eixo x para o sprite

-- OBS: randomChoice escolhe aleatóriamente um valor de uma tabela

offset = math.random() * randomChoice({-0.8*roadWidth, 0.8*roadWidth})

-- randomiza posição z do sprite

z = math.floor(math.random() * tablelength(segments))*segmentLength

-- randomiza o sprite móvel a ser usado e sua velocidade

sprite = randomChoice(SPRITES.CARS)

carspeed = maxSpeed/4 + math.random() * maxSpeed segment = findSegment(z)

car = { offset = offset, z = z, sprite = sprite, speed = carspeed, percent = 0, segment = segment}

-- insere o carro gerado na estrada e também na tabela auxiliar

table.insert(segment["cars"], car) table.insert(cars, car)

(36)

Atualizando Sprites

-- função que atualiza posição dos sprites móveis (no ex. carros)

function updateCars(dt) -- deve ser chamado no love.update()

for n = 1, tablelength(cars) do -- percorre tabela de carros auxiliar

car = cars[n]

oldSegment = car["segment"] -- segmento pré-atualização do carro -- atualiza posição z do carro de acordo com sua velocidade

car["z"] = increase(car["z"], dt * car["speed"], trackLength)

-- atualiza percentual percorrido do segmento atual

car["percent"] = percentOf(car["z"], segmentLength) -- facilita desenho

newSegment = findSegment(car["z"]) -- segmento pós-atualização

car["segment"] = newSegment -- atualiza segmento do carro -- se novo segmento é diferente do antigo

-- troca o segmento do carro na estrutura da estrada

if (oldSegment ~= newSegment) then

table.remove(oldSegment["cars"], indexOf(oldSegment["cars"], car))

-- indexOf retorna indíce de um objeto em uma tabela

table.insert(newSegment["cars"], car) end

(37)

Desenhando Sprites

-- função que desenha todos os sprites (deve ser chamada no love.draw())

function drawSprites()

-- loop em ordem contrária: algoritmo do pintor -- desenhamos o que está atrás primeiro!

n = (drawDistance-1) while n > 0 do

-- segmento atual do loop

segment = segments[((baseSegment["index"] + n) % tablelength(segments)) + 1]

(… desenha sprites fixos …) (… desenha sprites móveis …) (… desenha sprite do player …)

-- decrementa iterador do loop: algoritmo do pintor

n = n - 1 end

(38)

Desenhando Sprites

(… desenha sprites fixos …)

for i = 1 , tablelength(segment["sprites"]) do sprite = segment["sprites"][i]

spriteScale = segment["p1"]["screen"]["scale"] spriteX = segment["p1"]["screen"]["x"] +

(spriteScale * sprite["offset"] * width/2) spriteY = segment["p1"]["screen"]["y"]

drawSprite(width, height, resolution, roadWidth, sprites,

sprite["source"], spriteScale, spriteX, spriteY, (sprite["offset"] < 0 and -1 or 0), -1)

(39)

Desenhando Sprites

(… desenha sprites móveis …)

for i = 1 , tablelength(segment["cars"]) do car = segment["cars"][i] sprite = car["sprite"] spriteScale = interpolate(segment["p1"]["screen"]["scale"], segment["p2"]["screen"]["scale"], car["percent"]) spriteX = interpolate(segment["p1"]["screen"]["x"], segment["p2"]["screen"]["x"], car["percent"]) + (spriteScale * car["offset"] * width/2)

spriteY = interpolate(segment["p1"]["screen"]["y"], segment["p2"]["screen"]["y"], car["percent"]) drawSprite(width, height, resolution, roadWidth, sprites,

(40)

Desenhando Sprites

(… desenha sprite do player …)

if (segment == playerSegment) then

drawSprite(width, height, resolution, roadWidth, sprites, SPRITES.CAR02, cameraDepth/playerZ, width/2, (height/2), -0.5, 1); end

-- atualizado dentro do love.update()

playerSegment = findSegment(position+playerZ)

-- position + playerZ eqvuiale à:

(41)

Desenhando Sprites

-- função que desenha sprites aplicando escalas e offsets -- de acordo com parâmetros passados

function drawSprite(width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY, offsetX, offsetY) destW = (sprite.w * scale * width) * SPRITES.SCALE

destH = (sprite.h * scale * width) * SPRITES.SCALE destX = destX + (destW * offsetX)

destY = destY + (destH * offsetY)

quad = love.graphics.newQuad(sprite.x, sprite.y, sprite.w, sprite.h, sprites:getDimensions())

love.graphics.draw(sprites, quad, destX, destY, 0, (destW)/sprite.w, (destH )/sprite.h)

(42)

Collision Check

n segmentos

Basta checar um número de segmentos no

entorno do segmento atual do player

Se em algum dos segmentos checados

houver sprites, precisamos apenas verificar

se houve colisão no eixo x

(43)

Exercícios

1) Implemente a detecção de colisões entre o player e os sprites (fixos e

móveis) da estrada. Utilize o exemplo disponibilizado como base.

OBS: o exemplo disponibilizado possui uma função pronta para checar

colisão no eixo x (function overlap) que recebe o x e o width de dois sprites e retorna “true”

caso haja colisão. Lembre-se que o width é multiplicado pelo SPRITES.SCALE

Resources:

(44)

Leitura Complementar

Lou's Pseudo 3d Page

Referências

Documentos relacionados

A partir desses resultados da primeira etapa, foram selecionados alguns suplementos para análise.. Os suplementos foram classificados de acordo com o uso, ou seja, em

Nos processos nos quais os compostos reduzidos de enxofre são utilizados como fonte de elétrons, a velocidade de crescimento dos microrganismos envolvidos é maior do que

Two main findings emerge: first, per capita agrarian output was similar in the recently conquered South and the North, hinting that internal mi- gration after the Reconquista led to

Nos 14 municípios da zona sul analisados, a agricultura familiar, considerando a mão-de-obra utilizada por unidade de área, é mais intensiva, pela ordem, em municípios de Rio

Mais tarde, em 1992, foi instalado um espectrofotómetro Brewer no Observatório José Agostinho Fig.1 em Angra do Heroísmo na ilha Terceira Açores, o qual teve uma operação

Os resultados para o índice POD, durante a pré-estação chuvosa, indicam que o modelo possui habilidade entre média e alta para capturar eventos de acumulados

Após os preparos, os dentes foram restaurados com diferentes tipos de resina composta (química e fotoativada), diferentes tipos de sistemas adesivos (para dentina e para esmalte)

After 21 days, intraperitoneally vaccinated fishes showed the highest values of total protein and immunoglobulins, agglutination titer and serum antimicrobial