Slide 46 de 53

Três dimensões

Uma cena 3D, em uma window
type Msg { Tick }

func view(model: Int): hive.ui.View {
	shapes := [
		hive.ui.ground([hive.ui.paint(hive.ui.Tone.HEX("#2d5c34"))], 20.0, 20.0),
		hive.ui.box(
			[
				hive.ui.at(0.0, 0.5, -3.0),
				hive.ui.paint(hive.ui.Tone.HEX("#b5651d")),
				hive.ui.surface(hive.ui.Surface.Planks()),
			],
			1.0, 1.0, 1.0,
		),
		hive.ui.sphere([hive.ui.at(2.0, 1.0, -4.0), hive.ui.paint(hive.ui.Tone.HEX("#e0c341"))], 1.0),
		// Um cone recebe os dois raios: zero no topo é o pontudo.
		hive.ui.cone(
			[
				hive.ui.at(-2.0, 0.4, -3.0),
				hive.ui.paint(hive.ui.Tone.HEX("#ff5a1f")),
				hive.ui.surface(hive.ui.Surface.Gloss()),
			],
			0.0, 0.4, 0.8,
		),
		// E uma cor com alpha é desenhada translúcida.
		hive.ui.box(
			[hive.ui.at(0.0, 1.2, -1.5), hive.ui.paint(hive.ui.Tone.RGBA(120, 200, 255, 96))],
			3.0, 2.0, 0.2,
		),
	]
	return hive.ui.scene(
		[hive.ui.grow(1), hive.ui.eye(0.0, 1.6, 4.0), hive.ui.aim(0.0, 0.0), hive.ui.lens(70), hive.ui.fog(60)],
		shapes,
	)
}

proc update(model: Int, msg: Msg, envelope: hive.syslink.Envelope): Int {
	return model
}

proc main(): void {
	hive.ui.window("Uma cena 3D", view, update, 0)
}
Animando, e reagindo a um frame
type Msg {
	Tick { ms: Int }
}

func view(angle: Float): hive.ui.View {
	return hive.ui.scene(
		[
			hive.ui.grow(1),
			hive.ui.eye(0.0, 1.5, 5.0),
			hive.ui.aim(0.0, 0.0),
			hive.ui.onFrame(Msg.Tick(_)),
		],
		[
			hive.ui.ground([hive.ui.paint(hive.ui.Tone.Muted())], 10.0, 10.0),
			hive.ui.box(
				[hive.ui.at(0.0, 1.0, 0.0), hive.ui.turn(0.0, angle, 0.0), hive.ui.paint(hive.ui.Tone.Good())],
				1.0, 1.0, 1.0,
			),
		],
	)
}

// O onFrame de uma scene reporta quantos milissegundos o último frame levou,
// então o giro que esta caixa faz é em segundos reais, não em frames — a
// mesma caixa gira na mesma velocidade a trinta quadros por segundo ou a sessenta.
proc spin(angle: Float, msg: Msg, envelope: hive.syslink.Envelope): Float {
	if msg is Msg.Tick(ms) {
		return angle + hive.conv.itf(ms) * 0.002
	}
	return angle
}

proc main(): void {
	hive.ui.window("Uma caixa girando", view, spin, 0.0)
}
Dirigindo com um gamepad
type Msg {
	Tick { ms: Int }
	Padded { pad: Int, control: Str, at: Float }
}

type Kart {
	x:        Float
	z:        Float
	steer:    Float
	throttle: Float
}

func view(kart: Kart): hive.ui.View {
	return hive.ui.scene(
		[
			hive.ui.grow(1),
			hive.ui.eye(kart.x, 4.0, kart.z + 9.0),
			hive.ui.aim(0.0, -0.25),
			hive.ui.lens(70),
			hive.ui.onFrame(Msg.Tick(_)),
			hive.ui.onPad(Msg.Padded(_, _, _)),
		],
		[
			hive.ui.ground(
				[hive.ui.paint(hive.ui.Tone.HEX("#61686f")), hive.ui.surface(hive.ui.Surface.Tarmac())],
				120.0, 120.0,
			),
			hive.ui.box(
				[
					hive.ui.at(kart.x, 0.4, kart.z),
					hive.ui.paint(hive.ui.Tone.HEX("#00d2be")),
					hive.ui.surface(hive.ui.Surface.Gloss()),
				],
				1.6, 0.6, 3.4,
			),
		],
	)
}

// Um analógico e um gatilho chegam pelo mesmo buraco e são distinguidos pelo
// nome, porque um botão é um comando que só é 0 ou 1. Nada é guardado aqui
// que o controle não tenha dito: onPad reporta o que *mudou*, então o modelo
// lembra a última posição de cada comando que lhe interessa.
proc drive(kart: Kart, msg: Msg, envelope: hive.syslink.Envelope): Kart {
	mut Kart it = kart
	if msg is Msg.Padded(pad, control, at) {
		if control == "leftX" { it.steer = at }
		if control == "rt" { it.throttle = at }
		return it
	}
	if msg is Msg.Tick(ms) {
		step := hive.conv.itf(ms) / 1000.0
		it.x = it.x + it.steer * it.throttle * 16.0 * step
		it.z = it.z - it.throttle * 16.0 * step
	}
	return it
}

proc main(): void {
	hive.ui.window("Um controle", view, drive, Kart(0.0, 0.0, 0.0, 0.0))
}
Uma scene é o único widget que somente uma window consegue desenhar, e é um módulo à parte — hive.ui.scene — porque é a única coisa na biblioteca padrão que carrega um download: um three.js fixado, buscado na primeira compilação que precisa dele e guardado em cache dali em diante. Uma window que não desenha nenhuma scene não linka nada disso. Uma scene é um valor como qualquer outra view: uma câmera, um céu e um vetor de formas, reconstruído por um func comum a partir do estado tantas vezes quantos forem os frames. Nada aqui é um handle — nenhuma malha para guardar, nenhum material para alocar, nenhum canvas para segurar — então um programa responde o que está ali agora, e nunca o que mudou.box, sphere, cylinder, cone, ground, label e line são as formas, e cada uma recebe suas dimensões como carga e não como atributos, porque elas são o que a forma é: uma esfera sem raio não é uma esfera com um raio padrão. cone recebe os seus dois raios em vez de só o da base — um cone no mundo quase nunca é o perfeito, um cone de sinalização é rombudo e o bico de um carro termina em lâmina, e uma forma que só pudesse ser a perfeita acaba sendo contornada em vez de usada. Zero no topo é a perfeita; um topo igual à base é um cilindro. ground já está deitado, então um chão não custa rotação nenhuma e turn continua significando em todo lugar o que significa em qualquer lugar; label sempre encara quem olha, porque uma que só dá para ler de um lado não é uma label; e line não recebe posição alguma, já que uma linha fica entre dois lugares e não tem um terceiro para chamar de seu.at, turn e paint posicionam e colorem uma forma. surface diz do que ela é feita, que é uma pergunta diferente de qual é a cor dela, e o conjunto se lê do chão para cima: aquilo em que se anda — Turf, Gravel, Tarmac, Kerb — depois aquilo de que as coisas são feitas — Concrete, Brick, Planks, Staves, Bark, Leaves, Crowd, Mesh — e depois o acabamento delas: Speckle, Metal, Carbon, Rubber, Glass e Gloss. Cada uma é desenhada por cima da cor que a forma já tem — então um padrão só serve para um zebrado vermelho e para um azul, e Gloss não acrescenta padrão nenhum. Onde a forma não diz nada, o tipo dela decide: um ground é Turf, um cylinder ou um cone é Staves, uma sphere é Speckle, e todo o resto é Planks. E uma cor que carrega alpha é translúcida — Tone.RGBA(r, g, b, a), ou um Tone.HEX de oito dígitos — desenhada vazada e sem escrever profundidade, que é como se desenha um fantasma, um vidro colorido ou o rastro atrás de alguma coisa.eye, aim, lens, fog, background, grab e crosshair vão na própria scene, e não em uma forma: onde a câmera está e para onde olha, quão aberta é a lente, até onde se enxerga, o céu no qual a névoa se dissolve, e se a window deve segurar o mouse. Iluminação deliberadamente não é algo que uma scene descreve — o conjunto é fixo, escolhido para que um sólido pintado de uma cor saia daquela cor com um lado sombreado — então o que deixa uma scene escura é a paleta dela, e não uma configuração abaixada. Os eventos são só da scene: onFrame reporta quantos milissegundos o último frame levou, onKeyDown e onKeyUp o nome de uma tecla, onLook o quanto o mouse andou, onGrab se a window está com o ponteiro, e onPad um gamepad.Um gamepad é a única entrada sobre a qual um navegador não avisa: não existe evento para um analógico se mexer, só um retrato que você precisa pedir. Então o runtime lê os controles uma vez por frame e publica o que mudou — o que faz de um botão de controle a mesma coisa que uma tecla sendo pressionada, em vez de um segundo jeito de ouvir sobre entrada. onPad reporta três coisas: qual controle, qual comando dele, e onde esse comando está agora. Uma forma só cobre o aparelho inteiro, porque um botão é um comando que só é 0 ou 1. leftX, leftY, rightX e rightY são os analógicos; lt e rt os gatilhos; a, b, x, y, lb, rb, up, down, left, right, back, start, guide, lstick e rstick os botões; e qualquer coisa além disso é reportada por número. here é o próprio controle: 1 quando ele chega, 2 quando chega com um layout que o navegador não soube nomear, e 0 quando ele vai embora — e um controle desconectado solta antes tudo que estava segurando, então um carro cujo controle ficou sem bateria no meio da curva não continua virando. Uma zona morta é aplicada antes de o evento ser enviado, e nada é enviado até um comando ter de fato se mexido, porque um analógico parado nunca está totalmente parado.Uma scene guarda mais uma forma além das sete acima, e essa é ouvida em vez de desenhada: ela tem o slide seguinte só para si.Uma página servida renderiza a caixa de uma scene vazia — não há socket por onde os frames viajem — então os exemplos abaixo são mostrados apenas como código: abra um deles em uma window para realmente ver.